deepspace 0.3.11 → 0.4.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
@@ -165,6 +165,11 @@ var BUILT_IN_TOOLS = [
165
165
 
166
166
  // src/server/utils/scoped-r2-files.ts
167
167
  var CORS_HEADERS = { "Access-Control-Allow-Origin": "*" };
168
+ function contentDisposition(originalName) {
169
+ const safe = originalName.replace(/[\x00-\x1f\x7f"\\]/g, "_") || "download";
170
+ const encoded = encodeURIComponent(originalName);
171
+ return `inline; filename="${safe}"; filename*=UTF-8''${encoded}`;
172
+ }
168
173
  function sanitizeSubpath(raw) {
169
174
  if (!raw) return "";
170
175
  const segments = raw.split("/");
@@ -352,339 +357,11 @@ async function handleDownload(bucket, key) {
352
357
  headers.set("Cache-Control", "public, max-age=31536000, immutable");
353
358
  headers.set("Access-Control-Allow-Origin", "*");
354
359
  if (object.customMetadata?.originalName) {
355
- headers.set("Content-Disposition", `inline; filename="${object.customMetadata.originalName}"`);
360
+ headers.set("Content-Disposition", contentDisposition(object.customMetadata.originalName));
356
361
  }
357
362
  return new Response(object.body, { headers });
358
363
  }
359
364
 
360
- // src/server/auth/jwtVerifier.ts
361
- import { importSPKI, jwtVerify } from "jose";
362
-
363
- // src/server/auth/utils.ts
364
- var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
365
- function bufferToHex(bytes) {
366
- const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
367
- let hex = "";
368
- for (const byte of view) {
369
- hex += byte.toString(16).padStart(2, "0");
370
- }
371
- return hex;
372
- }
373
- function normalizeArray(value) {
374
- if (value == null) return void 0;
375
- if (Array.isArray(value)) {
376
- return value.length ? value : void 0;
377
- }
378
- return [value];
379
- }
380
- function decodeJwtPayload(token) {
381
- if (!token) return void 0;
382
- const parts = token.split(".");
383
- if (parts.length < 2) return void 0;
384
- const payloadSegment = parts[1];
385
- if (!payloadSegment) return void 0;
386
- if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
387
- try {
388
- const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
389
- const bytes = base64ToUint8Array(normalized);
390
- const json = new TextDecoder().decode(bytes);
391
- const payload = JSON.parse(json);
392
- return {
393
- iss: payload.iss ?? null,
394
- aud: payload.aud ?? null,
395
- azp: payload?.azp ?? null,
396
- exp: payload.exp ?? null,
397
- iat: payload.iat ?? null
398
- };
399
- } catch {
400
- return void 0;
401
- }
402
- }
403
- function nowMs() {
404
- return Date.now();
405
- }
406
- function base64ToUint8Array(value) {
407
- if (typeof atob === "function") {
408
- const binary = atob(value);
409
- const out = new Uint8Array(binary.length);
410
- for (let i = 0; i < binary.length; i += 1) {
411
- out[i] = binary.charCodeAt(i);
412
- }
413
- return out;
414
- }
415
- if (typeof Buffer !== "undefined") {
416
- return Uint8Array.from(Buffer.from(value, "base64"));
417
- }
418
- throw new Error("No base64 decoder available in current environment");
419
- }
420
-
421
- // src/server/auth/jwtVerifier.ts
422
- var DEFAULT_CLOCK_SKEW_MS = 5e3;
423
- function matchesAuthorizedParty(azp, patterns) {
424
- if (!azp) return true;
425
- for (const pattern of patterns) {
426
- if (pattern === azp) return true;
427
- if (pattern.includes("*")) {
428
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
429
- const regexPattern = escaped.replace(/\*/g, "[^/]*");
430
- const regex = new RegExp(`^${regexPattern}$`);
431
- if (regex.test(azp)) return true;
432
- if (pattern.includes("*.")) {
433
- const rootPattern = pattern.replace("*.", "");
434
- if (rootPattern === azp) return true;
435
- }
436
- }
437
- }
438
- return false;
439
- }
440
- var keyCache = /* @__PURE__ */ new Map();
441
- async function getPublicKey(pem) {
442
- const cached = keyCache.get(pem);
443
- if (cached) return cached;
444
- const normalized = pem.replace(/\\n/g, "\n");
445
- const key = await importSPKI(normalized, "ES256");
446
- keyCache.set(pem, key);
447
- return key;
448
- }
449
- async function verifyJwt(config, token) {
450
- if (!token) {
451
- return { result: null };
452
- }
453
- try {
454
- const publicKey = await getPublicKey(config.publicKey);
455
- const audience = normalizeArray(config.audience);
456
- const clockTolerance = Math.floor(
457
- (config.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS) / 1e3
458
- );
459
- const { payload } = await jwtVerify(token, publicKey, {
460
- issuer: config.issuer,
461
- audience: audience && audience.length === 1 ? audience[0] : audience,
462
- clockTolerance
463
- });
464
- const claims = payload;
465
- if (!claims.sub) {
466
- throw new Error("JWT verification succeeded but subject claim is missing");
467
- }
468
- const authorizedParties = normalizeArray(config.authorizedParties);
469
- if (authorizedParties) {
470
- const azp = claims.azp;
471
- if (!matchesAuthorizedParty(azp, authorizedParties)) {
472
- throw new Error(
473
- `Authorized party '${azp}' does not match any allowed pattern`
474
- );
475
- }
476
- }
477
- return {
478
- result: {
479
- userId: claims.sub,
480
- claims
481
- }
482
- };
483
- } catch (error) {
484
- return {
485
- result: null,
486
- error,
487
- debug: decodeJwtPayload(token)
488
- };
489
- }
490
- }
491
-
492
- // src/server/auth/internalAuth.ts
493
- var DEFAULT_MAX_SKEW_MS = 5 * 6e4;
494
- var nodeCryptoPromise = null;
495
- async function loadNodeCrypto() {
496
- if (!nodeCryptoPromise) {
497
- nodeCryptoPromise = import("crypto").then((module) => module).catch(() => null);
498
- }
499
- return nodeCryptoPromise;
500
- }
501
- function hasSubtleCrypto() {
502
- return typeof globalThis.crypto?.subtle?.importKey === "function";
503
- }
504
- async function computeHmacHex(secret, payload) {
505
- if (hasSubtleCrypto()) {
506
- const encoder = new TextEncoder();
507
- const key = await crypto.subtle.importKey(
508
- "raw",
509
- encoder.encode(secret),
510
- { name: "HMAC", hash: "SHA-256" },
511
- false,
512
- ["sign"]
513
- );
514
- const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
515
- return bufferToHex(signature);
516
- }
517
- const nodeCrypto = await loadNodeCrypto();
518
- if (!nodeCrypto) {
519
- throw new Error("Crypto implementation unavailable for HMAC computation");
520
- }
521
- return nodeCrypto.createHmac("sha256", secret).update(payload).digest("hex");
522
- }
523
- async function timingSafeEqualHex(a, b) {
524
- if (a.length !== b.length) {
525
- return false;
526
- }
527
- const nodeCrypto = await loadNodeCrypto();
528
- if (nodeCrypto?.timingSafeEqual) {
529
- const aBuf = Buffer.from(a, "hex");
530
- const bBuf = Buffer.from(b, "hex");
531
- try {
532
- return nodeCrypto.timingSafeEqual(aBuf, bBuf);
533
- } catch {
534
- return false;
535
- }
536
- }
537
- let diff = 0;
538
- for (let i = 0; i < a.length; i += 1) {
539
- diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
540
- }
541
- return diff === 0;
542
- }
543
- async function signInternalPayload({
544
- secret,
545
- payload,
546
- timestamp
547
- }) {
548
- const ts = timestamp ?? nowMs().toString();
549
- const signature = await computeHmacHex(secret, `${ts}.${payload}`);
550
- return { timestamp: ts, signature };
551
- }
552
- async function verifyInternalSignature({
553
- secret,
554
- timestamp,
555
- signature,
556
- payload,
557
- maxSkewMs = DEFAULT_MAX_SKEW_MS
558
- }) {
559
- if (!secret || !timestamp || !signature) {
560
- return false;
561
- }
562
- const tsNumber = Number(timestamp);
563
- if (!Number.isFinite(tsNumber)) {
564
- return false;
565
- }
566
- const skew = Math.abs(nowMs() - tsNumber);
567
- if (skew > maxSkewMs) {
568
- return false;
569
- }
570
- const expected = await computeHmacHex(secret, `${timestamp}.${payload}`);
571
- return timingSafeEqualHex(signature.toLowerCase(), expected.toLowerCase());
572
- }
573
- function buildInternalPayload(body) {
574
- if (body === void 0) return "{}";
575
- if (typeof body === "string") return body;
576
- try {
577
- return JSON.stringify(body);
578
- } catch {
579
- return "{}";
580
- }
581
- }
582
-
583
- // src/server/auth/betterAuth.ts
584
- import { betterAuth } from "better-auth";
585
- import { organization, twoFactor } from "better-auth/plugins";
586
- function createDeepSpaceAuth(config) {
587
- const socialProviders = {};
588
- if (config.google) {
589
- socialProviders.google = config.google;
590
- }
591
- if (config.github) {
592
- socialProviders.github = config.github;
593
- }
594
- return betterAuth({
595
- database: config.database,
596
- baseURL: config.baseURL,
597
- secret: config.secret,
598
- emailAndPassword: {
599
- enabled: config.emailAndPassword ?? true
600
- },
601
- socialProviders,
602
- trustedOrigins: config.trustedOrigins ?? [
603
- "https://deep.space",
604
- "https://*.deep.space",
605
- "https://*.app.space",
606
- "http://localhost:*"
607
- ],
608
- plugins: [organization(), twoFactor()]
609
- });
610
- }
611
-
612
- // src/server/utils/cron.ts
613
- var DEFAULT_API_BASE_URL = "https://api-worker.deep.space";
614
- function buildCronContext(env, ownerUserId, roomId = "default") {
615
- const hmacSecret = env.INTERNAL_STORAGE_HMAC_SECRET;
616
- const apiBase = env.API_BASE_URL || DEFAULT_API_BASE_URL;
617
- const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
618
- const room = env.RECORD_ROOMS.get(roomIdObj);
619
- async function executeTool3(tool, params) {
620
- const response = await room.fetch(new Request("https://internal/api/tools/execute", {
621
- method: "POST",
622
- headers: {
623
- "Content-Type": "application/json",
624
- "X-User-Id": ownerUserId,
625
- "X-App-Action": "true"
626
- },
627
- body: JSON.stringify({ tool, params })
628
- }));
629
- const result = await response.json();
630
- if (!result.success) {
631
- throw new Error(`RecordRoom tool ${tool} failed: ${result.error || "Unknown error"}`);
632
- }
633
- return result.data;
634
- }
635
- const records = {
636
- async query(collection, opts) {
637
- const params = { collection };
638
- if (opts?.where) params.where = opts.where;
639
- if (opts?.limit) params.limit = opts.limit;
640
- const data = await executeTool3("records.query", params);
641
- return data.records || [];
642
- },
643
- async create(collection, data) {
644
- return executeTool3("records.create", { collection, data });
645
- },
646
- async update(collection, recordId, data) {
647
- return executeTool3("records.update", { collection, recordId, data });
648
- },
649
- async delete(collection, recordId) {
650
- return executeTool3("records.delete", { collection, recordId });
651
- }
652
- };
653
- const integrations = {
654
- async call(endpoint, params) {
655
- if (!hmacSecret) {
656
- throw new Error("INTERNAL_STORAGE_HMAC_SECRET not configured \u2014 cannot call integrations");
657
- }
658
- const payload = { userId: ownerUserId, ...params };
659
- const payloadStr = buildInternalPayload(payload);
660
- const { timestamp, signature } = await signInternalPayload({
661
- secret: hmacSecret,
662
- payload: payloadStr
663
- });
664
- const response = await fetch(`${apiBase}/api/internal/integrations/${endpoint}`, {
665
- method: "POST",
666
- headers: {
667
- "Content-Type": "application/json",
668
- "x-internal-timestamp": timestamp,
669
- "x-internal-signature": signature
670
- },
671
- body: payloadStr
672
- });
673
- if (!response.ok) {
674
- const errText = await response.text();
675
- throw new Error(`Integration call ${endpoint} failed (${response.status}): ${errText}`);
676
- }
677
- return response.json();
678
- }
679
- };
680
- return { records, integrations, ownerUserId };
681
- }
682
-
683
- // src/server/utils/ai.ts
684
- import { createAnthropic } from "@ai-sdk/anthropic";
685
- import { createOpenAI } from "@ai-sdk/openai";
686
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
687
-
688
365
  // src/server/utils/proxies.ts
689
366
  function resolveApiTransport(env) {
690
367
  if (env.API_WORKER) return { kind: "binding", fetcher: env.API_WORKER };
@@ -763,7 +440,79 @@ function authWorkerFetch(env, path, init) {
763
440
  return fetch(`${baseUrl}${pathAndSearch}`, init);
764
441
  }
765
442
 
443
+ // src/server/utils/cron.ts
444
+ function buildCronContext(env, ownerUserId, roomId = "default") {
445
+ const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
446
+ const room = env.RECORD_ROOMS.get(roomIdObj);
447
+ async function executeTool3(tool, params) {
448
+ const response = await room.fetch(new Request("https://internal/api/tools/execute", {
449
+ method: "POST",
450
+ headers: {
451
+ "Content-Type": "application/json",
452
+ "X-User-Id": ownerUserId,
453
+ "X-App-Action": "true"
454
+ },
455
+ body: JSON.stringify({ tool, params })
456
+ }));
457
+ const result = await response.json();
458
+ if (!result.success) {
459
+ throw new Error(`RecordRoom tool ${tool} failed: ${result.error || "Unknown error"}`);
460
+ }
461
+ return result.data;
462
+ }
463
+ const records = {
464
+ async query(collection, opts) {
465
+ const params = { collection };
466
+ if (opts?.where) params.where = opts.where;
467
+ if (opts?.limit) params.limit = opts.limit;
468
+ const data = await executeTool3("records.query", params);
469
+ return data.records || [];
470
+ },
471
+ async create(collection, data) {
472
+ return executeTool3("records.create", { collection, data });
473
+ },
474
+ async update(collection, recordId, data) {
475
+ return executeTool3("records.update", { collection, recordId, data });
476
+ },
477
+ async delete(collection, recordId) {
478
+ return executeTool3("records.delete", { collection, recordId });
479
+ }
480
+ };
481
+ const integrations = {
482
+ async call(endpoint, params = {}) {
483
+ if (!env.APP_OWNER_JWT) {
484
+ throw new Error(
485
+ "integrations.call requires env.APP_OWNER_JWT. Deployed apps receive this automatically; in dev, `deepspace dev` writes it into .dev.vars."
486
+ );
487
+ }
488
+ const response = await apiWorkerFetch(env, `/api/integrations/${endpoint}`, {
489
+ method: "POST",
490
+ headers: {
491
+ "Content-Type": "application/json",
492
+ Authorization: `Bearer ${env.APP_OWNER_JWT}`
493
+ },
494
+ body: JSON.stringify(params)
495
+ });
496
+ const text = await response.text();
497
+ let result = {};
498
+ try {
499
+ result = text ? JSON.parse(text) : {};
500
+ } catch {
501
+ }
502
+ if (!response.ok || !result.success) {
503
+ const detail = result.message || result.error || text || `HTTP ${response.status}`;
504
+ throw new Error(`Integration call ${endpoint} failed: ${detail}`);
505
+ }
506
+ return result.data;
507
+ }
508
+ };
509
+ return { records, integrations, ownerUserId };
510
+ }
511
+
766
512
  // src/server/utils/ai.ts
513
+ import { createAnthropic } from "@ai-sdk/anthropic";
514
+ import { createOpenAI } from "@ai-sdk/openai";
515
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
767
516
  var ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 4096;
768
517
  function withDefaultAnthropicMaxTokens(body) {
769
518
  if (typeof body !== "string") return body;
@@ -1566,6 +1315,13 @@ var MSG = {
1566
1315
  USER_LIST: "user.list",
1567
1316
  SET_ROLE: "user.set_role",
1568
1317
  USER_UPDATE: "user.update",
1318
+ // ---- Auth signal -----------------------------------------------------
1319
+ // Server → client one-shot on connect from rooms that enforce role-based
1320
+ // writes (Canvas, Game, Cron). Tells the client whether this connection
1321
+ // can write so its hook can disable local write APIs and the UI can
1322
+ // render controls accordingly. Without this, a viewer's click would
1323
+ // round-trip to the server and silently come back as ERROR.
1324
+ AUTH: "auth",
1569
1325
  // ---- Yjs JSON control frames (binary sync lives in ./yjs.ts) ---------
1570
1326
  YJS_JOIN: "yjs.join",
1571
1327
  YJS_LEAVE: "yjs.leave",
@@ -4201,7 +3957,20 @@ var YjsRoom = class extends BaseRoom {
4201
3957
  }
4202
3958
  };
4203
3959
 
3960
+ // src/shared/roles.ts
3961
+ var ROLES = {
3962
+ VIEWER: "viewer",
3963
+ MEMBER: "member",
3964
+ ADMIN: "admin"
3965
+ };
3966
+
4204
3967
  // src/server/rooms/game-room.ts
3968
+ var GAME_WRITE_TYPES = /* @__PURE__ */ new Set([
3969
+ MSG.GAME_INPUT,
3970
+ MSG.GAME_PLAYER_READY,
3971
+ MSG.GAME_START,
3972
+ MSG.GAME_END
3973
+ ]);
4205
3974
  var GameRoom = class extends BaseRoom {
4206
3975
  config;
4207
3976
  players = /* @__PURE__ */ new Map();
@@ -4257,10 +4026,26 @@ var GameRoom = class extends BaseRoom {
4257
4026
  // ==========================================================================
4258
4027
  onConnect(ws, user) {
4259
4028
  this.ensureInitialized();
4029
+ const role = user.role ?? ROLES.VIEWER;
4030
+ const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN;
4260
4031
  const attachment = {
4261
4032
  ...user,
4262
- joinedAt: (/* @__PURE__ */ new Date()).toISOString()
4033
+ joinedAt: (/* @__PURE__ */ new Date()).toISOString(),
4034
+ canWrite
4263
4035
  };
4036
+ this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } });
4037
+ if (!canWrite) {
4038
+ this.sendTo(ws, {
4039
+ type: MSG.GAME_STATE,
4040
+ payload: {
4041
+ state: this.gameState,
4042
+ tick: this.currentTick,
4043
+ players: Array.from(this.players.values()),
4044
+ running: this.running
4045
+ }
4046
+ });
4047
+ return attachment;
4048
+ }
4264
4049
  const player = {
4265
4050
  userId: user.userId,
4266
4051
  userName: user.userName,
@@ -4289,6 +4074,13 @@ var GameRoom = class extends BaseRoom {
4289
4074
  async onMessage(ws, user, message) {
4290
4075
  this.ensureInitialized();
4291
4076
  const { type, payload } = message;
4077
+ if (GAME_WRITE_TYPES.has(type) && !user.canWrite) {
4078
+ this.sendTo(ws, {
4079
+ type: MSG.ERROR,
4080
+ payload: { error: "Write access denied: spectators cannot drive the game" }
4081
+ });
4082
+ return;
4083
+ }
4292
4084
  switch (type) {
4293
4085
  case MSG.GAME_INPUT: {
4294
4086
  this.inputBuffer.push({
@@ -4437,6 +4229,15 @@ var GameRoom = class extends BaseRoom {
4437
4229
 
4438
4230
  // src/server/rooms/canvas-room.ts
4439
4231
  import * as Y5 from "yjs";
4232
+ var CANVAS_WRITE_TYPES = /* @__PURE__ */ new Set([
4233
+ MSG.CANVAS_ADD,
4234
+ MSG.CANVAS_MOVE,
4235
+ MSG.CANVAS_RESIZE,
4236
+ MSG.CANVAS_DELETE,
4237
+ MSG.CANVAS_UPDATE,
4238
+ MSG.CANVAS_UNDO,
4239
+ MSG.CANVAS_REDO
4240
+ ]);
4440
4241
  var CanvasRoom = class extends BaseRoom {
4441
4242
  doc = null;
4442
4243
  initialized = false;
@@ -4496,10 +4297,14 @@ var CanvasRoom = class extends BaseRoom {
4496
4297
  }
4497
4298
  onConnect(ws, user) {
4498
4299
  this.ensureInitialized();
4300
+ const role = user.role ?? ROLES.VIEWER;
4301
+ const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN;
4499
4302
  const attachment = {
4500
4303
  ...user,
4501
- viewport: null
4304
+ viewport: null,
4305
+ canWrite
4502
4306
  };
4307
+ this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } });
4503
4308
  const shapes = this.getAllShapes();
4504
4309
  this.sendTo(ws, {
4505
4310
  type: MSG.CANVAS_SHAPES,
@@ -4513,6 +4318,13 @@ var CanvasRoom = class extends BaseRoom {
4513
4318
  async onMessage(ws, user, message) {
4514
4319
  this.ensureInitialized();
4515
4320
  const { type, payload } = message;
4321
+ if (CANVAS_WRITE_TYPES.has(type) && !user.canWrite) {
4322
+ this.sendTo(ws, {
4323
+ type: MSG.ERROR,
4324
+ payload: { error: "Write access denied: viewer role cannot modify the canvas" }
4325
+ });
4326
+ return;
4327
+ }
4516
4328
  switch (type) {
4517
4329
  case MSG.CANVAS_ADD: {
4518
4330
  const shape = payload;
@@ -4807,6 +4619,11 @@ var PresenceRoom = class extends BaseRoom {
4807
4619
  };
4808
4620
 
4809
4621
  // src/server/rooms/cron-room.ts
4622
+ var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
4623
+ MSG.CRON_TRIGGER,
4624
+ MSG.CRON_PAUSE,
4625
+ MSG.CRON_RESUME
4626
+ ]);
4810
4627
  var CronRoom = class extends BaseRoom {
4811
4628
  tasks;
4812
4629
  initialized = false;
@@ -4885,6 +4702,9 @@ var CronRoom = class extends BaseRoom {
4885
4702
  }
4886
4703
  onConnect(ws, user) {
4887
4704
  this.ensureInitialized();
4705
+ const role = user.role ?? ROLES.VIEWER;
4706
+ const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN;
4707
+ this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } });
4888
4708
  this.sendTo(ws, {
4889
4709
  type: MSG.CRON_TASKS,
4890
4710
  payload: { tasks: this.getTaskStates() }
@@ -4893,11 +4713,18 @@ var CronRoom = class extends BaseRoom {
4893
4713
  type: MSG.CRON_HISTORY,
4894
4714
  payload: { history: this.getRecentHistory(50) }
4895
4715
  });
4896
- return user;
4716
+ return { ...user, canWrite };
4897
4717
  }
4898
4718
  async onMessage(ws, user, message) {
4899
4719
  this.ensureInitialized();
4900
4720
  const { type, payload } = message;
4721
+ if (CRON_WRITE_TYPES.has(type) && !user.canWrite) {
4722
+ this.sendTo(ws, {
4723
+ type: MSG.ERROR,
4724
+ payload: { error: "Write access denied: viewer role cannot modify cron tasks" }
4725
+ });
4726
+ return;
4727
+ }
4901
4728
  switch (type) {
4902
4729
  case MSG.CRON_TRIGGER: {
4903
4730
  const taskName = payload.taskName;
@@ -5825,7 +5652,6 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
5825
5652
  "AUTH_WORKER_URL",
5826
5653
  "APP_IDENTITY_TOKEN",
5827
5654
  "APP_OWNER_JWT",
5828
- "INTERNAL_STORAGE_HMAC_SECRET",
5829
5655
  "USAGE_EVENTS"
5830
5656
  ]);
5831
5657
  function validateBindingManifest(manifest) {
@@ -6657,6 +6483,215 @@ var RESERVED_COLLECTION_NAMES = new Set(
6657
6483
  GLOBAL_DO_TYPES.flatMap((t) => t.schemas.map((s) => s.name))
6658
6484
  );
6659
6485
 
6486
+ // src/server/auth/jwtVerifier.ts
6487
+ import { importSPKI, jwtVerify } from "jose";
6488
+
6489
+ // src/server/auth/utils.ts
6490
+ var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
6491
+ function bufferToHex(bytes) {
6492
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
6493
+ let hex = "";
6494
+ for (const byte of view) {
6495
+ hex += byte.toString(16).padStart(2, "0");
6496
+ }
6497
+ return hex;
6498
+ }
6499
+ function normalizeArray(value) {
6500
+ if (value == null) return void 0;
6501
+ if (Array.isArray(value)) {
6502
+ return value.length ? value : void 0;
6503
+ }
6504
+ return [value];
6505
+ }
6506
+ function decodeJwtPayload(token) {
6507
+ if (!token) return void 0;
6508
+ const parts = token.split(".");
6509
+ if (parts.length < 2) return void 0;
6510
+ const payloadSegment = parts[1];
6511
+ if (!payloadSegment) return void 0;
6512
+ if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
6513
+ try {
6514
+ const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
6515
+ const bytes = base64ToUint8Array(normalized);
6516
+ const json = new TextDecoder().decode(bytes);
6517
+ const payload = JSON.parse(json);
6518
+ return {
6519
+ iss: payload.iss ?? null,
6520
+ aud: payload.aud ?? null,
6521
+ azp: payload?.azp ?? null,
6522
+ exp: payload.exp ?? null,
6523
+ iat: payload.iat ?? null
6524
+ };
6525
+ } catch {
6526
+ return void 0;
6527
+ }
6528
+ }
6529
+ function base64ToUint8Array(value) {
6530
+ if (typeof atob === "function") {
6531
+ const binary = atob(value);
6532
+ const out = new Uint8Array(binary.length);
6533
+ for (let i = 0; i < binary.length; i += 1) {
6534
+ out[i] = binary.charCodeAt(i);
6535
+ }
6536
+ return out;
6537
+ }
6538
+ if (typeof Buffer !== "undefined") {
6539
+ return Uint8Array.from(Buffer.from(value, "base64"));
6540
+ }
6541
+ throw new Error("No base64 decoder available in current environment");
6542
+ }
6543
+
6544
+ // src/server/auth/jwtVerifier.ts
6545
+ var DEFAULT_CLOCK_SKEW_MS = 5e3;
6546
+ function matchesAuthorizedParty(azp, patterns) {
6547
+ if (!azp) return true;
6548
+ for (const pattern of patterns) {
6549
+ if (pattern === azp) return true;
6550
+ if (pattern.includes("*")) {
6551
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
6552
+ const regexPattern = escaped.replace(/\*/g, "[^/]*");
6553
+ const regex = new RegExp(`^${regexPattern}$`);
6554
+ if (regex.test(azp)) return true;
6555
+ if (pattern.includes("*.")) {
6556
+ const rootPattern = pattern.replace("*.", "");
6557
+ if (rootPattern === azp) return true;
6558
+ }
6559
+ }
6560
+ }
6561
+ return false;
6562
+ }
6563
+ var keyCache = /* @__PURE__ */ new Map();
6564
+ async function getPublicKey(pem) {
6565
+ const cached = keyCache.get(pem);
6566
+ if (cached) return cached;
6567
+ const normalized = pem.replace(/\\n/g, "\n");
6568
+ const key = await importSPKI(normalized, "ES256");
6569
+ keyCache.set(pem, key);
6570
+ return key;
6571
+ }
6572
+ async function verifyJwt(config, token) {
6573
+ if (!token) {
6574
+ return { result: null };
6575
+ }
6576
+ try {
6577
+ const publicKey = await getPublicKey(config.publicKey);
6578
+ const audience = normalizeArray(config.audience);
6579
+ const clockTolerance = Math.floor(
6580
+ (config.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS) / 1e3
6581
+ );
6582
+ const { payload } = await jwtVerify(token, publicKey, {
6583
+ issuer: config.issuer,
6584
+ audience: audience && audience.length === 1 ? audience[0] : audience,
6585
+ clockTolerance
6586
+ });
6587
+ const claims = payload;
6588
+ if (!claims.sub) {
6589
+ throw new Error("JWT verification succeeded but subject claim is missing");
6590
+ }
6591
+ const authorizedParties = normalizeArray(config.authorizedParties);
6592
+ if (authorizedParties) {
6593
+ const azp = claims.azp;
6594
+ if (!matchesAuthorizedParty(azp, authorizedParties)) {
6595
+ throw new Error(
6596
+ `Authorized party '${azp}' does not match any allowed pattern`
6597
+ );
6598
+ }
6599
+ }
6600
+ return {
6601
+ result: {
6602
+ userId: claims.sub,
6603
+ claims
6604
+ }
6605
+ };
6606
+ } catch (error) {
6607
+ return {
6608
+ result: null,
6609
+ error,
6610
+ debug: decodeJwtPayload(token)
6611
+ };
6612
+ }
6613
+ }
6614
+
6615
+ // src/server/auth/internalAuth.ts
6616
+ var nodeCryptoPromise = null;
6617
+ async function loadNodeCrypto() {
6618
+ if (!nodeCryptoPromise) {
6619
+ nodeCryptoPromise = import("crypto").then((module) => module).catch(() => null);
6620
+ }
6621
+ return nodeCryptoPromise;
6622
+ }
6623
+ function hasSubtleCrypto() {
6624
+ return typeof globalThis.crypto?.subtle?.importKey === "function";
6625
+ }
6626
+ async function computeHmacHex(secret, payload) {
6627
+ if (hasSubtleCrypto()) {
6628
+ const encoder = new TextEncoder();
6629
+ const key = await crypto.subtle.importKey(
6630
+ "raw",
6631
+ encoder.encode(secret),
6632
+ { name: "HMAC", hash: "SHA-256" },
6633
+ false,
6634
+ ["sign"]
6635
+ );
6636
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
6637
+ return bufferToHex(signature);
6638
+ }
6639
+ const nodeCrypto = await loadNodeCrypto();
6640
+ if (!nodeCrypto) {
6641
+ throw new Error("Crypto implementation unavailable for HMAC computation");
6642
+ }
6643
+ return nodeCrypto.createHmac("sha256", secret).update(payload).digest("hex");
6644
+ }
6645
+ async function timingSafeEqualHex(a, b) {
6646
+ if (a.length !== b.length) {
6647
+ return false;
6648
+ }
6649
+ const nodeCrypto = await loadNodeCrypto();
6650
+ if (nodeCrypto?.timingSafeEqual) {
6651
+ const aBuf = Buffer.from(a, "hex");
6652
+ const bBuf = Buffer.from(b, "hex");
6653
+ try {
6654
+ return nodeCrypto.timingSafeEqual(aBuf, bBuf);
6655
+ } catch {
6656
+ return false;
6657
+ }
6658
+ }
6659
+ let diff = 0;
6660
+ for (let i = 0; i < a.length; i += 1) {
6661
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
6662
+ }
6663
+ return diff === 0;
6664
+ }
6665
+
6666
+ // src/server/auth/betterAuth.ts
6667
+ import { betterAuth } from "better-auth";
6668
+ import { organization, twoFactor } from "better-auth/plugins";
6669
+ function createDeepSpaceAuth(config) {
6670
+ const socialProviders = {};
6671
+ if (config.google) {
6672
+ socialProviders.google = config.google;
6673
+ }
6674
+ if (config.github) {
6675
+ socialProviders.github = config.github;
6676
+ }
6677
+ return betterAuth({
6678
+ database: config.database,
6679
+ baseURL: config.baseURL,
6680
+ secret: config.secret,
6681
+ emailAndPassword: {
6682
+ enabled: config.emailAndPassword ?? true
6683
+ },
6684
+ socialProviders,
6685
+ trustedOrigins: config.trustedOrigins ?? [
6686
+ "https://deep.space",
6687
+ "https://*.deep.space",
6688
+ "https://*.app.space",
6689
+ "http://localhost:*"
6690
+ ],
6691
+ plugins: [organization(), twoFactor()]
6692
+ });
6693
+ }
6694
+
6660
6695
  // src/shared/subscription.ts
6661
6696
  var ENTITLED_STATUSES = /* @__PURE__ */ new Set([
6662
6697
  "active",
@@ -6821,7 +6856,6 @@ export {
6821
6856
  CronRoom,
6822
6857
  DEFAULT_CONTEXT_CONFIG,
6823
6858
  DEFAULT_DO_MANIFEST,
6824
- DEFAULT_MAX_SKEW_MS,
6825
6859
  DIRECTORY_SCHEMAS,
6826
6860
  GLOBAL_DO_TYPES,
6827
6861
  GLOBAL_DO_TYPE_NAMES,
@@ -6853,7 +6887,6 @@ export {
6853
6887
  broadcastChange,
6854
6888
  broadcastYjsUpdate,
6855
6889
  buildCronContext,
6856
- buildInternalPayload,
6857
6890
  buildTableSelect,
6858
6891
  buildUiParts,
6859
6892
  canCreate,
@@ -6924,7 +6957,6 @@ export {
6924
6957
  rowToData,
6925
6958
  runMigrations,
6926
6959
  saveYjsDoc,
6927
- signInternalPayload,
6928
6960
  timingSafeEqualHex,
6929
6961
  totalChars,
6930
6962
  truncateOldToolResults,
@@ -6934,7 +6966,6 @@ export {
6934
6966
  validateAppName,
6935
6967
  validateBindingManifest,
6936
6968
  validateDoManifest,
6937
- verifyInternalSignature,
6938
6969
  verifyJwt,
6939
6970
  workspaceAccountsSchema,
6940
6971
  workspaceContentSharesSchema,