File size: 5,497 Bytes
6202252 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | import {
authentication,
AuthenticationGetSessionOptions,
AuthenticationProvider,
AuthenticationProviderAuthenticationSessionsChangeEvent,
AuthenticationSession,
Disposable,
Event,
EventEmitter,
SecretStorage,
window,
} from 'vscode';
class AzureDevOpsPatSession implements AuthenticationSession {
// We don't know the user's account name, so we'll just use a constant
readonly account = { id: AzureDevOpsAuthenticationProvider.id, label: 'Personal Access Token' };
// This id isn't used for anything in this example, so we set it to a constant
readonly id = AzureDevOpsAuthenticationProvider.id;
// We don't know what scopes the PAT has, so we have an empty array here.
readonly scopes = [];
/**
*
* @param accessToken The personal access token to use for authentication
*/
constructor(public readonly accessToken: string) { }
}
export class AzureDevOpsAuthenticationProvider implements AuthenticationProvider, Disposable {
static id = 'azuredevopspat';
private static secretKey = 'AzureDevOpsPAT';
// this property is used to determine if the token has been changed in another window of VS Code.
// It is used in the checkForUpdates function.
private currentToken: Promise<string | undefined> | undefined;
private initializedDisposable: Disposable | undefined;
private _onDidChangeSessions = new EventEmitter<AuthenticationProviderAuthenticationSessionsChangeEvent>();
get onDidChangeSessions(): Event<AuthenticationProviderAuthenticationSessionsChangeEvent> {
return this._onDidChangeSessions.event;
}
constructor(private readonly secretStorage: SecretStorage) { }
dispose(): void {
this.initializedDisposable?.dispose();
}
private ensureInitialized(): void {
if (this.initializedDisposable === undefined) {
void this.cacheTokenFromStorage();
this.initializedDisposable = Disposable.from(
// This onDidChange event happens when the secret storage changes in _any window_ since
// secrets are shared across all open windows.
this.secretStorage.onDidChange(e => {
if (e.key === AzureDevOpsAuthenticationProvider.secretKey) {
void this.checkForUpdates();
}
}),
// This fires when the user initiates a "silent" auth flow via the Accounts menu.
authentication.onDidChangeSessions(e => {
if (e.provider.id === AzureDevOpsAuthenticationProvider.id) {
void this.checkForUpdates();
}
}),
);
}
}
// This is a crucial function that handles whether or not the token has changed in
// a different window of VS Code and sends the necessary event if it has.
private async checkForUpdates(): Promise<void> {
const added: AuthenticationSession[] = [];
const removed: AuthenticationSession[] = [];
const changed: AuthenticationSession[] = [];
const previousToken = await this.currentToken;
const session = (await this.getSessions(undefined))[0];
if (session?.accessToken && !previousToken) {
added.push(session);
} else if (!session?.accessToken && previousToken) {
removed.push(session);
} else if (session?.accessToken !== previousToken) {
changed.push(session);
} else {
return;
}
void this.cacheTokenFromStorage();
this._onDidChangeSessions.fire({ added: added, removed: removed, changed: changed });
}
private cacheTokenFromStorage() {
this.currentToken = this.secretStorage.get(AzureDevOpsAuthenticationProvider.secretKey) as Promise<string | undefined>;
return this.currentToken;
}
// This function is called first when `vscode.authentication.getSessions` is called.
async getSessions(_scopes: string[] | undefined, _options?: AuthenticationGetSessionOptions): Promise<AuthenticationSession[]> {
this.ensureInitialized();
const token = await this.cacheTokenFromStorage();
return token ? [new AzureDevOpsPatSession(token)] : [];
}
// This function is called after `this.getSessions` is called and only when:
// - `this.getSessions` returns nothing but `createIfNone` was set to `true` in `vscode.authentication.getSessions`
// - `vscode.authentication.getSessions` was called with `forceNewSession: true`
// - The end user initiates the "silent" auth flow via the Accounts menu
async createSession(_scopes: string[]): Promise<AuthenticationSession> {
this.ensureInitialized();
// Prompt for the PAT.
const token = await window.showInputBox({
ignoreFocusOut: true,
placeHolder: 'Personal access token',
prompt: 'Enter an Azure DevOps Personal Access Token (PAT).',
password: true,
});
// Note: this example doesn't do any validation of the token beyond making sure it's not empty.
if (!token) {
throw new Error('PAT is required');
}
// Don't set `currentToken` here, since we want to fire the proper events in the `checkForUpdates` call
await this.secretStorage.store(AzureDevOpsAuthenticationProvider.secretKey, token);
console.log('Successfully logged in to Azure DevOps');
return new AzureDevOpsPatSession(token);
}
// This function is called when the end user signs out of the account.
async removeSession(_sessionId: string): Promise<void> {
const token = await this.currentToken;
if (!token) {
return;
}
await this.secretStorage.delete(AzureDevOpsAuthenticationProvider.secretKey);
this._onDidChangeSessions.fire({
removed: [new AzureDevOpsPatSession(token)],
added: [],
changed: [],
});
}
}
|