@@ -195,6 +195,9 @@ describe('validateWorkflowAccess', () => {
195195 } )
196196
197197 it ( 'returns 404 for workspace api keys scoped to a different workspace' , async ( ) => {
198+ const request = new NextRequest ( `http://localhost:3000/api/workflows/${ WORKFLOW_ID } /status` , {
199+ headers : { 'x-api-key' : 'workspace-key' } ,
200+ } )
198201 const auth = {
199202 success : true ,
200203 userId : 'user-1' ,
@@ -204,21 +207,118 @@ describe('validateWorkflowAccess', () => {
204207 }
205208
206209 mockCheckHybridAuth . mockResolvedValue ( auth )
210+ mockAuthenticateApiKeyFromHeader . mockResolvedValue ( {
211+ success : false ,
212+ error : 'Invalid API key' ,
213+ } )
207214
208- const result = await validateWorkflowAccess ( createRequest ( ) , WORKFLOW_ID , {
215+ const result = await validateWorkflowAccess ( request , WORKFLOW_ID , {
209216 requireDeployment : false ,
210217 action : 'read' ,
211218 } )
212219
213220 expect ( result ) . toEqual ( {
214221 error : {
215- message : 'Workflow not found ' ,
216- status : 404 ,
222+ message : 'Unauthorized: Invalid API key ' ,
223+ status : 401 ,
217224 } ,
218225 } )
226+ expect ( mockAuthenticateApiKeyFromHeader ) . toHaveBeenCalledWith ( 'workspace-key' , {
227+ workspaceId : WORKSPACE_ID ,
228+ keyTypes : [ 'workspace' , 'personal' ] ,
229+ } )
219230 expect ( mockAuthorizeWorkflowByWorkspacePermission ) . not . toHaveBeenCalled ( )
220231 } )
221232
233+ it ( 'denies personal api keys when the workflow workspace disallows them' , async ( ) => {
234+ const request = new NextRequest ( `http://localhost:3000/api/workflows/${ WORKFLOW_ID } /status` , {
235+ headers : { 'x-api-key' : 'personal-key' } ,
236+ } )
237+ const auth = {
238+ success : true ,
239+ userId : 'user-1' ,
240+ authType : 'api_key' as const ,
241+ apiKeyType : 'personal' as const ,
242+ }
243+
244+ mockCheckHybridAuth . mockResolvedValue ( auth )
245+ mockAuthenticateApiKeyFromHeader . mockResolvedValue ( {
246+ success : false ,
247+ error : 'Invalid API key' ,
248+ } )
249+
250+ const result = await validateWorkflowAccess ( request , WORKFLOW_ID , {
251+ requireDeployment : false ,
252+ action : 'read' ,
253+ } )
254+
255+ expect ( result ) . toEqual ( {
256+ error : {
257+ message : 'Unauthorized: Invalid API key' ,
258+ status : 401 ,
259+ } ,
260+ } )
261+ expect ( mockAuthenticateApiKeyFromHeader ) . toHaveBeenCalledWith ( 'personal-key' , {
262+ workspaceId : WORKSPACE_ID ,
263+ keyTypes : [ 'workspace' , 'personal' ] ,
264+ } )
265+ expect ( mockAuthorizeWorkflowByWorkspacePermission ) . not . toHaveBeenCalled ( )
266+ } )
267+
268+ it ( 'allows personal api keys when the workflow workspace permits them' , async ( ) => {
269+ const workflow = createWorkflow ( { name : 'Personal Key Workflow' } )
270+ const request = new NextRequest ( `http://localhost:3000/api/workflows/${ WORKFLOW_ID } /status` , {
271+ headers : { 'x-api-key' : 'personal-key' } ,
272+ } )
273+ const auth = {
274+ success : true ,
275+ userId : 'user-1' ,
276+ authType : 'api_key' as const ,
277+ apiKeyType : 'personal' as const ,
278+ }
279+
280+ mockCheckHybridAuth . mockResolvedValue ( auth )
281+ mockGetActiveWorkflowRecord . mockResolvedValue ( workflow )
282+ mockAuthenticateApiKeyFromHeader . mockResolvedValue ( {
283+ success : true ,
284+ userId : 'user-1' ,
285+ userName : 'Personal Key User' ,
286+ userEmail : 'personal@example.com' ,
287+ keyId : 'key-1' ,
288+ keyType : 'personal' ,
289+ workspaceId : WORKSPACE_ID ,
290+ } )
291+
292+ const result = await validateWorkflowAccess ( request , WORKFLOW_ID , {
293+ requireDeployment : false ,
294+ action : 'read' ,
295+ } )
296+
297+ expect ( result ) . toEqual ( {
298+ workflow,
299+ auth : {
300+ success : true ,
301+ userId : 'user-1' ,
302+ workspaceId : WORKSPACE_ID ,
303+ userName : 'Personal Key User' ,
304+ userEmail : 'personal@example.com' ,
305+ authType : 'api_key' ,
306+ apiKeyType : 'personal' ,
307+ } ,
308+ } )
309+ expect ( mockAuthenticateApiKeyFromHeader ) . toHaveBeenCalledWith ( 'personal-key' , {
310+ workspaceId : WORKSPACE_ID ,
311+ keyTypes : [ 'workspace' , 'personal' ] ,
312+ } )
313+ expect ( mockUpdateApiKeyLastUsed ) . toHaveBeenCalledWith ( 'key-1' )
314+ expect ( mockAuthorizeWorkflowByWorkspacePermission ) . toHaveBeenCalledWith ( {
315+ workflowId : WORKFLOW_ID ,
316+ userId : 'user-1' ,
317+ action : 'read' ,
318+ workflow,
319+ } )
320+ } )
321+
222322 it ( 'preserves session auth semantics for accessible workflows' , async ( ) => {
223323 const workflow = createWorkflow ( { name : 'Session Workflow' } )
224324 const auth = { success : true , userId : 'user-1' , authType : 'session' as const }
@@ -242,6 +342,9 @@ describe('validateWorkflowAccess', () => {
242342
243343 it ( 'allows workspace api keys scoped to the same workspace' , async ( ) => {
244344 const workflow = createWorkflow ( { name : 'Scoped Workflow' } )
345+ const request = new NextRequest ( `http://localhost:3000/api/workflows/${ WORKFLOW_ID } /status` , {
346+ headers : { 'x-api-key' : 'workspace-key' } ,
347+ } )
245348 const auth = {
246349 success : true ,
247350 userId : 'user-1' ,
@@ -252,13 +355,34 @@ describe('validateWorkflowAccess', () => {
252355
253356 mockCheckHybridAuth . mockResolvedValue ( auth )
254357 mockGetActiveWorkflowRecord . mockResolvedValue ( workflow )
358+ mockAuthenticateApiKeyFromHeader . mockResolvedValue ( {
359+ success : true ,
360+ userId : 'user-1' ,
361+ keyId : 'key-1' ,
362+ keyType : 'workspace' ,
363+ workspaceId : WORKSPACE_ID ,
364+ } )
255365
256- const result = await validateWorkflowAccess ( createRequest ( ) , WORKFLOW_ID , {
366+ const result = await validateWorkflowAccess ( request , WORKFLOW_ID , {
257367 requireDeployment : false ,
258368 action : 'read' ,
259369 } )
260370
261- expect ( result ) . toEqual ( { workflow, auth } )
371+ expect ( result ) . toEqual ( {
372+ workflow,
373+ auth : {
374+ success : true ,
375+ userId : 'user-1' ,
376+ workspaceId : WORKSPACE_ID ,
377+ authType : 'api_key' ,
378+ apiKeyType : 'workspace' ,
379+ } ,
380+ } )
381+ expect ( mockAuthenticateApiKeyFromHeader ) . toHaveBeenCalledWith ( 'workspace-key' , {
382+ workspaceId : WORKSPACE_ID ,
383+ keyTypes : [ 'workspace' , 'personal' ] ,
384+ } )
385+ expect ( mockUpdateApiKeyLastUsed ) . toHaveBeenCalledWith ( 'key-1' )
262386 expect ( mockAuthorizeWorkflowByWorkspacePermission ) . toHaveBeenCalledWith ( {
263387 workflowId : WORKFLOW_ID ,
264388 userId : 'user-1' ,
0 commit comments