@@ -29,6 +29,7 @@ const {
2929 mockSendEmail,
3030 mockRenderOTPEmail,
3131 mockSetChatAuthCookie,
32+ mockIsEmailAllowed,
3233 mockGetStorageMethod,
3334 mockZodParse,
3435 mockAfterResponse,
@@ -48,6 +49,11 @@ const {
4849 const mockSendEmail = vi . fn ( )
4950 const mockRenderOTPEmail = vi . fn ( )
5051 const mockSetChatAuthCookie = vi . fn ( )
52+ const mockIsEmailAllowed = vi . fn ( ( email : string , allowedEmails : string [ ] ) => {
53+ if ( allowedEmails . includes ( email ) ) return true
54+ const domain = email . slice ( email . indexOf ( '@' ) + 1 )
55+ return allowedEmails . includes ( `@${ domain } ` )
56+ } )
5157 const mockGetStorageMethod = vi . fn ( )
5258 const mockZodParse = vi . fn ( )
5359 const mockAfterResponse = vi . fn ( )
@@ -62,6 +68,7 @@ const {
6268 mockSendEmail,
6369 mockRenderOTPEmail,
6470 mockSetChatAuthCookie,
71+ mockIsEmailAllowed,
6572 mockGetStorageMethod,
6673 mockZodParse,
6774 mockAfterResponse,
@@ -101,15 +108,7 @@ vi.mock('@/components/emails', () => ({
101108} ) )
102109
103110vi . mock ( '@/lib/core/security/deployment' , ( ) => ( {
104- isEmailAllowed : ( email : string , allowedEmails : string [ ] ) => {
105- if ( allowedEmails . includes ( email ) ) return true
106- const atIndex = email . indexOf ( '@' )
107- if ( atIndex > 0 ) {
108- const domain = email . substring ( atIndex + 1 )
109- if ( domain && allowedEmails . some ( ( allowed : string ) => allowed === `@${ domain } ` ) ) return true
110- }
111- return false
112- } ,
111+ isEmailAllowed : mockIsEmailAllowed ,
113112} ) )
114113
115114vi . mock ( '@/app/api/chat/utils' , ( ) => ( {
@@ -173,7 +172,7 @@ describe('Chat OTP API Route', () => {
173172
174173 /** Queues the chat-deployment row the route reads before touching OTP storage. */
175174 const queueDeployment = ( row : Record < string , unknown > ) => {
176- queueTableRows ( schemaMock . chat , [ row ] )
175+ queueTableRows ( schemaMock . chat , [ { allowedEmails : [ mockEmail ] , ... row } ] )
177176 }
178177
179178 const emailDeployment = {
@@ -483,6 +482,15 @@ describe('Chat OTP API Route', () => {
483482
484483 expect ( mockRedisGet ) . toHaveBeenCalledWith ( `otp:${ mockEmail } :${ mockChatId } ` )
485484 expect ( mockRedisDel ) . toHaveBeenCalledWith ( `otp:${ mockEmail } :${ mockChatId } ` )
485+ expect ( mockSetChatAuthCookie ) . toHaveBeenCalledWith (
486+ expect . anything ( ) ,
487+ expect . objectContaining ( {
488+ id : mockChatId ,
489+ authType : 'email' ,
490+ allowedEmails : [ mockEmail ] ,
491+ } ) ,
492+ mockEmail
493+ )
486494 expect ( dbChainMockFns . select ) . toHaveBeenCalledTimes ( 1 )
487495 } )
488496 } )
@@ -514,6 +522,22 @@ describe('Chat OTP API Route', () => {
514522 expect ( mockRedisGet ) . not . toHaveBeenCalled ( )
515523 expect ( mockSetChatAuthCookie ) . not . toHaveBeenCalled ( )
516524 } )
525+
526+ it ( 'rejects verification when the email is no longer allowed' , async ( ) => {
527+ mockIsEmailAllowed . mockReturnValueOnce ( false )
528+ queueDeployment ( { id : mockChatId , authType : 'email' } )
529+
530+ const request = new NextRequest ( 'http://localhost:3000/api/chat/test/otp' , {
531+ method : 'PUT' ,
532+ body : JSON . stringify ( { email : mockEmail , otp : mockOTP } ) ,
533+ } )
534+
535+ await PUT ( request , { params : Promise . resolve ( { identifier : mockIdentifier } ) } )
536+
537+ expect ( mockCreateErrorResponse ) . toHaveBeenCalledWith ( 'Email not authorized' , 403 )
538+ expect ( mockRedisGet ) . not . toHaveBeenCalled ( )
539+ expect ( mockSetChatAuthCookie ) . not . toHaveBeenCalled ( )
540+ } )
517541 } )
518542
519543 describe ( 'PUT - Verify OTP (Database path)' , ( ) => {
0 commit comments