@@ -6,15 +6,25 @@ import {
66 ServiceAccountAuth ,
77 UsernamePasswordAuth
88} from "../types" ;
9- import { TokenKey , inMemoryCache , noneCache } from "../common/tokenCache" ;
10- import ReadWriteLock from "rwlock" ;
9+ import {
10+ TokenKey ,
11+ inMemoryCache ,
12+ noneCache ,
13+ rwLock
14+ } from "../common/tokenCache" ;
1115
1216type Login = {
1317 access_token : string ;
1418 token_type : string ;
1519 expires_in : number ;
1620} ;
1721
22+ type TokenInfo = {
23+ access_token : string ;
24+ // seconds until expiration
25+ expires_in : number ;
26+ } ;
27+
1828const AUTH_AUDIENCE = "https://api.firebolt.io" ;
1929const AUTH_GRANT_TYPE = "client_credentials" ;
2030
@@ -23,7 +33,7 @@ export class Authenticator {
2333 options : ConnectionOptions ;
2434
2535 accessToken ?: string ;
26- private readonly rwlock = new ReadWriteLock ( ) ;
36+ tokenExpirationTime ?: number ;
2737
2838 constructor ( context : Context , options : ConnectionOptions ) {
2939 context . httpClient . authenticator = this ;
@@ -62,23 +72,40 @@ export class Authenticator {
6272 }
6373
6474 private setToken ( token : string , expiresIn : number ) {
75+ // Set expiration to half of the expiresIn value
76+ // to allow for some buffer time before the token expires
77+ const expirationTimeMs = Date . now ( ) + ( expiresIn * 1000 ) / 2 ;
6578 this . accessToken = token ;
79+ this . tokenExpirationTime = expirationTimeMs ;
80+ // Update cache
6681 const key = this . getCacheKey ( ) ;
67- key && this . getCache ( ) . set ( key , { token, expiration : expiresIn } ) ;
82+ key &&
83+ this . getCache ( ) . set ( key , { token, tokenExpiryTimeMs : expirationTimeMs } ) ;
6884 }
6985
70- private getCachedToken ( ) : string | undefined {
86+ private getCachedTokenInfo ( ) :
87+ | { token : string ; tokenExpiryTimeMs : number }
88+ | undefined {
7189 const key = this . getCacheKey ( ) ;
72- return key ? this . getCache ( ) . get ( key ) ?. token : undefined ;
73- }
90+ if ( ! key ) return undefined ;
7491
75- getHeaders ( ) {
76- if ( this . accessToken ) {
92+ const cachedTokenInfo = this . getCache ( ) . get ( key ) ;
93+ // Check if token exists and is not expired
94+ if ( cachedTokenInfo && Date . now ( ) < cachedTokenInfo . tokenExpiryTimeMs ) {
7795 return {
78- Authorization : `Bearer ${ this . accessToken } `
96+ token : cachedTokenInfo . token ,
97+ tokenExpiryTimeMs : cachedTokenInfo . tokenExpiryTimeMs
7998 } ;
8099 }
81- return { } ;
100+
101+ return undefined ;
102+ }
103+
104+ async getToken ( ) : Promise < string | undefined > {
105+ if ( this . tokenExpirationTime && this . tokenExpirationTime < Date . now ( ) ) {
106+ await this . authenticate ( ) ;
107+ }
108+ return this . accessToken ;
82109 }
83110
84111 private static getAuthEndpoint ( apiEndpoint : string ) {
@@ -94,7 +121,9 @@ export class Authenticator {
94121 return myURL . toString ( ) ;
95122 }
96123
97- private async authenticateUsernamePassword ( auth : UsernamePasswordAuth ) {
124+ private async authenticateUsernamePassword (
125+ auth : UsernamePasswordAuth
126+ ) : Promise < TokenInfo > {
98127 const { httpClient, apiEndpoint } = this . context ;
99128 const { username, password } = auth ;
100129 const url = `${ apiEndpoint } /${ USERNAME_PASSWORD_LOGIN } ` ;
@@ -103,19 +132,21 @@ export class Authenticator {
103132 password
104133 } ) ;
105134
106- this . accessToken = undefined ;
107-
135+ // Expiration is in seconds
108136 const { access_token, expires_in } = await httpClient
109137 . request < Login > ( "POST" , url , {
110138 body,
111- retry : false
139+ retry : false ,
140+ auth : false
112141 } )
113142 . ready ( ) ;
114143
115- this . setToken ( access_token , expires_in ) ;
144+ return { access_token, expires_in } ;
116145 }
117146
118- private async authenticateServiceAccount ( auth : ServiceAccountAuth ) {
147+ private async authenticateServiceAccount (
148+ auth : ServiceAccountAuth
149+ ) : Promise < TokenInfo > {
119150 const { httpClient, apiEndpoint } = this . context ;
120151 const { client_id, client_secret } = auth ;
121152
@@ -128,19 +159,19 @@ export class Authenticator {
128159 } ) ;
129160 const url = `${ authEndpoint } ${ SERVICE_ACCOUNT_LOGIN } ` ;
130161
131- this . accessToken = undefined ;
132-
162+ // Expiration is in seconds
133163 const { access_token, expires_in } = await httpClient
134164 . request < Login > ( "POST" , url , {
135165 retry : false ,
136166 headers : {
137167 "Content-Type" : "application/x-www-form-urlencoded"
138168 } ,
139- body : params
169+ body : params ,
170+ auth : false
140171 } )
141172 . ready ( ) ;
142173
143- this . setToken ( access_token , expires_in ) ;
174+ return { access_token, expires_in } ;
144175 }
145176
146177 isUsernamePassword ( ) {
@@ -163,19 +194,21 @@ export class Authenticator {
163194 // Try to get token from cache using read lock
164195 const cachedToken = await this . tryGetCachedToken ( ) ;
165196 if ( cachedToken ) {
166- this . accessToken = cachedToken ;
197+ this . accessToken = cachedToken . token ;
198+ this . tokenExpirationTime = cachedToken . tokenExpiryTimeMs ;
167199 return ;
168200 }
169-
170201 // No cached token, acquire write lock and authenticate
171202 await this . acquireWriteLockAndAuthenticate ( ) ;
172203 }
173204
174- private async tryGetCachedToken ( ) : Promise < string | undefined > {
205+ private async tryGetCachedToken ( ) : Promise <
206+ { token : string ; tokenExpiryTimeMs : number } | undefined
207+ > {
175208 return new Promise ( ( resolve , reject ) => {
176- this . rwlock . readLock ( releaseReadLock => {
209+ rwLock . readLock ( releaseReadLock => {
177210 try {
178- const cachedToken = this . getCachedToken ( ) ;
211+ const cachedToken = this . getCachedTokenInfo ( ) ;
179212 resolve ( cachedToken ) ;
180213 } catch ( error ) {
181214 reject ( error instanceof Error ? error : new Error ( String ( error ) ) ) ;
@@ -188,15 +221,15 @@ export class Authenticator {
188221
189222 private async acquireWriteLockAndAuthenticate ( ) : Promise < void > {
190223 return new Promise ( ( resolve , reject ) => {
191- this . rwlock . writeLock ( async releaseWriteLock => {
224+ rwLock . writeLock ( async releaseWriteLock => {
192225 try {
193226 // Double-check cache in case another thread authenticated while waiting
194- const cachedToken = this . getCachedToken ( ) ;
195- if ( cachedToken ) {
196- this . accessToken = cachedToken ;
227+ const cachedTokenInfo = this . getCachedTokenInfo ( ) ;
228+ if ( cachedTokenInfo ) {
229+ this . accessToken = cachedTokenInfo . token ;
230+ this . tokenExpirationTime = cachedTokenInfo . tokenExpiryTimeMs ;
197231 return resolve ( ) ;
198232 }
199-
200233 await this . performAuthentication ( ) ;
201234
202235 resolve ( ) ;
@@ -212,14 +245,20 @@ export class Authenticator {
212245 private async performAuthentication ( ) : Promise < void > {
213246 const options = this . options . auth || this . options ;
214247
215- if ( this . isUsernamePassword ( ) ) {
216- return this . authenticateUsernamePassword ( options as UsernamePasswordAuth ) ;
217- }
248+ let auth : TokenInfo ;
218249
219- if ( this . isServiceAccount ( ) ) {
220- return this . authenticateServiceAccount ( options as ServiceAccountAuth ) ;
250+ if ( this . isUsernamePassword ( ) ) {
251+ auth = await this . authenticateUsernamePassword (
252+ options as UsernamePasswordAuth
253+ ) ;
254+ } else if ( this . isServiceAccount ( ) ) {
255+ auth = await this . authenticateServiceAccount (
256+ options as ServiceAccountAuth
257+ ) ;
258+ } else {
259+ throw new Error ( "Please provide valid auth credentials" ) ;
221260 }
222261
223- throw new Error ( "Please provide valid auth credentials" ) ;
262+ this . setToken ( auth . access_token , auth . expires_in ) ;
224263 }
225264}
0 commit comments