Adopt getAccounts API in GitHub Authentication (#221224)

This allows the GitHub Auth provider to take in a account per this proposal:

417dddb83f/src/vscode-dts/vscode.proposed.authGetSessions.d.ts (L35-L69)

Additionally, this makes an additional small change that allows `clearSessionPreference` to work in single-account auth providers.
This commit is contained in:
Tyler James Leonhardt
2024-07-08 15:19:40 -07:00
committed by GitHub
parent 6c907814bf
commit 1b24381b5c
5 changed files with 80 additions and 21 deletions

View File

@@ -173,6 +173,8 @@ const allFlows: IFlow[] = [
]);
if (existingLogin) {
searchParams.append('login', existingLogin);
} else {
searchParams.append('prompt', 'select_account');
}
// The extra toString, parse is apparently needed for env.openExternal
@@ -240,6 +242,8 @@ const allFlows: IFlow[] = [
]);
if (existingLogin) {
searchParams.append('login', existingLogin);
} else {
searchParams.append('prompt', 'select_account');
}
const loginUrl = baseUri.with({

View File

@@ -11,7 +11,7 @@ import { PromiseAdapter, arrayEquals, promiseFromEvent } from './common/utils';
import { ExperimentationTelemetry } from './common/experimentationService';
import { Log } from './common/logger';
import { crypto } from './node/crypto';
import { TIMED_OUT_ERROR, USER_CANCELLATION_ERROR } from './common/errors';
import { CANCELLATION_ERROR, TIMED_OUT_ERROR, USER_CANCELLATION_ERROR } from './common/errors';
interface SessionData {
id: string;
@@ -148,14 +148,17 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
return this._sessionChangeEmitter.event;
}
async getSessions(scopes?: string[]): Promise<vscode.AuthenticationSession[]> {
async getSessions(scopes: string[] | undefined, options?: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession[]> {
// For GitHub scope list, order doesn't matter so we immediately sort the scopes
const sortedScopes = scopes?.sort() || [];
this._logger.info(`Getting sessions for ${sortedScopes.length ? sortedScopes.join(',') : 'all scopes'}...`);
const sessions = await this._sessionsPromise;
const finalSessions = sortedScopes.length
? sessions.filter(session => arrayEquals([...session.scopes].sort(), sortedScopes))
const accountFilteredSessions = options?.account
? sessions.filter(session => session.account.label === options.account?.label)
: sessions;
const finalSessions = sortedScopes.length
? accountFilteredSessions.filter(session => arrayEquals([...session.scopes].sort(), sortedScopes))
: accountFilteredSessions;
this._logger.info(`Got ${finalSessions.length} sessions for ${sortedScopes?.join(',') ?? 'all scopes'}...`);
return finalSessions;
@@ -279,7 +282,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
this._logger.info(`Stored ${sessions.length} sessions!`);
}
public async createSession(scopes: string[]): Promise<vscode.AuthenticationSession> {
public async createSession(scopes: string[], options?: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession> {
try {
// For GitHub scope list, order doesn't matter so we use a sorted scope to determine
// if we've got a session already.
@@ -298,11 +301,59 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
const sessions = await this._sessionsPromise;
const accounts = new Set(sessions.map(session => session.account.label));
const existingLogin = accounts.size <= 1 ? sessions[0]?.account.label : await vscode.window.showQuickPick([...accounts], { placeHolder: 'Choose an account that you would like to log in to' });
let forcedLogin = options?.account?.label;
let backupLogin: string | undefined;
if (!forcedLogin) {
const accounts = new Set(sessions.map(session => session.account.label));
this._logger.info(`Found ${accounts.size} accounts.`);
// This helps us tell GitHub that we're already logged in to an account/accounts
// and should probably use it. The user _can_ sign in to a different account
// if they want to, in the browser, but this is a good default for the happy path.
if (accounts.size > 1) {
// If there are multiple accounts, we should prompt the user to choose one.
const newAccount = vscode.l10n.t('New account...');
const accountChoiceResult = await vscode.window.showQuickPick(
[...accounts, newAccount],
{ placeHolder: vscode.l10n.t('Choose an account that you would like to log in to') }
);
forcedLogin = accountChoiceResult === newAccount ? undefined : accountChoiceResult;
} else {
// If there is only one account, we can use that to seed the login, but
// we don't want to force the user to use it.
backupLogin = sessions[0]?.account.label;
}
}
this._logger.info(`Logging in with '${forcedLogin ? forcedLogin : 'any'}' account...`);
const scopeString = sortedScopes.join(' ');
const token = await this._githubServer.login(scopeString, existingLogin);
const token = await this._githubServer.login(scopeString, forcedLogin ?? backupLogin);
const session = await this.tokenToSession(token, scopes);
// If an account was specified, we should ensure that the token we got back is for that account.
if (forcedLogin) {
if (session.account.label !== forcedLogin) {
const keepNewAccount = vscode.l10n.t('Keep {0}', session.account.label);
const tryAgain = vscode.l10n.t('Login with {0}', forcedLogin);
const result = await vscode.window.showWarningMessage(
vscode.l10n.t('Incorrect account detected'),
{ modal: true, detail: vscode.l10n.t('The chosen account, {0}, does not match the requested account, {1}.', session.account.label, forcedLogin) },
keepNewAccount,
tryAgain
);
if (result === tryAgain) {
return await this.createSession(scopes, {
...options,
// The id doesn't matter here, we just need to pass the label through
account: { id: forcedLogin, label: forcedLogin }
});
}
// Cancelled result
if (!result) {
throw new Error(CANCELLATION_ERROR);
}
// Keep result continues on
}
}
this.afterSessionLoad(session);
const sessionIndex = sessions.findIndex(s => s.id === session.id || arrayEquals([...s.scopes].sort(), sortedScopes));