mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Better error handling in github auth provider
This commit is contained in:
parent
37f849dde0
commit
badd41f04a
|
@ -6,7 +6,7 @@
|
|||
import * as vscode from 'vscode';
|
||||
import * as uuid from 'uuid';
|
||||
import { keychain } from './common/keychain';
|
||||
import { GitHubServer } from './githubServer';
|
||||
import { GitHubServer, NETWORK_ERROR } from './githubServer';
|
||||
import Logger from './common/logger';
|
||||
|
||||
export const onDidChangeSessions = new vscode.EventEmitter<vscode.AuthenticationSessionsChangeEvent>();
|
||||
|
@ -38,13 +38,25 @@ export class GitHubAuthenticationProvider {
|
|||
private _githubServer = new GitHubServer();
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
this._sessions = await this.readSessions();
|
||||
try {
|
||||
this._sessions = await this.readSessions();
|
||||
} catch (e) {
|
||||
// Ignore, network request failed
|
||||
}
|
||||
|
||||
await this.validateSessions();
|
||||
this.pollForChange();
|
||||
}
|
||||
|
||||
private pollForChange() {
|
||||
setTimeout(async () => {
|
||||
const storedSessions = await this.readSessions();
|
||||
let storedSessions: vscode.AuthenticationSession[];
|
||||
try {
|
||||
storedSessions = await this.readSessions();
|
||||
} catch (e) {
|
||||
// Ignore, network request failed
|
||||
return;
|
||||
}
|
||||
|
||||
const added: string[] = [];
|
||||
const removed: string[] = [];
|
||||
|
@ -53,6 +65,7 @@ export class GitHubAuthenticationProvider {
|
|||
const matchesExisting = this._sessions.some(s => s.id === session.id);
|
||||
// Another window added a session to the keychain, add it to our state as well
|
||||
if (!matchesExisting) {
|
||||
Logger.info('Adding session found in keychain');
|
||||
this._sessions.push(session);
|
||||
added.push(session.id);
|
||||
}
|
||||
|
@ -62,6 +75,7 @@ export class GitHubAuthenticationProvider {
|
|||
const matchesExisting = storedSessions.some(s => s.id === session.id);
|
||||
// Another window has logged out, remove from our state
|
||||
if (!matchesExisting) {
|
||||
Logger.info('Removing session no longer found in keychain');
|
||||
const sessionIndex = this._sessions.findIndex(s => s.id === session.id);
|
||||
if (sessionIndex > -1) {
|
||||
this._sessions.splice(sessionIndex, 1);
|
||||
|
@ -79,40 +93,62 @@ export class GitHubAuthenticationProvider {
|
|||
}, 1000 * 30);
|
||||
}
|
||||
|
||||
private async validateSessions(): Promise<void> {
|
||||
const validationPromises = this._sessions.map(async session => {
|
||||
try {
|
||||
await this._githubServer.validateToken(await session.getAccessToken());
|
||||
return session;
|
||||
} catch (e) {
|
||||
if (e === NETWORK_ERROR) {
|
||||
return session;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
const validSessions = (await Promise.all(validationPromises)).filter((x: vscode.AuthenticationSession | undefined): x is vscode.AuthenticationSession => !!x);
|
||||
if (validSessions.length !== this._sessions.length) {
|
||||
this._sessions = validSessions;
|
||||
this.storeSessions();
|
||||
}
|
||||
}
|
||||
|
||||
private async readSessions(): Promise<vscode.AuthenticationSession[]> {
|
||||
const storedSessions = await keychain.getToken();
|
||||
if (storedSessions) {
|
||||
try {
|
||||
const sessionData: (SessionData | OldSessionData)[] = JSON.parse(storedSessions);
|
||||
const sessionPromises = sessionData.map(async (session: SessionData | OldSessionData): Promise<vscode.AuthenticationSession | undefined> => {
|
||||
try {
|
||||
const needsUserInfo = isOldSessionData(session) || !session.account;
|
||||
let userInfo: { id: string, accountName: string };
|
||||
if (needsUserInfo) {
|
||||
userInfo = await this._githubServer.getUserInfo(session.accessToken);
|
||||
}
|
||||
|
||||
return {
|
||||
id: session.id,
|
||||
account: {
|
||||
displayName: isOldSessionData(session)
|
||||
? session.accountName
|
||||
: session.account?.displayName ?? userInfo!.accountName,
|
||||
id: isOldSessionData(session)
|
||||
? userInfo!.id
|
||||
: session.account?.id ?? userInfo!.id
|
||||
},
|
||||
scopes: session.scopes,
|
||||
getAccessToken: () => Promise.resolve(session.accessToken)
|
||||
};
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
const sessionPromises = sessionData.map(async (session: SessionData | OldSessionData): Promise<vscode.AuthenticationSession> => {
|
||||
const needsUserInfo = isOldSessionData(session) || !session.account;
|
||||
let userInfo: { id: string, accountName: string };
|
||||
if (needsUserInfo) {
|
||||
userInfo = await this._githubServer.getUserInfo(session.accessToken);
|
||||
}
|
||||
|
||||
return {
|
||||
id: session.id,
|
||||
account: {
|
||||
displayName: isOldSessionData(session)
|
||||
? session.accountName
|
||||
: session.account?.displayName ?? userInfo!.accountName,
|
||||
id: isOldSessionData(session)
|
||||
? userInfo!.id
|
||||
: session.account?.id ?? userInfo!.id
|
||||
},
|
||||
scopes: session.scopes,
|
||||
getAccessToken: () => Promise.resolve(session.accessToken)
|
||||
};
|
||||
});
|
||||
|
||||
return (await Promise.all(sessionPromises)).filter((x: vscode.AuthenticationSession | undefined): x is vscode.AuthenticationSession => !!x);
|
||||
return Promise.all(sessionPromises);
|
||||
} catch (e) {
|
||||
if (e === NETWORK_ERROR) {
|
||||
return [];
|
||||
}
|
||||
|
||||
Logger.error(`Error reading sessions: ${e}`);
|
||||
await keychain.deleteToken();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ import { PromiseAdapter, promiseFromEvent } from './common/utils';
|
|||
import Logger from './common/logger';
|
||||
import ClientRegistrar, { ClientDetails } from './common/clientRegistrar';
|
||||
|
||||
export const NETWORK_ERROR = 'network error';
|
||||
|
||||
class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.UriHandler {
|
||||
public handleUri(uri: vscode.Uri) {
|
||||
this.fire(uri);
|
||||
|
@ -145,7 +147,7 @@ export class GitHubServer {
|
|||
Logger.info('Got account info!');
|
||||
resolve({ id: json.id, accountName: json.login });
|
||||
} else {
|
||||
Logger.error('Getting account info failed');
|
||||
Logger.error(`Getting account info failed: ${result.statusMessage}`);
|
||||
reject(new Error(result.statusMessage));
|
||||
}
|
||||
});
|
||||
|
@ -153,7 +155,52 @@ export class GitHubServer {
|
|||
|
||||
post.end();
|
||||
post.on('error', err => {
|
||||
reject(err);
|
||||
Logger.error(err.message);
|
||||
reject(new Error(NETWORK_ERROR));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async validateToken(token: string): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`));
|
||||
const clientDetails = ClientRegistrar.getClientDetails(callbackUri);
|
||||
const detailsString = `${clientDetails.id}:${clientDetails.secret}`;
|
||||
|
||||
const payload = JSON.stringify({ access_token: token });
|
||||
|
||||
Logger.info('Validating token...');
|
||||
const post = https.request({
|
||||
host: 'api.github.com',
|
||||
path: `/applications/${clientDetails.id}/token`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(detailsString).toString('base64')}`,
|
||||
'User-Agent': 'Visual-Studio-Code',
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(payload)
|
||||
}
|
||||
}, result => {
|
||||
const buffer: Buffer[] = [];
|
||||
result.on('data', (chunk: Buffer) => {
|
||||
buffer.push(chunk);
|
||||
});
|
||||
result.on('end', () => {
|
||||
if (result.statusCode === 200) {
|
||||
Logger.info('Validated token!');
|
||||
resolve();
|
||||
} else {
|
||||
Logger.info(`Validating token failed: ${result.statusMessage}`);
|
||||
reject(new Error(result.statusMessage));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
post.write(payload);
|
||||
post.end();
|
||||
post.on('error', err => {
|
||||
Logger.error(err.message);
|
||||
reject(new Error(NETWORK_ERROR));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue