@foir/sdk 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -184,6 +184,67 @@ function parseContextHeader(raw) {
184
184
  return out;
185
185
  }
186
186
 
187
+ // src/server/require-scoped-token.ts
188
+ var TOKEN_HEADER = "X-Foir-Token";
189
+ var CONTEXT_HEADER = "X-Foir-Context";
190
+ var DEFAULT_JWKS_URL = "https://api.foir.dev/.well-known/jwks.json";
191
+ var ScopedTokenError = class extends Error {
192
+ status;
193
+ constructor(message, status) {
194
+ super(message);
195
+ this.name = "ScopedTokenError";
196
+ this.status = status;
197
+ }
198
+ };
199
+ async function requireScopedToken(request, options) {
200
+ const token = request.headers.get(TOKEN_HEADER);
201
+ if (!token) {
202
+ throw new ScopedTokenError(
203
+ `missing ${TOKEN_HEADER} header \u2014 this route only accepts platform-dispatched requests`,
204
+ 401
205
+ );
206
+ }
207
+ let claims;
208
+ try {
209
+ claims = await verifyScopedToken(
210
+ token,
211
+ options?.jwksUrl ?? DEFAULT_JWKS_URL,
212
+ options
213
+ );
214
+ } catch (err) {
215
+ throw new ScopedTokenError(
216
+ `invalid scoped token: ${err instanceof Error ? err.message : String(err)}`,
217
+ 401
218
+ );
219
+ }
220
+ let subject;
221
+ try {
222
+ subject = parseSubject(claims.sub);
223
+ } catch (err) {
224
+ throw new ScopedTokenError(
225
+ `invalid scoped token subject: ${err instanceof Error ? err.message : String(err)}`,
226
+ 401
227
+ );
228
+ }
229
+ if (options?.expectedProjectId && subject.projectId !== options.expectedProjectId) {
230
+ throw new ScopedTokenError(
231
+ "scoped token is bound to a different project",
232
+ 403
233
+ );
234
+ }
235
+ if (options?.expectedTenantId && subject.tenantId !== options.expectedTenantId) {
236
+ throw new ScopedTokenError(
237
+ "scoped token is bound to a different tenant",
238
+ 403
239
+ );
240
+ }
241
+ return {
242
+ claims,
243
+ subject,
244
+ context: parseContextHeader(request.headers.get(CONTEXT_HEADER))
245
+ };
246
+ }
247
+
187
248
  // src/server/callback-client.ts
188
249
  function createCallbackClient(callback, opts) {
189
250
  const { executionId, signingSecret } = opts;
@@ -284,6 +345,7 @@ async function hmacSha256Hex(secret, body) {
284
345
  return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
285
346
  }
286
347
  export {
348
+ ScopedTokenError,
287
349
  ServerClientError,
288
350
  createCallbackClient,
289
351
  createServerClient,
@@ -291,6 +353,7 @@ export {
291
353
  parseSubject,
292
354
  recordsReadModels,
293
355
  recordsWriteModels,
356
+ requireScopedToken,
294
357
  verifyScopedToken,
295
358
  verifyWebhookSignature
296
359
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Official Foir SDK — editor iframe client + server-side platform helpers",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -33,6 +33,18 @@
33
33
  "types": "./dist/auth.d.ts",
34
34
  "import": "./dist/auth.js"
35
35
  },
36
+ "./react": {
37
+ "types": "./dist/react.d.ts",
38
+ "import": "./dist/react.js"
39
+ },
40
+ "./next": {
41
+ "types": "./dist/next.d.ts",
42
+ "import": "./dist/next.js"
43
+ },
44
+ "./push": {
45
+ "types": "./dist/push.d.ts",
46
+ "import": "./dist/push.js"
47
+ },
36
48
  "./realtime": {
37
49
  "types": "./dist/realtime.d.ts",
38
50
  "import": "./dist/realtime.js"