UNPKG

oidc-provider

Version:

OAuth 2.0 Authorization Server implementation for Node.js with OpenID Connect

142 lines (110 loc) 3.46 kB
/* eslint-disable max-classes-per-file */ import snakeCase from '../helpers/_/snake_case.js'; import epochTime from '../helpers/epoch_time.js'; import pickBy from '../helpers/_/pick_by.js'; import instance from '../helpers/weak_cache.js'; import isConstructable from '../helpers/type_validators.js'; import hasFormat from './mixins/has_format.js'; const IN_PAYLOAD = [ 'iat', 'exp', 'jti', 'kind', ]; const adapterCache = new WeakMap(); export default function getBaseModel(provider) { function adapter(ctx) { const obj = typeof ctx === 'function' ? ctx : ctx.constructor; if (!adapterCache.has(obj)) { if (isConstructable(instance(provider).Adapter)) { adapterCache.set(obj, new (instance(provider).Adapter)(obj.name)); } else { adapterCache.set(obj, instance(provider).Adapter(obj.name)); } } return adapterCache.get(obj); } class Class { constructor({ jti, kind, ...payload } = {}) { Object.assign(this, pickBy( payload, (val, key) => this.constructor.IN_PAYLOAD.includes(key), )); if (kind && kind !== this.constructor.name) { throw new TypeError('kind mismatch'); } this.kind = kind || this.constructor.name; this.jti = jti; } static instantiate(payload) { return new this(payload); } async save(ttl) { if (!this.jti) { this.jti = this.generateTokenId(); } // this is true for all BaseToken descendants if (typeof this.constructor.expiresIn !== 'function') { this.exp = epochTime() + ttl; } const { value, payload } = await this.getValueAndPayload(); if (payload) { await this.adapter.upsert(this.jti, payload, ttl); this.emit('saved'); } else { this.emit('issued'); } return value; } async destroy() { await this.adapter.destroy(this.jti); this.emit('destroyed'); } static get adapter() { return adapter(this); } get adapter() { return adapter(this); } static get IN_PAYLOAD() { return IN_PAYLOAD; } static async find(value, { ignoreExpiration = false } = {}) { if (typeof value !== 'string') { return undefined; } const stored = await this.adapter.find(value); if (!stored) { return undefined; } try { const payload = await this.verify(stored, { ignoreExpiration }); return this.instantiate(payload); } catch (err) { return undefined; } } emit(eventName) { provider.emit(`${snakeCase(this.kind)}.${eventName}`, this); } /* * ttlPercentagePassed * returns a Number (0 to 100) with the value being percentage of the token's ttl already * passed. The higher the percentage the older the token is. At 0 the token is fresh, at a 100 * it is expired. */ ttlPercentagePassed() { const now = epochTime(); const percentage = Math.floor(100 * ((now - this.iat) / (this.exp - this.iat))); return Math.max(Math.min(100, percentage), 0); } get isValid() { return !this.isExpired; } get isExpired() { return this.exp <= epochTime(); } get remainingTTL() { if (!this.exp) { return this.expiration; } return this.exp - epochTime(); } } class BaseModel extends hasFormat(provider, 'base', Class) {} return BaseModel; }