@auth0/auth0-spa-js
Version:
Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE
318 lines (317 loc) • 13.4 kB
JavaScript
(function(factory) {
typeof define === "function" && define.amd ? define(factory) : factory();
})(function() {
"use strict";
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: error, error_description: error_description} = _ref;
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 {clientId: client_id} = _a, params = __rest(_a, [ "clientId" ]);
return new URLSearchParams(stripUndefined(Object.assign({
client_id: client_id
}, params))).toString();
};
const fromEntries = iterable => [ ...iterable ].reduce((obj, _ref) => {
let [key, val] = _ref;
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 [key, token] = _ref;
if (cacheKeyContainsAudience(audience, key)) {
seen.add(token);
}
});
return Array.from(seen);
};
const deleteRefreshTokensByValue = refreshToken => {
Object.entries(refreshTokens).forEach(_ref2 => {
let [key, token] = _ref2;
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(_ref3 => {
let [key, token] = _ref3;
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 _ref4 => {
let {data: {timeout: timeout, auth: auth, fetchUrl: fetchUrl, fetchOptions: fetchOptions, useFormData: useFormData, useMrrt: useMrrt}, ports: [port]} = _ref4;
let headers = {};
let json;
let refreshToken;
const {audience: audience, scope: scope} = auth || {};
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 _ref5 => {
let {data: {timeout: timeout, auth: auth, fetchUrl: fetchUrl, fetchOptions: fetchOptions, useFormData: useFormData}, ports: [port]} = _ref5;
const {audience: audience} = auth || {};
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 {error_description: error_description} = JSON.parse(await response.text());
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: data, ports: ports} = event;
const [port] = ports;
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