Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import { CopilotRemoteAgentManager } from './github/copilotRemoteAgent';
import { CredentialStore } from './github/credentials';
import { FolderRepositoryManager } from './github/folderRepositoryManager';
import { FolderRepositoryManagerResolver } from './github/folderRepositoryManagerResolver';
import { IssueOverviewPanel } from './github/issueOverview';
import { OverviewRestorer } from './github/overviewRestorer';
import { PullRequestOverviewPanel } from './github/pullRequestOverview';
import { RepositoriesManager } from './github/repositoriesManager';
import { registerBuiltinGitProvider, registerLiveShareGitProvider } from './gitProviders/api';
import { GitHubContactServiceProvider } from './gitProviders/GitHubContactServiceProvider';
Expand Down Expand Up @@ -276,10 +278,21 @@ async function init(
if (e.provider.id !== AuthProvider.github && e.provider.id !== AuthProvider.githubEnterprise) {
return;
}
if (e.accountChanged) {
IssueOverviewPanel.clearAll();
PullRequestOverviewPanel.clearAll();
activePrViewCoordinator.clearForAuthChange();
createPrHelper.clearForAuthChange();
const reviewsCleanup = reviewsManager.clearForAuthChange();
issueStateManager.clearForAuthChange();
notificationsManager.clear();
reposManager.clearForAuthChange();
await reviewsCleanup;
}
await reposManager.refreshRepositories();
await Promise.all(reviewsManager.reviewManagers.map(reviewManager => reviewManager.updateState(true)));
tree.refreshAll(true);
await issueStateManager.refreshForAuthChange();
reviewsManager.refreshPullRequestsTree(!e.accountChanged);
await issueStateManager.refreshAfterAuthChange();
notificationsManager.refresh();
}));

Expand Down
44 changes: 39 additions & 5 deletions src/github/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,28 @@ interface ExistingSession {
scopes: string[];
}

export function hasAccountChanged(currentAccountId: string | undefined, newSession: vscode.AuthenticationSession | undefined): boolean {
return currentAccountId !== newSession?.account.id;
}

export async function findExistingSession(
authProviderId: AuthProvider,
getSession: AuthenticationSessionGetter = (providerId, scopes, options) => vscode.authentication.getSession(providerId, scopes, options),
): Promise<ExistingSession | undefined> {
// Establish the preferred account with the normal scopes before looking for broader sessions.
// Otherwise, a single broader session from another account can override the workspace preference.
// Establish the preferred account across all scopes before looking for its best session.
// A scope-specific lookup can otherwise fall back to the only account with those scopes.
const preferredSession = await getSession(authProviderId, [], { silent: true });
if (preferredSession) {
const scopesInPreferenceOrder = [SCOPES_WITH_ADDITIONAL, SCOPES_OLD, SCOPES_OLDEST];
for (const scopes of scopesInPreferenceOrder) {
const session = await getSession(authProviderId, scopes, { silent: true, account: preferredSession.account });
if (session) {
return { session, scopes };
}
}
return { session: preferredSession, scopes: [...preferredSession.scopes] };
}

const scopePreferences = [
{ scopes: SCOPES_OLD, broaderScopes: [SCOPES_WITH_ADDITIONAL] },
{ scopes: SCOPES_OLDEST, broaderScopes: [SCOPES_WITH_ADDITIONAL, SCOPES_OLD] },
Expand Down Expand Up @@ -88,12 +104,18 @@ interface AuthResult {
canceled: boolean;
}

export interface CredentialStoreSessionsChangeEvent extends vscode.AuthenticationSessionsChangeEvent {
accountChanged: boolean;
}

export class CredentialStore extends Disposable {
private static readonly ID = 'Authentication';
private _githubAPI: GitHub | undefined;
private _sessionId: string | undefined;
private _accountId: string | undefined;
private _githubEnterpriseAPI: GitHub | undefined;
private _enterpriseSessionId: string | undefined;
private _enterpriseAccountId: string | undefined;
private _isInitialized: boolean = false;
private _onDidInitialize: vscode.EventEmitter<void> = new vscode.EventEmitter();
public readonly onDidInitialize: vscode.Event<void> = this._onDidInitialize.event;
Expand All @@ -108,7 +130,7 @@ export class CredentialStore extends Disposable {
// is invalidated again soon after re-auth will still trigger another prompt.
private static readonly AUTH_ERROR_COOLDOWN_MS = 60_000;

private _onDidChangeSessions: vscode.EventEmitter<vscode.AuthenticationSessionsChangeEvent> = new vscode.EventEmitter();
private _onDidChangeSessions: vscode.EventEmitter<CredentialStoreSessionsChangeEvent> = new vscode.EventEmitter();
public readonly onDidChangeSessions = this._onDidChangeSessions.event;

private _onDidGetSession: vscode.EventEmitter<void> = new vscode.EventEmitter();
Expand All @@ -130,6 +152,7 @@ export class CredentialStore extends Disposable {
return;
}
let sessionChanged = false;
let accountChanged = false;
if (currentProvider) {
const newSession = await this.getSession(currentProvider, { silent: true }, currentProvider === AuthProvider.github ? this._scopes : this._scopesEnterprise, false);
const currentSessionId = currentProvider === AuthProvider.github ? this._sessionId : this._enterpriseSessionId;
Expand All @@ -138,11 +161,15 @@ export class CredentialStore extends Disposable {
}
sessionChanged = true;
if (currentProvider === AuthProvider.github) {
accountChanged = hasAccountChanged(this._accountId, newSession.session);
this._githubAPI = undefined;
this._sessionId = undefined;
this._accountId = undefined;
} else {
accountChanged = hasAccountChanged(this._enterpriseAccountId, newSession.session);
this._githubEnterpriseAPI = undefined;
this._enterpriseSessionId = undefined;
this._enterpriseAccountId = undefined;
}
}
const promises: Promise<any>[] = [];
Expand All @@ -158,10 +185,10 @@ export class CredentialStore extends Disposable {
if (this.isAnyAuthenticated()) {
this._onDidGetSession.fire();
if (sessionChanged && !this._isSamling) {
this._onDidChangeSessions.fire(e);
this._onDidChangeSessions.fire({ ...e, accountChanged });
}
} else if (!this._isSamling) {
this._onDidChangeSessions.fire(e);
this._onDidChangeSessions.fire({ ...e, accountChanged });
}
}

Expand Down Expand Up @@ -196,6 +223,7 @@ export class CredentialStore extends Disposable {
const github = await this.createHub(token, authProviderId);
this._githubAPI = github;
this._sessionId = 'environment-token';
this._accountId = undefined;
if (!this._isInitialized) {
this._isInitialized = true;
this._onDidInitialize.fire();
Expand Down Expand Up @@ -261,8 +289,10 @@ export class CredentialStore extends Disposable {
if (session) {
if (!isEnterprise(authProviderId)) {
this._sessionId = session.id;
this._accountId = session.account.id;
} else {
this._enterpriseSessionId = session.id;
this._enterpriseAccountId = session.account.id;
}
let github: GitHub | undefined;
try {
Expand Down Expand Up @@ -413,6 +443,10 @@ export class CredentialStore extends Disposable {
return this._githubEnterpriseAPI;
}

public getAccountId(authProviderId: AuthProvider): string | undefined {
return isEnterprise(authProviderId) ? this._enterpriseAccountId : this._accountId;
}

public areScopesOld(authProviderId: AuthProvider): boolean {
if (!isEnterprise(authProviderId)) {
return !this.allScopesIncluded(this._scopes, SCOPES_OLD);
Expand Down
Loading