Move 'incorrect account' handling into core (#224872)

So that all auth providers can take advantage of this logic. This basically will do a light enforcement that the account you signed in to matches the account that was requested (if it was specified).

This is needed for finalization.
This commit is contained in:
Tyler James Leonhardt
2024-08-05 12:45:11 -07:00
committed by GitHub
parent 02b638ae27
commit 04bcb01ddf
2 changed files with 43 additions and 36 deletions

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 { CANCELLATION_ERROR, TIMED_OUT_ERROR, USER_CANCELLATION_ERROR } from './common/errors';
import { TIMED_OUT_ERROR, USER_CANCELLATION_ERROR } from './common/errors';
interface SessionData {
id: string;
@@ -316,39 +316,13 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
const sessions = await this._sessionsPromise;
const forcedLogin = options?.account?.label;
const backupLogin = sessions[0]?.account.label;
this._logger.info(`Logging in with '${forcedLogin ? forcedLogin : 'any'}' account...`);
// First we use the account specified in the options, otherwise we use the first account we have to seed auth.
const loginWith = options?.account?.label ?? sessions[0]?.account.label;
this._logger.info(`Logging in with '${loginWith ? loginWith : 'any'}' account...`);
const scopeString = sortedScopes.join(' ');
const token = await this._githubServer.login(scopeString, forcedLogin ?? backupLogin);
const token = await this._githubServer.login(scopeString, loginWith);
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(

View File

@@ -19,6 +19,7 @@ import { IAuthenticationUsageService } from 'vs/workbench/services/authenticatio
import { getAuthenticationProviderActivationEvent } from 'vs/workbench/services/authentication/browser/authenticationService';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { CancellationError } from 'vs/base/common/errors';
interface AuthenticationForceNewSessionOptions {
detail?: string;
@@ -159,6 +160,31 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
return result ?? false;
}
private async continueWithIncorrectAccountPrompt(chosenAccountLabel: string, requestedAccountLabel: string): Promise<boolean> {
const result = await this.dialogService.prompt({
message: nls.localize('incorrectAccount', "Incorrect account detected"),
detail: nls.localize('incorrectAccountDetail', "The chosen account, {0}, does not match the requested account, {1}.", chosenAccountLabel, requestedAccountLabel),
type: Severity.Warning,
cancelButton: true,
buttons: [
{
label: nls.localize('keep', 'Keep {0}', chosenAccountLabel),
run: () => chosenAccountLabel
},
{
label: nls.localize('loginWith', 'Login with {0}', requestedAccountLabel),
run: () => requestedAccountLabel
}
],
});
if (!result.result) {
throw new CancellationError();
}
return result.result === chosenAccountLabel;
}
private async doGetSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise<AuthenticationSession | undefined> {
const sessions = await this.authenticationService.getSessions(providerId, scopes, options.account, true);
const provider = this.authenticationService.getProvider(providerId);
@@ -212,18 +238,25 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
throw new Error('User did not consent to login.');
}
let session;
let session: AuthenticationSession;
if (sessions?.length && !options.forceNewSession) {
session = provider.supportsMultipleAccounts && !options.account
? await this.authenticationExtensionsService.selectSession(providerId, extensionId, extensionName, scopes, sessions)
: sessions[0];
} else {
let account: AuthenticationSessionAccount | undefined = options.account;
if (!account) {
let accountToCreate: AuthenticationSessionAccount | undefined = options.account;
if (!accountToCreate) {
const sessionIdToRecreate = this.authenticationExtensionsService.getSessionPreference(providerId, extensionId, scopes);
account = sessionIdToRecreate ? sessions.find(session => session.id === sessionIdToRecreate)?.account : undefined;
accountToCreate = sessionIdToRecreate ? sessions.find(session => session.id === sessionIdToRecreate)?.account : undefined;
}
session = await this.authenticationService.createSession(providerId, scopes, { activateImmediate: true, account });
do {
session = await this.authenticationService.createSession(providerId, scopes, { activateImmediate: true, account: accountToCreate });
} while (
accountToCreate
&& accountToCreate.label !== session.account.label
&& !await this.continueWithIncorrectAccountPrompt(session.account.label, accountToCreate.label)
);
}
this.authenticationAccessService.updateAllowedExtensions(providerId, session.account.label, [{ id: extensionId, name: extensionName, allowed: true }]);