diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/RefreshingAuthSessionManager.ts b/packages/core/src/api/apis/implementations/lib/AuthSessionManager/RefreshingAuthSessionManager.ts index 34415ea615..0cb0f2f1ea 100644 --- a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/RefreshingAuthSessionManager.ts +++ b/packages/core/src/api/apis/implementations/lib/AuthSessionManager/RefreshingAuthSessionManager.ts @@ -14,29 +14,23 @@ * limitations under the License. */ -import { hasScopes } from '../../OAuthRequestManager/OAuthPendingRequests'; -import { SessionManager } from './types'; +import { + SessionManager, + SessionScopesFunc, + SessionShouldRefreshFunc, +} from './types'; import { AuthConnector } from '../AuthConnector'; +import { SessionScopeHelper } from './common'; +import { hasScopes } from '../../OAuthRequestManager/OAuthPendingRequests'; -type Options = { - /** - * The connector used for acting on the auth session. - */ - connector: AuthConnector; - /** - * A function called to determine the scopes of the session. - */ - sessionScopes: (session: AuthSession) => Set; - /** - * A function called to determine whether it's time for a session to refresh. - * - * This should return true before the session expires, for example, if a session - * expires after 60 minutes, you could return true if the session is older than 45 minutes. - */ - sessionShouldRefresh: (session: AuthSession) => boolean; - /** - * The default scopes that should always be present in a session, defaults to none. - */ +type Options = { + /** The connector used for acting on the auth session */ + connector: AuthConnector; + /** Used to get the scope of the session */ + sessionScopes: SessionScopesFunc; + /** Used to check if the session needs to be refreshed */ + sessionShouldRefresh: SessionShouldRefreshFunc; + /** The default scopes that should always be present in a session, defaults to none. */ defaultScopes?: Set; }; @@ -44,17 +38,16 @@ type Options = { * RefreshingAuthSessionManager manages an underlying session that has * and expiration time and needs to be refreshed periodically. */ -export class RefreshingAuthSessionManager - implements SessionManager { - private readonly connector: AuthConnector; - private readonly defaultScopes?: Set; - private readonly sessionScopesFunc: (session: AuthSession) => Set; - private readonly sessionShouldRefreshFunc: (session: AuthSession) => boolean; +export class RefreshingAuthSessionManager implements SessionManager { + private readonly connector: AuthConnector; + private readonly helper: SessionScopeHelper; + private readonly sessionScopesFunc: SessionScopesFunc; + private readonly sessionShouldRefreshFunc: SessionShouldRefreshFunc; - private refreshPromise?: Promise; - private currentSession: AuthSession | undefined; + private refreshPromise?: Promise; + private currentSession: T | undefined; - constructor(options: Options) { + constructor(options: Options) { const { connector, defaultScopes = new Set(), @@ -63,24 +56,26 @@ export class RefreshingAuthSessionManager } = options; this.connector = connector; - this.defaultScopes = defaultScopes; this.sessionScopesFunc = sessionScopes; this.sessionShouldRefreshFunc = sessionShouldRefresh; + this.helper = new SessionScopeHelper({ sessionScopes, defaultScopes }); } async getSession(options: { optional: false; scopes?: Set; - }): Promise; + }): Promise; async getSession(options: { optional?: boolean; scopes?: Set; - }): Promise; + }): Promise; async getSession(options: { optional?: boolean; scopes?: Set; - }): Promise { - if (this.sessionExistsAndHasScope(this.currentSession, options.scopes)) { + }): Promise { + if ( + this.helper.sessionExistsAndHasScope(this.currentSession, options.scopes) + ) { const shouldRefresh = this.sessionShouldRefreshFunc(this.currentSession!); if (!shouldRefresh) { return this.currentSession!; @@ -111,7 +106,7 @@ export class RefreshingAuthSessionManager // The session might not have the scopes requested so go back and check again return this.getSession(options); } catch { - // If the refresh attemp fails we assume we don't have a session, so continue to create one. + // If the refresh attempt fails we assume we don't have a session, so continue to create one. } } @@ -122,7 +117,7 @@ export class RefreshingAuthSessionManager // We can call authRequester multiple times, the returned session will contain all requested scopes. this.currentSession = await this.connector.createSession( - this.getExtendedScope(options.scopes), + this.helper.getExtendedScope(this.currentSession, options.scopes), ); return this.currentSession; } @@ -132,37 +127,7 @@ export class RefreshingAuthSessionManager window.location.reload(); // TODO(Rugvip): make this work without reload? } - private sessionExistsAndHasScope( - session: AuthSession | undefined, - scopes?: Set, - ): boolean { - if (!session) { - return false; - } - if (!scopes) { - return true; - } - const sessionScopes = this.sessionScopesFunc(session); - return hasScopes(sessionScopes, scopes); - } - - private getExtendedScope(scopes?: Set) { - const newScope = new Set(this.defaultScopes); - if (this.currentSession) { - const sessionScopes = this.sessionScopesFunc(this.currentSession); - for (const scope of sessionScopes) { - newScope.add(scope); - } - } - if (scopes) { - for (const scope of scopes) { - newScope.add(scope); - } - } - return newScope; - } - - private async collapsedSessionRefresh(): Promise { + private async collapsedSessionRefresh(): Promise { if (this.refreshPromise) { return this.refreshPromise; } diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/common.ts b/packages/core/src/api/apis/implementations/lib/AuthSessionManager/common.ts new file mode 100644 index 0000000000..83b81bc81a --- /dev/null +++ b/packages/core/src/api/apis/implementations/lib/AuthSessionManager/common.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SessionScopesFunc } from './types'; + +export function hasScopes( + searched: Set, + searchFor: Set, +): boolean { + for (const scope of searchFor) { + if (!searched.has(scope)) { + return false; + } + } + return true; +} + +type ScopeHelperOptions = { + sessionScopes: SessionScopesFunc; + defaultScopes?: Set; +}; + +export class SessionScopeHelper { + constructor(private readonly options: ScopeHelperOptions) {} + + sessionExistsAndHasScope( + session: T | undefined, + scopes?: Set, + ): boolean { + if (!session) { + return false; + } + if (!scopes) { + return true; + } + const sessionScopes = this.options.sessionScopes(session); + return hasScopes(sessionScopes, scopes); + } + + getExtendedScope(session: T | undefined, scopes?: Set) { + const newScope = new Set(this.options.defaultScopes); + if (session) { + const sessionScopes = this.options.sessionScopes(session); + for (const scope of sessionScopes) { + newScope.add(scope); + } + } + if (scopes) { + for (const scope of scopes) { + newScope.add(scope); + } + } + return newScope; + } +} diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/types.ts b/packages/core/src/api/apis/implementations/lib/AuthSessionManager/types.ts index 5c5f700047..5ebc8643e1 100644 --- a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/types.ts +++ b/packages/core/src/api/apis/implementations/lib/AuthSessionManager/types.ts @@ -19,15 +19,25 @@ * multiple simultaneous requests for sessions with different scope are handled * in a correct way. */ -export type SessionManager = { - getSession(options: { - optional: false; - scopes?: Set; - }): Promise; +export type SessionManager = { + getSession(options: { optional: false; scopes?: Set }): Promise; getSession(options: { optional?: boolean; scopes?: Set; - }): Promise; + }): Promise; removeSession(): Promise; }; + +/** + * A function called to determine the scopes of a session. + */ +export type SessionScopesFunc = (session: T) => Set; + +/** + * A function called to determine whether it's time for a session to refresh. + * + * This should return true before the session expires, for example, if a session + * expires after 60 minutes, you could return true if the session is older than 45 minutes. + */ +export type SessionShouldRefreshFunc = (session: T) => boolean;