Skip to content

Commit 03d1c79

Browse files
author
Rachel Macfarlane
committed
Set authStatus to unavailable when unable to refresh token, #89200
1 parent 7040f41 commit 03d1c79

File tree

2 files changed

+74
-46
lines changed

2 files changed

+74
-46
lines changed

extensions/vscode-account/src/AADHelper.ts

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56';
1818
const tenant = 'organizations';
1919

2020
interface IToken {
21-
expiresIn: string; // How long access token is valid, in seconds
22-
accessToken: string;
21+
accessToken?: string; // When unable to refresh due to network problems, the access token becomes undefined
22+
23+
expiresIn?: string; // How long access token is valid, in seconds
2324
refreshToken: string;
2425

2526
accountName: string;
@@ -41,6 +42,7 @@ interface IStoredSession {
4142
id: string;
4243
refreshToken: string;
4344
scope: string; // Scopes are alphabetized and joined with a space
45+
accountName: string;
4446
}
4547

4648
function parseQuery(uri: vscode.Uri) {
@@ -93,7 +95,20 @@ export class AzureActiveDirectoryService {
9395
try {
9496
await this.refreshToken(session.refreshToken, session.scope);
9597
} catch (e) {
96-
this.handleTokenRefreshFailure(e, session.id, session.refreshToken, session.scope, false);
98+
if (e.message === REFRESH_NETWORK_FAILURE) {
99+
const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope);
100+
if (!didSucceedOnRetry) {
101+
this._tokens.push({
102+
accessToken: undefined,
103+
refreshToken: session.refreshToken,
104+
accountName: session.accountName,
105+
scope: session.scope,
106+
sessionId: session.id
107+
});
108+
}
109+
} else {
110+
await this.logout(session.id);
111+
}
97112
}
98113
});
99114

@@ -115,7 +130,8 @@ export class AzureActiveDirectoryService {
115130
return {
116131
id: token.sessionId,
117132
refreshToken: token.refreshToken,
118-
scope: token.scope
133+
scope: token.scope,
134+
accountName: token.accountName
119135
};
120136
});
121137

