All files / src/Authentication TokenStore.ts

100% Statements 54/54
90.7% Branches 39/43
100% Functions 12/12
100% Lines 54/54
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150      1x                   1x                   44x 44x 44x 44x 44x 44x 44x 44x   44x 40x 4x 2x   2x             44x               250x       8x 8x 4x 8x 4x 4x 4x     4x       4x 4x 2x   4x                 219x 219x 219x   203x   4x   4x     8x         4x                 31x 31x 31x   23x 23x   2x 2x   2x 2x   2x 2x   2x 2x               160x     11x             59x     9x        
/**
 * @module Authentication
 */ /** */
import { Token, TokenPersist, TokenStoreType } from './';
 
/**
 * Indicates the type of the token
 */
export type TokenType = 'access' | 'refresh';
 
/**
 * This class is intended to store token data in LocalStorage or in-memory storage.
 */
export class TokenStore {
 
    /**
     * @param {strnig} baseUrl The Base URL to the related site
     * @param {string} keyTemplate The template to use when generating keys in the local/session storage or for a cookie. ${siteName} and ${tokenName} will be replaced. Example: 'sn-${siteName}-${tokenName}'
     * @param {TokenPersist} tokenPersist Setting that indicates if the token should be persisted per session (browser close) or per Token expiration (based on the token `exp` property)
     * @param {Partial<Document>} documentRef The Document reference (used by unit tests)
     * @param {Storage} localStorageRef The localStorage reference (used by unit tests)
     * @param {Storage} sessionStorageRef The sessionStorage reference (used by unit tests)
     */
    constructor(private readonly _baseUrl: string,
                private readonly _keyTemplate: string,
                private readonly _tokenPersist: TokenPersist,
                private _documentRef = (typeof document === 'object') ? document : undefined,
                private _localStorageRef = (typeof localStorage === 'object') ? localStorage : undefined,
                private _sessionStorageRef = (typeof sessionStorage === 'object') ? sessionStorage : undefined) {
        const storesAvailable = (typeof this._localStorageRef !== 'undefined' && typeof this._sessionStorageRef !== 'undefined');
        const cookieAvailable = (typeof this._documentRef !== 'undefined' && typeof this._documentRef.cookie !== 'undefined');
 
        if (!storesAvailable && !cookieAvailable) {
            this.TokenStoreType = TokenStoreType.InMemory;
        } else if (this._tokenPersist === TokenPersist.Expiration) {
            storesAvailable ? this.TokenStoreType = TokenStoreType.LocalStorage : this.TokenStoreType = TokenStoreType.ExpirationCookie;
        } else {
            storesAvailable ? this.TokenStoreType = TokenStoreType.SessionStorage : this.TokenStoreType = TokenStoreType.SessionCookie;
        }
    }
 
    /**
     * If localStorage is not available, stores the token data in this in-memory array
     */
    private _innerStore: Map<string, string> = new Map();
 
    /**
     * The type of the generated Token Store
     */
    public readonly TokenStoreType: TokenStoreType;
 
    private getStoreKey(key: TokenType) {
        return this._keyTemplate.replace('${siteName}', this._baseUrl).replace('${tokenName}', key);
    }
 
    private getTokenFromCookie(key: string, document: Document): Token {
        const prefix = key + '=';
        if (document && document.cookie) {
            const cookieVal = document.cookie.split(';')
                .map((v) => v.trim())
                .find((v) => v.trim().indexOf(prefix) === 0);
            Eif (cookieVal) {
                return Token.FromHeadAndPayload(cookieVal.substring(prefix.length));
            }
        }
        return Token.CreateEmpty();
    }
 
    private setTokenToCookie(key: string, token: Token, persist: TokenPersist, doc: Document): void {
        let cookie = `${key}=${token.toString()}`;
        if (persist === TokenPersist.Expiration) {
            cookie += `; expires=${token.ExpirationTime.toUTCString()};`;
        }
        doc.cookie = cookie;
    }
 
    /**
     * Gets the specified token
     * @param key {TokenType} The key for the token
     * @returns {Token} The requested token, or Token.Empty in case of error
     */
    public GetToken(key: TokenType): Token {
        const storeKey = this.getStoreKey(key);
        try {
            switch (this.TokenStoreType) {
                case TokenStoreType.InMemory:
                    return this._innerStore.has(storeKey) ? Token.FromHeadAndPayload(this._innerStore.get(storeKey) as string) : Token.CreateEmpty();
                case TokenStoreType.LocalStorage:
                    return Token.FromHeadAndPayload((this._localStorageRef as any).getItem(storeKey));
                case TokenStoreType.SessionStorage:
                    return Token.FromHeadAndPayload((this._sessionStorageRef as any).getItem(storeKey));
                case TokenStoreType.ExpirationCookie:
                case TokenStoreType.SessionCookie:
                    return this.getTokenFromCookie(storeKey, this._documentRef as Document);
            }
        } catch (err) {
            //
        }
        return Token.CreateEmpty();
    }
 
    /**
     * Sets the token with the specified key to the specified value
     * @param key {TokenType} The key for the token to set
     * @param token {Token} The token to set with the specified key
     */
    public SetToken(key: TokenType, token: Token) {
        const storeKey = this.getStoreKey(key);
        const dtaString = token.toString();
        switch (this.TokenStoreType) {
            case TokenStoreType.InMemory:
                this._innerStore.set(storeKey, dtaString);
                break;
            case TokenStoreType.LocalStorage:
                (this._localStorageRef as Storage).setItem(storeKey, dtaString);
                break;
            case TokenStoreType.SessionStorage:
                (this._sessionStorageRef as Storage).setItem(storeKey, dtaString);
                break;
            case TokenStoreType.ExpirationCookie:
                this.setTokenToCookie(storeKey, token, TokenPersist.Expiration, this._documentRef as Document);
                break;
            case TokenStoreType.SessionCookie:
                this.setTokenToCookie(storeKey, token, TokenPersist.Session, this._documentRef as Document);
                break;
        }
    }
 
    /**
     * The current Access Token
     */
    public get AccessToken() {
        return this.GetToken('access');
    }
    public set AccessToken(value: Token) {
        this.SetToken('access', value);
    }
 
    /**
     * The current Refresh Token
     */
    public get RefreshToken() {
        return this.GetToken('refresh');
    }
    public set RefreshToken(value: Token) {
        this.SetToken('refresh', value);
    }
 
}