Skip to content

Commit 1714f71

Browse files
Organize Errors in GitHub Auth and make sure no double prompting happens (microsoft#180734)
* Organize Errors in GitHub Auth and make sure no double prompting happens This mostly just moves some strings into variables... but this also fixes the GH Auth side of microsoft#180697 so you should only be asked once if you want to try a different way to log in. * add comments
1 parent af8b51e commit 1714f71

File tree

1 file changed

+30
-18
lines changed

1 file changed

+30
-18
lines changed

extensions/github-authentication/src/githubServer.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ import { fetching } from './node/fetch';
1616

1717
const CLIENT_ID = '01ab8ac9400c4e429b23';
1818
const GITHUB_TOKEN_URL = 'https://vscode.dev/codeExchangeProxyEndpoints/github/login/oauth/access_token';
19+
20+
// This is the error message that we throw if the login was cancelled for any reason. Extensions
21+
// calling `getSession` can handle this error to know that the user cancelled the login.
22+
const CANCELLATION_ERROR = 'Cancelled';
23+
// These error messages are internal and should not be shown to the user in any way.
24+
const TIMED_OUT_ERROR = 'Timed out';
25+
const USER_CANCELLATION_ERROR = 'User Cancelled';
1926
const NETWORK_ERROR = 'network error';
2027

2128
const REDIRECT_URL_STABLE = 'https://vscode.dev/redirect';
@@ -132,7 +139,7 @@ export class GitHubServer implements IGitHubServer {
132139
: vscode.l10n.t('You have not yet finished authorizing this extension to use GitHub. Would you like to keep trying?');
133140
const result = await vscode.window.showWarningMessage(message, yes, no);
134141
if (result !== yes) {
135-
throw new Error('Cancelled');
142+
throw new Error(CANCELLATION_ERROR);
136143
}
137144
};
138145

@@ -146,7 +153,7 @@ export class GitHubServer implements IGitHubServer {
146153
return await this.doLoginWithoutLocalServer(scopes, nonce, callbackUri);
147154
} catch (e) {
148155
this._logger.error(e);
149-
userCancelled = e.message ?? e === 'User Cancelled';
156+
userCancelled = e.message ?? e === USER_CANCELLATION_ERROR;
150157
}
151158
}
152159

@@ -163,8 +170,7 @@ export class GitHubServer implements IGitHubServer {
163170
await promptToContinue();
164171
return await this.doLoginWithLocalServer(scopes);
165172
} catch (e) {
166-
this._logger.error(e);
167-
userCancelled = e.message ?? e === 'User Cancelled';
173+
userCancelled = this.processLoginError(e);
168174
}
169175
}
170176

@@ -174,8 +180,7 @@ export class GitHubServer implements IGitHubServer {
174180
await promptToContinue();
175181
return await this.doLoginDeviceCodeFlow(scopes);
176182
} catch (e) {
177-
this._logger.error(e);
178-
userCancelled = e.message ?? e === 'User Cancelled';
183+
userCancelled = this.processLoginError(e);
179184
}
180185
}
181186

@@ -186,12 +191,11 @@ export class GitHubServer implements IGitHubServer {
186191
await promptToContinue();
187192
return await this.doLoginWithPat(scopes);
188193
} catch (e) {
189-
this._logger.error(e);
190-
userCancelled = e.message ?? e === 'User Cancelled';
194+
userCancelled = this.processLoginError(e);
191195
}
192196
}
193197

194-
throw new Error(userCancelled ? 'Cancelled' : 'No auth flow succeeded.');
198+
throw new Error(userCancelled ? CANCELLATION_ERROR : 'No auth flow succeeded.');
195199
}
196200

