11import {
2- BadRequestException ,
3- HttpException ,
4- Injectable ,
5- InternalServerErrorException ,
6- NestMiddleware ,
7- NotFoundException ,
8- UnauthorizedException ,
2+ BadRequestException ,
3+ HttpException ,
4+ Injectable ,
5+ InternalServerErrorException ,
6+ NestMiddleware ,
7+ NotFoundException ,
8+ UnauthorizedException ,
99} from '@nestjs/common' ;
1010import { InjectRepository } from '@nestjs/typeorm' ;
1111import { UserEntity } from '../entities/user/user.entity.js' ;
@@ -23,93 +23,109 @@ import { IRequestWithCognitoInfo } from './cognito-decoded.interface.js';
2323
2424@Injectable ( )
2525export class AuthWithApiMiddleware implements NestMiddleware {
26- public constructor (
27- @InjectRepository ( UserEntity )
28- private readonly userRepository : Repository < UserEntity > ,
29- ) { }
26+ public constructor (
27+ @InjectRepository ( UserEntity )
28+ private readonly userRepository : Repository < UserEntity > ,
29+ ) { }
3030
31- async use ( req : IRequestWithCognitoInfo , _res : Response , next : ( err ?: any , res ?: any ) => void ) : Promise < void > {
32- try {
33- await this . authenticateRequest ( req ) ;
34- next ( ) ;
35- } catch ( error ) {
36- Sentry . captureException ( error ) ;
37- this . handleAuthenticationError ( error ) ;
38- }
39- }
31+ async use ( req : IRequestWithCognitoInfo , _res : Response , next : ( err ?: any , res ?: any ) => void ) : Promise < void > {
32+ try {
33+ await this . authenticateRequest ( req ) ;
34+ next ( ) ;
35+ } catch ( error ) {
36+ Sentry . captureException ( error ) ;
37+ this . handleAuthenticationError ( error ) ;
38+ }
39+ }
4040
41- private async authenticateRequest ( req : IRequestWithCognitoInfo ) : Promise < void > {
42- const tokenFromCookie = this . getTokenFromCookie ( req ) ;
43- if ( tokenFromCookie ) {
44- await this . authenticateWithToken ( tokenFromCookie , req ) ;
45- } else {
46- await this . authenticateWithApiKey ( req ) ;
47- }
48- }
41+ private async authenticateRequest ( req : IRequestWithCognitoInfo ) : Promise < void > {
42+ const tokenFromCookie = this . getTokenFromCookie ( req ) ;
43+ if ( tokenFromCookie ) {
44+ await this . authenticateWithToken ( tokenFromCookie , req ) ;
45+ } else {
46+ await this . authenticateWithApiKey ( req ) ;
47+ }
48+ }
4949
50- private getTokenFromCookie ( req : Request ) : string | undefined {
51- return req . cookies ?. [ Constants . JWT_COOKIE_KEY_NAME ] ;
52- }
50+ private getTokenFromCookie ( req : Request ) : string | undefined {
51+ return req . cookies ?. [ Constants . JWT_COOKIE_KEY_NAME ] ;
52+ }
5353
54- private handleAuthenticationError ( error : any ) : void {
55- if ( error instanceof HttpException || error instanceof UnauthorizedException ) {
56- throw error ;
57- }
58- throw new InternalServerErrorException ( Messages . AUTHORIZATION_REJECTED ) ;
59- }
54+ private handleAuthenticationError ( error : any ) : void {
55+ if ( error instanceof HttpException || error instanceof UnauthorizedException ) {
56+ throw error ;
57+ }
58+ throw new InternalServerErrorException ( Messages . AUTHORIZATION_REJECTED ) ;
59+ }
6060
61- private async authenticateWithToken ( tokenFromCookie : string , req : IRequestWithCognitoInfo ) : Promise < void > {
62- try {
63- const jwtSecret = process . env . JWT_SECRET ;
64- const data = jwt . verify ( tokenFromCookie , jwtSecret ) as jwt . JwtPayload ;
65- const userId = data . id ;
66- if ( ! userId ) {
67- throw new UnauthorizedException ( 'JWT verification failed' ) ;
68- }
69- const addedScope : Array < JwtScopesEnum > = data . scope ;
70- if ( addedScope && addedScope . length > 0 ) {
71- if ( addedScope . includes ( JwtScopesEnum . TWO_FA_ENABLE ) ) {
72- throw new BadRequestException ( Messages . TWO_FA_REQUIRED ) ;
73- }
74- }
61+ private async authenticateWithToken ( tokenFromCookie : string , req : IRequestWithCognitoInfo ) : Promise < void > {
62+ try {
63+ const jwtSecret = process . env . JWT_SECRET ;
64+ const data = jwt . verify ( tokenFromCookie , jwtSecret ) as jwt . JwtPayload ;
65+ const userId = data . id ;
7566
76- const payload = {
77- sub : userId ,
78- email : data . email ,
79- exp : data . exp ,
80- iat : data . iat ,
81- } ;
82- if ( ! payload || isObjectEmpty ( payload ) ) {
83- throw new UnauthorizedException ( 'JWT verification failed' ) ;
84- }
85- req . decoded = payload ;
86- } catch ( error ) {
87- Sentry . captureException ( error ) ;
88- throw error ;
89- }
90- }
67+ if ( ! userId ) {
68+ throw new UnauthorizedException ( 'JWT verification failed' ) ;
69+ }
9170
92- private async authenticateWithApiKey ( req : IRequestWithCognitoInfo ) : Promise < void > {
93- let apiKey = req . headers ?. [ 'x-api-key' ] ;
94- if ( Array . isArray ( apiKey ) ) {
95- apiKey = apiKey [ 0 ] ;
96- }
97- if ( ! apiKey ) {
98- throw new UnauthorizedException ( Messages . NO_AUTH_KEYS_FOUND ) ;
99- }
100- const apiKeyHash = await Encryptor . processDataWithAlgorithm ( apiKey , EncryptionAlgorithmEnum . sha256 ) ;
101- const foundUserByApiKey = await this . userRepository
102- . createQueryBuilder ( 'user' )
103- . innerJoinAndSelect ( 'user.api_keys' , 'api_key' )
104- . where ( 'api_key.hash = :hash' , { hash : apiKeyHash } )
105- . getOne ( ) ;
71+ const userExists = await this . userRepository . findOne ( { where : { id : userId } } ) ;
72+ if ( ! userExists ) {
73+ throw new UnauthorizedException ( 'JWT verification failed' ) ;
74+ }
10675
107- if ( ! foundUserByApiKey ) {
108- throw new NotFoundException ( Messages . NO_AUTH_KEYS_FOUND ) ;
109- }
110- req . decoded = {
111- sub : foundUserByApiKey . id ,
112- email : foundUserByApiKey . email ,
113- } ;
114- }
76+ if ( userExists . suspended ) {
77+ throw new UnauthorizedException ( Messages . ACCOUNT_SUSPENDED ) ;
78+ }
79+
80+ const addedScope : Array < JwtScopesEnum > = data . scope ;
81+ if ( addedScope && addedScope . length > 0 ) {
82+ if ( addedScope . includes ( JwtScopesEnum . TWO_FA_ENABLE ) ) {
83+ throw new BadRequestException ( Messages . TWO_FA_REQUIRED ) ;
84+ }
85+ }
86+
87+ const payload = {
88+ sub : userId ,
89+ email : data . email ,
90+ exp : data . exp ,
91+ iat : data . iat ,
92+ } ;
93+ if ( ! payload || isObjectEmpty ( payload ) ) {
94+ throw new UnauthorizedException ( 'JWT verification failed' ) ;
95+ }
96+ req . decoded = payload ;
97+ } catch ( error ) {
98+ Sentry . captureException ( error ) ;
99+ throw error ;
100+ }
101+ }
102+
103+ private async authenticateWithApiKey ( req : IRequestWithCognitoInfo ) : Promise < void > {
104+ let apiKey = req . headers ?. [ 'x-api-key' ] ;
105+ if ( Array . isArray ( apiKey ) ) {
106+ apiKey = apiKey [ 0 ] ;
107+ }
108+ if ( ! apiKey ) {
109+ throw new UnauthorizedException ( Messages . NO_AUTH_KEYS_FOUND ) ;
110+ }
111+ const apiKeyHash = await Encryptor . processDataWithAlgorithm ( apiKey , EncryptionAlgorithmEnum . sha256 ) ;
112+ const foundUserByApiKey = await this . userRepository
113+ . createQueryBuilder ( 'user' )
114+ . innerJoinAndSelect ( 'user.api_keys' , 'api_key' )
115+ . where ( 'api_key.hash = :hash' , { hash : apiKeyHash } )
116+ . getOne ( ) ;
117+
118+ if ( ! foundUserByApiKey ) {
119+ throw new NotFoundException ( Messages . NO_AUTH_KEYS_FOUND ) ;
120+ }
121+
122+ if ( foundUserByApiKey . suspended ) {
123+ throw new UnauthorizedException ( Messages . API_KEY_SUSPENDED ) ;
124+ }
125+
126+ req . decoded = {
127+ sub : foundUserByApiKey . id ,
128+ email : foundUserByApiKey . email ,
129+ } ;
130+ }
115131}
0 commit comments