@uniformdev/mesh-sdk 20.72.3-alpha.2 → 20.72.3-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,354 @@
1
+ var __typeError = (msg) => {
2
+ throw TypeError(msg);
3
+ };
4
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
+
10
+ // src/server/clients/DelegationTokenClient.ts
11
+ import { ApiClient } from "@uniformdev/context/api";
12
+ var DelegationTokenError = class extends Error {
13
+ constructor(status, kind, publicMessage) {
14
+ super(publicMessage);
15
+ this.name = "DelegationTokenError";
16
+ this.status = status;
17
+ this.kind = kind;
18
+ }
19
+ };
20
+ var PUBLIC_MESSAGES = {
21
+ bad_request: "Token exchange request was rejected as invalid",
22
+ unauthenticated: "Token exchange authentication failed",
23
+ forbidden: "Token exchange forbidden by server",
24
+ not_found: "Token exchange target was not found",
25
+ rate_limited: "Token exchange rate limit exceeded",
26
+ server_error: "Token exchange server error",
27
+ unknown: "Token exchange failed"
28
+ };
29
+ function classifyDelegationTokenStatus(status) {
30
+ if (status === 400) {
31
+ return "bad_request";
32
+ }
33
+ if (status === 401) {
34
+ return "unauthenticated";
35
+ }
36
+ if (status === 403) {
37
+ return "forbidden";
38
+ }
39
+ if (status === 404) {
40
+ return "not_found";
41
+ }
42
+ if (status === 429) {
43
+ return "rate_limited";
44
+ }
45
+ if (status >= 500) {
46
+ return "server_error";
47
+ }
48
+ return "unknown";
49
+ }
50
+ function buildDelegationTokenError(status) {
51
+ const kind = classifyDelegationTokenStatus(status);
52
+ return new DelegationTokenError(status, kind, PUBLIC_MESSAGES[kind]);
53
+ }
54
+ var _integrationId, _integrationSecret, _explicitFetch, _DelegationTokenClient_instances, post_fn;
55
+ var DelegationTokenClient = class extends ApiClient {
56
+ constructor(options) {
57
+ super({ apiHost: options.apiHost, apiKey: "avoid_constructor_check", fetch: options.fetch });
58
+ __privateAdd(this, _DelegationTokenClient_instances);
59
+ __privateAdd(this, _integrationId);
60
+ __privateAdd(this, _integrationSecret);
61
+ // Preserve the caller-supplied fetch so #post can fall back to the global fetch at
62
+ // call time when none was provided. ApiClient captures the global at construction,
63
+ // which breaks test-time global stubbing (vi.stubGlobal).
64
+ __privateAdd(this, _explicitFetch);
65
+ __privateSet(this, _integrationId, options.integrationId);
66
+ __privateSet(this, _integrationSecret, options.integrationSecret);
67
+ __privateSet(this, _explicitFetch, options.fetch);
68
+ }
69
+ /**
70
+ * Exchanges a short-lived session token for a delegation token and refresh token.
71
+ * The session token is obtained by the integration's frontend via `sdk.getSessionToken()`.
72
+ *
73
+ * @deprecated This beta identity delegation API may change with breaking changes.
74
+ */
75
+ async exchangeSessionToken(sessionToken) {
76
+ return __privateMethod(this, _DelegationTokenClient_instances, post_fn).call(this, {
77
+ grantType: "delegation_token",
78
+ sessionToken,
79
+ integrationId: __privateGet(this, _integrationId),
80
+ integrationSecret: __privateGet(this, _integrationSecret)
81
+ });
82
+ }
83
+ /**
84
+ * Exchanges a refresh token for a new delegation token and a new refresh token.
85
+ *
86
+ * Replay posture: refresh tokens are bearer credentials that are valid until
87
+ * their server-side expiry. They are NOT single-use — a captured refresh token can be
88
+ * replayed by an attacker that also has the integration secret until it expires.
89
+ * Single-use enforcement (refresh-token storage, family/jti tracking, replay revocation)
90
+ * is tracked in `UNI-9279`.
91
+ *
92
+ * @deprecated This beta identity delegation API may change with breaking changes.
93
+ */
94
+ async refreshDelegationToken(refreshToken) {
95
+ return __privateMethod(this, _DelegationTokenClient_instances, post_fn).call(this, {
96
+ grantType: "refresh_token",
97
+ refreshToken,
98
+ integrationId: __privateGet(this, _integrationId),
99
+ integrationSecret: __privateGet(this, _integrationSecret)
100
+ });
101
+ }
102
+ };
103
+ _integrationId = new WeakMap();
104
+ _integrationSecret = new WeakMap();
105
+ _explicitFetch = new WeakMap();
106
+ _DelegationTokenClient_instances = new WeakSet();
107
+ post_fn = async function(body) {
108
+ var _a;
109
+ const url = this.createUrl("/api/v1/token");
110
+ const fetchFn = (_a = __privateGet(this, _explicitFetch)) != null ? _a : fetch;
111
+ const response = await this.options.limitPolicy(
112
+ () => fetchFn(url.toString(), {
113
+ method: "POST",
114
+ headers: { "Content-Type": "application/json" },
115
+ body: JSON.stringify(body)
116
+ })
117
+ );
118
+ if (!response.ok) {
119
+ await response.text().catch(() => "");
120
+ throw buildDelegationTokenError(response.status);
121
+ }
122
+ return await response.json();
123
+ };
124
+
125
+ // src/server/cookies/cookieConstants.ts
126
+ var DELEGATION_COOKIE_NAME = "__Host-uniform_mesh_delegation";
127
+ var SESSION_TTL_SECONDS = 8 * 60 * 60;
128
+
129
+ // src/server/cookies/parseCookies.ts
130
+ import { parse } from "cookie";
131
+ function parseCookies(cookieHeader) {
132
+ if (cookieHeader === void 0 || cookieHeader.length === 0) {
133
+ return {};
134
+ }
135
+ const parsed = parse(cookieHeader);
136
+ const result = {};
137
+ for (const key of Object.keys(parsed)) {
138
+ const value = parsed[key];
139
+ if (typeof value === "string") {
140
+ result[key] = value;
141
+ }
142
+ }
143
+ return result;
144
+ }
145
+
146
+ // src/server/cookies/serializeSessionCookie.ts
147
+ import { serialize } from "cookie";
148
+ var DEFAULT_PATH = "/";
149
+ function serializeSessionCookie(name, value, opts) {
150
+ var _a, _b, _c;
151
+ const path = (_a = opts == null ? void 0 : opts.path) != null ? _a : DEFAULT_PATH;
152
+ if (name.startsWith("__Host-") && path !== "/") {
153
+ throw new Error("Cookies with the __Host- prefix require Path=/");
154
+ }
155
+ const maxAge = (_b = opts == null ? void 0 : opts.maxAge) != null ? _b : SESSION_TTL_SECONDS;
156
+ const partitioned = (_c = opts == null ? void 0 : opts.partitioned) != null ? _c : true;
157
+ return serialize(name, value, {
158
+ httpOnly: true,
159
+ secure: true,
160
+ sameSite: "none",
161
+ path,
162
+ maxAge,
163
+ partitioned
164
+ });
165
+ }
166
+
167
+ // src/delegation/csrfConstants.ts
168
+ var CSRF_HEADER_VALUE = "1";
169
+
170
+ // src/server/util/parseOrigin.ts
171
+ function parseOrigin(referer) {
172
+ if (referer === void 0 || referer.length === 0) {
173
+ return void 0;
174
+ }
175
+ try {
176
+ const url = new URL(referer);
177
+ if (url.origin === "null") {
178
+ return void 0;
179
+ }
180
+ return url.origin;
181
+ } catch (e) {
182
+ return void 0;
183
+ }
184
+ }
185
+
186
+ // src/server/csrf/verifyCsrf.ts
187
+ function verifyCsrf(input, config) {
188
+ var _a, _b, _c, _d;
189
+ const headerValue = (_a = config.headerValue) != null ? _a : CSRF_HEADER_VALUE;
190
+ const requireSecFetchSite = (_b = config.requireSecFetchSite) != null ? _b : false;
191
+ const skipOriginValidation = (_c = config.skipOriginValidation) != null ? _c : false;
192
+ const normalizedOrigins = config.allowedOrigins.map((o) => o.replace(/\/+$/, ""));
193
+ const refererOrigin = parseOrigin(input.referer);
194
+ const effectiveOrigin = (_d = input.origin) != null ? _d : refererOrigin;
195
+ const provedSameOrigin = input.secFetchSite === "same-origin";
196
+ if (!skipOriginValidation) {
197
+ if (effectiveOrigin === void 0 && !provedSameOrigin) {
198
+ return { ok: false, reason: "origin_missing" };
199
+ }
200
+ if (effectiveOrigin !== void 0 && !normalizedOrigins.includes(effectiveOrigin)) {
201
+ return { ok: false, reason: "origin_mismatch" };
202
+ }
203
+ }
204
+ if (input.customHeader !== headerValue) {
205
+ return { ok: false, reason: "header_missing" };
206
+ }
207
+ if (requireSecFetchSite && input.secFetchSite !== "same-origin") {
208
+ return { ok: false, reason: "sec_fetch_site_mismatch" };
209
+ }
210
+ return { ok: true };
211
+ }
212
+
213
+ // src/server/session/needsRefresh.ts
214
+ var DEFAULT_BUFFER_MS = 6e4;
215
+ function needsRefresh(session, bufferMs) {
216
+ const buffer = bufferMs != null ? bufferMs : DEFAULT_BUFFER_MS;
217
+ return session.expiresAt - Date.now() < buffer;
218
+ }
219
+
220
+ // src/server/session/sealDelegationSession.ts
221
+ import { EncryptJWT } from "jose";
222
+
223
+ // src/server/util/deriveKey.ts
224
+ var MESH_SESSION_SECRET_MIN_BYTES = 32;
225
+ var HKDF_INFO = new TextEncoder().encode("mesh-delegation-session-v1");
226
+ async function deriveKey(secret) {
227
+ const ikm = new TextEncoder().encode(secret);
228
+ if (ikm.byteLength < MESH_SESSION_SECRET_MIN_BYTES) {
229
+ throw new Error(
230
+ `MESH_SESSION_SECRET must be at least ${MESH_SESSION_SECRET_MIN_BYTES} UTF-8 bytes (got ${ikm.byteLength}).`
231
+ );
232
+ }
233
+ const baseKey = await crypto.subtle.importKey("raw", ikm, "HKDF", false, ["deriveBits"]);
234
+ const bits = await crypto.subtle.deriveBits(
235
+ {
236
+ name: "HKDF",
237
+ hash: "SHA-256",
238
+ salt: new Uint8Array(0),
239
+ info: HKDF_INFO
240
+ },
241
+ baseKey,
242
+ 256
243
+ );
244
+ return new Uint8Array(bits);
245
+ }
246
+
247
+ // src/server/session/sealDelegationSession.ts
248
+ async function sealDelegationSession(session, secret) {
249
+ const key = await deriveKey(secret);
250
+ return new EncryptJWT(session).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${SESSION_TTL_SECONDS}s`).encrypt(key);
251
+ }
252
+
253
+ // src/server/session/unsealDelegationSession.ts
254
+ import { jwtDecrypt } from "jose";
255
+ async function unsealDelegationSession(jwe, secret) {
256
+ try {
257
+ const key = await deriveKey(secret);
258
+ const { payload } = await jwtDecrypt(jwe, key);
259
+ const { accessToken, refreshToken, expiresAt } = payload;
260
+ if (typeof accessToken !== "string" || typeof expiresAt !== "number") {
261
+ console.error("Invalid delegation session payload", {
262
+ hasAccessToken: typeof accessToken === "string" && accessToken.length > 0,
263
+ accessTokenType: typeof accessToken,
264
+ hasRefreshToken: typeof refreshToken === "string" && refreshToken.length > 0,
265
+ refreshTokenType: typeof refreshToken,
266
+ expiresAtType: typeof expiresAt
267
+ });
268
+ return null;
269
+ }
270
+ return {
271
+ accessToken,
272
+ refreshToken: typeof refreshToken === "string" ? refreshToken : void 0,
273
+ expiresAt
274
+ };
275
+ } catch (e) {
276
+ console.error("Error decrypting delegation session cookie");
277
+ return null;
278
+ }
279
+ }
280
+
281
+ // src/server/verify/constants.ts
282
+ var DEFAULT_UNIFORM_ORIGIN = "https://uniform.app";
283
+ var uniformDelegationIssuer = "uniform:delegation";
284
+ var uniformApiAudience = "uniform:api";
285
+ function getJwksUrl(origin = DEFAULT_UNIFORM_ORIGIN) {
286
+ const normalizedOrigin = origin.replace(/\/$/, "");
287
+ return `${normalizedOrigin}/delegation/.well-known/jwks.json`;
288
+ }
289
+
290
+ // src/server/verify/verifyDelegationAccessToken.ts
291
+ import { createRemoteJWKSet, jwtVerify } from "jose";
292
+ var DEFAULT_CLOCK_TOLERANCE_SECONDS = 2;
293
+ var jwksByUrl = /* @__PURE__ */ new Map();
294
+ function getRemoteJwkSet(jwksUrl) {
295
+ const key = String(jwksUrl);
296
+ let jwks = jwksByUrl.get(key);
297
+ if (!jwks) {
298
+ jwks = createRemoteJWKSet(new URL(key));
299
+ jwksByUrl.set(key, jwks);
300
+ }
301
+ return jwks;
302
+ }
303
+ async function verifyDelegationAccessToken(token, options = {}) {
304
+ var _a, _b;
305
+ const jwksUrl = (_a = options.jwksUrl) != null ? _a : getJwksUrl();
306
+ const jwks = getRemoteJwkSet(jwksUrl);
307
+ const clockTolerance = (_b = options.clockToleranceSeconds) != null ? _b : DEFAULT_CLOCK_TOLERANCE_SECONDS;
308
+ const { payload } = await jwtVerify(token, jwks, {
309
+ issuer: uniformDelegationIssuer,
310
+ audience: uniformApiAudience,
311
+ clockTolerance
312
+ });
313
+ const { sub, teamId, act, iat, exp } = payload;
314
+ if (typeof sub !== "string" || !sub) {
315
+ throw new Error("Missing required delegation token claim: sub");
316
+ }
317
+ if (typeof teamId !== "string" || !teamId) {
318
+ throw new Error("Missing required delegation token claim: teamId");
319
+ }
320
+ const actRecord = act;
321
+ const actSub = actRecord == null ? void 0 : actRecord.sub;
322
+ const actIntegrationType = actRecord == null ? void 0 : actRecord.integration_type;
323
+ if (!actRecord || typeof actSub !== "string" || !actSub || typeof actIntegrationType !== "string" || !actIntegrationType) {
324
+ throw new Error("Missing required delegation token claim: act");
325
+ }
326
+ if (typeof iat !== "number" || typeof exp !== "number") {
327
+ throw new Error("Missing required delegation token claim: iat/exp");
328
+ }
329
+ return {
330
+ sub,
331
+ teamId,
332
+ act: { sub: actSub, integration_type: actIntegrationType },
333
+ iat,
334
+ exp
335
+ };
336
+ }
337
+ export {
338
+ DELEGATION_COOKIE_NAME,
339
+ DelegationTokenClient,
340
+ DelegationTokenError,
341
+ SESSION_TTL_SECONDS,
342
+ buildDelegationTokenError,
343
+ classifyDelegationTokenStatus,
344
+ getJwksUrl,
345
+ needsRefresh,
346
+ parseCookies,
347
+ sealDelegationSession,
348
+ serializeSessionCookie,
349
+ uniformApiAudience,
350
+ uniformDelegationIssuer,
351
+ unsealDelegationSession,
352
+ verifyCsrf,
353
+ verifyDelegationAccessToken
354
+ };
package/package.json CHANGED
@@ -1,17 +1,35 @@
1
1
  {
2
2
  "name": "@uniformdev/mesh-sdk",
3
- "version": "20.72.3-alpha.2+36e92d30ab",
3
+ "version": "20.72.3-alpha.5+87cd60cb2e",
4
4
  "description": "Uniform Mesh Framework SDK",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
+ "engines": {
7
+ "node": ">=18"
8
+ },
6
9
  "main": "./dist/index.js",
7
10
  "module": "./dist/index.esm.js",
8
11
  "exports": {
9
- "import": {
10
- "types": "./dist/index.d.mts",
11
- "node": "./dist/index.mjs",
12
- "default": "./dist/index.esm.js"
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.mts",
15
+ "node": "./dist/index.mjs",
16
+ "default": "./dist/index.esm.js"
17
+ },
18
+ "require": "./dist/index.js"
13
19
  },
14
- "require": "./dist/index.js"
20
+ "./server": {
21
+ "import": {
22
+ "types": "./dist/server/index.d.mts",
23
+ "default": "./dist/server/index.mjs"
24
+ }
25
+ }
26
+ },
27
+ "typesVersions": {
28
+ "*": {
29
+ "server": [
30
+ "./dist/server/index.d.mts"
31
+ ]
32
+ }
15
33
  },
16
34
  "types": "./dist/index.d.ts",
17
35
  "sideEffects": false,
@@ -22,6 +40,7 @@
22
40
  "dev": "pnpm update-openapi && pnpm build:setversion && tsup --watch",
23
41
  "clean": "rimraf dist",
24
42
  "format": "prettier --write \"src/**/*.{js,ts,tsx}\"",
43
+ "test": "vitest run",
25
44
  "update-openapi": "tsx ./scripts/update-openapi.cts",
26
45
  "apidocs-extract": "api-extractor run --local"
27
46
  },
@@ -32,16 +51,19 @@
32
51
  "access": "public"
33
52
  },
34
53
  "dependencies": {
35
- "@uniformdev/assets": "20.72.3-alpha.2+36e92d30ab",
36
- "@uniformdev/canvas": "20.72.3-alpha.2+36e92d30ab",
37
- "@uniformdev/context": "20.72.3-alpha.2+36e92d30ab",
38
- "@uniformdev/project-map": "20.72.3-alpha.2+36e92d30ab",
54
+ "@uniformdev/assets": "20.72.3-alpha.5+87cd60cb2e",
55
+ "@uniformdev/canvas": "20.72.3-alpha.5+87cd60cb2e",
56
+ "@uniformdev/context": "20.72.3-alpha.5+87cd60cb2e",
57
+ "@uniformdev/project-map": "20.72.3-alpha.5+87cd60cb2e",
58
+ "cookie": "^1.1.1",
39
59
  "imagesloaded": "^5.0.0",
60
+ "jose": "^6.2.2",
40
61
  "mitt": "^3.0.1"
41
62
  },
42
63
  "devDependencies": {
43
64
  "@types/imagesloaded": "^4.1.2",
44
- "openai": "6.44.0"
65
+ "openai": "6.44.0",
66
+ "tsup": "8.5.1"
45
67
  },
46
- "gitHead": "36e92d30ab1b97fc68cf16fc22c6f7865ac8ba97"
68
+ "gitHead": "87cd60cb2eae75aac26c8c51566e66281c3921f9"
47
69
  }