@zitadel/sdk-core 1.0.0-alpha.25 → 1.0.0-alpha.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.mjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * Shared constants and utility functions for the SDK middleware proxy layer.\n *\n * These are consumed by every server SDK to avoid duplicating\n * framework-agnostic logic.\n */\n\nimport type { JwtPayload } from \"./jwt.js\";\n\nimport { joinURL } from \"ufo\";\n\nimport { isJwtShaped, verifyJwt } from \"./jwt.js\";\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\n/**\n * Headers that must never be forwarded to an upstream service.\n * These are connection-level headers that are meaningful only between\n * two directly connected peers and become invalid when proxied.\n */\nexport const HOP_BY_HOP: ReadonlySet<string> = new Set([\n \"connection\",\n // content-length is not hop-by-hop per RFC 7230, but it must be stripped: the\n // proxy re-sends a freshly buffered body, so the fetch implementation computes\n // the correct content-length itself. Forwarding the client's value is at best\n // redundant and at worst fatal — undici rejects a manually-set content-length\n // alongside a body with `UND_ERR_INVALID_ARG: invalid content-length header`,\n // which surfaced as an opaque \"fetch failed\" in the Qwik Router proxy.\n \"content-length\",\n // host is not a hop-by-hop header per RFC 7230, but it must be stripped so\n // the fetch implementation derives the correct Host from the upstream URL\n // rather than forwarding the client's Host and causing SNI/vhost mismatches.\n \"host\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n]);\n\n/**\n * SDK-internal headers that must never be forwarded to the upstream backend.\n * These headers carry session data between the middleware and server components;\n * forwarding them upstream would expose internal state and allow header injection.\n */\nexport const INTERNAL_HEADERS: ReadonlySet<string> = new Set([\"x-nextgen-auth-token\"]);\n\n// ─── Route matching ──────────────────────────────────────────────────────────\n\n/**\n * Returns `true` when `pathname` matches at least one entry in `routes`.\n *\n * An entry ending with `*` matches any path that starts with the prefix\n * before the `*`. All other entries match exactly, ignoring a trailing slash\n * on either side: `/profile` and `/profile/` name the same route, and several\n * frameworks (Qwik Router by default) serve the slashed form as canonical. An\n * exact compare would let `/profile/` past a `protectedRoutes: [\"/profile\"]`.\n *\n * @param pathname - The URL pathname to test.\n * @param routes - The list of route patterns to match against.\n * @returns `true` if at least one pattern matches.\n */\nexport function matchesRoutes(pathname: string, routes: readonly string[]): boolean {\n // A linear scan, not /\\/+$/: that regex backtracks quadratically on a long\n // run of slashes, and the pathname is attacker-controlled on every request.\n const withoutTrailingSlash = (path: string): string => {\n let end = path.length;\n while (end > 1 && path.charCodeAt(end - 1) === 0x2f) {\n end--;\n }\n return path.slice(0, end);\n };\n const path = withoutTrailingSlash(pathname);\n\n return routes.some((route) => {\n if (route.endsWith(\"*\")) {\n return pathname.startsWith(route.slice(0, -1));\n }\n return path === withoutTrailingSlash(route);\n });\n}\n\n// ─── Login redirect ──────────────────────────────────────────────────────────\n\n/**\n * The query parameter that brings a signed-out request's path to the login\n * page, where `<zitadel-login>` returns after sign-in.\n */\nconst RETURN_PATH_PARAM = \"next\";\n\n/**\n * Where a signed-out request for a protected route is sent: `loginPath`, with\n * the requested path and query string in `next`, so `<zitadel-login>` returns\n * to the page as it was asked for, filters and tabs included. The fragment\n * never reaches the server, so it cannot be carried.\n *\n * A `next` the request carries itself is left out, so a redirect never nests\n * an earlier one: should the login page be protected by mistake, each pass of\n * the redirect loop would otherwise wrap the last and grow the URL. The rest\n * of the query keeps the encoding it was sent with.\n *\n * Whatever query `loginPath` has of its own is kept, except a `next`, which\n * this replaces.\n *\n * @param loginPath - The login page, a path starting with a single `/`.\n * @param requestUrl - The full URL of the signed-out request.\n * @returns The URL to redirect to, on the request's origin.\n */\nexport function loginRedirectUrl(loginPath: string, requestUrl: string | URL): URL {\n // A copy made from the whole URL, never the caller's `search`: SvelteKit\n // makes `search` throw on the request event's URL while it prerenders.\n const requested = new URL(requestUrl);\n const query = requested.search\n .slice(1)\n .split(\"&\")\n .filter((pair) => !new URLSearchParams(pair).has(RETURN_PATH_PARAM))\n .join(\"&\");\n const loginUrl = new URL(loginPath, requested);\n loginUrl.searchParams.set(\n RETURN_PATH_PARAM,\n query === \"\" ? requested.pathname : `${requested.pathname}?${query}`,\n );\n return loginUrl;\n}\n\n// ─── Proxy credentials ───────────────────────────────────────────────────────\n\n/**\n * Whether a proxied call may carry the confidential project secret.\n *\n * ADR 036: the browser flow needs the secret only for the handoff exchange\n * (`POST /sessions/exchange`). Stamping it onto every proxied operation would\n * turn the app's public `/__nextgen/*` boundary into an open relay carrying\n * project-level authority for any anonymous caller.\n *\n * @param method - The proxied request's HTTP method.\n * @param pathname - The path after the proxy prefix, e.g. `/sessions/exchange`.\n * @returns `true` only for the handoff exchange.\n */\nexport function requiresProjectSecret(method: string, pathname: string): boolean {\n return method.toUpperCase() === \"POST\" && pathname === \"/sessions/exchange\";\n}\n\n// ─── Response header filtering ───────────────────────────────────────────────\n\n/**\n * An upstream `Location`, pointed back at this app.\n *\n * Only the path, query and fragment survive; whatever origin the upstream\n * named is discarded and the result is re-based onto the request's own URL.\n * That keeps both properties the blanket strip was protecting -- an internal\n * hostname can never reach the browser, and the redirect can only ever land on\n * this app -- without needing to know what this app's public origin is.\n *\n * Comparing origins instead would have required that knowledge, and there is\n * no reliable source for it: `X-Forwarded-Host` is attacker-settable on a\n * direct request, and the request's own host is wrong behind a proxy that\n * terminates TLS or rewrites it. Discarding the origin sidesteps the question\n * -- a redirect to another origin becomes the same path on this one rather\n * than being dropped, so a deployment behind such a proxy works instead of\n * landing on a blank page.\n *\n * Nothing legitimate on this path points off-origin anyway: an outbound\n * redirect to a provider travels as `step.redirect_url` in a JSON body and the\n * widget performs it, never as a proxied header.\n *\n * A relative reference resolves against the path it came from (RFC 3986 §5),\n * so `next?flow=x` answered to `/__nextgen/idp/google/callback` means\n * `/__nextgen/idp/google/next?flow=x`. An unparseable value is the one case with\n * nothing to forward.\n */\nfunction ontoThisApp(location: string, selfUrl: string | undefined): string | undefined {\n if (selfUrl === undefined || selfUrl === \"\") {\n return undefined;\n }\n try {\n const base = new URL(selfUrl);\n const target = new URL(location, base);\n return new URL(`${target.pathname}${target.search}${target.hash}`, base).toString();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Filters upstream response headers for proxying: strips hop-by-hop headers\n * and `set-cookie` (handled separately via `getSetCookie()`), and reduces\n * `location` to a redirect that can only land on this app.\n *\n * `location` was stripped outright until the identity-provider callback\n * needed it: the provider returns the browser to `/__nextgen/idp/{slug}/callback`,\n * and the engine answers `303` back to the page the sign-in started on. That\n * is a top-level navigation with no JavaScript in the loop, so a dropped\n * `Location` leaves the browser on an empty page. Outbound redirects to a\n * provider do not travel this way -- they arrive as `step.redirect_url` in a\n * JSON body and the widget navigates -- so nothing legitimate points off-origin.\n *\n * @param upstream - The upstream response headers.\n * @param selfUrl - The app's own request URL, which is both the origin a\n * redirect must land on and the base a relative one resolves against.\n * Omitting it keeps the old behaviour of dropping every redirect.\n * @returns A new `Headers` object with filtered headers.\n */\nexport function filterResponseHeaders(upstream: Headers, selfUrl?: string): Headers {\n const filtered = new Headers();\n upstream.forEach((value, key) => {\n const name = key.toLowerCase();\n if (HOP_BY_HOP.has(name) || name === \"set-cookie\") {\n return;\n }\n if (name === \"location\") {\n const onThisApp = ontoThisApp(value, selfUrl);\n if (onThisApp !== undefined) {\n filtered.set(\"location\", onThisApp);\n }\n return;\n }\n filtered.set(key, value);\n });\n return filtered;\n}\n\n// ─── Session cookies ─────────────────────────────────────────────────────────\n\n/** The cookie the server sets a session in and reads it from. */\nexport const SERVER_SESSION_COOKIE = \"__nextgen_session\";\n\n/** Every app's own session cookie starts with this. */\nconst PROJECT_SESSION_COOKIE_PREFIX = `${SERVER_SESSION_COOKIE}_`;\n\n/**\n * The cookie an app keeps its project's session in: the server's name with the\n * project id appended (ADR 073). A browser shares one cookie jar across every\n * port of a host, so under the server's name alone the Console of a local\n * server and every app on `localhost` would hold their sessions in one slot and\n * replace each other's.\n *\n * Every byte of the id outside `[A-Za-z0-9_-]` is percent-encoded, so any\n * configured id makes a valid cookie name. Ids the server mints need none.\n */\nexport function sessionCookieName(projectId: string): string {\n let name = PROJECT_SESSION_COOKIE_PREFIX;\n for (const byte of new TextEncoder().encode(projectId)) {\n const char = String.fromCharCode(byte);\n name += /[A-Za-z0-9_-]/.test(char)\n ? char\n : `%${byte.toString(16).toUpperCase().padStart(2, \"0\")}`;\n }\n return name;\n}\n\n/** Whether `name` is a session cookie: the server's, or any app's own. */\nfunction isSessionCookie(name: string): boolean {\n return name === SERVER_SESSION_COOKIE || name.startsWith(PROJECT_SESSION_COOKIE_PREFIX);\n}\n\n/**\n * A session cookie a response sets. An empty `value` with `maxAge: 0` clears\n * it.\n */\nexport interface SessionCookie {\n readonly name: string;\n readonly value: string;\n /** Seconds the browser keeps it, or `undefined` for the browser session. */\n readonly maxAge: number | undefined;\n readonly secure: boolean;\n}\n\n/**\n * The `Set-Cookie` value for `cookie`, with the attributes the server gives its\n * own session cookie: `Path=/`, `HttpOnly`, `SameSite=Lax`.\n */\nexport function serializeSessionCookie(cookie: SessionCookie): string {\n const attributes = [`${cookie.name}=${cookie.value}`, \"Path=/\"];\n if (cookie.maxAge !== undefined) {\n attributes.push(`Max-Age=${cookie.maxAge}`);\n }\n attributes.push(\"HttpOnly\", \"SameSite=Lax\");\n if (cookie.secure) {\n attributes.push(\"Secure\");\n }\n return attributes.join(\"; \");\n}\n\n/**\n * The `Cache-Control` of a response that sets a session cookie, as the server\n * sends on its session answers: a shared cache must not hand the cookie, the\n * session token it carries or its clearing, to the next visitor.\n */\nexport const SESSION_COOKIE_CACHE_CONTROL = \"private, no-store\";\n\n/** The browser's request as the app sees it, and as a proxy in front of it forwarded it. */\nexport interface BrowserRequest {\n /** The app's own request URL. */\n readonly requestUrl: string | URL;\n /** The request's `X-Forwarded-Proto`, or `null`. */\n readonly forwardedProto?: string | null;\n /** The request's `X-Forwarded-Host`, or `null`. */\n readonly forwardedHost?: string | null;\n}\n\n/** The first entry of a forwarded header a chain of proxies appended to, if any. */\nfunction firstForwarded(value: string | null | undefined): string | undefined {\n const first = value?.split(\",\")[0]?.trim();\n return first ? first : undefined;\n}\n\n/**\n * Whether a session cookie this app sets needs `Secure`, by the rule the server\n * applies to its own (`cookieSecureFromContext`): always, except over plain\n * http to a loopback host, where Safari would drop a `Secure` cookie. The\n * scheme and host are what a proxy in front of the app forwarded (the first\n * `X-Forwarded-Proto` and `X-Forwarded-Host`), else the app's request URL's,\n * so an app behind a TLS proxy that talks to it over loopback keeps `Secure`.\n */\nfunction secureFor(request: BrowserRequest): boolean {\n let url: URL;\n try {\n url = new URL(request.requestUrl);\n } catch {\n return true;\n }\n const proto = firstForwarded(request.forwardedProto) ?? url.protocol.slice(0, -1);\n if (proto.toLowerCase() !== \"http\") {\n return true;\n }\n let host: string;\n try {\n host = new URL(`http://${firstForwarded(request.forwardedHost) ?? url.host}`).hostname;\n } catch {\n return true;\n }\n host = host.toLowerCase();\n return !(host === \"localhost\" || host === \"[::1]\" || /^127(?:\\.\\d{1,3}){3}$/.test(host));\n}\n\n/** Seconds from now until `epochMs`, at least one, or `undefined` when unknown. */\nfunction secondsUntil(epochMs: number): number | undefined {\n return Number.isFinite(epochMs)\n ? Math.max(1, Math.floor((epochMs - Date.now()) / 1000))\n : undefined;\n}\n\n/** Clears the cookie `name`. */\nfunction clearedCookie(name: string, secure: boolean): SessionCookie {\n return { name, value: \"\", maxAge: 0, secure };\n}\n\n/**\n * Copies a session of this project found in the server's cookie into the\n * project's own. The server's cookie keeps it: on a server without platform\n * bootstrap the Console signs in to the app's project, and clearing the cookie\n * would sign it out. The proxy retires the server's cookie when the app signs\n * a user in or out ({@link proxySessionCookies}), so it cannot sign its user\n * back in once the app's own cookie is gone.\n */\nfunction copiedToProjectCookie(\n token: string,\n projectId: string,\n maxAge: number | undefined,\n secure: boolean,\n): SessionCookie {\n return { name: sessionCookieName(projectId), value: token, maxAge, secure };\n}\n\n/** The pieces of a `Cookie` header, each with the name it carries. */\nfunction cookiePieces(header: string | null): { name: string; piece: string }[] {\n const pieces: { name: string; piece: string }[] = [];\n for (const raw of (header ?? \"\").split(\";\")) {\n const piece = raw.trim();\n if (piece !== \"\") {\n const index = piece.indexOf(\"=\");\n pieces.push({ name: (index === -1 ? piece : piece.slice(0, index)).trim(), piece });\n }\n }\n return pieces;\n}\n\n/** The value of the first cookie named `name`, or `null` when it is absent or empty. */\nfunction cookieValue(pieces: { name: string; piece: string }[], name: string): string | null {\n const found = pieces.find((candidate) => candidate.name === name);\n const value = found ? found.piece.slice(found.piece.indexOf(\"=\") + 1).trim() : \"\";\n return value === \"\" ? null : value;\n}\n\n/** The name a `Set-Cookie` value sets. */\nfunction setCookieName(setCookie: string): string {\n const index = setCookie.indexOf(\"=\");\n return index === -1 ? \"\" : setCookie.slice(0, index).trim();\n}\n\n/**\n * What a proxied request carries that decides its session cookies. The\n * request URL and the forwarded headers decide whether a cookie needs `Secure`.\n */\nexport interface ProxiedRequest extends BrowserRequest {\n readonly method: string;\n /** The path after the proxy prefix, e.g. `/sessions/me`. */\n readonly path: string;\n /** The query string, with its leading `?`, or `\"\"`. */\n readonly search: string;\n /** The request's `Cookie` header, or `null`. */\n readonly cookie: string | null;\n}\n\n/** The server a proxy forwards to, for the calls it makes of its own. */\nexport interface ProxyUpstream {\n /** Base URL of the auth backend. */\n readonly url: string;\n /** How long to wait for an answer. */\n readonly timeoutMs: number;\n}\n\n/** What a proxied answer sends the browser. */\nexport interface ProxiedAnswer {\n readonly setCookies: string[];\n readonly body: ReadableStream<Uint8Array> | string | null;\n}\n\n/** A proxied call's session cookies, on the way up and on the way back. */\nexport interface ProxiedSessionCookies {\n /** The `Cookie` header to send upstream, or `null` to send none. */\n readonly upstreamCookie: string | null;\n /**\n * The upstream answer's way back to the browser: the `Set-Cookie` values and\n * the body to send. Send this body, never the upstream response's own, which\n * this may have read.\n */\n toBrowser(upstream: Response): Promise<ProxiedAnswer>;\n}\n\n/**\n * The session cookies of a call the app proxies to the server (ADR 073). The\n * server knows one session cookie, `__nextgen_session`; the app keeps its\n * project's session in {@link sessionCookieName}, so it neither replaces nor\n * reads the session of another app or of the Console on the same host.\n *\n * - On the way up, the app's own session cookie goes under the server's name,\n * and every other session cookie stays behind. Other cookies pass unchanged.\n * - On the way back, a `Set-Cookie` of the server's session cookie, which sets\n * or clears it, is renamed to the app's.\n * - A session kept under the server's name goes up to one call only, the\n * session probe `GET /sessions/me?project_id=<project>`, which the server\n * refuses for another project's session. When the answer signs a user of\n * this project in, the session is copied to the app's cookie; the server's\n * cookie keeps it, since the Console may share the project.\n * - When the app signs a user in (`POST /sessions/exchange`) or out\n * (`DELETE /sessions/me`), a session still kept under the server's name is\n * retired, or it would sign its user back in once the app's own cookie is\n * gone: the server is asked about it in this project, and a session of this\n * project is cleared (and on sign-out also ended), a dead one cleared, and\n * one of another project, the Console's on `localhost`, left alone.\n * - Without a project the cookies pass as they are: nothing says which session\n * is the app's.\n *\n * @param request - The proxied request.\n * @param projectId - The project this app signs its users in to.\n * @param upstream - The server the proxy forwards to, which it asks about a\n * session kept under the server's name.\n */\nexport function proxySessionCookies(\n request: ProxiedRequest,\n projectId: string | undefined,\n upstream: ProxyUpstream,\n): ProxiedSessionCookies {\n if (!projectId) {\n return {\n upstreamCookie: request.cookie,\n toBrowser: async (upstream) => ({\n setCookies: upstream.headers.getSetCookie?.() ?? [],\n body: upstream.body,\n }),\n };\n }\n\n const ownName = sessionCookieName(projectId);\n const pieces = cookiePieces(request.cookie);\n const own = cookieValue(pieces, ownName);\n // A session kept under the server's name, which goes up to the probe only.\n const shared = cookieValue(pieces, SERVER_SESSION_COOKIE);\n const method = request.method.toUpperCase();\n const isProbe =\n method === \"GET\" &&\n request.path === \"/sessions/me\" &&\n new URLSearchParams(request.search).get(\"project_id\") === projectId;\n const signsIn = method === \"POST\" && request.path === \"/sessions/exchange\";\n const signsOut = method === \"DELETE\" && request.path === \"/sessions/me\";\n const probed = own === null && isProbe ? shared : null;\n const secure = secureFor(request);\n\n const sent = pieces.filter(({ name }) => !isSessionCookie(name)).map(({ piece }) => piece);\n const session = own ?? probed;\n if (session !== null) {\n sent.push(`${SERVER_SESSION_COOKIE}=${session}`);\n }\n\n return {\n upstreamCookie: sent.length > 0 ? sent.join(\"; \") : null,\n async toBrowser(answer) {\n const upstreamSetCookies = answer.headers.getSetCookie?.() ?? [];\n const setCookies = upstreamSetCookies.map((setCookie) =>\n setCookieName(setCookie) === SERVER_SESSION_COOKIE\n ? `${ownName}${setCookie.slice(setCookie.indexOf(\"=\"))}`\n : setCookie,\n );\n // A sign-in that set the app's session, or any sign-out: the session the\n // server's cookie still holds goes too, unless it is another project's.\n const setsSession = upstreamSetCookies.some(\n (setCookie) => setCookieName(setCookie) === SERVER_SESSION_COOKIE,\n );\n if (shared !== null && (signsOut || (signsIn && answer.ok && setsSession))) {\n if (await retiresSharedSession(shared, projectId, upstream, signsOut)) {\n setCookies.push(serializeSessionCookie(clearedCookie(SERVER_SESSION_COOKIE, secure)));\n }\n }\n if (probed === null || answer.status !== 200) {\n return { setCookies, body: answer.body };\n }\n const body = await answer.text();\n const signedIn = sessionAnswer(parsedJson(body), projectId);\n if (\n signedIn.userId &&\n !setCookies.some((setCookie) => setCookieName(setCookie) === ownName)\n ) {\n const maxAge = signedIn.expiresAt\n ? secondsUntil(Date.parse(signedIn.expiresAt))\n : undefined;\n setCookies.push(\n serializeSessionCookie(copiedToProjectCookie(probed, projectId, maxAge, secure)),\n );\n }\n return { setCookies, body };\n },\n };\n}\n\n/**\n * Whether the session kept under the server's name goes when the app signs a\n * user in or out: the server is asked about it in this project. A session of\n * this project goes, and on sign-out it is ended too, so that it cannot sign\n * its user back in; one the server no longer holds goes as well. One of another\n * project, the Console's on `localhost`, stays, and so does one the server gave\n * no verdict on, and a JWT, which the middleware judges itself.\n */\nasync function retiresSharedSession(\n token: string,\n projectId: string,\n upstream: ProxyUpstream,\n signsOut: boolean,\n): Promise<boolean> {\n if (isJwtShaped(token)) {\n return false;\n }\n const cookie = `${SERVER_SESSION_COOKIE}=${token}`;\n let res: Response;\n try {\n res = await fetch(\n `${joinURL(upstream.url, \"/sessions/me\")}?project_id=${encodeURIComponent(projectId)}`,\n {\n method: \"GET\",\n headers: { cookie },\n signal: AbortSignal.timeout(upstream.timeoutMs),\n },\n );\n } catch {\n return false;\n }\n if (res.status === 401) {\n return true;\n }\n if (res.status === 404 || res.status === 403) {\n return (await errorCode(res)) === SESSION_NOT_FOUND;\n }\n if (!res.ok) {\n return false;\n }\n const answer = parsedJson(await res.text().catch(() => \"\")) as\n | { project_id?: unknown }\n | undefined;\n if (answer?.project_id !== projectId) {\n return false;\n }\n if (signsOut) {\n // Logout needs no CSRF token (ADR 053 §5); a call without an Origin is no\n // cross-site request. A failed revoke still clears the cookie: the\n // browser no longer holds the session.\n await fetch(joinURL(upstream.url, \"/sessions/me\"), {\n method: \"DELETE\",\n headers: { cookie },\n signal: AbortSignal.timeout(upstream.timeoutMs),\n }).catch(() => undefined);\n }\n return true;\n}\n\n/** `text` as JSON, or `undefined` when it is not JSON. */\nfunction parsedJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n// ─── Middleware types ──────────────────────────────────────────────────────────\n\n/**\n * The authenticated session for a signed-in user. Identity follows the\n * user-ref vocabulary (ADR 058): render `display`, falling back to\n * `identifier`, then `userId`.\n */\nexport type NextgenSession = {\n /** The user's unique identifier (`sub` claim). */\n userId: string;\n /**\n * The user schema's designated identifier value (the login identifier,\n * e.g. an email address or username), or `null` when unresolved. On the\n * JWT path this carries the `email` claim.\n */\n identifier: string | null;\n /**\n * The schema property `identifier` came from (e.g. `\"email\"`), or `null`\n * when unknown — JWT-claim identities carry no property attribution.\n */\n identifierProperty: string | null;\n /** The user's display name rendering, or `null` when the schema designates none. */\n display: string | null;\n /** The raw verified JWT. */\n token: string;\n};\n\n/** Auth state when the user is signed in. */\nexport type AuthState = { isAuthenticated: true; session: NextgenSession };\n\n/** Auth state when the user is not signed in. */\nexport type UnauthState = { isAuthenticated: false; session: null };\n\n/** Union of all possible auth states. */\nexport type AuthResult = AuthState | UnauthState;\n\n/**\n * The client-safe session exposed to app UI (headers, account menus).\n * Identical to {@link NextgenSession} but omits `token` — the raw session\n * token must never reach client-side JavaScript, whether through an SSR\n * payload or a client-side fetch result.\n */\nexport type ClientSession = {\n /** The user's unique identifier (`sub` claim). */\n userId: string;\n /** The designated identifier value (login identifier), or `null`. */\n identifier: string | null;\n /** The schema property the identifier came from, or `null`. */\n identifierProperty: string | null;\n /** The display name rendering, or `null`. */\n display: string | null;\n};\n\n/** Client-safe auth state when the user is signed in. */\nexport type ClientAuthState = { isAuthenticated: true; session: ClientSession };\n\n/**\n * Union of all possible client-safe auth states. Returned by the client\n * session reads (`useAuth()` in sdk-nuxt, `getSession()` in sdk-next).\n * Token is intentionally absent — use the server-side helpers when the raw\n * token is needed.\n */\nexport type ClientAuthResult = ClientAuthState | UnauthState;\n\n/**\n * Options passed to the SDK middleware factory (`nextgenMiddleware` in\n * sdk-next, `createNextgenMiddleware` in sdk-nuxt).\n */\nexport type NextgenMiddlewareOptions = {\n /**\n * Full URL of the Zitadel auth backend.\n * @default process.env.ZITADEL_URL ?? \"http://localhost:8080\"\n */\n url?: string;\n\n /**\n * The project this app signs its users in to. A session counts as signed in\n * only when it belongs to this project. A session of any other project on the\n * same server (a second app, or the Console, whose cookie the browser sends\n * to every port of a shared host) reads as signed out, and its cookie is left\n * alone.\n *\n * The app keeps its session in a cookie of its own,\n * `__nextgen_session_<projectId>` ({@link sessionCookieName}), so signing in\n * here neither replaces nor reads the session of another app or of the\n * Console on the same host (ADR 073).\n *\n * An opaque session token is checked by the backend\n * (`GET /sessions/me?project_id=…`) and against the project its answer names;\n * a JWT must name the project in its `aud` claim.\n *\n * Required: with no project, no session counts as signed in, and the proxy\n * passes the server's session cookie through unchanged. Each SDK falls back\n * to the `ZITADEL_PROJECT_ID` environment variable, then to its framework's\n * public project id variable, which `zitadel setup` writes to `.env.local`.\n */\n projectId?: string;\n\n /**\n * URL path prefix that is reverse-proxied to the auth backend.\n *\n * Sign-in with an external provider currently requires the default: the\n * provider returns to `/__nextgen/idp/{slug}/callback`, which is proxied only under\n * the default prefix.\n * @default \"/__nextgen\"\n */\n proxyPath?: string;\n\n /**\n * Pathnames that require a valid session. Requests to these routes without\n * a valid token are redirected to {@link loginPath}.\n * Entries ending with `*` match any sub-path (e.g. `\"/admin*\"`).\n *\n * When the backend cannot say whether a session cookie is live (it answers\n * `5xx`, does not answer in time, or cannot be reached), the route answers\n * `503 Service Unavailable` instead: the user may well be signed in, and the\n * login page would tell them they are not.\n * @default []\n */\n protectedRoutes?: string[];\n\n /**\n * Pathnames that are completely skipped by the middleware — no JWT\n * verification, no protection check, no header tunnelling.\n * Useful for webhooks, health-check endpoints, or any route where\n * running auth logic is undesirable.\n * Entries ending with `*` match any sub-path (e.g. `\"/public*\"`).\n * @default []\n */\n ignoredRoutes?: string[];\n\n /**\n * Pathname to redirect unauthenticated users to.\n * A `next` query parameter carries the requested path and query string,\n * where `<zitadel-login>` returns after sign-in.\n * @default \"/login\"\n */\n loginPath?: string;\n\n /**\n * Reserved for future use. Local PEM public key for offline JWT verification.\n */\n jwtKey?: string;\n\n /**\n * Restrict accepted JWT `alg` header values. Tokens whose algorithm is not\n * in this list are rejected before JWKS is fetched.\n * @default [\"RS256\", \"ES256\"]\n */\n allowedAlgorithms?: string[];\n\n /**\n * Clock skew tolerance in milliseconds applied to `exp`, `nbf`, and `iat`\n * claim validation.\n * @default 5000\n */\n clockSkewMs?: number;\n\n /**\n * Expected value(s) of the `aud` claim. When omitted, audience is not\n * validated. Pass a single string or an array when a token may carry\n * multiple audiences.\n */\n audience?: string | string[];\n\n /**\n * Accepted values for the JWT header `typ` claim (case-insensitive).\n * Tokens whose `typ` is not in this list are rejected.\n * Set to `[]` to disable token-type checking entirely.\n * @default [\"JWT\", \"at+JWT\"]\n */\n allowedTokenTypes?: string[];\n\n /**\n * Timeout in milliseconds for JWKS endpoint requests.\n * Requests that do not complete within this window are aborted and the\n * token is rejected, treating the request as unauthenticated.\n * @default 5000\n */\n jwksTimeoutMs?: number;\n\n /**\n * Timeout in milliseconds for upstream proxy requests.\n * Requests that do not complete within this window are aborted with a\n * network error. Defaults to 5 000 ms.\n * @default 5000\n */\n proxyTimeoutMs?: number;\n\n /**\n * Timeout in milliseconds for opaque (non-JWT) session token validation\n * via the backend's `GET /sessions/me` endpoint. A token the backend does\n * not answer for within this window is not refused: the request counts as\n * signed out, its session cookie is kept, and a protected route answers\n * `503` (see {@link protectedRoutes}).\n * @default 5000\n */\n opaqueTokenTimeoutMs?: number;\n};\n\n// ─── Opaque session tokens ───────────────────────────────────────────────────\n\n/**\n * What the backend reports for an opaque (non-JWT) session token.\n *\n * `userId` is `null` when the session names no user of this app's project.\n * That is an anonymous session — `CreateSession` sets the session cookie\n * before any user factor is verified, so an abandoned or in-progress sign-in\n * holds a live session that names no user — or a live session of another\n * project. Such a session is **not** signed in, but it is not dead either, so\n * callers keep its cookie instead of clearing it: an anonymous sign-in may\n * still complete, and the other project's session belongs to the app that\n * signed it in.\n */\nexport interface OpaqueSession {\n readonly userId: string | null;\n readonly identifier: string | null;\n readonly identifierProperty: string | null;\n readonly display: string | null;\n /** When the session expires, as the backend reports it, or `null`. */\n readonly expiresAt: string | null;\n}\n\n/**\n * The error code the backend answers a session read with when the session\n * belongs to another project than the one the read names.\n */\nexport const SESSION_PROJECT_MISMATCH = \"sess.project_mismatch\";\n\n/** A live session that names no user of this app's project. */\nfunction noUser(): OpaqueSession {\n return {\n userId: null,\n identifier: null,\n identifierProperty: null,\n display: null,\n expiresAt: null,\n };\n}\n\n/**\n * What a `GET /sessions/me` answer says about this app's project. A session of\n * another project names no user here: a backend that predates `project_id`\n * answers for any project, so the project the answer names is checked too.\n */\nfunction sessionAnswer(body: unknown, projectId: string): OpaqueSession {\n const answer = (typeof body === \"object\" && body !== null ? body : {}) as {\n project_id?: unknown;\n user_id?: unknown;\n expires_at?: unknown;\n user?: { identifier?: unknown; identifier_property?: unknown; display?: unknown };\n };\n if (answer.project_id !== projectId) {\n return noUser();\n }\n const text = (value: unknown): string | null =>\n typeof value === \"string\" && value !== \"\" ? value : null;\n return {\n userId: text(answer.user_id),\n identifier: text(answer.user?.identifier),\n identifierProperty: text(answer.user?.identifier_property),\n display: text(answer.user?.display),\n expiresAt: text(answer.expires_at),\n };\n}\n\n/**\n * The backend's verdict on an opaque session token, from\n * {@link checkOpaqueSessionToken}.\n *\n * - `live`: the backend holds a live session for the token. Its `userId` is\n * `null` when the session names no user of this app's project: an anonymous\n * one, or one of another project (see {@link OpaqueSession}).\n * - `refused`: the backend judged the token and refused it (`401`), or found\n * the session it names gone (`404` with the `sess.not_found` code). Only\n * this verdict makes the cookie dead.\n * - `undecided`: the backend gave no verdict: a `5xx` or any other answer, no\n * answer in time, a network error, or a body that cannot be read. The\n * backend answers `500`, never `401`, when it cannot decide (its token or\n * key store failed), so that an outage does not sign its users out. The\n * request counts as signed out, but the cookie is kept.\n */\nexport type OpaqueSessionCheck =\n | { readonly verdict: \"live\"; readonly session: OpaqueSession }\n | { readonly verdict: \"refused\" }\n | { readonly verdict: \"undecided\" };\n\n/** The code the backend answers `GET /sessions/me` with when the session is gone. */\nconst SESSION_NOT_FOUND = \"sess.not_found\";\n\n/**\n * Asks the backend (`GET {issuerUrl}/sessions/me?project_id={projectId}`)\n * whether an opaque session token names a live session in this app's project,\n * telling a refusal apart from no verdict.\n *\n * A live session of another project comes back with no user, like an\n * anonymous one: it signs no one in here, and its cookie is not dead. The\n * backend refuses such a session itself (`403 sess.project_mismatch`); the\n * project its answer names is checked as well, because a backend that predates\n * the parameter answers for any project.\n *\n * A `404` is a refusal only with the `sess.not_found` code. Without it, the\n * answer came from something in front of the backend (a proxy, a `url` with\n * the wrong path), which judged nothing.\n *\n * When there is no verdict, it logs why: the request it was asked for then\n * renders signed out, or answers `503` on a protected route, and nothing else\n * would say what went wrong.\n *\n * @param token - The opaque session token from the app's session cookie.\n * @param issuerUrl - Base URL of the auth backend.\n * @param timeoutMs - How long to wait for the backend before giving up.\n * @param projectId - The project this app signs its users in to. Without one,\n * no session can be shown to belong here: the backend is not asked, and the\n * session reads as live with no user, so its cookie is kept.\n * @returns The backend's {@link OpaqueSessionCheck | verdict}.\n */\nexport async function checkOpaqueSessionToken(\n token: string,\n issuerUrl: string,\n timeoutMs: number,\n projectId: string,\n): Promise<OpaqueSessionCheck> {\n if (!projectId) {\n return { verdict: \"live\", session: noUser() };\n }\n const key = await otherProjectKey(token, projectId);\n if (otherProjectSessions.knows(key)) {\n return { verdict: \"live\", session: noUser() };\n }\n const endpoint = joinURL(issuerUrl, \"/sessions/me\");\n try {\n const res = await fetch(`${endpoint}?project_id=${encodeURIComponent(projectId)}`, {\n method: \"GET\",\n headers: { cookie: `${SERVER_SESSION_COOKIE}=${token}` },\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (res.status === 401) {\n return { verdict: \"refused\" };\n }\n if (res.status === 404 && (await errorCode(res)) === SESSION_NOT_FOUND) {\n return { verdict: \"refused\" };\n }\n if (res.status === 403 && (await errorCode(res)) === SESSION_PROJECT_MISMATCH) {\n otherProjectSessions.remember(key);\n return { verdict: \"live\", session: noUser() };\n }\n if (!res.ok) {\n return undecided(endpoint, `HTTP ${res.status}`);\n }\n const body: unknown = await res.json();\n if ((body as { project_id?: unknown } | null)?.project_id !== projectId) {\n otherProjectSessions.remember(key);\n }\n return { verdict: \"live\", session: sessionAnswer(body, projectId) };\n } catch (err) {\n return undecided(endpoint, failureCause(err, timeoutMs));\n }\n}\n\n/**\n * The opaque session tokens the backend placed in another project, so that a\n * request signed out here does not ask again about the session another app,\n * or on `localhost` the Console, keeps under the server's cookie: a session\n * never changes its project. Each is known for a while, and only so many.\n */\nconst otherProjectSessions = (() => {\n const TTL_MS = 5 * 60_000;\n const MAX_ENTRIES = 1024;\n const known = new Map<string, number>();\n return {\n knows(key: string): boolean {\n const until = known.get(key);\n if (until === undefined) {\n return false;\n }\n if (until > Date.now()) {\n return true;\n }\n known.delete(key);\n return false;\n },\n remember(key: string): void {\n known.delete(key);\n known.set(key, Date.now() + TTL_MS);\n if (known.size > MAX_ENTRIES) {\n const oldest = known.keys().next().value;\n if (oldest !== undefined) {\n known.delete(oldest);\n }\n }\n },\n clear(): void {\n known.clear();\n },\n };\n})();\n\n/** The memo key of a token in a project: a hash, so that the memo holds no session token. */\nasync function otherProjectKey(token: string, projectId: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(token));\n let hex = \"\";\n for (const byte of new Uint8Array(digest)) {\n hex += byte.toString(16).padStart(2, \"0\");\n }\n return `${projectId}:${hex}`;\n}\n\n/**\n * Forgets which session tokens the backend placed in another project\n * ({@link checkOpaqueSessionToken} remembers them for a while). For tests that\n * answer for the same token differently.\n */\nexport function clearSessionCheckCache(): void {\n otherProjectSessions.clear();\n}\n\n/**\n * Validates an opaque session token by asking the backend\n * (`GET {issuerUrl}/sessions/me?project_id={projectId}`) whether the session is\n * live in this app's project.\n *\n * `null` folds a refusal and no verdict together, so it says whether the\n * request is signed in, not whether the cookie is dead: clearing the cookie on\n * `null` signs users out whenever the backend fails. To decide that, use\n * {@link checkOpaqueSessionToken}.\n *\n * @param token - The opaque session token from the app's session cookie.\n * @param issuerUrl - Base URL of the auth backend.\n * @param timeoutMs - How long to wait for the backend before giving up.\n * @param projectId - The project this app signs its users in to.\n * @returns The live session — with a `null` `userId` when it names no user of\n * this project — or `null` when the backend did not confirm one: it refused\n * the token, or gave no verdict.\n */\nexport async function validateOpaqueSessionToken(\n token: string,\n issuerUrl: string,\n timeoutMs: number,\n projectId: string,\n): Promise<OpaqueSession | null> {\n const check = await checkOpaqueSessionToken(token, issuerUrl, timeoutMs, projectId);\n return check.verdict === \"live\" ? check.session : null;\n}\n\n/** The `code` of an error envelope, or `undefined` when the body is not one. */\nasync function errorCode(res: Response): Promise<string | undefined> {\n try {\n const body = (await res.json()) as { code?: unknown };\n return typeof body.code === \"string\" ? body.code : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Why a session check failed, for the log. */\nfunction failureCause(err: unknown, timeoutMs: number): string {\n if ((err as { name?: unknown } | null)?.name === \"TimeoutError\") {\n return `no answer within ${timeoutMs} ms`;\n }\n // A failed fetch says only \"fetch failed\"; what failed is in its cause.\n const cause = (err as { cause?: unknown } | null)?.cause;\n return cause instanceof Error ? `${String(err)}: ${cause.message}` : String(err);\n}\n\n/** Logs why the backend gave no verdict on a session cookie, and returns that. */\nfunction undecided(endpoint: string, cause: string): OpaqueSessionCheck {\n console.warn(\n `[nextgen] Could not check the session cookie against ${endpoint} (${cause}). ` +\n \"That is not a refusal: the request counts as signed out, and the cookie is kept.\",\n );\n return { verdict: \"undecided\" };\n}\n\n/**\n * Whether a verified JWT names `projectId` in its `aud` claim, the one place a\n * self-contained token can say which project it was issued for.\n */\nexport function jwtNamesProject(payload: JwtPayload, projectId: string): boolean {\n const audience = payload.aud === undefined ? [] : [payload.aud].flat();\n return projectId !== \"\" && audience.includes(projectId);\n}\n\n// ─── Session resolution ──────────────────────────────────────────────────────\n\n/**\n * What a request carries that decides its session. The request URL and the\n * forwarded headers decide whether a cookie needs `Secure`.\n */\nexport interface SessionRequest extends BrowserRequest {\n /**\n * The `Authorization: Bearer` token, or `null`. It takes precedence: when it\n * signs a user of this project in, the cookie is not consulted.\n */\n readonly bearer: string | null;\n /** Reads one of the request's cookies by name. */\n readonly cookie: (name: string) => string | null | undefined;\n}\n\n/** The settings {@link resolveSession} judges a request's session by. */\nexport interface ResolveSessionOptions {\n readonly url: string;\n /** The project this app signs its users in to; see {@link NextgenMiddlewareOptions.projectId}. */\n readonly projectId: string | undefined;\n readonly allowedAlgorithms: readonly string[] | undefined;\n readonly clockSkewMs: number;\n readonly audience: string | readonly string[] | undefined;\n readonly allowedTokenTypes: readonly string[];\n readonly jwksTimeoutMs: number | undefined;\n readonly opaqueTokenTimeoutMs: number;\n}\n\n/** A request's session, as {@link resolveSession} judged it. */\nexport interface ResolvedSession {\n readonly auth: AuthResult;\n /**\n * Whether the backend gave no verdict on the session cookie\n * ({@link OpaqueSessionCheck}). The request counts as signed out and the\n * cookie is kept; a protected route answers `503` rather than send a user\n * who may well be signed in to the login page.\n */\n readonly undecided: boolean;\n /**\n * The session cookies the response sets, with {@link SESSION_COOKIE_CACHE_CONTROL}\n * when there are any. A cookie the backend refused is cleared so the browser\n * stops sending it; a session of this project found in the server's cookie is\n * copied to the project's own, and the server's cookie keeps it, since the\n * Console may share the project. No other cookie is\n * touched: a session that is live but signs no one in here (an anonymous\n * one, or one of another project) keeps its cookie, and so does one the\n * backend gave no verdict on, and one behind a Bearer token that signed the\n * request in, which was never judged.\n */\n readonly cookies: readonly SessionCookie[];\n}\n\nfunction signedOut(): UnauthState {\n return { isAuthenticated: false, session: null };\n}\n\nlet warnedMissingProject = false;\n\n/** Says once per process why no session counts: no project is configured. */\nfunction warnMissingProject(): void {\n if (warnedMissingProject) {\n return;\n }\n warnedMissingProject = true;\n console.error(\n \"[nextgen] No project id is configured, so no session counts as signed in. \" +\n \"Set ZITADEL_PROJECT_ID (or the `projectId` option) to the project this app signs its users in to.\",\n );\n}\n\n/**\n * The cookie a request's session is read from: the project's own\n * ({@link sessionCookieName}), else the server's `__nextgen_session`, where\n * this app kept its sessions before ADR 073 and where, on `localhost`, the\n * Console keeps its own.\n */\nfunction storedSession(\n cookie: SessionRequest[\"cookie\"],\n projectId: string,\n): { readonly name: string; readonly token: string } | null {\n for (const name of [sessionCookieName(projectId), SERVER_SESSION_COOKIE]) {\n const token = cookie(name);\n if (token) {\n return { name, token };\n }\n }\n return null;\n}\n\n/**\n * What {@link judgeJwt} makes of a JWT: who it signs in, whether a cookie\n * holding it is dead, and how long it lives.\n */\ninterface JwtVerdict {\n readonly auth: AuthResult;\n /** Whether a cookie holding the JWT is dead, so the browser can drop it. */\n readonly dead: boolean;\n /** Seconds until a JWT that signs its user in expires, if it says. */\n readonly maxAge: number | undefined;\n}\n\n/**\n * Judges a JWT. It signs its `sub` in when it verifies and its `aud` names\n * this project. Held in a cookie, it is dead when it fails verification or\n * names no user of this project; a verified JWT of another project is live\n * there, so its cookie stays.\n */\nasync function judgeJwt(\n token: string,\n projectId: string,\n options: ResolveSessionOptions,\n): Promise<JwtVerdict> {\n const payload = isJwtShaped(token)\n ? await verifyJwt(token, {\n issuerUrl: options.url,\n allowedAlgorithms: options.allowedAlgorithms,\n clockSkewMs: options.clockSkewMs,\n audience: options.audience,\n allowedTokenTypes: options.allowedTokenTypes,\n jwksTimeoutMs: options.jwksTimeoutMs,\n })\n : null;\n if (!payload) {\n return { auth: signedOut(), dead: true, maxAge: undefined };\n }\n const namesProject = jwtNamesProject(payload, projectId);\n if (!payload.sub || !namesProject) {\n return { auth: signedOut(), dead: namesProject, maxAge: undefined };\n }\n return {\n auth: {\n isAuthenticated: true,\n session: {\n userId: payload.sub,\n identifier: payload.email ?? null,\n identifierProperty: null,\n display: payload.name ?? null,\n token,\n },\n },\n dead: false,\n maxAge: payload.exp === undefined ? undefined : secondsUntil(payload.exp * 1000),\n };\n}\n\n/**\n * Judges which user, if any, a request is signed in as in this app's project.\n * The one implementation behind every server SDK's middleware, so they cannot\n * drift on who counts as signed in.\n *\n * - A Bearer token is judged first, as a JWT. When it signs a user of this\n * project in, that is the answer and the cookie is not judged; otherwise the\n * cookie speaks for itself.\n * - The session cookie is the project's own, else the server's. A session of\n * this project found in the server's cookie is copied to the project's own.\n * - A JWT-shaped session cookie signs its `sub` in when it verifies and its\n * `aud` names the project. It never reaches the backend, which does not\n * re-check JWT claims.\n * - Any other session cookie is checked by the backend\n * ({@link checkOpaqueSessionToken}) and signs its user in when the session\n * is live in this project.\n * - Only a cookie that held a dead session is cleared: one the backend refused,\n * or a JWT that fails verification or names no user of this project. When\n * the backend gives no verdict, the result says so\n * ({@link ResolvedSession.undecided}) and the cookie stays.\n * - Without a project nothing signs in, and no cookie is touched.\n */\nexport async function resolveSession(\n request: SessionRequest,\n options: ResolveSessionOptions,\n): Promise<ResolvedSession> {\n const { url, projectId, opaqueTokenTimeoutMs } = options;\n if (!projectId) {\n warnMissingProject();\n return { auth: signedOut(), undecided: false, cookies: [] };\n }\n\n // A Bearer token and the cookie are separate credentials: one that is not\n // this app's leaves the cookie to be judged on its own.\n if (request.bearer !== null) {\n const bearer = await judgeJwt(request.bearer, projectId, options);\n if (bearer.auth.isAuthenticated) {\n return { auth: bearer.auth, undecided: false, cookies: [] };\n }\n }\n\n const stored = storedSession(request.cookie, projectId);\n if (!stored) {\n return { auth: signedOut(), undecided: false, cookies: [] };\n }\n const secure = secureFor(request);\n // A session of this project in the server's cookie is copied to the\n // project's own once it signs a user in; one already there stays as it is.\n const moved = (maxAge: number | undefined): SessionCookie[] =>\n stored.name === SERVER_SESSION_COOKIE\n ? [copiedToProjectCookie(stored.token, projectId, maxAge, secure)]\n : [];\n const cleared = (): SessionCookie[] => [clearedCookie(stored.name, secure)];\n\n if (isJwtShaped(stored.token)) {\n const judged = await judgeJwt(stored.token, projectId, options);\n if (judged.auth.isAuthenticated) {\n return { auth: judged.auth, undecided: false, cookies: moved(judged.maxAge) };\n }\n return { auth: signedOut(), undecided: false, cookies: judged.dead ? cleared() : [] };\n }\n\n const check = await checkOpaqueSessionToken(stored.token, url, opaqueTokenTimeoutMs, projectId);\n const userId = check.verdict === \"live\" ? check.session.userId : null;\n if (check.verdict === \"live\" && userId) {\n const { session } = check;\n return {\n auth: {\n isAuthenticated: true,\n session: {\n userId,\n identifier: session.identifier,\n identifierProperty: session.identifierProperty,\n display: session.display,\n token: stored.token,\n },\n },\n undecided: false,\n cookies: moved(session.expiresAt ? secondsUntil(Date.parse(session.expiresAt)) : undefined),\n };\n }\n // Only the backend's refusal makes the cookie dead: a live session that\n // signs no one in here may still complete its sign-in or belongs to another\n // app, and a check without a verdict refused nothing.\n return {\n auth: signedOut(),\n undecided: check.verdict === \"undecided\",\n cookies: check.verdict === \"refused\" ? cleared() : [],\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,301 @@
1
+ import { ZitadelProject } from "@zitadel/api/config";
2
+ import { CreateFlow201Step, CreateFlow201StepComplete, CreateFlowBodyPurpose, CreateFlowBodyPurpose as CreateFlowBodyPurpose$1 } from "@zitadel/api/generated/model";
3
+ //#region src/types.d.ts
4
+ /**
5
+ * The SPA widget contract shared by every SPA SDK (`sdk-react`, `sdk-vue`,
6
+ * `sdk-angular`, `sdk-solid`, `sdk-svelte`, `sdk-qwik`): the widget event detail
7
+ * types plus the config/handler/props types derived from them. Each SPA SDK
8
+ * re-exports these from its own public surface so consumers don't need a direct
9
+ * `sdk-core` dependency. The middleware-layer types (`sdk-next`, `sdk-nuxt`) now
10
+ * live in `@zitadel/sdk-core/middleware`.
11
+ */
12
+ /** Payload of the `zitadel-flow-step` event. */
13
+ export interface ZitadelFlowStepDetail {
14
+ readonly step: CreateFlow201Step;
15
+ }
16
+ /** Payload of the `zitadel-flow-input` event. */
17
+ export interface ZitadelFlowInputDetail {
18
+ readonly name: string;
19
+ readonly value: string;
20
+ }
21
+ /** Payload of the `zitadel-flow-complete` event. */
22
+ export interface ZitadelFlowCompleteDetail {
23
+ readonly behavior: CreateFlow201StepComplete;
24
+ readonly redirect_uri?: string;
25
+ readonly handoff_token?: string;
26
+ readonly handoff_token_expires_at?: string;
27
+ }
28
+ /**
29
+ * Payload of the `zitadel-flow-redirect` event — the widget is handing the
30
+ * browser to an identity provider. Distinct from `zitadel-flow-complete`:
31
+ * nobody is signed in yet, and the flow resumes when the provider returns.
32
+ */
33
+ export interface ZitadelFlowRedirectDetail {
34
+ readonly redirect_url: string;
35
+ readonly step: CreateFlow201Step;
36
+ }
37
+ /** Payload of the `zitadel-flow-error` event. */
38
+ export interface ZitadelFlowErrorDetail {
39
+ readonly message: string;
40
+ }
41
+ /** Payload of the `zitadel-signout` event. */
42
+ export interface ZitadelSignoutDetail {
43
+ /** The signed-out user's display rendering ("" when none was resolved). */
44
+ readonly display: string;
45
+ /** The signed-out user's login identifier ("" when none was resolved). */
46
+ readonly identifier: string;
47
+ }
48
+ /** Events emitted by `<zitadel-login>`, keyed to their detail payload. */
49
+ export interface ZitadelLoginEventMap {
50
+ "zitadel-flow-step": ZitadelFlowStepDetail;
51
+ "zitadel-flow-input": ZitadelFlowInputDetail;
52
+ "zitadel-flow-complete": ZitadelFlowCompleteDetail;
53
+ "zitadel-flow-redirect": ZitadelFlowRedirectDetail;
54
+ "zitadel-flow-error": ZitadelFlowErrorDetail;
55
+ }
56
+ /** Events emitted by `<zitadel-logout>`, keyed to their detail payload. */
57
+ export interface ZitadelLogoutEventMap {
58
+ "zitadel-signout": ZitadelSignoutDetail;
59
+ }
60
+ /**
61
+ * Events emitted by `<zitadel-session>`, keyed to their detail payload.
62
+ * `zitadel-signout` reuses {@link ZitadelSignoutDetail} so the post-sign-in
63
+ * card and the avatar menu speak the same sign-out contract.
64
+ */
65
+ export interface ZitadelSessionEventMap {
66
+ "zitadel-signout": ZitadelSignoutDetail;
67
+ }
68
+ /**
69
+ * Maps each `<zitadel-login>` event to the callback prop the SDKs expose. The
70
+ * `satisfies Record<keyof ZitadelLoginEventMap, …>` is the drift lock: every
71
+ * event needs exactly one handler entry — a missing one, or an extra one left
72
+ * behind for a removed event, fails `tsc`. Everything below derives from this.
73
+ */
74
+ export declare const ZITADEL_LOGIN_EVENT_HANDLERS: {
75
+ readonly "zitadel-flow-step": "onFlowStep";
76
+ readonly "zitadel-flow-input": "onFlowInput";
77
+ readonly "zitadel-flow-complete": "onFlowComplete";
78
+ readonly "zitadel-flow-redirect": "onFlowRedirect";
79
+ readonly "zitadel-flow-error": "onFlowError";
80
+ };
81
+ /** Maps the `<zitadel-logout>` event to the callback prop the SDKs expose. */
82
+ export declare const ZITADEL_LOGOUT_EVENT_HANDLERS: {
83
+ readonly "zitadel-signout": "onSignout";
84
+ };
85
+ /** Maps the `<zitadel-session>` event to the callback prop the SDKs expose. */
86
+ export declare const ZITADEL_SESSION_EVENT_HANDLERS: {
87
+ readonly "zitadel-signout": "onSignout";
88
+ };
89
+ /** Runtime list of `<zitadel-login>` event names (drives wiring + drift tests). */
90
+ export declare const ZITADEL_LOGIN_EVENTS: (keyof ZitadelLoginEventMap)[];
91
+ /** Runtime list of `<zitadel-logout>` event names. */
92
+ export declare const ZITADEL_LOGOUT_EVENTS: (keyof ZitadelLogoutEventMap)[];
93
+ /** Runtime list of `<zitadel-session>` event names. */
94
+ export declare const ZITADEL_SESSION_EVENTS: (keyof ZitadelSessionEventMap)[];
95
+ /** Configuration props shared by every `ZitadelLogin` wrapper. */
96
+ export interface ZitadelLoginConfig {
97
+ /** The SDK handle from `configureZitadel(...)`. */
98
+ readonly project?: ZitadelProject;
99
+ /** Discrete project id, read when no {@link project} handle is supplied. */
100
+ readonly projectId?: string;
101
+ /** Reverse-proxy path prefix the widget calls. */
102
+ readonly proxyPath?: string;
103
+ /**
104
+ * Sizing/chrome mode. `widget` (default) is content-sized and paints no
105
+ * page chrome — for embedding inside an existing layout. `page` claims
106
+ * the viewport, paints the surface background, loads the brand font, and
107
+ * focuses the first field — for dedicated login routes.
108
+ * @default "widget"
109
+ */
110
+ readonly variant?: "widget" | "page";
111
+ /**
112
+ * Colour mode. Unset defers to the tenant's `branding.theme.mode`, then to
113
+ * the variant default — `dark` for `page`, `auto` (follow
114
+ * `prefers-color-scheme`) for `widget`. Set it when your app's surface is
115
+ * fixed so the widget doesn't render a dark card on a light page.
116
+ */
117
+ readonly theme?: "light" | "dark" | "auto";
118
+ /**
119
+ * Visually hide the widget's own heading block while keeping it in the
120
+ * accessibility tree — for embeds whose page already carries the heading
121
+ * (a brand-voice title above the card) so the card doesn't repeat it.
122
+ * @default false
123
+ */
124
+ readonly suppressHeader?: boolean;
125
+ /**
126
+ * Hide the show/hide button on password fields. The button is shown by
127
+ * default, as NIST SP 800-63B recommends.
128
+ * @default false
129
+ */
130
+ readonly suppressPasswordToggle?: boolean;
131
+ /** Flow purpose. @default "login" */
132
+ readonly purpose?: CreateFlowBodyPurpose$1;
133
+ /**
134
+ * Selects a specific flow definition by its `name` (the `name` field in
135
+ * the flow file). Omit to run the project's default flow for the purpose.
136
+ */
137
+ readonly flowName?: string;
138
+ /**
139
+ * Where to navigate after a completed sign-in. When the page's `next` query
140
+ * parameter names a path on the same origin, the widget returns there
141
+ * instead: the server SDKs' middleware sends a signed-out request for a
142
+ * protected route to the login page with `?next=<path>`.
143
+ */
144
+ readonly postSignInUrl?: string;
145
+ /**
146
+ * Go to {@link postSignInUrl} even when the page's `next` query parameter
147
+ * names a path, for a page that uses `next` for something of its own.
148
+ * @default false
149
+ */
150
+ readonly forcePostSignInUrl?: boolean;
151
+ /**
152
+ * An existing flow's id to resume instead of starting a new flow, for
153
+ * example one kept across a reload. Unset or `null` falls back to the `flow` query
154
+ * parameter a provider callback brings back; an id that no longer resolves
155
+ * starts a new flow.
156
+ */
157
+ readonly resumeFlowId?: string | null;
158
+ /**
159
+ * Copy overrides merged over the builtin locale dictionaries, keyed by
160
+ * primary language subtag. Each dictionary may be partial — the widget
161
+ * merges it over the builtin copy — so presets like the components'
162
+ * `businessLocales` overlay are directly assignable. This is how custom
163
+ * flow steps and actions get labels (keys follow `<step>.title`,
164
+ * `<step>.action.<name>`, `<step>.field.<field>`):
165
+ *
166
+ * ```tsx
167
+ * <ZitadelLogin locales={{ en: { "identifier.title": "Welcome back" } }} />
168
+ * ```
169
+ */
170
+ readonly locales?: Record<string, Partial<Record<string, string>>>;
171
+ /** BCP-47 language override; defaults to the browser language. */
172
+ readonly lang?: string;
173
+ }
174
+ /** Configuration props shared by every `ZitadelLogout` wrapper. */
175
+ export interface ZitadelLogoutConfig {
176
+ /** The SDK handle from `configureZitadel(...)`. */
177
+ readonly project?: ZitadelProject;
178
+ /** Discrete project id, read when no {@link project} handle is supplied. */
179
+ readonly projectId?: string;
180
+ /** Reverse-proxy path prefix the widget calls. */
181
+ readonly proxyPath?: string;
182
+ /** Where to navigate after sign-out. */
183
+ readonly postSignOutUrl?: string;
184
+ /**
185
+ * Colour mode. Unset defaults to `auto` (follow `prefers-color-scheme`) —
186
+ * the control lives inside the app's own chrome. Set it when the
187
+ * surrounding surface is fixed.
188
+ */
189
+ readonly theme?: "light" | "dark" | "auto";
190
+ }
191
+ /** Configuration props shared by every `ZitadelSession` wrapper. */
192
+ export interface ZitadelSessionConfig {
193
+ /** The SDK handle from `configureZitadel(...)`. */
194
+ readonly project?: ZitadelProject;
195
+ /** Discrete project id, read when no {@link project} handle is supplied. */
196
+ readonly projectId?: string;
197
+ /** Reverse-proxy path prefix the widget calls. */
198
+ readonly proxyPath?: string;
199
+ /** Where to navigate after sign-out. */
200
+ readonly postSignOutUrl?: string;
201
+ /** Heading text override. */
202
+ readonly heading?: string;
203
+ /** Logout action label override. */
204
+ readonly logoutLabel?: string;
205
+ /**
206
+ * Sizing/chrome mode. `widget` (default) is content-sized and paints no
207
+ * page chrome — for embedding the signed-in card inside an existing
208
+ * layout. `page` claims the viewport and paints the surface background —
209
+ * for dedicated signed-in routes.
210
+ * @default "widget"
211
+ */
212
+ readonly variant?: "widget" | "page";
213
+ /**
214
+ * Colour mode. Unset defers to the variant default — `dark` for `page`,
215
+ * `auto` (follow `prefers-color-scheme`) for `widget`. Set it when your
216
+ * app's surface is fixed so the card doesn't render dark on a light page.
217
+ */
218
+ readonly theme?: "light" | "dark" | "auto";
219
+ /**
220
+ * Visually hide the widget's own heading block while keeping it in the
221
+ * accessibility tree — for embeds whose page already carries the heading
222
+ * (a brand-voice title above the card) so the card doesn't repeat it.
223
+ * @default false
224
+ */
225
+ readonly suppressHeader?: boolean;
226
+ }
227
+ /**
228
+ * Plain-callback handlers for the `<zitadel-login>` events, derived from
229
+ * {@link ZitadelLoginEventMap} and {@link ZITADEL_LOGIN_EVENT_HANDLERS} so the
230
+ * prop set can never drift from the events it forwards.
231
+ */
232
+ export type ZitadelLoginHandlers = { readonly [E in keyof ZitadelLoginEventMap as (typeof ZITADEL_LOGIN_EVENT_HANDLERS)[E]]?: (detail: ZitadelLoginEventMap[E]) => void; };
233
+ /** Plain-callback handler for the `<zitadel-logout>` event (derived, as above). */
234
+ export type ZitadelLogoutHandlers = { readonly [E in keyof ZitadelLogoutEventMap as (typeof ZITADEL_LOGOUT_EVENT_HANDLERS)[E]]?: (detail: ZitadelLogoutEventMap[E]) => void; };
235
+ /** Plain-callback handlers for the `<zitadel-session>` events (derived, as above). */
236
+ export type ZitadelSessionHandlers = { readonly [E in keyof ZitadelSessionEventMap as (typeof ZITADEL_SESSION_EVENT_HANDLERS)[E]]?: (detail: ZitadelSessionEventMap[E]) => void; };
237
+ /** Full prop set for a `ZitadelLogin` wrapper (config + plain-callback handlers). */
238
+ export type ZitadelLoginProps = ZitadelLoginConfig & ZitadelLoginHandlers;
239
+ /** Full prop set for a `ZitadelLogout` wrapper (config + plain-callback handlers). */
240
+ export type ZitadelLogoutProps = ZitadelLogoutConfig & ZitadelLogoutHandlers;
241
+ /** Full prop set for a `ZitadelSession` wrapper (config + plain-callback handlers). */
242
+ export type ZitadelSessionProps = ZitadelSessionConfig & ZitadelSessionHandlers;
243
+ /** The names every framework applies to an element itself, so none of them takes a prefix. */
244
+ declare const NATIVE_NAMES: readonly ["class", "className", "style"];
245
+ type NativeName = (typeof NATIVE_NAMES)[number];
246
+ declare const STANDARD_NAMES: readonly ["class", "className", "style", "id", "title", "slot", "hidden", "role", "tabindex", "tabIndex"];
247
+ /**
248
+ * The name of a standard HTML attribute: the fixed ones (`className` and
249
+ * `tabIndex` are the spellings React and Qwik use), `data-*` and `aria-*`.
250
+ * Matching is exact and case-sensitive.
251
+ */
252
+ export type StandardAttributeName = (typeof STANDARD_NAMES)[number] | `data-${string}` | `aria-${string}`;
253
+ /**
254
+ * The standard attributes among the props `Props`, each name with `Prefix`
255
+ * before it, except the native names. Every value keeps the type it has in
256
+ * `Props`.
257
+ */
258
+ export type StandardAttributes<Props extends object, Prefix extends string = ""> = { [Name in keyof Props & StandardAttributeName as Name extends NativeName ? Name : `${Prefix}${Name}`]?: Props[Name]; };
259
+ /**
260
+ * What a wrapper's props type adds to the widget's own: the standard attributes
261
+ * of the framework's element attributes type `Attributes`, and nothing else, so
262
+ * no prop the type accepts is dropped.
263
+ */
264
+ export type StandardAttributeProps<Attributes extends object> = Pick<Attributes, Extract<keyof Attributes, StandardAttributeName>>;
265
+ /**
266
+ * Whether `name` is the name of a standard HTML attribute (see
267
+ * {@link StandardAttributeName}). A wrapper that needs to keep such a name away
268
+ * from a property binding of its own asks this rather than listing the names.
269
+ */
270
+ export declare function isStandardAttributeName(name: string): name is StandardAttributeName;
271
+ /**
272
+ * The standard HTML attributes among the given props: `class`, `style`, `id`,
273
+ * `title`, `slot`, `hidden`, `role`, `tabindex`, `data-*` and `aria-*`. A widget
274
+ * element takes these as plain DOM attributes, so a wrapper passes the result to
275
+ * the element. Anything else is left out, so no stray prop can reach the
276
+ * widget's configuration. A value of `undefined` or `null` is left out, and so
277
+ * is `false` except for `aria-*` and `data-*`, which render as the string
278
+ * `"false"`. A framework that needs the names marked (Solid writes `attr:id` as
279
+ * an attribute) passes the `prefix` to put before each name except the native
280
+ * ones (`class`, `className` and `style`).
281
+ */
282
+ export declare function standardAttributes<Props extends object, Prefix extends string = "">(props: Props, prefix?: Prefix): StandardAttributes<Props, Prefix>;
283
+ /** Every widget name, for code that loops over the widgets. */
284
+ export declare const widgetNames: readonly ["login", "logout", "session"];
285
+ /** The name of a widget. */
286
+ export type WidgetName = (typeof widgetNames)[number];
287
+ /**
288
+ * The tag names of the widget elements. The elements register under these
289
+ * names, and every SDK wrapper, spec and guard reads them from here rather than
290
+ * spelling them out. The type ties each tag to its widget name.
291
+ */
292
+ export declare const widgetTags: {
293
+ readonly login: "zitadel-login";
294
+ readonly logout: "zitadel-logout";
295
+ readonly session: "zitadel-session";
296
+ };
297
+ /** One of the widget element tag names. */
298
+ export type WidgetTag = (typeof widgetTags)[WidgetName];
299
+ //#endregion
300
+ export type { CreateFlowBodyPurpose };
301
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":["CreateFlowBodyPurpose"],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;iBAoBiB;WACN,MAAM;;;iBAIA;WACN;WACA;;;iBAIM;WACN,UAAU;WACV;WACA;WACA;;;;;;;iBAQM;WACN;WACA,MAAM;;;iBAIA;WACN;;;iBAIM;;WAEN;;WAEA;;;iBAcM;EACf,qBAAqB;EACrB,sBAAsB;EACtB,yBAAyB;EACzB,yBAAyB;EACzB,sBAAsB;;;iBAIP;EACf,mBAAmB;;;;;;;iBAQJ;EACf,mBAAmB;;;;;;;;qBASR;;;;;;;;qBASA;WAAA;;;qBAKA;WAAA;;;qBAKA,6BAED;;qBAGC,8BAED;;qBAGC,+BAED;;iBAGK;;WAEN,UAAU;;WAEV;;WAEA;;;;;;;;WAQA;;;;;;;WAOA;;;;;;;WAOA;;;;;;WAMA;;WAEA,UAAUA;;;;;WAKV;;;;;;;WAOA;;;;;;WAMA;;;;;;;WAOA;;;;;;;;;;;;;WAaA,UAAU,eAAe,QAAQ;;WAEjC;;;iBAIM;;WAEN,UAAU;;WAEV;;WAEA;;WAEA;;;;;;WAMA;;;iBAIM;;WAEN,UAAU;;WAEV;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;WAQA;;;;;;WAMA;;;;;;;WAOA;;;;;;;YAQC,mCACA,WAAW,gCAAgC,8BAA8B,OACjF,QAAQ,qBAAqB;;YAKrB,oCACA,WAAW,iCAAiC,+BAA+B,OACnF,QAAQ,sBAAsB;;YAKtB,qCACA,WAAW,kCAAkC,gCAAgC,OACrF,QAAQ,uBAAuB;;YAKvB,oBAAoB,qBAAqB;;YAGzC,qBAAqB,sBAAsB;;YAG3C,sBAAsB,uBAAuB;;cAWnD;KACD,qBAAqB;cAEpB;;;;;;YAgBM,gCACA;;;;;;YASA,mBAAmB,sBAAsB,iCAClD,cAAc,QAAQ,yBAAyB,aAAa,aACzD,UACG,SAAS,UAAU,MAAM;;;;;;YAQtB,uBAAuB,6BAA6B,KAC9D,YACA,cAAc,YAAY;;;;;;wBAQZ,wBAAwB,eAAe,QAAQ;;;;;;;;;;;;wBA0B/C,mBAAmB,sBAAsB,4BACvD,OAAO,OACP,SAAS,SACR,mBAAmB,OAAO;;qBAoBhB;;YAGD,qBAAqB;;;;;;qBAOpB;;;;;;YAOD,oBAAoB,YAAY"}
package/dist/types.mjs ADDED
@@ -0,0 +1,78 @@
1
+ //#region src/types.ts
2
+ /**
3
+ * Maps each `<zitadel-login>` event to the callback prop the SDKs expose. The
4
+ * `satisfies Record<keyof ZitadelLoginEventMap, …>` is the drift lock: every
5
+ * event needs exactly one handler entry — a missing one, or an extra one left
6
+ * behind for a removed event, fails `tsc`. Everything below derives from this.
7
+ */
8
+ const ZITADEL_LOGIN_EVENT_HANDLERS = {
9
+ "zitadel-flow-step": "onFlowStep",
10
+ "zitadel-flow-input": "onFlowInput",
11
+ "zitadel-flow-complete": "onFlowComplete",
12
+ "zitadel-flow-redirect": "onFlowRedirect",
13
+ "zitadel-flow-error": "onFlowError"
14
+ };
15
+ /** Maps the `<zitadel-logout>` event to the callback prop the SDKs expose. */
16
+ const ZITADEL_LOGOUT_EVENT_HANDLERS = { "zitadel-signout": "onSignout" };
17
+ /** Maps the `<zitadel-session>` event to the callback prop the SDKs expose. */
18
+ const ZITADEL_SESSION_EVENT_HANDLERS = { "zitadel-signout": "onSignout" };
19
+ /** Runtime list of `<zitadel-login>` event names (drives wiring + drift tests). */
20
+ const ZITADEL_LOGIN_EVENTS = Object.keys(ZITADEL_LOGIN_EVENT_HANDLERS);
21
+ /** Runtime list of `<zitadel-logout>` event names. */
22
+ const ZITADEL_LOGOUT_EVENTS = Object.keys(ZITADEL_LOGOUT_EVENT_HANDLERS);
23
+ /** Runtime list of `<zitadel-session>` event names. */
24
+ const ZITADEL_SESSION_EVENTS = Object.keys(ZITADEL_SESSION_EVENT_HANDLERS);
25
+ /** The names every framework applies to an element itself, so none of them takes a prefix. */
26
+ const NATIVE_NAMES = [
27
+ "class",
28
+ "className",
29
+ "style"
30
+ ];
31
+ const STANDARD_NAMES = [
32
+ ...NATIVE_NAMES,
33
+ "id",
34
+ "title",
35
+ "slot",
36
+ "hidden",
37
+ "role",
38
+ "tabindex",
39
+ "tabIndex"
40
+ ];
41
+ /**
42
+ * Whether `name` is the name of a standard HTML attribute (see
43
+ * {@link StandardAttributeName}). A wrapper that needs to keep such a name away
44
+ * from a property binding of its own asks this rather than listing the names.
45
+ */
46
+ function isStandardAttributeName(name) {
47
+ return STANDARD_NAMES.some((standard) => standard === name) || name.startsWith("data-") && name.length > 5 || name.startsWith("aria-") && name.length > 5;
48
+ }
49
+ const isEnumerated = (name) => name.startsWith("aria-") || name.startsWith("data-");
50
+ const isOmitted = (name, value) => value == null || value === false && !isEnumerated(name);
51
+ function standardAttributes(props, prefix = "") {
52
+ const record = {};
53
+ for (const [name, value] of Object.entries(props)) if (isStandardAttributeName(name) && !isOmitted(name, value)) {
54
+ const native = NATIVE_NAMES.some((nativeName) => nativeName === name);
55
+ record[native ? name : `${prefix}${name}`] = typeof value === "boolean" && isEnumerated(name) ? String(value) : value;
56
+ }
57
+ return record;
58
+ }
59
+ /** Every widget name, for code that loops over the widgets. */
60
+ const widgetNames = [
61
+ "login",
62
+ "logout",
63
+ "session"
64
+ ];
65
+ /**
66
+ * The tag names of the widget elements. The elements register under these
67
+ * names, and every SDK wrapper, spec and guard reads them from here rather than
68
+ * spelling them out. The type ties each tag to its widget name.
69
+ */
70
+ const widgetTags = {
71
+ login: "zitadel-login",
72
+ logout: "zitadel-logout",
73
+ session: "zitadel-session"
74
+ };
75
+ //#endregion
76
+ export { ZITADEL_LOGIN_EVENTS, ZITADEL_LOGIN_EVENT_HANDLERS, ZITADEL_LOGOUT_EVENTS, ZITADEL_LOGOUT_EVENT_HANDLERS, ZITADEL_SESSION_EVENTS, ZITADEL_SESSION_EVENT_HANDLERS, isStandardAttributeName, standardAttributes, widgetNames, widgetTags };
77
+
78
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { ZitadelProject } from \"@zitadel/api/config\";\nimport type {\n CreateFlow201Step,\n CreateFlow201StepComplete,\n CreateFlowBodyPurpose,\n} from \"@zitadel/api/generated/model\";\n\n/** Re-exported so consumers can type the `purpose` prop without a deep import. */\nexport type { CreateFlowBodyPurpose } from \"@zitadel/api/generated/model\";\n\n/**\n * The SPA widget contract shared by every SPA SDK (`sdk-react`, `sdk-vue`,\n * `sdk-angular`, `sdk-solid`, `sdk-svelte`, `sdk-qwik`): the widget event detail\n * types plus the config/handler/props types derived from them. Each SPA SDK\n * re-exports these from its own public surface so consumers don't need a direct\n * `sdk-core` dependency. The middleware-layer types (`sdk-next`, `sdk-nuxt`) now\n * live in `@zitadel/sdk-core/middleware`.\n */\n\n/** Payload of the `zitadel-flow-step` event. */\nexport interface ZitadelFlowStepDetail {\n readonly step: CreateFlow201Step;\n}\n\n/** Payload of the `zitadel-flow-input` event. */\nexport interface ZitadelFlowInputDetail {\n readonly name: string;\n readonly value: string;\n}\n\n/** Payload of the `zitadel-flow-complete` event. */\nexport interface ZitadelFlowCompleteDetail {\n readonly behavior: CreateFlow201StepComplete;\n readonly redirect_uri?: string;\n readonly handoff_token?: string;\n readonly handoff_token_expires_at?: string;\n}\n\n/**\n * Payload of the `zitadel-flow-redirect` event — the widget is handing the\n * browser to an identity provider. Distinct from `zitadel-flow-complete`:\n * nobody is signed in yet, and the flow resumes when the provider returns.\n */\nexport interface ZitadelFlowRedirectDetail {\n readonly redirect_url: string;\n readonly step: CreateFlow201Step;\n}\n\n/** Payload of the `zitadel-flow-error` event. */\nexport interface ZitadelFlowErrorDetail {\n readonly message: string;\n}\n\n/** Payload of the `zitadel-signout` event. */\nexport interface ZitadelSignoutDetail {\n /** The signed-out user's display rendering (\"\" when none was resolved). */\n readonly display: string;\n /** The signed-out user's login identifier (\"\" when none was resolved). */\n readonly identifier: string;\n}\n\n/* ─────────────────────────── SPA widget contract ───────────────────────\n * Single source of truth shared by every SPA SDK (react, vue, angular, solid,\n * svelte, qwik). The event maps below declare which widget events exist and\n * what each carries; the config/handler/props types and the runtime name lists\n * are all derived from them. Adding or removing an event here is the ONE edit\n * that ripples to every SDK — the compile-time assertions in this file, the\n * `@zitadel/components` emit-conformance test, and each SDK's contract-driven\n * forwarding test all fail until every wrapper is brought back into line.\n * ───────────────────────────────────────────────────────────────────────── */\n\n/** Events emitted by `<zitadel-login>`, keyed to their detail payload. */\nexport interface ZitadelLoginEventMap {\n \"zitadel-flow-step\": ZitadelFlowStepDetail;\n \"zitadel-flow-input\": ZitadelFlowInputDetail;\n \"zitadel-flow-complete\": ZitadelFlowCompleteDetail;\n \"zitadel-flow-redirect\": ZitadelFlowRedirectDetail;\n \"zitadel-flow-error\": ZitadelFlowErrorDetail;\n}\n\n/** Events emitted by `<zitadel-logout>`, keyed to their detail payload. */\nexport interface ZitadelLogoutEventMap {\n \"zitadel-signout\": ZitadelSignoutDetail;\n}\n\n/**\n * Events emitted by `<zitadel-session>`, keyed to their detail payload.\n * `zitadel-signout` reuses {@link ZitadelSignoutDetail} so the post-sign-in\n * card and the avatar menu speak the same sign-out contract.\n */\nexport interface ZitadelSessionEventMap {\n \"zitadel-signout\": ZitadelSignoutDetail;\n}\n\n/**\n * Maps each `<zitadel-login>` event to the callback prop the SDKs expose. The\n * `satisfies Record<keyof ZitadelLoginEventMap, …>` is the drift lock: every\n * event needs exactly one handler entry — a missing one, or an extra one left\n * behind for a removed event, fails `tsc`. Everything below derives from this.\n */\nexport const ZITADEL_LOGIN_EVENT_HANDLERS = {\n \"zitadel-flow-step\": \"onFlowStep\",\n \"zitadel-flow-input\": \"onFlowInput\",\n \"zitadel-flow-complete\": \"onFlowComplete\",\n \"zitadel-flow-redirect\": \"onFlowRedirect\",\n \"zitadel-flow-error\": \"onFlowError\",\n} as const satisfies Record<keyof ZitadelLoginEventMap, `on${string}`>;\n\n/** Maps the `<zitadel-logout>` event to the callback prop the SDKs expose. */\nexport const ZITADEL_LOGOUT_EVENT_HANDLERS = {\n \"zitadel-signout\": \"onSignout\",\n} as const satisfies Record<keyof ZitadelLogoutEventMap, `on${string}`>;\n\n/** Maps the `<zitadel-session>` event to the callback prop the SDKs expose. */\nexport const ZITADEL_SESSION_EVENT_HANDLERS = {\n \"zitadel-signout\": \"onSignout\",\n} as const satisfies Record<keyof ZitadelSessionEventMap, `on${string}`>;\n\n/** Runtime list of `<zitadel-login>` event names (drives wiring + drift tests). */\nexport const ZITADEL_LOGIN_EVENTS = Object.keys(\n ZITADEL_LOGIN_EVENT_HANDLERS,\n) as (keyof ZitadelLoginEventMap)[];\n\n/** Runtime list of `<zitadel-logout>` event names. */\nexport const ZITADEL_LOGOUT_EVENTS = Object.keys(\n ZITADEL_LOGOUT_EVENT_HANDLERS,\n) as (keyof ZitadelLogoutEventMap)[];\n\n/** Runtime list of `<zitadel-session>` event names. */\nexport const ZITADEL_SESSION_EVENTS = Object.keys(\n ZITADEL_SESSION_EVENT_HANDLERS,\n) as (keyof ZitadelSessionEventMap)[];\n\n/** Configuration props shared by every `ZitadelLogin` wrapper. */\nexport interface ZitadelLoginConfig {\n /** The SDK handle from `configureZitadel(...)`. */\n readonly project?: ZitadelProject;\n /** Discrete project id, read when no {@link project} handle is supplied. */\n readonly projectId?: string;\n /** Reverse-proxy path prefix the widget calls. */\n readonly proxyPath?: string;\n /**\n * Sizing/chrome mode. `widget` (default) is content-sized and paints no\n * page chrome — for embedding inside an existing layout. `page` claims\n * the viewport, paints the surface background, loads the brand font, and\n * focuses the first field — for dedicated login routes.\n * @default \"widget\"\n */\n readonly variant?: \"widget\" | \"page\";\n /**\n * Colour mode. Unset defers to the tenant's `branding.theme.mode`, then to\n * the variant default — `dark` for `page`, `auto` (follow\n * `prefers-color-scheme`) for `widget`. Set it when your app's surface is\n * fixed so the widget doesn't render a dark card on a light page.\n */\n readonly theme?: \"light\" | \"dark\" | \"auto\";\n /**\n * Visually hide the widget's own heading block while keeping it in the\n * accessibility tree — for embeds whose page already carries the heading\n * (a brand-voice title above the card) so the card doesn't repeat it.\n * @default false\n */\n readonly suppressHeader?: boolean;\n /**\n * Hide the show/hide button on password fields. The button is shown by\n * default, as NIST SP 800-63B recommends.\n * @default false\n */\n readonly suppressPasswordToggle?: boolean;\n /** Flow purpose. @default \"login\" */\n readonly purpose?: CreateFlowBodyPurpose;\n /**\n * Selects a specific flow definition by its `name` (the `name` field in\n * the flow file). Omit to run the project's default flow for the purpose.\n */\n readonly flowName?: string;\n /**\n * Where to navigate after a completed sign-in. When the page's `next` query\n * parameter names a path on the same origin, the widget returns there\n * instead: the server SDKs' middleware sends a signed-out request for a\n * protected route to the login page with `?next=<path>`.\n */\n readonly postSignInUrl?: string;\n /**\n * Go to {@link postSignInUrl} even when the page's `next` query parameter\n * names a path, for a page that uses `next` for something of its own.\n * @default false\n */\n readonly forcePostSignInUrl?: boolean;\n /**\n * An existing flow's id to resume instead of starting a new flow, for\n * example one kept across a reload. Unset or `null` falls back to the `flow` query\n * parameter a provider callback brings back; an id that no longer resolves\n * starts a new flow.\n */\n readonly resumeFlowId?: string | null;\n /**\n * Copy overrides merged over the builtin locale dictionaries, keyed by\n * primary language subtag. Each dictionary may be partial — the widget\n * merges it over the builtin copy — so presets like the components'\n * `businessLocales` overlay are directly assignable. This is how custom\n * flow steps and actions get labels (keys follow `<step>.title`,\n * `<step>.action.<name>`, `<step>.field.<field>`):\n *\n * ```tsx\n * <ZitadelLogin locales={{ en: { \"identifier.title\": \"Welcome back\" } }} />\n * ```\n */\n readonly locales?: Record<string, Partial<Record<string, string>>>;\n /** BCP-47 language override; defaults to the browser language. */\n readonly lang?: string;\n}\n\n/** Configuration props shared by every `ZitadelLogout` wrapper. */\nexport interface ZitadelLogoutConfig {\n /** The SDK handle from `configureZitadel(...)`. */\n readonly project?: ZitadelProject;\n /** Discrete project id, read when no {@link project} handle is supplied. */\n readonly projectId?: string;\n /** Reverse-proxy path prefix the widget calls. */\n readonly proxyPath?: string;\n /** Where to navigate after sign-out. */\n readonly postSignOutUrl?: string;\n /**\n * Colour mode. Unset defaults to `auto` (follow `prefers-color-scheme`) —\n * the control lives inside the app's own chrome. Set it when the\n * surrounding surface is fixed.\n */\n readonly theme?: \"light\" | \"dark\" | \"auto\";\n}\n\n/** Configuration props shared by every `ZitadelSession` wrapper. */\nexport interface ZitadelSessionConfig {\n /** The SDK handle from `configureZitadel(...)`. */\n readonly project?: ZitadelProject;\n /** Discrete project id, read when no {@link project} handle is supplied. */\n readonly projectId?: string;\n /** Reverse-proxy path prefix the widget calls. */\n readonly proxyPath?: string;\n /** Where to navigate after sign-out. */\n readonly postSignOutUrl?: string;\n /** Heading text override. */\n readonly heading?: string;\n /** Logout action label override. */\n readonly logoutLabel?: string;\n /**\n * Sizing/chrome mode. `widget` (default) is content-sized and paints no\n * page chrome — for embedding the signed-in card inside an existing\n * layout. `page` claims the viewport and paints the surface background —\n * for dedicated signed-in routes.\n * @default \"widget\"\n */\n readonly variant?: \"widget\" | \"page\";\n /**\n * Colour mode. Unset defers to the variant default — `dark` for `page`,\n * `auto` (follow `prefers-color-scheme`) for `widget`. Set it when your\n * app's surface is fixed so the card doesn't render dark on a light page.\n */\n readonly theme?: \"light\" | \"dark\" | \"auto\";\n /**\n * Visually hide the widget's own heading block while keeping it in the\n * accessibility tree — for embeds whose page already carries the heading\n * (a brand-voice title above the card) so the card doesn't repeat it.\n * @default false\n */\n readonly suppressHeader?: boolean;\n}\n\n/**\n * Plain-callback handlers for the `<zitadel-login>` events, derived from\n * {@link ZitadelLoginEventMap} and {@link ZITADEL_LOGIN_EVENT_HANDLERS} so the\n * prop set can never drift from the events it forwards.\n */\nexport type ZitadelLoginHandlers = {\n readonly [E in keyof ZitadelLoginEventMap as (typeof ZITADEL_LOGIN_EVENT_HANDLERS)[E]]?: (\n detail: ZitadelLoginEventMap[E],\n ) => void;\n};\n\n/** Plain-callback handler for the `<zitadel-logout>` event (derived, as above). */\nexport type ZitadelLogoutHandlers = {\n readonly [E in keyof ZitadelLogoutEventMap as (typeof ZITADEL_LOGOUT_EVENT_HANDLERS)[E]]?: (\n detail: ZitadelLogoutEventMap[E],\n ) => void;\n};\n\n/** Plain-callback handlers for the `<zitadel-session>` events (derived, as above). */\nexport type ZitadelSessionHandlers = {\n readonly [E in keyof ZitadelSessionEventMap as (typeof ZITADEL_SESSION_EVENT_HANDLERS)[E]]?: (\n detail: ZitadelSessionEventMap[E],\n ) => void;\n};\n\n/** Full prop set for a `ZitadelLogin` wrapper (config + plain-callback handlers). */\nexport type ZitadelLoginProps = ZitadelLoginConfig & ZitadelLoginHandlers;\n\n/** Full prop set for a `ZitadelLogout` wrapper (config + plain-callback handlers). */\nexport type ZitadelLogoutProps = ZitadelLogoutConfig & ZitadelLogoutHandlers;\n\n/** Full prop set for a `ZitadelSession` wrapper (config + plain-callback handlers). */\nexport type ZitadelSessionProps = ZitadelSessionConfig & ZitadelSessionHandlers;\n\n/* ─────────────────────── Standard element attributes ───────────────────────\n * The one definition of which props a wrapper hands to the widget element\n * besides its own, shared by every SPA SDK except Angular (whose component tag\n * already carries them). The set and every widget prop are disjoint, which the\n * contract spec checks, so a wrapper can pass the result to the element without\n * a widget prop ever being shadowed.\n * ───────────────────────────────────────────────────────────────────────── */\n\n/** The names every framework applies to an element itself, so none of them takes a prefix. */\nconst NATIVE_NAMES = [\"class\", \"className\", \"style\"] as const;\ntype NativeName = (typeof NATIVE_NAMES)[number];\n\nconst STANDARD_NAMES = [\n ...NATIVE_NAMES,\n \"id\",\n \"title\",\n \"slot\",\n \"hidden\",\n \"role\",\n \"tabindex\",\n \"tabIndex\",\n] as const;\n\n/**\n * The name of a standard HTML attribute: the fixed ones (`className` and\n * `tabIndex` are the spellings React and Qwik use), `data-*` and `aria-*`.\n * Matching is exact and case-sensitive.\n */\nexport type StandardAttributeName =\n | (typeof STANDARD_NAMES)[number]\n | `data-${string}`\n | `aria-${string}`;\n\n/**\n * The standard attributes among the props `Props`, each name with `Prefix`\n * before it, except the native names. Every value keeps the type it has in\n * `Props`.\n */\nexport type StandardAttributes<Props extends object, Prefix extends string = \"\"> = {\n [Name in keyof Props & StandardAttributeName as Name extends NativeName\n ? Name\n : `${Prefix}${Name}`]?: Props[Name];\n};\n\n/**\n * What a wrapper's props type adds to the widget's own: the standard attributes\n * of the framework's element attributes type `Attributes`, and nothing else, so\n * no prop the type accepts is dropped.\n */\nexport type StandardAttributeProps<Attributes extends object> = Pick<\n Attributes,\n Extract<keyof Attributes, StandardAttributeName>\n>;\n\n/**\n * Whether `name` is the name of a standard HTML attribute (see\n * {@link StandardAttributeName}). A wrapper that needs to keep such a name away\n * from a property binding of its own asks this rather than listing the names.\n */\nexport function isStandardAttributeName(name: string): name is StandardAttributeName {\n return (\n STANDARD_NAMES.some((standard) => standard === name) ||\n (name.startsWith(\"data-\") && name.length > \"data-\".length) ||\n (name.startsWith(\"aria-\") && name.length > \"aria-\".length)\n );\n}\n\nconst isEnumerated = (name: string): boolean =>\n name.startsWith(\"aria-\") || name.startsWith(\"data-\");\n\n// Only a boolean attribute is absent when false; `aria-hidden=\"false\"` and `data-open=\"false\"` mean something.\nconst isOmitted = (name: string, value: unknown): boolean =>\n value == null || (value === false && !isEnumerated(name));\n\n/**\n * The standard HTML attributes among the given props: `class`, `style`, `id`,\n * `title`, `slot`, `hidden`, `role`, `tabindex`, `data-*` and `aria-*`. A widget\n * element takes these as plain DOM attributes, so a wrapper passes the result to\n * the element. Anything else is left out, so no stray prop can reach the\n * widget's configuration. A value of `undefined` or `null` is left out, and so\n * is `false` except for `aria-*` and `data-*`, which render as the string\n * `\"false\"`. A framework that needs the names marked (Solid writes `attr:id` as\n * an attribute) passes the `prefix` to put before each name except the native\n * ones (`class`, `className` and `style`).\n */\nexport function standardAttributes<Props extends object, Prefix extends string = \"\">(\n props: Props,\n prefix?: Prefix,\n): StandardAttributes<Props, Prefix>;\n// The one place the record is typed loosely: the overload above is the type callers get.\nexport function standardAttributes(props: object, prefix = \"\"): Record<string, unknown> {\n const record: Record<string, unknown> = {};\n for (const [name, value] of Object.entries(props)) {\n if (isStandardAttributeName(name) && !isOmitted(name, value)) {\n const native = NATIVE_NAMES.some((nativeName) => nativeName === name);\n // An `aria-*` or `data-*` boolean is written out, since not every framework renders `false`.\n record[native ? name : `${prefix}${name}`] =\n typeof value === \"boolean\" && isEnumerated(name) ? String(value) : value;\n }\n }\n return record;\n}\n\n/* ─────────────────────────────── Widget names ───────────────────────────────\n * The widgets and the tag names of their elements, defined once.\n * ───────────────────────────────────────────────────────────────────────── */\n\n/** Every widget name, for code that loops over the widgets. */\nexport const widgetNames = [\"login\", \"logout\", \"session\"] as const;\n\n/** The name of a widget. */\nexport type WidgetName = (typeof widgetNames)[number];\n\n/**\n * The tag names of the widget elements. The elements register under these\n * names, and every SDK wrapper, spec and guard reads them from here rather than\n * spelling them out. The type ties each tag to its widget name.\n */\nexport const widgetTags = {\n login: \"zitadel-login\",\n logout: \"zitadel-logout\",\n session: \"zitadel-session\",\n} as const satisfies Record<WidgetName, `zitadel-${WidgetName}`>;\n\n/** One of the widget element tag names. */\nexport type WidgetTag = (typeof widgetTags)[WidgetName];\n"],"mappings":";;;;;;;AAoGA,MAAa,+BAA+B;CAC1C,qBAAqB;CACrB,sBAAsB;CACtB,yBAAyB;CACzB,yBAAyB;CACzB,sBAAsB;AACxB;;AAGA,MAAa,gCAAgC,EAC3C,mBAAmB,YACrB;;AAGA,MAAa,iCAAiC,EAC5C,mBAAmB,YACrB;;AAGA,MAAa,uBAAuB,OAAO,KACzC,4BACF;;AAGA,MAAa,wBAAwB,OAAO,KAC1C,6BACF;;AAGA,MAAa,yBAAyB,OAAO,KAC3C,8BACF;;AAoLA,MAAM,eAAe;CAAC;CAAS;CAAa;AAAO;AAGnD,MAAM,iBAAiB;CACrB,GAAG;CACH;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;AAsCA,SAAgB,wBAAwB,MAA6C;CACnF,OACE,eAAe,MAAM,aAAa,aAAa,IAAI,KAClD,KAAK,WAAW,OAAO,KAAK,KAAK,SAAS,KAC1C,KAAK,WAAW,OAAO,KAAK,KAAK,SAAS;AAE/C;AAEA,MAAM,gBAAgB,SACpB,KAAK,WAAW,OAAO,KAAK,KAAK,WAAW,OAAO;AAGrD,MAAM,aAAa,MAAc,UAC/B,SAAS,QAAS,UAAU,SAAS,CAAC,aAAa,IAAI;AAkBzD,SAAgB,mBAAmB,OAAe,SAAS,IAA6B;CACtF,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAC9C,IAAI,wBAAwB,IAAI,KAAK,CAAC,UAAU,MAAM,KAAK,GAAG;EAC5D,MAAM,SAAS,aAAa,MAAM,eAAe,eAAe,IAAI;EAEpE,OAAO,SAAS,OAAO,GAAG,SAAS,UACjC,OAAO,UAAU,aAAa,aAAa,IAAI,IAAI,OAAO,KAAK,IAAI;CACvE;CAEF,OAAO;AACT;;AAOA,MAAa,cAAc;CAAC;CAAS;CAAU;AAAS;;;;;;AAUxD,MAAa,aAAa;CACxB,OAAO;CACP,QAAQ;CACR,SAAS;AACX"}