44 *--------------------------------------------------------------------------------------------*/
55
66import * as https from 'https' ;
7+ import * as nls from 'vscode-nls' ;
78import * as vscode from 'vscode' ;
89import * as uuid from 'uuid' ;
910import { PromiseAdapter , promiseFromEvent } from './common/utils' ;
1011import Logger from './common/logger' ;
11- import ClientRegistrar , { ClientDetails } from './common/clientRegistrar' ;
12+ import ClientRegistrar from './common/clientRegistrar' ;
13+
14+ const localize = nls . loadMessageBundle ( ) ;
1215
1316export const NETWORK_ERROR = 'network error' ;
17+ const AUTH_RELAY_SERVER = 'vscode-auth.github.com' ;
1418
1519class UriEventHandler extends vscode . EventEmitter < vscode . Uri > implements vscode . UriHandler {
1620 public handleUri ( uri : vscode . Uri ) {
@@ -20,8 +24,8 @@ class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.
2024
2125export const uriHandler = new UriEventHandler ;
2226
23- const exchangeCodeForToken : ( state : string , clientDetails : ClientDetails ) => PromiseAdapter < vscode . Uri , string > =
24- ( state , clientDetails ) => async ( uri , resolve , reject ) => {
27+ const exchangeCodeForToken : ( state : string , host : string , getPath : ( code : string ) => string ) => PromiseAdapter < vscode . Uri , string > =
28+ ( state , host , getPath ) => async ( uri , resolve , reject ) => {
2529 Logger . info ( 'Exchanging code for token...' ) ;
2630 const query = parseQuery ( uri ) ;
2731 const code = query . code ;
@@ -32,8 +36,8 @@ const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => Pro
3236 }
3337
3438 const post = https . request ( {
35- host : 'github.com' ,
36- path : `/login/oauth/access_token?client_id= ${ clientDetails . id } &client_secret= ${ clientDetails . secret } &state= ${ query . state } & code= ${ code } ` ,
39+ host : host ,
40+ path : getPath ( code ) ,
3741 method : 'POST' ,
3842 headers : {
3943 Accept : 'application/json'
@@ -69,15 +73,55 @@ function parseQuery(uri: vscode.Uri) {
6973}
7074
7175export class GitHubServer {
76+ private _statusBarItem : vscode . StatusBarItem | undefined ;
77+
7278 public async login ( scopes : string ) : Promise < string > {
7379 Logger . info ( 'Logging in...' ) ;
80+ this . updateStatusBarItem ( true ) ;
81+
7482 const state = uuid ( ) ;
7583 const callbackUri = await vscode . env . asExternalUri ( vscode . Uri . parse ( `${ vscode . env . uriScheme } ://vscode.github-authentication/did-authenticate` ) ) ;
7684 const clientDetails = scopes === 'vso' ? ClientRegistrar . getGitHubAppDetails ( ) : ClientRegistrar . getClientDetails ( callbackUri ) ;
77- const uri = vscode . Uri . parse ( `https://github.com/login/oauth/authorize?redirect_uri=${ encodeURIComponent ( callbackUri . toString ( ) ) } &scope=${ scopes } &state=${ state } &client_id=${ clientDetails . id } ` ) ;
85+ const uri = scopes !== 'vso'
86+ ? vscode . Uri . parse ( `https://${ AUTH_RELAY_SERVER } /authorize/?callbackUri=${ encodeURIComponent ( callbackUri . toString ( ) ) } &scope=${ scopes } &state=${ state } &responseType=code` )
87+ : vscode . Uri . parse ( `https://github.com/login/oauth/authorize?redirect_uri=${ encodeURIComponent ( callbackUri . toString ( ) ) } &scope=${ scopes } &state=${ state } &client_id=${ clientDetails . id } ` ) ;
7888
7989 vscode . env . openExternal ( uri ) ;
80- return promiseFromEvent ( uriHandler . event , exchangeCodeForToken ( state , clientDetails ) ) ;
90+
91+ return promiseFromEvent ( uriHandler . event , exchangeCodeForToken ( state , AUTH_RELAY_SERVER , ( code ) => {
92+ return scopes !== 'vso'
93+ ? `/token?code=${ code } &state=${ state } `
94+ : `/login/oauth/access_token?client_id=${ clientDetails . id } &client_secret=${ clientDetails . secret } &state=${ state } &code=${ code } &authServer=github.com` ;
95+ } ) ) . finally ( ( ) => {
96+ this . updateStatusBarItem ( false ) ;
97+ } ) ;
98+ }
99+
100+ private updateStatusBarItem ( isStart ?: boolean ) {
101+ if ( isStart && ! this . _statusBarItem ) {
102+ this . _statusBarItem = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Left ) ;
103+ this . _statusBarItem . text = localize ( 'signingIn' , "$(mark-github) Signing in to github.com..." ) ;
104+ this . _statusBarItem . command = 'github.provide-token' ;
105+ this . _statusBarItem . show ( ) ;
106+ }
107+
108+ if ( ! isStart && this . _statusBarItem ) {
109+ this . _statusBarItem . dispose ( ) ;
110+ this . _statusBarItem = undefined ;
111+ }
112+ }
113+
114+ public async manuallyProvideToken ( ) {
115+ const uriOrToken = await vscode . window . showInputBox ( { prompt : 'Token' , ignoreFocusOut : true } ) ;
116+ if ( ! uriOrToken ) { return ; }
117+ try {
118+ const uri = vscode . Uri . parse ( uriOrToken ) ;
119+ if ( ! uri . scheme || uri . scheme === 'file' ) { throw new Error ; }
120+ uriHandler . handleUri ( uri ) ;
121+ } catch ( e ) {
122+ Logger . error ( e ) ;
123+ vscode . window . showErrorMessage ( localize ( 'unexpectedInput' , "The input did not matched the expected format" ) ) ;
124+ }
81125 }
82126
83127 public async hasUserInstallation ( token : string ) : Promise < boolean > {
@@ -122,7 +166,7 @@ export class GitHubServer {
122166 const uri = vscode . Uri . parse ( `https://github.com/apps/microsoft-visual-studio-code/installations/new?state=${ state } ` ) ;
123167
124168 vscode . env . openExternal ( uri ) ;
125- return promiseFromEvent ( uriHandler . event , exchangeCodeForToken ( state , clientDetails ) ) ;
169+ return promiseFromEvent ( uriHandler . event , exchangeCodeForToken ( state , 'github.com' , ( code ) => `/login/oauth/access_token?client_id= ${ clientDetails . id } &client_secret= ${ clientDetails . secret } &state= ${ state } &code= ${ code } ` ) ) ;
126170 }
127171
128172 public async getUserInfo ( token : string ) : Promise < { id : string , accountName : string } > {
0 commit comments