@nhost/hasura-auth-js
Version:
Hasura-auth client
1,445 lines • 110 kB
JavaScript
import { jwtDecode as Te } from "jwt-decode";
import { createMachine as N, send as w, assign as g, interpret as C } from "xstate";
import H from "js-cookie";
import pe from "fetch-ponyfill";
const x = "nhostRefreshToken", U = "nhostRefreshTokenId", O = "nhostRefreshTokenExpiresAt", we = 3, Q = 60, X = 5, ie = 0, oe = 1, v = 10, D = 20;
class b extends Error {
constructor(e) {
super(e.message), Error.captureStackTrace && Error.captureStackTrace(this, this.constructor), e instanceof Error ? (this.name = e.name, this.error = {
error: e.name,
status: oe,
message: e.message
}) : (this.name = e.error, this.error = e);
}
}
const R = {
status: v,
error: "invalid-email",
message: "Email is incorrectly formatted"
}, ke = {
status: v,
error: "invalid-mfa-type",
message: "MFA type is invalid"
}, ye = {
status: v,
error: "invalid-mfa-code",
message: "MFA code is invalid"
}, Y = {
status: v,
error: "invalid-password",
message: "Password is incorrectly formatted"
}, Z = {
status: v,
error: "invalid-phone-number",
message: "Phone number is incorrectly formatted"
}, Ie = {
status: v,
error: "invalid-mfa-ticket",
message: "MFA ticket is invalid"
}, Se = {
status: v,
error: "no-mfa-ticket",
message: "No MFA ticket has been provided"
}, Ae = {
status: v,
error: "no-refresh-token",
message: "No refresh token has been provided"
}, Re = {
status: D,
error: "refresher-already-running",
message: "The token refresher is already running. You must wait until is has finished before submitting a new token."
}, k = {
status: D,
error: "already-signed-in",
message: "User is already signed in"
}, _e = {
status: D,
error: "unauthenticated-user",
message: "User is not authenticated"
}, wr = {
status: D,
error: "user-not-anonymous",
message: "User is not anonymous"
}, ve = {
status: D,
error: "unverified-user",
message: "Email needs verification"
}, Pe = {
status: v,
error: "invalid-refresh-token",
message: "Invalid or expired refresh token"
}, Oe = {
status: oe,
error: "invalid-sign-in-method",
message: "Invalid sign-in method"
}, $ = {
user: null,
mfa: null,
accessToken: {
value: null,
expiresAt: null,
expiresInSeconds: 15
},
refreshTimer: {
startedAt: null,
attempts: 0,
lastAttempt: null
},
refreshToken: {
value: null
},
importTokenAttempts: 0,
errors: {}
};
function be(s) {
return new TextEncoder().encode(s);
}
function P(s) {
const e = new Uint8Array(s);
let r = "";
for (const t of e)
r += String.fromCharCode(t);
return btoa(r).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
function q(s) {
const e = s.replace(/-/g, "+").replace(/_/g, "/"), r = (4 - e.length % 4) % 4, n = e.padEnd(e.length + r, "="), t = atob(n), i = new ArrayBuffer(t.length), u = new Uint8Array(i);
for (let f = 0; f < t.length; f++)
u[f] = t.charCodeAt(f);
return i;
}
function ae() {
return (window == null ? void 0 : window.PublicKeyCredential) !== void 0 && typeof window.PublicKeyCredential == "function";
}
function ce(s) {
const { id: e } = s;
return {
...s,
id: q(e),
transports: s.transports
};
}
function ue(s) {
return s === "localhost" || /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(s);
}
class p extends Error {
constructor({ message: e, code: r, cause: n, name: t }) {
super(e, { cause: n }), this.name = t != null ? t : n.name, this.code = r;
}
}
function Ne({ error: s, options: e }) {
var n, t;
const { publicKey: r } = e;
if (!r)
throw Error("options was missing required publicKey property");
if (s.name === "AbortError") {
if (e.signal instanceof AbortSignal)
return new p({
message: "Registration ceremony was sent an abort signal",
code: "ERROR_CEREMONY_ABORTED",
cause: s
});
} else if (s.name === "ConstraintError") {
if (((n = r.authenticatorSelection) == null ? void 0 : n.requireResidentKey) === !0)
return new p({
message: "Discoverable credentials were required but no available authenticator supported it",
code: "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",
cause: s
});
if (((t = r.authenticatorSelection) == null ? void 0 : t.userVerification) === "required")
return new p({
message: "User verification was required but no available authenticator supported it",
code: "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",
cause: s
});
} else {
if (s.name === "InvalidStateError")
return new p({
message: "The authenticator was previously registered",
code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",
cause: s
});
if (s.name === "NotAllowedError")
return new p({
message: s.message,
code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",
cause: s
});
if (s.name === "NotSupportedError")
return r.pubKeyCredParams.filter((u) => u.type === "public-key").length === 0 ? new p({
message: 'No entry in pubKeyCredParams was of type "public-key"',
code: "ERROR_MALFORMED_PUBKEYCREDPARAMS",
cause: s
}) : new p({
message: "No available authenticator supported any of the specified pubKeyCredParams algorithms",
code: "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",
cause: s
});
if (s.name === "SecurityError") {
const i = window.location.hostname;
if (ue(i)) {
if (r.rp.id !== i)
return new p({
message: `The RP ID "${r.rp.id}" is invalid for this domain`,
code: "ERROR_INVALID_RP_ID",
cause: s
});
} else return new p({
message: `${window.location.hostname} is an invalid domain`,
code: "ERROR_INVALID_DOMAIN",
cause: s
});
} else if (s.name === "TypeError") {
if (r.user.id.byteLength < 1 || r.user.id.byteLength > 64)
return new p({
message: "User ID was not between 1 and 64 characters",
code: "ERROR_INVALID_USER_ID_LENGTH",
cause: s
});
} else if (s.name === "UnknownError")
return new p({
message: "The authenticator was unable to process the specified options, or could not create a new credential",
code: "ERROR_AUTHENTICATOR_GENERAL_ERROR",
cause: s
});
}
return s;
}
class xe {
createNewAbortSignal() {
if (this.controller) {
const r = new Error("Cancelling existing WebAuthn API call for new one");
r.name = "AbortError", this.controller.abort(r);
}
const e = new AbortController();
return this.controller = e, e.signal;
}
cancelCeremony() {
if (this.controller) {
const e = new Error("Manually cancelling existing WebAuthn API call");
e.name = "AbortError", this.controller.abort(e), this.controller = void 0;
}
}
}
const le = new xe(), Ce = ["cross-platform", "platform"];
function de(s) {
if (s && !(Ce.indexOf(s) < 0))
return s;
}
async function fe(s) {
var a;
if (!ae())
throw new Error("WebAuthn is not supported in this browser");
const r = { publicKey: {
...s,
challenge: q(s.challenge),
user: {
...s.user,
id: be(s.user.id)
},
excludeCredentials: (a = s.excludeCredentials) == null ? void 0 : a.map(ce)
} };
r.signal = le.createNewAbortSignal();
let n;
try {
n = await navigator.credentials.create(r);
} catch (o) {
throw Ne({ error: o, options: r });
}
if (!n)
throw new Error("Registration was not completed");
const { id: t, rawId: i, response: u, type: f } = n;
let d;
typeof u.getTransports == "function" && (d = u.getTransports());
let h;
if (typeof u.getPublicKeyAlgorithm == "function")
try {
h = u.getPublicKeyAlgorithm();
} catch (o) {
W("getPublicKeyAlgorithm()", o);
}
let E;
if (typeof u.getPublicKey == "function")
try {
const o = u.getPublicKey();
o !== null && (E = P(o));
} catch (o) {
W("getPublicKey()", o);
}
let T;
if (typeof u.getAuthenticatorData == "function")
try {
T = P(u.getAuthenticatorData());
} catch (o) {
W("getAuthenticatorData()", o);
}
return {
id: t,
rawId: P(i),
response: {
attestationObject: P(u.attestationObject),
clientDataJSON: P(u.clientDataJSON),
transports: d,
publicKeyAlgorithm: h,
publicKey: E,
authenticatorData: T
},
type: f,
clientExtensionResults: n.getClientExtensionResults(),
authenticatorAttachment: de(n.authenticatorAttachment)
};
}
function W(s, e) {
console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${s}. You should report this error to them.
`, e);
}
function De(s) {
return new TextDecoder("utf-8").decode(s);
}
function Ue() {
const s = window.PublicKeyCredential;
return s.isConditionalMediationAvailable === void 0 ? new Promise((e) => e(!1)) : s.isConditionalMediationAvailable();
}
function Me({ error: s, options: e }) {
const { publicKey: r } = e;
if (!r)
throw Error("options was missing required publicKey property");
if (s.name === "AbortError") {
if (e.signal instanceof AbortSignal)
return new p({
message: "Authentication ceremony was sent an abort signal",
code: "ERROR_CEREMONY_ABORTED",
cause: s
});
} else {
if (s.name === "NotAllowedError")
return new p({
message: s.message,
code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",
cause: s
});
if (s.name === "SecurityError") {
const n = window.location.hostname;
if (ue(n)) {
if (r.rpId !== n)
return new p({
message: `The RP ID "${r.rpId}" is invalid for this domain`,
code: "ERROR_INVALID_RP_ID",
cause: s
});
} else return new p({
message: `${window.location.hostname} is an invalid domain`,
code: "ERROR_INVALID_DOMAIN",
cause: s
});
} else if (s.name === "UnknownError")
return new p({
message: "The authenticator was unable to process the specified options, or could not create a new assertion signature",
code: "ERROR_AUTHENTICATOR_GENERAL_ERROR",
cause: s
});
}
return s;
}
async function j(s, e = !1) {
var T, a;
if (!ae())
throw new Error("WebAuthn is not supported in this browser");
let r;
((T = s.allowCredentials) == null ? void 0 : T.length) !== 0 && (r = (a = s.allowCredentials) == null ? void 0 : a.map(ce));
const n = {
...s,
challenge: q(s.challenge),
allowCredentials: r
}, t = {};
if (e) {
if (!await Ue())
throw Error("Browser does not support WebAuthn autofill");
if (document.querySelectorAll("input[autocomplete$='webauthn']").length < 1)
throw Error('No <input> with "webauthn" as the only or last value in its `autocomplete` attribute was detected');
t.mediation = "conditional", n.allowCredentials = [];
}
t.publicKey = n, t.signal = le.createNewAbortSignal();
let i;
try {
i = await navigator.credentials.get(t);
} catch (o) {
throw Me({ error: o, options: t });
}
if (!i)
throw new Error("Authentication was not completed");
const { id: u, rawId: f, response: d, type: h } = i;
let E;
return d.userHandle && (E = De(d.userHandle)), {
id: u,
rawId: P(f),
response: {
authenticatorData: P(d.authenticatorData),
clientDataJSON: P(d.clientDataJSON),
signature: P(d.signature),
userHandle: E
},
type: h,
clientExtensionResults: i.getClientExtensionResults(),
authenticatorAttachment: de(i.authenticatorAttachment)
};
}
const G = typeof window != "undefined", V = /* @__PURE__ */ new Map(), Ke = (s) => {
var e;
return G && typeof localStorage != "undefined" ? localStorage.getItem(s) : (e = V.get(s)) != null ? e : null;
}, Ve = (s, e) => {
G && typeof localStorage != "undefined" ? e ? localStorage.setItem(s, e) : localStorage.removeItem(s) : e ? V.set(s, e) : V.has(s) && V.delete(s);
}, Le = (s, e) => {
if (s === "localStorage" || s === "web")
return Ke;
if (s === "cookie")
return (r) => {
var n;
return G && (n = H.get(r)) != null ? n : null;
};
if (!e)
throw Error(
`clientStorageType is set to '${s}' but no clientStorage has been given`
);
if (s === "react-native")
return (r) => {
var n;
return (n = e.getItem) == null ? void 0 : n.call(e, r);
};
if (s === "capacitor")
return (r) => {
var n;
return (n = e.get) == null ? void 0 : n.call(e, { key: r });
};
if (s === "expo-secure-storage")
return (r) => {
var n;
return (n = e.getItemAsync) == null ? void 0 : n.call(e, r);
};
if (s === "custom") {
if (e.getItem && e.removeItem)
return e.getItem;
if (e.getItemAsync)
return e.getItemAsync;
throw Error(
`clientStorageType is set to 'custom' but clientStorage is missing either "getItem" and "removeItem" properties or "getItemAsync" property`
);
}
throw Error(`Unknown storage type: ${s}`);
}, Ge = (s, e) => {
if (s === "localStorage" || s === "web")
return Ve;
if (s === "cookie")
return (r, n) => {
G && (n ? H.set(r, n, { expires: 30, sameSite: "lax", httpOnly: !1 }) : H.remove(r));
};
if (!e)
throw Error(
`clientStorageType is set to '${s}' but no clienStorage has been given`
);
if (s === "react-native")
return (r, n) => {
var t, i;
return n ? (t = e.setItem) == null ? void 0 : t.call(e, r, n) : (i = e.removeItem) == null ? void 0 : i.call(e, r);
};
if (s === "capacitor")
return (r, n) => {
var t, i;
return n ? (t = e.set) == null ? void 0 : t.call(e, { key: r, value: n }) : (i = e.remove) == null ? void 0 : i.call(e, { key: r });
};
if (s === "expo-secure-storage")
return async (r, n) => {
var t, i;
return n ? (t = e.setItemAsync) == null ? void 0 : t.call(e, r, n) : (i = e.deleteItemAsync) == null ? void 0 : i.call(e, r);
};
if (s === "custom") {
if (!e.removeItem)
throw Error(
"clientStorageType is set to 'custom' but clientStorage is missing a removeItem property"
);
if (e.setItem)
return (r, n) => {
var t, i;
return n ? (t = e.setItem) == null ? void 0 : t.call(e, r, n) : (i = e.removeItem) == null ? void 0 : i.call(e, r);
};
if (e.setItemAsync)
return async (r, n) => {
var t, i;
return n ? (t = e.setItemAsync) == null ? void 0 : t.call(e, r, n) : (i = e.removeItem) == null ? void 0 : i.call(e, r);
};
throw Error(
"clientStorageType is set to 'custom' but clientStorage is missing setItem or setItemAsync property"
);
}
throw Error(`Unknown storage type: ${s}`);
}, M = (s) => !s || !s.accessToken.value || !s.accessToken.expiresAt || !s.user ? null : {
accessToken: s.accessToken.value,
accessTokenExpiresIn: (s.accessToken.expiresAt.getTime() - Date.now()) / 1e3,
refreshToken: s.refreshToken.value,
user: s.user
}, A = ({
accessToken: s,
refreshToken: e,
isError: r,
user: n,
error: t
}) => r ? {
session: null,
error: t
} : n && s ? {
// TODO either return the refresh token or remove it from the session type
session: { accessToken: s, accessTokenExpiresIn: 0, refreshToken: e, user: n },
error: null
} : { session: null, error: null }, L = () => typeof window != "undefined" && typeof window.location != "undefined";
let he = globalThis.fetch;
typeof EdgeRuntime != "string" && (he = pe().fetch);
const me = async (s, e, {
token: r,
body: n,
extraHeaders: t
} = {}) => {
const i = {
"Content-Type": "application/json",
Accept: "*/*"
};
r && (i.Authorization = `Bearer ${r}`);
const u = { ...i, ...t }, f = {
method: e,
headers: u
};
n && (f.body = JSON.stringify(n));
try {
const d = await he(s, f);
if (!d.ok) {
const h = await d.json();
return Promise.reject({ error: h });
}
try {
return { data: await d.json(), error: null };
} catch {
return console.warn(`Unexpected response: can't parse the response of the server at ${s}`), { data: "OK", error: null };
}
} catch {
const h = {
message: "Network Error",
status: ie,
error: "network"
};
return Promise.reject({ error: h });
}
}, I = async (s, e, r, n) => me(s, "POST", { token: r, body: e, extraHeaders: n }), $e = (s, e) => me(s, "GET", { token: e }), F = (s, e) => {
const r = e && Object.entries(e).map(([n, t]) => {
const i = Array.isArray(t) ? t.join(",") : typeof t == "object" ? JSON.stringify(t) : t;
return `${n}=${encodeURIComponent(i)}`;
}).join("&");
return r ? `${s}?${r}` : s;
}, S = (s, e) => {
if (!(e != null && e.redirectTo))
return e;
const { redirectTo: r, ...n } = e;
if (!s)
return r.startsWith("/") ? n : e;
const t = new URL(s), i = Object.fromEntries(new URLSearchParams(t.search)), u = new URL(r.startsWith("/") ? t.origin + r : r), f = new URLSearchParams(u.search);
let d = Object.fromEntries(f);
r.startsWith("/") && (d = { ...i, ...d });
let h = t.pathname;
return u.pathname.length > 1 && (h += u.pathname.slice(1)), {
...n,
redirectTo: F(u.origin + h, d)
};
};
function K(s, e) {
var t;
if (!e) {
if (typeof window == "undefined")
return;
e = ((t = window.location) == null ? void 0 : t.href) || "";
}
s = s.replace(/[\[\]]/g, "\\$&");
const r = new RegExp("[?&#]" + s + "(=([^&#]*)|&|#|$)"), n = r.exec(e);
return n ? n[2] ? decodeURIComponent(n[2].replace(/\+/g, " ")) : "" : null;
}
function J(s) {
var r;
if (typeof window == "undefined")
return;
const e = window == null ? void 0 : window.location;
if (e && e) {
const n = new URLSearchParams(e.search), t = new URLSearchParams((r = e.hash) == null ? void 0 : r.slice(1));
n.delete(s), t.delete(s);
let i = window.location.pathname;
Array.from(n).length && (i += `?${n.toString()}`), Array.from(t).length && (i += `#${t.toString()}`), window.history.pushState({}, "", i);
}
}
const _ = (s) => !!s && typeof s == "string" && !!String(s).toLowerCase().match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
), B = (s) => !!s && typeof s == "string" && s.length >= we, ee = (s) => !!s && typeof s == "string", We = (s) => s && typeof s == "string" && s.match(/^mfaTotp:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i), He = ({
backendUrl: s,
clientUrl: e,
broadcastKey: r,
clientStorageType: n = "web",
clientStorage: t,
refreshIntervalTime: i,
autoRefreshToken: u = !0,
autoSignIn: f = !0
}) => {
const d = Le(n, t), h = Ge(n, t), E = async (a, o, c, l) => (await I(`${s}${a}`, o, c, l)).data;
let T = null;
if (typeof window != "undefined" && r)
try {
T = new BroadcastChannel(r);
} catch {
}
return N(
{
schema: {
context: {},
events: {},
services: {}
},
tsTypes: {},
context: $,
predictableActionArguments: !0,
id: "nhost",
type: "parallel",
states: {
authentication: {
initial: "starting",
on: {
SESSION_UPDATE: [
{
cond: "hasSession",
actions: ["saveSession", "resetTimer", "reportTokenChanged"],
target: ".signedIn"
}
]
},
states: {
starting: {
tags: ["loading"],
always: { cond: "isSignedIn", target: "signedIn" },
invoke: {
id: "importRefreshToken",
src: "importRefreshToken",
onDone: [
{
cond: "hasSession",
actions: ["saveSession", "reportTokenChanged"],
target: "signedIn"
},
{
target: "signedOut"
}
],
onError: [
{
cond: "shouldRetryImportToken",
actions: "incrementTokenImportAttempts",
target: "retryTokenImport"
},
{ actions: ["saveAuthenticationError"], target: "signedOut" }
]
}
},
retryTokenImport: {
tags: ["loading"],
after: {
RETRY_IMPORT_TOKEN_DELAY: "starting"
}
},
signedOut: {
initial: "noErrors",
entry: "reportSignedOut",
states: {
noErrors: {},
success: {},
needsSmsOtp: {},
needsMfa: {},
failed: {},
signingOut: {
entry: ["clearContextExceptTokens"],
exit: ["destroyAccessToken", "destroyRefreshToken", "reportTokenChanged"],
invoke: {
src: "signout",
id: "signingOut",
onDone: {
target: "success"
},
onError: {
target: "failed",
actions: ["saveAuthenticationError"]
}
}
}
},
on: {
SIGNIN_PASSWORD: "authenticating.password",
SIGNIN_ANONYMOUS: "authenticating.anonymous",
SIGNIN_SECURITY_KEY_EMAIL: "authenticating.securityKeyEmail",
SIGNIN_SECURITY_KEY: "authenticating.securityKey",
SIGNIN_MFA_TOTP: "authenticating.mfa.totp",
SIGNIN_PAT: "authenticating.pat",
SIGNIN_ID_TOKEN: "authenticating.idToken"
}
},
authenticating: {
entry: "resetErrors",
states: {
password: {
invoke: {
src: "signInPassword",
id: "authenticateUserWithPassword",
onDone: [
{
cond: "hasMfaTicket",
actions: ["saveMfaTicket"],
target: "#nhost.authentication.signedOut.needsMfa"
},
{
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
}
],
onError: [
{
cond: "unverified",
target: [
"#nhost.authentication.signedOut",
"#nhost.registration.incomplete.needsEmailVerification"
]
},
{
actions: "saveAuthenticationError",
target: "#nhost.authentication.signedOut.failed"
}
]
}
},
pat: {
invoke: {
src: "signInPAT",
id: "authenticateWithPAT",
onDone: {
actions: ["savePATSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
onError: {
actions: "saveAuthenticationError",
target: "#nhost.authentication.signedOut.failed"
}
}
},
idToken: {
invoke: {
src: "signInIdToken",
id: "authenticateWithIdToken",
onDone: {
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
onError: {
actions: "saveAuthenticationError",
target: "#nhost.authentication.signedOut.failed"
}
}
},
anonymous: {
invoke: {
src: "signInAnonymous",
id: "authenticateAnonymously",
onDone: {
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
onError: {
actions: "saveAuthenticationError",
target: "#nhost.authentication.signedOut.failed"
}
}
},
mfa: {
states: {
totp: {
invoke: {
src: "signInMfaTotp",
id: "signInMfaTotp",
onDone: {
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
onError: {
actions: ["saveAuthenticationError"],
target: "#nhost.authentication.signedOut.failed"
}
}
}
}
},
securityKeyEmail: {
invoke: {
src: "signInSecurityKeyEmail",
id: "authenticateUserWithSecurityKey",
onDone: {
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
onError: [
{
cond: "unverified",
target: [
"#nhost.authentication.signedOut",
"#nhost.registration.incomplete.needsEmailVerification"
]
},
{
actions: "saveAuthenticationError",
target: "#nhost.authentication.signedOut.failed"
}
]
}
},
securityKey: {
invoke: {
src: "signInSecurityKey",
id: "authenticateUserWithSecurityKey",
onDone: {
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
onError: [
{
cond: "unverified",
target: [
"#nhost.authentication.signedOut",
"#nhost.registration.incomplete.needsEmailVerification"
]
},
{
actions: "saveAuthenticationError",
target: "#nhost.authentication.signedOut.failed"
}
]
}
}
}
},
signedIn: {
type: "parallel",
entry: ["reportSignedIn", "cleanUrl", "broadcastToken", "resetErrors"],
on: {
SIGNOUT: "signedOut.signingOut"
},
states: {
refreshTimer: {
id: "timer",
initial: "idle",
states: {
disabled: { type: "final" },
stopped: {
always: {
cond: "noToken",
target: "idle"
}
},
idle: {
always: [
{ cond: "isAutoRefreshDisabled", target: "disabled" },
{ cond: "isRefreshTokenPAT", target: "disabled" },
{ cond: "hasRefreshToken", target: "running" }
]
},
running: {
initial: "pending",
entry: "resetTimer",
states: {
pending: {
after: {
1e3: {
internal: !1,
target: "pending"
}
},
always: {
cond: "refreshTimerShouldRefresh",
target: "refreshing"
}
},
refreshing: {
invoke: {
src: "refreshToken",
id: "refreshToken",
onDone: {
actions: [
"saveSession",
"resetTimer",
"reportTokenChanged",
"broadcastToken"
],
target: "pending"
},
onError: [
{
cond: "isUnauthorizedError",
target: "#nhost.authentication.signedOut"
},
{ actions: "saveRefreshAttempt", target: "pending" }
]
}
}
}
}
}
}
}
}
}
},
token: {
initial: "idle",
states: {
idle: {
on: {
TRY_TOKEN: "running"
},
initial: "noErrors",
states: { noErrors: {}, error: {} }
},
running: {
invoke: {
src: "refreshToken",
id: "authenticateWithToken",
onDone: {
actions: ["saveSession", "reportTokenChanged", "broadcastToken"],
target: ["#nhost.authentication.signedIn", "idle.noErrors"]
},
onError: [
{ cond: "isSignedIn", target: "idle.error" },
{
actions: "saveAuthenticationError",
target: ["#nhost.authentication.signedOut.failed", "idle.error"]
}
]
}
}
}
},
registration: {
initial: "incomplete",
on: {
SIGNED_IN: [{ cond: "isAnonymous", target: ".incomplete" }, ".complete"]
},
states: {
incomplete: {
on: {
SIGNUP_EMAIL_PASSWORD: "emailPassword",
SIGNUP_SECURITY_KEY: "securityKey",
PASSWORDLESS_EMAIL: "passwordlessEmail",
PASSWORDLESS_SMS: "passwordlessSms",
PASSWORDLESS_SMS_OTP: "passwordlessSmsOtp",
SIGNIN_EMAIL_OTP: "signInEmailOTP",
VERIFY_EMAIL_OTP: "verifyEmailOTP"
},
initial: "noErrors",
states: {
noErrors: {},
needsEmailVerification: {},
needsOtp: {},
failed: {}
}
},
emailPassword: {
entry: ["resetErrors"],
invoke: {
src: "signUpEmailPassword",
id: "signUpEmailPassword",
onDone: [
{
cond: "hasSession",
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
{
actions: "clearContext",
target: ["#nhost.authentication.signedOut", "incomplete.needsEmailVerification"]
}
],
onError: [
{
cond: "unverified",
target: "incomplete.needsEmailVerification"
},
{
actions: "saveRegistrationError",
target: "incomplete.failed"
}
]
}
},
securityKey: {
entry: ["resetErrors"],
invoke: {
src: "signUpSecurityKey",
id: "signUpSecurityKey",
onDone: [
{
cond: "hasSession",
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
{
actions: "clearContext",
target: ["#nhost.authentication.signedOut", "incomplete.needsEmailVerification"]
}
],
onError: [
{
cond: "unverified",
target: "incomplete.needsEmailVerification"
},
{
actions: "saveRegistrationError",
target: "incomplete.failed"
}
]
}
},
passwordlessEmail: {
entry: ["resetErrors"],
invoke: {
src: "passwordlessEmail",
id: "passwordlessEmail",
onDone: {
actions: "clearContext",
target: ["#nhost.authentication.signedOut", "incomplete.needsEmailVerification"]
},
onError: {
actions: "saveRegistrationError",
target: "incomplete.failed"
}
}
},
passwordlessSms: {
entry: ["resetErrors"],
invoke: {
src: "passwordlessSms",
id: "passwordlessSms",
onDone: {
actions: "clearContext",
target: ["#nhost.authentication.signedOut", "incomplete.needsOtp"]
},
onError: {
actions: "saveRegistrationError",
target: "incomplete.failed"
}
}
},
passwordlessSmsOtp: {
entry: ["resetErrors"],
invoke: {
src: "passwordlessSmsOtp",
id: "passwordlessSmsOtp",
onDone: {
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
onError: {
actions: "saveRegistrationError",
target: "incomplete.failed"
}
}
},
signInEmailOTP: {
entry: ["resetErrors"],
invoke: {
src: "signInEmailOTP",
id: "signInEmailOTP",
onDone: {
actions: "clearContext",
target: ["#nhost.authentication.signedOut", "incomplete.needsOtp"]
},
onError: {
actions: "saveRegistrationError",
target: "incomplete.failed"
}
}
},
verifyEmailOTP: {
entry: ["resetErrors"],
invoke: {
src: "verifyEmailOTP",
id: "verifyEmailOTP",
onDone: {
actions: ["saveSession", "reportTokenChanged"],
target: "#nhost.authentication.signedIn"
},
onError: {
actions: "saveRegistrationError",
target: "incomplete.failed"
}
}
},
complete: {
on: {
SIGNED_OUT: "incomplete"
}
}
}
}
}
},
{
actions: {
reportSignedIn: w("SIGNED_IN"),
reportSignedOut: w("SIGNED_OUT"),
reportTokenChanged: w("TOKEN_CHANGED"),
incrementTokenImportAttempts: g({
importTokenAttempts: ({ importTokenAttempts: a }) => a + 1
}),
clearContext: g(() => (h(O, null), h(x, null), h(U, null), {
...$
})),
clearContextExceptTokens: g(({ accessToken: a, refreshToken: o }) => ({
...$,
accessToken: a,
refreshToken: o
})),
// * Save session in the context, and persist the refresh token and the jwt expiration outside of the machine
saveSession: g({
user: (a, { data: o }) => {
var c;
return ((c = o == null ? void 0 : o.session) == null ? void 0 : c.user) || null;
},
accessToken: (a, { data: o }) => {
if (o.session) {
const { accessTokenExpiresIn: c, accessToken: l } = o.session, m = new Date(Date.now() + c * 1e3);
return h(O, m.toISOString()), {
value: l,
expiresAt: m,
expiresInSeconds: c
};
}
return h(O, null), {
value: null,
expiresAt: null,
expiresInSeconds: null
};
},
refreshToken: (a, { data: o }) => {
var m, y;
const c = ((m = o.session) == null ? void 0 : m.refreshToken) || null, l = ((y = o.session) == null ? void 0 : y.refreshTokenId) || null;
return c && h(x, c), l && h(U, l), { value: c };
}
}),
savePATSession: g({
user: (a, { data: o }) => {
var c;
return ((c = o == null ? void 0 : o.session) == null ? void 0 : c.user) || null;
},
accessToken: (a, { data: o }) => {
if (o.session) {
const { accessTokenExpiresIn: c, accessToken: l } = o.session, m = new Date(Date.now() + c * 1e3);
return h(O, m.toISOString()), {
value: l,
expiresAt: m,
expiresInSeconds: c
};
}
return h(O, null), {
value: null,
expiresAt: null,
expiresInSeconds: null
};
},
refreshToken: (a, { data: o }) => {
var m, y;
const c = ((m = o.session) == null ? void 0 : m.refreshToken) || null, l = ((y = o.session) == null ? void 0 : y.refreshTokenId) || null;
return c && h(x, c), l && h(U, l), { value: c, isPAT: !0 };
}
}),
saveMfaTicket: g({
mfa: (a, o) => {
var c;
return (c = o.data) == null ? void 0 : c.mfa;
}
}),
resetTimer: g({
refreshTimer: (a) => ({
startedAt: /* @__PURE__ */ new Date(),
attempts: 0,
lastAttempt: null
})
}),
saveRefreshAttempt: g({
refreshTimer: (a, o) => ({
startedAt: a.refreshTimer.startedAt,
attempts: a.refreshTimer.attempts + 1,
lastAttempt: /* @__PURE__ */ new Date()
})
}),
// * Authentication errors
saveAuthenticationError: g({
// * Untyped action payload. See https://github.com/statelyai/xstate/issues/3037
errors: ({ errors: a }, { data: { error: o } }) => ({
...a,
authentication: o
})
}),
resetErrors: g({
errors: (a) => ({}),
importTokenAttempts: (a) => 0
}),
saveRegistrationError: g({
// * Untyped action payload. See https://github.com/statelyai/xstate/issues/3037
errors: ({ errors: a }, { data: { error: o } }) => ({ ...a, registration: o })
}),
destroyRefreshToken: g({
refreshToken: (a) => (h(x, null), h(U, null), { value: null })
}),
destroyAccessToken: g({
accessToken: (a) => (h(O, null), {
value: null,
expiresAt: null,
expiresInSeconds: null
})
}),
// * Clean the browser url when `autoSignIn` is activated
cleanUrl: () => {
f && K("refreshToken") && (J("refreshToken"), J("type"));
},
// * Broadcast the session to other tabs when `autoSignIn` is activated
broadcastToken: (a) => {
if (f && r && T)
try {
T.postMessage({
type: "broadcast_session",
payload: {
token: a.refreshToken.value,
accessToken: a.accessToken.value,
user: a.user,
expiresAt: a.accessToken.expiresAt ? a.accessToken.expiresAt.toISOString() : null,
expiresInSeconds: a.accessToken.expiresInSeconds
}
});
} catch {
}
}
},
guards: {
isAnonymous: (a, o) => {
var c;
return !!((c = a.user) != null && c.isAnonymous);
},
isSignedIn: (a) => !!a.user && !!a.accessToken.value,
noToken: (a) => !a.refreshToken.value,
isRefreshTokenPAT: (a) => {
var o;
return !!((o = a.refreshToken) != null && o.isPAT);
},
hasRefreshToken: (a) => !!a.refreshToken.value,
isAutoRefreshDisabled: () => !u,
refreshTimerShouldRefresh: (a) => {
const { expiresAt: o } = a.accessToken;
if (!o)
return !1;
if (a.refreshTimer.lastAttempt)
return a.refreshTimer.attempts > X ? !1 : Date.now() - a.refreshTimer.lastAttempt.getTime() > Math.pow(2, a.refreshTimer.attempts - 1) * 5e3;
if (o.getTime() < Date.now() || i && Date.now() - a.refreshTimer.startedAt.getTime() > i * 1e3)
return !0;
if (!a.accessToken.expiresInSeconds)
return !1;
const l = o.getTime() - Date.now();
return l <= Q * 1e3 / 2 || l <= Q * 1e3 && Math.random() < 0.1;
},
// * Untyped action payload. See https://github.com/statelyai/xstate/issues/3037
/** Should retry to import the token on network error or any internal server error.
* Don't retry more than REFRESH_TOKEN_MAX_ATTEMPTS times.
*/
shouldRetryImportToken: (a, o) => a.importTokenAttempts < X && (o.data.error.status === ie || o.data.error.status >= 500),
// * Authentication errors
// * Untyped action payload. See https://github.com/statelyai/xstate/issues/3037
unverified: (a, { data: { error: o } }) => o.status === 401 && // * legacy: don't use the message contents to determine if the email is unverified, but the error type (error.error)
(o.message === "Email is not verified" || o.error === "unverified-user"),
// * Event guards
hasSession: (a, o) => {
var c;
return !!((c = o.data) != null && c.session);
},
hasMfaTicket: (a, o) => {
var c;
return !!((c = o.data) != null && c.mfa);
},
isUnauthorizedError: (a, { data: { error: o } }) => o.status === 401
},
services: {
signInPassword: (a, { email: o, password: c }) => _(o) ? B(c) ? E("/signin/email-password", {
email: o,
password: c
}) : Promise.reject({ error: Y }) : Promise.reject({ error: R }),
signInPAT: (a, { pat: o }) => E("/signin/pat", {
personalAccessToken: o
}),
signInIdToken: (a, { provider: o, idToken: c, nonce: l }) => E("/signin/idtoken", {
provider: o,
idToken: c,
...l && { nonce: l }
}),
passwordlessSms: (a, { phoneNumber: o, options: c }) => {
var l;
return ee(o) ? (l = a.user) != null && l.isAnonymous ? (console.warn(
"Deanonymisation from a phone number is not yet implemented in hasura-auth"
), E(
"/user/deanonymize",
{
signInMethod: "passwordless",
connection: "sms",
phoneNumber: o,
options: S(e, c)
},
a.accessToken.value
)) : E("/signin/passwordless/sms", {
phoneNumber: o,
options: S(e, c)
}) : Promise.reject({ error: Z });
},
passwordlessSmsOtp: (a, { phoneNumber: o, otp: c }) => ee(o) ? E("/signin/passwordless/sms/otp", {
phoneNumber: o,
otp: c
}) : Promise.reject({ error: Z }),
signInEmailOTP: (a, { email: o, options: c }) => _(o) ? E("/signin/otp/email", {
email: o,
options: S(e, c)
}) : Promise.reject({ error: R }),
verifyEmailOTP: (a, { email: o, otp: c }) => _(o) ? E("/signin/otp/email/verify", {
email: o,
otp: c
}) : Promise.reject({ error: R }),
passwordlessEmail: (a, { email: o, options: c }) => {
var l;
return _(o) ? (l = a.user) != null && l.isAnonymous ? E(
"/user/deanonymize",
{
signInMethod: "passwordless",
connection: "email",
email: o,
options: S(e, c)
},
a.accessToken.value
) : E("/signin/passwordless/email", {
email: o,
options: S(e, c)
}) : Promise.reject({ error: R });
},
signInAnonymous: (a) => E("/signin/anonymous"),
signInMfaTotp: (a, o) => {
var l;
const c = o.ticket || ((l = a.mfa) == null ? void 0 : l.ticket);
return c ? We(c) ? E("/signin/mfa/totp", {
ticket: c,
otp: o.otp
}) : Promise.reject({ error: Ie }) : Promise.reject({ error: Se });
},
signInSecurityKeyEmail: async (a, { email: o }) => {
if (!_(o))
throw new b(R);
const c = await E(
"/signin/webauthn",
{ email: o }
);
let l;
try {
l = await j(c);
} catch (m) {
throw new b(m);
}
return E("/signin/webauthn/verify", { email: o, credential: l });
},
refreshToken: async (a, o) => {
const c = o.type === "TRY_TOKEN" ? o.token : a.refreshToken.value;
return { session: await E("/token", {
refreshToken: c
}), error: null };
},
signInSecurityKey: async () => {
try {
const a = await E(
"/signin/webauthn",
{}
);
let o;
try {
o = await j(a);
} catch (c) {
throw new b(c);
}
return E("/signin/webauthn/verify", { credential: o });
} catch (a) {
throw new b(a);
}
},
signout: async (a, o) => {
const c = await E(
"/signout",
{
refreshToken: a.refreshToken.value,
all: !!o.all
},
o.all ? a.accessToken.value : void 0
);
if (r && T)
try {
T.postMessage({ type: "signout" });
} catch {
}
return c;
},
signUpEmailPassword: async (a, { email: o, password: c, options: l, requestOptions: m }) => {
var y;
return _(o) ? B(c) ? (y = a.user) != null && y.isAnonymous ? E(
"/user/deanonymize",
{
signInMethod: "email-password",
email: o,
password: c,
options: S(e, l)
},
a.accessToken.value,
m == null ? void 0 : m.headers
) : E(
"/signup/email-password",
{
email: o,
password: c,
options: S(e, l)
},
null,
m == null ? void 0 : m.headers
) : Promise.reject({ error: Y }) : Promise.reject({ error: R });
},
signUpSecurityKey: async (a, { email: o, options: c, requestOptions: l }) => {
if (!_(o))
return Promise.reject({ error: R });
const m = c == null ? void 0 : c.nickname;
m && delete c.nickname;
const y = await E(
"/signup/webauthn",
{ email: o, options: c },
null,
l == null ? void 0 : l.headers
);
let z;
try {
z = await fe(y);
} catch (ge) {
throw new b(ge);
}
return E("/signup/webauthn/verify", {
credential: z,
options: {
redirectTo: c == null ? void 0 : c.redirectTo,
nickname: m,
displayName: c == null ? void 0 : c.displayName,
...(c == null ? void 0 : c.metadata) && { metadata: c == null ? void 0 : c.metadata }
}
});
},
importRefreshToken: async (a) => {
if (a.user && a.refreshTok