@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,717 @@
1
+ import { GateConfig } from './mode.js';
2
+ export { GateMode, configureGate, getGateConfig, resetGateConfig } from './mode.js';
3
+ import { Envelope, SignUpInput, AuthResult, SignInInput, SignOutInput, WhoamiResult, RefreshTokenInput, VerifySessionResult, HostedGateInfo, SignInCallbackResult, RequestOtpInput, VerifyOtpInput, RequestMagicLinkInput, VerifyMagicLinkInput, MagicLinkCallbackResult } from './types.js';
4
+ export { EndUser, GateJwtClaims, OtpPurpose, Session, VerifyJwtFailure, VerifyJwtResult } from './types.js';
5
+ export { VerifyJwtOptions, extractClaims, requireScope, verifyJwt } from './jwt.js';
6
+ export { GateError, GateErrorCode, KErrorWire, gateErrorFromWire } from './errors.js';
7
+
8
+ /**
9
+ * `gate.magicLinkRedirects` sub-namespace — the per-project magic-link
10
+ * redirect-URL allowlist, PROJECT_ADMIN+ managed. Blessed shape (
11
+ * 2026-07-16): `list()`/`add()`/`remove()`, mirroring the
12
+ * `gate.idpProviders.*` sub-namespace convention (gate-admin surface,
13
+ * not the end-user-facing `passwordless.*` send/verify flow).
14
+ *
15
+ * Wraps the live gateway routes (`services/gateway/src/routes/
16
+ * gate-magic-link-redirects.ts`):
17
+ *
18
+ * GET /@<projectSlug>/gate/magic-link-redirects
19
+ * POST /@<projectSlug>/gate/magic-link-redirects { redirectUrl, isDefault? }
20
+ * DELETE /@<projectSlug>/gate/magic-link-redirects/:id
21
+ *
22
+ * One deliberate deviation from the shorthand in the original blessing
23
+ * relay ("remove(redirectUrl)"): the shipped route deletes by the
24
+ * redirect row's `id`, not by re-presenting its `redirectUrl` value —
25
+ * verified directly against the route, not assumed from the relay's
26
+ * informal shape sketch. `remove(id)` matches what the gateway actually
27
+ * does; a `redirectUrl`-keyed removal would need an extra `list()`
28
+ * round-trip to resolve the id first, which the SDK should not do
29
+ * silently on the caller's behalf.
30
+ *
31
+ * Auth: operator JWT with PROJECT_ADMIN+ (or ORG_ADMIN/ORG_OWNER) role —
32
+ * the gateway rejects end-user JWTs and lower-role operators alike
33
+ * (K9O_RBAC_DENY). The allowlist is a security control: registering a
34
+ * redirect target is an admin act, same posture as `idpProviders.*`.
35
+ */
36
+
37
+ /** A single allowlisted magic-link redirect target. */
38
+ type MagicLinkRedirect = {
39
+ id: string;
40
+ redirectUrl: string;
41
+ isDefault: boolean;
42
+ createdAt: string;
43
+ };
44
+ type AddMagicLinkRedirectInput = {
45
+ redirectUrl: string;
46
+ /**
47
+ * When true, this target becomes the default the gateway falls back to
48
+ * when a `requestMagicLink` call doesn't specify one. The gateway
49
+ * atomically demotes any previously-default entry — never two
50
+ * simultaneous defaults.
51
+ */
52
+ isDefault?: boolean;
53
+ };
54
+ type RemoveMagicLinkRedirectResult = {
55
+ removed: true;
56
+ id: string;
57
+ };
58
+
59
+ /**
60
+ * `gate.identityProviders` namespace — project-scoped end-user IdP
61
+ * provider-config management (ch04 §1.6).
62
+ *
63
+ * Wraps the live gateway route family shipped in
64
+ * `services/gateway/src/routes/project-identity-providers.ts`, mounted on
65
+ * the project plane (`<orgSlug>.api.k9o.io/@<projectSlug>/identity-providers`):
66
+ *
67
+ * POST /@<projectSlug>/identity-providers — create (PROJECT_ADMIN)
68
+ * GET /@<projectSlug>/identity-providers — list (READ_ROLES)
69
+ * PATCH /@<projectSlug>/identity-providers/:epcId — update (PROJECT_ADMIN)
70
+ * POST /@<projectSlug>/identity-providers/:epcId/test — testProvider (PROJECT_ADMIN)
71
+ *
72
+ * `create()` + `list()` back the Gate Studio `FederationPresetsPanel`; `update()` backs its
73
+ * `DirectorySyncPanel` (directory-sync settings are read off `list()` and written via `update()`, replacing
74
+ * the unanswered `federation.getDirectorySync`/`updateDirectorySync` bridge messages). Read-one/delete stay
75
+ * unbound until a caller needs them (the SDK does not speculatively surface routes).
76
+ *
77
+ * Auth: operator JWT with PROJECT_ADMIN+ role, server-enforced by the
78
+ * gateway RBAC layer (`requireProjectAdmin`); end-user JWTs (`eu_`-prefixed
79
+ * subjects) are rejected at the route. The bearer is taken from
80
+ * `partnerToken` in config, or per-call via the client's bearer plumbing.
81
+ *
82
+ * 🔴 `clientSecretRef` is a RAW SECRET on the way in, not a reference.
83
+ * Post-v105 the gateway encrypts the submitted value (AES-256-GCM under
84
+ * `K9O_SECRET_STORAGE_MASTER_KEY`) into the opaque `client_secret_enc`
85
+ * handle and persists only that; the field name is retained for wire
86
+ * compatibility but the create handler comment
87
+ * (`project-identity-providers.ts`, the v105 INSERT) is authoritative —
88
+ * there is NO Kibo custody on this path. Reads NEVER carry the secret or its
89
+ * handle: they carry only `clientSecretSet` (a presence boolean,
90
+ * the write-only fix). `serviceAccountKey` (directory sync) follows the same write-only rule.
91
+ */
92
+
93
+ /** Closed end-user-class provider enum per ch04 §1.6 — 11 values since v221 (the project-plane substrate
94
+ * `_sys.end_user_idp_provider_configs`); the org plane carries the same 11 since v222/v223. */
95
+ type IdpProviderKind = "google" | "github" | "microsoft" | "oidc" | "saml" | "scim" | "ldap" | "active_directory" | "apple" | "facebook" | "twitter";
96
+ /**
97
+ * The result of a provider connectivity test (`testProvider`). `ok` is the reachability + metadata-validity
98
+ * verdict; `reason` (present only when `!ok`) is a CLOSED-enum machine code, NEVER the upstream response body
99
+ * or any credential material. v1 is a NO-SECRET probe: it validates the issuer/metadata
100
+ * endpoint is reachable + structurally valid; it never transmits the stored client secret. `credentials_rejected`
101
+ * is deliberately NOT a v1 reason — the secret is never used, so it cannot be validated (a v2 concern).
102
+ */
103
+ type FederationTestProviderResult = {
104
+ ok: boolean;
105
+ reason?: "endpoint_unreachable" | "endpoint_forbidden" | "metadata_invalid" | "secret_not_provisioned" | "kind_unsupported";
106
+ };
107
+ /** Per ch04 §1.6 the provider→tier pairing is structurally fixed:
108
+ * `free_tier_ok ∈ {google, github, microsoft, apple, facebook, twitter}`,
109
+ * `ridge_plus ∈ {oidc, saml, scim, ldap, active_directory}`. The gateway rejects a mismatched
110
+ * pairing (`K9O_INVALID_FIELD_VALUE`); the SDK forwards the caller value
111
+ * verbatim rather than second-guessing it. */
112
+ type IdpTierGating = "free_tier_ok" | "ridge_plus";
113
+ /**
114
+ * The provider-config metadata shape returned by every read surface and
115
+ * mutation response (`shapeIdpConfig`). Mirrors the gateway wire shape
116
+ * one-to-one.
117
+ *
118
+ * Reads carry `clientSecretSet` (presence only), never the secret or a handle (see the module header).
119
+ * `metadataUrl`/`updatedAt` are `null` until set; `attributeMap` defaults to `{}`; `allowedRedirectUris`
120
+ * defaults to `[]`. The `directory*` fields are GS-F7 scheduled directory sync (Google only, ch03 §5.11):
121
+ * settings plus the scheduler's last-run status; the service-account key itself is never returned.
122
+ */
123
+ type IdpConfig = {
124
+ id: string;
125
+ projectId: string;
126
+ idpProvider: IdpProviderKind;
127
+ displayName: string;
128
+ clientId: string;
129
+ /** Whether a client secret is stored. The secret (and any handle to it) is write-only. */
130
+ clientSecretSet: boolean;
131
+ redirectUri: string;
132
+ metadataUrl: string | null;
133
+ attributeMap: Record<string, unknown>;
134
+ tierGating: IdpTierGating;
135
+ enabled: boolean;
136
+ allowedRedirectUris: string[];
137
+ directoryPullEnabled: boolean;
138
+ directorySyncSubject: string | null;
139
+ directorySyncIntervalMins: number | null;
140
+ directorySyncLastRunAt: string | null;
141
+ directorySyncLastStatus: string | null;
142
+ createdAt: string;
143
+ updatedAt: string | null;
144
+ };
145
+ /** GS-F7 directory-sync write fields — accepted on create AND update (one PROJECT_ADMIN surface). */
146
+ type DirectorySyncInput = {
147
+ /** Enabling requires `idpProvider: "google"`, a `serviceAccountKey` (now or already stored) and an interval. */
148
+ directoryPullEnabled?: boolean;
149
+ directorySyncIntervalMins?: number;
150
+ /** The raw Google service-account key JSON. Write-only: encrypted at rest server-side, never returned. */
151
+ serviceAccountKey?: string;
152
+ /** The Workspace admin user the service account impersonates (domain-wide delegation subject). */
153
+ directorySyncSubject?: string;
154
+ };
155
+ /**
156
+ * Create-request body — mirrors the gateway `CreateIdpBody` (t.Object)
157
+ * exactly, including which fields are optional.
158
+ */
159
+ type CreateIdpInput = {
160
+ idpProvider: IdpProviderKind;
161
+ displayName: string;
162
+ clientId: string;
163
+ /**
164
+ * The OAuth2 client secret. Sent over TLS; encrypted-at-rest server-side
165
+ * (AES-256-GCM) and never returned. Named `clientSecretRef` for wire
166
+ * compatibility — it is NOT a reference on the way in (see module header).
167
+ */
168
+ clientSecretRef: string;
169
+ redirectUri: string;
170
+ /** Optional; `null`/omitted for providers that don't use discovery metadata. */
171
+ metadataUrl?: string | null;
172
+ /** Optional claim→attribute mapping; defaults to `{}` server-side. */
173
+ attributeMap?: Record<string, string>;
174
+ tierGating: IdpTierGating;
175
+ /** Defaults to `false` server-side — a provider is created disabled. */
176
+ enabled?: boolean;
177
+ /**
178
+ * Customer SPA origins the SDK gate flow (`gate.signInWithProvider`) may
179
+ * hand session tokens back to. Origin-matched + fail-closed at authorize,
180
+ * so an empty/omitted list means SDK sign-in is disabled for this provider
181
+ * until an origin is registered. Defaults to `[]` server-side.
182
+ */
183
+ allowedRedirectUris?: string[] | null;
184
+ } & DirectorySyncInput;
185
+ /**
186
+ * Update-request body — mirrors the gateway `PatchIdpBody` exactly: every field optional; `idpProvider` and
187
+ * `tierGating` are immutable after create. An empty patch returns the current config unchanged.
188
+ *
189
+ * 🔴 For `oidc`/`microsoft`, changing `metadataUrl` WITHOUT `clientSecretRef` in the same patch CLEARS the
190
+ * stored client secret (the gateway's secret-invalidate-on-host-change control, ch04 §1.6): re-supply the
191
+ * secret whenever the metadata URL changes.
192
+ */
193
+ type PatchIdpInput = {
194
+ displayName?: string;
195
+ clientId?: string;
196
+ /** The raw client secret (write-only; see the module header). */
197
+ clientSecretRef?: string;
198
+ redirectUri?: string;
199
+ metadataUrl?: string | null;
200
+ attributeMap?: Record<string, string>;
201
+ enabled?: boolean;
202
+ allowedRedirectUris?: string[] | null;
203
+ } & DirectorySyncInput;
204
+ /** Factory — produces an `identityProviders` namespace bound to the supplied
205
+ * config getter. Used by both the `gate` singleton + `createGateClient`. */
206
+ declare function makeIdentityProviders(getConfig: () => GateConfig): {
207
+ /**
208
+ * Register a new end-user IdP provider config for the configured
209
+ * project. PROJECT_ADMIN+ required (server-enforced). The plaintext
210
+ * client secret is encrypted-at-rest server-side; the returned config
211
+ * carries only the opaque secret handle.
212
+ *
213
+ * The gateway validates the `idpProvider`→`tierGating` pairing per ch04
214
+ * §1.6 and rejects a mismatch with `K9O_INVALID_FIELD_VALUE`.
215
+ */
216
+ create(input: CreateIdpInput): Promise<Envelope<IdpConfig>>;
217
+ /**
218
+ * List the configured end-user IdP provider configs for the project.
219
+ * READ_ROLES-gated server-side (PROJECT_ADMIN/EDITOR/MEMBER/VIEWER/AUTHOR/
220
+ * ANALYST) — the SAME shape + roles as the existing GET route
221
+ * (`listProviders` returns the existing route's shape, no
222
+ * confidentiality-narrower endpoint). Each `IdpConfig` carries the opaque
223
+ * secret handle, never the secret. Callers that only need the summary
224
+ * (`id`/`idpProvider`/`displayName`/`enabled`/`tierGating`) pick fields off
225
+ * the result — no server change needed for that shaping.
226
+ */
227
+ list(): Promise<Envelope<IdpConfig[]>>;
228
+ /**
229
+ * Test connectivity to a configured provider by id — a NO-SECRET reachability + metadata-validity probe
230
+ * (no-secret): the gateway resolve-and-pins the issuer/metadata endpoint (SSRF-fenced) and
231
+ * validates it structurally, WITHOUT decrypting or transmitting the stored client secret. Returns
232
+ * `{ok, reason?}`; the `reason` is a closed machine code, never the upstream body or any credential.
233
+ * PROJECT_ADMIN required (server-enforced) — it triggers live external egress on the platform's behalf.
234
+ */
235
+ /**
236
+ * Update a provider config by id (PATCH; PROJECT_ADMIN, server-enforced). Partial: only supplied fields
237
+ * change; `idpProvider`/`tierGating` are immutable. Returns the updated config (secrets never included).
238
+ * GS-F7: write directory-sync settings here and read their status back off `list()`. See `PatchIdpInput`
239
+ * for the metadataUrl-change-clears-secret rule.
240
+ */
241
+ update(configId: string, patch: PatchIdpInput): Promise<Envelope<IdpConfig>>;
242
+ testProvider(configId: string): Promise<Envelope<FederationTestProviderResult>>;
243
+ };
244
+ type IdentityProvidersNamespace = ReturnType<typeof makeIdentityProviders>;
245
+
246
+ /** Factory — produces a `gate.*` namespace bound to the supplied config getter.
247
+ * Used by both the module-level `gate` singleton (config from `configureGate`)
248
+ * and `createGateClient(config)` (config supplied at construction time). */
249
+ declare function makeGateNamespace(getConfig: () => GateConfig): {
250
+ /**
251
+ * Create a new end-user identity for the configured project. Returns
252
+ * the new user record + an initial session per ch01 §12.2. Emits a
253
+ * `data.write.auth.signup` audit event per ch01 §12.5.
254
+ *
255
+ * Errors (closed taxonomy per ch01 §2):
256
+ * - `EMAIL_ALREADY_REGISTERED` (409) when the email collides
257
+ * - `INVALID_FIELD_VALUE` (400) on schema violation (password policy, malformed email)
258
+ * - `TIER_FEATURE_GATED` (403) when the project's tier blocks email/password
259
+ */
260
+ signUp(input: SignUpInput): Promise<Envelope<AuthResult>>;
261
+ /**
262
+ * Sign in an existing end-user with email + password. Returns the
263
+ * user record + a fresh session. Emits a `data.write.auth.signin`
264
+ * audit event.
265
+ *
266
+ * Errors:
267
+ * - `INVALID_CREDENTIALS` (401) on bad email/password (ambiguous on
268
+ * purpose — does not distinguish "user not found" vs "password
269
+ * wrong" to avoid enumeration)
270
+ */
271
+ signIn(input: SignInInput): Promise<Envelope<AuthResult>>;
272
+ /**
273
+ * Sign out — revoke the bound refresh token (or an explicit one, if
274
+ * supplied). Emits a `data.write.auth.signout` audit event.
275
+ *
276
+ * Errors:
277
+ * - `UNAUTHENTICATED` (401) when no session is present
278
+ * - `REFRESH_TOKEN_INVALID` (400) when the supplied refresh token
279
+ * doesn't match the bearer's session
280
+ */
281
+ signOut(input?: SignOutInput): Promise<Envelope<{
282
+ revoked: boolean;
283
+ }>>;
284
+ /**
285
+ * Return the caller's identity claims from the bearer JWT. Useful in
286
+ * Standalone mode where the SDK consumer wants to surface the user's
287
+ * roles / groups without parsing the JWT itself.
288
+ *
289
+ * Errors:
290
+ * - `UNAUTHENTICATED` (401) when no bearer is present
291
+ * - `SESSION_EXPIRED` (401) when the bearer's exp has passed
292
+ */
293
+ whoami(): Promise<Envelope<WhoamiResult>>;
294
+ /**
295
+ * Rotate the access token using a refresh token. Returns a fresh
296
+ * session pair (new access token + same refresh token; gateway may
297
+ * also rotate the refresh token per project policy).
298
+ *
299
+ * Errors:
300
+ * - `REFRESH_TOKEN_INVALID` (400) when the refresh token is unknown,
301
+ * expired, or revoked
302
+ */
303
+ refreshToken(input: RefreshTokenInput): Promise<Envelope<{
304
+ session: AuthResult["session"];
305
+ }>>;
306
+ /**
307
+ * Check whether the bearer's session is currently valid. Does NOT
308
+ * throw on expiry or invalid token — returns `{ valid: false }`
309
+ * inside the envelope. The intended use is health-checking a session
310
+ * without surfacing an error to the end-user (the SDK consumer
311
+ * typically wants to redirect to sign-in rather than surface a 401).
312
+ */
313
+ verifySession(): Promise<Envelope<VerifySessionResult>>;
314
+ /**
315
+ * The project's hosted sign-in page URL and WebAuthn `rpId`. Unauthenticated and cacheable; no credential is sent,
316
+ * even when a partner token is configured. Take both values as given: an unknown org, a deleted project and a
317
+ * non-Gate project all answer the same `PROJECT_NOT_FOUND`, so a failure here says nothing about which.
318
+ */
319
+ hosted(): Promise<Envelope<HostedGateInfo>>;
320
+ /**
321
+ * Initiate an external-IdP sign-in flow (Google / GitHub / Microsoft).
322
+ * The SDK POSTs to the gateway's authorize endpoint, receives the
323
+ * provider's authorization URL with a signed-state JWT (carrying PKCE
324
+ * verifier + the supplied `redirectUri`), and redirects the browser.
325
+ *
326
+ * After the user authenticates with the IdP, the gateway processes the
327
+ * callback and redirects the browser back to `redirectUri` with session
328
+ * tokens in the URL fragment. Use `parseSignInCallback()` on the
329
+ * destination page to extract them.
330
+ *
331
+ * Example:
332
+ * await gate.signInWithProvider({
333
+ * provider: 'google',
334
+ * redirectUri: 'https://app.example.com/auth/callback',
335
+ * });
336
+ * // …user lands at /auth/callback#access_token=…&refresh_token=…
337
+ *
338
+ * Generic OIDC requires authorization-endpoint discovery and is not
339
+ * yet wired; use a hosted provider (google/github/microsoft) for now.
340
+ */
341
+ signInWithProvider(input: {
342
+ provider: "google" | "github" | "microsoft" | "oidc" | "saml";
343
+ redirectUri: string;
344
+ }): Promise<void>;
345
+ /**
346
+ * Parse session tokens out of the URL fragment delivered by an
347
+ * IdP callback redirect. Returns the session payload on success or an
348
+ * error object on failure. Pass `window.location.hash` (or any
349
+ * `#`-prefixed string) — leading `#` is stripped automatically.
350
+ *
351
+ * Side-effect-free: does not call the gateway and does not mutate
352
+ * any session storage. The consumer is responsible for persisting
353
+ * the returned tokens (e.g. via the app's auth context).
354
+ */
355
+ parseSignInCallback(hash: string): SignInCallbackResult;
356
+ /**
357
+ * Passwordless sub-namespace per ch01 §12.1 — OTP + Magic Link flows
358
+ * for email-based authentication without a stored password.
359
+ *
360
+ * Wire endpoints (LIVE on the Gate end-user plane — verified against
361
+ * staging): OTP → `POST /@<projectSlug>/gate/otp/{request,verify}`
362
+ * (routes/otp.ts), Magic Link → `POST /@<projectSlug>/gate/magic-link/
363
+ * {send,redeem}` (routes/magic-link.ts). NOTE: these are the gateway's
364
+ * canonical paths; the SDK ergonomic method names (`requestMagicLink`/
365
+ * `verifyMagicLink`) do not mirror the path segments (`send`/`redeem`).
366
+ * (The earlier `passwordless/*` SDK paths were interface-first scaffold
367
+ * that never matched the shipped gateway routes — reconciled here.)
368
+ */
369
+ passwordless: {
370
+ /**
371
+ * Request a one-time passcode delivered to the supplied email.
372
+ * The OTP is short-lived (typically 5 min per ch03 §3.4) and
373
+ * single-use; redemption via `verifyOtp` mints a session.
374
+ */
375
+ requestOtp(input: RequestOtpInput): Promise<Envelope<{
376
+ requested: boolean;
377
+ }>>;
378
+ /**
379
+ * Verify a one-time passcode previously sent via `requestOtp`.
380
+ * On success returns the same `AuthResult` shape as `signIn` —
381
+ * a fresh session bound to the end-user identity.
382
+ *
383
+ * Errors:
384
+ * - INVALID_CREDENTIALS (401) on wrong / expired / consumed OTP
385
+ * (ambiguous on purpose — enumeration defense)
386
+ */
387
+ verifyOtp(input: VerifyOtpInput): Promise<Envelope<AuthResult>>;
388
+ /**
389
+ * Request a magic-link emailed to the supplied address. The link
390
+ * carries an opaque short-lived token (typically 15 min per ch03
391
+ * §3.3); clicking the link lands at the customer's chosen
392
+ * redirect URL with the token in the query string, which the
393
+ * customer's frontend then exchanges via `verifyMagicLink`.
394
+ */
395
+ requestMagicLink(input: RequestMagicLinkInput): Promise<Envelope<{
396
+ requested: boolean;
397
+ }>>;
398
+ /**
399
+ * Verify a magic-link token previously delivered via
400
+ * `requestMagicLink`. On success returns the same `AuthResult`
401
+ * shape as `signIn`.
402
+ *
403
+ * Errors:
404
+ * - INVALID_CREDENTIALS (401) on wrong / expired / consumed
405
+ * token (same enumeration-defense rationale as verifyOtp)
406
+ */
407
+ verifyMagicLink(input: VerifyMagicLinkInput): Promise<Envelope<AuthResult>>;
408
+ /**
409
+ * Extract the opaque magic-link token from the landing-page URL's query
410
+ * string (the gateway redirects to `<configured-redirect>?token=…&project=…`).
411
+ * Pure + side-effect-free — pass `window.location.search`. Feed the
412
+ * returned `token` to `verifyMagicLink({ token })` to mint the session.
413
+ *
414
+ * This is the magic-link counterpart to `parseSignInCallback` (which parses
415
+ * the SSO *hash fragment* for session tokens) — magic-link delivers an
416
+ * opaque token in the *query string*, so the two are not interchangeable.
417
+ */
418
+ parseMagicLinkCallback(search: string): MagicLinkCallbackResult;
419
+ };
420
+ /**
421
+ * Project-scoped end-user IdP provider-config management (ch04 §1.6).
422
+ * PROJECT_ADMIN+ required; end-user JWTs rejected at the route. Only
423
+ * `create()` is bound — the REST create the Gate Studio
424
+ * `FederationPresetsPanel` routes to when it drops the `gate_flows`
425
+ * `__addFromPreset` sentinel. Gate-admin surface.
426
+ */
427
+ identityProviders: {
428
+ create(input: CreateIdpInput): Promise<Envelope<IdpConfig>>;
429
+ list(): Promise<Envelope<IdpConfig[]>>;
430
+ update(configId: string, patch: PatchIdpInput): Promise<Envelope<IdpConfig>>;
431
+ testProvider(configId: string): Promise<Envelope<FederationTestProviderResult>>;
432
+ };
433
+ /**
434
+ * Per-project magic-link redirect-URL allowlist management (blessed
435
+ * shape). Gate-admin surface alongside `identityProviders`
436
+ * — PROJECT_ADMIN+ required, distinct from the end-user-facing
437
+ * `passwordless.requestMagicLink`/`verifyMagicLink` send/verify flow.
438
+ */
439
+ magicLinkRedirects: {
440
+ list(): Promise<Envelope<{
441
+ redirects: MagicLinkRedirect[];
442
+ }>>;
443
+ add(input: AddMagicLinkRedirectInput): Promise<Envelope<{
444
+ redirect: MagicLinkRedirect;
445
+ }>>;
446
+ remove(id: string): Promise<Envelope<RemoveMagicLinkRedirectResult>>;
447
+ };
448
+ };
449
+ /** Singleton bound to the module-level `configureGate()` config.
450
+ * v0.4.0+: per-instance clients via `createGateClient(config)`. */
451
+ declare const gate: {
452
+ /**
453
+ * Create a new end-user identity for the configured project. Returns
454
+ * the new user record + an initial session per ch01 §12.2. Emits a
455
+ * `data.write.auth.signup` audit event per ch01 §12.5.
456
+ *
457
+ * Errors (closed taxonomy per ch01 §2):
458
+ * - `EMAIL_ALREADY_REGISTERED` (409) when the email collides
459
+ * - `INVALID_FIELD_VALUE` (400) on schema violation (password policy, malformed email)
460
+ * - `TIER_FEATURE_GATED` (403) when the project's tier blocks email/password
461
+ */
462
+ signUp(input: SignUpInput): Promise<Envelope<AuthResult>>;
463
+ /**
464
+ * Sign in an existing end-user with email + password. Returns the
465
+ * user record + a fresh session. Emits a `data.write.auth.signin`
466
+ * audit event.
467
+ *
468
+ * Errors:
469
+ * - `INVALID_CREDENTIALS` (401) on bad email/password (ambiguous on
470
+ * purpose — does not distinguish "user not found" vs "password
471
+ * wrong" to avoid enumeration)
472
+ */
473
+ signIn(input: SignInInput): Promise<Envelope<AuthResult>>;
474
+ /**
475
+ * Sign out — revoke the bound refresh token (or an explicit one, if
476
+ * supplied). Emits a `data.write.auth.signout` audit event.
477
+ *
478
+ * Errors:
479
+ * - `UNAUTHENTICATED` (401) when no session is present
480
+ * - `REFRESH_TOKEN_INVALID` (400) when the supplied refresh token
481
+ * doesn't match the bearer's session
482
+ */
483
+ signOut(input?: SignOutInput): Promise<Envelope<{
484
+ revoked: boolean;
485
+ }>>;
486
+ /**
487
+ * Return the caller's identity claims from the bearer JWT. Useful in
488
+ * Standalone mode where the SDK consumer wants to surface the user's
489
+ * roles / groups without parsing the JWT itself.
490
+ *
491
+ * Errors:
492
+ * - `UNAUTHENTICATED` (401) when no bearer is present
493
+ * - `SESSION_EXPIRED` (401) when the bearer's exp has passed
494
+ */
495
+ whoami(): Promise<Envelope<WhoamiResult>>;
496
+ /**
497
+ * Rotate the access token using a refresh token. Returns a fresh
498
+ * session pair (new access token + same refresh token; gateway may
499
+ * also rotate the refresh token per project policy).
500
+ *
501
+ * Errors:
502
+ * - `REFRESH_TOKEN_INVALID` (400) when the refresh token is unknown,
503
+ * expired, or revoked
504
+ */
505
+ refreshToken(input: RefreshTokenInput): Promise<Envelope<{
506
+ session: AuthResult["session"];
507
+ }>>;
508
+ /**
509
+ * Check whether the bearer's session is currently valid. Does NOT
510
+ * throw on expiry or invalid token — returns `{ valid: false }`
511
+ * inside the envelope. The intended use is health-checking a session
512
+ * without surfacing an error to the end-user (the SDK consumer
513
+ * typically wants to redirect to sign-in rather than surface a 401).
514
+ */
515
+ verifySession(): Promise<Envelope<VerifySessionResult>>;
516
+ /**
517
+ * The project's hosted sign-in page URL and WebAuthn `rpId`. Unauthenticated and cacheable; no credential is sent,
518
+ * even when a partner token is configured. Take both values as given: an unknown org, a deleted project and a
519
+ * non-Gate project all answer the same `PROJECT_NOT_FOUND`, so a failure here says nothing about which.
520
+ */
521
+ hosted(): Promise<Envelope<HostedGateInfo>>;
522
+ /**
523
+ * Initiate an external-IdP sign-in flow (Google / GitHub / Microsoft).
524
+ * The SDK POSTs to the gateway's authorize endpoint, receives the
525
+ * provider's authorization URL with a signed-state JWT (carrying PKCE
526
+ * verifier + the supplied `redirectUri`), and redirects the browser.
527
+ *
528
+ * After the user authenticates with the IdP, the gateway processes the
529
+ * callback and redirects the browser back to `redirectUri` with session
530
+ * tokens in the URL fragment. Use `parseSignInCallback()` on the
531
+ * destination page to extract them.
532
+ *
533
+ * Example:
534
+ * await gate.signInWithProvider({
535
+ * provider: 'google',
536
+ * redirectUri: 'https://app.example.com/auth/callback',
537
+ * });
538
+ * // …user lands at /auth/callback#access_token=…&refresh_token=…
539
+ *
540
+ * Generic OIDC requires authorization-endpoint discovery and is not
541
+ * yet wired; use a hosted provider (google/github/microsoft) for now.
542
+ */
543
+ signInWithProvider(input: {
544
+ provider: "google" | "github" | "microsoft" | "oidc" | "saml";
545
+ redirectUri: string;
546
+ }): Promise<void>;
547
+ /**
548
+ * Parse session tokens out of the URL fragment delivered by an
549
+ * IdP callback redirect. Returns the session payload on success or an
550
+ * error object on failure. Pass `window.location.hash` (or any
551
+ * `#`-prefixed string) — leading `#` is stripped automatically.
552
+ *
553
+ * Side-effect-free: does not call the gateway and does not mutate
554
+ * any session storage. The consumer is responsible for persisting
555
+ * the returned tokens (e.g. via the app's auth context).
556
+ */
557
+ parseSignInCallback(hash: string): SignInCallbackResult;
558
+ /**
559
+ * Passwordless sub-namespace per ch01 §12.1 — OTP + Magic Link flows
560
+ * for email-based authentication without a stored password.
561
+ *
562
+ * Wire endpoints (LIVE on the Gate end-user plane — verified against
563
+ * staging): OTP → `POST /@<projectSlug>/gate/otp/{request,verify}`
564
+ * (routes/otp.ts), Magic Link → `POST /@<projectSlug>/gate/magic-link/
565
+ * {send,redeem}` (routes/magic-link.ts). NOTE: these are the gateway's
566
+ * canonical paths; the SDK ergonomic method names (`requestMagicLink`/
567
+ * `verifyMagicLink`) do not mirror the path segments (`send`/`redeem`).
568
+ * (The earlier `passwordless/*` SDK paths were interface-first scaffold
569
+ * that never matched the shipped gateway routes — reconciled here.)
570
+ */
571
+ passwordless: {
572
+ /**
573
+ * Request a one-time passcode delivered to the supplied email.
574
+ * The OTP is short-lived (typically 5 min per ch03 §3.4) and
575
+ * single-use; redemption via `verifyOtp` mints a session.
576
+ */
577
+ requestOtp(input: RequestOtpInput): Promise<Envelope<{
578
+ requested: boolean;
579
+ }>>;
580
+ /**
581
+ * Verify a one-time passcode previously sent via `requestOtp`.
582
+ * On success returns the same `AuthResult` shape as `signIn` —
583
+ * a fresh session bound to the end-user identity.
584
+ *
585
+ * Errors:
586
+ * - INVALID_CREDENTIALS (401) on wrong / expired / consumed OTP
587
+ * (ambiguous on purpose — enumeration defense)
588
+ */
589
+ verifyOtp(input: VerifyOtpInput): Promise<Envelope<AuthResult>>;
590
+ /**
591
+ * Request a magic-link emailed to the supplied address. The link
592
+ * carries an opaque short-lived token (typically 15 min per ch03
593
+ * §3.3); clicking the link lands at the customer's chosen
594
+ * redirect URL with the token in the query string, which the
595
+ * customer's frontend then exchanges via `verifyMagicLink`.
596
+ */
597
+ requestMagicLink(input: RequestMagicLinkInput): Promise<Envelope<{
598
+ requested: boolean;
599
+ }>>;
600
+ /**
601
+ * Verify a magic-link token previously delivered via
602
+ * `requestMagicLink`. On success returns the same `AuthResult`
603
+ * shape as `signIn`.
604
+ *
605
+ * Errors:
606
+ * - INVALID_CREDENTIALS (401) on wrong / expired / consumed
607
+ * token (same enumeration-defense rationale as verifyOtp)
608
+ */
609
+ verifyMagicLink(input: VerifyMagicLinkInput): Promise<Envelope<AuthResult>>;
610
+ /**
611
+ * Extract the opaque magic-link token from the landing-page URL's query
612
+ * string (the gateway redirects to `<configured-redirect>?token=…&project=…`).
613
+ * Pure + side-effect-free — pass `window.location.search`. Feed the
614
+ * returned `token` to `verifyMagicLink({ token })` to mint the session.
615
+ *
616
+ * This is the magic-link counterpart to `parseSignInCallback` (which parses
617
+ * the SSO *hash fragment* for session tokens) — magic-link delivers an
618
+ * opaque token in the *query string*, so the two are not interchangeable.
619
+ */
620
+ parseMagicLinkCallback(search: string): MagicLinkCallbackResult;
621
+ };
622
+ /**
623
+ * Project-scoped end-user IdP provider-config management (ch04 §1.6).
624
+ * PROJECT_ADMIN+ required; end-user JWTs rejected at the route. Only
625
+ * `create()` is bound — the REST create the Gate Studio
626
+ * `FederationPresetsPanel` routes to when it drops the `gate_flows`
627
+ * `__addFromPreset` sentinel. Gate-admin surface.
628
+ */
629
+ identityProviders: {
630
+ create(input: CreateIdpInput): Promise<Envelope<IdpConfig>>;
631
+ list(): Promise<Envelope<IdpConfig[]>>;
632
+ update(configId: string, patch: PatchIdpInput): Promise<Envelope<IdpConfig>>;
633
+ testProvider(configId: string): Promise<Envelope<FederationTestProviderResult>>;
634
+ };
635
+ /**
636
+ * Per-project magic-link redirect-URL allowlist management (blessed
637
+ * shape). Gate-admin surface alongside `identityProviders`
638
+ * — PROJECT_ADMIN+ required, distinct from the end-user-facing
639
+ * `passwordless.requestMagicLink`/`verifyMagicLink` send/verify flow.
640
+ */
641
+ magicLinkRedirects: {
642
+ list(): Promise<Envelope<{
643
+ redirects: MagicLinkRedirect[];
644
+ }>>;
645
+ add(input: AddMagicLinkRedirectInput): Promise<Envelope<{
646
+ redirect: MagicLinkRedirect;
647
+ }>>;
648
+ remove(id: string): Promise<Envelope<RemoveMagicLinkRedirectResult>>;
649
+ };
650
+ };
651
+ /** Convenience type alias mirroring the `gate` runtime export.
652
+ * Identical shape as the per-instance `GateClient` returned from
653
+ * `createGateClient`. */
654
+ type GateNamespace = ReturnType<typeof makeGateNamespace>;
655
+
656
+ /**
657
+ * `createGateClient(config)` — per-instance Gate SDK factory (v0.4.0).
658
+ *
659
+ * Returns a self-contained `GateClient` that does not depend on the
660
+ * module-level `configureGate()` singleton. Multiple clients can coexist
661
+ * in the same process, each scoped to its own project + baseUrl + mode —
662
+ * useful for multi-tenant Backends-For-Frontends (one client per
663
+ * incoming request's resolved tenant) or for apps that maintain both a
664
+ * Standalone and Native binding against the same SDK instance.
665
+ *
666
+ * Per v2.5.68 URL grammar, supply `projectSlug` for
667
+ * canonical `/@<slug>/gate/...` routing. Slug is the only customer-facing
668
+ * project addressing form — project ids are internal-only
669
+ * (`feedback_slug_addressing_v2_5_68`); the retired `/v2/projects/...`
670
+ * mounts were dropped at pass17 (`feedback_v2_path_is_an_error`).
671
+ *
672
+ * Example:
673
+ * const client = createGateClient({
674
+ * baseUrl: 'https://formelo.api.k9o.io',
675
+ * projectSlug: 'partner-console-staging',
676
+ * mode: 'native',
677
+ * });
678
+ * await client.signIn({ email, password });
679
+ */
680
+
681
+ /** Shape of the per-instance client returned by `createGateClient`. */
682
+ type GateClient = ReturnType<typeof makeGateNamespace>;
683
+ /** Construct a Gate client bound to the supplied config. The config is
684
+ * validated up-front and stored on the instance; subsequent calls do not
685
+ * re-read process-level state. Throws `TypeError` on invalid config. */
686
+ declare function createGateClient(config: GateConfig): GateClient;
687
+
688
+ /**
689
+ * Regional host resolution.
690
+ *
691
+ * The region-free org host `<org>.api[-staging].k9o.io` redirects GET/HEAD only and REFUSES a request that carries a
692
+ * body, an `Authorization` header or a cookie (`400 K9O_INVALID_REQUEST_SHAPE`, `region_free_host_takes_no_credentials`),
693
+ * so credentials must go to `<org>.api[-staging]-<rgn>.k9o.io`. An SDK learns the region with ONE unauthenticated
694
+ * `HEAD https://<org>.api[-staging].k9o.io/`, takes the `308` `Location`, and caches the regional origin.
695
+ *
696
+ * Properties this module holds:
697
+ * - Only a region-free org host (shape `<org>.api.k9o.io` or `<org>.api-staging.k9o.io`) is ever probed. A regional,
698
+ * custom, self-hosted or dev baseUrl is used exactly as given.
699
+ * - The probe carries NO credential and none of the configured default headers (they may hold one).
700
+ * - The `Location` is trusted only if it is `https`, has no userinfo/port, and its host is exactly the SAME org on
701
+ * the SAME apex family with a 4-character region code. Anything else is ignored and the configured baseUrl stays,
702
+ * so a hostile redirect can never receive a credential.
703
+ * - Not a redirect (the platform has not cut over, or the host answers directly), or any failure: the configured
704
+ * baseUrl stays and the server's own refusal, if any, is what the caller sees. A definite answer is cached for the
705
+ * life of the client's config; a network failure is not cached.
706
+ */
707
+ /** The slice of a client config this module needs (structural, so core and gate share one implementation). */
708
+ type RegionConfig = {
709
+ baseUrl: string;
710
+ fetch?: typeof globalThis.fetch | undefined;
711
+ };
712
+ /** The regional origin a `Location` may name for `host`, or null if it is not exactly that. Pure. */
713
+ declare function regionalOriginFromLocation(host: string, location: string, base: string): string | null;
714
+ /** The origin requests for this client go to: the cached regional origin once learned, else the configured baseUrl. */
715
+ declare function resolveRegionalOrigin(config: RegionConfig): Promise<string>;
716
+
717
+ export { AuthResult, type CreateIdpInput, type DirectorySyncInput, Envelope, type GateClient, GateConfig, type GateNamespace, HostedGateInfo, type IdentityProvidersNamespace, type IdpConfig, type IdpProviderKind, type IdpTierGating, MagicLinkCallbackResult, type PatchIdpInput, RefreshTokenInput, type RegionConfig, RequestMagicLinkInput, RequestOtpInput, SignInCallbackResult, SignInInput, SignOutInput, SignUpInput, VerifyMagicLinkInput, VerifyOtpInput, VerifySessionResult, WhoamiResult, createGateClient, gate, regionalOriginFromLocation, resolveRegionalOrigin };