197201
private async doLoginWithoutLocalServer(scopes: string, nonce: string, callbackUri: vscode.Uri): Promise<string> {
@@ -232,8 +236,8 @@ export class GitHubServer implements IGitHubServer {
232236
try {
233237
return await Promise.race([
234238
codeExchangePromise.promise,
235-
new Promise<string>((_, reject) => setTimeout(() => reject('Timed out'), 300_000)), // 5min timeout
236-
promiseFromEvent<any, any>(token.onCancellationRequested, (_, __, reject) => { reject('User Cancelled'); }).promise
239+
new Promise<string>((_, reject) => setTimeout(() => reject(TIMED_OUT_ERROR), 300_000)), // 5min timeout
240+
promiseFromEvent<any, any>(token.onCancellationRequested, (_, __, reject) => { reject(USER_CANCELLATION_ERROR); }).promise
237241
]);
238242
} finally {
239243
this._pendingNonces.delete(scopes);
@@ -273,8 +277,8 @@ export class GitHubServer implements IGitHubServer {
273277
vscode.env.openExternal(vscode.Uri.parse(`http://127.0.0.1:${port}/signin?nonce=${encodeURIComponent(server.nonce)}`));
274278
const { code } = await Promise.race([
275279
server.waitForOAuthResponse(),
276-
new Promise<any>((_, reject) => setTimeout(() => reject('Timed out'), 300_000)), // 5min timeout
277-
promiseFromEvent<any, any>(token.onCancellationRequested, (_, __, reject) => { reject('User Cancelled'); }).promise
280+
new Promise<any>((_, reject) => setTimeout(() => reject(TIMED_OUT_ERROR), 300_000)), // 5min timeout
281+
promiseFromEvent<any, any>(token.onCancellationRequested, (_, __, reject) => { reject(USER_CANCELLATION_ERROR); }).promise
278282
]);
279283
codeToExchange = code;
280284
} finally {
@@ -317,7 +321,7 @@ export class GitHubServer implements IGitHubServer {
317321
}, button);
318322

319323
if (modalResult !== button) {
320-
throw new Error('User Cancelled');
324+
throw new Error(USER_CANCELLATION_ERROR);
321325
}
322326

323327
await vscode.env.clipboard.writeText(json.user_code);
@@ -340,14 +344,14 @@ export class GitHubServer implements IGitHubServer {
340344
}, button);
341345

342346
if (modalResult !== button) {
343-
throw new Error('User Cancelled');
347+
throw new Error(USER_CANCELLATION_ERROR);
344348
}
345349

346350
const description = `${vscode.env.appName} (${scopes})`;
347351
const uriToOpen = await vscode.env.asExternalUri(this.baseUri.with({ path: '/settings/tokens/new', query: `description=${description}&scopes=${scopes.split(' ').join(',')}` }));
348352
await vscode.env.openExternal(uriToOpen);
349353
const token = await vscode.window.showInputBox({ placeHolder: `ghp_1a2b3c4...`, prompt: `GitHub Personal Access Token - ${scopes}`, ignoreFocusOut: true });
350-
if (!token) { throw new Error('User Cancelled'); }
354+
if (!token) { throw new Error(USER_CANCELLATION_ERROR); }
351355

352356
const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user']
353357
const scopesList = scopes.split(' '); // Example: 'read:user repo user:email'
@@ -392,7 +396,7 @@ export class GitHubServer implements IGitHubServer {
392396
for (let i = 0; i < attempts; i++) {
393397
await new Promise(resolve => setTimeout(resolve, json.interval * 1000));
394398
if (token.isCancellationRequested) {
395-
throw new Error('User Cancelled');
399+
throw new Error(USER_CANCELLATION_ERROR);
396400
}
397401
let accessTokenResult;
398402
try {
@@ -423,7 +427,7 @@ export class GitHubServer implements IGitHubServer {
423427
return accessTokenJson.access_token;
424428
}
425429

426-
throw new Error('Cancelled');
430+
throw new Error(TIMED_OUT_ERROR);
427431
});
428432
}
429433

@@ -641,4 +645,12 @@ export class GitHubServer implements IGitHubServer {
641645
// No-op
642646
}
643647
}
648+
649+
private processLoginError(error: Error): boolean {
650+
if (error.message === CANCELLATION_ERROR) {
651+
throw error;
652+
}
653+
this._logger.error(error.message ?? error);
654+
return error.message === USER_CANCELLATION_ERROR;
655+
}
644656
}

0 commit comments

Comments
 (0)