UNPKG

@salte-auth/salte-auth

Version:
1,456 lines (1,227 loc) 37.5 kB
import { v4 } from 'uuid'; import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2'; function AuthMixinGenerator(auth) { return function (Base) { return class extends Base { constructor() { super(...arguments); this.auth = void 0; this.auth = auth; this.auth.on('login', () => { if (this.requestUpdate) this.requestUpdate('auth'); }); this.auth.on('logout', () => { if (this.requestUpdate) this.requestUpdate('auth'); }); } }; }; } class SalteAuthError extends Error { constructor(_ref) { var { message, code } = _ref; super(message); this.code = void 0; this.code = code; } } class Required { constructor(config) { this.config = config || {}; } required() { for (var _len = arguments.length, keys = new Array(_len), _key = 0; _key < _len; _key++) { keys[_key] = arguments[_key]; } var missing = keys.filter(key => { return this.config[key] === undefined; }); if (missing.length > 0) { throw new SalteAuthError({ code: 'missing_required_properties', message: "Missing the following required fields. (".concat(missing.join(', '), ")") }); } } } class Fetch { static setup(force) { if (this.hasSetup && !force) return; this.hasSetup = true; this.interceptors = []; if (!this.real) this.real = window.fetch; if (window.fetch) { window.fetch = async function (input, options) { var request = input instanceof Request ? input : new Request(input, options); for (var i = 0; i < Fetch.interceptors.length; i++) { var interceptor = Fetch.interceptors[i]; await Promise.resolve(interceptor(request)); } return Fetch.real.call(this, request); }; } } static add(interceptor) { this.setup(); this.interceptors.push(interceptor); } } Fetch.real = void 0; Fetch.hasSetup = false; Fetch.interceptors = void 0; var setup = false; var callbacks = []; function onRouteChange() { callbacks.forEach(callback => callback()); } class Events$1 { static route(callback) { if (!setup) { window.addEventListener('popstate', onRouteChange, { passive: true }); window.addEventListener('click', onRouteChange, { passive: true }); setTimeout(onRouteChange); setup = true; } callbacks.push(callback); } static create(name, params) { var event = document.createEvent('Event'); event.initEvent(name, params.bubbles || false, params.cancelable || true); event.detail = params.detail; return event; } static isCrossDomainError(e) { return e instanceof DOMException || e.message === 'Permission denied'; } } class XHR { static setup(force) { if (this.hasSetup && !force) return; this.hasSetup = true; this.interceptors = []; if (!this.realOpen) this.realOpen = XMLHttpRequest.prototype.open; if (!this.realSend) this.realSend = XMLHttpRequest.prototype.send; var requestPrototype = XMLHttpRequest.prototype; requestPrototype.open = function () { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var [, url] = args; this.$url = url; return XHR.realOpen.apply(this, args); }; requestPrototype.send = function (data) { var promises = []; for (var i = 0; i < XHR.interceptors.length; i++) { var interceptor = XHR.interceptors[i]; promises.push(interceptor(this, data)); } Promise.all(promises).then(() => { XHR.realSend.call(this, data); }).catch(error => { this.dispatchEvent(Events$1.create('error', { detail: error })); }); }; } static add(interceptor) { this.setup(); this.interceptors.push(interceptor); } } XHR.realOpen = void 0; XHR.realSend = void 0; XHR.hasSetup = false; XHR.interceptors = void 0; var index$2 = /*#__PURE__*/Object.freeze({ __proto__: null, Fetch: Fetch, XHR: XHR }); var urlDocument; var base; var anchor; class URL { /** * Outputs a result equivalent to `location.origin` */ static get origin() { return "".concat(location.protocol, "//").concat(location.host); } static resolve(path) { if (!urlDocument) { urlDocument = document.implementation.createHTMLDocument('url'); base = urlDocument.createElement('base'); anchor = urlDocument.createElement('a'); urlDocument.head.appendChild(base); } base.href = window.location.protocol + '//' + window.location.host; anchor.href = path.replace(/ /g, '%20'); return anchor.href.replace(/\/$/, ''); } static match(url, tests) { if (tests instanceof Array) { var resolvedUrl = this.resolve(url); var match = Common.find(tests, test => { if (test instanceof RegExp) { return !!resolvedUrl.match(test); } return resolvedUrl.indexOf(this.resolve(test)) === 0; }); return !!match; } return tests === true; } static parse(_ref) { var { search, hash } = _ref; var params = []; if (search) params = params.concat(search.replace('?', '').split('&')); if (hash) params = params.concat(hash.replace('#', '').split('&')); var parsed = {}; Common.forEach(params, param => { var [key, value] = param.split('='); parsed[key] = value; }); return parsed; } /** * Creates a url with the given query parameters * @param base - the base url without query parameters * @param params - the query parameters to attache to the url * @returns the built url */ static url(base, params) { var url = base; Common.forEach(params, (value, key) => { if (Common.includes([undefined, null, ''], value)) return; url += "".concat(url.indexOf('?') === -1 ? '?' : '&').concat(key, "=").concat(encodeURIComponent(value)); }); return url; } } var debounces = {}; class Common { static includes(source, value) { return source.indexOf(value) !== -1; } static forEach(source, cb) { if (Array.isArray(source)) { for (var i = 0; i < source.length; i++) { cb(source[i], i); } } else { for (var _key in source) { cb(source[_key], _key); } } } static find(source, cb) { if (Array.isArray(source)) { for (var i = 0; i < source.length; i++) { var _item = source[i]; if (cb(_item, i)) { return _item; } } } else { for (var _key2 in source) { var _item2 = source[_key2]; if (cb(_item2, _key2)) { return _item2; } } } return null; } static assign(target) { for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key3 = 1; _key3 < _len; _key3++) { sources[_key3 - 1] = arguments[_key3]; } this.forEach(sources, source => { for (var _key4 in source) { target[_key4] = source[_key4]; } }); return target; } static defaults(target) { for (var _len2 = arguments.length, sources = new Array(_len2 > 1 ? _len2 - 1 : 0), _key5 = 1; _key5 < _len2; _key5++) { sources[_key5 - 1] = arguments[_key5]; } this.forEach(sources, source => { for (var _key6 in source) { if (this.isObject(target[_key6]) && this.isObject(source[_key6])) { target[_key6] = this.defaults(target[_key6], source[_key6]); } else if (target[_key6] === undefined) { target[_key6] = source[_key6]; } } }); return target; } static isObject(value) { return typeof value === 'object' && !Array.isArray(value); } static async iframe(_ref) { var { url, redirectUrl, visible } = _ref; var iframe = document.createElement('iframe'); iframe.setAttribute('owner', '@salte-auth/salte-auth'); if (visible) { this.assign(iframe.style, { border: 'none', bottom: 0, height: '100%', left: 0, position: 'fixed', right: 0, top: 0, width: '100%', zIndex: 9999, opacity: 0, transition: '0.5s opacity' }); setTimeout(() => iframe.style.opacity = '1'); } else { iframe.style.display = 'none'; } iframe.src = url; document.body.appendChild(iframe); return new Promise((resolve, reject) => { var checker = setInterval(() => { try { var { location } = iframe.contentWindow; // This could throw cross-domain errors, so we need to silence them. if (location.href.indexOf(redirectUrl) !== 0) return; var parsed = URL.parse(location); this.removeIframe(iframe); clearInterval(checker); resolve(parsed); } catch (error) { if (Events$1.isCrossDomainError(error)) return; this.removeIframe(iframe); clearInterval(checker); reject(error); } }); }); } static removeIframe(iframe) { /* istanbul ignore if */ if (!iframe.parentElement) return; iframe.parentElement.removeChild(iframe); } static debounce(identifier, callback, timeout) { clearTimeout(debounces[identifier]); debounces[identifier] = window.setTimeout(() => { delete debounces[identifier]; callback(); }, timeout); } } class Storage$1 { constructor(baseKey) { this.baseKey = void 0; this.baseKey = baseKey; } /** * Determines if the current browser supports this storage type. * @returns true if the storage type is supported */ static supported() { return true; } has(name) { return !Common.includes([undefined, null], this.get(name)); } /** * Returns a scoped key for storage. * @param key - The storage key. * * @example Storage.key('hello') // 'salte.auth.handler.redirect.hello' */ key() { var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; if (this.baseKey && key.indexOf(this.baseKey) === -1) { return "".concat(this.baseKey, ".").concat(key); } return key; } } class CookieStorage extends Storage$1 { /** * Determines if the current browser allows cookies. * @returns true if cookies aren't disabled. */ static supported() { return navigator.cookieEnabled === true; } get(name, defaultValue) { var match = document.cookie.match(new RegExp("".concat(this.key(name), "=([^;]+)"))); var result = match && match[1].trim(); if (!Common.includes([undefined, null], result)) return result;else if (!Common.includes([undefined, null], defaultValue)) return defaultValue; return null; } set(name, value) { if (Common.includes([undefined, null], value)) { this.delete(name); } else { document.cookie = "".concat(this.key(name), "=").concat(value, "; SameSite=Lax"); } } delete(name) { document.cookie = "".concat(this.key(name), "=; expires=").concat(new Date(0).toUTCString()); } clear() { var base = this.key(); var cookies = document.cookie.split(';'); Common.forEach(cookies, cookie => { var [name] = cookie.trim().split('='); if (name.indexOf(base) === 0) { this.delete(name); } }); } } class LocalStorage extends Storage$1 { get(name, defaultValue) { var result = localStorage.getItem(this.key(name)); if (!Common.includes([undefined, null], result)) return result;else if (!Common.includes([undefined, null], defaultValue)) return defaultValue; return null; } set(name, value) { if (Common.includes([undefined, null], value)) { this.delete(name); } else { localStorage.setItem(this.key(name), value); } } delete(name) { localStorage.removeItem(this.key(name)); } clear() { var base = this.key(); for (var key in localStorage) { if (key.indexOf(base) === 0) { this.delete(key); } } } } class SessionStorage extends Storage$1 { get(name, defaultValue) { var result = sessionStorage.getItem(this.key(name)); if (!Common.includes([undefined, null], result)) return result;else if (!Common.includes([undefined, null], defaultValue)) return defaultValue; return null; } set(name, value) { if (Common.includes([undefined, null], value)) { this.delete(name); } else { sessionStorage.setItem(this.key(name), value); } } delete(name) { sessionStorage.removeItem(this.key(name)); } clear() { var base = this.key(); for (var key in sessionStorage) { if (key.indexOf(base) === 0) { this.delete(key); } } } } var StorageTypes = { cookie: CookieStorage, local: LocalStorage, session: SessionStorage }; var index$1 = /*#__PURE__*/Object.freeze({ __proto__: null, StorageTypes: StorageTypes, CookieStorage: CookieStorage, LocalStorage: LocalStorage, SessionStorage: SessionStorage }); class AccessToken { constructor(accessToken, expiration, type) { this.raw = void 0; this.expiration = void 0; this.type = void 0; this.raw = accessToken; this.expiration = Common.includes([undefined, null], expiration) ? null : Number(expiration); this.type = type; } get expired() { return !this.raw || this.expiration <= Date.now(); } } class IDToken { constructor(idToken) { this.raw = void 0; this.user = void 0; this.raw = idToken; this.user = IDToken.parse(this.raw); } get expired() { return !this.user || this.user.exp * 1000 <= Date.now(); } static parse(idToken) { try { var separated = idToken.split('.'); if (separated.length !== 3) { throw new SalteAuthError({ code: 'invalid_id_token', message: "ID Token didn't match the desired format. ({header}.{payload}.{validation})" }); } // This fixes an issue where various providers will encode values // incorrectly and cause the browser to fail to decode. // https://stackoverflow.com/questions/43065553/base64-decoded-differently-in-java-jjwt return JSON.parse(atob(separated[1].replace(/-/g, '+').replace(/_/g, '/'))); } catch (error) { return null; } } } class Logger { constructor(name, level) { this.name = void 0; this.level = void 0; this.name = name; this.level = typeof level === 'string' ? this.toLevel(level) : level; } trace(message) { for (var _len = arguments.length, optionalParams = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { optionalParams[_key - 1] = arguments[_key]; } this.log('trace', message, ...optionalParams); } info(message) { for (var _len2 = arguments.length, optionalParams = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { optionalParams[_key2 - 1] = arguments[_key2]; } this.log('info', message, ...optionalParams); } warn(message) { for (var _len3 = arguments.length, optionalParams = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { optionalParams[_key3 - 1] = arguments[_key3]; } this.log('warn', message, ...optionalParams); } error(message) { for (var _len4 = arguments.length, optionalParams = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { optionalParams[_key4 - 1] = arguments[_key4]; } this.log('error', message, ...optionalParams); } log(level, message) { if (this.enabled(level)) { for (var _len5 = arguments.length, optionalParams = new Array(_len5 > 2 ? _len5 - 2 : 0), _key5 = 2; _key5 < _len5; _key5++) { optionalParams[_key5 - 2] = arguments[_key5]; } console.log("".concat(level, ": ").concat(message), ...optionalParams); } } enabled(level) { if (this.level === false) return false; return this.level === true || this.level <= this.toLevel(level); } toLevel(name) { return Common.find(Logger.levels, (_level, key) => key === name); } } Logger.levels = { trace: 0, info: 1, warn: 2, error: 3 }; class Dedupe { static dedupe() { var dedupes = {}; return (key, fn) => { if (!dedupes[key]) { dedupes[key] = fn().then(response => { delete dedupes[key]; return response; }).catch(error => { delete dedupes[key]; throw error; }); } return dedupes[key]; }; } } class GUID { static generate(prefix) { return [prefix, v4()].filter(Boolean).join('-'); } static state(prefix) { return [prefix, GUID.generate('state')].filter(Boolean).join('-'); } static nonce(prefix) { return [prefix, GUID.generate('nonce')].filter(Boolean).join('-'); } } var index = /*#__PURE__*/Object.freeze({ __proto__: null, Interceptors: index$2, StorageHelpers: index$1, AccessToken: AccessToken, IDToken: IDToken, Common: Common, Events: Events$1, URL: URL, Logger: Logger, Dedupe: Dedupe, GUID: GUID }); var defaultStorageOrder = ['cookie', 'session', 'local']; class Storage extends Required { constructor(config) { super(config); this.storage = void 0; this.config = Common.defaults(this.config, { storage: Common.find(defaultStorageOrder, storageType => StorageTypes[storageType].supported()) }); var Storage = StorageTypes[this.config.storage]; if (Storage) { this.storage = new Storage(this.key); } else { throw new SalteAuthError({ code: 'invalid_storage', message: "Storage doesn't exist for the given value. (".concat(this.config.storage, ")") }); } } get key() { return 'salte.auth'; } } class Events extends Storage { constructor() { super(...arguments); this.listeners = new Map(); } on(name, listener) { if (!this.listeners.has(name)) { this.listeners.set(name, []); } var listeners = this.listeners.get(name); listeners.push(listener); } off(name, listener) { if (!this.listeners.has(name)) return; var listeners = this.listeners.get(name); if (!listeners.length) return; var index = listeners.indexOf(listener); if (index === -1) return; listeners.splice(index, 1); } emit(name) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } if (!this.listeners.has(name)) return; var listeners = this.listeners.get(name); Common.forEach(listeners, listener => listener(...args)); } } class Shared extends Events { constructor(config) { super(config); this.config = Common.defaults(this.config, { redirectUrl: location.origin, level: 'warn' }); } /** * Returns a redirect url for the given login type. * @param type - Are we logging in or logging out? */ redirectUrl(type) { if (typeof this.config.redirectUrl === 'string') { return this.config.redirectUrl; } return this.config.redirectUrl[type]; } } class Provider extends Shared { constructor(config) { super(config); this.logger = void 0; this.url = URL.url; this.dedupe = Dedupe.dedupe(); this.config = Common.defaults(this.config, { validation: true, level: 'warn' }); this.logger = new Logger("@salte-auth/salte-auth:providers/".concat(this.$name), this.config.level); } /** * Determines if validation is enabled for the given key. * @param key - the key to determine whether validation is enabled for * @returns whether validation is enabled for the key. */ validation(key) { if (typeof this.config.validation === 'object') { return this.config.validation[key] === true; } return this.config.validation === true; } /** * The unique name of the provider */ get $name() { return this.config.name || this.name; } /** * Returns a scoped key for storage. * @param key - The storage key. * * @example auth0.key('hello') // 'salte.auth.provider.auth0.hello' */ get key() { return "salte.auth.provider.".concat(this.$name); } /** * Returns the logout url for the provider. */ get logout() { throw new SalteAuthError({ code: 'logout_not_supported', message: "This provider doesn't support logout." }); } } class OAuth2Provider extends Provider { constructor(config) { super(config); this.accessToken = void 0; this.sync(); } connected() { this.required('clientID', 'responseType'); } async secure(request) { if (this.config.responseType === 'token') { if (this.accessToken.expired) { return 'login'; } if (request) { if (request instanceof Request) { request.headers.set('Authorization', "Bearer ".concat(this.accessToken.raw)); } else if (request instanceof XMLHttpRequest) { request.setRequestHeader('Authorization', "Bearer ".concat(this.accessToken.raw)); } else { throw new SalteAuthError({ code: 'unknown_request', message: "Unknown request type. (".concat(request, ")") }); } } } return true; } $validate(options) { try { if (!options) { throw new SalteAuthError({ code: 'empty_response', message: "The response provided was empty, this is most likely due to the configured handler not providing it." }); } if (options.error) { throw new SalteAuthError({ code: options.error, message: "".concat(options.error_description ? options.error_description : options.error).concat(options.error_uri ? " (".concat(options.error_uri, ")") : '') }); } var { code: _code, access_token, state, expires_in, token_type } = options; if (this.validation('state') && this.storage.get('state') !== state) { throw new SalteAuthError({ code: 'invalid_state', message: 'State provided by identity provider did not match local state.' }); } var types = this.storage.get('response-type', '').split(' '); if (Common.includes(types, 'code')) { if (!_code) { throw new SalteAuthError({ code: 'invalid_code', message: 'Expected a code to be returned by the Provider.' }); } } else if (Common.includes(types, 'token')) { if (!access_token) { throw new SalteAuthError({ code: 'invalid_access_token', message: 'Expected an access token to be returned by the Provider.' }); } } if (_code) { this.storage.set('code.raw', _code); this.storage.delete('access-token.raw'); this.storage.delete('access-token.expiration'); this.storage.delete('access-token.type'); } else if (access_token) { this.storage.set('access-token.raw', access_token); this.storage.set('access-token.expiration', Date.now() + Number(expires_in) * 1000); this.storage.set('access-token.type', token_type); this.storage.delete('code.raw'); } } finally { this.storage.delete('state'); } } validate(options) { this.logger.trace('[validate] (options): ', options); try { this.$validate(options); } catch (error) { this.emit('login', error); throw error; } finally { this.sync(); } this.emit('login', null, this.code || this.accessToken); } get code() { return this.storage.get('code.raw'); } $login() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var state = GUID.state(this.$name); var responseType = options.responseType || this.config.responseType; this.storage.set('state', state); this.storage.set('response-type', responseType); return this.url(this.login, _objectSpread(_objectSpread({}, this.config.queryParams && this.config.queryParams('login')), {}, { client_id: this.config.clientID, response_type: responseType, redirect_uri: this.redirectUrl('login'), scope: this.config.scope, state })); } sync() { this.logger.trace('[sync] updating access token'); this.accessToken = new AccessToken(this.storage.get('access-token.raw'), this.storage.get('access-token.expiration'), this.storage.get('access-token.type')); } } class OpenIDProvider extends OAuth2Provider { constructor(config) { super(config); this.idToken = void 0; this.config.renewal = typeof this.config.renewal === 'object' ? this.config.renewal : { type: this.config.renewal }; this.config = Common.defaults(this.config, { responseType: 'id_token', scope: 'openid', renewal: { type: 'auto', buffer: 60000 } }); this.sync(); } async secure(request) { if (Common.includes(['id_token', 'id_token token', 'token'], this.config.responseType)) { if (this.idToken.expired) { this.logger.trace('[secure]: ID Token has expired, requesting login...'); return 'login'; } if (this.accessToken.expired) { await this.dedupe('access-token', async () => { this.logger.info("[secure]: Expired access token detected, retrieving..."); var parsed = await Common.iframe({ redirectUrl: this.redirectUrl('login'), url: this.$login({ prompt: 'none', responseType: 'token' }) }); this.logger.info("[secure]: Access token retrieved! Validating..."); this.validate(parsed); }); } if (request) { if (request instanceof Request) { request.headers.set('Authorization', "Bearer ".concat(this.accessToken.raw)); } else if (request instanceof XMLHttpRequest) { request.setRequestHeader('Authorization', "Bearer ".concat(this.accessToken.raw)); } else { throw new SalteAuthError({ code: 'unknown_request', message: "Unknown request type. (".concat(request, ")") }); } } } return true; } $validate(options) { try { super.$validate(options); var { id_token, code } = options; if (id_token) { var { id_token: _id_token } = options; var user = IDToken.parse(_id_token); if (!user) { throw new SalteAuthError({ code: 'invalid_id_token', message: 'Failed to parse user information due to invalid id token.' }); } if (this.validation('nonce') && this.storage.get('nonce') !== user.nonce) { throw new SalteAuthError({ code: 'invalid_nonce', message: 'Nonce provided by identity provider did not match the local nonce.' }); } this.storage.set('id-token.raw', _id_token); } else if (code) { this.storage.delete('id-token.raw'); } } finally { this.storage.delete('nonce'); } } validate(options) { this.logger.trace('[validate] (options): ', options); try { this.$validate(options); } catch (error) { this.emit('login', error); throw error; } finally { this.sync(); } var responseType = this.storage.get('response-type', ''); var types = responseType.split(' '); if (Common.includes(types, 'id_token')) { this.emit('login', null, this.idToken); } else if (Common.includes(types, 'token')) { this.emit('login', null, this.accessToken); } else if (Common.includes(types, 'code')) { this.emit('login', null, this.code); } else { throw new SalteAuthError({ code: 'invalid_response_type', message: "Unknown Response Type (".concat(responseType, ")") }); } } $login(options) { var nonce = GUID.nonce(this.$name); this.storage.set('nonce', nonce); return this.url(super.$login(options), { prompt: options && options.prompt, nonce }); } sync() { super.sync(); this.logger.trace('[sync] updating id token'); this.idToken = new IDToken(this.storage.get('id-token.raw')); } } class OAuth2 extends OAuth2Provider { constructor(config) { super(config); this.required('login'); } get name() { return 'generic.oauth2'; } get login() { return this.config.login.apply(this); } } class OpenID extends OpenIDProvider { constructor(config) { super(config); this.required('login', 'logout'); } get name() { return 'generic.openid'; } get login() { return this.config.login.apply(this); } get logout() { return this.config.logout.apply(this); } } var generic = /*#__PURE__*/Object.freeze({ __proto__: null, OAuth2: OAuth2, OpenID: OpenID }); class Handler extends Storage { constructor(config) { super(config); this.logger = void 0; this.config = Common.defaults(this.config, { navigate: 'reload', level: 'warn' }); this.logger = new Logger("@salte-auth/salte-auth:handlers/".concat(this.$name), this.config.level); } /** * The unique name of the handler */ get $name() { return this.config.name || this.name; } get key() { return "salte.auth.handler.".concat(this.$name); } /** * Navigates to the url provided. * @param url - the url to navigate to */ /* istanbul ignore next */ navigate(url) { if (this.config.navigate === 'history' && url.indexOf(URL.origin) === 0) { history.pushState('', document.title, url); } location.href = url; } } class SalteAuth extends Shared { constructor(config) { super(config); this.logger = void 0; this.mixin = void 0; this.required('providers', 'handlers'); this.config = Common.defaults(this.config, { validation: true, level: 'warn' }); this.logger = new Logger("@salte-auth/salte-auth:core", this.config.level); Common.forEach(this.config.providers, provider => { provider.connected && provider.connected(); provider.on('login', (error, data) => { this.emit('login', error, { provider: provider.$name, data: data }); }); provider.on('logout', error => { this.emit('logout', error, { provider: provider.$name }); }); }); var action = this.storage.get('action'); var provider = action ? this.provider(this.storage.get('provider')) : null; var handlerName = action ? this.storage.get('handler') : null; if (!Common.includes([undefined, null, 'login', 'logout'], action)) { throw new SalteAuthError({ code: 'unknown_action', message: "Unable to finish redirect due to an unknown action! (".concat(action, ")") }); } Common.forEach(this.config.handlers, handler => { if (!handler.connected) return; var responsible = handler.$name === handlerName; if (responsible) { provider.dedupe(action, async () => { this.logger.trace("[constructor]: wrapping up authentication for ".concat(handler.$name, "...")); await new Promise(resolve => setTimeout(resolve)); var parsed = handler.connected({ action }); if (action === 'login') { provider.validate(parsed); this.logger.info('[constructor]: login complete'); } else { provider.storage.clear(); provider.sync(); provider.emit('logout'); this.logger.info('[constructor]: logout complete'); } }); } else { handler.connected({ action: null }); } }); this.storage.delete('action'); this.storage.delete('provider'); this.storage.delete('handler'); Fetch.add(async request => { for (var i = 0; i < this.config.providers.length; i++) { var _provider = this.config.providers[i]; if (URL.match(request.url, _provider.config.endpoints)) { await this.$secure(_provider, request); } } }); XHR.add(async request => { for (var i = 0; i < this.config.providers.length; i++) { var _provider2 = this.config.providers[i]; if (URL.match(request.$url, _provider2.config.endpoints)) { await this.$secure(_provider2, request); } } }); Events$1.route(async () => { for (var i = 0; i < this.config.providers.length; i++) { var _provider3 = this.config.providers[i]; if (URL.match(location.href, _provider3.config.routes)) { await this.$secure(_provider3); } } }); this.mixin = AuthMixinGenerator(this); } /** * Login to the specified provider. * * @param options - the authentication options */ async login(options) { var normalizedOptions = typeof options === 'string' ? { provider: options } : options; var provider = this.provider(normalizedOptions.provider); return provider.dedupe('login', async () => { var handler = this.handler(normalizedOptions.handler); try { this.storage.set('action', 'login'); this.storage.set('provider', provider.$name); this.storage.set('handler', handler.$name); this.logger.info("[login]: logging in with ".concat(provider.$name, " via ").concat(handler.$name, "...")); var params = await handler.open({ redirectUrl: provider.redirectUrl('login'), url: provider.$login() }); this.logger.trace("[login]: validating response...", params); provider.validate(params); this.logger.info('[login]: login complete'); } finally { this.storage.delete('action'); this.storage.delete('provider'); this.storage.delete('handler'); } }); } /** * Logout of the specified provider. * * @param options - the authentication options */ async logout(options) { var normalizedOptions = typeof options === 'string' ? { provider: options } : options; var provider = this.provider(normalizedOptions.provider); return provider.dedupe('logout', async () => { try { var handler = this.handler(normalizedOptions.handler); this.storage.set('action', 'logout'); this.storage.set('provider', provider.$name); this.storage.set('handler', handler.$name); this.logger.info("[logout]: logging out with ".concat(provider.$name, " via ").concat(handler.$name, "...")); await handler.open({ redirectUrl: provider.redirectUrl('logout'), url: URL.url(provider.logout, provider.config.queryParams && provider.config.queryParams('logout')) }); provider.storage.clear(); provider.sync(); provider.emit('logout'); this.logger.info('[logout]: logout complete'); } catch (error) { provider.emit('logout', error); throw error; } finally { this.storage.delete('action'); this.storage.delete('provider'); this.storage.delete('handler'); } }); } /** * Returns a provider that matches the given name. * @param name - the name of the provider * @returns the provider with the given name. */ provider(name) { var provider = Common.find(this.config.providers, provider => provider.$name === name); if (!provider) { throw new SalteAuthError({ code: 'invalid_provider', message: "Unable to locate provider with the given name. (".concat(name, ")") }); } return provider; } /** * Returns a handler that matches the given name. * @param name - the name of the handler * @returns the handler with the given name, if no name is specified then the default handler. */ handler(name) { var handler = name === undefined ? Common.find(this.config.handlers, handler => Boolean(handler.config.default)) : Common.find(this.config.handlers, handler => handler.$name === name); if (!handler) { throw new SalteAuthError({ code: 'invalid_handler', message: "Unable to locate handler with the given name. (".concat(name, ")") }); } return handler; } async $secure(provider, request) { var handler = this.handler(); var response = null; while (response !== true) { response = await provider.secure(request); if (response === 'login') { if (!handler.auto) { throw new SalteAuthError({ code: 'auto_unsupported', message: "The default handler doesn't support automatic authentication! (".concat(handler.$name, ")") }); } await this.login({ provider: provider.$name, handler: handler.$name }); } } } } export { generic as Generic, Handler, OAuth2Provider, OpenIDProvider, Provider, SalteAuth, SalteAuthError, index as Utils };