11import fetch from "node-fetch" ;
2- import { v4 as uuid } from "uuid" ;
32import {
3+ authentication ,
4+ AuthenticationProvider ,
5+ AuthenticationProviderAuthenticationSessionsChangeEvent ,
46 AuthenticationSession ,
7+ Disposable ,
58 env ,
9+ Event ,
10+ EventEmitter ,
11+ ExtensionContext ,
612 ProgressLocation ,
713 Uri ,
814 window ,
@@ -13,54 +19,61 @@ import {
1319 UserDeviceCode ,
1420 UserInfo ,
1521} from "../@types/types" ;
16- import { AuthProvider } from "../common/authProviderInterface" ;
1722import {
1823 COMMIT_API_BASE_URL ,
1924 COMMIT_APP_BASE_URL ,
2025 COMMIT_AUTH0_DOMAIN ,
26+ COMMIT_AUTH_NAME ,
27+ COMMIT_AUTH_TYPE ,
2128 COMMIT_CLIENT_ID ,
29+ COMMIT_SESSIONS_SECRET_KEY ,
2230} from "../common/constants" ;
2331
24- export const AUTH_TYPE = `commit-auth0` ;
25- const AUTH_NAME = `Commit` ;
26- const SESSIONS_SECRET_KEY = `${ AUTH_TYPE } .sessions` ;
32+ export class Auth0AuthProvider implements AuthenticationProvider , Disposable {
33+ private _secretSessionKey : string = COMMIT_SESSIONS_SECRET_KEY ;
2734
28- export class Auth0AuthenticationProvider extends AuthProvider {
29- private _pendingStates : string [ ] = [ ] ;
35+ // On Session Change Event Emitter
36+ private _onDidChangeSessions =
37+ new EventEmitter < AuthenticationProviderAuthenticationSessionsChangeEvent > ( ) ;
3038
31- /**
32- * Methid get authentication type
33- * @returns string
34- */
35- getAuthType ( ) : string {
36- return AUTH_TYPE ;
39+ // Disposable
40+ private _disposable : Disposable ;
41+
42+ constructor ( private readonly context : ExtensionContext ) {
43+ this . _disposable = Disposable . from (
44+ authentication . registerAuthenticationProvider (
45+ COMMIT_AUTH_TYPE ,
46+ COMMIT_AUTH_NAME ,
47+ this ,
48+ { supportsMultipleAccounts : false }
49+ )
50+ ) ;
3751 }
3852
53+ dispose ( ) {
54+ this . _disposable . dispose ( ) ;
55+ }
3956 /**
40- * Method to get authentication name
41- * @returns string
57+ * Event to listen to session changes
4258 */
43- getAuthName ( ) : string {
44- return AUTH_NAME ;
59+ get onDidChangeSessions ( ) : Event < AuthenticationProviderAuthenticationSessionsChangeEvent > {
60+ return this . _onDidChangeSessions . event ;
4561 }
4662
47- /**
48- * Method to get session secret key
49- * @returns string
50- */
51- getSecretKey ( ) : string {
52- return SESSIONS_SECRET_KEY ;
63+ async getSessions (
64+ scopes ?: readonly string [ ] | undefined
65+ ) : Promise < readonly AuthenticationSession [ ] > {
66+ const allSessions = await this . context . secrets . get ( this . _secretSessionKey ) ;
67+ if ( ! allSessions ) {
68+ return [ ] ;
69+ }
70+
71+ const sessions = ( await JSON . parse ( allSessions ) ) as AuthenticationSession [ ] ;
72+ return sessions ;
5373 }
5474
55- /**
56- * Create a new auth session
57- * @param scopes
58- * @returns
59- */
60- public async createSession ( scopes : string [ ] ) : Promise < AuthenticationSession > {
75+ async createSession ( scopes : string [ ] ) : Promise < AuthenticationSession > {
6176 try {
62- // Append the default scopes if does not include openid and email
63-
6477 if ( ! scopes . includes ( "openid" ) ) {
6578 scopes . push ( "openid" ) ;
6679 }
@@ -69,84 +82,113 @@ export class Auth0AuthenticationProvider extends AuthProvider {
6982 scopes . push ( "email" ) ;
7083 }
7184
72- const { accessToken, expiresIn } = await this . _login ( scopes ) ;
85+ // Login
86+ const { accessToken } = ( await this . _login ( scopes ) ) as AuthDetails ;
7387 if ( ! accessToken ) {
74- // console.log("Invalid access token");
75- throw new Error ( `Commit login failed` ) ;
88+ throw new Error ( "Unable to login, not access token found" ) ;
7689 }
7790
78- const userInfo : UserInfo = await this . _getUserInfo ( accessToken ) ;
91+ // Get User Info
92+ const userInfo = ( await this . _getUserInfo ( accessToken ) ) as UserInfo ;
7993
94+ // Create Session
8095 const session : AuthenticationSession = {
81- id : "commit-" + uuid ( ) ,
96+ id : userInfo . id ,
8297 accessToken : accessToken ,
8398 account : {
84- label : `${ userInfo . name } <${ userInfo . email } >` ,
8599 id : userInfo . id ,
100+ label : userInfo . email ,
86101 } ,
87102 scopes : scopes ,
88103 } ;
89104
90- await this . getContext ( ) . secrets . store (
91- SESSIONS_SECRET_KEY ,
105+ // Add the session to secret storage
106+ await this . context . secrets . store (
107+ this . _secretSessionKey ,
92108 JSON . stringify ( [ session ] )
93109 ) ;
94110
95- this . getOnDidChangeSessions ( ) . fire ( {
111+ this . _onDidChangeSessions . fire ( {
96112 added : [ session ] ,
97113 removed : [ ] ,
98114 changed : [ ] ,
99115 } ) ;
100116
101117 return session ;
102- } catch ( e ) {
103- window . showErrorMessage ( ` ${ e } ` ) ;
104- throw e ;
118+ } catch ( error : any ) {
119+ window . showErrorMessage ( error . message ) ;
120+ throw error ;
105121 }
106122 }
107123
108- /**
109- * Get User Info
110- * @param token
111- * @returns
112- * @private
113- */
114- private async _getUserInfo ( token : string ) : Promise < UserInfo > {
115- // Make POST request to get user info
116- try {
117- const response = await fetch (
118- `${ COMMIT_API_BASE_URL } /v1/auth.get-own-user` ,
119- {
120- method : "POST" ,
121- headers : {
122- // eslint-disable-next-line @typescript-eslint/naming-convention
123- "content-type" : "application/json" ,
124- cookie : `helix_session_token=${ token } ` ,
125- origin : COMMIT_APP_BASE_URL || "" ,
126- referer : COMMIT_APP_BASE_URL || "" ,
127- } ,
128- }
129- ) ;
124+ async removeSessions ( ) : Promise < void > {
125+ await this . context . secrets . delete ( this . _secretSessionKey ) ;
126+ this . _onDidChangeSessions . fire ( {
127+ added : [ ] ,
128+ removed : [ ] ,
129+ changed : [ ] ,
130+ } ) ;
131+ }
130132
131- if ( ! response . ok ) {
132- throw new Error ( `Get user info invalid status: ${ response . statusText } ` ) ;
133- }
133+ async removeSession ( sessionId : string ) : Promise < void > {
134+ const allSessions = await this . context . secrets . get ( this . _secretSessionKey ) ;
135+ if ( ! allSessions ) {
136+ return ;
137+ }
134138
135- const data = ( await response . json ( ) ) as any ;
136- return {
137- email : data . email ,
138- id : data . id ,
139- name : data . name ,
140- commits : [ ] ,
141- } ;
142- } catch ( e ) {
143- throw new Error ( `Get user info failed: ${ e } ` ) ;
139+ const sessions = ( await JSON . parse ( allSessions ) ) as AuthenticationSession [ ] ;
140+ const session = sessions . find ( ( s ) => s . id === sessionId ) ;
141+ if ( ! session ) {
142+ return ;
144143 }
144+
145+ await this . context . secrets . delete ( this . _secretSessionKey ) ;
146+ this . _onDidChangeSessions . fire ( {
147+ added : [ ] ,
148+ removed : [ session ] ,
149+ changed : [ ] ,
150+ } ) ;
151+ }
152+
153+ // Local Methods
154+ private async _login ( scopes : string [ ] = [ ] ) {
155+ return await window . withProgress < AuthDetails > (
156+ {
157+ location : ProgressLocation . Notification ,
158+ title : "Signing in to Commit..." ,
159+ cancellable : true ,
160+ } ,
161+ async ( _ , __ ) => {
162+ // Get the device Code from Auth0
163+ const registerDeviceResponse = await this . _registerDeviceCode ( scopes ) ;
164+ const endTime = Date . now ( ) + registerDeviceResponse . expiresIn * 1000 ;
165+
166+ // Extract the user code from the verificationUriComplete
167+ const userCode =
168+ registerDeviceResponse . verificationUriComplete . match (
169+ / u s e r _ c o d e = ( [ ^ & ] + ) /
170+ ) ?. [ 1 ] ;
171+
172+ // Show the user code in a message
173+ window . showInformationMessage (
174+ `Confirm Device Code: ${ userCode } in your browser`
175+ ) ;
176+
177+ // Open the verification URL
178+ await env . openExternal (
179+ Uri . parse ( registerDeviceResponse . verificationUriComplete )
180+ ) ;
181+
182+ // Poll Access Token
183+ return await this . _pollAccessToken (
184+ endTime ,
185+ registerDeviceResponse . deviceCode ,
186+ registerDeviceResponse . interval
187+ ) ;
188+ }
189+ ) ;
145190 }
146191
147- /**
148- * Get the device code from Auth0
149- */
150192 private async _registerDeviceCode ( scopes : string [ ] ) : Promise < UserDeviceCode > {
151193 try {
152194 const response = await fetch ( `${ COMMIT_AUTH0_DOMAIN } /oauth/device/code` , {
@@ -185,64 +227,39 @@ export class Auth0AuthenticationProvider extends AuthProvider {
185227 }
186228 }
187229
188- /**
189- * Login to Auth0
190- */
191- private async _login ( scopes : string [ ] = [ ] ) {
192- return await window . withProgress < AuthDetails > (
193- {
194- location : ProgressLocation . Notification ,
195- title : "Signing in to Commit..." ,
196- cancellable : true ,
197- } ,
198- async ( _ , __ ) => {
199- const stateId = uuid ( ) ;
200-
201- this . _pendingStates . push ( stateId ) ;
202-
203- // Get the device Code from Auth0
204- const registerDeviceResponse = await this . _registerDeviceCode ( scopes ) ;
205- const endTime = Date . now ( ) + registerDeviceResponse . expiresIn * 1000 ;
206-
207- // Extract the user code from the verificationUriComplete
208- const userCode =
209- registerDeviceResponse . verificationUriComplete . match (
210- / u s e r _ c o d e = ( [ ^ & ] + ) /
211- ) ?. [ 1 ] ;
212-
213- // Show the user code in a message
214- window . showInformationMessage (
215- `Confirm Device Code: ${ userCode } in your browser`
216- ) ;
217-
218- // Open the verification URL
219- await env . openExternal (
220- Uri . parse ( registerDeviceResponse . verificationUriComplete )
221- ) ;
222-
223- try {
224- // Poll Access Token
225- return await this . _pollAccessToken (
226- endTime ,
227- registerDeviceResponse . deviceCode ,
228- registerDeviceResponse . interval
229- ) ;
230- } finally {
231- this . _pendingStates = this . _pendingStates . filter (
232- ( s ) => s !== stateId
233- ) ;
230+ private async _getUserInfo ( token : string ) : Promise < UserInfo > {
231+ // Make POST request to get user info
232+ try {
233+ const response = await fetch (
234+ `${ COMMIT_API_BASE_URL } /v1/auth.get-own-user` ,
235+ {
236+ method : "POST" ,
237+ headers : {
238+ // eslint-disable-next-line @typescript-eslint/naming-convention
239+ "content-type" : "application/json" ,
240+ cookie : `helix_session_token=${ token } ` ,
241+ origin : COMMIT_APP_BASE_URL || "" ,
242+ referer : COMMIT_APP_BASE_URL || "" ,
243+ } ,
234244 }
245+ ) ;
246+
247+ if ( ! response . ok ) {
248+ throw new Error ( `Get user info invalid status: ${ response . statusText } ` ) ;
235249 }
236- ) ;
250+
251+ const data = ( await response . json ( ) ) as any ;
252+ return {
253+ email : data . email ,
254+ id : data . id ,
255+ name : data . name ,
256+ commits : [ ] ,
257+ } ;
258+ } catch ( e ) {
259+ throw new Error ( `Get user info failed: ${ e } ` ) ;
260+ }
237261 }
238262
239- /**
240- * Poll for the access token
241- * @param deviceCode
242- * @param interval
243- * @param expiresIn
244- * @returns
245- */
246263 private async _pollAccessToken (
247264 endTime : number ,
248265 deviceCode : string ,
0 commit comments