@@ -16,12 +16,15 @@ import type {
1616TokenResponse ,
1717} from "./types" ;
1818
19- const OAUTH_GRANT_TYPE = "authorization_code" as const ;
20- const OAUTH_RESPONSE_TYPE = "code" as const ;
21- const OAUTH_AUTH_METHOD = "client_secret_post" as const ;
19+ const AUTH_GRANT_TYPE = "authorization_code" as const ;
20+ const REFRESH_GRANT_TYPE = "refresh_token" as const ;
21+ const RESPONSE_TYPE = "code" as const ;
22+ const OAUTH_METHOD = "client_secret_post" as const ;
2223const PKCE_CHALLENGE_METHOD = "S256" as const ;
2324const CLIENT_NAME = "VS Code Coder Extension" ;
2425
26+ const REQUIRED_GRANT_TYPES = [ AUTH_GRANT_TYPE , REFRESH_GRANT_TYPE ] as const ;
27+
2528export class CoderOAuthHelper {
2629private clientRegistration :ClientRegistrationResponse | undefined ;
2730private cachedMetadata :OAuthServerMetadata | undefined ;
@@ -46,7 +49,7 @@ export class CoderOAuthHelper {
4649logger ,
4750context ,
4851) ;
49- await helper . loadRegistration ( ) ;
52+ await helper . loadClientRegistration ( ) ;
5053return helper ;
5154}
5255private constructor (
@@ -67,13 +70,7 @@ export class CoderOAuthHelper {
6770
6871const response = await this . client
6972. getAxiosInstance ( )
70- . request < OAuthServerMetadata > ( {
71- url :"/.well-known/oauth-authorization-server" ,
72- method :"GET" ,
73- headers :{
74- Accept :"application/json" ,
75- } ,
76- } ) ;
73+ . get < OAuthServerMetadata > ( "/.well-known/oauth-authorization-server" ) ;
7774
7875const metadata = response . data ;
7976
@@ -89,33 +86,26 @@ export class CoderOAuthHelper {
8986}
9087
9188if (
92- ! includesAllTypes ( metadata . grant_types_supported , [
93- OAUTH_GRANT_TYPE ,
94- "refresh_token" ,
95- ] )
89+ ! includesAllTypes ( metadata . grant_types_supported , REQUIRED_GRANT_TYPES )
9690) {
9791throw new Error (
98- `Server does not support required grant types:authorization_code, refresh_token . Supported:${ metadata . grant_types_supported ?. join ( ", " ) || "none" } ` ,
92+ `Server does not support required grant types:${ REQUIRED_GRANT_TYPES . join ( ", " ) } . Supported:${ metadata . grant_types_supported ?. join ( ", " ) || "none" } ` ,
9993) ;
10094}
10195
102- if (
103- ! includesAllTypes ( metadata . response_types_supported , [
104- OAUTH_RESPONSE_TYPE ,
105- ] )
106- ) {
96+ if ( ! includesAllTypes ( metadata . response_types_supported , [ RESPONSE_TYPE ] ) ) {
10797throw new Error (
108- `Server does not support required response type:code . Supported:${ metadata . response_types_supported ?. join ( ", " ) || "none" } ` ,
98+ `Server does not support required response type:${ RESPONSE_TYPE } . Supported:${ metadata . response_types_supported ?. join ( ", " ) || "none" } ` ,
10999) ;
110100}
111101
112102if (
113103! includesAllTypes ( metadata . token_endpoint_auth_methods_supported , [
114- OAUTH_AUTH_METHOD ,
104+ OAUTH_METHOD ,
115105] )
116106) {
117107throw new Error (
118- `Server does not support required auth method:client_secret_post . Supported:${ metadata . token_endpoint_auth_methods_supported ?. join ( ", " ) || "none" } ` ,
108+ `Server does not support required auth method:${ OAUTH_METHOD } . Supported:${ metadata . token_endpoint_auth_methods_supported ?. join ( ", " ) || "none" } ` ,
119109) ;
120110}
121111
@@ -125,7 +115,7 @@ export class CoderOAuthHelper {
125115] )
126116) {
127117throw new Error (
128- `Server does not support required PKCE method:S256 . Supported:${ metadata . code_challenge_methods_supported ?. join ( ", " ) || "none" } ` ,
118+ `Server does not support required PKCE method:${ PKCE_CHALLENGE_METHOD } . Supported:${ metadata . code_challenge_methods_supported ?. join ( ", " ) || "none" } ` ,
129119) ;
130120}
131121
@@ -143,15 +133,15 @@ export class CoderOAuthHelper {
143133return `${ vscode . env . uriScheme } ://${ this . extensionId } ${ CALLBACK_PATH } ` ;
144134}
145135
146- private async loadRegistration ( ) :Promise < void > {
136+ private async loadClientRegistration ( ) :Promise < void > {
147137const registration = await this . secretsManager . getOAuthClientRegistration ( ) ;
148138if ( registration ) {
149139this . clientRegistration = registration ;
150140this . logger . info ( "Loaded existing OAuth client:" , registration . client_id ) ;
151141}
152142}
153143
154- private async saveRegistration (
144+ private async saveClientRegistration (
155145registration :ClientRegistrationResponse ,
156146) :Promise < void > {
157147await this . secretsManager . setOAuthClientRegistration ( registration ) ;
@@ -191,25 +181,21 @@ export class CoderOAuthHelper {
191181const registrationRequest :ClientRegistrationRequest = {
192182redirect_uris :[ redirectUri ] ,
193183application_type :"native" ,
194- grant_types :[ OAUTH_GRANT_TYPE ] ,
195- response_types :[ OAUTH_RESPONSE_TYPE ] ,
184+ grant_types :[ AUTH_GRANT_TYPE ] ,
185+ response_types :[ RESPONSE_TYPE ] ,
196186client_name :CLIENT_NAME ,
197- token_endpoint_auth_method :OAUTH_AUTH_METHOD ,
187+ token_endpoint_auth_method :OAUTH_METHOD ,
198188} ;
199189
200190const response = await this . client
201191. getAxiosInstance ( )
202- . request < ClientRegistrationResponse > ( {
203- url :metadata . registration_endpoint ,
204- method :"POST" ,
205- headers :{
206- "Content-Type" :"application/json" ,
207- Accept :"application/json" ,
208- } ,
209- data :registrationRequest ,
210- } ) ;
192+ . post < ClientRegistrationResponse > (
193+ metadata . registration_endpoint ,
194+ registrationRequest ,
195+ ) ;
196+
197+ await this . saveClientRegistration ( response . data ) ;
211198
212- await this . saveRegistration ( response . data ) ;
213199return response . data . client_id ;
214200}
215201
@@ -232,7 +218,7 @@ export class CoderOAuthHelper {
232218
233219const params :AuthorizationRequestParams = {
234220client_id :clientId ,
235- response_type :OAUTH_RESPONSE_TYPE ,
221+ response_type :RESPONSE_TYPE ,
236222redirect_uri :this . getRedirectUri ( ) ,
237223scope,
238224state,
@@ -268,12 +254,15 @@ export class CoderOAuthHelper {
268254
269255return new Promise < { code :string ; verifier :string } > (
270256( resolve , reject ) => {
257+ const timeoutMins = 5 ;
271258const timeout = setTimeout (
272259( ) => {
273260this . clearPendingAuth ( ) ;
274- reject ( new Error ( "OAuth flow timed out after 5 minutes" ) ) ;
261+ reject (
262+ new Error ( `OAuth flow timed out after${ timeoutMins } minutes` ) ,
263+ ) ;
275264} ,
276- 5 * 60 * 1000 ,
265+ timeoutMins * 60 * 1000 ,
277266) ;
278267
279268const clearPromise = ( ) => {
@@ -366,7 +355,7 @@ export class CoderOAuthHelper {
366355this . logger . info ( "Exchanging authorization code for token" ) ;
367356
368357const params :TokenRequestParams = {
369- grant_type :OAUTH_GRANT_TYPE ,
358+ grant_type :AUTH_GRANT_TYPE ,
370359code,
371360redirect_uri :this . getRedirectUri ( ) ,
372361client_id :this . clientRegistration . client_id ,
@@ -383,14 +372,10 @@ export class CoderOAuthHelper {
383372
384373const response = await this . client
385374. getAxiosInstance ( )
386- . request < TokenResponse > ( {
387- url :metadata . token_endpoint ,
388- method :"POST" ,
375+ . post < TokenResponse > ( metadata . token_endpoint , tokenRequest . toString ( ) , {
389376headers :{
390377"Content-Type" :"application/x-www-form-urlencoded" ,
391- Accept :"application/json" ,
392378} ,
393- data :tokenRequest . toString ( ) ,
394379} ) ;
395380
396381this . logger . info ( "Token exchange successful" ) ;