Files
backend/src/modules/auth/presentation/auth.controller.ts
T
codex-bot e03fad622b
Backend verify / commits (push) Skipped
Backend verify / commits (pull_request) Failing after 32s
Backend verify / security (pull_request) Skipped
Backend verify / verify (pull_request) Skipped
Backend verify / security (push) Successful in 10s
Backend verify / verify (push) Failing after 6m21s
fix: align password policy to 8-72 printable ASCII on register (WealthLedger/backend#60) (#109)
2026-10-16 10:00:47 +03:00

140 lines
3.6 KiB
TypeScript

import type { Request, Response } from 'express';
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Post,
Req,
Res,
UnauthorizedException,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiNoContentResponse,
ApiOkResponse,
ApiProperty,
ApiTags,
} from '@nestjs/swagger';
import { ApiProblemResponses } from '../../../common/decorators/api-safe-error-response.decorator';
import { HttpErrorCode } from '../../../common/error-code';
import { AuthService } from '../application/auth.service';
import { AccessPayload, AuthenticationResult } from '../application/auth.types';
import { LoginDto, RegisterDto } from './auth.dto';
import { JwtAuthGuard } from './jwt-auth.guard';
const REFRESH_COOKIE = 'refresh_token';
const COOKIE_PATH = '/api/v1/auth';
class UserDto {
@ApiProperty()
declare id: string;
@ApiProperty()
declare email: string;
}
class AuthenticationDto {
@ApiProperty()
declare accessToken: string;
@ApiProperty({ type: UserDto })
declare user: UserDto;
}
@ApiTags('auth')
@ApiProblemResponses()
@Controller('auth')
export class AuthController {
constructor(private readonly auth: AuthService) {}
@Post('register')
@HttpCode(HttpStatus.OK)
@ApiOkResponse({ type: AuthenticationDto })
async register(
@Body() credentials: RegisterDto,
@Res({ passthrough: true }) response: Response,
) {
return this.respond(
response,
await this.auth.register(credentials.email, credentials.password),
);
}
@Post('login')
@HttpCode(HttpStatus.OK)
@ApiOkResponse({ type: AuthenticationDto })
async login(
@Body() credentials: LoginDto,
@Res({ passthrough: true }) response: Response,
) {
return this.respond(
response,
await this.auth.login(credentials.email, credentials.password),
);
}
@Post('refresh')
@HttpCode(HttpStatus.OK)
@ApiOkResponse({ type: AuthenticationDto })
async refresh(
@Req() request: Request,
@Res({ passthrough: true }) response: Response,
) {
const token = this.refreshToken(request);
if (!token) throw new UnauthorizedException({ code: HttpErrorCode.Unauthorized });
return this.respond(response, await this.auth.refresh(token));
}
@Post('logout')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiNoContentResponse()
async logout(
@Req() request: Request,
@Res({ passthrough: true }) response: Response,
): Promise<void> {
await this.auth.logout(this.refreshToken(request));
this.clearCookie(response);
}
@Get('me')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: UserDto })
async me(@Req() request: Request & { user: AccessPayload }) {
const user = await this.auth.userById(request.user.sub);
return this.auth.toUser(user);
}
private respond(
response: Response,
result: AuthenticationResult,
) {
response.cookie(REFRESH_COOKIE, result.refreshToken, this.cookieOptions());
return { user: result.user, accessToken: result.accessToken };
}
private clearCookie(response: Response): void {
response.clearCookie(REFRESH_COOKIE, this.cookieOptions());
}
private refreshToken(request: Request): string | undefined {
// eslint-disable-next-line security/detect-object-injection -- The cookie name is a fixed application constant; its value is narrowed below.
const token: unknown = request.cookies?.[REFRESH_COOKIE];
return typeof token === 'string' ? token : undefined;
}
private cookieOptions() {
return {
httpOnly: true,
sameSite: 'lax' as const,
secure: process.env['NODE_ENV'] !== 'development',
path: COOKIE_PATH,
};
}
}