mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-22 03:09:13 +00:00
Introduce auth-provider specific props & use it for social sign in for GitHub (#251649)
ref https://github.com/microsoft/vscode/issues/251648 NOTE: the server side is not quite ready. It doesn't redirect back to VS Code properly, but this should be good to go whenever that is.
This commit is contained in:
committed by
GitHub
parent
480485f0d0
commit
2fabac413d
@@ -18,7 +18,8 @@
|
||||
"workspace"
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"authIssuers"
|
||||
"authIssuers",
|
||||
"authProviderSpecific"
|
||||
],
|
||||
"activationEvents": [],
|
||||
"capabilities": {
|
||||
|
||||
@@ -60,15 +60,46 @@ export interface IFlowQuery {
|
||||
}
|
||||
|
||||
interface IFlowTriggerOptions {
|
||||
/**
|
||||
* The scopes to request for the OAuth flow.
|
||||
*/
|
||||
scopes: string;
|
||||
/**
|
||||
* The base URI for the flow. This is used to determine which GitHub instance to authenticate against.
|
||||
*/
|
||||
baseUri: Uri;
|
||||
logger: Log;
|
||||
/**
|
||||
* The specific auth provider to use for the flow.
|
||||
*/
|
||||
signInProvider?: GitHubSocialSignInProvider;
|
||||
/**
|
||||
* The Uri that the OAuth flow will redirect to. (i.e. vscode.dev/redirect)
|
||||
*/
|
||||
redirectUri: Uri;
|
||||
nonce: string;
|
||||
/**
|
||||
* The Uri to redirect to after redirecting to the redirect Uri. (i.e. vscode://....)
|
||||
*/
|
||||
callbackUri: Uri;
|
||||
uriHandler: UriEventHandler;
|
||||
/**
|
||||
* The enterprise URI for the flow, if applicable.
|
||||
*/
|
||||
enterpriseUri?: Uri;
|
||||
/**
|
||||
* The existing login which will be used to pre-fill the login prompt.
|
||||
*/
|
||||
existingLogin?: string;
|
||||
/**
|
||||
* The nonce for this particular flow. This is used to prevent replay attacks.
|
||||
*/
|
||||
nonce: string;
|
||||
/**
|
||||
* The instance of the Uri Handler for this extension
|
||||
*/
|
||||
uriHandler: UriEventHandler;
|
||||
/**
|
||||
* The logger to use for this flow.
|
||||
*/
|
||||
logger: Log;
|
||||
}
|
||||
|
||||
interface IFlow {
|
||||
@@ -143,12 +174,13 @@ class UrlHandlerFlow implements IFlow {
|
||||
scopes,
|
||||
baseUri,
|
||||
redirectUri,
|
||||
logger,
|
||||
nonce,
|
||||
callbackUri,
|
||||
uriHandler,
|
||||
enterpriseUri,
|
||||
existingLogin
|
||||
nonce,
|
||||
signInProvider: authProvider,
|
||||
uriHandler,
|
||||
existingLogin,
|
||||
logger,
|
||||
}: IFlowTriggerOptions): Promise<string> {
|
||||
logger.info(`Trying without local server... (${scopes})`);
|
||||
return await window.withProgress<string>({
|
||||
@@ -177,7 +209,7 @@ class UrlHandlerFlow implements IFlow {
|
||||
// The extra toString, parse is apparently needed for env.openExternal
|
||||
// to open the correct URL.
|
||||
const uri = Uri.parse(baseUri.with({
|
||||
path: '/login/oauth/authorize',
|
||||
path: getAuthorizeUrlPath(authProvider),
|
||||
query: searchParams.toString()
|
||||
}).toString(true));
|
||||
await env.openExternal(uri);
|
||||
@@ -219,10 +251,11 @@ class LocalServerFlow implements IFlow {
|
||||
scopes,
|
||||
baseUri,
|
||||
redirectUri,
|
||||
logger,
|
||||
callbackUri,
|
||||
enterpriseUri,
|
||||
existingLogin
|
||||
signInProvider: authProvider,
|
||||
existingLogin,
|
||||
logger
|
||||
}: IFlowTriggerOptions): Promise<string> {
|
||||
logger.info(`Trying with local server... (${scopes})`);
|
||||
return await window.withProgress<string>({
|
||||
@@ -246,7 +279,7 @@ class LocalServerFlow implements IFlow {
|
||||
}
|
||||
|
||||
const loginUrl = baseUri.with({
|
||||
path: '/login/oauth/authorize',
|
||||
path: getAuthorizeUrlPath(authProvider),
|
||||
query: searchParams.toString()
|
||||
});
|
||||
const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl.toString(true), callbackUri.toString(true));
|
||||
@@ -520,3 +553,24 @@ export function getFlows(query: IFlowQuery) {
|
||||
return useFlow;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Social authentication providers for GitHub
|
||||
*/
|
||||
export const enum GitHubSocialSignInProvider {
|
||||
Google = 'google',
|
||||
// Apple = 'apple',
|
||||
}
|
||||
|
||||
export function isSocialSignInProvider(provider: unknown): provider is GitHubSocialSignInProvider {
|
||||
return provider === GitHubSocialSignInProvider.Google; // || provider === GitHubSocialSignInProvider.Apple;
|
||||
}
|
||||
|
||||
export function getAuthorizeUrlPath(provider: GitHubSocialSignInProvider | undefined): string {
|
||||
switch (provider) {
|
||||
case GitHubSocialSignInProvider.Google:
|
||||
// case GitHubSocialSignInProvider.Apple:
|
||||
return `/sessions/social/${provider}/initiate`;
|
||||
}
|
||||
return '/login/oauth/authorize';
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ExperimentationTelemetry } from './common/experimentationService';
|
||||
import { Log } from './common/logger';
|
||||
import { crypto } from './node/crypto';
|
||||
import { TIMED_OUT_ERROR, USER_CANCELLATION_ERROR } from './common/errors';
|
||||
import { GitHubSocialSignInProvider, isSocialSignInProvider } from './flows';
|
||||
|
||||
interface SessionData {
|
||||
id: string;
|
||||
@@ -31,6 +32,20 @@ export enum AuthProviderType {
|
||||
githubEnterprise = 'github-enterprise'
|
||||
}
|
||||
|
||||
interface GitHubAuthenticationProviderOptions extends vscode.AuthenticationProviderSessionOptions {
|
||||
/**
|
||||
* This is specific to GitHub and is used to determine which social sign-in provider to use.
|
||||
* If not provided, the default (GitHub) is used which shows all options.
|
||||
*
|
||||
* Example: If you specify Google, then the sign-in flow will skip the initial page that asks you
|
||||
* to choose how you want to sign in and will directly take you to the Google sign-in page.
|
||||
*
|
||||
* This allows us to show "Continue with Google" buttons in the product, rather than always
|
||||
* leaving it up to the user to choose the social sign-in provider on the sign-in page.
|
||||
*/
|
||||
readonly provider?: GitHubSocialSignInProvider;
|
||||
}
|
||||
|
||||
export class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.UriHandler {
|
||||
private readonly _pendingNonces = new Map<string, string[]>();
|
||||
private readonly _codeExchangePromises = new Map<string, { promise: Promise<string>; cancel: vscode.EventEmitter<void> }>();
|
||||
@@ -306,7 +321,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
|
||||
this._logger.info(`Stored ${sessions.length} sessions!`);
|
||||
}
|
||||
|
||||
public async createSession(scopes: string[], options?: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession> {
|
||||
public async createSession(scopes: string[], options?: GitHubAuthenticationProviderOptions): Promise<vscode.AuthenticationSession> {
|
||||
try {
|
||||
// For GitHub scope list, order doesn't matter so we use a sorted scope to determine
|
||||
// if we've got a session already.
|
||||
@@ -325,9 +340,10 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
|
||||
|
||||
const sessions = await this._sessionsPromise;
|
||||
const loginWith = options?.account?.label;
|
||||
this._logger.info(`Logging in with '${loginWith ? loginWith : 'any'}' account...`);
|
||||
const signInProvider = isSocialSignInProvider(options?.provider) ? options.provider : undefined;
|
||||
this._logger.info(`Logging in with${signInProvider ? ` ${signInProvider}, ` : ''} '${loginWith ? loginWith : 'any'}' account...`);
|
||||
const scopeString = sortedScopes.join(' ');
|
||||
const token = await this._githubServer.login(scopeString, loginWith);
|
||||
const token = await this._githubServer.login(scopeString, signInProvider, loginWith);
|
||||
const session = await this.tokenToSession(token, scopes);
|
||||
this.afterSessionLoad(session);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Log } from './common/logger';
|
||||
import { isSupportedClient, isSupportedTarget } from './common/env';
|
||||
import { crypto } from './node/crypto';
|
||||
import { fetching } from './node/fetch';
|
||||
import { ExtensionHost, GitHubTarget, getFlows } from './flows';
|
||||
import { ExtensionHost, GitHubSocialSignInProvider, GitHubTarget, getFlows } from './flows';
|
||||
import { CANCELLATION_ERROR, NETWORK_ERROR, USER_CANCELLATION_ERROR } from './common/errors';
|
||||
import { Config } from './config';
|
||||
import { base64Encode } from './node/buffer';
|
||||
@@ -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, existingLogin?: string): Promise<string>;
|
||||
login(scopes: string, signInProvider?: GitHubSocialSignInProvider, existingLogin?: string): Promise<string>;
|
||||
logout(session: vscode.AuthenticationSession): Promise<void>;
|
||||
getUserInfo(token: string): Promise<{ id: string; accountName: string }>;
|
||||
sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise<void>;
|
||||
@@ -87,7 +87,7 @@ export class GitHubServer implements IGitHubServer {
|
||||
return this._isNoCorsEnvironment;
|
||||
}
|
||||
|
||||
public async login(scopes: string, existingLogin?: string): Promise<string> {
|
||||
public async login(scopes: string, signInProvider?: GitHubSocialSignInProvider, existingLogin?: string): Promise<string> {
|
||||
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.
|
||||
@@ -135,6 +135,7 @@ export class GitHubServer implements IGitHubServer {
|
||||
scopes,
|
||||
callbackUri,
|
||||
nonce,
|
||||
signInProvider,
|
||||
baseUri: this.baseUri,
|
||||
logger: this._logger,
|
||||
uriHandler: this._uriHandler,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.authIssuers.d.ts"
|
||||
"../../src/vscode-dts/vscode.proposed.authIssuers.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.authProviderSpecific.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user