@@ -10,6 +10,7 @@ describe('OAuth Token API Routes', () => {
1010 const mockGetUserId = vi . fn ( )
1111 const mockGetCredential = vi . fn ( )
1212 const mockRefreshTokenIfNeeded = vi . fn ( )
13+ const mockGetOAuthToken = vi . fn ( )
1314 const mockAuthorizeCredentialUse = vi . fn ( )
1415 const mockCheckHybridAuth = vi . fn ( )
1516
@@ -29,6 +30,7 @@ describe('OAuth Token API Routes', () => {
2930 getUserId : mockGetUserId ,
3031 getCredential : mockGetCredential ,
3132 refreshTokenIfNeeded : mockRefreshTokenIfNeeded ,
33+ getOAuthToken : mockGetOAuthToken ,
3234 } ) )
3335
3436 vi . doMock ( '@sim/logger' , ( ) => ( {
@@ -230,6 +232,140 @@ describe('OAuth Token API Routes', () => {
230232 expect ( response . status ) . toBe ( 401 )
231233 expect ( data ) . toHaveProperty ( 'error' , 'Failed to refresh access token' )
232234 } )
235+
236+ describe ( 'credentialAccountUserId + providerId path' , ( ) => {
237+ it ( 'should reject unauthenticated requests' , async ( ) => {
238+ mockCheckHybridAuth . mockResolvedValueOnce ( {
239+ success : false ,
240+ error : 'Authentication required' ,
241+ } )
242+
243+ const req = createMockRequest ( 'POST' , {
244+ credentialAccountUserId : 'target-user-id' ,
245+ providerId : 'google' ,
246+ } )
247+
248+ const { POST } = await import ( '@/app/api/auth/oauth/token/route' )
249+
250+ const response = await POST ( req )
251+ const data = await response . json ( )
252+
253+ expect ( response . status ) . toBe ( 401 )
254+ expect ( data ) . toHaveProperty ( 'error' , 'User not authenticated' )
255+ expect ( mockGetOAuthToken ) . not . toHaveBeenCalled ( )
256+ } )
257+
258+ it ( 'should reject API key authentication' , async ( ) => {
259+ mockCheckHybridAuth . mockResolvedValueOnce ( {
260+ success : true ,
261+ authType : 'api_key' ,
262+ userId : 'test-user-id' ,
263+ } )
264+
265+ const req = createMockRequest ( 'POST' , {
266+ credentialAccountUserId : 'test-user-id' ,
267+ providerId : 'google' ,
268+ } )
269+
270+ const { POST } = await import ( '@/app/api/auth/oauth/token/route' )
271+
272+ const response = await POST ( req )
273+ const data = await response . json ( )
274+
275+ expect ( response . status ) . toBe ( 401 )
276+ expect ( data ) . toHaveProperty ( 'error' , 'User not authenticated' )
277+ expect ( mockGetOAuthToken ) . not . toHaveBeenCalled ( )
278+ } )
279+
280+ it ( 'should reject internal JWT authentication' , async ( ) => {
281+ mockCheckHybridAuth . mockResolvedValueOnce ( {
282+ success : true ,
283+ authType : 'internal_jwt' ,
284+ userId : 'test-user-id' ,
285+ } )
286+
287+ const req = createMockRequest ( 'POST' , {
288+ credentialAccountUserId : 'test-user-id' ,
289+ providerId : 'google' ,
290+ } )
291+
292+ const { POST } = await import ( '@/app/api/auth/oauth/token/route' )
293+
294+ const response = await POST ( req )
295+ const data = await response . json ( )
296+
297+ expect ( response . status ) . toBe ( 401 )
298+ expect ( data ) . toHaveProperty ( 'error' , 'User not authenticated' )
299+ expect ( mockGetOAuthToken ) . not . toHaveBeenCalled ( )
300+ } )
301+
302+ it ( 'should reject requests for other users credentials' , async ( ) => {
303+ mockCheckHybridAuth . mockResolvedValueOnce ( {
304+ success : true ,
305+ authType : 'session' ,
306+ userId : 'attacker-user-id' ,
307+ } )
308+
309+ const req = createMockRequest ( 'POST' , {
310+ credentialAccountUserId : 'victim-user-id' ,
311+ providerId : 'google' ,
312+ } )
313+
314+ const { POST } = await import ( '@/app/api/auth/oauth/token/route' )
315+
316+ const response = await POST ( req )
317+ const data = await response . json ( )
318+
319+ expect ( response . status ) . toBe ( 403 )
320+ expect ( data ) . toHaveProperty ( 'error' , 'Unauthorized' )
321+ expect ( mockGetOAuthToken ) . not . toHaveBeenCalled ( )
322+ } )
323+
324+ it ( 'should allow session-authenticated users to access their own credentials' , async ( ) => {
325+ mockCheckHybridAuth . mockResolvedValueOnce ( {
326+ success : true ,
327+ authType : 'session' ,
328+ userId : 'test-user-id' ,
329+ } )
330+ mockGetOAuthToken . mockResolvedValueOnce ( 'valid-access-token' )
331+
332+ const req = createMockRequest ( 'POST' , {
333+ credentialAccountUserId : 'test-user-id' ,
334+ providerId : 'google' ,
335+ } )
336+
337+ const { POST } = await import ( '@/app/api/auth/oauth/token/route' )
338+
339+ const response = await POST ( req )
340+ const data = await response . json ( )
341+
342+ expect ( response . status ) . toBe ( 200 )
343+ expect ( data ) . toHaveProperty ( 'accessToken' , 'valid-access-token' )
344+ expect ( mockGetOAuthToken ) . toHaveBeenCalledWith ( 'test-user-id' , 'google' )
345+ } )
346+
347+ it ( 'should return 404 when credential not found for user' , async ( ) => {
348+ mockCheckHybridAuth . mockResolvedValueOnce ( {
349+ success : true ,
350+ authType : 'session' ,
351+ userId : 'test-user-id' ,
352+ } )
353+ mockGetOAuthToken . mockResolvedValueOnce ( null )
354+
355+ const req = createMockRequest ( 'POST' , {
356+ credentialAccountUserId : 'test-user-id' ,
357+ providerId : 'nonexistent-provider' ,
358+ } )
359+
360+ const { POST } = await import ( '@/app/api/auth/oauth/token/route' )
361+
362+ const response = await POST ( req )
363+ const data = await response . json ( )
364+
365+ expect ( response . status ) . toBe ( 404 )
366+ expect ( data . error ) . toContain ( 'No credential found' )
367+ } )
368+ } )
233369 } )
234370
235371 /**
0 commit comments