Files
tonePage/apps/backend/src/auth/auth.controller.ts

184 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
BadRequestException,
Body,
Controller,
Post,
Req,
Res,
UseGuards,
} from '@nestjs/common';
import { LoginByPasswordDto } from './dto/login.dto';
import { AuthService } from './auth.service';
import { UserSessionService } from 'src/auth/service/user-session.service';
import { Throttle, ThrottlerGuard } from '@nestjs/throttler';
import { Request, Response } from 'express';
import { UserService } from 'src/user/user.service';
import { AuthGuard } from './guards/auth.guard';
import { SmsLoginDto } from './dto/sms-login.dto';
import { SmsService } from 'src/sms/sms.service';
import { UserSession } from 'src/auth/entity/user-session.entity';
import { PasskeyService } from './service/passkey.service';
import { v4 as uuidv4 } from 'uuid';
import { PasskeyLoginDto } from './dto/passkey-login.dto';
import { AuthUser, CurrentUser } from './decorator/current-user.decorator';
import { PasskeyRegisterDto } from './dto/passkey-register.dto';
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
private readonly userSessionService: UserSessionService,
private readonly smsService: SmsService,
private readonly passkeyService: PasskeyService,
) { }
private setUserSession(res: Response, session: UserSession) {
res.cookie('session', session.sessionId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
// 永不过期不用设置maxAge
path: '/',
})
}
@Post('login/password')
@UseGuards(ThrottlerGuard)
@Throttle({
'min': { limit: 5, ttl: 60 * 1000 },
'hour': { limit: 20, ttl: 60 * 60 * 1000 },
'day': { limit: 50, ttl: 24 * 60 * 60 * 1000 }
})
async loginByPassword(
@Body() loginDto: LoginByPasswordDto,
@Res({ passthrough: true }) res: Response,
) {
const { identifier, password } = loginDto;
const session = await this.authService.loginWithPassword(identifier, password);
this.setUserSession(res, session);
return {
user: await this.userService.findById(session.userId),
};
}
@Post('login/sms')
@UseGuards(ThrottlerGuard)
@Throttle({
'day': { limit: 50, ttl: 24 * 60 * 60 * 1000 }
})
async loginBySms(
@Body() dto: SmsLoginDto,
@Res({ passthrough: true }) res: Response,
) {
const { phone, code } = dto;
await this.smsService.checkSms(phone, 'login', code);
// 验证通过,(注册并)登陆
const session = await this.authService.loginWithPhone(phone);
this.setUserSession(res, session);
return {
user: await this.userService.findById(session.userId),
}
}
@Post('passkey/login/options')
@UseGuards(ThrottlerGuard)
@Throttle({
'day': { limit: 20, ttl: 24 * 60 * 60 * 1000 }
})
async loginByPasskeyOptions(
@Res({ passthrough: true }) res: Response,
) {
const tempSessionId = uuidv4();
const options = await this.passkeyService.getAuthenticationOptions(tempSessionId);
res.cookie('passkey_temp_session', tempSessionId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/api/auth/passkey/login',
maxAge: 1 * 60 * 1000,
});
return options;
}
@Post('passkey/login')
@UseGuards(ThrottlerGuard)
@Throttle({
'day': { limit: 20, ttl: 24 * 60 * 60 * 1000 }
})
async loginByPasskey(
@Req() req: Request,
@Body() body: PasskeyLoginDto,
@Res({ passthrough: true }) res: Response,
) {
const tempSessionId = req.cookies?.passkey_temp_session;
if (!tempSessionId) {
throw new BadRequestException('登录失败,请重试');
}
try {
const user = await this.passkeyService.login(tempSessionId, body.credentialResponse);
const session = await this.userSessionService.createSession(user.userId);
this.setUserSession(res, session);
return {
user: await this.userService.findById(user.userId),
};
} catch (error) {
throw error;
} finally {
res.clearCookie('passkey_temp_session', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/api/auth/passkey/login',
});
}
}
@UseGuards(AuthGuard)
@Post('passkey/register/options')
async getPasskeyRegisterOptions(
@CurrentUser() user: AuthUser,
) {
const { userId } = user;
return this.passkeyService.getRegistrationOptions(userId);
}
@UseGuards(AuthGuard)
@Post('passkey/register')
async registerPasskey(
@CurrentUser() user: AuthUser,
@Body() dto: PasskeyRegisterDto,
) {
const { userId } = user;
const { credentialResponse, name } = dto;
const passkey = await this.passkeyService.register(userId, credentialResponse, name.trim());
return {
id: passkey.id,
name: passkey.name,
createdAt: passkey.createdAt,
};
}
@UseGuards(AuthGuard)
@Post('logout')
async logout(@CurrentUser() user: AuthUser, @Res({ passthrough: true }) res: Response) {
const { sessionId } = user;
await this.userSessionService.invalidateSession(sessionId, '用户主动登出');
res.clearCookie('session', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
})
return true;
}
}