@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/CHANGELOG.md +28 -0
- package/README.md +58 -19
- package/dist/{chunk-JEV37YEC.js → chunk-7EED6YWR.js} +1 -1
- package/dist/chunk-7EED6YWR.js.map +1 -0
- package/dist/{chunk-YINWOWJC.js → chunk-OJQYLAGZ.js} +42 -5
- package/dist/chunk-OJQYLAGZ.js.map +1 -0
- package/dist/{chunk-XP6U5QKF.js → chunk-RPE5N37W.js} +1 -1
- package/dist/chunk-RPE5N37W.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.d.ts +149 -239
- package/dist/index.js +75 -113
- package/dist/index.js.map +1 -1
- package/dist/jwt.d.ts +25 -36
- package/dist/jwt.js +4 -2
- package/dist/mode.d.ts +6 -33
- package/dist/mode.js +1 -1
- package/dist/types.d.ts +63 -99
- package/package.json +1 -1
- package/src/api-host.ts +43 -0
- package/src/client-factory.ts +2 -5
- package/src/client.ts +3 -3
- package/src/errors.ts +1 -1
- package/src/gate.ts +62 -134
- package/src/idp-providers/identity-providers.ts +8 -11
- package/src/index.ts +10 -6
- package/src/jwt.ts +63 -45
- package/src/magic-link-redirects.ts +1 -2
- package/src/mode.ts +6 -33
- package/src/paths.ts +2 -6
- package/src/region.ts +24 -24
- package/src/types.ts +60 -94
- package/dist/chunk-JEV37YEC.js.map +0 -1
- package/dist/chunk-XP6U5QKF.js.map +0 -1
- package/dist/chunk-YINWOWJC.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,26 +1,43 @@
|
|
|
1
1
|
import {
|
|
2
2
|
extractClaims,
|
|
3
|
+
fetchJwks,
|
|
3
4
|
requireScope,
|
|
4
5
|
verifyJwt
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-OJQYLAGZ.js";
|
|
6
7
|
import {
|
|
7
8
|
GateError,
|
|
8
9
|
gateErrorFromWire
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7EED6YWR.js";
|
|
10
11
|
import {
|
|
11
12
|
configureGate,
|
|
12
13
|
getGateConfig,
|
|
13
14
|
resetGateConfig,
|
|
14
15
|
validateGateConfig
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RPE5N37W.js";
|
|
17
|
+
|
|
18
|
+
// src/api-host.ts
|
|
19
|
+
var API_LABEL = /^(api|api-staging)(?:-([a-z]{2}\d{2}))?$/;
|
|
20
|
+
var SLUG_RE = /^[a-z][a-z0-9-]{1,62}$/;
|
|
21
|
+
function parseApiHost(host) {
|
|
22
|
+
const labels = host.toLowerCase().split(".");
|
|
23
|
+
if (labels.some((l) => l.length === 0)) return null;
|
|
24
|
+
const mk = (gatewayLabel, orgSlug, rest) => {
|
|
25
|
+
const m = API_LABEL.exec(gatewayLabel);
|
|
26
|
+
if (m === null || rest.length === 0) return null;
|
|
27
|
+
return { orgSlug, family: m[1], region: m[2] ?? null, apex: rest.join(".") };
|
|
28
|
+
};
|
|
29
|
+
if (labels.length >= 3 && SLUG_RE.test(labels[0]) && !labels[0].endsWith("-") && API_LABEL.test(labels[1])) {
|
|
30
|
+
return mk(labels[1], labels[0], labels.slice(2));
|
|
31
|
+
}
|
|
32
|
+
if (labels.length >= 2 && API_LABEL.test(labels[0])) return mk(labels[0], null, labels.slice(1));
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
16
35
|
|
|
17
36
|
// src/region.ts
|
|
18
|
-
var REGION_FREE_ORG_HOST = /^([a-z][a-z0-9-]{1,62})\.(api|api-staging)\.k9o\.io$/;
|
|
19
|
-
var REGION_CODE = /^[a-z]{2}\d{2}$/;
|
|
20
37
|
var cache = /* @__PURE__ */ new WeakMap();
|
|
21
38
|
function regionalOriginFromLocation(host, location, base) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
39
|
+
const from = parseApiHost(host);
|
|
40
|
+
if (from === null || from.orgSlug === null || from.region !== null) return null;
|
|
24
41
|
let u;
|
|
25
42
|
try {
|
|
26
43
|
u = new URL(location, base);
|
|
@@ -28,10 +45,9 @@ function regionalOriginFromLocation(host, location, base) {
|
|
|
28
45
|
return null;
|
|
29
46
|
}
|
|
30
47
|
if (u.protocol !== "https:" || u.username !== "" || u.password !== "" || u.port !== "") return null;
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (r === null || !REGION_CODE.test(r[1])) return null;
|
|
48
|
+
const to = parseApiHost(u.hostname);
|
|
49
|
+
if (to === null || to.region === null) return null;
|
|
50
|
+
if (to.orgSlug !== from.orgSlug || to.family !== from.family || to.apex !== from.apex) return null;
|
|
35
51
|
return `https://${u.hostname}`;
|
|
36
52
|
}
|
|
37
53
|
async function probe(config) {
|
|
@@ -44,7 +60,8 @@ async function probe(config) {
|
|
|
44
60
|
} catch {
|
|
45
61
|
return { origin: configured, cacheable: true };
|
|
46
62
|
}
|
|
47
|
-
|
|
63
|
+
const parsedHost = parseApiHost(host);
|
|
64
|
+
if (parsedHost === null || parsedHost.orgSlug === null || parsedHost.region !== null) return { origin: configured, cacheable: true };
|
|
48
65
|
try {
|
|
49
66
|
const res = await fetchImpl(`${configured.replace(/\/+$/, "")}/`, {
|
|
50
67
|
method: "HEAD",
|
|
@@ -227,7 +244,7 @@ function makeIdentityProviders(getConfig) {
|
|
|
227
244
|
/**
|
|
228
245
|
* Update a provider config by id (PATCH; PROJECT_ADMIN, server-enforced). Partial: only supplied fields
|
|
229
246
|
* change; `idpProvider`/`tierGating` are immutable. Returns the updated config (secrets never included).
|
|
230
|
-
*
|
|
247
|
+
* Write directory-sync settings here and read their status back off `list()`. See `PatchIdpInput`
|
|
231
248
|
* for the metadataUrl-change-clears-secret rule.
|
|
232
249
|
*/
|
|
233
250
|
async update(configId, patch) {
|
|
@@ -296,13 +313,12 @@ function gatePath(config, suffix) {
|
|
|
296
313
|
function makeGateNamespace(getConfig) {
|
|
297
314
|
return {
|
|
298
315
|
/**
|
|
299
|
-
* Create a new end-user
|
|
300
|
-
*
|
|
301
|
-
* `data.write.auth.signup` audit event.
|
|
316
|
+
* Create a new end-user for the configured project. Returns the new user and an initial session. The body is
|
|
317
|
+
* closed: `{ email, password, displayName? }`.
|
|
302
318
|
*
|
|
303
|
-
* Errors
|
|
319
|
+
* Errors:
|
|
304
320
|
* - `EMAIL_ALREADY_REGISTERED` (409) when the email collides
|
|
305
|
-
* - `INVALID_FIELD_VALUE` (400) on schema violation (password policy, malformed email)
|
|
321
|
+
* - `INVALID_FIELD_VALUE` (400) on a schema violation (password policy, malformed email, unknown field)
|
|
306
322
|
* - `TIER_FEATURE_GATED` (403) when the project's tier blocks email/password
|
|
307
323
|
*/
|
|
308
324
|
async signUp(input) {
|
|
@@ -313,14 +329,10 @@ function makeGateNamespace(getConfig) {
|
|
|
313
329
|
});
|
|
314
330
|
},
|
|
315
331
|
/**
|
|
316
|
-
* Sign in an existing end-user with email
|
|
317
|
-
* user record + a fresh session. Emits a `data.write.auth.signin`
|
|
318
|
-
* audit event.
|
|
332
|
+
* Sign in an existing end-user with email and password. Returns the user and a fresh session.
|
|
319
333
|
*
|
|
320
334
|
* Errors:
|
|
321
|
-
* - `INVALID_CREDENTIALS` (401) on bad email
|
|
322
|
-
* purpose — does not distinguish "user not found" vs "password
|
|
323
|
-
* wrong" to avoid enumeration)
|
|
335
|
+
* - `INVALID_CREDENTIALS` (401) on a bad email or password (the same answer for both, on purpose)
|
|
324
336
|
*/
|
|
325
337
|
async signIn(input) {
|
|
326
338
|
return requestWith(getConfig(), {
|
|
@@ -330,15 +342,10 @@ function makeGateNamespace(getConfig) {
|
|
|
330
342
|
});
|
|
331
343
|
},
|
|
332
344
|
/**
|
|
333
|
-
* Sign out
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* Errors:
|
|
337
|
-
* - `UNAUTHENTICATED` (401) when no session is present
|
|
338
|
-
* - `REFRESH_TOKEN_INVALID` (400) when the supplied refresh token
|
|
339
|
-
* doesn't match the bearer's session
|
|
345
|
+
* Sign out: revoke the session that the given refresh token belongs to. The route needs the refresh token in the
|
|
346
|
+
* body, so it is required here. Revoking an unknown or already-revoked token also answers `{ revoked: false }`.
|
|
340
347
|
*/
|
|
341
|
-
async signOut(input
|
|
348
|
+
async signOut(input) {
|
|
342
349
|
return requestWith(getConfig(), {
|
|
343
350
|
method: "POST",
|
|
344
351
|
path: gatePath(getConfig(), "signout"),
|
|
@@ -346,13 +353,11 @@ function makeGateNamespace(getConfig) {
|
|
|
346
353
|
});
|
|
347
354
|
},
|
|
348
355
|
/**
|
|
349
|
-
* Return the
|
|
350
|
-
* Standalone mode where the SDK consumer wants to surface the user's
|
|
351
|
-
* roles / groups without parsing the JWT itself.
|
|
356
|
+
* Return the signed-in end-user (the bearer token's subject). The answer is the user record itself, not wrapped.
|
|
352
357
|
*
|
|
353
358
|
* Errors:
|
|
354
359
|
* - `UNAUTHENTICATED` (401) when no bearer is present
|
|
355
|
-
* - `SESSION_EXPIRED` (401) when the bearer
|
|
360
|
+
* - `SESSION_EXPIRED` (401) when the bearer has expired or was revoked
|
|
356
361
|
*/
|
|
357
362
|
async whoami() {
|
|
358
363
|
return requestWith(getConfig(), {
|
|
@@ -361,13 +366,11 @@ function makeGateNamespace(getConfig) {
|
|
|
361
366
|
});
|
|
362
367
|
},
|
|
363
368
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* also rotate the refresh token per project policy).
|
|
369
|
+
* Exchange a refresh token for a new access token. The answer carries only the new `accessToken` and its
|
|
370
|
+
* `expiresAt`; the refresh token you hold stays valid and is not returned again.
|
|
367
371
|
*
|
|
368
372
|
* Errors:
|
|
369
|
-
* - `
|
|
370
|
-
* expired, or revoked
|
|
373
|
+
* - `INVALID_CREDENTIALS` (401) when the refresh token is unknown, used or revoked
|
|
371
374
|
*/
|
|
372
375
|
async refreshToken(input) {
|
|
373
376
|
return requestWith(getConfig(), {
|
|
@@ -376,31 +379,6 @@ function makeGateNamespace(getConfig) {
|
|
|
376
379
|
body: input
|
|
377
380
|
});
|
|
378
381
|
},
|
|
379
|
-
/**
|
|
380
|
-
* Check whether the bearer's session is currently valid. Does NOT
|
|
381
|
-
* throw on expiry or invalid token — returns `{ valid: false }`
|
|
382
|
-
* inside the envelope. The intended use is health-checking a session
|
|
383
|
-
* without surfacing an error to the end-user (the SDK consumer
|
|
384
|
-
* typically wants to redirect to sign-in rather than surface a 401).
|
|
385
|
-
*/
|
|
386
|
-
async verifySession() {
|
|
387
|
-
return requestWith(getConfig(), {
|
|
388
|
-
method: "GET",
|
|
389
|
-
path: gatePath(getConfig(), "verify")
|
|
390
|
-
});
|
|
391
|
-
},
|
|
392
|
-
/**
|
|
393
|
-
* The project's hosted sign-in page URL and WebAuthn `rpId`. Unauthenticated and cacheable; no credential is sent,
|
|
394
|
-
* even when a partner token is configured. Take both values as given: an unknown org, a deleted project and a
|
|
395
|
-
* non-Gate project all answer the same `PROJECT_NOT_FOUND`, so a failure here says nothing about which.
|
|
396
|
-
*/
|
|
397
|
-
async hosted() {
|
|
398
|
-
return requestWith(getConfig(), {
|
|
399
|
-
method: "GET",
|
|
400
|
-
path: gatePath(getConfig(), "hosted"),
|
|
401
|
-
bearerToken: ""
|
|
402
|
-
});
|
|
403
|
-
},
|
|
404
382
|
/**
|
|
405
383
|
* Initiate an external-IdP sign-in flow (Google / GitHub / Microsoft).
|
|
406
384
|
* The SDK POSTs to the gateway's authorize endpoint, receives the
|
|
@@ -419,8 +397,6 @@ function makeGateNamespace(getConfig) {
|
|
|
419
397
|
* });
|
|
420
398
|
* // …user lands at /auth/callback#access_token=…&refresh_token=…
|
|
421
399
|
*
|
|
422
|
-
* Generic OIDC requires authorization-endpoint discovery and is not
|
|
423
|
-
* yet wired; use a hosted provider (google/github/microsoft) for now.
|
|
424
400
|
*/
|
|
425
401
|
async signInWithProvider(input) {
|
|
426
402
|
const env = await requestWith(getConfig(), {
|
|
@@ -477,22 +453,16 @@ function makeGateNamespace(getConfig) {
|
|
|
477
453
|
};
|
|
478
454
|
},
|
|
479
455
|
/**
|
|
480
|
-
* Passwordless
|
|
481
|
-
* for email-based authentication without a stored password.
|
|
456
|
+
* Passwordless sign-in: one-time passcode and magic link, both by email.
|
|
482
457
|
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
* {send,redeem}` (routes/magic-link.ts). NOTE: these are the gateway's
|
|
487
|
-
* canonical paths; the SDK ergonomic method names (`requestMagicLink`/
|
|
488
|
-
* `verifyMagicLink`) do not mirror the path segments (`send`/`redeem`).
|
|
489
|
-
* (The SDK paths match the shipped gateway routes.)
|
|
458
|
+
* Routes: `POST /@<projectSlug>/gate/otp/{request,verify}` and `POST /@<projectSlug>/gate/magic-link/{send,redeem}`.
|
|
459
|
+
* The method names do not mirror the last path segment (`requestMagicLink` calls `send`, `verifyMagicLink` calls
|
|
460
|
+
* `redeem`).
|
|
490
461
|
*/
|
|
491
462
|
passwordless: {
|
|
492
463
|
/**
|
|
493
|
-
* Request a one-time passcode
|
|
494
|
-
* The
|
|
495
|
-
* single-use; redemption via `verifyOtp` mints a session.
|
|
464
|
+
* Request a one-time passcode for the email. It is short-lived and single-use; redeem it with `verifyOtp`.
|
|
465
|
+
* The answer carries the code's id and expiry (`data.otp`); the code itself goes by email.
|
|
496
466
|
*/
|
|
497
467
|
async requestOtp(input) {
|
|
498
468
|
return requestWith(getConfig(), {
|
|
@@ -502,13 +472,12 @@ function makeGateNamespace(getConfig) {
|
|
|
502
472
|
});
|
|
503
473
|
},
|
|
504
474
|
/**
|
|
505
|
-
* Verify a one-time passcode previously sent via `requestOtp`.
|
|
506
|
-
*
|
|
507
|
-
*
|
|
475
|
+
* Verify a one-time passcode previously sent via `requestOtp`. For the default `signin` purpose the answer
|
|
476
|
+
* carries a fresh `session`; for any other purpose it only confirms (`verified: true`) and the caller continues
|
|
477
|
+
* its own flow.
|
|
508
478
|
*
|
|
509
479
|
* Errors:
|
|
510
|
-
* - INVALID_CREDENTIALS (401) on wrong
|
|
511
|
-
* (ambiguous on purpose — enumeration defense)
|
|
480
|
+
* - `INVALID_CREDENTIALS` (401) on a wrong, expired or used code
|
|
512
481
|
*/
|
|
513
482
|
async verifyOtp(input) {
|
|
514
483
|
return requestWith(getConfig(), {
|
|
@@ -518,11 +487,9 @@ function makeGateNamespace(getConfig) {
|
|
|
518
487
|
});
|
|
519
488
|
},
|
|
520
489
|
/**
|
|
521
|
-
* Request a magic
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
* redirect URL with the token in the query string, which the
|
|
525
|
-
* customer's frontend then exchanges via `verifyMagicLink`.
|
|
490
|
+
* Request a magic link by email. The link carries an opaque short-lived token and lands at the project's
|
|
491
|
+
* redirect URL with the token in the query string; exchange it with `verifyMagicLink`. The answer is
|
|
492
|
+
* `data.magicLink` with `sent` and the expiry.
|
|
526
493
|
*/
|
|
527
494
|
async requestMagicLink(input) {
|
|
528
495
|
return requestWith(getConfig(), {
|
|
@@ -532,13 +499,11 @@ function makeGateNamespace(getConfig) {
|
|
|
532
499
|
});
|
|
533
500
|
},
|
|
534
501
|
/**
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
* shape as `signIn`.
|
|
502
|
+
* Redeem a magic-link token previously delivered via `requestMagicLink`. The answer carries the new `session`
|
|
503
|
+
* and the `endUserId` (not the full user record; call `whoami` for that).
|
|
538
504
|
*
|
|
539
505
|
* Errors:
|
|
540
|
-
* - INVALID_CREDENTIALS (401) on wrong
|
|
541
|
-
* token (same enumeration-defense rationale as verifyOtp)
|
|
506
|
+
* - `INVALID_CREDENTIALS` (401) on a wrong, expired or used token
|
|
542
507
|
*/
|
|
543
508
|
async verifyMagicLink(input) {
|
|
544
509
|
return requestWith(getConfig(), {
|
|
@@ -548,15 +513,15 @@ function makeGateNamespace(getConfig) {
|
|
|
548
513
|
});
|
|
549
514
|
},
|
|
550
515
|
/**
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
516
|
+
* Extract the opaque magic-link token from the landing-page URL's query
|
|
517
|
+
* string (the gateway redirects to `<configured-redirect>?token=…&project=…`).
|
|
518
|
+
* Pure + side-effect-free — pass `window.location.search`. Feed the
|
|
519
|
+
* returned `token` to `verifyMagicLink({ token })` to mint the session.
|
|
520
|
+
*
|
|
521
|
+
* This is the magic-link counterpart to `parseSignInCallback` (which parses
|
|
522
|
+
* the SSO *hash fragment* for session tokens) — magic-link delivers an
|
|
523
|
+
* opaque token in the *query string*, so the two are not interchangeable.
|
|
524
|
+
*/
|
|
560
525
|
parseMagicLinkCallback(search) {
|
|
561
526
|
const raw = search.startsWith("?") ? search.slice(1) : search;
|
|
562
527
|
const params = new URLSearchParams(raw);
|
|
@@ -572,18 +537,13 @@ function makeGateNamespace(getConfig) {
|
|
|
572
537
|
}
|
|
573
538
|
},
|
|
574
539
|
/**
|
|
575
|
-
* Project-scoped end-user IdP provider-config management.
|
|
576
|
-
* PROJECT_ADMIN+ required; end-user JWTs rejected at the route.
|
|
577
|
-
* `create()` is bound — the REST create the Gate Studio
|
|
578
|
-
* `FederationPresetsPanel` routes to when it drops the `gate_flows`
|
|
579
|
-
* `__addFromPreset` sentinel. Gate-admin surface.
|
|
540
|
+
* Project-scoped end-user IdP provider-config management (create, list, update, test).
|
|
541
|
+
* PROJECT_ADMIN+ required; end-user JWTs are rejected at the route.
|
|
580
542
|
*/
|
|
581
543
|
identityProviders: makeIdentityProviders(getConfig),
|
|
582
544
|
/**
|
|
583
|
-
* Per-project magic-link redirect-URL allowlist management (
|
|
584
|
-
*
|
|
585
|
-
* — PROJECT_ADMIN+ required, distinct from the end-user-facing
|
|
586
|
-
* `passwordless.requestMagicLink`/`verifyMagicLink` send/verify flow.
|
|
545
|
+
* Per-project magic-link redirect-URL allowlist management (list, add, remove).
|
|
546
|
+
* PROJECT_ADMIN+ required; distinct from the end-user `passwordless` flow.
|
|
587
547
|
*/
|
|
588
548
|
magicLinkRedirects: makeMagicLinkRedirects(getConfig)
|
|
589
549
|
};
|
|
@@ -601,9 +561,11 @@ export {
|
|
|
601
561
|
configureGate,
|
|
602
562
|
createGateClient,
|
|
603
563
|
extractClaims,
|
|
564
|
+
fetchJwks,
|
|
604
565
|
gate,
|
|
605
566
|
gateErrorFromWire,
|
|
606
567
|
getGateConfig,
|
|
568
|
+
parseApiHost,
|
|
607
569
|
regionalOriginFromLocation,
|
|
608
570
|
requireScope,
|
|
609
571
|
resetGateConfig,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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 envelope unwrap on success\n * - the standard `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 +, 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.\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 * 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.\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 — 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 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,):\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,): 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\n * 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.\n *\n * surfaces (canonical 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 }` and throws a `GateError` on non-2xx\n * gateway responses. Native vs Standalone mode does not affect SDK\n * ergonomics; 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. Emits a\n * `data.write.auth.signup` audit event.\n *\n * Errors (closed taxonomy):\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 — 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 SDK paths match the shipped gateway routes.)\n */\n passwordless: {\n /**\n * Request a one-time passcode delivered to the supplied email.\n * The OTP is short-lived (typically 5 min) 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\n *); 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.\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 * Per-instance clients: `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.\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 the 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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/api-host.ts","../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 * Reads the structure of a gateway API host without knowing its apex.\n *\n * The gateway label is `api` or `api-staging`, optionally followed by `-<rgn>` (a 4-character region code). The label\n * before it is the org slug and whatever follows it is the apex, which is never matched against a list: a public-cloud,\n * internal-apex, air-gapped or localhost deployment all work.\n */\n\n/** The structure of a gateway API host, read from the host itself. */\nexport type ApiHost = {\n /** First label of an org host (`acme` in `acme.api.corp.internal`); null for a platform host (`api.corp.internal`). */\n orgSlug: string | null;\n /** `api` or `api-staging`: the label that marks a gateway host. */\n family: \"api\" | \"api-staging\";\n /** 4-character region code when the host is regional (`api-de01`), else null (region-free). */\n region: string | null;\n /** Everything after the gateway label (`corp.internal`, `k9o.io`). */\n apex: string;\n};\n\nconst API_LABEL = /^(api|api-staging)(?:-([a-z]{2}\\d{2}))?$/;\nconst SLUG_RE = /^[a-z][a-z0-9-]{1,62}$/;\n\n/**\n * Returns null for any other shape (localhost, an IP, a single-label host, a host with no gateway label), which callers\n * use exactly as given.\n */\nexport function parseApiHost(host: string): ApiHost | null {\n const labels = host.toLowerCase().split(\".\");\n if (labels.some((l) => l.length === 0)) return null;\n const mk = (gatewayLabel: string, orgSlug: string | null, rest: string[]): ApiHost | null => {\n const m = API_LABEL.exec(gatewayLabel);\n if (m === null || rest.length === 0) return null;\n return { orgSlug, family: m[1] as \"api\" | \"api-staging\", region: m[2] ?? null, apex: rest.join(\".\") };\n };\n // Org host first: `<org>.<api[-rgn]>.<apex>`.\n if (labels.length >= 3 && SLUG_RE.test(labels[0]!) && !labels[0]!.endsWith(\"-\") && API_LABEL.test(labels[1]!)) {\n return mk(labels[1]!, labels[0]!, labels.slice(2));\n }\n // Platform host: `api.<apex>`.\n if (labels.length >= 2 && API_LABEL.test(labels[0]!)) return mk(labels[0]!, null, labels.slice(1));\n return null;\n}\n","/**\n * Regional host resolution.\n *\n * A region-free org host `<org>.api.<rest>` (or `<org>.api-staging.<rest>`) redirects GET/HEAD only and REFUSES a\n * request that carries a body, an `Authorization` header or a cookie (`400 K9O_INVALID_REQUEST_SHAPE`,\n * `region_free_host_takes_no_credentials`), so credentials must go to `<org>.api-<rgn>.<rest>`. The client learns the\n * region with ONE unauthenticated `HEAD` to the configured host, takes the `308` `Location`, and caches the regional\n * origin.\n *\n * Nothing here knows an apex. The host comes from the configured baseUrl; `<rest>` is whatever follows the second\n * label, so a public-cloud, internal-apex, air-gapped or localhost deployment all work. Properties:\n * - Only a host shaped `<org>.api.<rest>` / `<org>.api-staging.<rest>` is probed. A regional, single-label, IP,\n * localhost or any other 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 and\n * SAME remainder with a 4-character region code appended to the second label. Anything else is ignored and the\n * configured baseUrl stays, so a hostile redirect can never receive a credential.\n * - Not a redirect, or any failure: the configured baseUrl stays and the server's own refusal, if any, is what the\n * caller sees. A definite answer is cached for the life of the client's config; a network failure is not cached.\n */\n\nimport { parseApiHost } from \"./api-host.ts\";\n\n/** The slice of a client config this module needs. */\nexport type RegionConfig = { baseUrl: string; fetch?: typeof globalThis.fetch | undefined };\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 from = parseApiHost(host);\n if (from === null || from.orgSlug === null || from.region !== 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 to = parseApiHost(u.hostname);\n if (to === null || to.region === null) return null;\n if (to.orgSlug !== from.orgSlug || to.family !== from.family || to.apex !== from.apex) 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 const parsedHost = parseApiHost(host);\n if (parsedHost === null || parsedHost.orgSlug === null || parsedHost.region !== null) 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 response envelope check on success\n * - the `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, 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.\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 * Project-scoped resources are addressed\n * as `/@<projectSlug>/...` against the org host. The slug is the only project addressing form.\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.\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 * 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 and 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 (11 values). */\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/** 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 scheduled directory sync (Google only):\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/** 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): 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\n * 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 * 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. `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 * The `gate.*` namespace, bound to the end-user Gate routes (project-scoped, `<org>.api.<apex>/@<projectSlug>/gate/...`):\n * - `gate.signUp` POST /gate/signup\n * - `gate.signIn` POST /gate/signin\n * - `gate.signOut` POST /gate/signout\n * - `gate.whoami` GET /gate/whoami\n * - `gate.refreshToken` POST /gate/refresh\n * - `gate.signInWithProvider` POST /gate/idp/{provider}/authorize\n * - `gate.passwordless.*` POST /gate/otp/{request,verify} and /gate/magic-link/{send,redeem}\n *\n * Every function returns the standard envelope `{ data, meta }` and throws a `GateError` on a non-2xx gateway answer.\n * The mode (native or standalone) does not change the call shapes; the client sends it as the `X-Gate-Mode` header.\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 MagicLinkCallbackResult,\n RefreshResult,\n RefreshTokenInput,\n RequestMagicLinkResult,\n RequestOtpResult,\n RequestMagicLinkInput,\n RequestOtpInput,\n SignInCallbackResult,\n SignInInput,\n SignOutInput,\n SignOutResult,\n SignUpInput,\n VerifyMagicLinkInput,\n VerifyMagicLinkResult,\n VerifyOtpInput,\n VerifyOtpResult,\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 for the configured project. Returns the new user and an initial session. The body is\n * closed: `{ email, password, displayName? }`.\n *\n * Errors:\n * - `EMAIL_ALREADY_REGISTERED` (409) when the email collides\n * - `INVALID_FIELD_VALUE` (400) on a schema violation (password policy, malformed email, unknown field)\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 and password. Returns the user and a fresh session.\n *\n * Errors:\n * - `INVALID_CREDENTIALS` (401) on a bad email or password (the same answer for both, on purpose)\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 session that the given refresh token belongs to. The route needs the refresh token in the\n * body, so it is required here. Revoking an unknown or already-revoked token also answers `{ revoked: false }`.\n */\n async signOut(input: SignOutInput): Promise<Envelope<SignOutResult>> {\n return requestWith<SignOutResult>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"signout\"),\n body: input,\n });\n },\n\n /**\n * Return the signed-in end-user (the bearer token's subject). The answer is the user record itself, not wrapped.\n *\n * Errors:\n * - `UNAUTHENTICATED` (401) when no bearer is present\n * - `SESSION_EXPIRED` (401) when the bearer has expired or was revoked\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 * Exchange a refresh token for a new access token. The answer carries only the new `accessToken` and its\n * `expiresAt`; the refresh token you hold stays valid and is not returned again.\n *\n * Errors:\n * - `INVALID_CREDENTIALS` (401) when the refresh token is unknown, used or revoked\n */\n async refreshToken(input: RefreshTokenInput): Promise<Envelope<RefreshResult>> {\n return requestWith<RefreshResult>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"refresh\"),\n body: input,\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 */\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 sign-in: one-time passcode and magic link, both by email.\n *\n * Routes: `POST /@<projectSlug>/gate/otp/{request,verify}` and `POST /@<projectSlug>/gate/magic-link/{send,redeem}`.\n * The method names do not mirror the last path segment (`requestMagicLink` calls `send`, `verifyMagicLink` calls\n * `redeem`).\n */\n passwordless: {\n /**\n * Request a one-time passcode for the email. It is short-lived and single-use; redeem it with `verifyOtp`.\n * The answer carries the code's id and expiry (`data.otp`); the code itself goes by email.\n */\n async requestOtp(input: RequestOtpInput): Promise<Envelope<RequestOtpResult>> {\n return requestWith<RequestOtpResult>(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`. For the default `signin` purpose the answer\n * carries a fresh `session`; for any other purpose it only confirms (`verified: true`) and the caller continues\n * its own flow.\n *\n * Errors:\n * - `INVALID_CREDENTIALS` (401) on a wrong, expired or used code\n */\n async verifyOtp(input: VerifyOtpInput): Promise<Envelope<VerifyOtpResult>> {\n return requestWith<VerifyOtpResult>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"otp/verify\"),\n body: input,\n });\n },\n\n /**\n * Request a magic link by email. The link carries an opaque short-lived token and lands at the project's\n * redirect URL with the token in the query string; exchange it with `verifyMagicLink`. The answer is\n * `data.magicLink` with `sent` and the expiry.\n */\n async requestMagicLink(input: RequestMagicLinkInput): Promise<Envelope<RequestMagicLinkResult>> {\n return requestWith<RequestMagicLinkResult>(getConfig(), {\n method: \"POST\",\n path: gatePath(getConfig(), \"magic-link/send\"),\n body: input,\n });\n },\n\n /**\n * Redeem a magic-link token previously delivered via `requestMagicLink`. The answer carries the new `session`\n * and the `endUserId` (not the full user record; call `whoami` for that).\n *\n * Errors:\n * - `INVALID_CREDENTIALS` (401) on a wrong, expired or used token\n */\n async verifyMagicLink(input: VerifyMagicLinkInput): Promise<Envelope<VerifyMagicLinkResult>> {\n return requestWith<VerifyMagicLinkResult>(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 (create, list, update, test).\n * PROJECT_ADMIN+ required; end-user JWTs are rejected at the route.\n */\n identityProviders: makeIdentityProviders(getConfig),\n\n /**\n * Per-project magic-link redirect-URL allowlist management (list, add, remove).\n * PROJECT_ADMIN+ required; distinct from the end-user `passwordless` flow.\n */\n magicLinkRedirects: makeMagicLinkRedirects(getConfig),\n };\n}\n\n/** Singleton bound to the module-level `configureGate()` config.\n * Per-instance clients: `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.\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 * Supply `projectSlug` for\n * `/@<slug>/gate/...` routing. The slug is the only project addressing form.\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":[]}
|