UNPKG

@nhost/hasura-auth-js

Version:

Hasura-auth client

1,445 lines 110 kB
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