@@ -136,7 +152,11 @@ export class AzureActiveDirectoryService {
136152
await this.refreshToken(session.refreshToken, session.scope);
137153
didChange = true;
138154
} catch (e) {
139-
this.handleTokenRefreshFailure(e, session.id, session.refreshToken, session.scope, false);
155+
if (e.message === REFRESH_NETWORK_FAILURE) {
156+
// Ignore, will automatically retry on next poll.
157+
} else {
158+
await this.logout(session.id);
159+
}
140160
}
141161
}
142162
});
@@ -175,7 +195,7 @@ export class AzureActiveDirectoryService {
175195
private convertToSession(token: IToken): vscode.AuthenticationSession {
176196
return {
177197
id: token.sessionId,
178-
accessToken: () => Promise.resolve(token.accessToken),
198+
accessToken: () => !token.accessToken ? Promise.reject('Unavailable due to network problems') : Promise.resolve(token.accessToken),
179199
accountName: token.accountName,
180200
scopes: token.scope.split(' ')
181201
};
@@ -339,14 +359,21 @@ export class AzureActiveDirectoryService {
339359

340360
this.clearSessionTimeout(token.sessionId);
341361

342-
this._refreshTimeouts.set(token.sessionId, setTimeout(async () => {
343-
try {
344-
await this.refreshToken(token.refreshToken, scope);
345-
onDidChangeSessions.fire();
346-
} catch (e) {
347-
this.handleTokenRefreshFailure(e, token.sessionId, token.refreshToken, scope, true);
348-
}
349-
}, 1000 * (parseInt(token.expiresIn) - 30)));
362+
if (token.expiresIn) {
363+
this._refreshTimeouts.set(token.sessionId, setTimeout(async () => {
364+
try {
365+
await this.refreshToken(token.refreshToken, scope);
366+
onDidChangeSessions.fire();
367+
} catch (e) {
368+
if (e.message === REFRESH_NETWORK_FAILURE) {
369+
await this.handleRefreshNetworkError(token.sessionId, token.refreshToken, scope);
370+
} else {
371+
await this.logout(token.sessionId);
372+
onDidChangeSessions.fire();
373+
}
374+
}
375+
}, 1000 * (parseInt(token.expiresIn) - 30)));
376+
}
350377

351378
this.storeTokenData();
352379
}
@@ -480,40 +507,35 @@ export class AzureActiveDirectoryService {
480507
this.clearSessionTimeout(sessionId);
481508
}
482509

483-
private async handleTokenRefreshFailure(e: Error, sessionId: string, refreshToken: string, scope: string, sendChangeEvent: boolean): Promise<void> {
484-
if (e.message === REFRESH_NETWORK_FAILURE) {
485-
this.handleRefreshNetworkError(sessionId, refreshToken, scope);
486-
} else {
487-
await this.logout(sessionId);
488-
if (sendChangeEvent) {
489-
onDidChangeSessions.fire();
510+
private handleRefreshNetworkError(sessionId: string, refreshToken: string, scope: string, attempts: number = 1): Promise<boolean> {
511+
return new Promise((resolve, _) => {
512+
if (attempts === 3) {
513+
Logger.error('Token refresh failed after 3 attempts');
514+
return resolve(false);
490515
}
491-
}
492-
}
493516

494-
private handleRefreshNetworkError(sessionId: string, refreshToken: string, scope: string, attempts: number = 1) {
495-
if (attempts === 5) {
496-
Logger.error('Token refresh failed after 5 attempts');
497-
return;
498-
}
517+
if (attempts === 1) {
518+
const token = this._tokens.find(token => token.sessionId === sessionId);
519+
if (token) {
520+
token.accessToken = undefined;
521+
}
499522

500-
if (attempts === 1) {
501-
this.removeInMemorySessionData(sessionId);
502-
onDidChangeSessions.fire();
503-
}
523+
onDidChangeSessions.fire();
524+
}
504525

505-
const delayBeforeRetry = 5 * attempts * attempts;
526+
const delayBeforeRetry = 5 * attempts * attempts;
506527

507-
this.clearSessionTimeout(sessionId);
528+
this.clearSessionTimeout(sessionId);
508529

509-
this._refreshTimeouts.set(sessionId, setTimeout(async () => {
510-
try {
511-
await this.refreshToken(refreshToken, scope);
512-
onDidChangeSessions.fire();
513-
} catch (e) {
514-
await this.handleRefreshNetworkError(sessionId, refreshToken, scope, attempts + 1);
515-
}
516-
}, 1000 * delayBeforeRetry));
530+
this._refreshTimeouts.set(sessionId, setTimeout(async () => {
531+
try {
532+
await this.refreshToken(refreshToken, scope);
533+
return resolve(true);
534+
} catch (e) {
535+
return resolve(await this.handleRefreshNetworkError(sessionId, refreshToken, scope, attempts + 1));
536+
}
537+
}, 1000 * delayBeforeRetry));
538+
});
517539
}
518540

519541
public async logout(sessionId: string) {

src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
4848
const enum AuthStatus {
4949
Initializing = 'Initializing',
5050
SignedIn = 'SignedIn',
51-
SignedOut = 'SignedOut'
51+
SignedOut = 'SignedOut',
52+
Unavailable = 'Unavailable'
5253
}
5354
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
5455

@@ -176,9 +177,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
176177
this._activeAccount = account;
177178

178179
if (account) {
179-
const token = await account.accessToken();
180-
this.userDataAuthTokenService.setToken(token);
181-
this.authenticationState.set(AuthStatus.SignedIn);
180+
try {
181+
const token = await account.accessToken();
182+
this.userDataAuthTokenService.setToken(token);
183+
this.authenticationState.set(AuthStatus.SignedIn);
184+
} catch (e) {
185+
this.userDataAuthTokenService.setToken(undefined);
186+
this.authenticationState.set(AuthStatus.Unavailable);
187+
}
182188
} else {
183189
this.userDataAuthTokenService.setToken(undefined);
184190
this.authenticationState.set(AuthStatus.SignedOut);

0 commit comments

Comments
 (0)