From d44d16c4640611bd8cd8bb95093eea3599b5a22c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 22 Aug 2025 07:42:25 +0200 Subject: [PATCH] Wire in a auth callback parameter to indicate Copilot terms were shown to the user (microsoft/vscode-internalbacklog#5761) (#262439) * implement as a bag * nit --------- Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com> --- extensions/github-authentication/src/flows.ts | 27 ++++++++++++++++-- .../github-authentication/src/github.ts | 28 +++++++++++++++++-- .../github-authentication/src/githubServer.ts | 5 ++-- .../chat/common/chatEntitlementService.ts | 8 +++++- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/extensions/github-authentication/src/flows.ts b/extensions/github-authentication/src/flows.ts index 762ca04989f..6e37dfe5dfd 100644 --- a/extensions/github-authentication/src/flows.ts +++ b/extensions/github-authentication/src/flows.ts @@ -72,6 +72,10 @@ interface IFlowTriggerOptions { * The specific auth provider to use for the flow. */ signInProvider?: GitHubSocialSignInProvider; + /** + * Extra parameters to include in the OAuth flow. + */ + extraAuthorizeParameters?: Record; /** * The Uri that the OAuth flow will redirect to. (i.e. vscode.dev/redirect) */ @@ -180,6 +184,7 @@ class UrlHandlerFlow implements IFlow { enterpriseUri, nonce, signInProvider, + extraAuthorizeParameters, uriHandler, existingLogin, logger, @@ -210,6 +215,11 @@ class UrlHandlerFlow implements IFlow { if (signInProvider) { searchParams.append('provider', signInProvider); } + if (extraAuthorizeParameters) { + for (const [key, value] of Object.entries(extraAuthorizeParameters)) { + searchParams.append(key, value); + } + } // The extra toString, parse is apparently needed for env.openExternal // to open the correct URL. @@ -259,6 +269,7 @@ class LocalServerFlow implements IFlow { callbackUri, enterpriseUri, signInProvider, + extraAuthorizeParameters, existingLogin, logger }: IFlowTriggerOptions): Promise { @@ -285,6 +296,11 @@ class LocalServerFlow implements IFlow { if (signInProvider) { searchParams.append('provider', signInProvider); } + if (extraAuthorizeParameters) { + for (const [key, value] of Object.entries(extraAuthorizeParameters)) { + searchParams.append(key, value); + } + } const loginUrl = baseUri.with({ path: '/login/oauth/authorize', @@ -332,7 +348,7 @@ class DeviceCodeFlow implements IFlow { supportsSupportedClients: true, supportsUnsupportedClients: true }; - async trigger({ scopes, baseUri, signInProvider, logger }: IFlowTriggerOptions) { + async trigger({ scopes, baseUri, signInProvider, extraAuthorizeParameters, logger }: IFlowTriggerOptions) { logger.info(`Trying device code flow... (${scopes})`); // Get initial device code @@ -369,9 +385,16 @@ class DeviceCodeFlow implements IFlow { await env.clipboard.writeText(json.user_code); let open = Uri.parse(json.verification_uri); + const query = new URLSearchParams(open.query); if (signInProvider) { - const query = new URLSearchParams(open.query); query.set('provider', signInProvider); + } + if (extraAuthorizeParameters) { + for (const [key, value] of Object.entries(extraAuthorizeParameters)) { + query.set(key, value); + } + } + if (signInProvider || extraAuthorizeParameters) { open = open.with({ query: query.toString() }); } const uriToOpen = await env.asExternalUri(open); diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index c5762ada869..dab57ffd776 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -44,6 +44,27 @@ interface GitHubAuthenticationProviderOptions extends vscode.AuthenticationProvi * leaving it up to the user to choose the social sign-in provider on the sign-in page. */ readonly provider?: GitHubSocialSignInProvider; + readonly extraAuthorizeParameters?: Record; +} + +function isGitHubAuthenticationProviderOptions(object: any): object is GitHubAuthenticationProviderOptions { + if (!object || typeof object !== 'object') { + throw new Error('Options are not an object'); + } + if (object.provider !== undefined && !isSocialSignInProvider(object.provider)) { + throw new Error(`Provider is invalid: ${object.provider}`); + } + if (object.extraAuthorizeParameters !== undefined) { + if (!object.extraAuthorizeParameters || typeof object.extraAuthorizeParameters !== 'object') { + throw new Error('Extra parameters must be a record of string keys and string values.'); + } + for (const [key, value] of Object.entries(object.extraAuthorizeParameters)) { + if (typeof key !== 'string' || typeof value !== 'string') { + throw new Error('Extra parameters must be a record of string keys and string values.'); + } + } + } + return true; } export class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { @@ -338,12 +359,15 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid scopes: JSON.stringify(scopes), }); + if (options && !isGitHubAuthenticationProviderOptions(options)) { + throw new Error('Invalid options'); + } const sessions = await this._sessionsPromise; const loginWith = options?.account?.label; - const signInProvider = isSocialSignInProvider(options?.provider) ? options.provider : undefined; + const signInProvider = options?.provider; this._logger.info(`Logging in with${signInProvider ? ` ${signInProvider}, ` : ''} '${loginWith ? loginWith : 'any'}' account...`); const scopeString = sortedScopes.join(' '); - const token = await this._githubServer.login(scopeString, signInProvider, loginWith); + const token = await this._githubServer.login(scopeString, signInProvider, options?.extraAuthorizeParameters, loginWith); const session = await this.tokenToSession(token, scopes); this.afterSessionLoad(session); diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 869f6bdc465..b71bf5ec6d7 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -19,7 +19,7 @@ const REDIRECT_URL_STABLE = 'https://vscode.dev/redirect'; const REDIRECT_URL_INSIDERS = 'https://insiders.vscode.dev/redirect'; export interface IGitHubServer { - login(scopes: string, signInProvider?: GitHubSocialSignInProvider, existingLogin?: string): Promise; + login(scopes: string, signInProvider?: GitHubSocialSignInProvider, extraAuthorizeParameters?: Record, existingLogin?: string): Promise; logout(session: vscode.AuthenticationSession): Promise; getUserInfo(token: string): Promise<{ id: string; accountName: string }>; sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise; @@ -87,7 +87,7 @@ export class GitHubServer implements IGitHubServer { return this._isNoCorsEnvironment; } - public async login(scopes: string, signInProvider?: GitHubSocialSignInProvider, existingLogin?: string): Promise { + public async login(scopes: string, signInProvider?: GitHubSocialSignInProvider, extraAuthorizeParameters?: Record, existingLogin?: string): Promise { this._logger.info(`Logging in for the following scopes: ${scopes}`); // Used for showing a friendlier message to the user when the explicitly cancel a flow. @@ -136,6 +136,7 @@ export class GitHubServer implements IGitHubServer { callbackUri, nonce, signInProvider, + extraAuthorizeParameters, baseUri: this.baseUri, logger: this._logger, uriHandler: this._uriHandler, diff --git a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts b/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts index 57c1d7baa18..39967c07aa8 100644 --- a/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEntitlementService.ts @@ -908,7 +908,13 @@ export class ChatEntitlementRequests extends Disposable { const providerId = ChatEntitlementRequests.providerId(this.configurationService); const scopes = options?.additionalScopes ? distinct([...defaultChat.providerScopes[0], ...options.additionalScopes]) : defaultChat.providerScopes[0]; - const session = await this.authenticationService.createSession(providerId, scopes, options?.useSocialProvider ? { provider: options.useSocialProvider } : undefined); + const session = await this.authenticationService.createSession( + providerId, + scopes, + { + extraAuthorizeParameters: { get_started_with: 'copilot-vscode' }, + provider: options?.useSocialProvider + }); this.authenticationExtensionsService.updateAccountPreference(defaultChat.extensionId, providerId, session.account); this.authenticationExtensionsService.updateAccountPreference(defaultChat.chatExtensionId, providerId, session.account);