add more auth docs

This commit is contained in:
andres
2023-07-16 20:40:58 +02:00
parent aa7ece41a9
commit 971b165be8
3 changed files with 64 additions and 16 deletions

View File

@@ -13,7 +13,15 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common' } from '@nestjs/common'
import { CommandBus } from '@nestjs/cqrs' import { CommandBus } from '@nestjs/cqrs'
import { ApiBody, ApiTags } from '@nestjs/swagger' import {
ApiBadRequestResponse,
ApiBody,
ApiNoContentResponse,
ApiNotFoundResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger'
import { Response as ExpressResponse } from 'express' import { Response as ExpressResponse } from 'express'
import { Cookies } from '../../infrastructure/decorators' import { Cookies } from '../../infrastructure/decorators'
@@ -25,6 +33,7 @@ import {
RegistrationDto, RegistrationDto,
ResendVerificationEmailDto, ResendVerificationEmailDto,
} from './dto' } from './dto'
import { ResetPasswordDto } from './dto/reset-password.dto'
import { LoginResponse, UserEntity } from './entities/auth.entity' import { LoginResponse, UserEntity } from './entities/auth.entity'
import { JwtAuthGuard, JwtRefreshGuard, LocalAuthGuard } from './guards' import { JwtAuthGuard, JwtRefreshGuard, LocalAuthGuard } from './guards'
import { import {
@@ -43,6 +52,9 @@ import {
export class AuthController { export class AuthController {
constructor(private commandBus: CommandBus) {} constructor(private commandBus: CommandBus) {}
@ApiOperation({ description: 'Retrieve current user data.' })
@ApiUnauthorizedResponse({ description: 'Not logged in' })
@ApiBadRequestResponse({ description: 'User not found' })
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('me') @Get('me')
async getUserData(@Request() req): Promise<UserEntity> { async getUserData(@Request() req): Promise<UserEntity> {
@@ -51,8 +63,10 @@ export class AuthController {
return await this.commandBus.execute(new GetCurrentUserDataCommand(userId)) return await this.commandBus.execute(new GetCurrentUserDataCommand(userId))
} }
@HttpCode(HttpStatus.OK) @ApiOperation({ description: 'Sign in using email and password. Must have an account to do so.' })
@ApiUnauthorizedResponse({ description: 'Invalid credentials' })
@ApiBody({ type: LoginDto }) @ApiBody({ type: LoginDto })
@HttpCode(HttpStatus.OK)
@UseGuards(LocalAuthGuard) @UseGuards(LocalAuthGuard)
@Post('login') @Post('login')
async login( async login(
@@ -76,73 +90,101 @@ export class AuthController {
return { accessToken: req.user.data.accessToken } return { accessToken: req.user.data.accessToken }
} }
@ApiOperation({ description: 'Create a new user account' })
@ApiBadRequestResponse({ description: 'Email already exists' })
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.CREATED)
@Post('sign-up') @Post('sign-up')
async registration(@Body() registrationData: RegistrationDto): Promise<UserEntity> { async registration(@Body() registrationData: RegistrationDto): Promise<UserEntity> {
return await this.commandBus.execute(new CreateUserCommand(registrationData)) return await this.commandBus.execute(new CreateUserCommand(registrationData))
} }
@ApiOperation({ description: 'Verify user email' })
@ApiBadRequestResponse({ description: 'Email has already been verified' })
@ApiNotFoundResponse({ description: 'User not found' })
@ApiNoContentResponse({ description: 'Email verified successfully' })
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.NO_CONTENT)
@Post('verify-email') @Post('verify-email')
async confirmRegistration(@Body() body: EmailVerificationDto): Promise<void> { async confirmRegistration(@Body() body: EmailVerificationDto): Promise<void> {
return await this.commandBus.execute(new VerifyEmailCommand(body.code)) return await this.commandBus.execute(new VerifyEmailCommand(body.code))
} }
@ApiOperation({ description: 'Send verification email again' })
@ApiBadRequestResponse({ description: 'Email has already been verified' })
@ApiNotFoundResponse({ description: 'User not found' })
@ApiNoContentResponse({ description: 'Verification email sent successfully' })
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.NO_CONTENT)
@Post('resend-verification-email') @Post('resend-verification-email')
async resendVerificationEmail(@Body() body: ResendVerificationEmailDto): Promise<void> { async resendVerificationEmail(@Body() body: ResendVerificationEmailDto): Promise<void> {
return await this.commandBus.execute(new ResendVerificationEmailCommand(body.userId)) return await this.commandBus.execute(new ResendVerificationEmailCommand(body.userId))
} }
@ApiOperation({ description: 'Sign current user out' })
@ApiUnauthorizedResponse({ description: 'Not logged in' })
@ApiNoContentResponse({ description: 'Logged out successfully' })
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.NO_CONTENT)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Post('logout') @Post('logout')
async logout( async logout(
@Cookies('refreshToken') refreshToken: string, @Cookies('accessToken') accessToken: string,
@Res({ passthrough: true }) res: ExpressResponse @Res({ passthrough: true }) res: ExpressResponse
): Promise<void> { ): Promise<void> {
if (!refreshToken) throw new UnauthorizedException() if (!accessToken) throw new UnauthorizedException()
await this.commandBus.execute(new LogoutCommand(refreshToken)) await this.commandBus.execute(new LogoutCommand(accessToken))
res.clearCookie('accessToken')
res.clearCookie('refreshToken') res.clearCookie('refreshToken')
return null return null
} }
@HttpCode(HttpStatus.OK) @ApiOperation({ description: 'Get new access token using refresh token' })
@ApiUnauthorizedResponse({ description: 'Invalid or missing refreshToken' })
@ApiNoContentResponse({ description: 'New tokens generated successfully' })
@HttpCode(HttpStatus.NO_CONTENT)
@UseGuards(JwtRefreshGuard) @UseGuards(JwtRefreshGuard)
@Post('refresh-token') @Post('refresh-token')
async refreshToken( async refreshToken(
@Request() req, @Request() req,
@Response({ passthrough: true }) res: ExpressResponse @Response({ passthrough: true }) res: ExpressResponse
): Promise<LoginResponse> { ): Promise<void> {
if (!req.cookies?.refreshToken) throw new UnauthorizedException() if (!req.cookies?.refreshToken) throw new UnauthorizedException()
const userId = req.user.id const userId = req.user.id
const newTokens = await this.commandBus.execute(new RefreshTokenCommand(userId)) const newTokens = await this.commandBus.execute(new RefreshTokenCommand(userId))
res.cookie('refreshToken', newTokens.refreshToken, { res.cookie('refreshToken', newTokens.refreshToken, {
httpOnly: true, httpOnly: true,
// secure: true, sameSite: 'none',
path: '/v1/auth/refresh-token', path: '/v1/auth/refresh-token',
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), secure: true,
})
res.cookie('accessToken', newTokens.accessToken, {
httpOnly: true,
sameSite: 'none',
secure: true,
}) })
return { return null
accessToken: newTokens.accessToken,
}
} }
@ApiOperation({ description: 'Send password recovery email' })
@ApiBadRequestResponse({ description: 'Email has already been verified' })
@ApiNotFoundResponse({ description: 'User not found' })
@ApiNoContentResponse({ description: 'Password recovery email sent successfully' })
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.NO_CONTENT)
@Post('recover-password') @Post('recover-password')
async recoverPassword(@Body() body: RecoverPasswordDto): Promise<void> { async recoverPassword(@Body() body: RecoverPasswordDto): Promise<void> {
return await this.commandBus.execute(new SendPasswordRecoveryEmailCommand(body.email)) return await this.commandBus.execute(new SendPasswordRecoveryEmailCommand(body.email))
} }
@ApiOperation({ description: 'Reset password' })
@ApiBadRequestResponse({ description: 'Password is required' })
@ApiNotFoundResponse({ description: 'Incorrect or expired password reset token' })
@ApiNoContentResponse({ description: 'Password reset successfully' })
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.NO_CONTENT)
@Post('reset-password/:token') @Post('reset-password/:token')
async resetPassword( async resetPassword(
@Body('password') password: string, @Body() body: ResetPasswordDto,
@Param('token') token: string @Param('token') token: string
): Promise<void> { ): Promise<void> {
return await this.commandBus.execute(new ResetPasswordCommand(token, password)) return await this.commandBus.execute(new ResetPasswordCommand(token, body.password))
} }
} }

View File

@@ -0,0 +1,6 @@
import { Length } from 'class-validator'
export class ResetPasswordDto {
@Length(3, 30)
password: string
}

View File

@@ -5,7 +5,7 @@ import jwt from 'jsonwebtoken'
import { UsersRepository } from '../../users/infrastructure/users.repository' import { UsersRepository } from '../../users/infrastructure/users.repository'
export class LogoutCommand { export class LogoutCommand {
constructor(public readonly refreshToken: string) {} constructor(public readonly accessToken: string) {}
} }
@CommandHandler(LogoutCommand) @CommandHandler(LogoutCommand)
@@ -15,7 +15,7 @@ export class LogoutHandler implements ICommandHandler<LogoutCommand> {
private readonly logger = new Logger(LogoutHandler.name) private readonly logger = new Logger(LogoutHandler.name)
async execute(command: LogoutCommand) { async execute(command: LogoutCommand) {
const token = command.refreshToken const token = command.accessToken
const secretKey = process.env.JWT_SECRET_KEY const secretKey = process.env.JWT_SECRET_KEY