@@ -240,6 +240,81 @@ describe('requireScope', () => {
240240 } )
241241} )
242242
243+ describe ( 'authMiddleware with async tokenValidator (real-world behavior)' , ( ) => {
244+ let storage , authMiddleware , requireAdmin
245+
246+ beforeEach ( ( ) => {
247+ storage = createMockStorage ( )
248+ // The real tokenValidator.validateToken is async — the mock must match
249+ const tokenValidator = {
250+ async validateToken ( rawToken ) {
251+ if ( rawToken === 'tt_validtoken00000000000000000000' ) {
252+ return { id : 'tok-1' , ownerEmail : 'admin@test.com' , name : 'Test' , scopes : null }
253+ }
254+ if ( rawToken === 'tt_scopedtoken000000000000000000000' ) {
255+ return { id : 'tok-2' , ownerEmail : 'admin@test.com' , name : 'Scoped' , scopes : [ 'roster:read' ] }
256+ }
257+ return null
258+ } ,
259+ touchLastUsed ( ) { }
260+ }
261+ const result = createAuthMiddleware (
262+ storage . readFromStorage . bind ( storage ) ,
263+ storage . writeToStorage . bind ( storage ) ,
264+ { tokenValidator }
265+ )
266+ authMiddleware = result . authMiddleware
267+ requireAdmin = result . requireAdmin
268+ } )
269+
270+ it ( 'resolves ownerEmail from async validateToken' , async ( ) => {
271+ const req = createMockReq ( {
272+ headers : { authorization : 'Bearer tt_validtoken00000000000000000000' }
273+ } )
274+ const res = createMockRes ( )
275+ let nextCalled = false
276+ await authMiddleware ( req , res , ( ) => { nextCalled = true } )
277+ expect ( nextCalled ) . toBe ( true )
278+ expect ( req . userEmail ) . toBe ( 'admin@test.com' )
279+ expect ( req . authMethod ) . toBe ( 'token' )
280+ } )
281+
282+ it ( 'grants admin to token owner who is in the admin allowlist' , async ( ) => {
283+ const req = createMockReq ( {
284+ headers : { authorization : 'Bearer tt_validtoken00000000000000000000' }
285+ } )
286+ const res = createMockRes ( )
287+ await authMiddleware ( req , res , ( ) => { } )
288+ expect ( req . isAdmin ) . toBe ( true )
289+
290+ // requireAdmin should pass
291+ const res2 = createMockRes ( )
292+ let adminNextCalled = false
293+ requireAdmin ( req , res2 , ( ) => { adminNextCalled = true } )
294+ expect ( adminNextCalled ) . toBe ( true )
295+ } )
296+
297+ it ( 'rejects invalid token with 401 when validateToken is async' , async ( ) => {
298+ const req = createMockReq ( {
299+ headers : { authorization : 'Bearer tt_garbage0000000000000000000000' }
300+ } )
301+ const res = createMockRes ( )
302+ let nextCalled = false
303+ await authMiddleware ( req , res , ( ) => { nextCalled = true } )
304+ expect ( nextCalled ) . toBe ( false )
305+ expect ( res . statusCode ) . toBe ( 401 )
306+ } )
307+
308+ it ( 'preserves scopes from async validateToken' , async ( ) => {
309+ const req = createMockReq ( {
310+ headers : { authorization : 'Bearer tt_scopedtoken000000000000000000000' }
311+ } )
312+ const res = createMockRes ( )
313+ await authMiddleware ( req , res , ( ) => { } )
314+ expect ( req . tokenScopes ) . toEqual ( [ 'roster:read' ] )
315+ } )
316+ } )
317+
243318describe ( 'proxySecretGuard with Bearer tokens' , ( ) => {
244319 let originalEnv , tokenValidator
245320
0 commit comments