@k9o/gate 1.1.1 → 1.3.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.
package/dist/index.d.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import { GateConfig } from './mode.js';
2
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';
3
+ import { Envelope, SignUpInput, AuthResult, SignInInput, SignOutInput, SignOutResult, WhoamiResult, RefreshTokenInput, RefreshResult, SignInCallbackResult, RequestOtpInput, RequestOtpResult, VerifyOtpInput, VerifyOtpResult, RequestMagicLinkInput, RequestMagicLinkResult, VerifyMagicLinkInput, VerifyMagicLinkResult, MagicLinkCallbackResult } from './types.js';
4
4
  export { EndUser, GateJwtClaims, OtpPurpose, Session, VerifyJwtFailure, VerifyJwtResult } from './types.js';
5
- export { VerifyJwtOptions, extractClaims, requireScope, verifyJwt } from './jwt.js';
5
+ export { VerifyJwtOptions, extractClaims, fetchJwks, requireScope, verifyJwt } from './jwt.js';
6
6
  export { GateError, GateErrorCode, KErrorWire, gateErrorFromWire } from './errors.js';
7
7
 
8
8
  /**
9
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
10
+ * redirect-URL allowlist, PROJECT_ADMIN+ managed. `list()`/`add()`/`remove()`, mirroring the
12
11
  * `gate.idpProviders.*` sub-namespace convention (gate-admin surface,
13
12
  * not the end-user-facing `passwordless.*` send/verify flow).
14
13
  *
@@ -80,18 +79,15 @@ type RemoveMagicLinkRedirectResult = {
80
79
  * `partnerToken` in config, or per-call via the client's bearer plumbing.
81
80
  *
82
81
  * 🔴 `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
82
+ * The gateway encrypts the submitted value (AES-256-GCM under
84
83
  * `K9O_SECRET_STORAGE_MASTER_KEY`) into the opaque `client_secret_enc`
85
84
  * 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
85
+ * compatibility and there is no Kibo custody on this path. Reads NEVER carry the secret or its
89
86
  * handle: they carry only `clientSecretSet` (a presence boolean,
90
87
  * the write-only fix). `serviceAccountKey` (directory sync) follows the same write-only rule.
91
88
  */
92
89
 
93
- /** Closed end-user-class provider enum — 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. */
90
+ /** Closed end-user-class provider enum (11 values). */
95
91
  type IdpProviderKind = "google" | "github" | "microsoft" | "oidc" | "saml" | "scim" | "ldap" | "active_directory" | "apple" | "facebook" | "twitter";
96
92
  /**
97
93
  * The result of a provider connectivity test (`testProvider`). `ok` is the reachability + metadata-validity
@@ -104,7 +100,7 @@ type FederationTestProviderResult = {
104
100
  ok: boolean;
105
101
  reason?: "endpoint_unreachable" | "endpoint_forbidden" | "metadata_invalid" | "secret_not_provisioned" | "kind_unsupported";
106
102
  };
107
- /** Per the provider→tier pairing is structurally fixed:
103
+ /** The provider→tier pairing is structurally fixed:
108
104
  * `free_tier_ok ∈ {google, github, microsoft, apple, facebook, twitter}`,
109
105
  * `ridge_plus ∈ {oidc, saml, scim, ldap, active_directory}`. The gateway rejects a mismatched
110
106
  * pairing (`K9O_INVALID_FIELD_VALUE`); the SDK forwards the caller value
@@ -117,7 +113,7 @@ type IdpTierGating = "free_tier_ok" | "ridge_plus";
117
113
  *
118
114
  * Reads carry `clientSecretSet` (presence only), never the secret or a handle (see the module header).
119
115
  * `metadataUrl`/`updatedAt` are `null` until set; `attributeMap` defaults to `{}`; `allowedRedirectUris`
120
- * defaults to `[]`. The `directory*` fields are GS-F7 scheduled directory sync (Google only,):
116
+ * defaults to `[]`. The `directory*` fields are scheduled directory sync (Google only):
121
117
  * settings plus the scheduler's last-run status; the service-account key itself is never returned.
122
118
  */
