forked from datawhale/whale-town-end
style(login_core): 优化login_core模块代码规范
范围:src/core/login_core/ - 提取魔法数字为常量,提升代码可维护性 - 拆分过长方法,提升代码可读性 - 消除代码重复,优化方法结构 - 添加详细的类和方法注释 - 统一JWT配置常量管理 - 清理TODO项和未使用代码
This commit is contained in:
@@ -1,157 +1,157 @@
|
||||
# LoginCore 登录核心模块
|
||||
|
||||
LoginCore 是应用的用户认证核心模块,提供完整的用户登录、注册、密码管理和邮箱验证功能,支持多种认证方式包括密码登录、验证码登录和 GitHub OAuth 登录。
|
||||
LoginCore 是 Whale Town 游戏服务器的用户认证核心模块,提供完整的用户登录、注册、密码管理、邮箱验证和JWT令牌管理功能,支持多种认证方式包括密码登录、验证码登录和 GitHub OAuth 登录。
|
||||
|
||||
## 认证相关
|
||||
## 对外提供的接口
|
||||
|
||||
### login()
|
||||
支持用户名/邮箱/手机号的密码登录
|
||||
- 支持多种登录标识符(用户名、邮箱、手机号)
|
||||
- 密码哈希验证
|
||||
- 用户状态检查
|
||||
- OAuth用户检测
|
||||
支持用户名/邮箱/手机号的密码登录,验证用户身份并返回认证结果。
|
||||
|
||||
### verificationCodeLogin()
|
||||
使用邮箱或手机验证码登录
|
||||
- 邮箱验证码登录(需邮箱已验证)
|
||||
- 手机验证码登录
|
||||
- 自动清除验证码冷却时间
|
||||
使用邮箱或手机验证码登录,提供无密码认证方式。
|
||||
|
||||
### githubOAuth()
|
||||
GitHub OAuth 第三方登录
|
||||
- 现有用户信息更新
|
||||
- 新用户自动注册
|
||||
- 用户名冲突自动处理
|
||||
|
||||
## 注册相关
|
||||
GitHub OAuth 第三方登录,支持新用户注册和现有用户信息更新。
|
||||
|
||||
### register()
|
||||
用户注册,支持邮箱验证
|
||||
- 用户名、邮箱、手机号唯一性检查
|
||||
- 邮箱验证码验证(可选)
|
||||
- 密码强度验证
|
||||
- 自动发送欢迎邮件
|
||||
|
||||
## 密码管理
|
||||
用户注册功能,支持邮箱验证和用户唯一性检查。
|
||||
|
||||
### changePassword()
|
||||
修改用户密码
|
||||
- 旧密码验证
|
||||
- 新密码强度检查
|
||||
- OAuth用户保护
|
||||
修改用户密码,验证旧密码并设置新密码。
|
||||
|
||||
### resetPassword()
|
||||
通过验证码重置密码
|
||||
- 验证码验证
|
||||
- 新密码强度检查
|
||||
- 自动清除验证码冷却
|
||||
通过验证码重置密码,支持忘记密码场景。
|
||||
|
||||
### sendPasswordResetCode()
|
||||
发送密码重置验证码
|
||||
- 邮箱/手机号用户查找
|
||||
- 邮箱验证状态检查
|
||||
- 验证码生成和发送
|
||||
|
||||
## 邮箱验证
|
||||
发送密码重置验证码到用户邮箱或手机。
|
||||
|
||||
### sendEmailVerification()
|
||||
发送邮箱验证码
|
||||
- 邮箱重复注册检查
|
||||
- 验证码生成和发送
|
||||
- 测试模式支持
|
||||
发送邮箱验证码,用于邮箱验证和注册流程。
|
||||
|
||||
### verifyEmailCode()
|
||||
验证邮箱验证码
|
||||
- 验证码验证
|
||||
- 用户邮箱验证状态更新
|
||||
- 自动发送欢迎邮件
|
||||
验证邮箱验证码,完成邮箱验证流程。
|
||||
|
||||
### resendEmailVerification()
|
||||
重新发送邮箱验证码
|
||||
- 用户存在性检查
|
||||
- 邮箱验证状态检查
|
||||
- 防重复验证
|
||||
|
||||
## 登录验证码
|
||||
重新发送邮箱验证码,处理验证码丢失情况。
|
||||
|
||||
### sendLoginVerificationCode()
|
||||
发送登录用验证码
|
||||
- 用户存在性验证
|
||||
- 邮箱验证状态检查
|
||||
- 支持邮箱和手机号
|
||||
发送登录用验证码,支持验证码登录方式。
|
||||
|
||||
## 辅助功能
|
||||
### generateTokenPair()
|
||||
生成JWT访问令牌和刷新令牌对,用于用户会话管理。
|
||||
|
||||
### verifyToken()
|
||||
验证JWT令牌有效性,支持访问令牌和刷新令牌验证。
|
||||
|
||||
### refreshAccessToken()
|
||||
使用刷新令牌生成新的访问令牌,实现无感知令牌续期。
|
||||
|
||||
### deleteUser()
|
||||
删除用户(用于回滚操作)
|
||||
- 用户存在性验证
|
||||
- 安全删除操作
|
||||
- 异常处理
|
||||
删除用户记录,用于注册失败时的回滚操作。
|
||||
|
||||
### debugVerificationCode()
|
||||
调试验证码信息
|
||||
- 验证码状态查询
|
||||
- 开发调试支持
|
||||
调试验证码信息,用于开发环境调试。
|
||||
|
||||
## 使用的项目内部依赖
|
||||
|
||||
### UsersService (来自 core/db/users)
|
||||
用户数据访问服务,提供用户的增删改查操作和唯一性验证。
|
||||
|
||||
### EmailService (来自 core/utils/email)
|
||||
邮件发送服务,用于发送验证码邮件、欢迎邮件和密码重置邮件。
|
||||
|
||||
### VerificationService (来自 core/utils/verification)
|
||||
验证码管理服务,提供验证码生成、验证、冷却时间管理等功能。
|
||||
|
||||
### JwtService (来自 @nestjs/jwt)
|
||||
JWT令牌服务,用于生成和验证JWT访问令牌。
|
||||
|
||||
### ConfigService (来自 @nestjs/config)
|
||||
配置管理服务,用于获取JWT密钥、过期时间等配置信息。
|
||||
|
||||
### UserStatus (来自 core/db/users/user_status.enum)
|
||||
用户状态枚举,定义用户的激活、禁用、待验证等状态值。
|
||||
|
||||
### VerificationCodeType (来自 core/utils/verification)
|
||||
验证码类型枚举,区分邮箱验证、短信验证、密码重置等不同用途。
|
||||
|
||||
## 核心特性
|
||||
|
||||
### JWT令牌管理
|
||||
- 生成访问令牌和刷新令牌对,支持Bearer认证
|
||||
- 令牌签名验证,包含签发者和受众验证
|
||||
- 自动令牌刷新机制,实现无感知续期
|
||||
- 支持自定义过期时间配置(默认7天访问令牌,30天刷新令牌)
|
||||
- 令牌载荷包含用户ID、用户名、角色等关键信息
|
||||
|
||||
### 多种认证方式
|
||||
- 支持密码、验证码、OAuth 三种登录方式
|
||||
- 灵活的认证策略选择
|
||||
- 统一的认证结果格式
|
||||
|
||||
### 灵活的登录标识
|
||||
- 支持用户名、邮箱、手机号登录
|
||||
- 自动识别标识符类型
|
||||
- 统一的查找逻辑
|
||||
|
||||
### 完整的用户生命周期
|
||||
- 从注册到登录的完整流程
|
||||
- 邮箱验证和用户激活
|
||||
- 密码管理和重置
|
||||
- 密码认证:支持用户名、邮箱、手机号登录
|
||||
- 验证码认证:支持邮箱和短信验证码登录
|
||||
- OAuth认证:支持GitHub第三方登录
|
||||
- 统一的认证结果格式和异常处理
|
||||
|
||||
### 安全性保障
|
||||
- 密码哈希存储(bcrypt,12轮盐值)
|
||||
- 用户状态检查
|
||||
- 验证码冷却机制
|
||||
- OAuth用户保护
|
||||
- 密码强度验证(最少8位,包含字母和数字)
|
||||
- 用户状态检查,防止禁用用户登录
|
||||
- 验证码冷却机制,防止频繁发送
|
||||
- OAuth用户保护,防止密码操作
|
||||
|
||||
### 完整的用户生命周期
|
||||
- 用户注册:支持邮箱验证和唯一性检查
|
||||
- 邮箱验证:发送验证码和验证流程
|
||||
- 密码管理:修改密码和重置密码
|
||||
- 用户激活:自动发送欢迎邮件
|
||||
|
||||
### 灵活的验证码系统
|
||||
- 支持邮箱和短信验证码
|
||||
- 多种验证码用途(注册、登录、密码重置)
|
||||
- 验证码冷却时间管理
|
||||
- 测试模式支持,便于开发调试
|
||||
|
||||
### 异常处理完善
|
||||
- 详细的错误分类和异常处理
|
||||
- 详细的错误分类和业务异常
|
||||
- 用户友好的错误信息
|
||||
- 业务逻辑异常捕获
|
||||
|
||||
### 测试覆盖完整
|
||||
- 15个测试用例,覆盖所有核心功能
|
||||
- Mock外部依赖,确保单元测试独立性
|
||||
- 异常情况和边界条件测试
|
||||
- 完整的参数验证和边界检查
|
||||
- 安全的异常信息,不泄露敏感数据
|
||||
|
||||
## 潜在风险
|
||||
|
||||
### 验证码安全
|
||||
### JWT令牌安全风险
|
||||
- 令牌泄露可能导致身份冒用
|
||||
- 刷新令牌有效期较长(30天)
|
||||
- 建议实施令牌黑名单机制
|
||||
- 缓解措施:HTTPS传输、安全存储、定期轮换
|
||||
|
||||
### 验证码安全风险
|
||||
- 验证码在测试模式下会输出到控制台
|
||||
- 生产环境需确保安全传输
|
||||
- 建议实施验证码加密传输
|
||||
- 邮件传输可能被拦截
|
||||
- 验证码重放攻击风险
|
||||
- 缓解措施:加密传输、一次性使用、时间限制
|
||||
|
||||
### 密码强度
|
||||
- 当前密码验证规则相对简单(8位+字母数字)
|
||||
- 可能需要更严格的密码策略
|
||||
- 建议增加特殊字符要求
|
||||
### 密码安全风险
|
||||
- 当前密码策略相对简单(8位+字母数字)
|
||||
- 缺少特殊字符和大小写要求
|
||||
- 密码重置可能被滥用
|
||||
- 缓解措施:增强密码策略、多因素认证、操作日志
|
||||
|
||||
### 频率限制
|
||||
- 依赖 VerificationService 的频率限制
|
||||
- 需确保该服务正常工作
|
||||
- 建议增加备用限制机制
|
||||
### 用户枚举风险
|
||||
- 登录失败信息可能泄露用户存在性
|
||||
- 注册接口可能被用于用户枚举
|
||||
- 密码重置可能泄露用户信息
|
||||
- 缓解措施:统一错误信息、频率限制、验证码保护
|
||||
|
||||
### 用户状态管理
|
||||
- 用户状态变更可能影响登录
|
||||
- 需要完善的状态管理机制
|
||||
- 建议增加状态变更日志
|
||||
### 第三方依赖风险
|
||||
- GitHub OAuth 依赖外部服务可用性
|
||||
- 邮件服务依赖第三方提供商
|
||||
- 数据库连接异常影响认证
|
||||
- 缓解措施:服务降级、重试机制、监控告警
|
||||
|
||||
### 第三方依赖
|
||||
- GitHub OAuth 依赖外部服务
|
||||
- 需要处理网络异常情况
|
||||
- 建议增加重试和降级机制
|
||||
### 并发安全风险
|
||||
- 用户名冲突处理可能存在竞态条件
|
||||
- 验证码并发验证可能导致状态不一致
|
||||
- 令牌刷新并发可能产生多个有效令牌
|
||||
- 缓解措施:数据库锁、原子操作、幂等性设计
|
||||
|
||||
## 使用示例
|
||||
|
||||
@@ -184,17 +184,45 @@ const oauthResult = await loginCoreService.githubOAuth({
|
||||
nickname: 'GitHub用户',
|
||||
email: 'user@example.com'
|
||||
});
|
||||
|
||||
// 生成JWT令牌对
|
||||
const tokenPair = await loginCoreService.generateTokenPair(user);
|
||||
console.log(tokenPair.access_token); // JWT访问令牌
|
||||
console.log(tokenPair.refresh_token); // JWT刷新令牌
|
||||
|
||||
// 验证JWT令牌
|
||||
const payload = await loginCoreService.verifyToken(accessToken, 'access');
|
||||
console.log(payload.sub); // 用户ID
|
||||
console.log(payload.username); // 用户名
|
||||
|
||||
// 刷新访问令牌
|
||||
const newTokenPair = await loginCoreService.refreshAccessToken(refreshToken);
|
||||
|
||||
// 发送邮箱验证码
|
||||
const verificationResult = await loginCoreService.sendEmailVerification(
|
||||
'user@example.com',
|
||||
'用户昵称'
|
||||
);
|
||||
|
||||
// 修改密码
|
||||
const updatedUser = await loginCoreService.changePassword(
|
||||
userId,
|
||||
'oldPassword',
|
||||
'newPassword123'
|
||||
);
|
||||
```
|
||||
|
||||
## 依赖服务
|
||||
|
||||
- **UsersService**: 用户数据访问服务
|
||||
- **EmailService**: 邮件发送服务
|
||||
- **VerificationService**: 验证码管理服务
|
||||
- **UsersService**: 用户数据访问服务,提供用户增删改查和唯一性验证
|
||||
- **EmailService**: 邮件发送服务,用于验证码邮件和欢迎邮件发送
|
||||
- **VerificationService**: 验证码管理服务,提供验证码生成、验证和冷却管理
|
||||
- **JwtService**: JWT令牌服务,用于令牌生成和验证
|
||||
- **ConfigService**: 配置管理服务,提供JWT密钥和过期时间配置
|
||||
|
||||
## 版本信息
|
||||
|
||||
- **版本**: 1.0.1
|
||||
- **版本**: 1.1.0
|
||||
- **作者**: moyin
|
||||
- **创建时间**: 2025-12-17
|
||||
- **最后修改**: 2025-01-07
|
||||
- **最后修改**: 2026-01-12
|
||||
151
src/core/login_core/login_core.module.spec.ts
Normal file
151
src/core/login_core/login_core.module.spec.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { LoginCoreService } from './login_core.service';
|
||||
import { UsersService } from '../db/users/users.service';
|
||||
import { EmailService } from '../utils/email/email.service';
|
||||
import { VerificationService } from '../utils/verification/verification.service';
|
||||
|
||||
describe('LoginCoreModule', () => {
|
||||
let module: TestingModule;
|
||||
let loginCoreService: LoginCoreService;
|
||||
let configService: ConfigService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockConfigService = {
|
||||
get: jest.fn((key: string, defaultValue?: any) => {
|
||||
switch (key) {
|
||||
case 'JWT_SECRET':
|
||||
return 'test-jwt-secret-key';
|
||||
case 'JWT_EXPIRES_IN':
|
||||
return defaultValue || '7d';
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
const mockUsersService = {
|
||||
findByUsername: jest.fn(),
|
||||
findByEmail: jest.fn(),
|
||||
findAll: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
findByGithubId: jest.fn(),
|
||||
};
|
||||
|
||||
const mockEmailService = {
|
||||
sendVerificationCode: jest.fn(),
|
||||
sendWelcomeEmail: jest.fn(),
|
||||
};
|
||||
|
||||
const mockVerificationService = {
|
||||
generateCode: jest.fn(),
|
||||
verifyCode: jest.fn(),
|
||||
clearCooldown: jest.fn(),
|
||||
debugCodeInfo: jest.fn(),
|
||||
};
|
||||
|
||||
const mockJwtService = {
|
||||
signAsync: jest.fn(),
|
||||
verifyAsync: jest.fn(),
|
||||
};
|
||||
|
||||
module = await Test.createTestingModule({
|
||||
providers: [
|
||||
LoginCoreService,
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: mockConfigService,
|
||||
},
|
||||
{
|
||||
provide: 'UsersService',
|
||||
useValue: mockUsersService,
|
||||
},
|
||||
{
|
||||
provide: EmailService,
|
||||
useValue: mockEmailService,
|
||||
},
|
||||
{
|
||||
provide: VerificationService,
|
||||
useValue: mockVerificationService,
|
||||
},
|
||||
{
|
||||
provide: JwtService,
|
||||
useValue: mockJwtService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
loginCoreService = module.get<LoginCoreService>(LoginCoreService);
|
||||
configService = module.get<ConfigService>(ConfigService);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (module) {
|
||||
await module.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(module).toBeDefined();
|
||||
});
|
||||
|
||||
describe('Service Providers', () => {
|
||||
it('should provide LoginCoreService', () => {
|
||||
expect(loginCoreService).toBeDefined();
|
||||
expect(loginCoreService).toBeInstanceOf(LoginCoreService);
|
||||
});
|
||||
|
||||
it('should provide ConfigService', () => {
|
||||
expect(configService).toBeDefined();
|
||||
// ConfigService is mocked, so we check if it has the expected methods
|
||||
expect(configService.get).toBeDefined();
|
||||
expect(typeof configService.get).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('JWT Configuration', () => {
|
||||
it('should have access to JWT configuration', () => {
|
||||
// Test that the mock ConfigService can provide JWT configuration
|
||||
const jwtSecret = configService.get('JWT_SECRET');
|
||||
const jwtExpiresIn = configService.get('JWT_EXPIRES_IN', '7d');
|
||||
|
||||
expect(jwtSecret).toBe('test-jwt-secret-key');
|
||||
expect(jwtExpiresIn).toBe('7d');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Module Dependencies', () => {
|
||||
it('should import required modules', () => {
|
||||
expect(module).toBeDefined();
|
||||
expect(loginCoreService).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not have circular dependencies', () => {
|
||||
expect(module).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Module Exports', () => {
|
||||
it('should export LoginCoreService', () => {
|
||||
expect(loginCoreService).toBeDefined();
|
||||
expect(loginCoreService).toBeInstanceOf(LoginCoreService);
|
||||
});
|
||||
|
||||
it('should make LoginCoreService available for injection', () => {
|
||||
const service = module.get<LoginCoreService>(LoginCoreService);
|
||||
expect(service).toBe(loginCoreService);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Configuration Validation', () => {
|
||||
it('should validate JWT configuration completeness', () => {
|
||||
// Test that all required configuration keys are accessible
|
||||
expect(configService.get('JWT_SECRET')).toBeDefined();
|
||||
expect(configService.get('JWT_EXPIRES_IN', '7d')).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -18,12 +18,13 @@
|
||||
* - LoginCoreService: 登录核心业务逻辑服务
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-12: 代码规范优化 - 提取JWT配置魔法字符串为常量 (修改者: moyin)
|
||||
* - 2026-01-07: 架构优化 - 添加JWT服务支持,将JWT技术实现从Business层移到Core层
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.2
|
||||
* @version 1.1.0
|
||||
* @since 2025-12-17
|
||||
* @lastModified 2026-01-07
|
||||
* @lastModified 2026-01-12
|
||||
*/
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
@@ -34,6 +35,11 @@ import { UsersModule } from '../db/users/users.module';
|
||||
import { EmailModule } from '../utils/email/email.module';
|
||||
import { VerificationModule } from '../utils/verification/verification.module';
|
||||
|
||||
// JWT配置常量
|
||||
const DEFAULT_JWT_EXPIRES_IN = '7d'; // 默认JWT过期时间
|
||||
const JWT_ISSUER = 'whale-town'; // JWT签发者
|
||||
const JWT_AUDIENCE = 'whale-town-users'; // JWT受众
|
||||
|
||||
/**
|
||||
* 登录核心模块类
|
||||
*
|
||||
@@ -61,13 +67,13 @@ import { VerificationModule } from '../utils/verification/verification.module';
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const expiresIn = configService.get<string>('JWT_EXPIRES_IN', '7d');
|
||||
const expiresIn = configService.get<string>('JWT_EXPIRES_IN', DEFAULT_JWT_EXPIRES_IN);
|
||||
return {
|
||||
secret: configService.get<string>('JWT_SECRET'),
|
||||
signOptions: {
|
||||
expiresIn: expiresIn as any, // JWT库支持字符串格式如 '7d'
|
||||
issuer: 'whale-town',
|
||||
audience: 'whale-town-users',
|
||||
issuer: JWT_ISSUER,
|
||||
audience: JWT_AUDIENCE,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -31,11 +31,15 @@
|
||||
* - VerificationService: 验证码管理服务
|
||||
*
|
||||
* 测试用例统计:
|
||||
* - 总计:15个测试用例
|
||||
* - 总计:32个测试用例
|
||||
* - login: 4个测试(成功登录、用户不存在、密码错误、用户状态)
|
||||
* - register: 4个测试(成功注册、邮箱验证、异常处理、密码验证)
|
||||
* - githubOAuth: 2个测试(现有用户、新用户)
|
||||
* - 密码管理: 5个测试(重置、修改、验证码发送等)
|
||||
* - sendPasswordResetCode: 2个测试(成功发送、用户不存在)
|
||||
* - resetPassword: 4个测试(成功重置、冷却清理、异常处理、验证码错误)
|
||||
* - changePassword: 2个测试(成功修改、旧密码错误)
|
||||
* - sendLoginVerificationCode: 4个测试(成功发送、测试模式、未验证邮箱、用户不存在)
|
||||
* - verificationCodeLogin: 10个测试(邮箱登录、手机登录、冷却清理、异常处理等)
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-08: 架构分层优化 - 修正导入路径,从Core层直接导入UserStatus枚举 (修改者: moyin)
|
||||
|
||||
@@ -12,15 +12,16 @@
|
||||
* - 为business层提供可复用的服务
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-12: 代码规范优化 - 提取魔法数字为常量,拆分过长方法,消除代码重复 (修改者: moyin)
|
||||
* - 2026-01-12: 代码规范优化 - 添加LoginCoreService类注释,完善类职责和方法说明 (修改者: moyin)
|
||||
* - 2026-01-12: 代码规范优化 - 处理TODO项,移除短信发送相关的TODO注释 (修改者: moyin)
|
||||
* - 2025-01-07: 代码规范优化 - 清理未使用的导入(EmailSendResult, crypto)
|
||||
* - 2025-01-07: 代码规范优化 - 修复常量命名(saltRounds -> SALT_ROUNDS)
|
||||
* - 2025-01-07: 代码规范优化 - 删除未使用的私有方法(generateVerificationCode)
|
||||
* - 2025-01-07: 代码质量提升 - 确保完全符合项目命名规范和注释规范
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @version 1.1.0
|
||||
* @since 2025-12-17
|
||||
* @lastModified 2025-01-07
|
||||
* @lastModified 2026-01-12
|
||||
*/
|
||||
|
||||
import { Injectable, UnauthorizedException, ConflictException, NotFoundException, BadRequestException, ForbiddenException, Inject } from '@nestjs/common';
|
||||
@@ -158,6 +159,57 @@ export interface VerificationCodeLoginRequest {
|
||||
verificationCode: string;
|
||||
}
|
||||
|
||||
// 常量定义
|
||||
const SALT_ROUNDS = 12; // 密码哈希盐值轮数
|
||||
const MIN_PASSWORD_LENGTH = 8; // 密码最小长度
|
||||
const MAX_PASSWORD_LENGTH = 128; // 密码最大长度
|
||||
const REFRESH_TOKEN_EXPIRES_IN = '30d'; // 刷新令牌过期时间
|
||||
const DEFAULT_ACCESS_TOKEN_EXPIRES_DAYS = 7; // 默认访问令牌过期天数
|
||||
const USERNAME_CONFLICT_MAX_ATTEMPTS = 100; // 用户名冲突处理最大尝试次数
|
||||
const DEFAULT_USER_ROLE = 1; // 默认用户角色(普通用户)
|
||||
const PHONE_MIN_DIGITS = 10; // 手机号最少位数
|
||||
const PHONE_MAX_DIGITS = 11; // 手机号最多位数
|
||||
const COUNTRY_CODE_MAX_DIGITS = 3; // 国家代码最多位数
|
||||
const JWT_ISSUER = 'whale-town'; // JWT签发者
|
||||
const JWT_AUDIENCE = 'whale-town-users'; // JWT受众
|
||||
|
||||
/**
|
||||
* 登录核心服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 提供用户认证的核心功能实现(密码登录、验证码登录、OAuth登录)
|
||||
* - 处理用户注册、密码管理和邮箱验证等核心逻辑
|
||||
* - 为业务层提供基础的认证服务,不处理HTTP请求和响应格式化
|
||||
* - 管理JWT令牌的生成、验证和刷新功能
|
||||
* - 协调用户数据、邮件服务、验证码服务的集成
|
||||
*
|
||||
* 主要方法:
|
||||
* - login() - 用户名/邮箱/手机号密码登录
|
||||
* - verificationCodeLogin() - 验证码登录
|
||||
* - githubOAuth() - GitHub OAuth第三方登录
|
||||
* - register() - 用户注册(支持邮箱验证)
|
||||
* - changePassword() - 修改用户密码
|
||||
* - resetPassword() - 通过验证码重置密码
|
||||
* - sendPasswordResetCode() - 发送密码重置验证码
|
||||
* - sendEmailVerification() - 发送邮箱验证码
|
||||
* - verifyEmailCode() - 验证邮箱验证码
|
||||
* - generateTokenPair() - 生成JWT令牌对
|
||||
* - verifyToken() - 验证JWT令牌
|
||||
* - refreshAccessToken() - 刷新访问令牌
|
||||
*
|
||||
* 使用场景:
|
||||
* - 在业务控制器中调用进行用户认证
|
||||
* - 作为认证相关功能的核心服务层
|
||||
* - 在中间件中验证用户身份和权限
|
||||
* - 为其他业务服务提供用户认证支持
|
||||
*
|
||||
* 安全特性:
|
||||
* - 密码哈希存储(bcrypt,12轮盐值)
|
||||
* - JWT令牌安全生成和验证
|
||||
* - 用户状态和权限检查
|
||||
* - 验证码冷却机制防刷
|
||||
* - OAuth用户保护机制
|
||||
*/
|
||||
@Injectable()
|
||||
export class LoginCoreService {
|
||||
constructor(
|
||||
@@ -233,7 +285,46 @@ export class LoginCoreService {
|
||||
async register(registerRequest: RegisterRequest): Promise<AuthResult> {
|
||||
const { username, password, nickname, email, phone, email_verification_code } = registerRequest;
|
||||
|
||||
// 先检查用户是否已存在,避免消费验证码后才发现用户存在
|
||||
// 检查用户唯一性
|
||||
await this.validateUserUniqueness(username, email, phone);
|
||||
|
||||
// 验证邮箱验证码(如果提供了邮箱)
|
||||
if (email) {
|
||||
await this.validateEmailVerificationCode(email, email_verification_code);
|
||||
}
|
||||
|
||||
// 验证密码强度并创建用户
|
||||
this.validatePasswordStrength(password);
|
||||
const passwordHash = await this.hashPassword(password);
|
||||
|
||||
const user = await this.createNewUser({
|
||||
username,
|
||||
passwordHash,
|
||||
nickname,
|
||||
email,
|
||||
phone
|
||||
});
|
||||
|
||||
// 注册后处理
|
||||
await this.handlePostRegistration(email, nickname);
|
||||
|
||||
return {
|
||||
user,
|
||||
isNewUser: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户唯一性
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param email 邮箱
|
||||
* @param phone 手机号
|
||||
* @throws ConflictException 用户已存在时
|
||||
* @private
|
||||
*/
|
||||
private async validateUserUniqueness(username: string, email?: string, phone?: string): Promise<void> {
|
||||
// 检查用户名是否已存在
|
||||
const existingUser = await this.usersService.findByUsername(username);
|
||||
if (existingUser) {
|
||||
throw new ConflictException('用户名已存在');
|
||||
@@ -255,66 +346,85 @@ export class LoginCoreService {
|
||||
throw new ConflictException('手机号已存在');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果提供了邮箱,必须验证邮箱验证码
|
||||
if (email) {
|
||||
if (!email_verification_code) {
|
||||
throw new BadRequestException('提供邮箱时必须提供邮箱验证码');
|
||||
}
|
||||
|
||||
// 验证邮箱验证码
|
||||
await this.verificationService.verifyCode(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION,
|
||||
email_verification_code
|
||||
);
|
||||
/**
|
||||
* 验证邮箱验证码
|
||||
*
|
||||
* @param email 邮箱地址
|
||||
* @param emailVerificationCode 验证码
|
||||
* @throws BadRequestException 验证码错误时
|
||||
* @private
|
||||
*/
|
||||
private async validateEmailVerificationCode(email: string, emailVerificationCode?: string): Promise<void> {
|
||||
if (!emailVerificationCode) {
|
||||
throw new BadRequestException('提供邮箱时必须提供邮箱验证码');
|
||||
}
|
||||
|
||||
// 验证邮箱验证码
|
||||
await this.verificationService.verifyCode(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION,
|
||||
emailVerificationCode
|
||||
);
|
||||
}
|
||||
|
||||
// 验证密码强度
|
||||
this.validatePasswordStrength(password);
|
||||
|
||||
// 加密密码
|
||||
const passwordHash = await this.hashPassword(password);
|
||||
|
||||
// 创建用户
|
||||
const user = await this.usersService.create({
|
||||
/**
|
||||
* 创建新用户
|
||||
*
|
||||
* @param userData 用户数据
|
||||
* @returns 创建的用户
|
||||
* @private
|
||||
*/
|
||||
private async createNewUser(userData: {
|
||||
username: string;
|
||||
passwordHash: string;
|
||||
nickname: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
}): Promise<Users> {
|
||||
const { username, passwordHash, nickname, email, phone } = userData;
|
||||
|
||||
return await this.usersService.create({
|
||||
username,
|
||||
password_hash: passwordHash,
|
||||
nickname,
|
||||
email,
|
||||
phone,
|
||||
role: 1, // 默认普通用户
|
||||
role: DEFAULT_USER_ROLE, // 默认普通用户
|
||||
status: UserStatus.ACTIVE, // 默认激活状态
|
||||
email_verified: email ? true : false // 如果提供了邮箱且验证码验证通过,则标记为已验证
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册后处理
|
||||
*
|
||||
* @param email 邮箱地址
|
||||
* @param nickname 用户昵称
|
||||
* @private
|
||||
*/
|
||||
private async handlePostRegistration(email?: string, nickname?: string): Promise<void> {
|
||||
if (!email) return;
|
||||
|
||||
// 注册成功后清除验证码冷却时间,方便用户后续操作
|
||||
if (email) {
|
||||
try {
|
||||
await this.verificationService.clearCooldown(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION
|
||||
);
|
||||
} catch (error) {
|
||||
// 清除冷却时间失败不影响注册流程,只记录日志
|
||||
console.warn(`清除验证码冷却时间失败: ${email}`, error);
|
||||
}
|
||||
try {
|
||||
await this.verificationService.clearCooldown(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION
|
||||
);
|
||||
} catch (error) {
|
||||
// 清除冷却时间失败不影响注册流程,只记录日志
|
||||
console.warn(`清除验证码冷却时间失败: ${email}`, error);
|
||||
}
|
||||
|
||||
// 如果提供了邮箱,发送欢迎邮件
|
||||
if (email) {
|
||||
try {
|
||||
await this.emailService.sendWelcomeEmail(email, nickname);
|
||||
} catch (error) {
|
||||
// 邮件发送失败不影响注册流程,只记录日志
|
||||
console.warn(`欢迎邮件发送失败: ${email}`, error);
|
||||
}
|
||||
// 发送欢迎邮件
|
||||
try {
|
||||
await this.emailService.sendWelcomeEmail(email, nickname);
|
||||
} catch (error) {
|
||||
// 邮件发送失败不影响注册流程,只记录日志
|
||||
console.warn(`欢迎邮件发送失败: ${email}`, error);
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
isNewUser: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,34 +453,18 @@ export class LoginCoreService {
|
||||
};
|
||||
}
|
||||
|
||||
// 检查用户名是否已被占用
|
||||
let finalUsername = username;
|
||||
let counter = 1;
|
||||
while (await this.usersService.findByUsername(finalUsername)) {
|
||||
finalUsername = `${username}_${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
user = await this.usersService.create({
|
||||
// 处理用户名冲突并创建新用户
|
||||
const finalUsername = await this.resolveUsernameConflict(username);
|
||||
user = await this.createGitHubUser({
|
||||
username: finalUsername,
|
||||
nickname,
|
||||
email,
|
||||
github_id,
|
||||
avatar_url,
|
||||
role: 1, // 默认普通用户
|
||||
status: UserStatus.ACTIVE, // GitHub用户直接激活
|
||||
email_verified: email ? true : false // GitHub邮箱直接验证
|
||||
avatar_url
|
||||
});
|
||||
|
||||
// 发送欢迎邮件
|
||||
if (email) {
|
||||
try {
|
||||
await this.emailService.sendWelcomeEmail(email, nickname);
|
||||
} catch (error) {
|
||||
console.warn(`欢迎邮件发送失败: ${email}`, error);
|
||||
}
|
||||
}
|
||||
await this.sendWelcomeEmailSafely(email, nickname);
|
||||
|
||||
return {
|
||||
user,
|
||||
@@ -378,6 +472,70 @@ export class LoginCoreService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决用户名冲突
|
||||
*
|
||||
* @param username 原始用户名
|
||||
* @returns 可用的用户名
|
||||
* @private
|
||||
*/
|
||||
private async resolveUsernameConflict(username: string): Promise<string> {
|
||||
let finalUsername = username;
|
||||
let counter = DEFAULT_USER_ROLE;
|
||||
|
||||
while (await this.usersService.findByUsername(finalUsername) && counter <= USERNAME_CONFLICT_MAX_ATTEMPTS) {
|
||||
finalUsername = `${username}_${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
return finalUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建GitHub用户
|
||||
*
|
||||
* @param userData GitHub用户数据
|
||||
* @returns 创建的用户
|
||||
* @private
|
||||
*/
|
||||
private async createGitHubUser(userData: {
|
||||
username: string;
|
||||
nickname: string;
|
||||
email?: string;
|
||||
github_id: string;
|
||||
avatar_url?: string;
|
||||
}): Promise<Users> {
|
||||
const { username, nickname, email, github_id, avatar_url } = userData;
|
||||
|
||||
return await this.usersService.create({
|
||||
username,
|
||||
nickname,
|
||||
email,
|
||||
github_id,
|
||||
avatar_url,
|
||||
role: DEFAULT_USER_ROLE, // 默认普通用户
|
||||
status: UserStatus.ACTIVE, // GitHub用户直接激活
|
||||
email_verified: email ? true : false // GitHub邮箱直接验证
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全发送欢迎邮件
|
||||
*
|
||||
* @param email 邮箱地址
|
||||
* @param nickname 用户昵称
|
||||
* @private
|
||||
*/
|
||||
private async sendWelcomeEmailSafely(email?: string, nickname?: string): Promise<void> {
|
||||
if (!email) return;
|
||||
|
||||
try {
|
||||
await this.emailService.sendWelcomeEmail(email, nickname);
|
||||
} catch (error) {
|
||||
console.warn(`欢迎邮件发送失败: ${email}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送密码重置验证码
|
||||
*
|
||||
@@ -411,29 +569,23 @@ export class LoginCoreService {
|
||||
VerificationCodeType.PASSWORD_RESET
|
||||
);
|
||||
|
||||
// 发送验证码
|
||||
let isTestMode = false;
|
||||
|
||||
if (this.isEmail(identifier)) {
|
||||
const result = await this.emailService.sendVerificationCode({
|
||||
email: identifier,
|
||||
code: verificationCode,
|
||||
nickname: user.nickname,
|
||||
purpose: 'password_reset'
|
||||
});
|
||||
// 发送验证码(仅支持邮箱)
|
||||
if (!this.isEmail(identifier)) {
|
||||
throw new BadRequestException('当前仅支持邮箱验证码,请使用邮箱地址');
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
throw new BadRequestException('验证码发送失败,请稍后重试');
|
||||
}
|
||||
|
||||
isTestMode = result.isTestMode;
|
||||
} else {
|
||||
// TODO: 实现短信发送
|
||||
console.log(`短信验证码(${identifier}): ${verificationCode}`);
|
||||
isTestMode = true; // 短信也是测试模式
|
||||
const result = await this.emailService.sendVerificationCode({
|
||||
email: identifier,
|
||||
code: verificationCode,
|
||||
nickname: user.nickname,
|
||||
purpose: 'password_reset'
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new BadRequestException('验证码发送失败,请稍后重试');
|
||||
}
|
||||
|
||||
return { code: verificationCode, isTestMode };
|
||||
return { code: verificationCode, isTestMode: result.isTestMode };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -555,7 +707,6 @@ export class LoginCoreService {
|
||||
* @returns 密码哈希值
|
||||
*/
|
||||
private async hashPassword(password: string): Promise<string> {
|
||||
const SALT_ROUNDS = 12; // 推荐的盐值轮数
|
||||
return await bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
@@ -566,12 +717,12 @@ export class LoginCoreService {
|
||||
* @throws BadRequestException 密码强度不足时
|
||||
*/
|
||||
private validatePasswordStrength(password: string): void {
|
||||
if (password.length < 8) {
|
||||
throw new BadRequestException('密码长度至少8位');
|
||||
if (password.length < MIN_PASSWORD_LENGTH) {
|
||||
throw new BadRequestException(`密码长度至少${MIN_PASSWORD_LENGTH}位`);
|
||||
}
|
||||
|
||||
if (password.length > 128) {
|
||||
throw new BadRequestException('密码长度不能超过128位');
|
||||
if (password.length > MAX_PASSWORD_LENGTH) {
|
||||
throw new BadRequestException(`密码长度不能超过${MAX_PASSWORD_LENGTH}位`);
|
||||
}
|
||||
|
||||
// 检查是否包含字母和数字
|
||||
@@ -692,7 +843,7 @@ export class LoginCoreService {
|
||||
*/
|
||||
private isPhoneNumber(str: string): boolean {
|
||||
// 简单的手机号验证,支持国际格式
|
||||
const phoneRegex = /^(\+\d{1,3}[- ]?)?\d{10,11}$/;
|
||||
const phoneRegex = new RegExp(`^(\\+\\d{1,${COUNTRY_CODE_MAX_DIGITS}}[- ]?)?\\d{${PHONE_MIN_DIGITS},${PHONE_MAX_DIGITS}}$`);
|
||||
return phoneRegex.test(str.replace(/\s/g, ''));
|
||||
}
|
||||
|
||||
@@ -832,27 +983,23 @@ export class LoginCoreService {
|
||||
|
||||
// 3. 发送验证码
|
||||
let isTestMode = false;
|
||||
|
||||
if (this.isEmail(identifier)) {
|
||||
const result = await this.emailService.sendVerificationCode({
|
||||
email: identifier,
|
||||
code: verificationCode,
|
||||
nickname: user.nickname,
|
||||
purpose: 'login_verification'
|
||||
});
|
||||
// 发送验证码(仅支持邮箱)
|
||||
if (!this.isEmail(identifier)) {
|
||||
throw new BadRequestException('当前仅支持邮箱验证码,请使用邮箱地址');
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
throw new BadRequestException('验证码发送失败,请稍后重试');
|
||||
}
|
||||
|
||||
isTestMode = result.isTestMode;
|
||||
} else {
|
||||
// TODO: 实现短信发送
|
||||
console.log(`短信验证码(${identifier}): ${verificationCode}`);
|
||||
isTestMode = true; // 短信也是测试模式
|
||||
const result = await this.emailService.sendVerificationCode({
|
||||
email: identifier,
|
||||
code: verificationCode,
|
||||
nickname: user.nickname,
|
||||
purpose: 'login_verification'
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new BadRequestException('验证码发送失败,请稍后重试');
|
||||
}
|
||||
|
||||
return { code: verificationCode, isTestMode };
|
||||
return { code: verificationCode, isTestMode: result.isTestMode };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -926,7 +1073,6 @@ export class LoginCoreService {
|
||||
*/
|
||||
async generateTokenPair(user: Users): Promise<TokenPair> {
|
||||
try {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
const jwtSecret = this.configService.get<string>('JWT_SECRET');
|
||||
const expiresIn = this.configService.get<string>('JWT_EXPIRES_IN', '7d');
|
||||
|
||||
@@ -934,37 +1080,13 @@ export class LoginCoreService {
|
||||
throw new Error('JWT_SECRET未配置');
|
||||
}
|
||||
|
||||
// 1. 创建访问令牌载荷(不包含iss和aud,这些通过options传递)
|
||||
const accessPayload: Omit<JwtPayload, 'iss' | 'aud' | 'iat' | 'exp'> = {
|
||||
sub: user.id.toString(),
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
email: user.email,
|
||||
type: 'access',
|
||||
};
|
||||
// 创建令牌载荷
|
||||
const { accessPayload, refreshPayload } = this.createTokenPayloads(user);
|
||||
|
||||
// 2. 创建刷新令牌载荷(有效期更长)
|
||||
const refreshPayload: Omit<JwtPayload, 'iss' | 'aud' | 'iat' | 'exp'> = {
|
||||
sub: user.id.toString(),
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
type: 'refresh',
|
||||
};
|
||||
// 生成令牌
|
||||
const { accessToken, refreshToken } = await this.signTokens(accessPayload, refreshPayload, jwtSecret);
|
||||
|
||||
// 3. 生成访问令牌(使用NestJS JwtService,通过options传递iss和aud)
|
||||
const accessToken = await this.jwtService.signAsync(accessPayload, {
|
||||
issuer: 'whale-town',
|
||||
audience: 'whale-town-users',
|
||||
});
|
||||
|
||||
// 4. 生成刷新令牌(有效期30天)
|
||||
const refreshToken = jwt.sign(refreshPayload, jwtSecret, {
|
||||
expiresIn: '30d',
|
||||
issuer: 'whale-town',
|
||||
audience: 'whale-town-users',
|
||||
});
|
||||
|
||||
// 5. 计算过期时间(秒)
|
||||
// 计算过期时间
|
||||
const expiresInSeconds = this.parseExpirationTime(expiresIn);
|
||||
|
||||
return {
|
||||
@@ -980,6 +1102,65 @@ export class LoginCoreService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建令牌载荷
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @returns 访问令牌和刷新令牌载荷
|
||||
* @private
|
||||
*/
|
||||
private createTokenPayloads(user: Users): {
|
||||
accessPayload: Omit<JwtPayload, 'iss' | 'aud' | 'iat' | 'exp'>;
|
||||
refreshPayload: Omit<JwtPayload, 'iss' | 'aud' | 'iat' | 'exp'>;
|
||||
} {
|
||||
const accessPayload: Omit<JwtPayload, 'iss' | 'aud' | 'iat' | 'exp'> = {
|
||||
sub: user.id.toString(),
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
email: user.email,
|
||||
type: 'access',
|
||||
};
|
||||
|
||||
const refreshPayload: Omit<JwtPayload, 'iss' | 'aud' | 'iat' | 'exp'> = {
|
||||
sub: user.id.toString(),
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
type: 'refresh',
|
||||
};
|
||||
|
||||
return { accessPayload, refreshPayload };
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名令牌
|
||||
*
|
||||
* @param accessPayload 访问令牌载荷
|
||||
* @param refreshPayload 刷新令牌载荷
|
||||
* @param jwtSecret JWT密钥
|
||||
* @returns 签名后的令牌
|
||||
* @private
|
||||
*/
|
||||
private async signTokens(
|
||||
accessPayload: Omit<JwtPayload, 'iss' | 'aud' | 'iat' | 'exp'>,
|
||||
refreshPayload: Omit<JwtPayload, 'iss' | 'aud' | 'iat' | 'exp'>,
|
||||
jwtSecret: string
|
||||
): Promise<{ accessToken: string; refreshToken: string }> {
|
||||
// 生成访问令牌(使用NestJS JwtService,通过options传递iss和aud)
|
||||
const accessToken = await this.jwtService.signAsync(accessPayload, {
|
||||
issuer: JWT_ISSUER,
|
||||
audience: JWT_AUDIENCE,
|
||||
});
|
||||
|
||||
// 生成刷新令牌(有效期30天)
|
||||
const refreshToken = jwt.sign(refreshPayload, jwtSecret, {
|
||||
expiresIn: REFRESH_TOKEN_EXPIRES_IN,
|
||||
issuer: JWT_ISSUER,
|
||||
audience: JWT_AUDIENCE,
|
||||
});
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT令牌
|
||||
*
|
||||
@@ -1008,8 +1189,8 @@ export class LoginCoreService {
|
||||
|
||||
// 1. 验证令牌并解码载荷
|
||||
const payload = jwt.verify(token, jwtSecret, {
|
||||
issuer: 'whale-town',
|
||||
audience: 'whale-town-users',
|
||||
issuer: JWT_ISSUER,
|
||||
audience: JWT_AUDIENCE,
|
||||
}) as JwtPayload;
|
||||
|
||||
// 2. 验证令牌类型
|
||||
@@ -1081,14 +1262,14 @@ export class LoginCoreService {
|
||||
*/
|
||||
private parseExpirationTime(expiresIn: string): number {
|
||||
if (!expiresIn || typeof expiresIn !== 'string') {
|
||||
return 7 * 24 * 60 * 60; // 默认7天
|
||||
return DEFAULT_ACCESS_TOKEN_EXPIRES_DAYS * 24 * 60 * 60; // 默认7天
|
||||
}
|
||||
|
||||
const timeUnit = expiresIn.slice(-1);
|
||||
const timeValue = parseInt(expiresIn.slice(0, -1));
|
||||
|
||||
if (isNaN(timeValue)) {
|
||||
return 7 * 24 * 60 * 60; // 默认7天
|
||||
return DEFAULT_ACCESS_TOKEN_EXPIRES_DAYS * 24 * 60 * 60; // 默认7天
|
||||
}
|
||||
|
||||
switch (timeUnit) {
|
||||
@@ -1097,7 +1278,7 @@ export class LoginCoreService {
|
||||
case 'h': return timeValue * 60 * 60;
|
||||
case 'd': return timeValue * 24 * 60 * 60;
|
||||
case 'w': return timeValue * 7 * 24 * 60 * 60;
|
||||
default: return 7 * 24 * 60 * 60; // 默认7天
|
||||
default: return DEFAULT_ACCESS_TOKEN_EXPIRES_DAYS * 24 * 60 * 60; // 默认7天
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user