Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcb9ab6a

Browse files
committed
WIP refactoring
1 parent0e8453b commitcb9ab6a

File tree

11 files changed

+1124
-939
lines changed

11 files changed

+1124
-939
lines changed

‎src/commands.ts‎

Lines changed: 146 additions & 173 deletions
Large diffs are not rendered by default.

‎src/core/mementoManager.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class MementoManager {
1313
* If the URL is falsey, then remove it as the last used URL and do not touch
1414
* the history.
1515
*/
16-
publicasyncsetUrl(url?:string):Promise<void>{
16+
publicasyncsetUrl(url:string|undefined):Promise<void>{
1717
awaitthis.memento.update("url",url);
1818
if(url){
1919
consthistory=this.withUrlHistory(url);

‎src/core/secretsManager.ts‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const OAUTH_TOKENS_KEY = "oauthTokens";
1515

1616
exporttypeStoredOAuthTokens=Omit<TokenResponse,"expires_in">&{
1717
expiry_timestamp:number;
18+
deployment_url:string;
1819
};
1920

2021
exportenumAuthAction{
@@ -29,7 +30,9 @@ export class SecretsManager {
2930
/**
3031
* Set or unset the last used token.
3132
*/
32-
publicasyncsetSessionToken(sessionToken?:string):Promise<void>{
33+
publicasyncsetSessionToken(
34+
sessionToken:string|undefined,
35+
):Promise<void>{
3336
if(sessionToken){
3437
awaitthis.secrets.store(SESSION_TOKEN_KEY,sessionToken);
3538
}else{
@@ -160,11 +163,4 @@ export class SecretsManager {
160163
}
161164
returnundefined;
162165
}
163-
164-
/**
165-
* Clear OAuth token data.
166-
*/
167-
publicasyncclearOAuthTokens():Promise<void>{
168-
awaitthis.secrets.delete(OAUTH_TOKENS_KEY);
169-
}
170166
}

‎src/extension.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Commands } from "./commands";
1212
import{ServiceContainer}from"./core/container";
1313
import{AuthAction}from"./core/secretsManager";
1414
import{CertificateError,getErrorDetail}from"./error";
15-
import{activateCoderOAuth}from"./oauth/oauthHelper";
15+
import{OAuthSessionManager}from"./oauth/sessionManager";
1616
import{CALLBACK_PATH}from"./oauth/utils";
1717
import{Remote}from"./remote/remote";
1818
import{toSafeHost}from"./util";
@@ -123,13 +123,13 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
123123
ctx.subscriptions,
124124
);
125125

126-
constoauthHelper=awaitactivateCoderOAuth(
126+
constoauthSessionManager=awaitOAuthSessionManager.create(
127127
url||"",
128128
secretsManager,
129129
output,
130130
ctx,
131131
);
132-
ctx.subscriptions.push(oauthHelper);
132+
ctx.subscriptions.push(oauthSessionManager);
133133

134134
// Listen for session token changes and sync state across all components
135135
ctx.subscriptions.push(
@@ -162,7 +162,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
162162
constcode=params.get("code");
163163
conststate=params.get("state");
164164
consterror=params.get("error");
165-
oauthHelper.handleCallback(code,state,error);
165+
oauthSessionManager.handleCallback(code,state,error);
166166
return;
167167
}
168168

@@ -316,7 +316,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
316316

317317
// Register globally available commands. Many of these have visibility
318318
// controlled by contexts, see `when` in the package.json.
319-
constcommands=newCommands(serviceContainer,client,oauthHelper);
319+
constcommands=newCommands(serviceContainer,client,oauthSessionManager);
320320
ctx.subscriptions.push(
321321
vscode.commands.registerCommand(
322322
"coder.login",

‎src/oauth/clientRegistry.ts‎

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
importtype{AxiosInstance}from"axios";
2+
3+
importtype{SecretsManager}from"../core/secretsManager";
4+
importtype{Logger}from"../logging/logger";
5+
6+
importtype{
7+
ClientRegistrationRequest,
8+
ClientRegistrationResponse,
9+
OAuthServerMetadata,
10+
}from"./types";
11+
12+
constAUTH_GRANT_TYPE="authorization_code"asconst;
13+
constRESPONSE_TYPE="code"asconst;
14+
constOAUTH_METHOD="client_secret_post"asconst;
15+
constCLIENT_NAME="VS Code Coder Extension";
16+
17+
/**
18+
* Manages OAuth client registration and persistence.
19+
*/
20+
exportclassOAuthClientRegistry{
21+
privateregistration:ClientRegistrationResponse|undefined;
22+
23+
constructor(
24+
privatereadonlyaxiosInstance:AxiosInstance,
25+
privatereadonlysecretsManager:SecretsManager,
26+
privatereadonlylogger:Logger,
27+
){}
28+
29+
/**
30+
* Load existing client registration from secure storage.
31+
* Should be called during initialization.
32+
*/
33+
asyncload():Promise<void>{
34+
constregistration=awaitthis.secretsManager.getOAuthClientRegistration();
35+
if(registration){
36+
this.registration=registration;
37+
this.logger.info("Loaded existing OAuth client:",registration.client_id);
38+
}
39+
}
40+
41+
/**
42+
* Get the current client registration if one exists.
43+
*/
44+
get():ClientRegistrationResponse|undefined{
45+
returnthis.registration;
46+
}
47+
48+
/**
49+
* Register a new OAuth client or return existing if still valid.
50+
* Re-registers if redirect URI has changed.
51+
*/
52+
asyncregister(
53+
metadata:OAuthServerMetadata,
54+
redirectUri:string,
55+
):Promise<ClientRegistrationResponse>{
56+
if(this.registration?.client_id){
57+
if(this.registration.redirect_uris.includes(redirectUri)){
58+
this.logger.info(
59+
"Using existing client registration:",
60+
this.registration.client_id,
61+
);
62+
returnthis.registration;
63+
}
64+
this.logger.info("Redirect URI changed, re-registering client");
65+
}
66+
67+
if(!metadata.registration_endpoint){
68+
thrownewError("Server does not support dynamic client registration");
69+
}
70+
71+
// "web" type since VS Code Secrets API allows secure client_secret storage (confidential client)
72+
constregistrationRequest:ClientRegistrationRequest={
73+
redirect_uris:[redirectUri],
74+
application_type:"web",
75+
grant_types:[AUTH_GRANT_TYPE],
76+
response_types:[RESPONSE_TYPE],
77+
client_name:CLIENT_NAME,
78+
token_endpoint_auth_method:OAUTH_METHOD,
79+
};
80+
81+
constresponse=awaitthis.axiosInstance.post<ClientRegistrationResponse>(
82+
metadata.registration_endpoint,
83+
registrationRequest,
84+
);
85+
86+
awaitthis.save(response.data);
87+
88+
returnresponse.data;
89+
}
90+
91+
/**
92+
* Save client registration to secure storage.
93+
*/
94+
privateasyncsave(registration:ClientRegistrationResponse):Promise<void>{
95+
awaitthis.secretsManager.setOAuthClientRegistration(registration);
96+
this.registration=registration;
97+
this.logger.info(
98+
"Saved OAuth client registration:",
99+
registration.client_id,
100+
);
101+
}
102+
103+
/**
104+
* Clear the current client registration from memory and storage.
105+
*/
106+
asyncclear():Promise<void>{
107+
awaitthis.secretsManager.setOAuthClientRegistration(undefined);
108+
this.registration=undefined;
109+
this.logger.info("Cleared OAuth client registration");
110+
}
111+
}

‎src/oauth/metadataClient.ts‎

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
importtype{AxiosInstance}from"axios";
2+
3+
importtype{Logger}from"../logging/logger";
4+
5+
importtype{OAuthServerMetadata}from"./types";
6+
7+
constOAUTH_DISCOVERY_ENDPOINT="/.well-known/oauth-authorization-server";
8+
9+
constAUTH_GRANT_TYPE="authorization_code"asconst;
10+
constREFRESH_GRANT_TYPE="refresh_token"asconst;
11+
constRESPONSE_TYPE="code"asconst;
12+
constOAUTH_METHOD="client_secret_post"asconst;
13+
constPKCE_CHALLENGE_METHOD="S256"asconst;
14+
15+
constREQUIRED_GRANT_TYPES=[AUTH_GRANT_TYPE,REFRESH_GRANT_TYPE]asconst;
16+
17+
/**
18+
* Client for discovering and validating OAuth server metadata.
19+
*/
20+
exportclassOAuthMetadataClient{
21+
constructor(
22+
privatereadonlyaxiosInstance:AxiosInstance,
23+
privatereadonlylogger:Logger,
24+
){}
25+
26+
/**
27+
* Check if a server supports OAuth by attempting to fetch the well-known endpoint.
28+
*/
29+
asynccheckOAuthSupport():Promise<boolean>{
30+
try{
31+
awaitthis.axiosInstance.get(OAUTH_DISCOVERY_ENDPOINT);
32+
this.logger.debug("Server supports OAuth");
33+
returntrue;
34+
}catch(error){
35+
this.logger.debug("Server does not support OAuth:",error);
36+
returnfalse;
37+
}
38+
}
39+
40+
/**
41+
* Fetch and validate OAuth server metadata.
42+
* Throws detailed errors if server doesn't meet OAuth 2.1 requirements.
43+
*/
44+
asyncgetMetadata():Promise<OAuthServerMetadata>{
45+
this.logger.info("Discovering OAuth endpoints...");
46+
47+
constresponse=awaitthis.axiosInstance.get<OAuthServerMetadata>(
48+
OAUTH_DISCOVERY_ENDPOINT,
49+
);
50+
51+
constmetadata=response.data;
52+
53+
this.validateRequiredEndpoints(metadata);
54+
this.validateGrantTypes(metadata);
55+
this.validateResponseTypes(metadata);
56+
this.validateAuthMethods(metadata);
57+
this.validatePKCEMethods(metadata);
58+
59+
this.logger.debug("OAuth endpoints discovered:",{
60+
authorization:metadata.authorization_endpoint,
61+
token:metadata.token_endpoint,
62+
registration:metadata.registration_endpoint,
63+
revocation:metadata.revocation_endpoint,
64+
});
65+
66+
returnmetadata;
67+
}
68+
69+
privatevalidateRequiredEndpoints(metadata:OAuthServerMetadata):void{
70+
if(
71+
!metadata.authorization_endpoint||
72+
!metadata.token_endpoint||
73+
!metadata.issuer
74+
){
75+
thrownewError(
76+
"OAuth server metadata missing required endpoints: "+
77+
JSON.stringify(metadata),
78+
);
79+
}
80+
}
81+
82+
privatevalidateGrantTypes(metadata:OAuthServerMetadata):void{
83+
if(
84+
!includesAllTypes(metadata.grant_types_supported,REQUIRED_GRANT_TYPES)
85+
){
86+
thrownewError(
87+
`Server does not support required grant types:${REQUIRED_GRANT_TYPES.join(", ")}. Supported:${metadata.grant_types_supported?.join(", ")||"none"}`,
88+
);
89+
}
90+
}
91+
92+
privatevalidateResponseTypes(metadata:OAuthServerMetadata):void{
93+
if(!includesAllTypes(metadata.response_types_supported,[RESPONSE_TYPE])){
94+
thrownewError(
95+
`Server does not support required response type:${RESPONSE_TYPE}. Supported:${metadata.response_types_supported?.join(", ")||"none"}`,
96+
);
97+
}
98+
}
99+
100+
privatevalidateAuthMethods(metadata:OAuthServerMetadata):void{
101+
if(
102+
!includesAllTypes(metadata.token_endpoint_auth_methods_supported,[
103+
OAUTH_METHOD,
104+
])
105+
){
106+
thrownewError(
107+
`Server does not support required auth method:${OAUTH_METHOD}. Supported:${metadata.token_endpoint_auth_methods_supported?.join(", ")||"none"}`,
108+
);
109+
}
110+
}
111+
112+
privatevalidatePKCEMethods(metadata:OAuthServerMetadata):void{
113+
if(
114+
!includesAllTypes(metadata.code_challenge_methods_supported,[
115+
PKCE_CHALLENGE_METHOD,
116+
])
117+
){
118+
thrownewError(
119+
`Server does not support required PKCE method:${PKCE_CHALLENGE_METHOD}. Supported:${metadata.code_challenge_methods_supported?.join(", ")||"none"}`,
120+
);
121+
}
122+
}
123+
}
124+
125+
/**
126+
* Check if an array includes all required types.
127+
* If the array is undefined, returns true (server didn't specify, assume all allowed).
128+
*/
129+
functionincludesAllTypes(
130+
arr:string[]|undefined,
131+
requiredTypes:readonlystring[],
132+
):boolean{
133+
if(arr===undefined){
134+
returntrue;
135+
}
136+
returnrequiredTypes.every((type)=>arr.includes(type));
137+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp