Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/authentication/vscode-node/copilotTokenManager.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { env, window } from 'vscode';
7
import { TaskSingler } from '../../../util/common/taskSingler';
8
import { ConfigKey, IConfigurationService } from '../../configuration/common/configurationService';
9
import { ICAPIClientService } from '../../endpoint/common/capiClient';
10
import { IDomainService } from '../../endpoint/common/domainService';
11
import { IEnvService } from '../../env/common/envService';
12
import { BaseOctoKitService } from '../../github/common/githubService';
13
import { ILogService } from '../../log/common/logService';
14
import { IFetcherService } from '../../networking/common/fetcherService';
15
import { ITelemetryService } from '../../telemetry/common/telemetry';
16
import { CopilotToken, ExtendedTokenInfo, TokenErrorNotificationId, TokenInfoOrError } from '../common/copilotToken';
17
import { nowSeconds } from '../common/copilotTokenManager';
18
import { BaseCopilotTokenManager } from '../node/copilotTokenManager';
19
import { getAnyAuthSession } from './session';
20
21
//Flag if we've shown message about broken oauth token.
22
let shown401Message = false;
23
24
export class NotSignedUpError extends Error { }
25
export class SubscriptionExpiredError extends Error { }
26
export class ContactSupportError extends Error { }
27
export class EnterpriseManagedError extends Error { }
28
export class InvalidTokenError extends Error { }
29
export class RateLimitedError extends Error { }
30
export class GitHubLoginFailedError extends Error { }
31
32
export class VSCodeCopilotTokenManager extends BaseCopilotTokenManager {
33
private _taskSingler = new TaskSingler<TokenInfoOrError>();
34
35
constructor(
36
@ILogService logService: ILogService,
37
@ITelemetryService telemetryService: ITelemetryService,
38
@IDomainService domainService: IDomainService,
39
@ICAPIClientService capiClientService: ICAPIClientService,
40
@IFetcherService fetcherService: IFetcherService,
41
@IEnvService envService: IEnvService,
42
@IConfigurationService protected readonly configurationService: IConfigurationService
43
) {
44
super(new BaseOctoKitService(capiClientService, fetcherService, logService, telemetryService), logService, telemetryService, domainService, capiClientService, fetcherService, envService);
45
}
46
47
async getCopilotToken(force?: boolean): Promise<CopilotToken> {
48
const failWith = this.configurationService.getConfig(ConfigKey.Advanced.DebugGitHubAuthFailWith);
49
if (failWith) {
50
this.copilotToken = undefined;
51
}
52
53
if (!this.copilotToken || this.copilotToken.expires_at - (60 * 5 /* 5min */) < nowSeconds() || force) {
54
try {
55
this._logService.debug(`Getting CopilotToken (force: ${force})...`);
56
this.copilotToken = await this._authShowWarnings();
57
this._logService.debug(`Got CopilotToken (force: ${force}).`);
58
} catch (e) {
59
this._logService.debug(`Getting CopilotToken (force: ${force}) threw error: ${e}`);
60
this.copilotToken = undefined;
61
throw e;
62
}
63
}
64
return new CopilotToken(this.copilotToken);
65
}
66
67
private async _auth(): Promise<TokenInfoOrError> {
68
const failWith = this.configurationService.getConfig(ConfigKey.Advanced.DebugGitHubAuthFailWith);
69
if (failWith) {
70
return { kind: 'failure', reason: failWith };
71
}
72
73
const allowNoAuthAccess = this.configurationService.getNonExtensionConfig<boolean>('chat.allowAnonymousAccess');
74
const session = await getAnyAuthSession(this.configurationService, { silent: true });
75
if (!session && !allowNoAuthAccess) {
76
this._logService.warn('GitHub login failed');
77
this._telemetryService.sendGHTelemetryErrorEvent('auth.github_login_failed');
78
return { kind: 'failure', reason: 'GitHubLoginFailed' };
79
}
80
if (session) {
81
// Log the steps by default, but only log actual token values when the log level is set to debug.
82
this._logService.info(`Logged in as ${session.account.label}`);
83
const tokenResult = await this.authFromGitHubToken(session.accessToken, session.account.label);
84
if (tokenResult.kind === 'success') {
85
this._logService.info(`Got Copilot token for ${session.account.label}`);
86
this._logService.info(`Copilot Chat: ${this._envService.getVersion()}, VS Code: ${this._envService.vscodeVersion}`);
87
}
88
return tokenResult;
89
} else {
90
this._logService.info(`Allowing anonymous access with devDeviceId`);
91
const tokenResult = await this.authFromDevDeviceId(env.devDeviceId);
92
if (tokenResult.kind === 'success') {
93
this._logService.info(`Got Copilot token for devDeviceId`);
94
this._logService.info(`Copilot Chat: ${this._envService.getVersion()}, VS Code: ${this._envService.vscodeVersion}`);
95
} else {
96
this._logService.warn('GitHub login failed');
97
return { kind: 'failure', reason: 'GitHubLoginFailed' };
98
}
99
return tokenResult;
100
}
101
}
102
103
private async _authShowWarnings(): Promise<ExtendedTokenInfo> {
104
const tokenResult = await this._taskSingler.getOrCreate('auth', () => this._auth());
105
this.sendTokenResultErrorTelemetry(tokenResult);
106
107
if (tokenResult.kind === 'failure' && tokenResult.reason === 'NotAuthorized') {
108
const message = tokenResult.message;
109
switch (tokenResult.notification_id) {
110
case TokenErrorNotificationId.NotSignedUp:
111
case TokenErrorNotificationId.NoCopilotAccess:
112
throw new NotSignedUpError(message ?? 'User not authorized');
113
case TokenErrorNotificationId.SubscriptionEnded:
114
throw new SubscriptionExpiredError(message);
115
case TokenErrorNotificationId.EnterPriseManagedUserAccount:
116
throw new EnterpriseManagedError(message);
117
case TokenErrorNotificationId.ServerError:
118
case TokenErrorNotificationId.FeatureFlagBlocked:
119
case TokenErrorNotificationId.SpammyUser:
120
case TokenErrorNotificationId.SnippyNotConfigured:
121
throw new ContactSupportError(message);
122
}
123
}
124
if (tokenResult.kind === 'failure' && tokenResult.reason === 'HTTP401') {
125
const message =
126
'Your GitHub token is invalid. Please sign out from your GitHub account using the VS Code accounts menu and try again.';
127
if (!shown401Message) {
128
shown401Message = true;
129
window.showWarningMessage(message);
130
}
131
throw new InvalidTokenError(message);
132
}
133
134
if (tokenResult.kind === 'failure' && tokenResult.reason === 'GitHubLoginFailed') {
135
throw new GitHubLoginFailedError('GitHubLoginFailed');
136
}
137
138
if (tokenResult.kind === 'failure' && tokenResult.reason === 'RateLimited') {
139
throw new RateLimitedError(`Your account has exceeded GitHub's API rate limit. Please try again later.`);
140
}
141
142
if (tokenResult.kind === 'failure') {
143
throw Error('Failed to get copilot token. reason: ' + tokenResult.reason);
144
}
145
146
return tokenResult;
147
}
148
149
private sendTokenResultErrorTelemetry(tokenResult: TokenInfoOrError): void {
150
if (tokenResult.kind === 'success') {
151
return;
152
}
153
154
/* __GDPR__
155
"copilotTokenFetching.error" : {
156
"owner": "TylerLeonhardt",
157
"comment": "Report on the frequency of token retrieval failures.",
158
"reason": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The reason for the token retrieval failure" },
159
"notification_id": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The notification ID associated with the failure, if any" }
160
}
161
*/
162
this._telemetryService.sendMSFTTelemetryEvent('copilotTokenFetching.error', {
163
reason: tokenResult.reason,
164
notification_id: tokenResult.notification_id,
165
});
166
}
167
}
168
169