Files
backend/src/modules/auth/presentation/auth.controller.ts
T
ksv741 2d14081d27
Backend security / security (push) Successful in 8s
Backend security / security (pull_request) Canceled after 0s
Backend verify / verify (push) Canceled after 9s
Backend verify / verify (pull_request) Canceled after 4s
Conventional commits / commits (pull_request) Successful in 31s
refactor: enforce enum machine codes
Use owner enums for error and T-Bank protocol values with ESLint regression checks.

Preserve open wire fallbacks and keep protocol imports independent of DI and Prisma.

Verified npm ci, verify, 226 unit tests, 262 isolated e2e tests and unchanged OpenAPI.
2026-09-28 13:18:06 +03:00

149 lines
3.8 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 } from '../application/auth.types';
import { CredentialsDto } 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: CredentialsDto,
@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: CredentialsDto,
@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,
) {
try {
const token = this.refreshToken(request);
if (!token) throw new UnauthorizedException({ code: HttpErrorCode.Unauthorized });
return this.respond(response, await this.auth.refresh(token));
} catch (error) {
if (error instanceof UnauthorizedException) this.clearCookie(response);
throw error;
}
}
@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: {
accessToken: string;
refreshToken: string;
user: { email: string; id: string };
},
) {
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,
};
}
}