11import { RequestHandler } from 'express' ;
2- import { AuthenticationScheme } from '../authentication-scheme' ;
3- import { SecuritySchemeObject } from 'auto-oas/oas/v3.1' ;
42import {
5- bearerAuthenticationMiddleware ,
6- BearerAuthenticationMiddlewareOptions ,
7- } from '../middleware/bearer-authentication' ;
3+ AuthenticationSchemeOptions ,
4+ InlineAuthenticationScheme ,
5+ } from '../authentication-scheme' ;
6+ import { SecuritySchemeObject } from 'auto-oas/oas/v3.1' ;
7+ import { UnauthorizedError } from '../../error' ;
8+ import { BearerTokenValSan } from 'valsan/primitives' ;
9+ import { LogInterface } from '../../log' ;
10+ import { AuthenticatedRequest } from '../authenticated-request' ;
11+ import { ApiNextFunction , ApiResponse } from '../../router' ;
812
9- export interface BearerAuthenticationSchemeOptions
10- extends BearerAuthenticationMiddlewareOptions {
13+ export type CheckTokenFunction = ( token : string ) => Promise < boolean > ;
14+
15+ // eslint-disable-next-line max-len
16+ export interface BearerAuthenticationSchemeOptions extends AuthenticationSchemeOptions {
1117 /**
1218 * The name of the security scheme in OpenAPI
1319 * @default 'BearerAuth'
@@ -26,30 +32,40 @@ export interface BearerAuthenticationSchemeOptions
2632 * Appears in the OpenAPI documentation
2733 */
2834 description ?: string ;
35+
36+ /**
37+ * Function to validate the bearer token
38+ * Should return true if the token is valid, false otherwise
39+ */
40+ checkToken : CheckTokenFunction ;
41+
42+ /**
43+ * Optional logger for security events
44+ */
45+ log ?: LogInterface ;
2946}
3047
3148/**
3249 * Bearer token authentication scheme
3350 * Validates the Authorization header with Bearer token
3451 * Automatically generates OpenAPI security scheme documentation
3552 */
36- export class BearerAuthenticationScheme extends AuthenticationScheme {
53+ export class BearerAuthenticationScheme extends InlineAuthenticationScheme {
3754 public readonly type = 'http' as const ;
3855 public readonly schemeName : string ;
3956
40- private readonly bearerFormat : string ;
41- private readonly description ?: string ;
42- private readonly middlewareOptions : BearerAuthenticationMiddlewareOptions ;
57+ protected readonly bearerFormat : string ;
58+ protected readonly description ?: string ;
59+ protected readonly checkToken : CheckTokenFunction ;
60+ protected readonly log ?: LogInterface ;
4361
4462 constructor ( options : BearerAuthenticationSchemeOptions ) {
45- super ( ) ;
63+ super ( options ) ;
4664 this . schemeName = options . schemeName || 'BearerAuth' ;
4765 this . bearerFormat = options . bearerFormat || 'JWT' ;
4866 this . description = options . description ;
49- this . middlewareOptions = {
50- checkToken : options . checkToken ,
51- log : options . log ,
52- } ;
67+ this . checkToken = options . checkToken ;
68+ this . log = options . log ;
5369 }
5470
5571 public getSecurityScheme ( ) : SecuritySchemeObject {
@@ -66,7 +82,96 @@ export class BearerAuthenticationScheme extends AuthenticationScheme {
6682 return scheme ;
6783 }
6884
69- public getMiddleware ( ) : RequestHandler {
70- return bearerAuthenticationMiddleware ( this . middlewareOptions ) ;
85+ public override getMiddleware ( ) : RequestHandler {
86+ return async (
87+ request : AuthenticatedRequest ,
88+ response : ApiResponse ,
89+ next : ApiNextFunction
90+ ) => {
91+ const authHeader =
92+ request . headers [ 'authorization' ] ||
93+ request . headers [ 'Authorization' ] ;
94+
95+ if ( ! authHeader ) {
96+ const msg =
97+ 'Unauthorized access attempt from ' +
98+ request . ip +
99+ ' - No token provided' ;
100+ this . log ?. warn ( msg ) ;
101+
102+ throw new UnauthorizedError ( 'Authorization header is missing' , {
103+ scheme : 'Bearer' ,
104+ } ) ;
105+ }
106+
107+ const validationResult = await new BearerTokenValSan ( ) . run (
108+ authHeader
109+ ) ;
110+ let token : string ;
111+
112+ if ( validationResult . success ) {
113+ token = validationResult . data ! ;
114+ }
115+ else {
116+ // eslint-disable-next-line max-len
117+ const msg =
118+ 'Unauthorized access attempt from ' +
119+ request . ip +
120+ ' - Invalid token format' ;
121+ const details = JSON . stringify (
122+ validationResult . errors ,
123+ null ,
124+ 2
125+ ) ;
126+ this . log ?. warn ( msg , details ) ;
127+
128+ throw new UnauthorizedError (
129+ 'Bearer token is empty or invalid' ,
130+ {
131+ scheme : 'Bearer' ,
132+ details : validationResult . errors ,
133+ }
134+ ) ;
135+ }
136+
137+ if ( ! ( await this . checkToken ( token ) ) ) {
138+ // eslint-disable-next-line max-len
139+ const msg =
140+ 'Unauthorized access attempt from ' +
141+ request . ip +
142+ ' - Check token failed' ;
143+ this . log ?. warn ( msg ) ;
144+
145+ throw new UnauthorizedError ( 'Bearer token check failed' , {
146+ scheme : 'Bearer' ,
147+ } ) ;
148+ }
149+
150+ request . authenticated = true ;
151+ return next ( ) ;
152+ } ;
153+ }
154+
155+ public async authenticate ( credentials : string ) : Promise < void > {
156+ if ( ! credentials ) {
157+ throw new UnauthorizedError ( 'Auth token is missing' , {
158+ scheme : 'Bearer' ,
159+ } ) ;
160+ }
161+
162+ const validationResult = await new BearerTokenValSan ( ) . run ( credentials ) ;
163+
164+ if ( ! validationResult . success ) {
165+ throw new UnauthorizedError ( 'Bearer token is empty or invalid' , {
166+ scheme : 'Bearer' ,
167+ details : validationResult . errors ,
168+ } ) ;
169+ }
170+
171+ if ( ! ( await this . checkToken ( credentials ) ) ) {
172+ throw new UnauthorizedError ( 'Bearer token check failed' , {
173+ scheme : 'Bearer' ,
174+ } ) ;
175+ }
71176 }
72177}
0 commit comments