@salte-auth/salte-auth
Version:
Authentication for the modern web!
1,467 lines (1,236 loc) • 37.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var uuid = require('uuid');
var _objectSpread = require('@babel/runtime/helpers/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, uuid.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
});
}
}
}
}
exports.Generic = generic;
exports.Handler = Handler;
exports.OAuth2Provider = OAuth2Provider;
exports.OpenIDProvider = OpenIDProvider;
exports.Provider = Provider;
exports.SalteAuth = SalteAuth;
exports.SalteAuthError = SalteAuthError;
exports.Utils = index;