@@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
33import { clickupListsSelectorContract } from '@/lib/api/contracts/selectors'
44import { parseRequest } from '@/lib/api/server'
55import { authorizeCredentialUse } from '@/lib/auth/credential-access'
6+ import { validateAlphanumericId } from '@/lib/core/security/input-validation'
67import { generateRequestId } from '@/lib/core/utils/request'
78import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
89import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
@@ -18,66 +19,87 @@ interface ClickUpNamedResource {
1819}
1920
2021export const POST = withRouteHandler ( async ( request : NextRequest ) => {
21- const requestId = generateRequestId ( )
22- const parsed = await parseRequest ( clickupListsSelectorContract , request , { } )
23- if ( ! parsed . success ) return parsed . response
24- const { credential, workflowId, folderId, spaceId } = parsed . data . body
22+ try {
23+ const requestId = generateRequestId ( )
24+ const parsed = await parseRequest ( clickupListsSelectorContract , request , { } )
25+ if ( ! parsed . success ) return parsed . response
26+ const { credential, workflowId, folderId, spaceId } = parsed . data . body
2527
26- const authz = await authorizeCredentialUse ( request , {
27- credentialId : credential ,
28- workflowId,
29- } )
30- if ( ! authz . ok || ! authz . credentialOwnerUserId ) {
31- return NextResponse . json ( { error : authz . error || 'Unauthorized' } , { status : 403 } )
32- }
28+ if ( folderId ?. trim ( ) ) {
29+ const folderIdValidation = validateAlphanumericId ( folderId . trim ( ) , 'folderId' )
30+ if ( ! folderIdValidation . isValid ) {
31+ logger . error ( 'Invalid folderId' , { error : folderIdValidation . error } )
32+ return NextResponse . json ( { error : folderIdValidation . error } , { status : 400 } )
33+ }
34+ }
35+
36+ if ( spaceId ?. trim ( ) ) {
37+ const spaceIdValidation = validateAlphanumericId ( spaceId . trim ( ) , 'spaceId' )
38+ if ( ! spaceIdValidation . isValid ) {
39+ logger . error ( 'Invalid spaceId' , { error : spaceIdValidation . error } )
40+ return NextResponse . json ( { error : spaceIdValidation . error } , { status : 400 } )
41+ }
42+ }
3343
34- const accessToken = await refreshAccessTokenIfNeeded (
35- credential ,
36- authz . credentialOwnerUserId ,
37- requestId
38- )
39- if ( ! accessToken ) {
40- logger . error ( 'Failed to get access token' , {
44+ const authz = await authorizeCredentialUse ( request , {
4145 credentialId : credential ,
42- userId : authz . credentialOwnerUserId ,
46+ workflowId ,
4347 } )
44- return NextResponse . json (
45- { error : 'Could not retrieve access token' , authRequired : true } ,
46- { status : 401 }
47- )
48- }
48+ if ( ! authz . ok || ! authz . credentialOwnerUserId ) {
49+ return NextResponse . json ( { error : authz . error || 'Unauthorized' } , { status : 403 } )
50+ }
4951
50- const response = await fetch (
51- folderId ?. trim ( )
52- ? `${ CLICKUP_API_BASE_URL } /folder/${ encodeURIComponent ( folderId . trim ( ) ) } /list`
53- : `${ CLICKUP_API_BASE_URL } /space/${ encodeURIComponent ( ( spaceId ?? '' ) . trim ( ) ) } /list` ,
54- {
55- headers : {
56- Authorization : clickupAuthorizationHeader ( accessToken ) ,
57- Accept : 'application/json' ,
58- } ,
52+ const accessToken = await refreshAccessTokenIfNeeded (
53+ credential ,
54+ authz . credentialOwnerUserId ,
55+ requestId
56+ )
57+ if ( ! accessToken ) {
58+ logger . error ( 'Failed to get access token' , {
59+ credentialId : credential ,
60+ userId : authz . credentialOwnerUserId ,
61+ } )
62+ return NextResponse . json (
63+ { error : 'Could not retrieve access token' , authRequired : true } ,
64+ { status : 401 }
65+ )
5966 }
60- )
6167
62- if ( ! response . ok ) {
63- const errorData = await response . json ( ) . catch ( ( ) => ( { } ) )
64- logger . error ( 'Failed to fetch ClickUp lists' , {
65- status : response . status ,
66- error : errorData ,
67- } )
68- return NextResponse . json (
69- { error : 'Failed to fetch ClickUp lists' } ,
70- { status : response . status }
68+ const response = await fetch (
69+ folderId ?. trim ( )
70+ ? `${ CLICKUP_API_BASE_URL } /folder/${ encodeURIComponent ( folderId . trim ( ) ) } /list`
71+ : `${ CLICKUP_API_BASE_URL } /space/${ encodeURIComponent ( ( spaceId ?? '' ) . trim ( ) ) } /list` ,
72+ {
73+ headers : {
74+ Authorization : clickupAuthorizationHeader ( accessToken ) ,
75+ Accept : 'application/json' ,
76+ } ,
77+ }
7178 )
72- }
7379
74- const data = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
75- lists ?: ClickUpNamedResource [ ]
76- }
77- const lists = ( Array . isArray ( data . lists ) ? data . lists : [ ] ) . map ( ( item ) => ( {
78- id : String ( item . id ) ,
79- name : item . name || `List ${ item . id } ` ,
80- } ) )
80+ if ( ! response . ok ) {
81+ const errorData = await response . json ( ) . catch ( ( ) => ( { } ) )
82+ logger . error ( 'Failed to fetch ClickUp lists' , {
83+ status : response . status ,
84+ error : errorData ,
85+ } )
86+ return NextResponse . json (
87+ { error : 'Failed to fetch ClickUp lists' } ,
88+ { status : response . status }
89+ )
90+ }
8191
82- return NextResponse . json ( { lists } )
92+ const data = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
93+ lists ?: ClickUpNamedResource [ ]
94+ }
95+ const lists = ( Array . isArray ( data . lists ) ? data . lists : [ ] ) . map ( ( item ) => ( {
96+ id : String ( item . id ) ,
97+ name : item . name || `List ${ item . id } ` ,
98+ } ) )
99+
100+ return NextResponse . json ( { lists } )
101+ } catch ( error ) {
102+ logger . error ( 'Error fetching ClickUp lists' , error )
103+ return NextResponse . json ( { error : 'Failed to fetch ClickUp lists' } , { status : 500 } )
104+ }
83105} )
0 commit comments