@auth0/auth0-spa-js
Version:
Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE
360 lines (359 loc) • 15.9 kB
JavaScript
(function(factory) {
typeof define === "function" && define.amd ? define(factory) : factory();
})(function() {
"use strict";
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e, n, i, u, a = [], f = !0, o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (;!(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) ;
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
class GenericError extends Error {
constructor(error, error_description) {
super(error_description);
this.error = error;
this.error_description = error_description;
Object.setPrototypeOf(this, GenericError.prototype);
}
static fromPayload(_ref) {
let error = _ref.error, error_description = _ref.error_description;
return new GenericError(error, error_description);
}
}
class MissingRefreshTokenError extends GenericError {
constructor(audience, scope) {
super("missing_refresh_token", "Missing Refresh Token (audience: '".concat(valueOrEmptyString(audience, [ "default" ]), "', scope: '").concat(valueOrEmptyString(scope), "')"));
this.audience = audience;
this.scope = scope;
Object.setPrototypeOf(this, MissingRefreshTokenError.prototype);
}
}
function valueOrEmptyString(value) {
let exclude = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
return value && !exclude.includes(value) ? value : "";
}
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
}
typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const stripUndefined = params => Object.keys(params).filter(k => typeof params[k] !== "undefined").reduce((acc, key) => Object.assign(Object.assign({}, acc), {
[key]: params[key]
}), {});
const createQueryParams = _a => {
var client_id = _a.clientId, params = __rest(_a, [ "clientId" ]);
return new URLSearchParams(stripUndefined(Object.assign({
client_id: client_id
}, params))).toString();
};
const fromEntries = iterable => [ ...iterable ].reduce((obj, _ref) => {
let _ref2 = _slicedToArray(_ref, 2), key = _ref2[0], val = _ref2[1];
obj[key] = val;
return obj;
}, {});
let refreshTokens = {};
let allowedBaseUrl = null;
const cacheKey = (audience, scope) => "".concat(audience, "|").concat(scope);
const cacheKeyContainsAudience = (audience, cacheKey) => cacheKey.startsWith("".concat(audience, "|"));
const getRefreshToken = (audience, scope) => refreshTokens[cacheKey(audience, scope)];
const setRefreshToken = (refreshToken, audience, scope) => refreshTokens[cacheKey(audience, scope)] = refreshToken;
const deleteRefreshToken = (audience, scope) => delete refreshTokens[cacheKey(audience, scope)];
const getRefreshTokensByAudience = audience => {
const seen = new Set;
Object.entries(refreshTokens).forEach(_ref => {
let _ref2 = _slicedToArray(_ref, 2), key = _ref2[0], token = _ref2[1];
if (cacheKeyContainsAudience(audience, key)) {
seen.add(token);
}
});
return Array.from(seen);
};
const deleteRefreshTokensByValue = refreshToken => {
Object.entries(refreshTokens).forEach(_ref3 => {
let _ref4 = _slicedToArray(_ref3, 2), key = _ref4[0], token = _ref4[1];
if (token === refreshToken) {
delete refreshTokens[key];
}
});
};
const wait = time => new Promise(resolve => setTimeout(resolve, time));
const formDataToObject = formData => {
const queryParams = new URLSearchParams(formData);
const parsedQuery = {};
queryParams.forEach((val, key) => {
parsedQuery[key] = val;
});
return parsedQuery;
};
const updateRefreshTokens = (oldRefreshToken, newRefreshToken) => {
Object.entries(refreshTokens).forEach(_ref5 => {
let _ref6 = _slicedToArray(_ref5, 2), key = _ref6[0], token = _ref6[1];
if (token === oldRefreshToken) {
refreshTokens[key] = newRefreshToken;
}
});
};
const checkDownscoping = (scope, audience) => {
const findCoincidence = Object.keys(refreshTokens).find(key => {
if (key !== "latest_refresh_token") {
const isSameAudience = cacheKeyContainsAudience(audience, key);
const scopesKey = key.split("|")[1].split(" ");
const requestedScopes = scope.split(" ");
const scopesAreIncluded = requestedScopes.every(key => scopesKey.includes(key));
return isSameAudience && scopesAreIncluded;
}
});
return findCoincidence ? true : false;
};
const messageHandler = async _ref7 => {
let _ref7$data = _ref7.data, timeout = _ref7$data.timeout, auth = _ref7$data.auth, fetchUrl = _ref7$data.fetchUrl, fetchOptions = _ref7$data.fetchOptions, useFormData = _ref7$data.useFormData, useMrrt = _ref7$data.useMrrt, _ref7$ports = _slicedToArray(_ref7.ports, 1), port = _ref7$ports[0];
let headers = {};
let json;
let refreshToken;
const _ref8 = auth || {}, audience = _ref8.audience, scope = _ref8.scope;
try {
const body = useFormData ? formDataToObject(fetchOptions.body) : JSON.parse(fetchOptions.body);
if (!body.refresh_token && body.grant_type === "refresh_token") {
refreshToken = getRefreshToken(audience, scope);
if (!refreshToken && useMrrt) {
const latestRefreshToken = refreshTokens["latest_refresh_token"];
const isDownscoping = checkDownscoping(scope, audience);
if (latestRefreshToken && !isDownscoping) {
refreshToken = latestRefreshToken;
}
}
if (!refreshToken) {
throw new MissingRefreshTokenError(audience, scope);
}
fetchOptions.body = useFormData ? createQueryParams(Object.assign(Object.assign({}, body), {
refresh_token: refreshToken
})) : JSON.stringify(Object.assign(Object.assign({}, body), {
refresh_token: refreshToken
}));
}
let abortController;
if (typeof AbortController === "function") {
abortController = new AbortController;
fetchOptions.signal = abortController.signal;
}
let response;
try {
response = await Promise.race([ wait(timeout), fetch(fetchUrl, Object.assign({}, fetchOptions)) ]);
} catch (error) {
port.postMessage({
error: error.message
});
return;
}
if (!response) {
if (abortController) abortController.abort();
port.postMessage({
error: "Timeout when executing 'fetch'"
});
return;
}
headers = fromEntries(response.headers);
json = await response.json();
if (json.refresh_token) {
if (useMrrt) {
refreshTokens["latest_refresh_token"] = json.refresh_token;
updateRefreshTokens(refreshToken, json.refresh_token);
}
setRefreshToken(json.refresh_token, audience, scope);
delete json.refresh_token;
} else {
deleteRefreshToken(audience, scope);
}
port.postMessage({
ok: response.ok,
json: json,
headers: headers
});
} catch (error) {
port.postMessage({
ok: false,
json: {
error: error.error,
error_description: error.message
},
headers: headers
});
}
};
const revokeMessageHandler = async _ref9 => {
let _ref9$data = _ref9.data, timeout = _ref9$data.timeout, auth = _ref9$data.auth, fetchUrl = _ref9$data.fetchUrl, fetchOptions = _ref9$data.fetchOptions, useFormData = _ref9$data.useFormData, _ref9$ports = _slicedToArray(_ref9.ports, 1), port = _ref9$ports[0];
const _ref0 = auth || {}, audience = _ref0.audience;
try {
const tokensToRevoke = getRefreshTokensByAudience(audience);
if (tokensToRevoke.length === 0) {
port.postMessage({
ok: true
});
return;
}
const baseBody = useFormData ? formDataToObject(fetchOptions.body) : JSON.parse(fetchOptions.body);
for (const refreshToken of tokensToRevoke) {
const body = useFormData ? createQueryParams(Object.assign(Object.assign({}, baseBody), {
token: refreshToken
})) : JSON.stringify(Object.assign(Object.assign({}, baseBody), {
token: refreshToken
}));
let abortController;
let signal;
if (typeof AbortController === "function") {
abortController = new AbortController;
signal = abortController.signal;
}
let timeoutId;
let response;
try {
response = await Promise.race([ new Promise(resolve => {
timeoutId = setTimeout(resolve, timeout);
}), fetch(fetchUrl, Object.assign(Object.assign({}, fetchOptions), {
body: body,
signal: signal
})) ]).finally(() => clearTimeout(timeoutId));
} catch (error) {
port.postMessage({
error: error.message
});
return;
}
if (!response) {
if (abortController) abortController.abort();
port.postMessage({
error: "Timeout when executing 'fetch'"
});
return;
}
if (!response.ok) {
let errorDescription;
try {
const _JSON$parse = JSON.parse(await response.text()), error_description = _JSON$parse.error_description;
errorDescription = error_description;
} catch (_a) {}
port.postMessage({
error: errorDescription || "HTTP error ".concat(response.status)
});
return;
}
deleteRefreshTokensByValue(refreshToken);
}
port.postMessage({
ok: true
});
} catch (error) {
port.postMessage({
error: error.message || "Unknown error during token revocation"
});
}
};
const isAuthorizedWorkerRequest = (workerRequest, expectedPath) => {
if (!allowedBaseUrl) {
return false;
}
try {
const allowedBaseOrigin = new URL(allowedBaseUrl).origin;
const requestedUrl = new URL(workerRequest.fetchUrl);
return requestedUrl.origin === allowedBaseOrigin && requestedUrl.pathname === expectedPath;
} catch (_a) {
return false;
}
};
const messageRouter = event => {
const data = event.data, ports = event.ports;
const _ports = _slicedToArray(ports, 1), port = _ports[0];
if ("type" in data && data.type === "init") {
if (allowedBaseUrl === null) {
try {
new URL(data.allowedBaseUrl);
allowedBaseUrl = data.allowedBaseUrl;
} catch (_a) {
return;
}
}
return;
}
if ("type" in data && data.type === "clear") {
refreshTokens = {};
port === null || port === void 0 ? void 0 : port.postMessage({
ok: true
});
return;
}
if ("type" in data && data.type === "revoke") {
if (!isAuthorizedWorkerRequest(data, "/oauth/revoke")) {
port === null || port === void 0 ? void 0 : port.postMessage({
ok: false,
json: {
error: "invalid_fetch_url",
error_description: "Unauthorized fetch URL"
},
headers: {}
});
return;
}
revokeMessageHandler(event);
return;
}
if (!("fetchUrl" in data) || !isAuthorizedWorkerRequest(data, "/oauth/token")) {
port === null || port === void 0 ? void 0 : port.postMessage({
ok: false,
json: {
error: "invalid_fetch_url",
error_description: "Unauthorized fetch URL"
},
headers: {}
});
return;
}
messageHandler(event);
};
{
addEventListener("message", messageRouter);
}
});
//# sourceMappingURL=auth0-spa-js.worker.development.js.map