11import { AuthenticationInfo , fetchAuthenticationInfo , logout } from "./api"
2+ import { runWithRetriesOnAnyError } from "./fetch_retries"
23import { currentTimeSeconds , getLocalStorageNumber , hasLocalStorage , hasWindow } from "./helpers"
3- import { runWithRetriesOnAnyError } from "./fetch_retries" ;
44
55const LOGGED_IN_AT_KEY = "__PROPEL_AUTH_LOGGED_IN_AT"
66const LOGGED_OUT_AT_KEY = "__PROPEL_AUTH_LOGGED_OUT_AT"
77const AUTH_TOKEN_REFRESH_BEFORE_EXPIRATION_SECONDS = 10 * 60
88const DEBOUNCE_DURATION_FOR_REFOCUS_SECONDS = 60
9+ const ACTIVE_ORG_ACCESS_TOKEN_REFRESH_EXPIRATION_SECONDS = 60 * 5
910
1011const encodeBase64 = ( str : string ) => {
1112 const encode = window ? window . btoa : btoa
@@ -38,6 +39,20 @@ export interface RedirectToSetupSAMLPageOptions {
3839 redirectBackToUrl ?: string
3940}
4041
42+ export type AccessTokenForActiveOrg =
43+ | {
44+ error : undefined
45+ accessToken : string
46+ }
47+ | {
48+ error : "user_not_in_org"
49+ accessToken : never
50+ }
51+ | {
52+ error : "unexpected_error"
53+ accessToken : never
54+ }
55+
4156export interface IAuthClient {
4257 /**
4358 * If the user is logged in, this method returns an access token, the time (in seconds) that the token will expire,
@@ -88,6 +103,11 @@ export interface IAuthClient {
88103 */
89104 getSetupSAMLPageUrl ( orgId : string ) : string
90105
106+ /**
107+ * Gets an access token for a specific organization, known as an Active Org.
108+ */
109+ getAccessTokenForOrg ( orgId : string ) : Promise < AccessTokenForActiveOrg >
110+
91111 /**
92112 * Redirects the user to the signup page.
93113 */
@@ -160,6 +180,13 @@ export interface IAuthOptions {
160180 enableBackgroundTokenRefresh ?: boolean
161181}
162182
183+ interface AccessTokenActiveOrgMap {
184+ [ orgId : string ] : {
185+ accessToken : string
186+ fetchedAt : number
187+ }
188+ }
189+
163190interface ClientState {
164191 initialLoadFinished : boolean
165192 authenticationInfo : AuthenticationInfo | null
@@ -169,6 +196,7 @@ interface ClientState {
169196 lastLoggedOutAtMessage : number | null
170197 refreshInterval : number | null
171198 lastRefresh : number | null
199+ accessTokenActiveOrgMap : AccessTokenActiveOrgMap
172200 readonly authUrl : string
173201}
174202
@@ -201,6 +229,7 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
201229 authUrl : authOptions . authUrl ,
202230 refreshInterval : null ,
203231 lastRefresh : null ,
232+ accessTokenActiveOrgMap : { } ,
204233 }
205234
206235 // Helper functions
@@ -248,6 +277,13 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
248277 }
249278 }
250279
280+ /**
281+ * Invalidates all org's access tokens.
282+ */
283+ function resetAccessTokenActiveOrgMap ( ) {
284+ clientState . accessTokenActiveOrgMap = { }
285+ }
286+
251287 function setAuthenticationInfoAndUpdateDownstream ( authenticationInfo : AuthenticationInfo | null ) {
252288 const previousAccessToken = clientState . authenticationInfo ?. accessToken
253289 clientState . authenticationInfo = authenticationInfo
@@ -265,14 +301,18 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
265301 notifyObserversOfAccessTokenChange ( accessToken )
266302 }
267303
304+ resetAccessTokenActiveOrgMap ( )
305+
268306 clientState . lastRefresh = currentTimeSeconds ( )
269307 clientState . initialLoadFinished = true
270308 }
271309
272310 async function forceRefreshToken ( returnCached : boolean ) : Promise < AuthenticationInfo | null > {
273311 try {
274312 // Happy case, we fetch auth info and save it
275- const authenticationInfo = await runWithRetriesOnAnyError ( ( ) => fetchAuthenticationInfo ( clientState . authUrl ) )
313+ const authenticationInfo = await runWithRetriesOnAnyError ( ( ) =>
314+ fetchAuthenticationInfo ( clientState . authUrl )
315+ )
276316 setAuthenticationInfoAndUpdateDownstream ( authenticationInfo )
277317 return authenticationInfo
278318 } catch ( e ) {
@@ -449,6 +489,52 @@ export function createClient(authOptions: IAuthOptions): IAuthClient {
449489 }
450490 } ,
451491
492+ async getAccessTokenForOrg ( orgId : string ) : Promise < AccessTokenForActiveOrg > {
493+ // First, check if there is a valid access token for the org ID in the
494+ // valid time frame.
495+ const currentTimeSecs = currentTimeSeconds ( )
496+
497+ const activeOrgAccessToken = clientState . accessTokenActiveOrgMap [ orgId ]
498+ if ( ! ! activeOrgAccessToken ) {
499+ if (
500+ currentTimeSecs <
501+ activeOrgAccessToken . fetchedAt + ACTIVE_ORG_ACCESS_TOKEN_REFRESH_EXPIRATION_SECONDS
502+ ) {
503+ return {
504+ accessToken : activeOrgAccessToken . accessToken ,
505+ error : undefined ,
506+ }
507+ }
508+ }
509+ // Fetch the access token for the org ID and update.
510+ try {
511+ const authenticationInfo = await runWithRetriesOnAnyError ( ( ) =>
512+ fetchAuthenticationInfo ( clientState . authUrl , orgId )
513+ )
514+ if ( ! authenticationInfo ) {
515+ // Only null if 401 unauthorized.
516+ return {
517+ error : "user_not_in_org" ,
518+ accessToken : null as never ,
519+ }
520+ }
521+ const { accessToken } = authenticationInfo
522+ clientState . accessTokenActiveOrgMap [ orgId ] = {
523+ accessToken,
524+ fetchedAt : currentTimeSecs ,
525+ }
526+ return {
527+ accessToken,
528+ error : undefined ,
529+ }
530+ } catch ( e ) {
531+ return {
532+ error : "unexpected_error" ,
533+ accessToken : null as never ,
534+ }
535+ }
536+ } ,
537+
452538 getSignupPageUrl ( options ?: RedirectToSignupOptions ) : string {
453539 return getSignupPageUrl ( options )
454540 } ,
0 commit comments