@ambasada/sdk 0.8.11 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.d.ts +6 -4
- package/dist/errors.d.ts +2 -0
- package/dist/platform.js +26 -21
- package/package.json +2 -2
package/dist/auth.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export interface AuthConfig {
|
|
|
40
40
|
issuer?: string;
|
|
41
41
|
/** Who a shell token must be for (`aud`): your app id and origin. The request's origin is added in `shell()`. */
|
|
42
42
|
audiences?: string[];
|
|
43
|
-
/** The hub,
|
|
43
|
+
/** The hub (default https://hub.ambasada.id): signs shell tokens, and has the full profile behind one (`/api/apps/profile`). */
|
|
44
44
|
hubUrl?: string;
|
|
45
45
|
/** Where `/v1/auth/profile` lives (the Ambasada API). */
|
|
46
46
|
apiUrl: string;
|
|
@@ -110,9 +110,11 @@ export declare class AmbasadaAuth {
|
|
|
110
110
|
/** Whoever is signed in on this request, or null (also null when sign-in is not set up). */
|
|
111
111
|
user(req: WithHeaders): Promise<AmbasadaUser | null>;
|
|
112
112
|
/**
|
|
113
|
-
* Verifies a shell token (
|
|
114
|
-
* `<AMBASADA_AUTH_ISSUER>/.well-known/jwks.json
|
|
115
|
-
*
|
|
113
|
+
* Verifies a shell token (RS256, 60 s) from the hub (`<AMBASADA_HUB_URL>/.well-known/jwks.json`, default
|
|
114
|
+
* https://hub.ambasada.id) or Ambasada ID (`<AMBASADA_AUTH_ISSUER>/.well-known/jwks.json`, the Clerk JWT template
|
|
115
|
+
* `ambasada-miniapp`), keys cached 1 h: signature, expiry, issuer, and that its `aud` is this app (its origin or
|
|
116
|
+
* id). Returns who it is, or throws `AmbasadaError("UNAUTHORIZED")`; `NOT_CONFIGURED` (503) while the issuer
|
|
117
|
+
* publishes no keys.
|
|
116
118
|
*/
|
|
117
119
|
verifyShellToken(token: string, options?: {
|
|
118
120
|
audience?: string | string[];
|
package/dist/errors.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export declare const ERROR_STATUS: {
|
|
|
47
47
|
readonly CAP_TOOLS_RATE: 429;
|
|
48
48
|
readonly NETWORK: 0;
|
|
49
49
|
readonly CONFIG: 0;
|
|
50
|
+
/** A platform feature this app relies on is not switched on yet (silent sign-in before the hub has keys). */
|
|
51
|
+
readonly NOT_CONFIGURED: 503;
|
|
50
52
|
readonly RECEIPT_INVALID: 402;
|
|
51
53
|
readonly WEBHOOK_INVALID: 400;
|
|
52
54
|
};
|
package/dist/platform.js
CHANGED
|
@@ -180,6 +180,8 @@ var ERROR_STATUS = {
|
|
|
180
180
|
// SDK-side
|
|
181
181
|
NETWORK: 0,
|
|
182
182
|
CONFIG: 0,
|
|
183
|
+
/** A platform feature this app relies on is not switched on yet (silent sign-in before the hub has keys). */
|
|
184
|
+
NOT_CONFIGURED: 503,
|
|
183
185
|
RECEIPT_INVALID: 402,
|
|
184
186
|
WEBHOOK_INVALID: 400
|
|
185
187
|
};
|
|
@@ -409,13 +411,17 @@ async function jwks(issuer, doFetch, refresh = false) {
|
|
|
409
411
|
if (cached && !refresh && Date.now() - cached.at < JWKS_TTL_MS) return cached.keys;
|
|
410
412
|
if (cached && refresh && Date.now() - cached.at < 6e4) return cached.keys;
|
|
411
413
|
const res = await doFetch(`${issuer}/.well-known/jwks.json`, { headers: { accept: "application/json" } });
|
|
412
|
-
if (
|
|
413
|
-
|
|
414
|
+
if (res.status === 404) throw notConfigured(`${issuer} publishes no signing keys yet`);
|
|
415
|
+
if (!res.ok) throw new AmbasadaError("UPSTREAM_ERROR", `could not read the signing keys of ${issuer} (${res.status})`, 502);
|
|
416
|
+
const body = await res.json().catch(() => ({}));
|
|
414
417
|
const keys3 = (body.keys ?? []).filter((k) => k.kty === "RSA" && k.n && k.e);
|
|
418
|
+
if (!keys3.length) throw notConfigured(`${issuer} publishes no RS256 signing keys yet`);
|
|
415
419
|
jwksCache.set(issuer, { at: Date.now(), keys: keys3 });
|
|
416
420
|
return keys3;
|
|
417
421
|
}
|
|
422
|
+
var hubOf = (c) => (c.hubUrl || DEFAULT_HUB_URL).replace(/\/+$/, "");
|
|
418
423
|
var shellRefused = (message) => new AmbasadaError("UNAUTHORIZED", `shell token refused: ${message}`, 401);
|
|
424
|
+
var notConfigured = (message) => new AmbasadaError("NOT_CONFIGURED", `silent sign-in is not configured: ${message}`, 503);
|
|
419
425
|
var AmbasadaAuth = class {
|
|
420
426
|
constructor(config) {
|
|
421
427
|
this.config = config;
|
|
@@ -505,14 +511,15 @@ var AmbasadaAuth = class {
|
|
|
505
511
|
return (await verifyCookie(secret, "session", readOurCookie(req, SESSION_COOKIE)))?.user ?? null;
|
|
506
512
|
}
|
|
507
513
|
/**
|
|
508
|
-
* Verifies a shell token (
|
|
509
|
-
* `<AMBASADA_AUTH_ISSUER>/.well-known/jwks.json
|
|
510
|
-
*
|
|
514
|
+
* Verifies a shell token (RS256, 60 s) from the hub (`<AMBASADA_HUB_URL>/.well-known/jwks.json`, default
|
|
515
|
+
* https://hub.ambasada.id) or Ambasada ID (`<AMBASADA_AUTH_ISSUER>/.well-known/jwks.json`, the Clerk JWT template
|
|
516
|
+
* `ambasada-miniapp`), keys cached 1 h: signature, expiry, issuer, and that its `aud` is this app (its origin or
|
|
517
|
+
* id). Returns who it is, or throws `AmbasadaError("UNAUTHORIZED")`; `NOT_CONFIGURED` (503) while the issuer
|
|
518
|
+
* publishes no keys.
|
|
511
519
|
*/
|
|
512
520
|
async verifyShellToken(token, options = {}) {
|
|
513
521
|
const c = this.config();
|
|
514
|
-
|
|
515
|
-
const trusted = [c.issuer, c.hubUrl].filter((v) => !!v).map((v) => v.replace(/\/+$/, ""));
|
|
522
|
+
const trusted = [...new Set([c.issuer, hubOf(c)].filter((v) => !!v).map((v) => v.replace(/\/+$/, "")))];
|
|
516
523
|
const audiences = [options.audience ?? c.audiences ?? []].flat().filter(Boolean).map((a) => a.replace(/\/+$/, ""));
|
|
517
524
|
if (!audiences.length) throw new AmbasadaError("CONFIG", "silent sign-in needs to know this app: set AMBASADA_APP_ORIGIN or PUBLIC_URL, or pass { audience }");
|
|
518
525
|
const doFetch = c.fetch ?? ((input, init) => fetch(input, init));
|
|
@@ -557,6 +564,7 @@ var AmbasadaAuth = class {
|
|
|
557
564
|
* Answers `{ user }`.
|
|
558
565
|
*/
|
|
559
566
|
async shell(req) {
|
|
567
|
+
if (!this.configured) throw notConfigured("this app has no Sign in with Ambasada credentials (AMBASADA_AUTH_*: on ambasada.dev after `ambasada deploy`, and under `ambasada dev`)");
|
|
560
568
|
const s = this.settings(req);
|
|
561
569
|
if (req.method !== "POST") throw new AmbasadaError("INVALID_REQUEST", "POST the shell token as JSON { token }", 405);
|
|
562
570
|
let token = /^Bearer\s+(.+)$/i.exec(req.headers.get("authorization") ?? "")?.[1]?.trim();
|
|
@@ -569,16 +577,13 @@ var AmbasadaAuth = class {
|
|
|
569
577
|
const audiences = [...this.config().audiences ?? [], s.origin];
|
|
570
578
|
const who = await this.verifyShellToken(token, { audience: audiences });
|
|
571
579
|
let user = { id: who.user_id, name: who.name, avatar_url: who.avatar, builder: false, builder_since: null, member_since: null, pass: null };
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
} else await res.body?.cancel();
|
|
580
|
-
} catch {
|
|
581
|
-
}
|
|
580
|
+
try {
|
|
581
|
+
const res = await s.fetch(`${hubOf(this.config())}/api/apps/profile`, { headers: { authorization: `Bearer ${token}`, accept: "application/json" } });
|
|
582
|
+
if (res.ok) {
|
|
583
|
+
const profile = await res.json();
|
|
584
|
+
if (profile.id === who.user_id) user = { ...user, ...profile, id: who.user_id };
|
|
585
|
+
} else await res.body?.cancel();
|
|
586
|
+
} catch {
|
|
582
587
|
}
|
|
583
588
|
const exp = Math.floor(Date.now() / 1e3) + SESSION_TTL_S;
|
|
584
589
|
const session = await signCookie(s.clientSecret, "session", { user, exp });
|
|
@@ -598,6 +603,7 @@ var AmbasadaAuth = class {
|
|
|
598
603
|
|
|
599
604
|
// src/client.ts
|
|
600
605
|
var DEFAULT_API_URL = "https://api.ambasada.dev";
|
|
606
|
+
var originOf = (url) => url && URL.canParse(url) ? new URL(url).origin : void 0;
|
|
601
607
|
var isBinding = (v) => typeof v === "object" && v !== null && typeof v.fetch === "function";
|
|
602
608
|
function processEnv() {
|
|
603
609
|
const p = globalThis.process;
|
|
@@ -648,14 +654,13 @@ var Ambasada = class {
|
|
|
648
654
|
issuer: this.options.auth?.issuer ?? this.env("AMBASADA_AUTH_ISSUER"),
|
|
649
655
|
publicUrl: this.options.auth?.publicUrl ?? this.env("PUBLIC_URL"),
|
|
650
656
|
apiUrl: this.apiUrl,
|
|
651
|
-
// Silent sign-in: a shell token must be for this app, by
|
|
652
|
-
audiences: [this.appOrigin, this.options.app?.id
|
|
657
|
+
// Silent sign-in: a shell token must be for this app, by origin (the deploy's and PUBLIC_URL) or id.
|
|
658
|
+
audiences: [...new Set([this.appOrigin, originOf(this.env("PUBLIC_URL")), this.options.app?.id, this.env("AMBASADA_APP_ID"), this.env("AMBASADA_COMPANY")].filter((a) => !!a))],
|
|
653
659
|
hubUrl: this.hubUrl,
|
|
654
660
|
fetch: this.fetcher
|
|
655
661
|
}));
|
|
656
662
|
get appOrigin() {
|
|
657
|
-
|
|
658
|
-
return url && URL.canParse(url) ? new URL(url).origin : void 0;
|
|
663
|
+
return originOf(this.options.app?.origin ?? this.env("AMBASADA_APP_ORIGIN") ?? this.options.auth?.publicUrl ?? this.env("PUBLIC_URL"));
|
|
659
664
|
}
|
|
660
665
|
get hubUrl() {
|
|
661
666
|
return this.options.app?.hubUrl ?? this.env("AMBASADA_HUB_URL");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ambasada/sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.13",
|
|
4
4
|
"description": "The Ambasada platform SDK: Sign in with Ambasada, mini-app notifications, the project store, files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"node": ">=22"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@ambasada/village": "^0.8.
|
|
30
|
+
"@ambasada/village": "^0.8.13"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.20.5",
|