@@ -76,6 +76,93 @@ export async function refreshLinkedAccountTokens(
7676 return updatedTokens ;
7777}
7878
79+ type ProviderCredentials = {
80+ clientId : string ;
81+ clientSecret : string ;
82+ baseUrl ?: string ;
83+ } ;
84+
85+ /**
86+ * Get credentials from deprecated environment variables.
87+ * This is for backwards compatibility with deployments using env vars instead of config file.
88+ */
89+ function getDeprecatedEnvCredentials ( provider : string ) : ProviderCredentials | null {
90+ if ( provider === 'github' && env . AUTH_EE_GITHUB_CLIENT_ID && env . AUTH_EE_GITHUB_CLIENT_SECRET ) {
91+ return {
92+ clientId : env . AUTH_EE_GITHUB_CLIENT_ID ,
93+ clientSecret : env . AUTH_EE_GITHUB_CLIENT_SECRET ,
94+ baseUrl : env . AUTH_EE_GITHUB_BASE_URL ,
95+ } ;
96+ }
97+ if ( provider === 'gitlab' && env . AUTH_EE_GITLAB_CLIENT_ID && env . AUTH_EE_GITLAB_CLIENT_SECRET ) {
98+ return {
99+ clientId : env . AUTH_EE_GITLAB_CLIENT_ID ,
100+ clientSecret : env . AUTH_EE_GITLAB_CLIENT_SECRET ,
101+ baseUrl : env . AUTH_EE_GITLAB_BASE_URL ,
102+ } ;
103+ }
104+ return null ;
105+ }
106+
107+ async function tryRefreshToken (
108+ provider : string ,
109+ refreshToken : string ,
110+ credentials : ProviderCredentials
111+ ) : Promise < { accessToken : string ; refreshToken : string | null ; expiresAt : number } | null > {
112+ const { clientId, clientSecret, baseUrl } = credentials ;
113+
114+ let url : string ;
115+ if ( baseUrl ) {
116+ url = provider === 'github'
117+ ? `${ baseUrl } /login/oauth/access_token`
118+ : `${ baseUrl } /oauth/token` ;
119+ } else if ( provider === 'github' ) {
120+ url = 'https://github.com/login/oauth/access_token' ;
121+ } else if ( provider === 'gitlab' ) {
122+ url = 'https://gitlab.com/oauth/token' ;
123+ } else {
124+ logger . error ( `Unsupported provider for token refresh: ${ provider } ` ) ;
125+ return null ;
126+ }
127+
128+ // Build request body parameters
129+ const bodyParams : Record < string , string > = {
130+ client_id : clientId ,
131+ client_secret : clientSecret ,
132+ grant_type : 'refresh_token' ,
133+ refresh_token : refreshToken ,
134+ } ;
135+
136+ // GitLab requires redirect_uri to match the original authorization request
137+ // even when refreshing tokens. Use URL constructor to handle trailing slashes.
138+ if ( provider === 'gitlab' ) {
139+ bodyParams . redirect_uri = new URL ( '/api/auth/callback/gitlab' , env . AUTH_URL ) . toString ( ) ;
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 ( bodyParams ) ,
149+ } ) ;
150+
151+ if ( ! response . ok ) {
152+ const errorText = await response . text ( ) ;
153+ logger . debug ( `Failed to refresh ${ provider } token: ${ response . status } ${ errorText } ` ) ;
154+ return null ;
155+ }
156+
157+ const data = await response . json ( ) ;
158+
159+ return {
160+ accessToken : data . access_token ,
161+ refreshToken : data . refresh_token ?? null ,
162+ expiresAt : data . expires_in ? Math . floor ( Date . now ( ) / 1000 ) + data . expires_in : 0 ,
163+ } ;
164+ }
165+
79166export async function refreshOAuthToken (
80167 provider : string ,
81168 refreshToken : string ,
@@ -85,14 +172,24 @@ export async function refreshOAuthToken(
85172 const identityProviders = config ?. identityProviders ?? [ ] ;
86173
87174 const providerConfigs = identityProviders . filter ( idp => idp . provider === provider ) ;
175+
176+ // If no provider configs in the config file, try deprecated env vars
88177 if ( providerConfigs . length === 0 ) {
178+ const envCredentials = getDeprecatedEnvCredentials ( provider ) ;
179+ if ( envCredentials ) {
180+ logger . debug ( `Using deprecated env vars for ${ provider } token refresh` ) ;
181+ const result = await tryRefreshToken ( provider , refreshToken , envCredentials ) ;
182+ if ( result ) {
183+ return result ;
184+ }
185+ }
89186 logger . error ( `Provider config not found or invalid for: ${ provider } ` ) ;
90187 return null ;
91188 }
92189
93190 // Loop through all provider configs and return on first successful fetch
94191 //
95- // The reason we have to do this is because 1) we might have multiple providers of the same type (ex. we're connecting to multiple gitlab instances) and 2) there isn't
192+ // The reason we have to do this is because 1) we might have multiple providers of the same type (ex. we're connecting to multiple gitlab instances) and 2) there isn't
96193 // a trivial way to map a provider config to the associated Account object in the DB. The reason the config is involved at all here is because we need the client
97194 // id/secret in order to refresh the token, and that info is in the config. We could in theory bypass this by storing the client id/secret for the provider in the
98195 // Account table but we decided not to do that since these are secret. Instead, we simply try all of the client/id secrets for the associated provider type. This is safe
@@ -103,60 +200,12 @@ export async function refreshOAuthToken(
103200 const linkedAccountProviderConfig = providerConfig as GitHubIdentityProviderConfig | GitLabIdentityProviderConfig
104201 const clientId = await getTokenFromConfig ( linkedAccountProviderConfig . clientId ) ;
105202 const clientSecret = await getTokenFromConfig ( linkedAccountProviderConfig . clientSecret ) ;
106- const baseUrl = linkedAccountProviderConfig . baseUrl
107-
108- let url : string ;
109- if ( baseUrl ) {
110- url = provider === 'github'
111- ? `${ baseUrl } /login/oauth/access_token`
112- : `${ baseUrl } /oauth/token` ;
113- } else if ( provider === 'github' ) {
114- url = 'https://github.com/login/oauth/access_token' ;
115- } else if ( provider === 'gitlab' ) {
116- url = 'https://gitlab.com/oauth/token' ;
117- } else {
118- logger . error ( `Unsupported provider for token refresh: ${ provider } ` ) ;
119- continue ;
120- }
121-
122- // Build request body parameters
123- const bodyParams : Record < string , string > = {
124- client_id : clientId ,
125- client_secret : clientSecret ,
126- grant_type : 'refresh_token' ,
127- refresh_token : refreshToken ,
128- } ;
129-
130- // GitLab requires redirect_uri to match the original authorization request
131- // even when refreshing tokens. Use URL constructor to handle trailing slashes.
132- if ( provider === 'gitlab' ) {
133- bodyParams . redirect_uri = new URL ( '/api/auth/callback/gitlab' , env . AUTH_URL ) . toString ( ) ;
134- }
203+ const baseUrl = linkedAccountProviderConfig . baseUrl ;
135204
136- const response = await fetch ( url , {
137- method : 'POST' ,
138- headers : {
139- 'Content-Type' : 'application/x-www-form-urlencoded' ,
140- 'Accept' : 'application/json' ,
141- } ,
142- body : new URLSearchParams ( bodyParams ) ,
143- } ) ;
144-
145- if ( ! response . ok ) {
146- const errorText = await response . text ( ) ;
147- logger . debug ( `Failed to refresh ${ provider } token with config: ${ response . status } ${ errorText } ` ) ;
148- continue ;
205+ const result = await tryRefreshToken ( provider , refreshToken , { clientId, clientSecret, baseUrl } ) ;
206+ if ( result ) {
207+ return result ;
149208 }
150-
151- const data = await response . json ( ) ;
152-
153- const result = {
154- accessToken : data . access_token ,
155- refreshToken : data . refresh_token ?? null ,
156- expiresAt : data . expires_in ? Math . floor ( Date . now ( ) / 1000 ) + data . expires_in : 0 ,
157- } ;
158-
159- return result ;
160209 } catch ( configError ) {
161210 logger . debug ( `Error trying provider config for ${ provider } :` , configError ) ;
162211 continue ;
0 commit comments