initial commit

This commit is contained in:
2026-06-09 14:07:37 +03:30
parent 30ac533800
commit 996a4fcda7
121 changed files with 20557 additions and 3 deletions

View File

@@ -0,0 +1,34 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import { API_KEY_HEADER } from '../../common/constants/app.constants';
/**
* API key authentication for inquiry endpoints.
* Expects x-api-key header matching configured API_KEY.
*/
@Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private readonly configService: ConfigService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
const apiKey = request.headers[API_KEY_HEADER] as string | undefined;
const expected = this.configService.get<string>('auth.apiKey');
if (!expected) {
throw new UnauthorizedException('API key authentication is not configured');
}
if (!apiKey || apiKey !== expected) {
throw new UnauthorizedException('Invalid or missing API key');
}
return true;
}
}

View File

@@ -0,0 +1,45 @@
import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { ADMIN_ROLES } from '../../common/enums/role.enum';
import { InquiryType } from '../../common/enums/inquiry-type.enum';
import { INQUIRY_ACCESS_KEY } from '../constants/auth.constants';
import { AuthenticatedUser } from '../interfaces/authenticated-user.interface';
/**
* Validates that the user may call a specific inquiry endpoint.
* ADMIN / SUPER_ADMIN bypass — external USER clients need allowedInquiries.
*/
@Injectable()
export class InquiryAccessGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredInquiry = this.reflector.getAllAndOverride<InquiryType>(INQUIRY_ACCESS_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredInquiry) {
return true;
}
const request = context.switchToHttp().getRequest<Request & { user: AuthenticatedUser }>();
const user = request.user;
if (!user) {
throw new ForbiddenException('Authentication required');
}
if (ADMIN_ROLES.includes(user.role)) {
return true;
}
const inquiryKey = requiredInquiry as string;
if (!user.allowedInquiries.includes(inquiryKey)) {
throw new ForbiddenException(`Access denied for inquiry: ${inquiryKey}`);
}
return true;
}
}

View File

@@ -0,0 +1,21 @@
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { JWT_ACCESS_STRATEGY } from '../constants/auth.constants';
/**
* Protects routes with Passport JWT access strategy.
*/
@Injectable()
export class JwtAuthGuard extends AuthGuard(JWT_ACCESS_STRATEGY) {
override handleRequest<TUser>(
err: Error | null,
user: TUser | false,
_info: unknown,
_context: ExecutionContext,
): TUser {
if (err || !user) {
throw err ?? new UnauthorizedException('Unauthorized');
}
return user;
}
}

View File

@@ -0,0 +1,34 @@
import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { ROLES_KEY } from '../constants/auth.constants';
import { AuthenticatedUser } from '../interfaces/authenticated-user.interface';
import { Role } from '../../common/enums/role.enum';
/**
* Enforces @Roles() metadata against the authenticated user's role.
*/
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles?.length) {
return true;
}
const request = context.switchToHttp().getRequest<Request & { user: AuthenticatedUser }>();
const user = request.user;
if (!user || !requiredRoles.includes(user.role)) {
throw new ForbiddenException('Insufficient role permissions');
}
return true;
}
}