123
119
  type IdpConfig = {
@@ -142,7 +138,7 @@ type IdpConfig = {
142
138
  createdAt: string;
143
139
  updatedAt: string | null;
144
140
  };
145
- /** GS-F7 directory-sync write fields — accepted on create AND update (one PROJECT_ADMIN surface). */
141
+ /** Directory-sync write fields — accepted on create AND update (one PROJECT_ADMIN surface). */
146
142
  type DirectorySyncInput = {
147
143
  /** Enabling requires `idpProvider: "google"`, a `serviceAccountKey` (now or already stored) and an interval. */
148
144
  directoryPullEnabled?: boolean;
@@ -187,7 +183,7 @@ type CreateIdpInput = {
187
183
  * `tierGating` are immutable after create. An empty patch returns the current config unchanged.
188
184
  *
189
185
  * 🔴 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,): re-supply the
186
+ * stored client secret (the gateway's secret-invalidate-on-host-change control): re-supply the
191
187
  * secret whenever the metadata URL changes.
192
188
  */
193
189
  type PatchIdpInput = {
@@ -235,7 +231,7 @@ declare function makeIdentityProviders(getConfig: () => GateConfig): {
235
231
  /**
236
232
  * Update a provider config by id (PATCH; PROJECT_ADMIN, server-enforced). Partial: only supplied fields
237
233
  * 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`
234
+ * Write directory-sync settings here and read their status back off `list()`. See `PatchIdpInput`
239
235
  * for the metadataUrl-change-clears-secret rule.
240
236
  */
241
237
  update(configId: string, patch: PatchIdpInput): Promise<Envelope<IdpConfig>>;
@@ -248,75 +244,43 @@ type IdentityProvidersNamespace = ReturnType<typeof makeIdentityProviders>;
248
244
  * and `createGateClient(config)` (config supplied at construction time). */
249
245
  declare function makeGateNamespace(getConfig: () => GateConfig): {
250
246
  /**
251
- * Create a new end-user identity for the configured project. Returns
252
- * the new user record + an initial session. Emits a
253
- * `data.write.auth.signup` audit event.
247
+ * Create a new end-user for the configured project. Returns the new user and an initial session. The body is
248
+ * closed: `{ email, password, displayName? }`.
254
249
  *
255
- * Errors (closed taxonomy):
250
+ * Errors:
256
251
  * - `EMAIL_ALREADY_REGISTERED` (409) when the email collides
257
- * - `INVALID_FIELD_VALUE` (400) on schema violation (password policy, malformed email)
252
+ * - `INVALID_FIELD_VALUE` (400) on a schema violation (password policy, malformed email, unknown field)
258
253
  * - `TIER_FEATURE_GATED` (403) when the project's tier blocks email/password
259
254
  */
260
255
  signUp(input: SignUpInput): Promise<Envelope<AuthResult>>;
261
256
  /**
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.
257
+ * Sign in an existing end-user with email and password. Returns the user and a fresh session.
265
258
  *
266
259
  * 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)
260
+ * - `INVALID_CREDENTIALS` (401) on a bad email or password (the same answer for both, on purpose)
270
261
  */
271
262
  signIn(input: SignInInput): Promise<Envelope<AuthResult>>;
272
263
  /**
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
264
+ * Sign out: revoke the session that the given refresh token belongs to. The route needs the refresh token in the
265
+ * body, so it is required here. Revoking an unknown or already-revoked token also answers `{ revoked: false }`.
280
266
  */
281
- signOut(input?: SignOutInput): Promise<Envelope<{
282
- revoked: boolean;
283
- }>>;
267
+ signOut(input: SignOutInput): Promise<Envelope<SignOutResult>>;
284
268
  /**
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.
269
+ * Return the signed-in end-user (the bearer token's subject). The answer is the user record itself, not wrapped.
288
270
  *
289
271
  * Errors:
290
272
  * - `UNAUTHENTICATED` (401) when no bearer is present
291
- * - `SESSION_EXPIRED` (401) when the bearer's exp has passed
273
+ * - `SESSION_EXPIRED` (401) when the bearer has expired or was revoked
292
274
  */
293
275
  whoami(): Promise<Envelope<WhoamiResult>>;
294
276
  /**
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).
277
+ * Exchange a refresh token for a new access token. The answer carries only the new `accessToken` and its
278
+ * `expiresAt`; the refresh token you hold stays valid and is not returned again.
298
279
  *
299
280
  * 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.
281
+ * - `INVALID_CREDENTIALS` (401) when the refresh token is unknown, used or revoked
318
282
  */
319
- hosted(): Promise<Envelope<HostedGateInfo>>;
283
+ refreshToken(input: RefreshTokenInput): Promise<Envelope<RefreshResult>>;
320
284
  /**
321
285
  * Initiate an external-IdP sign-in flow (Google / GitHub / Microsoft).
322
286
  * The SDK POSTs to the gateway's authorize endpoint, receives the
@@ -335,8 +299,6 @@ declare function makeGateNamespace(getConfig: () => GateConfig): {
335
299
  * });
336
300
  * // …user lands at /auth/callback#access_token=…&refresh_token=…
337
301
  *
338
- * Generic OIDC requires authorization-endpoint discovery and is not
339
- * yet wired; use a hosted provider (google/github/microsoft) for now.
340
302
  */
341
303
  signInWithProvider(input: {
342
304
  provider: "google" | "github" | "microsoft" | "oidc" | "saml";
@@ -354,74 +316,56 @@ declare function makeGateNamespace(getConfig: () => GateConfig): {
354
316
  */
355
317
  parseSignInCallback(hash: string): SignInCallbackResult;
356
318
  /**
357
- * Passwordless sub-namespace — OTP + Magic Link flows
358
- * for email-based authentication without a stored password.
319
+ * Passwordless sign-in: one-time passcode and magic link, both by email.
359
320
  *
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 SDK paths match the shipped gateway routes.)
321
+ * Routes: `POST /@<projectSlug>/gate/otp/{request,verify}` and `POST /@<projectSlug>/gate/magic-link/{send,redeem}`.
322
+ * The method names do not mirror the last path segment (`requestMagicLink` calls `send`, `verifyMagicLink` calls
323
+ * `redeem`).
367
324
  */
368
325
  passwordless: {
369
326
  /**
370
- * Request a one-time passcode delivered to the supplied email.
371
- * The OTP is short-lived (typically 5 min) and
372
- * single-use; redemption via `verifyOtp` mints a session.
327
+ * Request a one-time passcode for the email. It is short-lived and single-use; redeem it with `verifyOtp`.
328
+ * The answer carries the code's id and expiry (`data.otp`); the code itself goes by email.
373
329
  */
374
- requestOtp(input: RequestOtpInput): Promise<Envelope<{
375
- requested: boolean;
376
- }>>;
330
+ requestOtp(input: RequestOtpInput): Promise<Envelope<RequestOtpResult>>;
377
331
  /**
378
- * Verify a one-time passcode previously sent via `requestOtp`.
379
- * On success returns the same `AuthResult` shape as `signIn` —
380
- * a fresh session bound to the end-user identity.
332
+ * Verify a one-time passcode previously sent via `requestOtp`. For the default `signin` purpose the answer
333
+ * carries a fresh `session`; for any other purpose it only confirms (`verified: true`) and the caller continues
334
+ * its own flow.
381
335
  *
382
336
  * Errors:
383
- * - INVALID_CREDENTIALS (401) on wrong / expired / consumed OTP
384
- * (ambiguous on purpose — enumeration defense)
337
+ * - `INVALID_CREDENTIALS` (401) on a wrong, expired or used code
385
338
  */
386
- verifyOtp(input: VerifyOtpInput): Promise<Envelope<AuthResult>>;
339
+ verifyOtp(input: VerifyOtpInput): Promise<Envelope<VerifyOtpResult>>;
387
340
  /**
388
- * Request a magic-link emailed to the supplied address. The link
389
- * carries an opaque short-lived token (typically 15 min
390
- *); clicking the link lands at the customer's chosen
391
- * redirect URL with the token in the query string, which the
392
- * customer's frontend then exchanges via `verifyMagicLink`.
341
+ * Request a magic link by email. The link carries an opaque short-lived token and lands at the project's
342
+ * redirect URL with the token in the query string; exchange it with `verifyMagicLink`. The answer is
343
+ * `data.magicLink` with `sent` and the expiry.
393
344
  */
394
- requestMagicLink(input: RequestMagicLinkInput): Promise<Envelope<{
395
- requested: boolean;
396
- }>>;
345
+ requestMagicLink(input: RequestMagicLinkInput): Promise<Envelope<RequestMagicLinkResult>>;
397
346
  /**
398
- * Verify a magic-link token previously delivered via
399
- * `requestMagicLink`. On success returns the same `AuthResult`
400
- * shape as `signIn`.
347
+ * Redeem a magic-link token previously delivered via `requestMagicLink`. The answer carries the new `session`
348
+ * and the `endUserId` (not the full user record; call `whoami` for that).
401
349
  *
402
350
  * Errors:
403
- * - INVALID_CREDENTIALS (401) on wrong / expired / consumed
404
- * token (same enumeration-defense rationale as verifyOtp)
351
+ * - `INVALID_CREDENTIALS` (401) on a wrong, expired or used token
405
352
  */
406
- verifyMagicLink(input: VerifyMagicLinkInput): Promise<Envelope<AuthResult>>;
353
+ verifyMagicLink(input: VerifyMagicLinkInput): Promise<Envelope<VerifyMagicLinkResult>>;
407
354
  /**
408
- * Extract the opaque magic-link token from the landing-page URL's query
409
- * string (the gateway redirects to `<configured-redirect>?token=…&project=…`).
410
- * Pure + side-effect-free — pass `window.location.search`. Feed the
411
- * returned `token` to `verifyMagicLink({ token })` to mint the session.
412
- *
413
- * This is the magic-link counterpart to `parseSignInCallback` (which parses
414
- * the SSO *hash fragment* for session tokens) — magic-link delivers an
415
- * opaque token in the *query string*, so the two are not interchangeable.
416
- */
355
+ * Extract the opaque magic-link token from the landing-page URL's query
356
+ * string (the gateway redirects to `<configured-redirect>?token=…&project=…`).
357
+ * Pure + side-effect-free — pass `window.location.search`. Feed the
358
+ * returned `token` to `verifyMagicLink({ token })` to mint the session.
359
+ *
360
+ * This is the magic-link counterpart to `parseSignInCallback` (which parses
361
+ * the SSO *hash fragment* for session tokens) — magic-link delivers an
362
+ * opaque token in the *query string*, so the two are not interchangeable.
363
+ */
417
364
  parseMagicLinkCallback(search: string): MagicLinkCallbackResult;
418
365
  };
419
366
  /**
420
- * Project-scoped end-user IdP provider-config management.
421
- * PROJECT_ADMIN+ required; end-user JWTs rejected at the route. Only
422
- * `create()` is bound — the REST create the Gate Studio
423
- * `FederationPresetsPanel` routes to when it drops the `gate_flows`
424
- * `__addFromPreset` sentinel. Gate-admin surface.
367
+ * Project-scoped end-user IdP provider-config management (create, list, update, test).
368
+ * PROJECT_ADMIN+ required; end-user JWTs are rejected at the route.
425
369
  */
426
370
  identityProviders: {
427
371
  create(input: CreateIdpInput): Promise<Envelope<IdpConfig>>;
@@ -430,10 +374,8 @@ declare function makeGateNamespace(getConfig: () => GateConfig): {
430
374
  testProvider(configId: string): Promise<Envelope<FederationTestProviderResult>>;
431
375
  };
432
376
  /**
433
- * Per-project magic-link redirect-URL allowlist management (blessed
434
- * shape). Gate-admin surface alongside `identityProviders`
435
- * — PROJECT_ADMIN+ required, distinct from the end-user-facing
436
- * `passwordless.requestMagicLink`/`verifyMagicLink` send/verify flow.
377
+ * Per-project magic-link redirect-URL allowlist management (list, add, remove).
378
+ * PROJECT_ADMIN+ required; distinct from the end-user `passwordless` flow.
437
379
  */
438
380
  magicLinkRedirects: {
439
381
  list(): Promise<Envelope<{
@@ -449,75 +391,43 @@ declare function makeGateNamespace(getConfig: () => GateConfig): {
449
391
  * Per-instance clients: `createGateClient(config)`. */
450
392
  declare const gate: {
451
393
  /**
452
- * Create a new end-user identity for the configured project. Returns
453
- * the new user record + an initial session. Emits a
454
- * `data.write.auth.signup` audit event.
394
+ * Create a new end-user for the configured project. Returns the new user and an initial session. The body is
395
+ * closed: `{ email, password, displayName? }`.
455
396
  *
456
- * Errors (closed taxonomy):
397
+ * Errors:
457
398
  * - `EMAIL_ALREADY_REGISTERED` (409) when the email collides
458
- * - `INVALID_FIELD_VALUE` (400) on schema violation (password policy, malformed email)
399
+ * - `INVALID_FIELD_VALUE` (400) on a schema violation (password policy, malformed email, unknown field)
459
400
  * - `TIER_FEATURE_GATED` (403) when the project's tier blocks email/password
460
401
  */
461
402
  signUp(input: SignUpInput): Promise<Envelope<AuthResult>>;
462
403
  /**
463
- * Sign in an existing end-user with email + password. Returns the
464
- * user record + a fresh session. Emits a `data.write.auth.signin`
465
- * audit event.
404
+ * Sign in an existing end-user with email and password. Returns the user and a fresh session.
466
405
  *
467
406
  * Errors:
468
- * - `INVALID_CREDENTIALS` (401) on bad email/password (ambiguous on
469
- * purpose — does not distinguish "user not found" vs "password
470
- * wrong" to avoid enumeration)
407
+ * - `INVALID_CREDENTIALS` (401) on a bad email or password (the same answer for both, on purpose)
471
408
  */
472
409
  signIn(input: SignInInput): Promise<Envelope<AuthResult>>;
473
410
  /**
474
- * Sign out — revoke the bound refresh token (or an explicit one, if
475
- * supplied). Emits a `data.write.auth.signout` audit event.
476
- *
477
- * Errors:
478
- * - `UNAUTHENTICATED` (401) when no session is present
479
- * - `REFRESH_TOKEN_INVALID` (400) when the supplied refresh token
480
- * doesn't match the bearer's session
411
+ * Sign out: revoke the session that the given refresh token belongs to. The route needs the refresh token in the
412
+ * body, so it is required here. Revoking an unknown or already-revoked token also answers `{ revoked: false }`.
481
413
  */
482
- signOut(input?: SignOutInput): Promise<Envelope<{
483
- revoked: boolean;
484
- }>>;
414
+ signOut(input: SignOutInput): Promise<Envelope<SignOutResult>>;
485
415
  /**
486
- * Return the caller's identity claims from the bearer JWT. Useful in
487
- * Standalone mode where the SDK consumer wants to surface the user's
488
- * roles / groups without parsing the JWT itself.
416
+ * Return the signed-in end-user (the bearer token's subject). The answer is the user record itself, not wrapped.
489
417
  *
490
418
  * Errors:
491
419
  * - `UNAUTHENTICATED` (401) when no bearer is present
492
- * - `SESSION_EXPIRED` (401) when the bearer's exp has passed
420
+ * - `SESSION_EXPIRED` (401) when the bearer has expired or was revoked
493
421
  */
494
422
  whoami(): Promise<Envelope<WhoamiResult>>;
495
423
  /**
496
- * Rotate the access token using a refresh token. Returns a fresh
497
- * session pair (new access token + same refresh token; gateway may
498
- * also rotate the refresh token per project policy).
424
+ * Exchange a refresh token for a new access token. The answer carries only the new `accessToken` and its
425
+ * `expiresAt`; the refresh token you hold stays valid and is not returned again.
499
426
  *
500
427
  * Errors:
501
- * - `REFRESH_TOKEN_INVALID` (400) when the refresh token is unknown,
502
- * expired, or revoked
428
+ * - `INVALID_CREDENTIALS` (401) when the refresh token is unknown, used or revoked
503
429
  */
504
- refreshToken(input: RefreshTokenInput): Promise<Envelope<{
505
- session: AuthResult["session"];
506
- }>>;
507
- /**
508
- * Check whether the bearer's session is currently valid. Does NOT
509
- * throw on expiry or invalid token — returns `{ valid: false }`
510
- * inside the envelope. The intended use is health-checking a session
511
- * without surfacing an error to the end-user (the SDK consumer
512
- * typically wants to redirect to sign-in rather than surface a 401).
513
- */
514
- verifySession(): Promise<Envelope<VerifySessionResult>>;
515
- /**
516
- * The project's hosted sign-in page URL and WebAuthn `rpId`. Unauthenticated and cacheable; no credential is sent,
517
- * even when a partner token is configured. Take both values as given: an unknown org, a deleted project and a
518
- * non-Gate project all answer the same `PROJECT_NOT_FOUND`, so a failure here says nothing about which.
519
- */
520
- hosted(): Promise<Envelope<HostedGateInfo>>;
430
+ refreshToken(input: RefreshTokenInput): Promise<Envelope<RefreshResult>>;
521
431
  /**
522
432
  * Initiate an external-IdP sign-in flow (Google / GitHub / Microsoft).
523
433
  * The SDK POSTs to the gateway's authorize endpoint, receives the
@@ -536,8 +446,6 @@ declare const gate: {
536
446
  * });
537
447
  * // …user lands at /auth/callback#access_token=…&refresh_token=…
538
448
  *
539
- * Generic OIDC requires authorization-endpoint discovery and is not
540
- * yet wired; use a hosted provider (google/github/microsoft) for now.
541
449
  */
542
450
  signInWithProvider(input: {
543
451
  provider: "google" | "github" | "microsoft" | "oidc" | "saml";
@@ -555,74 +463,56 @@ declare const gate: {
555
463
  */
556
464
  parseSignInCallback(hash: string): SignInCallbackResult;
557
465
  /**
558
- * Passwordless sub-namespace — OTP + Magic Link flows
559
- * for email-based authentication without a stored password.
466
+ * Passwordless sign-in: one-time passcode and magic link, both by email.
560
467
  *
561
- * Wire endpoints (LIVE on the Gate end-user plane — verified against
562
- * staging): OTP → `POST /@<projectSlug>/gate/otp/{request,verify}`
563
- * (routes/otp.ts), Magic Link → `POST /@<projectSlug>/gate/magic-link/
564
- * {send,redeem}` (routes/magic-link.ts). NOTE: these are the gateway's
565
- * canonical paths; the SDK ergonomic method names (`requestMagicLink`/
566
- * `verifyMagicLink`) do not mirror the path segments (`send`/`redeem`).
567
- * (The SDK paths match the shipped gateway routes.)
468
+ * Routes: `POST /@<projectSlug>/gate/otp/{request,verify}` and `POST /@<projectSlug>/gate/magic-link/{send,redeem}`.
469
+ * The method names do not mirror the last path segment (`requestMagicLink` calls `send`, `verifyMagicLink` calls
470
+ * `redeem`).
568
471
  */
569
472
  passwordless: {
570
473
  /**
571
- * Request a one-time passcode delivered to the supplied email.
572
- * The OTP is short-lived (typically 5 min) and
573
- * single-use; redemption via `verifyOtp` mints a session.
474
+ * Request a one-time passcode for the email. It is short-lived and single-use; redeem it with `verifyOtp`.
475
+ * The answer carries the code's id and expiry (`data.otp`); the code itself goes by email.
574
476
  */
575
- requestOtp(input: RequestOtpInput): Promise<Envelope<{
576
- requested: boolean;
577
- }>>;
477
+ requestOtp(input: RequestOtpInput): Promise<Envelope<RequestOtpResult>>;
578
478
  /**
579
- * Verify a one-time passcode previously sent via `requestOtp`.
580
- * On success returns the same `AuthResult` shape as `signIn` —
581
- * a fresh session bound to the end-user identity.
479
+ * Verify a one-time passcode previously sent via `requestOtp`. For the default `signin` purpose the answer
480
+ * carries a fresh `session`; for any other purpose it only confirms (`verified: true`) and the caller continues
481
+ * its own flow.
582
482
  *
583
483
  * Errors:
584
- * - INVALID_CREDENTIALS (401) on wrong / expired / consumed OTP
585
- * (ambiguous on purpose — enumeration defense)
484
+ * - `INVALID_CREDENTIALS` (401) on a wrong, expired or used code
586
485
  */
587
- verifyOtp(input: VerifyOtpInput): Promise<Envelope<AuthResult>>;
486
+ verifyOtp(input: VerifyOtpInput): Promise<Envelope<VerifyOtpResult>>;
588
487
  /**
589
- * Request a magic-link emailed to the supplied address. The link
590
- * carries an opaque short-lived token (typically 15 min
591
- *); clicking the link lands at the customer's chosen
592
- * redirect URL with the token in the query string, which the
593
- * customer's frontend then exchanges via `verifyMagicLink`.
488
+ * Request a magic link by email. The link carries an opaque short-lived token and lands at the project's
489
+ * redirect URL with the token in the query string; exchange it with `verifyMagicLink`. The answer is
490
+ * `data.magicLink` with `sent` and the expiry.
594
491
  */
595
- requestMagicLink(input: RequestMagicLinkInput): Promise<Envelope<{
596
- requested: boolean;
597
- }>>;
492
+ requestMagicLink(input: RequestMagicLinkInput): Promise<Envelope<RequestMagicLinkResult>>;
598
493
  /**
599
- * Verify a magic-link token previously delivered via
600
- * `requestMagicLink`. On success returns the same `AuthResult`
601
- * shape as `signIn`.
494
+ * Redeem a magic-link token previously delivered via `requestMagicLink`. The answer carries the new `session`
495
+ * and the `endUserId` (not the full user record; call `whoami` for that).
602
496
  *
603
497
  * Errors:
604
- * - INVALID_CREDENTIALS (401) on wrong / expired / consumed
605
- * token (same enumeration-defense rationale as verifyOtp)
498
+ * - `INVALID_CREDENTIALS` (401) on a wrong, expired or used token
606
499
  */
607
- verifyMagicLink(input: VerifyMagicLinkInput): Promise<Envelope<AuthResult>>;
500
+ verifyMagicLink(input: VerifyMagicLinkInput): Promise<Envelope<VerifyMagicLinkResult>>;
608
501
  /**
609
- * Extract the opaque magic-link token from the landing-page URL's query
610
- * string (the gateway redirects to `<configured-redirect>?token=…&project=…`).
611
- * Pure + side-effect-free — pass `window.location.search`. Feed the
612
- * returned `token` to `verifyMagicLink({ token })` to mint the session.
613
- *
614
- * This is the magic-link counterpart to `parseSignInCallback` (which parses
615
- * the SSO *hash fragment* for session tokens) — magic-link delivers an
616
- * opaque token in the *query string*, so the two are not interchangeable.
617
- */
502
+ * Extract the opaque magic-link token from the landing-page URL's query
503
+ * string (the gateway redirects to `<configured-redirect>?token=…&project=…`).
504
+ * Pure + side-effect-free — pass `window.location.search`. Feed the
505
+ * returned `token` to `verifyMagicLink({ token })` to mint the session.
506
+ *
507
+ * This is the magic-link counterpart to `parseSignInCallback` (which parses
508
+ * the SSO *hash fragment* for session tokens) — magic-link delivers an
509
+ * opaque token in the *query string*, so the two are not interchangeable.
510
+ */
618
511
  parseMagicLinkCallback(search: string): MagicLinkCallbackResult;
619
512
  };
620
513
  /**
621
- * Project-scoped end-user IdP provider-config management.
622
- * PROJECT_ADMIN+ required; end-user JWTs rejected at the route. Only
623
- * `create()` is bound — the REST create the Gate Studio
624
- * `FederationPresetsPanel` routes to when it drops the `gate_flows`
625
- * `__addFromPreset` sentinel. Gate-admin surface.
514
+ * Project-scoped end-user IdP provider-config management (create, list, update, test).
515
+ * PROJECT_ADMIN+ required; end-user JWTs are rejected at the route.
626
516
  */
627
517
  identityProviders: {
628
518
  create(input: CreateIdpInput): Promise<Envelope<IdpConfig>>;
@@ -631,10 +521,8 @@ declare const gate: {
631
521
  testProvider(configId: string): Promise<Envelope<FederationTestProviderResult>>;
632
522
  };
633
523
  /**
634
- * Per-project magic-link redirect-URL allowlist management (blessed
635
- * shape). Gate-admin surface alongside `identityProviders`
636
- * — PROJECT_ADMIN+ required, distinct from the end-user-facing
637
- * `passwordless.requestMagicLink`/`verifyMagicLink` send/verify flow.
524
+ * Per-project magic-link redirect-URL allowlist management (list, add, remove).
525
+ * PROJECT_ADMIN+ required; distinct from the end-user `passwordless` flow.
638
526
  */
639
527
  magicLinkRedirects: {
640
528
  list(): Promise<Envelope<{
@@ -661,11 +549,8 @@ type GateNamespace = ReturnType<typeof makeGateNamespace>;
661
549
  * incoming request's resolved tenant) or for apps that maintain both a
662
550
  * Standalone and Native binding against the same SDK instance.
663
551
  *
664
- * Per the URL grammar, supply `projectSlug` for
665
- * canonical `/@<slug>/gate/...` routing. Slug is the only customer-facing
666
- * project addressing form — project ids are internal-only
667
- * (`feedback_slug_addressing_v2_5_68`); the retired `/v2/projects/...`
668
- * mounts were dropped at pass17 (`feedback_v2_path_is_an_error`).
552
+ * Supply `projectSlug` for
553
+ * `/@<slug>/gate/...` routing. The slug is the only project addressing form.
669
554
  *
670
555
  * Example:
671
556
  * const client = createGateClient({
@@ -683,26 +568,51 @@ type GateClient = ReturnType<typeof makeGateNamespace>;
683
568
  * re-read process-level state. Throws `TypeError` on invalid config. */
684
569
  declare function createGateClient(config: GateConfig): GateClient;
685
570
 
571
+ /**
572
+ * Reads the structure of a gateway API host without knowing its apex.
573
+ *
574
+ * The gateway label is `api` or `api-staging`, optionally followed by `-<rgn>` (a 4-character region code). The label
575
+ * before it is the org slug and whatever follows it is the apex, which is never matched against a list: a public-cloud,
576
+ * internal-apex, air-gapped or localhost deployment all work.
577
+ */
578
+ /** The structure of a gateway API host, read from the host itself. */
579
+ type ApiHost = {
580
+ /** First label of an org host (`acme` in `acme.api.corp.internal`); null for a platform host (`api.corp.internal`). */
581
+ orgSlug: string | null;
582
+ /** `api` or `api-staging`: the label that marks a gateway host. */
583
+ family: "api" | "api-staging";
584
+ /** 4-character region code when the host is regional (`api-de01`), else null (region-free). */
585
+ region: string | null;
586
+ /** Everything after the gateway label (`corp.internal`, `k9o.io`). */
587
+ apex: string;
588
+ };
589
+ /**
590
+ * Returns null for any other shape (localhost, an IP, a single-label host, a host with no gateway label), which callers
591
+ * use exactly as given.
592
+ */
593
+ declare function parseApiHost(host: string): ApiHost | null;
594
+
686
595
  /**
687
596
  * Regional host resolution.
688
597
  *
689
- * The region-free org host `<org>.api[-staging].k9o.io` redirects GET/HEAD only and REFUSES a request that carries a
690
- * body, an `Authorization` header or a cookie (`400 K9O_INVALID_REQUEST_SHAPE`, `region_free_host_takes_no_credentials`),
691
- * so credentials must go to `<org>.api[-staging]-<rgn>.k9o.io`. An SDK learns the region with ONE unauthenticated
692
- * `HEAD https://<org>.api[-staging].k9o.io/`, takes the `308` `Location`, and caches the regional origin.
598
+ * A region-free org host `<org>.api.<rest>` (or `<org>.api-staging.<rest>`) redirects GET/HEAD only and REFUSES a
599
+ * request that carries a body, an `Authorization` header or a cookie (`400 K9O_INVALID_REQUEST_SHAPE`,
600
+ * `region_free_host_takes_no_credentials`), so credentials must go to `<org>.api-<rgn>.<rest>`. The client learns the
601
+ * region with ONE unauthenticated `HEAD` to the configured host, takes the `308` `Location`, and caches the regional
602
+ * origin.
693
603
  *
694
- * Properties this module holds:
695
- * - Only a region-free org host (shape `<org>.api.k9o.io` or `<org>.api-staging.k9o.io`) is ever probed. A regional,
696
- * custom, self-hosted or dev baseUrl is used exactly as given.
604
+ * Nothing here knows an apex. The host comes from the configured baseUrl; `<rest>` is whatever follows the second
605
+ * label, so a public-cloud, internal-apex, air-gapped or localhost deployment all work. Properties:
606
+ * - Only a host shaped `<org>.api.<rest>` / `<org>.api-staging.<rest>` is probed. A regional, single-label, IP,
607
+ * localhost or any other baseUrl is used exactly as given.
697
608
  * - The probe carries NO credential and none of the configured default headers (they may hold one).
698
- * - The `Location` is trusted only if it is `https`, has no userinfo/port, and its host is exactly the SAME org on
699
- * the SAME apex family with a 4-character region code. Anything else is ignored and the configured baseUrl stays,
700
- * so a hostile redirect can never receive a credential.
701
- * - Not a redirect (the platform has not cut over, or the host answers directly), or any failure: the configured
702
- * baseUrl stays and the server's own refusal, if any, is what the caller sees. A definite answer is cached for the
703
- * life of the client's config; a network failure is not cached.
609
+ * - The `Location` is trusted only if it is `https`, has no userinfo/port, and its host is exactly the SAME org and
610
+ * SAME remainder with a 4-character region code appended to the second label. Anything else is ignored and the
611
+ * configured baseUrl stays, so a hostile redirect can never receive a credential.
612
+ * - Not a redirect, or any failure: the configured baseUrl stays and the server's own refusal, if any, is what the
613
+ * caller sees. A definite answer is cached for the life of the client's config; a network failure is not cached.
704
614
  */
705
- /** The slice of a client config this module needs (structural, so core and gate share one implementation). */
615
+ /** The slice of a client config this module needs. */
706
616
  type RegionConfig = {
707
617
  baseUrl: string;
708
618
  fetch?: typeof globalThis.fetch | undefined;
@@ -712,4 +622,4 @@ declare function regionalOriginFromLocation(host: string, location: string, base
712
622
  /** The origin requests for this client go to: the cached regional origin once learned, else the configured baseUrl. */
713
623
  declare function resolveRegionalOrigin(config: RegionConfig): Promise<string>;
714
624
 
715
- 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 };
625
+ export { type ApiHost, AuthResult, type CreateIdpInput, type DirectorySyncInput, Envelope, type GateClient, GateConfig, type GateNamespace, type IdentityProvidersNamespace, type IdpConfig, type IdpProviderKind, type IdpTierGating, MagicLinkCallbackResult, type PatchIdpInput, RefreshResult, RefreshTokenInput, type RegionConfig, RequestMagicLinkInput, RequestMagicLinkResult, RequestOtpInput, RequestOtpResult, SignInCallbackResult, SignInInput, SignOutInput, SignOutResult, SignUpInput, VerifyMagicLinkInput, VerifyMagicLinkResult, VerifyOtpInput, VerifyOtpResult, WhoamiResult, createGateClient, gate, parseApiHost, regionalOriginFromLocation, resolveRegionalOrigin };