@k9o/gate 0.0.0-stage → 1.0.0

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,"sources":["../src/region.ts","../src/client.ts","../src/paths.ts","../src/idp-providers/identity-providers.ts","../src/magic-link-redirects.ts","../src/gate.ts","../src/client-factory.ts"],"sourcesContent":["/**\n * Regional host resolution.\n *\n * The region-free org host `<org>.api[-staging].k9o.io` redirects GET/HEAD only and REFUSES a request that carries a\n * body, an `Authorization` header or a cookie (`400 K9O_INVALID_REQUEST_SHAPE`, `region_free_host_takes_no_credentials`),\n * so credentials must go to `<org>.api[-staging]-<rgn>.k9o.io`. An SDK learns the region with ONE unauthenticated\n * `HEAD https://<org>.api[-staging].k9o.io/`, takes the `308` `Location`, and caches the regional origin.\n *\n * Properties this module holds:\n * - Only a region-free org host (shape `<org>.api.k9o.io` or `<org>.api-staging.k9o.io`) is ever probed. A regional,\n * custom, self-hosted or dev baseUrl is used exactly as given.\n * - The probe carries NO credential and none of the configured default headers (they may hold one).\n * - The `Location` is trusted only if it is `https`, has no userinfo/port, and its host is exactly the SAME org on\n * the SAME apex family with a 4-character region code. Anything else is ignored and the configured baseUrl stays,\n * so a hostile redirect can never receive a credential.\n * - Not a redirect (the platform has not cut over, or the host answers directly), or any failure: the configured\n * baseUrl stays and the server's own refusal, if any, is what the caller sees. A definite answer is cached for the\n * life of the client's config; a network failure is not cached.\n */\n\n/** The slice of a client config this module needs (structural, so core and gate share one implementation). */\nexport type RegionConfig = { baseUrl: string; fetch?: typeof globalThis.fetch | undefined };\n\nconst REGION_FREE_ORG_HOST = /^([a-z][a-z0-9-]{1,62})\\.(api|api-staging)\\.k9o\\.io$/;\nconst REGION_CODE = /^[a-z]{2}\\d{2}$/;\n\nconst cache = new WeakMap<RegionConfig, Promise<string>>();\n\n/** The regional origin a `Location` may name for `host`, or null if it is not exactly that. Pure. */\nexport function regionalOriginFromLocation(host: string, location: string, base: string): string | null {\n const m = REGION_FREE_ORG_HOST.exec(host);\n if (m === null) return null;\n let u: URL;\n try {\n u = new URL(location, base);\n } catch {\n return null;\n }\n if (u.protocol !== \"https:\" || u.username !== \"\" || u.password !== \"\" || u.port !== \"\") return null;\n const [, org, apex] = m;\n const want = new RegExp(`^${org}\\\\.${apex}-([a-z0-9]+)\\\\.k9o\\\\.io$`);\n const r = want.exec(u.hostname);\n if (r === null || !REGION_CODE.test(r[1]!)) return null;\n return `https://${u.hostname}`;\n}\n\nasync function probe(config: RegionConfig): Promise<{ origin: string; cacheable: boolean }> {\n const fetchImpl = config.fetch ?? globalThis.fetch;\n const configured = config.baseUrl;\n if (typeof fetchImpl !== \"function\") return { origin: configured, cacheable: false };\n let host: string;\n try {\n host = new URL(configured).hostname;\n } catch {\n return { origin: configured, cacheable: true };\n }\n if (!REGION_FREE_ORG_HOST.test(host)) return { origin: configured, cacheable: true };\n try {\n const res = await fetchImpl(`${configured.replace(/\\/+$/, \"\")}/`, {\n method: \"HEAD\",\n redirect: \"manual\",\n headers: { accept: \"*/*\" },\n } as Parameters<typeof fetchImpl>[1]);\n if (res.status === 308) {\n const loc = res.headers.get(\"location\");\n const origin = loc === null ? null : regionalOriginFromLocation(host, loc, configured);\n if (origin !== null) return { origin, cacheable: true };\n }\n return { origin: configured, cacheable: true };\n } catch {\n return { origin: configured, cacheable: false };\n }\n}\n\n/** The origin requests for this client go to: the cached regional origin once learned, else the configured baseUrl. */\nexport function resolveRegionalOrigin(config: RegionConfig): Promise<string> {\n const hit = cache.get(config);\n if (hit !== undefined) return hit;\n const p = probe(config).then((r) => {\n if (!r.cacheable) cache.delete(config);\n return r.origin;\n });\n cache.set(config, p);\n return p;\n}\n","/**\n * HTTP client abstraction for the Gate SDK.\n *\n * Centralises:\n * - URL construction against the configured `baseUrl` + per-call path\n * - the standard ch01 §1 envelope unwrap on success\n * - the standard ch01 §2 `KError` rehydration on failure via `gateErrorFromWire`\n * - mode-routing via the `X-Gate-Mode` header (so the gateway routes\n * consumption to the right billing surface without per-function\n * branching at the SDK)\n *\n * Customers don't reach for this directly; it's the internal client\n * every `gate.*` function consumes. `request`/`requestWith` are\n * module-internal only — neither `index.ts` nor the `package.json`\n * `exports` map re-exports them, so they are not part of the public\n * `@k9o/gate` surface. In-package `gate.*` bindings and the test suite\n * import them from this module directly.\n */\n\nimport { getGateConfig, type GateConfig } from \"./mode.ts\";\nimport { resolveRegionalOrigin } from \"./region.ts\";\nimport { GateError, type KErrorWire, gateErrorFromWire } from \"./errors.ts\";\nimport type { Envelope } from \"./types.ts\";\n\nexport type RequestInit = {\n method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n path: string;\n /** Optional JSON body — serialised by the client. */\n body?: unknown;\n /** Optional per-call bearer token. Falls back to `partnerToken` from config when omitted. */\n bearerToken?: string;\n /** Optional additional headers; merged with the defaults. */\n headers?: Record<string, string>;\n /** Optional query parameters. */\n query?: Record<string, string | number | boolean | undefined>;\n};\n\n/**\n * Issue a request against the Gate API. Returns the unwrapped envelope\n * data on 2xx; throws a `GateError` carrying the closed-taxonomy `code`\n * + structured `context` on non-2xx.\n *\n * Implementation notes:\n * - The function pulls config lazily so `configureGate()` order doesn't\n * have to predate import-time module evaluation.\n * - `fetch` is taken from config when set, else from the global; this\n * keeps the SDK portable across Bun + Node 18+ + browsers + test\n * fixtures that supply a stub.\n * - JSON parsing of non-JSON responses throws — gateway always returns\n * JSON per ch01 §1 + §2, so a parse failure is an upstream defect.\n */\nexport async function request<T>(init: RequestInit): Promise<Envelope<T>> {\n return requestWith<T>(getGateConfig(), init);\n}\n\n/**\n * Issue a request using an explicit config (used by per-instance clients\n * returned from `createGateClient`). Singleton callers reach for\n * `request()` which delegates here against the module-level config.\n */\nexport async function requestWith<T>(\n config: GateConfig,\n init: RequestInit,\n): Promise<Envelope<T>> {\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new GateError({\n code: \"INTERNAL_ERROR\",\n message:\n \"No fetch implementation available — configure {fetch} in configureGate() for non-browser, non-Bun, non-Node-18+ environments\",\n context: {},\n recommendation: null,\n });\n }\n\n const url = buildUrl(await resolveRegionalOrigin(config), init.path, init.query);\n const headers: Record<string, string> = {\n accept: \"application/json\",\n \"x-gate-mode\": config.mode,\n ...(init.headers ?? {}),\n };\n const bearer = init.bearerToken ?? config.partnerToken;\n if (typeof bearer === \"string\" && bearer.length > 0) {\n headers[\"authorization\"] = `Bearer ${bearer}`;\n }\n const requestInit: Record<string, unknown> = {\n method: init.method,\n headers,\n };\n if (init.body !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n requestInit[\"body\"] = JSON.stringify(init.body);\n }\n\n let response: Response;\n try {\n response = await fetchImpl(url, requestInit as Parameters<typeof fetchImpl>[1]);\n } catch (e) {\n throw new GateError({\n code: \"UPSTREAM_UNAVAILABLE\",\n message: `Network error reaching ${url}: ${e instanceof Error ? e.message : String(e)}`,\n context: { url },\n recommendation: null,\n });\n }\n\n let parsed: unknown;\n try {\n parsed = await response.json();\n } catch (e) {\n throw new GateError({\n code: \"INTERNAL_ERROR\",\n message: `Failed to parse JSON response from ${url}: ${e instanceof Error ? e.message : String(e)}`,\n context: { url, status: response.status },\n recommendation: null,\n status: response.status,\n });\n }\n\n if (response.status >= 200 && response.status < 300) {\n if (!isEnvelope<T>(parsed)) {\n throw new GateError({\n code: \"INTERNAL_ERROR\",\n message: `Malformed envelope from ${url}: expected {data, meta}`,\n context: { url, status: response.status },\n recommendation: null,\n status: response.status,\n });\n }\n return parsed;\n }\n\n // Non-2xx: the gateway returns a KError-shaped JSON payload per ch01 §2.\n if (isKErrorWire(parsed)) {\n throw gateErrorFromWire(parsed, response.status);\n }\n throw new GateError({\n code: \"INTERNAL_ERROR\",\n message: `Upstream returned ${response.status} with non-KError body`,\n context: { url, status: response.status, body: parsed },\n recommendation: null,\n status: response.status,\n });\n}\n\nfunction buildUrl(\n baseUrl: string,\n path: string,\n query: RequestInit[\"query\"],\n): string {\n const trimmedBase = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n const trimmedPath = path.startsWith(\"/\") ? path : `/${path}`;\n let url = `${trimmedBase}${trimmedPath}`;\n if (query !== undefined) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) params.set(k, String(v));\n }\n const qs = params.toString();\n if (qs.length > 0) url += `?${qs}`;\n }\n return url;\n}\n\nfunction isEnvelope<T>(value: unknown): value is Envelope<T> {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as { data?: unknown; meta?: unknown };\n return \"data\" in v && \"meta\" in v && typeof v.meta === \"object\" && v.meta !== null;\n}\n\nfunction isKErrorWire(value: unknown): value is KErrorWire {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as { code?: unknown; message?: unknown };\n return typeof v.code === \"string\" && typeof v.message === \"string\";\n}\n","/**\n * Shared path-resolution helper for project-scoped routes.\n *\n * v2.5.68 URL grammar: project-scoped resources address\n * via `/@<projectSlug>/...` against the org subdomain. Slug addressing is\n * the only customer-facing form — project ids are internal-only and never\n * appear in customer paths (`feedback_slug_addressing_v2_5_68`); the\n * retired `/v2/projects/<bareId>/...` form was dropped at pass17\n * (`feedback_v2_path_is_an_error`).\n *\n * Caller is expected to validate that the config carries a `projectSlug`\n * at construction time (see `validateGateConfig`).\n */\n\nimport type { GateConfig } from \"./mode.ts\";\n\n/** Return the project-scoped path root for the supplied config, e.g.\n * `/@partner-console-staging`. */\nexport function resolveProjectPathRoot(config: GateConfig): string {\n return `/@${config.projectSlug}`;\n}\n","/**\n * `gate.identityProviders` namespace — project-scoped end-user IdP\n * provider-config management (ch04 §1.6).\n *\n * Wraps the live gateway route family shipped in\n * `services/gateway/src/routes/project-identity-providers.ts`, mounted on\n * the project plane (`<orgSlug>.api.k9o.io/@<projectSlug>/identity-providers`):\n *\n * POST /@<projectSlug>/identity-providers — create (PROJECT_ADMIN)\n * GET /@<projectSlug>/identity-providers — list (READ_ROLES)\n * PATCH /@<projectSlug>/identity-providers/:epcId — update (PROJECT_ADMIN)\n * POST /@<projectSlug>/identity-providers/:epcId/test — testProvider (PROJECT_ADMIN)\n *\n * `create()` + `list()` back the Gate Studio `FederationPresetsPanel`; `update()` backs its\n * `DirectorySyncPanel` (directory-sync settings are read off `list()` and written via `update()`, replacing\n * the unanswered `federation.getDirectorySync`/`updateDirectorySync` bridge messages). Read-one/delete stay\n * unbound until a caller needs them (the SDK does not speculatively surface routes).\n *\n * Auth: operator JWT with PROJECT_ADMIN+ role, server-enforced by the\n * gateway RBAC layer (`requireProjectAdmin`); end-user JWTs (`eu_`-prefixed\n * subjects) are rejected at the route. The bearer is taken from\n * `partnerToken` in config, or per-call via the client's bearer plumbing.\n *\n * 🔴 `clientSecretRef` is a RAW SECRET on the way in, not a reference.\n * Post-v105 the gateway encrypts the submitted value (AES-256-GCM under\n * `K9O_SECRET_STORAGE_MASTER_KEY`) into the opaque `client_secret_enc`\n * handle and persists only that; the field name is retained for wire\n * compatibility but the create handler comment\n * (`project-identity-providers.ts`, the v105 INSERT) is authoritative —\n * there is NO Kibo custody on this path. Reads NEVER carry the secret or its\n * handle: they carry only `clientSecretSet` (a presence boolean,\n * the write-only fix). `serviceAccountKey` (directory sync) follows the same write-only rule.\n */\n\nimport { getGateConfig, type GateConfig } from \"../mode.ts\";\nimport { requestWith } from \"../client.ts\";\nimport { resolveProjectPathRoot } from \"../paths.ts\";\nimport type { Envelope } from \"../types.ts\";\n\n/** Closed end-user-class provider enum per ch04 §1.6 — 11 values since v221 (the project-plane substrate\n * `_sys.end_user_idp_provider_configs`); the org plane carries the same 11 since v222/v223. */\nexport type IdpProviderKind =\n | \"google\" | \"github\" | \"microsoft\" | \"oidc\" | \"saml\" | \"scim\"\n | \"ldap\" | \"active_directory\" | \"apple\" | \"facebook\" | \"twitter\";\n\n/**\n * The result of a provider connectivity test (`testProvider`). `ok` is the reachability + metadata-validity\n * verdict; `reason` (present only when `!ok`) is a CLOSED-enum machine code, NEVER the upstream response body\n * or any credential material. v1 is a NO-SECRET probe: it validates the issuer/metadata\n * endpoint is reachable + structurally valid; it never transmits the stored client secret. `credentials_rejected`\n * is deliberately NOT a v1 reason — the secret is never used, so it cannot be validated (a v2 concern).\n */\nexport type FederationTestProviderResult = {\n ok: boolean;\n reason?:\n | \"endpoint_unreachable\"\n | \"endpoint_forbidden\"\n | \"metadata_invalid\"\n | \"secret_not_provisioned\"\n | \"kind_unsupported\";\n};\n\n/** Per ch04 §1.6 the provider→tier pairing is structurally fixed:\n * `free_tier_ok ∈ {google, github, microsoft, apple, facebook, twitter}`,\n * `ridge_plus ∈ {oidc, saml, scim, ldap, active_directory}`. The gateway rejects a mismatched\n * pairing (`K9O_INVALID_FIELD_VALUE`); the SDK forwards the caller value\n * verbatim rather than second-guessing it. */\nexport type IdpTierGating = \"free_tier_ok\" | \"ridge_plus\";\n\n/**\n * The provider-config metadata shape returned by every read surface and\n * mutation response (`shapeIdpConfig`). Mirrors the gateway wire shape\n * one-to-one.\n *\n * Reads carry `clientSecretSet` (presence only), never the secret or a handle (see the module header).\n * `metadataUrl`/`updatedAt` are `null` until set; `attributeMap` defaults to `{}`; `allowedRedirectUris`\n * defaults to `[]`. The `directory*` fields are GS-F7 scheduled directory sync (Google only, ch03 §5.11):\n * settings plus the scheduler's last-run status; the service-account key itself is never returned.\n */\nexport type IdpConfig = {\n id: string;\n projectId: string;\n idpProvider: IdpProviderKind;\n displayName: string;\n clientId: string;\n /** Whether a client secret is stored. The secret (and any handle to it) is write-only. */\n clientSecretSet: boolean;\n redirectUri: string;\n metadataUrl: string | null;\n attributeMap: Record<string, unknown>;\n tierGating: IdpTierGating;\n enabled: boolean;\n allowedRedirectUris: string[];\n directoryPullEnabled: boolean;\n directorySyncSubject: string | null;\n directorySyncIntervalMins: number | null;\n directorySyncLastRunAt: string | null;\n directorySyncLastStatus: string | null;\n createdAt: string;\n updatedAt: string | null;\n};\n\n/** GS-F7 directory-sync write fields — accepted on create AND update (one PROJECT_ADMIN surface). */\nexport type DirectorySyncInput = {\n /** Enabling requires `idpProvider: \"google\"`, a `serviceAccountKey` (now or already stored) and an interval. */\n directoryPullEnabled?: boolean;\n directorySyncIntervalMins?: number;\n /** The raw Google service-account key JSON. Write-only: encrypted at rest server-side, never returned. */\n serviceAccountKey?: string;\n /** The Workspace admin user the service account impersonates (domain-wide delegation subject). */\n directorySyncSubject?: string;\n};\n\n/**\n * Create-request body — mirrors the gateway `CreateIdpBody` (t.Object)\n * exactly, including which fields are optional.\n */\nexport type CreateIdpInput = {\n idpProvider: IdpProviderKind;\n displayName: string;\n clientId: string;\n /**\n * The OAuth2 client secret. Sent over TLS; encrypted-at-rest server-side\n * (AES-256-GCM) and never returned. Named `clientSecretRef` for wire\n * compatibility — it is NOT a reference on the way in (see module header).\n */\n clientSecretRef: string;\n redirectUri: string;\n /** Optional; `null`/omitted for providers that don't use discovery metadata. */\n metadataUrl?: string | null;\n /** Optional claim→attribute mapping; defaults to `{}` server-side. */\n attributeMap?: Record<string, string>;\n tierGating: IdpTierGating;\n /** Defaults to `false` server-side — a provider is created disabled. */\n enabled?: boolean;\n /**\n * Customer SPA origins the SDK gate flow (`gate.signInWithProvider`) may\n * hand session tokens back to. Origin-matched + fail-closed at authorize,\n * so an empty/omitted list means SDK sign-in is disabled for this provider\n * until an origin is registered. Defaults to `[]` server-side.\n */\n allowedRedirectUris?: string[] | null;\n} & DirectorySyncInput;\n\n/**\n * Update-request body — mirrors the gateway `PatchIdpBody` exactly: every field optional; `idpProvider` and\n * `tierGating` are immutable after create. An empty patch returns the current config unchanged.\n *\n * 🔴 For `oidc`/`microsoft`, changing `metadataUrl` WITHOUT `clientSecretRef` in the same patch CLEARS the\n * stored client secret (the gateway's secret-invalidate-on-host-change control, ch04 §1.6): re-supply the\n * secret whenever the metadata URL changes.\n */\nexport type PatchIdpInput = {\n displayName?: string;\n clientId?: string;\n /** The raw client secret (write-only; see the module header). */\n clientSecretRef?: string;\n redirectUri?: string;\n metadataUrl?: string | null;\n attributeMap?: Record<string, string>;\n enabled?: boolean;\n allowedRedirectUris?: string[] | null;\n} & DirectorySyncInput;\n\nfunction identityProvidersPath(config: GateConfig): string {\n return `${resolveProjectPathRoot(config)}/identity-providers`;\n}\n\n/** Factory — produces an `identityProviders` namespace bound to the supplied\n * config getter. Used by both the `gate` singleton + `createGateClient`. */\nexport function makeIdentityProviders(getConfig: () => GateConfig) {\n return {\n /**\n * Register a new end-user IdP provider config for the configured\n * project. PROJECT_ADMIN+ required (server-enforced). The plaintext\n * client secret is encrypted-at-rest server-side; the returned config\n * carries only the opaque secret handle.\n *\n * The gateway validates the `idpProvider`→`tierGating` pairing per ch04\n * §1.6 and rejects a mismatch with `K9O_INVALID_FIELD_VALUE`.\n */\n async create(input: CreateIdpInput): Promise<Envelope<IdpConfig>> {\n const config = getConfig();\n return requestWith<IdpConfig>(config, {\n method: \"POST\",\n path: identityProvidersPath(config),\n body: input,\n });\n },\n\n /**\n * List the configured end-user IdP provider configs for the project.\n * READ_ROLES-gated server-side (PROJECT_ADMIN/EDITOR/MEMBER/VIEWER/AUTHOR/\n * ANALYST) — the SAME shape + roles as the existing GET route\n * (`listProviders` returns the existing route's shape, no\n * confidentiality-narrower endpoint). Each `IdpConfig` carries the opaque\n * secret handle, never the secret. Callers that only need the summary\n * (`id`/`idpProvider`/`displayName`/`enabled`/`tierGating`) pick fields off\n * the result — no server change needed for that shaping.\n */\n async list(): Promise<Envelope<IdpConfig[]>> {\n const config = getConfig();\n return requestWith<IdpConfig[]>(config, {\n method: \"GET\",\n path: identityProvidersPath(config),\n });\n },\n\n /**\n * Test connectivity to a configured provider by id — a NO-SECRET reachability + metadata-validity probe\n * (no-secret): the gateway resolve-and-pins the issuer/metadata endpoint (SSRF-fenced) and\n * validates it structurally, WITHOUT decrypting or transmitting the stored client secret. Returns\n * `{ok, reason?}`; the `reason` is a closed machine code, never the upstream body or any credential.\n * PROJECT_ADMIN required (server-enforced) — it triggers live external egress on the platform's behalf.\n */\n /**\n * Update a provider config by id (PATCH; PROJECT_ADMIN, server-enforced). Partial: only supplied fields\n * change; `idpProvider`/`tierGating` are immutable. Returns the updated config (secrets never included).\n * GS-F7: write directory-sync settings here and read their status back off `list()`. See `PatchIdpInput`\n * for the metadataUrl-change-clears-secret rule.\n */\n async update(configId: string, patch: PatchIdpInput): Promise<Envelope<IdpConfig>> {\n const config = getConfig();\n return requestWith<IdpConfig>(config, {\n method: \"PATCH\",\n path: `${identityProvidersPath(config)}/${encodeURIComponent(configId)}`,\n body: patch,\n });\n },\n\n async testProvider(configId: string): Promise<Envelope<FederationTestProviderResult>> {\n const config = getConfig();\n return requestWith<FederationTestProviderResult>(config, {\n method: \"POST\",\n path: `${identityProvidersPath(config)}/${encodeURIComponent(configId)}/test`,\n });\n },\n };\n}\n\n/** Singleton bound to the module-level `configureGate()` config. */\nexport const identityProviders = makeIdentityProviders(getGateConfig);\n\nexport type IdentityProvidersNamespace = ReturnType<typeof makeIdentityProviders>;\n","/**\n * `gate.magicLinkRedirects` sub-namespace — the per-project magic-link\n * redirect-URL allowlist, PROJECT_ADMIN+ managed. Blessed shape (\n * 2026-07-16): `list()`/`add()`/`remove()`, mirroring the\n * `gate.idpProviders.*` sub-namespace convention (gate-admin surface,\n * not the end-user-facing `passwordless.*` send/verify flow).\n *\n * Wraps the live gateway routes (`services/gateway/src/routes/\n * gate-magic-link-redirects.ts`):\n *\n * GET /@<projectSlug>/gate/magic-link-redirects\n * POST /@<projectSlug>/gate/magic-link-redirects { redirectUrl, isDefault? }\n * DELETE /@<projectSlug>/gate/magic-link-redirects/:id\n *\n * One deliberate deviation from the shorthand in the original blessing\n * relay (\"remove(redirectUrl)\"): the shipped route deletes by the\n * redirect row's `id`, not by re-presenting its `redirectUrl` value —\n * verified directly against the route, not assumed from the relay's\n * informal shape sketch. `remove(id)` matches what the gateway actually\n * does; a `redirectUrl`-keyed removal would need an extra `list()`\n * round-trip to resolve the id first, which the SDK should not do\n * silently on the caller's behalf.\n *\n * Auth: operator JWT with PROJECT_ADMIN+ (or ORG_ADMIN/ORG_OWNER) role —\n * the gateway rejects end-user JWTs and lower-role operators alike\n * (K9O_RBAC_DENY). The allowlist is a security control: registering a\n * redirect target is an admin act, same posture as `idpProviders.*`.\n */\n\nimport { getGateConfig, type GateConfig } from \"./mode.ts\";\nimport { requestWith } from \"./client.ts\";\nimport { resolveProjectPathRoot } from \"./paths.ts\";\nimport type { Envelope } from \"./types.ts\";\n\n/** A single allowlisted magic-link redirect target. */\nexport type MagicLinkRedirect = {\n id: string;\n redirectUrl: string;\n isDefault: boolean;\n createdAt: string;\n};\n\nexport type AddMagicLinkRedirectInput = {\n redirectUrl: string;\n /**\n * When true, this target becomes the default the gateway falls back to\n * when a `requestMagicLink` call doesn't specify one. The gateway\n * atomically demotes any previously-default entry — never two\n * simultaneous defaults.\n */\n isDefault?: boolean;\n};\n\nexport type RemoveMagicLinkRedirectResult = { removed: true; id: string };\n\nfunction magicLinkRedirectsPath(config: GateConfig, id?: string): string {\n const root = `${resolveProjectPathRoot(config)}/gate/magic-link-redirects`;\n return id !== undefined ? `${root}/${id}` : root;\n}\n\n/** Factory — produces a `magicLinkRedirects` namespace bound to the\n * supplied config getter. Used by both the `gate` singleton +\n * `createGateClient`. */\nexport function makeMagicLinkRedirects(getConfig: () => GateConfig) {\n return {\n /** List every allowlisted redirect target for the active project. */\n async list(): Promise<Envelope<{ redirects: MagicLinkRedirect[] }>> {\n const config = getConfig();\n return requestWith<{ redirects: MagicLinkRedirect[] }>(config, {\n method: \"GET\",\n path: magicLinkRedirectsPath(config),\n });\n },\n\n /**\n * Register a new redirect target. `redirectUrl` must exact-match\n * what a customer later passes to `requestMagicLink` — the gateway\n * enforces exact match, never a prefix or wildcard.\n */\n async add(input: AddMagicLinkRedirectInput): Promise<Envelope<{ redirect: MagicLinkRedirect }>> {\n const config = getConfig();\n return requestWith<{ redirect: MagicLinkRedirect }>(config, {\n method: \"POST\",\n path: magicLinkRedirectsPath(config),\n body: input,\n });\n },\n\n /** Remove a previously-registered redirect target by its `id`. */\n async remove(id: string): Promise<Envelope<RemoveMagicLinkRedirectResult>> {\n const config = getConfig();\n return requestWith<RemoveMagicLinkRedirectResult>(config, {\n method: \"DELETE\",\n path: magicLinkRedirectsPath(config, id),\n });\n },\n };\n}\n\n/** Singleton bound to the module-level `configureGate()` config. */\nexport const magicLinkRedirects = makeMagicLinkRedirects(getGateConfig);\n\nexport type MagicLinkRedirectsNamespace = ReturnType<typeof makeMagicLinkRedirects>;\n","/**\n * Marquee `gate.*` namespace per ch01 §12.1.\n *\n * v0.1.0 surfaces (canonical v2.5.68 slug routing — `<orgSlug>.api.k9o.io/@<projectSlug>/...`):\n * - `gate.signUp` — POST /@<projectSlug>/gate/signup\n * - `gate.signIn` — POST /@<projectSlug>/gate/signin\n * - `gate.signOut` — POST /@<projectSlug>/gate/signout\n * - `gate.whoami` — GET /@<projectSlug>/gate/whoami\n * - `gate.refreshToken` — POST /@<projectSlug>/gate/refresh\n * - `gate.verifySession` — GET /@<projectSlug>/gate/verify\n * - `gate.hosted` — GET /@<projectSlug>/gate/hosted (unauthenticated; `{ signInUrl, rpId }`)\n *\n * Sub-namespaces (passwordless / passkeys / social / mfa / password /\n * users / email / sms / federation / admin) deferred to subsequent\n * cycles per the README catalogue.\n *\n * Wire shape: every function returns the standard envelope\n * `{ data, meta }` per ch01 §1 and throws a `GateError` on non-2xx\n * gateway responses. Native vs Standalone mode does not affect SDK\n * ergonomics per ch01 §12.4; the mode header is set internally by the\n * client + does not surface in the function-call ergonomics.\n *\n * Endpoint paths are reconciled to the LIVE gateway routes (verified\n * against staging): signup/signin/signout/whoami/refresh/verify under\n * `/@<projectSlug>/gate/*`, OTP under `/gate/otp/{request,verify}`,\n * Magic Link under `/gate/magic-link/{send,redeem}`, IdP under\n * `/gate/idp/:provider/{authorize,callback}`.\n */\n\nimport { getGateConfig, type GateConfig } from \"./mode.ts\";\nimport { requestWith } from \"./client.ts\";\nimport { resolveProjectPathRoot } from \"./paths.ts\";\nimport { makeIdentityProviders } from \"./idp-providers/identity-providers.ts\";\nimport { makeMagicLinkRedirects } from \"./magic-link-redirects.ts\";\nimport type {\n AuthResult,\n Envelope,\n HostedGateInfo,\n MagicLinkCallbackResult,\n RefreshTokenInput,\n RequestMagicLinkInput,\n RequestOtpInput,\n SignInCallbackResult,\n SignInInput,\n SignOutInput,\n SignUpInput,\n VerifyMagicLinkInput,\n VerifyOtpInput,\n VerifySessionResult,\n WhoamiResult,\n} from \"./types.ts\";\n\nfunction gatePath(config: GateConfig, suffix: string): string {\n return `${resolveProjectPathRoot(config)}/gate/${suffix}`;\n}\n\n/** Factory — produces a `gate.*` namespace bound to the supplied config getter.\n * Used by both the module-level `gate` singleton (config from `configureGate`)\n * and `createGateClient(config)` (config supplied at construction time). */\nexport function makeGateNamespace(getConfig: () => GateConfig) {\n return {\n /**\n * Create a new end-user identity for the configured project. Returns\n * the new user record + an initial session per ch01 §12.2. Emits a\n * `data.write.auth.signup` audit event per ch01 §12.5.\n *\n * Errors (closed taxonomy per ch01 §2):\n * - `EMAIL_ALREADY_REGISTERED` (409) when the email collides\n * - `INVALID_FIELD_VALUE` (400) on schema violation (password policy, malformed email)\n * - `TIER_FEATURE_GATED` (403) when the project's tier blocks email/password\n */\n async signUp(input: SignUpInput): Promise<Envelope<AuthResult>> {\n return requestWith<AuthResult>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"signup\"),\n body: input,\n });\n },\n\n /**\n * Sign in an existing end-user with email + password. Returns the\n * user record + a fresh session. Emits a `data.write.auth.signin`\n * audit event.\n *\n * Errors:\n * - `INVALID_CREDENTIALS` (401) on bad email/password (ambiguous on\n * purpose — does not distinguish \"user not found\" vs \"password\n * wrong\" to avoid enumeration)\n */\n async signIn(input: SignInInput): Promise<Envelope<AuthResult>> {\n return requestWith<AuthResult>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"signin\"),\n body: input,\n });\n },\n\n /**\n * Sign out — revoke the bound refresh token (or an explicit one, if\n * supplied). Emits a `data.write.auth.signout` audit event.\n *\n * Errors:\n * - `UNAUTHENTICATED` (401) when no session is present\n * - `REFRESH_TOKEN_INVALID` (400) when the supplied refresh token\n * doesn't match the bearer's session\n */\n async signOut(input: SignOutInput = {}): Promise<Envelope<{ revoked: boolean }>> {\n return requestWith<{ revoked: boolean }>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"signout\"),\n body: input,\n });\n },\n\n /**\n * Return the caller's identity claims from the bearer JWT. Useful in\n * Standalone mode where the SDK consumer wants to surface the user's\n * roles / groups without parsing the JWT itself.\n *\n * Errors:\n * - `UNAUTHENTICATED` (401) when no bearer is present\n * - `SESSION_EXPIRED` (401) when the bearer's exp has passed\n */\n async whoami(): Promise<Envelope<WhoamiResult>> {\n return requestWith<WhoamiResult>(getConfig(), {\n method: \"GET\",\n path: gatePath(getConfig(), \"whoami\"),\n });\n },\n\n /**\n * Rotate the access token using a refresh token. Returns a fresh\n * session pair (new access token + same refresh token; gateway may\n * also rotate the refresh token per project policy).\n *\n * Errors:\n * - `REFRESH_TOKEN_INVALID` (400) when the refresh token is unknown,\n * expired, or revoked\n */\n async refreshToken(input: RefreshTokenInput): Promise<Envelope<{ session: AuthResult[\"session\"] }>> {\n return requestWith<{ session: AuthResult[\"session\"] }>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"refresh\"),\n body: input,\n });\n },\n\n /**\n * Check whether the bearer's session is currently valid. Does NOT\n * throw on expiry or invalid token — returns `{ valid: false }`\n * inside the envelope. The intended use is health-checking a session\n * without surfacing an error to the end-user (the SDK consumer\n * typically wants to redirect to sign-in rather than surface a 401).\n */\n async verifySession(): Promise<Envelope<VerifySessionResult>> {\n return requestWith<VerifySessionResult>(getConfig(), {\n method: \"GET\",\n path: gatePath(getConfig(), \"verify\"),\n });\n },\n\n /**\n * The project's hosted sign-in page URL and WebAuthn `rpId`. Unauthenticated and cacheable; no credential is sent,\n * even when a partner token is configured. Take both values as given: an unknown org, a deleted project and a\n * non-Gate project all answer the same `PROJECT_NOT_FOUND`, so a failure here says nothing about which.\n */\n async hosted(): Promise<Envelope<HostedGateInfo>> {\n return requestWith<HostedGateInfo>(getConfig(), {\n method: \"GET\",\n path: gatePath(getConfig(), \"hosted\"),\n bearerToken: \"\",\n });\n },\n\n /**\n * Initiate an external-IdP sign-in flow (Google / GitHub / Microsoft).\n * The SDK POSTs to the gateway's authorize endpoint, receives the\n * provider's authorization URL with a signed-state JWT (carrying PKCE\n * verifier + the supplied `redirectUri`), and redirects the browser.\n *\n * After the user authenticates with the IdP, the gateway processes the\n * callback and redirects the browser back to `redirectUri` with session\n * tokens in the URL fragment. Use `parseSignInCallback()` on the\n * destination page to extract them.\n *\n * Example:\n * await gate.signInWithProvider({\n * provider: 'google',\n * redirectUri: 'https://app.example.com/auth/callback',\n * });\n * // …user lands at /auth/callback#access_token=…&refresh_token=…\n *\n * Generic OIDC requires authorization-endpoint discovery and is not\n * yet wired; use a hosted provider (google/github/microsoft) for now.\n */\n async signInWithProvider(input: {\n provider: \"google\" | \"github\" | \"microsoft\" | \"oidc\" | \"saml\";\n redirectUri: string;\n }): Promise<void> {\n const env = await requestWith<{ authorizationUrl: string }>(getConfig(), {\n method: \"POST\",\n path: `${resolveProjectPathRoot(getConfig())}/gate/idp/${input.provider}/authorize`,\n body: { redirectUri: input.redirectUri },\n });\n const w = (globalThis as { window?: { location: { assign: (url: string) => void } } }).window;\n if (w !== undefined) {\n w.location.assign(env.data.authorizationUrl);\n } else {\n throw new Error(\n \"signInWithProvider requires a browser environment (window.location); for server-side initiation, fetch the authorize endpoint directly.\",\n );\n }\n },\n\n /**\n * Parse session tokens out of the URL fragment delivered by an\n * IdP callback redirect. Returns the session payload on success or an\n * error object on failure. Pass `window.location.hash` (or any\n * `#`-prefixed string) — leading `#` is stripped automatically.\n *\n * Side-effect-free: does not call the gateway and does not mutate\n * any session storage. The consumer is responsible for persisting\n * the returned tokens (e.g. via the app's auth context).\n */\n parseSignInCallback(hash: string): SignInCallbackResult {\n const raw = hash.startsWith(\"#\") ? hash.slice(1) : hash;\n const params = new URLSearchParams(raw);\n const error = params.get(\"error\");\n if (error !== null) {\n return {\n ok: false,\n error,\n errorDescription: params.get(\"error_description\") ?? error,\n };\n }\n const accessToken = params.get(\"access_token\");\n const refreshToken = params.get(\"refresh_token\");\n const expiresAt = params.get(\"expires_at\");\n if (accessToken === null || refreshToken === null || expiresAt === null) {\n return {\n ok: false,\n error: \"malformed_callback\",\n errorDescription:\n \"Callback URL fragment missing one or more required fields (access_token, refresh_token, expires_at).\",\n };\n }\n return {\n ok: true,\n accessToken,\n refreshToken,\n expiresAt,\n tokenType: params.get(\"token_type\") ?? \"Bearer\",\n };\n },\n\n /**\n * Passwordless sub-namespace per ch01 §12.1 — OTP + Magic Link flows\n * for email-based authentication without a stored password.\n *\n * Wire endpoints (LIVE on the Gate end-user plane — verified against\n * staging): OTP → `POST /@<projectSlug>/gate/otp/{request,verify}`\n * (routes/otp.ts), Magic Link → `POST /@<projectSlug>/gate/magic-link/\n * {send,redeem}` (routes/magic-link.ts). NOTE: these are the gateway's\n * canonical paths; the SDK ergonomic method names (`requestMagicLink`/\n * `verifyMagicLink`) do not mirror the path segments (`send`/`redeem`).\n * (The earlier `passwordless/*` SDK paths were interface-first scaffold\n * that never matched the shipped gateway routes — reconciled here.)\n */\n passwordless: {\n /**\n * Request a one-time passcode delivered to the supplied email.\n * The OTP is short-lived (typically 5 min per ch03 §3.4) and\n * single-use; redemption via `verifyOtp` mints a session.\n */\n async requestOtp(input: RequestOtpInput): Promise<Envelope<{ requested: boolean }>> {\n return requestWith<{ requested: boolean }>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"otp/request\"),\n body: input,\n });\n },\n\n /**\n * Verify a one-time passcode previously sent via `requestOtp`.\n * On success returns the same `AuthResult` shape as `signIn` —\n * a fresh session bound to the end-user identity.\n *\n * Errors:\n * - INVALID_CREDENTIALS (401) on wrong / expired / consumed OTP\n * (ambiguous on purpose — enumeration defense)\n */\n async verifyOtp(input: VerifyOtpInput): Promise<Envelope<AuthResult>> {\n return requestWith<AuthResult>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"otp/verify\"),\n body: input,\n });\n },\n\n /**\n * Request a magic-link emailed to the supplied address. The link\n * carries an opaque short-lived token (typically 15 min per ch03\n * §3.3); clicking the link lands at the customer's chosen\n * redirect URL with the token in the query string, which the\n * customer's frontend then exchanges via `verifyMagicLink`.\n */\n async requestMagicLink(\n input: RequestMagicLinkInput,\n ): Promise<Envelope<{ requested: boolean }>> {\n return requestWith<{ requested: boolean }>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"magic-link/send\"),\n body: input,\n });\n },\n\n /**\n * Verify a magic-link token previously delivered via\n * `requestMagicLink`. On success returns the same `AuthResult`\n * shape as `signIn`.\n *\n * Errors:\n * - INVALID_CREDENTIALS (401) on wrong / expired / consumed\n * token (same enumeration-defense rationale as verifyOtp)\n */\n async verifyMagicLink(input: VerifyMagicLinkInput): Promise<Envelope<AuthResult>> {\n return requestWith<AuthResult>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"magic-link/redeem\"),\n body: input,\n });\n },\n\n /**\n * Extract the opaque magic-link token from the landing-page URL's query\n * string (the gateway redirects to `<configured-redirect>?token=…&project=…`).\n * Pure + side-effect-free — pass `window.location.search`. Feed the\n * returned `token` to `verifyMagicLink({ token })` to mint the session.\n *\n * This is the magic-link counterpart to `parseSignInCallback` (which parses\n * the SSO *hash fragment* for session tokens) — magic-link delivers an\n * opaque token in the *query string*, so the two are not interchangeable.\n */\n parseMagicLinkCallback(search: string): MagicLinkCallbackResult {\n const raw = search.startsWith(\"?\") ? search.slice(1) : search;\n const params = new URLSearchParams(raw);\n const error = params.get(\"error\");\n if (error !== null) {\n return { ok: false, error };\n }\n const token = params.get(\"token\");\n if (token === null || token.length === 0) {\n return { ok: false, error: \"missing_token\" };\n }\n return { ok: true, token };\n },\n },\n\n /**\n * Project-scoped end-user IdP provider-config management (ch04 §1.6).\n * PROJECT_ADMIN+ required; end-user JWTs rejected at the route. Only\n * `create()` is bound — the REST create the Gate Studio\n * `FederationPresetsPanel` routes to when it drops the `gate_flows`\n * `__addFromPreset` sentinel. Gate-admin surface.\n */\n identityProviders: makeIdentityProviders(getConfig),\n\n /**\n * Per-project magic-link redirect-URL allowlist management (blessed\n * shape). Gate-admin surface alongside `identityProviders`\n * — PROJECT_ADMIN+ required, distinct from the end-user-facing\n * `passwordless.requestMagicLink`/`verifyMagicLink` send/verify flow.\n */\n magicLinkRedirects: makeMagicLinkRedirects(getConfig),\n };\n}\n\n/** Singleton bound to the module-level `configureGate()` config.\n * v0.4.0+: per-instance clients via `createGateClient(config)`. */\nexport const gate = makeGateNamespace(getGateConfig);\n\n/** Convenience type alias mirroring the `gate` runtime export.\n * Identical shape as the per-instance `GateClient` returned from\n * `createGateClient`. */\nexport type GateNamespace = ReturnType<typeof makeGateNamespace>;\n","/**\n * `createGateClient(config)` — per-instance Gate SDK factory (v0.4.0).\n *\n * Returns a self-contained `GateClient` that does not depend on the\n * module-level `configureGate()` singleton. Multiple clients can coexist\n * in the same process, each scoped to its own project + baseUrl + mode —\n * useful for multi-tenant Backends-For-Frontends (one client per\n * incoming request's resolved tenant) or for apps that maintain both a\n * Standalone and Native binding against the same SDK instance.\n *\n * Per v2.5.68 URL grammar, supply `projectSlug` for\n * canonical `/@<slug>/gate/...` routing. Slug is the only customer-facing\n * project addressing form — project ids are internal-only\n * (`feedback_slug_addressing_v2_5_68`); the retired `/v2/projects/...`\n * mounts were dropped at pass17 (`feedback_v2_path_is_an_error`).\n *\n * Example:\n * const client = createGateClient({\n * baseUrl: 'https://formelo.api.k9o.io',\n * projectSlug: 'partner-console-staging',\n * mode: 'native',\n * });\n * await client.signIn({ email, password });\n */\n\nimport { makeGateNamespace } from \"./gate.ts\";\nimport { validateGateConfig, type GateConfig } from \"./mode.ts\";\n\n/** Shape of the per-instance client returned by `createGateClient`. */\nexport type GateClient = ReturnType<typeof makeGateNamespace>;\n\n/** Construct a Gate client bound to the supplied config. The config is\n * validated up-front and stored on the instance; subsequent calls do not\n * re-read process-level state. Throws `TypeError` on invalid config. */\nexport function createGateClient(config: GateConfig): GateClient {\n validateGateConfig(config);\n const frozen: GateConfig = { ...config };\n return makeGateNamespace(() => frozen);\n}\n"],"mappings":"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","names":[]}
package/dist/jwt.d.ts ADDED
@@ -0,0 +1,84 @@
1
+ import { GateJwtClaims, VerifyJwtResult } from './types.js';
2
+
3
+ /**
4
+ * JWT verification primitives for external-app standalone consumption.
5
+ *
6
+ * Standalone-mode customers consume Gate-issued JWTs in their own
7
+ * application code without needing the full SDK shape — they just need
8
+ * to verify the signature, extract claims, and (optionally) check
9
+ * scopes. This module is the canonical entry for that surface.
10
+ *
11
+ * Backed by `jose` (already a peer dependency in the gateway via the
12
+ * same library; chose `jose` for consistency + JWK fetch support out
13
+ * of the box).
14
+ *
15
+ * Native-mode customers don't typically reach for this directly — the
16
+ * gateway verifies project-issued tokens on every request and rehydrates
17
+ * `subject.*` claims per ch06 §3.2. But the same primitives are
18
+ * portable: Native customers running their own services downstream of
19
+ * the gateway can verify Gate-issued JWTs identically.
20
+ */
21
+
22
+ /**
23
+ * Configuration for `verifyJwt`. The verifier can resolve signing keys
24
+ * via either a remote JWKS URL (the typical Gate deployment shape) or
25
+ * an inline key set (for tests + air-gapped deployments).
26
+ */
27
+ type VerifyJwtOptions = {
28
+ token: string;
29
+ /**
30
+ * JWKS endpoint URL — the gateway exposes `/@<projectSlug>/gate/.well-known/jwks.json`
31
+ * by convention. Either `jwksUrl` or `jwks` must be set; not both.
32
+ */
33
+ jwksUrl?: string;
34
+ /**
35
+ * Inline JWK set — used by tests + integration scenarios that want to
36
+ * avoid the network hop. The shape is the standard JWKS `{keys: [...]}`
37
+ * envelope; pass through unchanged to `jose`.
38
+ */
39
+ jwks?: {
40
+ keys: ReadonlyArray<Record<string, unknown>>;
41
+ };
42
+ /** Expected issuer (`iss` claim). Strict comparison. */
43
+ expectedIssuer?: string;
44
+ /** Expected audience (`aud` claim). Accepts either string or array per RFC 7519. */
45
+ expectedAudience?: string;
46
+ /**
47
+ * Allowed signing algorithms. Defaults to `["RS256", "ES256"]` per the
48
+ * standard Gate issuer config. Reject anything outside this set
49
+ * (defense against alg-confusion attacks).
50
+ */
51
+ allowedAlgorithms?: ReadonlyArray<string>;
52
+ /**
53
+ * Clock-skew tolerance in seconds. Defaults to 30. Applies to both
54
+ * `exp` and `nbf` checks.
55
+ */
56
+ clockToleranceSeconds?: number;
57
+ };
58
+ /**
59
+ * Verify a Gate-issued JWT. Returns a typed result; does NOT throw on
60
+ * verification failure (a `valid: false` result carries the reason).
61
+ *
62
+ * Throws `GateError` only when:
63
+ * - `jwksUrl` and `jwks` are both unset or both set
64
+ * - the JWKS fetch fails for transport reasons (UPSTREAM_UNAVAILABLE)
65
+ */
66
+ declare function verifyJwt(opts: VerifyJwtOptions): Promise<VerifyJwtResult>;
67
+ /**
68
+ * Extract claims from a JWT WITHOUT verifying the signature. Used by
69
+ * SDK consumers that want to read claim data in client code where
70
+ * signature verification has already happened at a trusted hop (e.g.,
71
+ * a reverse proxy). Caller is responsible for confirming the upstream
72
+ * trust path before consuming these claims for security decisions.
73
+ *
74
+ * Throws `GateError` on malformed input.
75
+ */
76
+ declare function extractClaims(token: string): GateJwtClaims;
77
+ /**
78
+ * Assert that the given claim set contains all of the required scopes.
79
+ * Throws `GateError` with `code: "RBAC_DENY"` if any scope is missing.
80
+ * Returns nothing on success — caller uses the throw-or-pass shape.
81
+ */
82
+ declare function requireScope(claims: GateJwtClaims, requiredScopes: ReadonlyArray<string>): void;
83
+
84
+ export { type VerifyJwtOptions, extractClaims, requireScope, verifyJwt };
package/dist/jwt.js ADDED
@@ -0,0 +1,12 @@
1
+ import {
2
+ extractClaims,
3
+ requireScope,
4
+ verifyJwt
5
+ } from "./chunk-ZNFI2CQA.js";
6
+ import "./chunk-PEW6HZFB.js";
7
+ export {
8
+ extractClaims,
9
+ requireScope,
10
+ verifyJwt
11
+ };
12
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/mode.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Standalone vs Native mode wiring per ch01 §12.4.
3
+ *
4
+ * The SDK surface is identical across modes; mode determines gateway-
5
+ * side behavior (whether the end-user row scope applies on read/write paths),
6
+ * not SDK ergonomics. The mode boundary is a deployment-time decision
7
+ * per ch02; the SDK does not branch behavior at function-call time.
8
+ *
9
+ * What mode affects (today + forward):
10
+ *
11
+ * - **Billing-identity routing** (commission callout):
12
+ * Standalone customers are billed under the partner-credentialled
13
+ * contract (Gate as Auth0/Clerk/Okta alternative — Trojan Horse
14
+ * motion); Native customers compose with the broader Kilimanjaro
15
+ * billing model. The SDK records the mode on every wire request via
16
+ * the `X-Gate-Mode` header so the gateway routes consumption to the
17
+ * correct billing surface without per-function branching.
18
+ * - **Auto-scoping expectations** in marketing copy + docs hub copy
19
+ * (per memory `feedback_gate_iak_brand_architecture.md` marketing-
20
+ * honesty discipline). Standalone mode delivers Sovereign Ingress +
21
+ * Hardware-Attested Identity + Forensic Attribution + Unlimited MAUs;
22
+ * Native mode adds Automatic Identity-Aware Scoping on top.
23
+ *
24
+ * What mode does NOT affect:
25
+ *
26
+ * - Function names + signatures (identical across modes)
27
+ * - Wire-envelope shape (identical per ch01 §1)
28
+ * - Error taxonomy (identical per ch01 §2)
29
+ */
30
+ /**
31
+ * Two-mode discriminant. The third candidate "Hybrid" was rejected per
32
+ * memory `feedback_gate_iak_brand_architecture.md` — modes are
33
+ * orthogonal deployment shapes, not interleaved.
34
+ */
35
+ type GateMode = "standalone" | "native";
36
+ /**
37
+ * Gate SDK configuration. Returned by `getGateConfig()` and consumed
38
+ * internally by the HTTP client + JWT verifier. Set via `configureGate`
39
+ * at process boot or per-test in unit tests.
40
+ */
41
+ type GateConfig = {
42
+ mode: GateMode;
43
+ /** Project slug per v2.5.68 URL grammar (e.g. `partner-console-staging`).
44
+ * The only customer-facing project addressing form; routes resolve to
45
+ * `/@<slug>/gate/...` (`feedback_slug_addressing_v2_5_68`). Required. */
46
+ projectSlug: string;
47
+ /** Gateway base URL — e.g. `https://<orgSlug>.api.k9o.io` per v2.5.68 §5.4. */
48
+ baseUrl: string;
49
+ /**
50
+ * Optional partner-credentialled JWT for Standalone mode — used as the
51
+ * default Authorization header when no per-call bearer is supplied.
52
+ * Not required in Native mode; the gateway resolves credentials from
53
+ * the standard project context per ch02.
54
+ */
55
+ partnerToken?: string;
56
+ /**
57
+ * Optional fetch override for testing + non-browser environments. The
58
+ * SDK defaults to global `fetch` (available in Bun + Node 18+ +
59
+ * browsers) when not set.
60
+ */
61
+ fetch?: typeof fetch;
62
+ };
63
+ /**
64
+ * Initialise the SDK. Must be called before any `gate.*` invocation.
65
+ * Re-calling replaces the prior config wholesale (no per-field merge —
66
+ * keeps the config-set-at-boot ergonomic clean).
67
+ */
68
+ declare function configureGate(config: GateConfig): void;
69
+ /**
70
+ * Validate a config shape. Exposed for `createGateClient` so per-instance
71
+ * configs go through identical checks as the singleton.
72
+ */
73
+ declare function validateGateConfig(config: GateConfig): void;
74
+ /**
75
+ * Read the active config. Throws when called before `configureGate(...)`.
76
+ * Tests can call `resetGateConfig()` to clear between cases.
77
+ */
78
+ declare function getGateConfig(): GateConfig;
79
+ /** Reset the singleton — used by tests + by app-shutdown teardown paths. */
80
+ declare function resetGateConfig(): void;
81
+
82
+ export { type GateConfig, type GateMode, configureGate, getGateConfig, resetGateConfig, validateGateConfig };
package/dist/mode.js ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ configureGate,
3
+ getGateConfig,
4
+ resetGateConfig,
5
+ validateGateConfig
6
+ } from "./chunk-LZQ7MBXS.js";
7
+ export {
8
+ configureGate,
9
+ getGateConfig,
10
+ resetGateConfig,
11
+ validateGateConfig
12
+ };
13
+ //# sourceMappingURL=mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,250 @@
1
+ /**
2
+ * `@k9o/gate` v0.1.0 — public type surface.
3
+ *
4
+ * The shapes here track ch01 §12 verbatim. End-user IDs carry the `eu_`
5
+ * prefix per ch01 §3.2; role and group identifiers compose against the
6
+ * ch06 role-lattice (Trust-Zone-separate `PROJECT_END_USER` for project-
7
+ * level end-user identities). Sessions, passkeys, MFA factors, federation
8
+ * links, and the rest of the Gate-managed resources receive their per-
9
+ * resource prefixes as the ch04 end-user table family is specified.
10
+ *
11
+ * Envelope shape (`data` + `meta`) matches ch01 §1.1; failures land via
12
+ * the `GateError` family per `errors.ts`, which is `KError`-compatible
13
+ * per ch01 §2.
14
+ */
15
+ /**
16
+ * Standard ch01 §1 envelope. Generic over the `data` shape per function.
17
+ * Returned by every successful SDK call.
18
+ */
19
+ type Envelope<T> = {
20
+ data: T;
21
+ meta: Record<string, unknown>;
22
+ };
23
+ /**
24
+ * Project-level end-user identity (the customers OF the customer per
25
+ * ch06 `PROJECT_END_USER` 7th canonical role). End-user IDs carry the
26
+ * `eu_<base62>` prefix per ch01 §3.2.
27
+ */
28
+ type EndUser = {
29
+ /** `eu_<base62>` — see ch01 §3.2. */
30
+ id: string;
31
+ email: string;
32
+ status: "active" | "pending" | "disabled";
33
+ createdAt: string;
34
+ updatedAt: string | null;
35
+ lastSigninAt: string | null;
36
+ /** Project-level roles per ch06; `PROJECT_END_USER` is the floor membership. */
37
+ roles: ReadonlyArray<string>;
38
+ /** Project-level group memberships per ch04 `_sys.end_user_groups`. */
39
+ groups: ReadonlyArray<string>;
40
+ };
41
+ /**
42
+ * Session tokens returned by sign-up / sign-in / refresh flows. Both
43
+ * tokens are opaque to consumers; `accessToken` carries the JWT shape
44
+ * for downstream Gate-aware services; `refreshToken` is opaque to the
45
+ * SDK consumer and is presented back to `gate.refreshToken({...})` to
46
+ * rotate the access token.
47
+ */
48
+ type Session = {
49
+ accessToken: string;
50
+ refreshToken: string;
51
+ /** ISO 8601 instant the access token expires. */
52
+ expiresAt: string;
53
+ };
54
+ /**
55
+ * Marquee sign-up / sign-in shape per ch01 §12.2:
56
+ * `{ user, session }`
57
+ */
58
+ type AuthResult = {
59
+ user: EndUser;
60
+ session: Session;
61
+ };
62
+ /**
63
+ * `gate.whoami()` shape per ch01 §12.2:
64
+ * `{ user }` — no fresh session is minted by `whoami` (it reflects the
65
+ * caller's current session).
66
+ */
67
+ type WhoamiResult = {
68
+ user: EndUser;
69
+ };
70
+ /**
71
+ * `gate.verifySession()` shape — boolean plus the surfaced session
72
+ * details if valid. Returns `{ valid: false, user: null, session: null }`
73
+ * when the bearer session is missing or expired (no throw on invalid;
74
+ * throw only on transport / upstream failures).
75
+ */
76
+ type VerifySessionResult = {
77
+ valid: true;
78
+ user: EndUser;
79
+ session: Session;
80
+ } | {
81
+ valid: false;
82
+ user: null;
83
+ session: null;
84
+ };
85
+ /**
86
+ * Marquee request shapes. `gate.signUp` takes a CLOSED body: `{ email, password, displayName? }`.
87
+ * `displayName` is stored with the end user's PII (1 to 128 characters); any other field is refused by the
88
+ * gateway with `K9O_INVALID_FIELD_VALUE` (`context.reason: "unknown_field"`), so this type has no escape hatch.
89
+ */
90
+ type SignUpInput = {
91
+ email: string;
92
+ password: string;
93
+ /** Optional display name (1 to 128 characters); surfaces in account-management UI. */
94
+ displayName?: string;
95
+ };
96
+ type SignInInput = {
97
+ email: string;
98
+ password: string;
99
+ };
100
+ type SignOutInput = {
101
+ /**
102
+ * Optional refresh token to revoke. When omitted the gateway revokes
103
+ * the access token's bound refresh token only; explicit refresh tokens
104
+ * let callers revoke a specific device/session.
105
+ */
106
+ refreshToken?: string;
107
+ };
108
+ type RefreshTokenInput = {
109
+ refreshToken: string;
110
+ };
111
+ /**
112
+ * OTP purpose — scopes the one-time code to a flow. The gateway validates
113
+ * against this closed set (otp.ts) and defaults to "signin" when omitted.
114
+ */
115
+ type OtpPurpose = "signin" | "magic_link" | "password_reset" | "mfa_challenge";
116
+ /**
117
+ * `gate.passwordless.requestOtp` input — emails an OTP code to the
118
+ * given address. Idempotent at the per-email rate-limit ceiling per
119
+ * the chapter 10 anti-abuse policy.
120
+ */
121
+ type RequestOtpInput = {
122
+ email: string;
123
+ /** OTP purpose; defaults to "signin" server-side when omitted. */
124
+ purpose?: OtpPurpose;
125
+ };
126
+ /**
127
+ * `gate.passwordless.verifyOtp` input — caller supplies the OTP code
128
+ * received via email plus the originating email address so the
129
+ * gateway can resolve the right OTP row.
130
+ */
131
+ type VerifyOtpInput = {
132
+ email: string;
133
+ code: string;
134
+ /** Must match the `purpose` supplied to `requestOtp` (defaults "signin")
135
+ * so the gateway resolves the right OTP row. */
136
+ purpose?: OtpPurpose;
137
+ };
138
+ /**
139
+ * `gate.passwordless.requestMagicLink` input — emails a magic-link
140
+ * token to the given address; the customer's frontend handles the
141
+ * post-redirect exchange via `verifyMagicLink`.
142
+ */
143
+ type RequestMagicLinkInput = {
144
+ email: string;
145
+ /**
146
+ * Post-redeem landing URL. When supplied it MUST exact-match an entry in
147
+ * the project's magic-link redirect allowlist (Gate config, ch03); a
148
+ * non-allowlisted value is rejected with `GATE_REDIRECT_NOT_ALLOWED` (400).
149
+ * Omit to use the project's configured default redirect.
150
+ */
151
+ redirectUrl?: string;
152
+ };
153
+ /**
154
+ * `gate.passwordless.verifyMagicLink` input — caller supplies the
155
+ * opaque token from the magic-link query string.
156
+ */
157
+ type VerifyMagicLinkInput = {
158
+ token: string;
159
+ };
160
+ /**
161
+ * Result of `gate.passwordless.parseMagicLinkCallback(window.location.search)`
162
+ * on the magic-link landing page. The gateway delivers the opaque token in the
163
+ * query string (`?token=…&project=…`); feed `token` to `verifyMagicLink`.
164
+ * Distinct from `parseSignInCallback`, which parses the SSO hash fragment.
165
+ */
166
+ type MagicLinkCallbackResult = {
167
+ ok: true;
168
+ token: string;
169
+ } | {
170
+ ok: false;
171
+ error: string;
172
+ };
173
+ /**
174
+ * JWT claim shape that `verifyJwt` resolves. Mirrors ch06 §3.2 `subject.*`
175
+ * exposure for project-level JWTs plus the Gate-issued audience / issuer
176
+ * standard claims. Customer apps in Standalone mode consume this via the
177
+ * `verifyJwt` helper without needing the full SDK shape.
178
+ */
179
+ type GateJwtClaims = {
180
+ /** Issuer — the Kilimanjaro gateway base URL or project-bound issuer. */
181
+ iss: string;
182
+ /** Audience — typically the project id (`proj_<base62>`). */
183
+ aud: string | string[];
184
+ /** Subject — the end-user id (`eu_<base62>`). */
185
+ sub: string;
186
+ /** Issued-at (seconds since Unix epoch). */
187
+ iat: number;
188
+ /** Expiry (seconds since Unix epoch). */
189
+ exp: number;
190
+ /** JWT id (for revocation tracking + idempotency). */
191
+ jti?: string;
192
+ /** Project-level role array per ch06; floor is `PROJECT_END_USER`. */
193
+ roles?: ReadonlyArray<string>;
194
+ /** Project-level group memberships per ch04. */
195
+ groups?: ReadonlyArray<string>;
196
+ /** Project-level scope strings — used by `requireScope`. */
197
+ scopes?: ReadonlyArray<string>;
198
+ /** Email of the underlying end-user; convenience copy of the database column. */
199
+ email?: string;
200
+ /**
201
+ * Additional claims that aren't part of the canonical set. The gateway
202
+ * is free to embed custom claims per project policy.
203
+ */
204
+ [key: string]: unknown;
205
+ };
206
+ /**
207
+ * `verifyJwt` result. `valid: true` resolves to the parsed claim set;
208
+ * `valid: false` carries a typed reason for the failure (signature
209
+ * mismatch, expiry, audience mismatch, etc.) without throwing.
210
+ */
211
+ type VerifyJwtResult = {
212
+ valid: true;
213
+ claims: GateJwtClaims;
214
+ } | {
215
+ valid: false;
216
+ reason: VerifyJwtFailure;
217
+ };
218
+ type VerifyJwtFailure = "INVALID_SIGNATURE" | "EXPIRED" | "NOT_YET_VALID" | "AUDIENCE_MISMATCH" | "ISSUER_MISMATCH" | "MALFORMED" | "ALGORITHM_NOT_ALLOWED";
219
+ /**
220
+ * Result of `gate.parseSignInCallback(window.location.hash)` after the
221
+ * browser returns from an IdP-initiated sign-in. On success, carries
222
+ * the session tokens; on failure, carries an error subcode + description.
223
+ */
224
+ type SignInCallbackResult = {
225
+ ok: true;
226
+ accessToken: string;
227
+ refreshToken: string;
228
+ /** ISO 8601 absolute expiry timestamp for the access token. */
229
+ expiresAt: string;
230
+ /** Always "Bearer" today; reserved for future schemes. */
231
+ tokenType: string;
232
+ } | {
233
+ ok: false;
234
+ /** IdpErrorSubcode ("sso_failed" | "idp_provider_not_configured" | …)
235
+ * or the literal "malformed_callback" if the fragment can't be parsed. */
236
+ error: string;
237
+ errorDescription: string;
238
+ };
239
+ /**
240
+ * The project's hosted sign-in page and WebAuthn relying party (`GET /@<projectSlug>/gate/hosted`). Both values come from the platform: an SDK never composes a Gate host from a slug (the label is a hash of
241
+ * ids the SDK does not hold).
242
+ */
243
+ type HostedGateInfo = {
244
+ /** Absolute hosted sign-in URL: the verified custom domain's when there is one, else the project's Gate host. */
245
+ signInUrl: string;
246
+ /** The WebAuthn relying-party id (the project's Gate host). Use it as given; never hard-code it. */
247
+ rpId: string;
248
+ };
249
+
250
+ export type { AuthResult, EndUser, Envelope, GateJwtClaims, HostedGateInfo, MagicLinkCallbackResult, OtpPurpose, RefreshTokenInput, RequestMagicLinkInput, RequestOtpInput, Session, SignInCallbackResult, SignInInput, SignOutInput, SignUpInput, VerifyJwtFailure, VerifyJwtResult, VerifyMagicLinkInput, VerifyOtpInput, VerifySessionResult, WhoamiResult };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@k9o/gate",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Gate — Identity-Aware Kernel SDK (standalone Auth0-parity surface; Native composition via @k9o/core re-export)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./types": {
15
+ "types": "./dist/types.d.ts",
16
+ "import": "./dist/types.js",
17
+ "default": "./dist/types.js"
18
+ },
19
+ "./errors": {
20
+ "types": "./dist/errors.d.ts",
21
+ "import": "./dist/errors.js",
22
+ "default": "./dist/errors.js"
23
+ },
24
+ "./jwt": {
25
+ "types": "./dist/jwt.d.ts",
26
+ "import": "./dist/jwt.js",
27
+ "default": "./dist/jwt.js"
28
+ },
29
+ "./mode": {
30
+ "types": "./dist/mode.d.ts",
31
+ "import": "./dist/mode.js",
32
+ "default": "./dist/mode.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "src",
37
+ "LICENSE",
38
+ "NOTICE",
39
+ "README.md",
40
+ "CHANGELOG.md",
41
+ "dist"
42
+ ],
43
+ "license": "Apache-2.0",
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/k9o-dev",
50
+ "directory": "packages/gate"
51
+ },
52
+ "dependencies": {
53
+ "jose": "^5.9.0"
54
+ },
55
+ "devDependencies": {
56
+ "bun-types": "^1.1.0",
57
+ "typescript": "^5.6.0",
58
+ "tsup": "^8.5.1"
59
+ },
60
+ "module": "./dist/index.js"
61
+ }