UNPKG

@fullerstack/ngx-jwt

Version:
131 lines 15.4 kB
/** * @license * Copyright Neekware Inc. All Rights Reserved. * * Use of this source code is governed by a proprietary notice * that can be found at http://neekware.com/license/PRI.html */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Injectable } from '@angular/core'; import { ConfigService, DefaultApplicationConfig, } from '@fullerstack/ngx-config'; import { LoggerService } from '@fullerstack/ngx-logger'; import { Base64 } from 'js-base64'; import { cloneDeep as ldDeepClone, mergeWith as ldMergeWith } from 'lodash-es'; import { DefaultJwtConfig } from './jwt.default'; import * as i0 from "@angular/core"; import * as i1 from "@fullerstack/ngx-config"; import * as i2 from "@fullerstack/ngx-logger"; /** * An injectable class that handles JWT service */ export class JwtService { /** * Class constructor * @param options an optional configuration object */ constructor(config, logger) { this.config = config; this.logger = logger; this.nameSpace = 'JWT'; this.options = DefaultApplicationConfig; this.options = ldMergeWith(ldDeepClone({ jwt: DefaultJwtConfig }), this.config.options, (dest, src) => (Array.isArray(dest) ? src : undefined)); this.logger.info(`[${this.nameSpace}] JwtService ready ...`); } /** * Gets the payload portion of a JWT token * @param token JWT token (base64 encrypted) * @returns a payload object or null if decode fails */ getPayload(token) { let parts = []; try { parts = token.split('.'); if (parts.length !== 3) { throw Error('JWT must have 3 parts'); } } catch (e) { this.logger.error(e.message); return undefined; } try { const decoded = Base64.decode(parts[1]); const payload = JSON.parse(decoded); return payload; } catch (e) { this.logger.error('Cannot decode the token'); } return undefined; } /** * Tells if a JWT is token is expired * @param payload JWT payload object * @return true if JWT is already expired, else false */ isExpired(payload) { if (typeof payload === 'string') { payload = this.getPayload(payload); } if (payload) { const offset = (parseInt(payload.lee, 10) || this.options.jwt.expiryLeeway) * 1000; const now = this.utcSeconds(); const expiry = this.utcSeconds(payload.exp); const expired = now > expiry + offset; return expired; } return true; } /** * Calculates the next refresh time * @param payload JWT payload object * @param offset if true, a random time is added to the refresh time * where networkDelay < random < leeway * @returns total number of seconds till expiry or 0 if token is expired */ getRefreshTime(payload, offset = true) { if (typeof payload === 'string') { payload = this.getPayload(payload); } if (payload && !this.isExpired(payload)) { const now = this.utcSeconds(); const expiry = this.utcSeconds(payload.exp); const refresh = Math.floor((expiry - now) / 1000); const random = this.getRandomOffset(payload); const time = offset ? refresh + random : refresh; return time; } return 0; } /** * Calculates a random number where networkDelay < random < leeway * @param payload JWT payload object * @returns a random total number of seconds */ getRandomOffset(payload) { if (typeof payload === 'string') { payload = this.getPayload(payload); } const leeway = payload?.leeway || payload?.lee || this.options?.jwt?.expiryLeeway; const range = { lower: 1, upper: leeway - this.options?.jwt?.networkDelay || 2, }; return Math.floor(Math.random() * range.upper + range.lower); } /** * Calculates the UTC value of date/time in seconds * @param input date/time in seconds * @returns UTC value of date/time in seconds */ utcSeconds(input) { return input ? new Date(0).setUTCSeconds(input).valueOf() : new Date().valueOf(); } } JwtService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: JwtService, deps: [{ token: i1.ConfigService }, { token: i2.LoggerService }], target: i0.ɵɵFactoryTarget.Injectable }); JwtService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: JwtService, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.1.1", ngImport: i0, type: JwtService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: i1.ConfigService }, { type: i2.LoggerService }]; } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiand0LnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9saWJzL25neC1qd3Qvc3JjL2xpYi9qd3Quc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7O0dBTUc7QUFFSCx1REFBdUQ7QUFDdkQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBRUwsYUFBYSxFQUNiLHdCQUF3QixHQUN6QixNQUFNLHlCQUF5QixDQUFDO0FBQ2pDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN4RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ25DLE9BQU8sRUFBRSxTQUFTLElBQUksV0FBVyxFQUFFLFNBQVMsSUFBSSxXQUFXLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFHL0UsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sZUFBZSxDQUFDOzs7O0FBRWpEOztHQUVHO0FBRUgsTUFBTSxPQUFPLFVBQVU7SUFJckI7OztPQUdHO0lBQ0gsWUFBcUIsTUFBcUIsRUFBVyxNQUFxQjtRQUFyRCxXQUFNLEdBQU4sTUFBTSxDQUFlO1FBQVcsV0FBTSxHQUFOLE1BQU0sQ0FBZTtRQVBsRSxjQUFTLEdBQUcsS0FBSyxDQUFDO1FBQzFCLFlBQU8sR0FBb0Msd0JBQXdCLENBQUM7UUFPbEUsSUFBSSxDQUFDLE9BQU8sR0FBRyxXQUFXLENBQ3hCLFdBQVcsQ0FBQyxFQUFFLEdBQUcsRUFBRSxnQkFBZ0IsRUFBRSxDQUFDLEVBQ3RDLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUNuQixDQUFDLElBQUksRUFBRSxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FDdkQsQ0FBQztRQUVGLElBQUksQ0FB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QU8sT0FBTyxLQUFLLFFBQVEsRUFBRTtZQUMvQixPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztTQUNwQztRQUNELE1BQU0sTUFBTSxHQUFHLE9BQU8sRUFBRSxNQUFNLElBQUksT0FBTyxFQUFFLEdBQUcsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxZQUFZLENBQUM7UUFDbEYsTUFBTSxLQUFLLEdBQUc7WUFDWixLQUFLLEVBQUUsQ0FBQztZQUNSLEtBQUssRUFBRSxNQUFNLEdBQUcsSUFBSSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsWUFBWSxJQUFJLENBQUM7U0FDckQsQ0FBQztRQUNGLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsS0FBSyxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDL0QsQ0FBQztJQUVEOzs7O09BSUc7SUFDSyxVQUFVLENBQUMsS0FBYztRQUMvQixPQUFPLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ25GLENBQUM7O3VHQWhIVSxVQUFVOzJHQUFWLFVBQVUsY0FERyxNQUFNOzJGQUNuQixVQUFVO2tCQUR0QixVQUFVO21CQUFDLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBOZWVrd2FyZSBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYSBwcm9wcmlldGFyeSBub3RpY2VcbiAqIHRoYXQgY2FuIGJlIGZvdW5kIGF0IGh0dHA6Ly9uZWVrd2FyZS5jb20vbGljZW5zZS9QUkkuaHRtbFxuICovXG5cbi8qIGVzbGludC1kaXNhYmxlIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnkgKi9cbmltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7XG4gIEFwcGxpY2F0aW9uQ29uZmlnLFxuICBDb25maWdTZXJ2aWNlLFxuICBEZWZhdWx0QXBwbGljYXRpb25Db25maWcsXG59IGZyb20gJ0BmdWxsZXJzdGFjay9uZ3gtY29uZmlnJztcbmltcG9ydCB7IExvZ2dlclNlcnZpY2UgfSBmcm9tICdAZnVsbGVyc3RhY2svbmd4LWxvZ2dlcic7XG5pbXBvcnQgeyBCYXNlNjQgfSBmcm9tICdqcy1iYXNlNjQnO1xuaW1wb3J0IHsgY2xvbmVEZWVwIGFzIGxkRGVlcENsb25lLCBtZXJnZVdpdGggYXMgbGRNZXJnZVdpdGggfSBmcm9tICdsb2Rhc2gtZXMnO1xuaW1wb3J0IHsgRGVlcFJlYWRvbmx5IH0gZnJvbSAndHMtZXNzZW50aWFscyc7XG5cbmltcG9ydCB7IERlZmF1bHRKd3RDb25maWcgfSBmcm9tICcuL2p3dC5kZWZhdWx0JztcblxuLyoqXG4gKiBBbiBpbmplY3RhYmxlIGNsYXNzIHRoYXQgaGFuZGxlcyBKV1Qgc2VydmljZVxuICovXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIEp3dFNlcnZpY2Uge1xuICBwcml2YXRlIG5hbWVTcGFjZSA9ICdKV1QnO1xuICBvcHRpb25zOiBEZWVwUmVhZG9ubHk8QXBwbGljYXRpb25Db25maWc+ID0gRGVmYXVsdEFwcGxpY2F0aW9uQ29uZmlnO1xuXG4gIC8qKlxuICAgKiBDbGFzcyBjb25zdHJ1Y3RvclxuICAgKiBAcGFyYW0gb3B0aW9ucyBhbiBvcHRpb25hbCBjb25maWd1cmF0aW9uIG9iamVjdFxuICAgKi9cbiAgY29uc3RydWN0b3IocmVhZG9ubHkgY29uZmlnOiBDb25maWdTZXJ2aWNlLCByZWFkb25seSBsb2dnZXI6IExvZ2dlclNlcnZpY2UpIHtcbiAgICB0aGlzLm9wdGlvbnMgPSBsZE1lcmdlV2l0aChcbiAgICAgIGxkRGVlcENsb25lKHsgand0OiBEZWZhdWx0Snd0Q29uZmlnIH0pLFxuICAgICAgdGhpcy5jb25maWcub3B0aW9ucyxcbiAgICAgIChkZXN0LCBzcmMpID0+IChBcnJheS5pc0FycmF5KGRlc3QpID8gc3JjIDogdW5kZWZpbmVkKVxuICAgICk7XG5cbiAgICB0aGlzLmxvZ2dlci5pbmZvKGBbJHt0aGlzLm5hbWVTcGFjZX1dIEp3dFNlcnZpY2UgcmVhZHkgLi4uYCk7XG4gIH1cblxuICAvKipcbiAgICogR2V0cyB0aGUgcGF5bG9hZCBwb3J0aW9uIG9mIGEgSldUIHRva2VuXG4gICAqIEBwYXJhbSB0b2tlbiBKV1QgdG9rZW4gKGJhc2U2NCBlbmNyeXB0ZWQpXG4gICAqIEByZXR1cm5zIGEgcGF5bG9hZCBvYmplY3Qgb3IgbnVsbCBpZiBkZWNvZGUgZmFpbHNcbiAgICovXG4gIGdldFBheWxvYWQ8VD4odG9rZW46IHN0cmluZyk6IGFueSB7XG4gICAgbGV0IHBhcnRzID0gW107XG5cbiAgICB0cnkge1xuICAgICAgcGFydHMgPSB0b2tlbi5zcGxpdCgnLicpO1xuICAgICAgaWYgKHBhcnRzLmxlbmd0aCAhPT0gMykge1xuICAgICAgICB0aHJvdyBFcnJvcignSldUIG11c3QgaGF2ZSAzIHBhcnRzJyk7XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgdGhpcy5sb2dnZXIuZXJyb3IoZS5tZXNzYWdlKTtcbiAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGRlY29kZWQgPSBCYXNlNjQuZGVjb2RlKHBhcnRzWzFdKTtcbiAgICAgIGNvbnN0IHBheWxvYWQgPSBKU09OLnBhcnNlKGRlY29kZWQpO1xuICAgICAgcmV0dXJuIHBheWxvYWQgYXMgVDtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICB0aGlzLmxvZ2dlci5lcnJvcignQ2Fubm90IGRlY29kZSB0aGUgdG9rZW4nKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgLyoqXG4gICAqIFRlbGxzIGlmIGEgSldUIGlzIHRva2VuIGlzIGV4cGlyZWRcbiAgICogQHBhcmFtIHBheWxvYWQgSldUIHBheWxvYWQgb2JqZWN0XG4gICAqIEByZXR1cm4gdHJ1ZSBpZiBKV1QgaXMgYWxyZWFkeSBleHBpcmVkLCBlbHNlIGZhbHNlXG4gICAqL1xuICBpc0V4cGlyZWQocGF5bG9hZDogYW55KTogYm9vbGVhbiB7XG4gICAgaWYgKHR5cGVvZiBwYXlsb2FkID09PSAnc3RyaW5nJykge1xuICAgICAgcGF5bG9hZCA9IHRoaXMuZ2V0UGF5bG9hZChwYXlsb2FkKTtcbiAgICB9XG4gICAgaWYgKHBheWxvYWQpIHtcbiAgICAgIGNvbnN0IG9mZnNldCA9IChwYXJzZUludChwYXlsb2FkLmxlZSwgMTApIHx8IHRoaXMub3B0aW9ucy5qd3QuZXhwaXJ5TGVld2F5KSAqIDEwMDA7XG4gICAgICBjb25zdCBub3cgPSB0aGlzLnV0Y1NlY29uZHMoKTtcbiAgICAgIGNvbnN0IGV4cGlyeSA9IHRoaXMudXRjU2Vjb25kcyhwYXlsb2FkLmV4cCk7XG4gICAgICBjb25zdCBleHBpcmVkID0gbm93ID4gZXhwaXJ5ICsgb2Zmc2V0O1xuICAgICAgcmV0dXJuIGV4cGlyZWQ7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgLyoqXG4gICAqIENhbGN1bGF0ZXMgdGhlIG5leHQgcmVmcmVzaCB0aW1lXG4gICAqIEBwYXJhbSBwYXlsb2FkIEpXVCBwYXlsb2FkIG9iamVjdFxuICAgKiBAcGFyYW0gb2Zmc2V0IGlmIHRydWUsIGEgcmFuZG9tIHRpbWUgaXMgYWRkZWQgdG8gdGhlIHJlZnJlc2ggdGltZVxuICAgKiB3aGVyZSBuZXR3b3JrRGVsYXkgPCByYW5kb20gPCBsZWV3YXlcbiAgICogQHJldHVybnMgdG90YWwgbnVtYmVyIG9mIHNlY29uZHMgdGlsbCBleHBpcnkgb3IgMCBpZiB0b2tlbiBpcyBleHBpcmVkXG4gICAqL1xuICBnZXRSZWZyZXNoVGltZShwYXlsb2FkOiBhbnksIG9mZnNldCA9IHRydWUpOiBudW1iZXIge1xuICAgIGlmICh0eXBlb2YgcGF5bG9hZCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHBheWxvYWQgPSB0aGlzLmdldFBheWxvYWQocGF5bG9hZCk7XG4gICAgfVxuICAgIGlmIChwYXlsb2FkICYmICF0aGlzLmlzRXhwaXJlZChwYXlsb2FkKSkge1xuICAgICAgY29uc3Qgbm93ID0gdGhpcy51dGNTZWNvbmRzKCk7XG4gICAgICBjb25zdCBleHBpcnkgPSB0aGlzLnV0Y1NlY29uZHMocGF5bG9hZC5leHApO1xuICAgICAgY29uc3QgcmVmcmVzaCA9IE1hdGguZmxvb3IoKGV4cGlyeSAtIG5vdykgLyAxMDAwKTtcbiAgICAgIGNvbnN0IHJhbmRvbSA9IHRoaXMuZ2V0UmFuZG9tT2Zmc2V0KHBheWxvYWQpO1xuICAgICAgY29uc3QgdGltZSA9IG9mZnNldCA/IHJlZnJlc2ggKyByYW5kb20gOiByZWZyZXNoO1xuICAgICAgcmV0dXJuIHRpbWU7XG4gICAgfVxuICAgIHJldHVybiAwO1xuICB9XG5cbiAgLyoqXG4gICAqIENhbGN1bGF0ZXMgYSByYW5kb20gbnVtYmVyIHdoZXJlIG5ldHdvcmtEZWxheSA8IHJhbmRvbSA8IGxlZXdheVxuICAgKiBAcGFyYW0gcGF5bG9hZCBKV1QgcGF5bG9hZCBvYmplY3RcbiAgICogQHJldHVybnMgYSByYW5kb20gdG90YWwgbnVtYmVyIG9mIHNlY29uZHNcbiAgICovXG4gIHByaXZhdGUgZ2V0UmFuZG9tT2Zmc2V0KHBheWxvYWQ6IGFueSk6IG51bWJlciB7XG4gICAgaWYgKHR5cGVvZiBwYXlsb2FkID09PSAnc3RyaW5nJykge1xuICAgICAgcGF5bG9hZCA9IHRoaXMuZ2V0UGF5bG9hZChwYXlsb2FkKTtcbiAgICB9XG4gICAgY29uc3QgbGVld2F5ID0gcGF5bG9hZD8ubGVld2F5IHx8IHBheWxvYWQ/LmxlZSB8fCB0aGlzLm9wdGlvbnM/Lmp3dD8uZXhwaXJ5TGVld2F5O1xuICAgIGNvbnN0IHJhbmdlID0ge1xuICAgICAgbG93ZXI6IDEsXG4gICAgICB1cHBlcjogbGVld2F5IC0gdGhpcy5vcHRpb25zPy5qd3Q/Lm5ldHdvcmtEZWxheSB8fCAyLFxuICAgIH07XG4gICAgcmV0dXJuIE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIHJhbmdlLnVwcGVyICsgcmFuZ2UubG93ZXIpO1xuICB9XG5cbiAgLyoqXG4gICAqIENhbGN1bGF0ZXMgdGhlIFVUQyB2YWx1ZSBvZiBkYXRlL3RpbWUgaW4gc2Vjb25kc1xuICAgKiBAcGFyYW0gaW5wdXQgZGF0ZS90aW1lIGluIHNlY29uZHNcbiAgICogQHJldHVybnMgVVRDIHZhbHVlIG9mIGRhdGUvdGltZSBpbiBzZWNvbmRzXG4gICAqL1xuICBwcml2YXRlIHV0Y1NlY29uZHMoaW5wdXQ/OiBudW1iZXIpOiBudW1iZXIge1xuICAgIHJldHVybiBpbnB1dCA/IG5ldyBEYXRlKDApLnNldFVUQ1NlY29uZHMoaW5wdXQpLnZhbHVlT2YoKSA6IG5ldyBEYXRlKCkudmFsdWVPZigpO1xuICB9XG59XG4iXX0=