diff --git a/backend/src/authorization/auth-with-api.middleware.ts b/backend/src/authorization/auth-with-api.middleware.ts index c716db40c..4e0b98505 100644 --- a/backend/src/authorization/auth-with-api.middleware.ts +++ b/backend/src/authorization/auth-with-api.middleware.ts @@ -1,11 +1,11 @@ import { - BadRequestException, - HttpException, - Injectable, - InternalServerErrorException, - NestMiddleware, - NotFoundException, - UnauthorizedException, + BadRequestException, + HttpException, + Injectable, + InternalServerErrorException, + NestMiddleware, + NotFoundException, + UnauthorizedException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { UserEntity } from '../entities/user/user.entity.js'; @@ -23,93 +23,97 @@ import { IRequestWithCognitoInfo } from './cognito-decoded.interface.js'; @Injectable() export class AuthWithApiMiddleware implements NestMiddleware { - public constructor( - @InjectRepository(UserEntity) - private readonly userRepository: Repository, - ) {} + public constructor( + @InjectRepository(UserEntity) + private readonly userRepository: Repository, + ) {} - async use(req: IRequestWithCognitoInfo, _res: Response, next: (err?: any, res?: any) => void): Promise { - try { - await this.authenticateRequest(req); - next(); - } catch (error) { - Sentry.captureException(error); - this.handleAuthenticationError(error); - } - } + async use(req: IRequestWithCognitoInfo, _res: Response, next: (err?: any, res?: any) => void): Promise { + try { + await this.authenticateRequest(req); + next(); + } catch (error) { + Sentry.captureException(error); + this.handleAuthenticationError(error); + } + } - private async authenticateRequest(req: IRequestWithCognitoInfo): Promise { - const tokenFromCookie = this.getTokenFromCookie(req); - if (tokenFromCookie) { - await this.authenticateWithToken(tokenFromCookie, req); - } else { - await this.authenticateWithApiKey(req); - } - } + private async authenticateRequest(req: IRequestWithCognitoInfo): Promise { + const tokenFromCookie = this.getTokenFromCookie(req); + if (tokenFromCookie) { + await this.authenticateWithToken(tokenFromCookie, req); + } else { + await this.authenticateWithApiKey(req); + } + } - private getTokenFromCookie(req: Request): string | undefined { - return req.cookies?.[Constants.JWT_COOKIE_KEY_NAME]; - } + private getTokenFromCookie(req: Request): string | undefined { + return req.cookies?.[Constants.JWT_COOKIE_KEY_NAME]; + } - private handleAuthenticationError(error: any): void { - if (error instanceof HttpException || error instanceof UnauthorizedException) { - throw error; - } - throw new InternalServerErrorException(Messages.AUTHORIZATION_REJECTED); - } + private handleAuthenticationError(error: any): void { + if (error instanceof HttpException || error instanceof UnauthorizedException) { + throw error; + } + throw new InternalServerErrorException(Messages.AUTHORIZATION_REJECTED); + } - private async authenticateWithToken(tokenFromCookie: string, req: IRequestWithCognitoInfo): Promise { - try { - const jwtSecret = process.env.JWT_SECRET; - const data = jwt.verify(tokenFromCookie, jwtSecret) as jwt.JwtPayload; - const userId = data.id; - if (!userId) { - throw new UnauthorizedException('JWT verification failed'); - } - const addedScope: Array = data.scope; - if (addedScope && addedScope.length > 0) { - if (addedScope.includes(JwtScopesEnum.TWO_FA_ENABLE)) { - throw new BadRequestException(Messages.TWO_FA_REQUIRED); - } - } + private async authenticateWithToken(tokenFromCookie: string, req: IRequestWithCognitoInfo): Promise { + try { + const jwtSecret = process.env.JWT_SECRET; + const data = jwt.verify(tokenFromCookie, jwtSecret) as jwt.JwtPayload; + const userId = data.id; + if (!userId) { + throw new UnauthorizedException('JWT verification failed'); + } + const addedScope: Array = data.scope; + if (addedScope && addedScope.length > 0) { + if (addedScope.includes(JwtScopesEnum.TWO_FA_ENABLE)) { + throw new BadRequestException(Messages.TWO_FA_REQUIRED); + } + } - const payload = { - sub: userId, - email: data.email, - exp: data.exp, - iat: data.iat, - }; - if (!payload || isObjectEmpty(payload)) { - throw new UnauthorizedException('JWT verification failed'); - } - req.decoded = payload; - } catch (error) { - Sentry.captureException(error); - throw error; - } - } + const payload = { + sub: userId, + email: data.email, + exp: data.exp, + iat: data.iat, + }; + if (!payload || isObjectEmpty(payload)) { + throw new UnauthorizedException('JWT verification failed'); + } + req.decoded = payload; + } catch (error) { + Sentry.captureException(error); + throw error; + } + } - private async authenticateWithApiKey(req: IRequestWithCognitoInfo): Promise { - let apiKey = req.headers?.['x-api-key']; - if (Array.isArray(apiKey)) { - apiKey = apiKey[0]; - } - if (!apiKey) { - throw new UnauthorizedException(Messages.NO_AUTH_KEYS_FOUND); - } - const apiKeyHash = await Encryptor.processDataWithAlgorithm(apiKey, EncryptionAlgorithmEnum.sha256); - const foundUserByApiKey = await this.userRepository - .createQueryBuilder('user') - .innerJoinAndSelect('user.api_keys', 'api_key') - .where('api_key.hash = :hash', { hash: apiKeyHash }) - .getOne(); + private async authenticateWithApiKey(req: IRequestWithCognitoInfo): Promise { + let apiKey = req.headers?.['x-api-key']; + if (Array.isArray(apiKey)) { + apiKey = apiKey[0]; + } + if (!apiKey) { + throw new UnauthorizedException(Messages.NO_AUTH_KEYS_FOUND); + } + const apiKeyHash = await Encryptor.processDataWithAlgorithm(apiKey, EncryptionAlgorithmEnum.sha256); + const foundUserByApiKey = await this.userRepository + .createQueryBuilder('user') + .innerJoinAndSelect('user.api_keys', 'api_key') + .where('api_key.hash = :hash', { hash: apiKeyHash }) + .getOne(); - if (!foundUserByApiKey) { - throw new NotFoundException(Messages.NO_AUTH_KEYS_FOUND); - } - req.decoded = { - sub: foundUserByApiKey.id, - email: foundUserByApiKey.email, - }; - } + if (foundUserByApiKey.suspended) { + throw new UnauthorizedException(Messages.API_KEY_SUSPENDED); + } + + if (!foundUserByApiKey) { + throw new NotFoundException(Messages.NO_AUTH_KEYS_FOUND); + } + req.decoded = { + sub: foundUserByApiKey.id, + email: foundUserByApiKey.email, + }; + } } diff --git a/backend/src/exceptions/text/messages.ts b/backend/src/exceptions/text/messages.ts index 99ef4d3da..9184af32c 100644 --- a/backend/src/exceptions/text/messages.ts +++ b/backend/src/exceptions/text/messages.ts @@ -13,6 +13,7 @@ import { TableActionMethodEnum } from '../../enums/table-action-method-enum.js'; import { enumToString } from '../../helpers/enum-to-string.js'; import { toPrettyErrorsMsg } from '../../helpers/index.js'; export const Messages = { + API_KEY_SUSPENDED: 'API key is suspended', AI_REQUESTS_NOT_ALLOWED: 'AI requests are not allowed for this connection', AI_THREAD_NOT_FOUND: 'Thread with specified parameters not found', ACCOUNT_SUSPENDED: