feat(login): Add verification code login functionality

- Add verification code login endpoint to support passwordless authentication via email or phone
- Add send login verification code endpoint to initiate verification code delivery
- Implement verificationCodeLogin method in LoginService to handle verification code authentication
- Implement sendLoginVerificationCode method in LoginService to send verification codes to users
- Add VerificationCodeLoginRequest and related DTOs to support new login flow
- Add VerificationCodeLoginDto and SendLoginVerificationCodeDto for API request validation
- Implement verificationCodeLogin and sendLoginVerificationCode in LoginCoreService
- Add comprehensive Swagger documentation for new endpoints with proper status codes and responses
- Support test mode for verification code delivery with 206 Partial Content status
- Fix UsersService dependency injection in test specifications to use string token
- Enhance authentication options by providing passwordless login alternative to traditional password-based authentication
This commit is contained in:
angjustinl
2025-12-19 23:22:40 +08:00
parent 4e2f46223e
commit 9b35a1c500
6 changed files with 468 additions and 6 deletions

View File

@@ -23,7 +23,7 @@ import { Controller, Post, Put, Body, HttpCode, HttpStatus, ValidationPipe, UseP
import { ApiTags, ApiOperation, ApiResponse as SwaggerApiResponse, ApiBody } from '@nestjs/swagger';
import { Response } from 'express';
import { LoginService, ApiResponse, LoginResponse } from './login.service';
import { LoginDto, RegisterDto, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto, EmailVerificationDto, SendEmailVerificationDto } from '../../dto/login.dto';
import { LoginDto, RegisterDto, GitHubOAuthDto, ForgotPasswordDto, ResetPasswordDto, ChangePasswordDto, EmailVerificationDto, SendEmailVerificationDto, VerificationCodeLoginDto, SendLoginVerificationCodeDto } from '../../dto/login.dto';
import {
LoginResponseDto,
RegisterResponseDto,
@@ -398,6 +398,96 @@ export class LoginController {
}
}
/**
* 验证码登录
*
* @param verificationCodeLoginDto 验证码登录数据
* @returns 登录结果
*/
@ApiOperation({
summary: '验证码登录',
description: '使用邮箱或手机号和验证码进行登录,无需密码'
})
@ApiBody({ type: VerificationCodeLoginDto })
@SwaggerApiResponse({
status: 200,
description: '验证码登录成功',
type: LoginResponseDto
})
@SwaggerApiResponse({
status: 400,
description: '请求参数错误'
})
@SwaggerApiResponse({
status: 401,
description: '验证码错误或已过期'
})
@SwaggerApiResponse({
status: 404,
description: '用户不存在'
})
@Post('verification-code-login')
@HttpCode(HttpStatus.OK)
@UsePipes(new ValidationPipe({ transform: true }))
async verificationCodeLogin(@Body() verificationCodeLoginDto: VerificationCodeLoginDto): Promise<ApiResponse<LoginResponse>> {
return await this.loginService.verificationCodeLogin({
identifier: verificationCodeLoginDto.identifier,
verificationCode: verificationCodeLoginDto.verification_code
});
}
/**
* 发送登录验证码
*
* @param sendLoginVerificationCodeDto 发送验证码数据
* @param res Express响应对象
* @returns 发送结果
*/
@ApiOperation({
summary: '发送登录验证码',
description: '向用户邮箱或手机发送登录验证码'
})
@ApiBody({ type: SendLoginVerificationCodeDto })
@SwaggerApiResponse({
status: 200,
description: '验证码发送成功',
type: ForgotPasswordResponseDto
})
@SwaggerApiResponse({
status: 206,
description: '测试模式:验证码已生成但未真实发送',
type: ForgotPasswordResponseDto
})
@SwaggerApiResponse({
status: 400,
description: '请求参数错误'
})
@SwaggerApiResponse({
status: 404,
description: '用户不存在'
})
@SwaggerApiResponse({
status: 429,
description: '发送频率过高'
})
@Post('send-login-verification-code')
@UsePipes(new ValidationPipe({ transform: true }))
async sendLoginVerificationCode(
@Body() sendLoginVerificationCodeDto: SendLoginVerificationCodeDto,
@Res() res: Response
): Promise<void> {
const result = await this.loginService.sendLoginVerificationCode(sendLoginVerificationCodeDto.identifier);
// 根据结果设置不同的状态码
if (result.success) {
res.status(HttpStatus.OK).json(result);
} else if (result.error_code === 'TEST_MODE_ONLY') {
res.status(HttpStatus.PARTIAL_CONTENT).json(result); // 206 Partial Content
} else {
res.status(HttpStatus.BAD_REQUEST).json(result);
}
}
/**
* 调试验证码信息
* 仅用于开发和调试