@@ -62,6 +62,30 @@ export type VercelCustomEnvironment = {
6262 } ;
6363} ;
6464
65+ export type VercelAPIResult < T > = {
66+ success : true ;
67+ data : T ;
68+ } | {
69+ success : false ;
70+ authInvalid : boolean ;
71+ error : string ;
72+ } ;
73+
74+ function isVercelAuthError ( error : unknown ) : boolean {
75+ if ( error && typeof error === 'object' && 'status' in error ) {
76+ const status = ( error as { status ?: number } ) . status ;
77+ return status === 401 || status === 403 ;
78+ }
79+ if ( error && typeof error === 'object' && 'response' in error ) {
80+ const response = ( error as { response ?: { status ?: number } } ) . response ;
81+ return response ?. status === 401 || response ?. status === 403 ;
82+ }
83+ if ( error && typeof error === 'string' && ( error . includes ( '401' ) || error . includes ( '403' ) ) ) {
84+ return true ;
85+ }
86+ return false ;
87+ }
88+
6589export class VercelIntegrationRepository {
6690 static async getVercelClient (
6791 integration : OrganizationIntegration & { tokenReference : SecretReference }
@@ -82,6 +106,30 @@ export class VercelIntegrationRepository {
82106 } ) ;
83107 }
84108
109+ static async validateVercelToken (
110+ integration : OrganizationIntegration & { tokenReference : SecretReference }
111+ ) : Promise < { isValid : boolean } > {
112+ try {
113+ const client = await this . getVercelClient ( integration ) ;
114+ await client . user . getAuthUser ( ) ;
115+ return { isValid : true } ;
116+ } catch ( error ) {
117+ const authInvalid = isVercelAuthError ( error ) ;
118+ if ( authInvalid ) {
119+ logger . debug ( "Vercel token validation failed - auth error" , {
120+ integrationId : integration . id ,
121+ error,
122+ } ) ;
123+ return { isValid : false } ;
124+ }
125+ logger . error ( "Vercel token validation failed - unexpected error" , {
126+ integrationId : integration . id ,
127+ error,
128+ } ) ;
129+ throw error ;
130+ }
131+ }
132+
85133 static async getTeamIdFromIntegration (
86134 integration : OrganizationIntegration & { tokenReference : SecretReference }
87135 ) : Promise < string | null > {
@@ -162,7 +210,7 @@ export class VercelIntegrationRepository {
162210 client : Vercel ,
163211 projectId : string ,
164212 teamId ?: string | null
165- ) : Promise < VercelCustomEnvironment [ ] > {
213+ ) : Promise < VercelAPIResult < VercelCustomEnvironment [ ] > > {
166214 try {
167215 const response = await client . environment . getV9ProjectsIdOrNameCustomEnvironments ( {
168216 idOrName : projectId ,
@@ -172,19 +220,28 @@ export class VercelIntegrationRepository {
172220 // The response contains environments array
173221 const environments = response . environments || [ ] ;
174222
175- return environments . map ( ( env : any ) => ( {
176- id : env . id ,
177- slug : env . slug ,
178- description : env . description ,
179- branchMatcher : env . branchMatcher ,
180- } ) ) ;
223+ return {
224+ success : true ,
225+ data : environments . map ( ( env : any ) => ( {
226+ id : env . id ,
227+ slug : env . slug ,
228+ description : env . description ,
229+ branchMatcher : env . branchMatcher ,
230+ } ) ) ,
231+ } ;
181232 } catch ( error ) {
233+ const authInvalid = isVercelAuthError ( error ) ;
182234 logger . error ( "Failed to fetch Vercel custom environments" , {
183235 projectId,
184236 teamId,
185237 error,
238+ authInvalid,
186239 } ) ;
187- return [ ] ;
240+ return {
241+ success : false ,
242+ authInvalid,
243+ error : error instanceof Error ? error . message : "Unknown error" ,
244+ } ;
188245 }
189246 }
190247
@@ -193,7 +250,7 @@ export class VercelIntegrationRepository {
193250 client : Vercel ,
194251 projectId : string ,
195252 teamId ?: string | null
196- ) : Promise < VercelEnvironmentVariable [ ] > {
253+ ) : Promise < VercelAPIResult < VercelEnvironmentVariable [ ] > > {
197254 try {
198255 const response = await client . projects . filterProjectEnvs ( {
199256 idOrName : projectId ,
@@ -203,26 +260,35 @@ export class VercelIntegrationRepository {
203260 // The response is a union type - check if it has envs array
204261 const envs = extractEnvs ( response ) ;
205262
206- return envs . map ( ( env : any ) => {
207- const type = env . type as VercelEnvironmentVariable [ "type" ] ;
208- // Secret and sensitive types cannot have their values retrieved
209- const isSecret = type === "secret" || type === "sensitive" ;
263+ return {
264+ success : true ,
265+ data : envs . map ( ( env : any ) => {
266+ const type = env . type as VercelEnvironmentVariable [ "type" ] ;
267+ // Secret and sensitive types cannot have their values retrieved
268+ const isSecret = type === "secret" || type === "sensitive" ;
210269
211- return {
212- id : env . id ,
213- key : env . key ,
214- type,
215- isSecret,
216- target : normalizeTarget ( env . target ) ,
217- } ;
218- } ) ;
270+ return {
271+ id : env . id ,
272+ key : env . key ,
273+ type,
274+ isSecret,
275+ target : normalizeTarget ( env . target ) ,
276+ } ;
277+ } ) ,
278+ } ;
219279 } catch ( error ) {
280+ const authInvalid = isVercelAuthError ( error ) ;
220281 logger . error ( "Failed to fetch Vercel environment variables" , {
221282 projectId,
222283 teamId,
223284 error,
285+ authInvalid,
224286 } ) ;
225- return [ ] ;
287+ return {
288+ success : false ,
289+ authInvalid,
290+ error : error instanceof Error ? error . message : "Unknown error" ,
291+ } ;
226292 }
227293 }
228294
@@ -294,15 +360,13 @@ export class VercelIntegrationRepository {
294360 client : Vercel ,
295361 teamId : string ,
296362 projectId ?: string // Optional: filter by project
297- ) : Promise <
298- Array < {
363+ ) : Promise < VercelAPIResult < Array < {
299364 id : string ;
300365 key : string ;
301366 type : string ;
302367 isSecret : boolean ;
303368 target : string [ ] ;
304- } >
305- > {
369+ } > > > {
306370 try {
307371 const response = await client . environment . listSharedEnvVariable ( {
308372 teamId,
@@ -311,27 +375,36 @@ export class VercelIntegrationRepository {
311375
312376 const envVars = response . data || [ ] ;
313377
314- return envVars . map ( ( env ) => {
315- const type = ( env . type as string ) || "plain" ;
316- const isSecret = type === "secret" || type === "sensitive" ;
378+ return {
379+ success : true ,
380+ data : envVars . map ( ( env ) => {
381+ const type = ( env . type as string ) || "plain" ;
382+ const isSecret = type === "secret" || type === "sensitive" ;
317383
318- return {
319- id : env . id as string ,
320- key : env . key as string ,
321- type,
322- isSecret,
323- target : Array . isArray ( env . target )
324- ? ( env . target as string [ ] )
325- : [ env . target ] . filter ( Boolean ) as string [ ] ,
326- } ;
327- } ) ;
384+ return {
385+ id : env . id as string ,
386+ key : env . key as string ,
387+ type,
388+ isSecret,
389+ target : Array . isArray ( env . target )
390+ ? ( env . target as string [ ] )
391+ : [ env . target ] . filter ( Boolean ) as string [ ] ,
392+ } ;
393+ } ) ,
394+ } ;
328395 } catch ( error ) {
396+ const authInvalid = isVercelAuthError ( error ) ;
329397 logger . error ( "Failed to fetch Vercel shared environment variables" , {
330398 teamId,
331399 projectId,
332400 error,
401+ authInvalid,
333402 } ) ;
334- return [ ] ;
403+ return {
404+ success : false ,
405+ authInvalid,
406+ error : error instanceof Error ? error . message : "Unknown error" ,
407+ } ;
335408 }
336409 }
337410
@@ -505,27 +578,92 @@ export class VercelIntegrationRepository {
505578 static async getVercelProjects (
506579 client : Vercel ,
507580 teamId ?: string | null
508- ) : Promise < Array < { id : string ; name : string } > > {
581+ ) : Promise < VercelAPIResult < Array < { id : string ; name : string } > > > {
509582 try {
510583 const response = await client . projects . getProjects ( {
511584 ...( teamId && { teamId } ) ,
512585 } ) ;
513586
514587 const projects = response . projects || [ ] ;
515588
516- return projects . map ( ( project : any ) => ( {
517- id : project . id ,
518- name : project . name ,
519- } ) ) ;
589+ return {
590+ success : true ,
591+ data : projects . map ( ( project : any ) => ( {
592+ id : project . id ,
593+ name : project . name ,
594+ } ) ) ,
595+ } ;
520596 } catch ( error ) {
597+ const authInvalid = isVercelAuthError ( error ) ;
521598 logger . error ( "Failed to fetch Vercel projects" , {
522599 teamId,
523600 error,
601+ authInvalid,
524602 } ) ;
525- return [ ] ;
603+ return {
604+ success : false ,
605+ authInvalid,
606+ error : error instanceof Error ? error . message : "Unknown error" ,
607+ } ;
526608 }
527609 }
528610
611+ static async updateVercelOrgIntegrationToken ( params : {
612+ integrationId : string ;
613+ accessToken : string ;
614+ tokenType ?: string ;
615+ teamId : string | null ;
616+ userId ?: string ;
617+ installationId ?: string ;
618+ raw ?: Record < string , any > ;
619+ } ) : Promise < void > {
620+ await $transaction ( prisma , async ( tx ) => {
621+ // Get the existing integration to find the token reference
622+ const integration = await tx . organizationIntegration . findUnique ( {
623+ where : { id : params . integrationId } ,
624+ include : { tokenReference : true } ,
625+ } ) ;
626+
627+ if ( ! integration ) {
628+ throw new Error ( "Vercel integration not found" ) ;
629+ }
630+
631+ const secretStore = getSecretStore ( integration . tokenReference . provider , {
632+ prismaClient : tx ,
633+ } ) ;
634+
635+ const secretValue : VercelSecret = {
636+ accessToken : params . accessToken ,
637+ tokenType : params . tokenType ,
638+ teamId : params . teamId ,
639+ userId : params . userId ,
640+ installationId : params . installationId ,
641+ raw : params . raw ,
642+ } ;
643+
644+ logger . debug ( "Updating Vercel secret" , {
645+ integrationId : params . integrationId ,
646+ teamId : params . teamId ,
647+ installationId : params . installationId ,
648+ } ) ;
649+
650+ // Update the secret with new token
651+ await secretStore . setSecret ( integration . tokenReference . key , secretValue ) ;
652+
653+ // Update integration metadata
654+ await tx . organizationIntegration . update ( {
655+ where : { id : params . integrationId } ,
656+ data : {
657+ integrationData : {
658+ teamId : params . teamId ,
659+ userId : params . userId ,
660+ installationId : params . installationId ,
661+ } as any ,
662+ } ,
663+ } ) ;
664+ } ) ;
665+ }
666+
529667 static async createVercelOrgIntegration ( params : {
530668 accessToken : string ;
531669 tokenType ?: string ;
0 commit comments