@@ -8,7 +8,9 @@ import { env } from "@/env.mjs";
88import { OrgRole } from "@sourcebot/db" ;
99import { cookies } from "next/headers" ;
1010import { OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME } from "@/lib/constants" ;
11+ import { getTokenFromConfig } from '@sourcebot/crypto' ;
1112import { IntegrationIdentityProviderState } from "@/ee/features/permissionSyncing/types" ;
13+ import { GitHubIdentityProviderConfig , GitLabIdentityProviderConfig } from "@sourcebot/schemas/v3/index.type" ;
1214
1315const logger = createLogger ( 'web-ee-permission-syncing-actions' ) ;
1416
@@ -95,4 +97,92 @@ export const skipOptionalProvidersLink = async () => sew(async () => {
9597 maxAge : 365 * 24 * 60 * 60 , // 1 year in seconds
9698 } ) ;
9799 return true ;
98- } ) ;
100+ } ) ;
101+
102+ export const refreshOAuthToken = async (
103+ provider : string ,
104+ refreshToken : string ,
105+ userId : string
106+ ) : Promise < { accessToken : string ; refreshToken : string | null ; expiresAt : number } | null > => {
107+ try {
108+ // Load config and find the provider configuration
109+ const config = await loadConfig ( env . CONFIG_PATH ) ;
110+ const identityProviders = config ?. identityProviders ?? [ ] ;
111+
112+ const providerConfig = identityProviders . find (
113+ idp => idp . provider === provider
114+ ) as GitHubIdentityProviderConfig | GitLabIdentityProviderConfig ;
115+
116+ if ( ! providerConfig || ! ( 'clientId' in providerConfig ) || ! ( 'clientSecret' in providerConfig ) ) {
117+ logger . error ( `Provider config not found or invalid for: ${ provider } ` ) ;
118+ return null ;
119+ }
120+
121+ // Get client credentials from config
122+ const clientId = await getTokenFromConfig ( providerConfig . clientId ) ;
123+ const clientSecret = await getTokenFromConfig ( providerConfig . clientSecret ) ;
124+ const baseUrl = 'baseUrl' in providerConfig && providerConfig . baseUrl
125+ ? await getTokenFromConfig ( providerConfig . baseUrl )
126+ : undefined ;
127+
128+ let url : string ;
129+ if ( baseUrl ) {
130+ url = provider === 'github'
131+ ? `${ baseUrl } /login/oauth/access_token`
132+ : `${ baseUrl } /oauth/token` ;
133+ } else if ( provider === 'github' ) {
134+ url = 'https://github.com/login/oauth/access_token' ;
135+ } else if ( provider === 'gitlab' ) {
136+ url = 'https://gitlab.com/oauth/token' ;
137+ } else {
138+ logger . error ( `Unsupported provider for token refresh: ${ provider } ` ) ;
139+ return null ;
140+ }
141+
142+ const response = await fetch ( url , {
143+ method : 'POST' ,
144+ headers : {
145+ 'Content-Type' : 'application/x-www-form-urlencoded' ,
146+ 'Accept' : 'application/json' ,
147+ } ,
148+ body : new URLSearchParams ( {
149+ client_id : clientId ,
150+ client_secret : clientSecret ,
151+ grant_type : 'refresh_token' ,
152+ refresh_token : refreshToken ,
153+ } ) ,
154+ } ) ;
155+
156+ if ( ! response . ok ) {
157+ const errorText = await response . text ( ) ;
158+ logger . error ( `Failed to refresh ${ provider } token: ${ response . status } ${ errorText } ` ) ;
159+ return null ;
160+ }
161+
162+ const data = await response . json ( ) ;
163+
164+ const result = {
165+ accessToken : data . access_token ,
166+ refreshToken : data . refresh_token ?? null ,
167+ expiresAt : data . expires_in ? Math . floor ( Date . now ( ) / 1000 ) + data . expires_in : 0 ,
168+ } ;
169+
170+ const { prisma } = await import ( '@/prisma' ) ;
171+ await prisma . account . updateMany ( {
172+ where : {
173+ userId : userId ,
174+ provider : provider ,
175+ } ,
176+ data : {
177+ access_token : result . accessToken ,
178+ refresh_token : result . refreshToken ,
179+ expires_at : result . expiresAt ,
180+ } ,
181+ } ) ;
182+
183+ return result ;
184+ } catch ( error ) {
185+ logger . error ( `Error refreshing ${ provider } token:` , error ) ;
186+ return null ;
187+ }
188+ } ;
0 commit comments