UNPKG

@axway/amplify-sdk

Version:

Axway Amplify SDK for Node.js

245 lines (210 loc) 6.39 kB
import errors from '../errors.js'; import pluralize from 'pluralize'; import snooplogg from 'snooplogg'; /* eslint-disable no-unused-vars */ const { log } = snooplogg('amplify-sdk:auth:token-store'); const { highlight } = snooplogg.styles; /** * A regex to match a URL protocol. * @type {RegExp} */ const protoRegExp = /^.*\/\//; /** * Base class for token storage backends. */ class TokenStore { /** * Initializes the file store. * * @param {Object} [opts] - Various options. * @access public */ constructor(opts = {}) { if (!opts || typeof opts !== 'object') { throw errors.INVALID_ARGUMENT('Expected options to be an object'); } } /* istanbul ignore next */ /** * Removes all tokens. This method is intended to be overwritten. * * @param {String} [baseUrl] - The base URL used to filter accounts. * @returns {Promise<Array>} * @access public */ async clear(baseUrl) { return { entries: [], removed: [] }; } /** * Removes all tokens. * * @param {String} [baseUrl] - The base URL used to filter accounts. * @returns {Promise<Array>} * @access public */ async _clear(baseUrl) { const entries = await this.list(); if (!baseUrl) { for (const entry of entries) { Object.defineProperty(entry.auth, 'expired', { value: true }); } return { entries: [], removed: entries }; } const removed = []; baseUrl = baseUrl.replace(protoRegExp, ''); for (let i = 0; i < entries.length; i++) { if (entries[i].auth.baseUrl.replace(protoRegExp, '') === baseUrl) { const entry = entries.splice(i--, 1)[0]; Object.defineProperty(entry.auth, 'expired', { value: true }); removed.push(entry); } } return { entries, removed }; } /* istanbul ignore next */ /** * Deletes a token from the store. This method is intended to be overwritten. * * @param {String|Array.<String>} accounts - The account name(s) to delete. * @param {String} [baseUrl] - The base URL used to filter accounts. * @returns {Promise} * @access public */ async delete(accounts, baseUrl) { return []; } /** * Deletes a token from the store. * * @param {String|Array.<String>} accounts - The account name(s) to delete. * @param {String} [baseUrl] - The base URL used to filter accounts. * @returns {Promise} * @access public */ async _delete(accounts, baseUrl) { const entries = await this.list(); const removed = []; if (baseUrl) { baseUrl = baseUrl.replace(protoRegExp, ''); } if (!Array.isArray(accounts)) { accounts = [ accounts ]; } for (let i = 0; i < entries.length; i++) { if ((accounts.includes(entries[i].hash) || accounts.includes(entries[i].name)) && (!baseUrl || entries[i].auth.baseUrl.replace(protoRegExp, '') === baseUrl)) { const entry = entries.splice(i--, 1)[0]; Object.defineProperty(entry.auth, 'expired', { value: true }); removed.push(entry); } } return { entries, removed }; } /** * Retreives a token from the store. * * @param {Object} params - Various parameters. * @param {String} params.accountName - The account name to get. * @param {String} [params.baseUrl] - The base URL used to filter accounts. * @param {string} params.hash - The authenticator hash derived from the client id, base url, * realm, and authentication parameters. * @returns {Promise} Resolves the token or `undefined` if not set. * @access public */ async get({ accountName, baseUrl, hash } = {}) { const entries = await this.list(); if (baseUrl) { baseUrl = baseUrl.replace(protoRegExp, '').replace(/\/$/, ''); } if (!accountName && !hash) { throw errors.MISSING_REQUIRED_PARAMETER('Must specify either the account name or authenticator hash'); } const len = entries.length; log(`Scanning ${highlight(len)} ${pluralize('token', len)} for accountName=${highlight(accountName)} hash=${highlight(hash)} baseUrl=${highlight(baseUrl)}`); for (let i = 0; i < len; i++) { if (((accountName && entries[i].name === accountName) || (hash && entries[i].hash === hash)) && (!baseUrl || entries[i].auth.baseUrl.replace(protoRegExp, '').replace(/\/$/, '') === baseUrl)) { log(`Found account tokens: ${highlight(entries[i].name)}`); return entries[i]; } } log('Token not found'); return null; } /** * Retreives all tokens from the store. This method is intended to be overwritten. * * @returns {Promise<Array>} Resolves an array of tokens. * @access public */ async list() { return []; } /** * Ensures list of tokens is valid and does not contain any expired tokens. * * @param {Array.<Object>} entries - An array of tokens. * @returns {Array.<Object>} * @access private */ purge(entries) { if (!entries) { return []; } let count = 0; // loop over each entry and remove any expired tokens // NOTE: this code intentionally checkes `entries.length` each loop instead of caching the // length since splice() shrinks the array length for (let i = 0; i < entries.length; i++) { const expires = entries[i].auth?.expires; const now = Date.now(); if (expires && ((expires.access > now) || (expires.refresh && expires.refresh > now))) { // not expired if (!Object.getOwnPropertyDescriptor(entries[i].auth, 'expired')) { Object.defineProperty(entries[i].auth, 'expired', { configurable: true, get() { return this.expires.access < Date.now(); } }); } continue; } count++; entries.splice(i--, 1); } if (count) { log(`Purged ${highlight(count)} ${pluralize('entry', count)}`); } return entries; } /* istanbul ignore next */ /** * Saves account credentials. This method is intended to be overwritten. * * @param {Object} data - The token data. * @returns {Promise} * @access private */ async set(data) { // noop } /** * Saves account credentials. If exists, the old one is deleted. * * @param {Object} data - The token data. * @returns {Promise} * @access private */ async _set(data) { const entries = await this.list(); for (let i = 0, len = entries.length; i < len; i++) { if (entries[i].auth.baseUrl === data.auth.baseUrl && entries[i].name === data.name) { entries.splice(i, 1); break; } } entries.push(data); return entries; } } export { TokenStore as default }; //# sourceMappingURL=token-store.js.map