deepspace 0.3.11 → 0.4.1
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/cli.js +258 -120
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +68 -26
- package/dist/index.js +108 -48
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +72 -73
- package/dist/server.js +371 -338
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +72 -73
- package/dist/worker.js +425 -392
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
- package/dist/testing.d.ts +0 -115
package/dist/worker.js
CHANGED
|
@@ -206,6 +206,13 @@ var MSG = {
|
|
|
206
206
|
USER_LIST: "user.list",
|
|
207
207
|
SET_ROLE: "user.set_role",
|
|
208
208
|
USER_UPDATE: "user.update",
|
|
209
|
+
// ---- Auth signal -----------------------------------------------------
|
|
210
|
+
// Server → client one-shot on connect from rooms that enforce role-based
|
|
211
|
+
// writes (Canvas, Game, Cron). Tells the client whether this connection
|
|
212
|
+
// can write so its hook can disable local write APIs and the UI can
|
|
213
|
+
// render controls accordingly. Without this, a viewer's click would
|
|
214
|
+
// round-trip to the server and silently come back as ERROR.
|
|
215
|
+
AUTH: "auth",
|
|
209
216
|
// ---- Yjs JSON control frames (binary sync lives in ./yjs.ts) ---------
|
|
210
217
|
YJS_JOIN: "yjs.join",
|
|
211
218
|
YJS_LEAVE: "yjs.leave",
|
|
@@ -3294,7 +3301,20 @@ var YjsRoom = class extends BaseRoom {
|
|
|
3294
3301
|
}
|
|
3295
3302
|
};
|
|
3296
3303
|
|
|
3304
|
+
// src/shared/roles.ts
|
|
3305
|
+
var ROLES = {
|
|
3306
|
+
VIEWER: "viewer",
|
|
3307
|
+
MEMBER: "member",
|
|
3308
|
+
ADMIN: "admin"
|
|
3309
|
+
};
|
|
3310
|
+
|
|
3297
3311
|
// src/server/rooms/game-room.ts
|
|
3312
|
+
var GAME_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
3313
|
+
MSG.GAME_INPUT,
|
|
3314
|
+
MSG.GAME_PLAYER_READY,
|
|
3315
|
+
MSG.GAME_START,
|
|
3316
|
+
MSG.GAME_END
|
|
3317
|
+
]);
|
|
3298
3318
|
var GameRoom = class extends BaseRoom {
|
|
3299
3319
|
config;
|
|
3300
3320
|
players = /* @__PURE__ */ new Map();
|
|
@@ -3350,10 +3370,26 @@ var GameRoom = class extends BaseRoom {
|
|
|
3350
3370
|
// ==========================================================================
|
|
3351
3371
|
onConnect(ws, user) {
|
|
3352
3372
|
this.ensureInitialized();
|
|
3373
|
+
const role = user.role ?? ROLES.VIEWER;
|
|
3374
|
+
const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN;
|
|
3353
3375
|
const attachment = {
|
|
3354
3376
|
...user,
|
|
3355
|
-
joinedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3377
|
+
joinedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3378
|
+
canWrite
|
|
3356
3379
|
};
|
|
3380
|
+
this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } });
|
|
3381
|
+
if (!canWrite) {
|
|
3382
|
+
this.sendTo(ws, {
|
|
3383
|
+
type: MSG.GAME_STATE,
|
|
3384
|
+
payload: {
|
|
3385
|
+
state: this.gameState,
|
|
3386
|
+
tick: this.currentTick,
|
|
3387
|
+
players: Array.from(this.players.values()),
|
|
3388
|
+
running: this.running
|
|
3389
|
+
}
|
|
3390
|
+
});
|
|
3391
|
+
return attachment;
|
|
3392
|
+
}
|
|
3357
3393
|
const player = {
|
|
3358
3394
|
userId: user.userId,
|
|
3359
3395
|
userName: user.userName,
|
|
@@ -3382,6 +3418,13 @@ var GameRoom = class extends BaseRoom {
|
|
|
3382
3418
|
async onMessage(ws, user, message) {
|
|
3383
3419
|
this.ensureInitialized();
|
|
3384
3420
|
const { type, payload } = message;
|
|
3421
|
+
if (GAME_WRITE_TYPES.has(type) && !user.canWrite) {
|
|
3422
|
+
this.sendTo(ws, {
|
|
3423
|
+
type: MSG.ERROR,
|
|
3424
|
+
payload: { error: "Write access denied: spectators cannot drive the game" }
|
|
3425
|
+
});
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3385
3428
|
switch (type) {
|
|
3386
3429
|
case MSG.GAME_INPUT: {
|
|
3387
3430
|
this.inputBuffer.push({
|
|
@@ -3530,6 +3573,15 @@ var GameRoom = class extends BaseRoom {
|
|
|
3530
3573
|
|
|
3531
3574
|
// src/server/rooms/canvas-room.ts
|
|
3532
3575
|
import * as Y5 from "yjs";
|
|
3576
|
+
var CANVAS_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
3577
|
+
MSG.CANVAS_ADD,
|
|
3578
|
+
MSG.CANVAS_MOVE,
|
|
3579
|
+
MSG.CANVAS_RESIZE,
|
|
3580
|
+
MSG.CANVAS_DELETE,
|
|
3581
|
+
MSG.CANVAS_UPDATE,
|
|
3582
|
+
MSG.CANVAS_UNDO,
|
|
3583
|
+
MSG.CANVAS_REDO
|
|
3584
|
+
]);
|
|
3533
3585
|
var CanvasRoom = class extends BaseRoom {
|
|
3534
3586
|
doc = null;
|
|
3535
3587
|
initialized = false;
|
|
@@ -3589,10 +3641,14 @@ var CanvasRoom = class extends BaseRoom {
|
|
|
3589
3641
|
}
|
|
3590
3642
|
onConnect(ws, user) {
|
|
3591
3643
|
this.ensureInitialized();
|
|
3644
|
+
const role = user.role ?? ROLES.VIEWER;
|
|
3645
|
+
const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN;
|
|
3592
3646
|
const attachment = {
|
|
3593
3647
|
...user,
|
|
3594
|
-
viewport: null
|
|
3648
|
+
viewport: null,
|
|
3649
|
+
canWrite
|
|
3595
3650
|
};
|
|
3651
|
+
this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } });
|
|
3596
3652
|
const shapes = this.getAllShapes();
|
|
3597
3653
|
this.sendTo(ws, {
|
|
3598
3654
|
type: MSG.CANVAS_SHAPES,
|
|
@@ -3606,6 +3662,13 @@ var CanvasRoom = class extends BaseRoom {
|
|
|
3606
3662
|
async onMessage(ws, user, message) {
|
|
3607
3663
|
this.ensureInitialized();
|
|
3608
3664
|
const { type, payload } = message;
|
|
3665
|
+
if (CANVAS_WRITE_TYPES.has(type) && !user.canWrite) {
|
|
3666
|
+
this.sendTo(ws, {
|
|
3667
|
+
type: MSG.ERROR,
|
|
3668
|
+
payload: { error: "Write access denied: viewer role cannot modify the canvas" }
|
|
3669
|
+
});
|
|
3670
|
+
return;
|
|
3671
|
+
}
|
|
3609
3672
|
switch (type) {
|
|
3610
3673
|
case MSG.CANVAS_ADD: {
|
|
3611
3674
|
const shape = payload;
|
|
@@ -3900,6 +3963,11 @@ var PresenceRoom = class extends BaseRoom {
|
|
|
3900
3963
|
};
|
|
3901
3964
|
|
|
3902
3965
|
// src/server/rooms/cron-room.ts
|
|
3966
|
+
var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
3967
|
+
MSG.CRON_TRIGGER,
|
|
3968
|
+
MSG.CRON_PAUSE,
|
|
3969
|
+
MSG.CRON_RESUME
|
|
3970
|
+
]);
|
|
3903
3971
|
var CronRoom = class extends BaseRoom {
|
|
3904
3972
|
tasks;
|
|
3905
3973
|
initialized = false;
|
|
@@ -3978,6 +4046,9 @@ var CronRoom = class extends BaseRoom {
|
|
|
3978
4046
|
}
|
|
3979
4047
|
onConnect(ws, user) {
|
|
3980
4048
|
this.ensureInitialized();
|
|
4049
|
+
const role = user.role ?? ROLES.VIEWER;
|
|
4050
|
+
const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN;
|
|
4051
|
+
this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } });
|
|
3981
4052
|
this.sendTo(ws, {
|
|
3982
4053
|
type: MSG.CRON_TASKS,
|
|
3983
4054
|
payload: { tasks: this.getTaskStates() }
|
|
@@ -3986,11 +4057,18 @@ var CronRoom = class extends BaseRoom {
|
|
|
3986
4057
|
type: MSG.CRON_HISTORY,
|
|
3987
4058
|
payload: { history: this.getRecentHistory(50) }
|
|
3988
4059
|
});
|
|
3989
|
-
return user;
|
|
4060
|
+
return { ...user, canWrite };
|
|
3990
4061
|
}
|
|
3991
4062
|
async onMessage(ws, user, message) {
|
|
3992
4063
|
this.ensureInitialized();
|
|
3993
4064
|
const { type, payload } = message;
|
|
4065
|
+
if (CRON_WRITE_TYPES.has(type) && !user.canWrite) {
|
|
4066
|
+
this.sendTo(ws, {
|
|
4067
|
+
type: MSG.ERROR,
|
|
4068
|
+
payload: { error: "Write access denied: viewer role cannot modify cron tasks" }
|
|
4069
|
+
});
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
3994
4072
|
switch (type) {
|
|
3995
4073
|
case MSG.CRON_TRIGGER: {
|
|
3996
4074
|
const taskName = payload.taskName;
|
|
@@ -4918,7 +4996,6 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
|
4918
4996
|
"AUTH_WORKER_URL",
|
|
4919
4997
|
"APP_IDENTITY_TOKEN",
|
|
4920
4998
|
"APP_OWNER_JWT",
|
|
4921
|
-
"INTERNAL_STORAGE_HMAC_SECRET",
|
|
4922
4999
|
"USAGE_EVENTS"
|
|
4923
5000
|
]);
|
|
4924
5001
|
function validateBindingManifest(manifest) {
|
|
@@ -5752,6 +5829,11 @@ var RESERVED_COLLECTION_NAMES = new Set(
|
|
|
5752
5829
|
|
|
5753
5830
|
// src/server/utils/scoped-r2-files.ts
|
|
5754
5831
|
var CORS_HEADERS3 = { "Access-Control-Allow-Origin": "*" };
|
|
5832
|
+
function contentDisposition(originalName) {
|
|
5833
|
+
const safe = originalName.replace(/[\x00-\x1f\x7f"\\]/g, "_") || "download";
|
|
5834
|
+
const encoded = encodeURIComponent(originalName);
|
|
5835
|
+
return `inline; filename="${safe}"; filename*=UTF-8''${encoded}`;
|
|
5836
|
+
}
|
|
5755
5837
|
function sanitizeSubpath(raw) {
|
|
5756
5838
|
if (!raw) return "";
|
|
5757
5839
|
const segments = raw.split("/");
|
|
@@ -5788,7 +5870,9 @@ function createScopedR2Handler(config) {
|
|
|
5788
5870
|
{ status: 400, headers: CORS_HEADERS3 }
|
|
5789
5871
|
);
|
|
5790
5872
|
}
|
|
5791
|
-
const
|
|
5873
|
+
const isKeyedGet = subpath !== "" && request.method === "GET";
|
|
5874
|
+
const defaultScope = isKeyedGet ? "app" : "self";
|
|
5875
|
+
const scope = url.searchParams.get("scope") || defaultScope;
|
|
5792
5876
|
const result = config.resolvePrefix(scope, { userId: auth.userId, url });
|
|
5793
5877
|
if (!result.prefix) {
|
|
5794
5878
|
const errorMsg = result.error ?? "Invalid scope";
|
|
@@ -5939,418 +6023,162 @@ async function handleDownload(bucket, key) {
|
|
|
5939
6023
|
headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
5940
6024
|
headers.set("Access-Control-Allow-Origin", "*");
|
|
5941
6025
|
if (object.customMetadata?.originalName) {
|
|
5942
|
-
headers.set("Content-Disposition",
|
|
6026
|
+
headers.set("Content-Disposition", contentDisposition(object.customMetadata.originalName));
|
|
5943
6027
|
}
|
|
5944
6028
|
return new Response(object.body, { headers });
|
|
5945
6029
|
}
|
|
5946
6030
|
|
|
5947
|
-
// src/server/
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
function bufferToHex(bytes) {
|
|
5953
|
-
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
5954
|
-
let hex = "";
|
|
5955
|
-
for (const byte of view) {
|
|
5956
|
-
hex += byte.toString(16).padStart(2, "0");
|
|
6031
|
+
// src/server/utils/proxies.ts
|
|
6032
|
+
function resolveApiTransport(env) {
|
|
6033
|
+
if (env.API_WORKER) return { kind: "binding", fetcher: env.API_WORKER };
|
|
6034
|
+
if (env.API_WORKER_URL) {
|
|
6035
|
+
return { kind: "url", baseUrl: env.API_WORKER_URL.replace(/\/$/, "") };
|
|
5957
6036
|
}
|
|
5958
|
-
|
|
6037
|
+
throw new Error(
|
|
6038
|
+
"apiWorkerFetch: neither env.API_WORKER nor env.API_WORKER_URL is set. Add a [[services]] binding in wrangler.toml for production, or let `deepspace dev` write API_WORKER_URL into .dev.vars for local development."
|
|
6039
|
+
);
|
|
5959
6040
|
}
|
|
5960
|
-
function
|
|
5961
|
-
if (
|
|
5962
|
-
if (
|
|
5963
|
-
return
|
|
6041
|
+
function resolvePlatformTransport(env) {
|
|
6042
|
+
if (env.PLATFORM_WORKER) return { kind: "binding", fetcher: env.PLATFORM_WORKER };
|
|
6043
|
+
if (env.PLATFORM_WORKER_URL) {
|
|
6044
|
+
return { kind: "url", baseUrl: env.PLATFORM_WORKER_URL.replace(/\/$/, "") };
|
|
5964
6045
|
}
|
|
5965
|
-
|
|
6046
|
+
throw new Error(
|
|
6047
|
+
"platformWorkerFetch: neither env.PLATFORM_WORKER nor env.PLATFORM_WORKER_URL is set. Add a [[services]] binding in wrangler.toml for production, or let `deepspace dev` write PLATFORM_WORKER_URL into .dev.vars for local development."
|
|
6048
|
+
);
|
|
5966
6049
|
}
|
|
5967
|
-
function
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
if (
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
return {
|
|
5980
|
-
iss: payload.iss ?? null,
|
|
5981
|
-
aud: payload.aud ?? null,
|
|
5982
|
-
azp: payload?.azp ?? null,
|
|
5983
|
-
exp: payload.exp ?? null,
|
|
5984
|
-
iat: payload.iat ?? null
|
|
5985
|
-
};
|
|
5986
|
-
} catch {
|
|
5987
|
-
return void 0;
|
|
6050
|
+
function buildTargetUrl(transport, path, internalHost) {
|
|
6051
|
+
let pathAndSearch;
|
|
6052
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
6053
|
+
const u = new URL(path);
|
|
6054
|
+
pathAndSearch = u.pathname + u.search;
|
|
6055
|
+
} else if (path.startsWith("/")) {
|
|
6056
|
+
pathAndSearch = path;
|
|
6057
|
+
} else {
|
|
6058
|
+
pathAndSearch = "/" + path;
|
|
6059
|
+
}
|
|
6060
|
+
if (transport.kind === "binding") {
|
|
6061
|
+
return `https://${internalHost}${pathAndSearch}`;
|
|
5988
6062
|
}
|
|
6063
|
+
return `${transport.baseUrl}${pathAndSearch}`;
|
|
5989
6064
|
}
|
|
5990
|
-
function
|
|
5991
|
-
|
|
6065
|
+
function apiWorkerFetch(env, path, init) {
|
|
6066
|
+
const transport = resolveApiTransport(env);
|
|
6067
|
+
const url = buildTargetUrl(transport, path, "api-worker");
|
|
6068
|
+
if (transport.kind === "binding") {
|
|
6069
|
+
return transport.fetcher.fetch(url, init);
|
|
6070
|
+
}
|
|
6071
|
+
return fetch(url, init);
|
|
5992
6072
|
}
|
|
5993
|
-
function
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
const
|
|
5997
|
-
|
|
5998
|
-
|
|
6073
|
+
function platformWorkerFetch(env, pathOrRequest, init) {
|
|
6074
|
+
const transport = resolvePlatformTransport(env);
|
|
6075
|
+
if (typeof pathOrRequest === "string") {
|
|
6076
|
+
const url2 = buildTargetUrl(transport, pathOrRequest, "platform-worker");
|
|
6077
|
+
if (transport.kind === "binding") {
|
|
6078
|
+
return transport.fetcher.fetch(url2, init);
|
|
5999
6079
|
}
|
|
6000
|
-
return
|
|
6080
|
+
return fetch(url2, init);
|
|
6001
6081
|
}
|
|
6002
|
-
|
|
6003
|
-
|
|
6082
|
+
const req = pathOrRequest;
|
|
6083
|
+
const url = buildTargetUrl(transport, req.url, "platform-worker");
|
|
6084
|
+
const rewritten = new Request(url, req);
|
|
6085
|
+
if (transport.kind === "binding") {
|
|
6086
|
+
return transport.fetcher.fetch(rewritten);
|
|
6004
6087
|
}
|
|
6005
|
-
|
|
6088
|
+
return fetch(rewritten);
|
|
6006
6089
|
}
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
for (const pattern of patterns) {
|
|
6013
|
-
if (pattern === azp) return true;
|
|
6014
|
-
if (pattern.includes("*")) {
|
|
6015
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
6016
|
-
const regexPattern = escaped.replace(/\*/g, "[^/]*");
|
|
6017
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
6018
|
-
if (regex.test(azp)) return true;
|
|
6019
|
-
if (pattern.includes("*.")) {
|
|
6020
|
-
const rootPattern = pattern.replace("*.", "");
|
|
6021
|
-
if (rootPattern === azp) return true;
|
|
6022
|
-
}
|
|
6023
|
-
}
|
|
6090
|
+
function authWorkerFetch(env, path, init) {
|
|
6091
|
+
if (!env.AUTH_WORKER_URL) {
|
|
6092
|
+
throw new Error(
|
|
6093
|
+
"authWorkerFetch: env.AUTH_WORKER_URL is not set. `deepspace dev` should write this into .dev.vars; for production, set it as a wrangler var or secret."
|
|
6094
|
+
);
|
|
6024
6095
|
}
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
if (
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6096
|
+
const baseUrl = env.AUTH_WORKER_URL.replace(/\/$/, "");
|
|
6097
|
+
let pathAndSearch;
|
|
6098
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
6099
|
+
const u = new URL(path);
|
|
6100
|
+
pathAndSearch = u.pathname + u.search;
|
|
6101
|
+
} else if (path.startsWith("/")) {
|
|
6102
|
+
pathAndSearch = path;
|
|
6103
|
+
} else {
|
|
6104
|
+
pathAndSearch = "/" + path;
|
|
6105
|
+
}
|
|
6106
|
+
return fetch(`${baseUrl}${pathAndSearch}`, init);
|
|
6035
6107
|
}
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6108
|
+
|
|
6109
|
+
// src/server/utils/cron.ts
|
|
6110
|
+
function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
6111
|
+
const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
|
|
6112
|
+
const room = env.RECORD_ROOMS.get(roomIdObj);
|
|
6113
|
+
async function executeTool3(tool, params) {
|
|
6114
|
+
const response = await room.fetch(new Request("https://internal/api/tools/execute", {
|
|
6115
|
+
method: "POST",
|
|
6116
|
+
headers: {
|
|
6117
|
+
"Content-Type": "application/json",
|
|
6118
|
+
"X-User-Id": ownerUserId,
|
|
6119
|
+
"X-App-Action": "true"
|
|
6120
|
+
},
|
|
6121
|
+
body: JSON.stringify({ tool, params })
|
|
6122
|
+
}));
|
|
6123
|
+
const result = await response.json();
|
|
6124
|
+
if (!result.success) {
|
|
6125
|
+
throw new Error(`RecordRoom tool ${tool} failed: ${result.error || "Unknown error"}`);
|
|
6126
|
+
}
|
|
6127
|
+
return result.data;
|
|
6039
6128
|
}
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
(
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
}
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6129
|
+
const records = {
|
|
6130
|
+
async query(collection, opts) {
|
|
6131
|
+
const params = { collection };
|
|
6132
|
+
if (opts?.where) params.where = opts.where;
|
|
6133
|
+
if (opts?.limit) params.limit = opts.limit;
|
|
6134
|
+
const data = await executeTool3("records.query", params);
|
|
6135
|
+
return data.records || [];
|
|
6136
|
+
},
|
|
6137
|
+
async create(collection, data) {
|
|
6138
|
+
return executeTool3("records.create", { collection, data });
|
|
6139
|
+
},
|
|
6140
|
+
async update(collection, recordId, data) {
|
|
6141
|
+
return executeTool3("records.update", { collection, recordId, data });
|
|
6142
|
+
},
|
|
6143
|
+
async delete(collection, recordId) {
|
|
6144
|
+
return executeTool3("records.delete", { collection, recordId });
|
|
6054
6145
|
}
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
if (!
|
|
6146
|
+
};
|
|
6147
|
+
const integrations = {
|
|
6148
|
+
async call(endpoint, params = {}) {
|
|
6149
|
+
if (!env.APP_OWNER_JWT) {
|
|
6059
6150
|
throw new Error(
|
|
6060
|
-
|
|
6151
|
+
"integrations.call requires env.APP_OWNER_JWT. Deployed apps receive this automatically; in dev, `deepspace dev` writes it into .dev.vars."
|
|
6061
6152
|
);
|
|
6062
6153
|
}
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6154
|
+
const response = await apiWorkerFetch(env, `/api/integrations/${endpoint}`, {
|
|
6155
|
+
method: "POST",
|
|
6156
|
+
headers: {
|
|
6157
|
+
"Content-Type": "application/json",
|
|
6158
|
+
Authorization: `Bearer ${env.APP_OWNER_JWT}`
|
|
6159
|
+
},
|
|
6160
|
+
body: JSON.stringify(params)
|
|
6161
|
+
});
|
|
6162
|
+
const text = await response.text();
|
|
6163
|
+
let result = {};
|
|
6164
|
+
try {
|
|
6165
|
+
result = text ? JSON.parse(text) : {};
|
|
6166
|
+
} catch {
|
|
6068
6167
|
}
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
}
|
|
6077
|
-
}
|
|
6078
|
-
|
|
6079
|
-
// src/server/auth/internalAuth.ts
|
|
6080
|
-
var DEFAULT_MAX_SKEW_MS = 5 * 6e4;
|
|
6081
|
-
var nodeCryptoPromise = null;
|
|
6082
|
-
async function loadNodeCrypto() {
|
|
6083
|
-
if (!nodeCryptoPromise) {
|
|
6084
|
-
nodeCryptoPromise = import("crypto").then((module) => module).catch(() => null);
|
|
6085
|
-
}
|
|
6086
|
-
return nodeCryptoPromise;
|
|
6087
|
-
}
|
|
6088
|
-
function hasSubtleCrypto() {
|
|
6089
|
-
return typeof globalThis.crypto?.subtle?.importKey === "function";
|
|
6090
|
-
}
|
|
6091
|
-
async function computeHmacHex(secret, payload) {
|
|
6092
|
-
if (hasSubtleCrypto()) {
|
|
6093
|
-
const encoder = new TextEncoder();
|
|
6094
|
-
const key = await crypto.subtle.importKey(
|
|
6095
|
-
"raw",
|
|
6096
|
-
encoder.encode(secret),
|
|
6097
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
6098
|
-
false,
|
|
6099
|
-
["sign"]
|
|
6100
|
-
);
|
|
6101
|
-
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
|
|
6102
|
-
return bufferToHex(signature);
|
|
6103
|
-
}
|
|
6104
|
-
const nodeCrypto = await loadNodeCrypto();
|
|
6105
|
-
if (!nodeCrypto) {
|
|
6106
|
-
throw new Error("Crypto implementation unavailable for HMAC computation");
|
|
6107
|
-
}
|
|
6108
|
-
return nodeCrypto.createHmac("sha256", secret).update(payload).digest("hex");
|
|
6109
|
-
}
|
|
6110
|
-
async function timingSafeEqualHex(a, b) {
|
|
6111
|
-
if (a.length !== b.length) {
|
|
6112
|
-
return false;
|
|
6113
|
-
}
|
|
6114
|
-
const nodeCrypto = await loadNodeCrypto();
|
|
6115
|
-
if (nodeCrypto?.timingSafeEqual) {
|
|
6116
|
-
const aBuf = Buffer.from(a, "hex");
|
|
6117
|
-
const bBuf = Buffer.from(b, "hex");
|
|
6118
|
-
try {
|
|
6119
|
-
return nodeCrypto.timingSafeEqual(aBuf, bBuf);
|
|
6120
|
-
} catch {
|
|
6121
|
-
return false;
|
|
6122
|
-
}
|
|
6123
|
-
}
|
|
6124
|
-
let diff = 0;
|
|
6125
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
6126
|
-
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
6127
|
-
}
|
|
6128
|
-
return diff === 0;
|
|
6129
|
-
}
|
|
6130
|
-
async function signInternalPayload({
|
|
6131
|
-
secret,
|
|
6132
|
-
payload,
|
|
6133
|
-
timestamp
|
|
6134
|
-
}) {
|
|
6135
|
-
const ts = timestamp ?? nowMs().toString();
|
|
6136
|
-
const signature = await computeHmacHex(secret, `${ts}.${payload}`);
|
|
6137
|
-
return { timestamp: ts, signature };
|
|
6138
|
-
}
|
|
6139
|
-
async function verifyInternalSignature({
|
|
6140
|
-
secret,
|
|
6141
|
-
timestamp,
|
|
6142
|
-
signature,
|
|
6143
|
-
payload,
|
|
6144
|
-
maxSkewMs = DEFAULT_MAX_SKEW_MS
|
|
6145
|
-
}) {
|
|
6146
|
-
if (!secret || !timestamp || !signature) {
|
|
6147
|
-
return false;
|
|
6148
|
-
}
|
|
6149
|
-
const tsNumber = Number(timestamp);
|
|
6150
|
-
if (!Number.isFinite(tsNumber)) {
|
|
6151
|
-
return false;
|
|
6152
|
-
}
|
|
6153
|
-
const skew = Math.abs(nowMs() - tsNumber);
|
|
6154
|
-
if (skew > maxSkewMs) {
|
|
6155
|
-
return false;
|
|
6156
|
-
}
|
|
6157
|
-
const expected = await computeHmacHex(secret, `${timestamp}.${payload}`);
|
|
6158
|
-
return timingSafeEqualHex(signature.toLowerCase(), expected.toLowerCase());
|
|
6159
|
-
}
|
|
6160
|
-
function buildInternalPayload(body) {
|
|
6161
|
-
if (body === void 0) return "{}";
|
|
6162
|
-
if (typeof body === "string") return body;
|
|
6163
|
-
try {
|
|
6164
|
-
return JSON.stringify(body);
|
|
6165
|
-
} catch {
|
|
6166
|
-
return "{}";
|
|
6167
|
-
}
|
|
6168
|
-
}
|
|
6169
|
-
|
|
6170
|
-
// src/server/auth/betterAuth.ts
|
|
6171
|
-
import { betterAuth } from "better-auth";
|
|
6172
|
-
import { organization, twoFactor } from "better-auth/plugins";
|
|
6173
|
-
function createDeepSpaceAuth(config) {
|
|
6174
|
-
const socialProviders = {};
|
|
6175
|
-
if (config.google) {
|
|
6176
|
-
socialProviders.google = config.google;
|
|
6177
|
-
}
|
|
6178
|
-
if (config.github) {
|
|
6179
|
-
socialProviders.github = config.github;
|
|
6180
|
-
}
|
|
6181
|
-
return betterAuth({
|
|
6182
|
-
database: config.database,
|
|
6183
|
-
baseURL: config.baseURL,
|
|
6184
|
-
secret: config.secret,
|
|
6185
|
-
emailAndPassword: {
|
|
6186
|
-
enabled: config.emailAndPassword ?? true
|
|
6187
|
-
},
|
|
6188
|
-
socialProviders,
|
|
6189
|
-
trustedOrigins: config.trustedOrigins ?? [
|
|
6190
|
-
"https://deep.space",
|
|
6191
|
-
"https://*.deep.space",
|
|
6192
|
-
"https://*.app.space",
|
|
6193
|
-
"http://localhost:*"
|
|
6194
|
-
],
|
|
6195
|
-
plugins: [organization(), twoFactor()]
|
|
6196
|
-
});
|
|
6197
|
-
}
|
|
6198
|
-
|
|
6199
|
-
// src/server/utils/cron.ts
|
|
6200
|
-
var DEFAULT_API_BASE_URL = "https://api-worker.deep.space";
|
|
6201
|
-
function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
6202
|
-
const hmacSecret = env.INTERNAL_STORAGE_HMAC_SECRET;
|
|
6203
|
-
const apiBase = env.API_BASE_URL || DEFAULT_API_BASE_URL;
|
|
6204
|
-
const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
|
|
6205
|
-
const room = env.RECORD_ROOMS.get(roomIdObj);
|
|
6206
|
-
async function executeTool3(tool, params) {
|
|
6207
|
-
const response = await room.fetch(new Request("https://internal/api/tools/execute", {
|
|
6208
|
-
method: "POST",
|
|
6209
|
-
headers: {
|
|
6210
|
-
"Content-Type": "application/json",
|
|
6211
|
-
"X-User-Id": ownerUserId,
|
|
6212
|
-
"X-App-Action": "true"
|
|
6213
|
-
},
|
|
6214
|
-
body: JSON.stringify({ tool, params })
|
|
6215
|
-
}));
|
|
6216
|
-
const result = await response.json();
|
|
6217
|
-
if (!result.success) {
|
|
6218
|
-
throw new Error(`RecordRoom tool ${tool} failed: ${result.error || "Unknown error"}`);
|
|
6219
|
-
}
|
|
6220
|
-
return result.data;
|
|
6221
|
-
}
|
|
6222
|
-
const records = {
|
|
6223
|
-
async query(collection, opts) {
|
|
6224
|
-
const params = { collection };
|
|
6225
|
-
if (opts?.where) params.where = opts.where;
|
|
6226
|
-
if (opts?.limit) params.limit = opts.limit;
|
|
6227
|
-
const data = await executeTool3("records.query", params);
|
|
6228
|
-
return data.records || [];
|
|
6229
|
-
},
|
|
6230
|
-
async create(collection, data) {
|
|
6231
|
-
return executeTool3("records.create", { collection, data });
|
|
6232
|
-
},
|
|
6233
|
-
async update(collection, recordId, data) {
|
|
6234
|
-
return executeTool3("records.update", { collection, recordId, data });
|
|
6235
|
-
},
|
|
6236
|
-
async delete(collection, recordId) {
|
|
6237
|
-
return executeTool3("records.delete", { collection, recordId });
|
|
6238
|
-
}
|
|
6239
|
-
};
|
|
6240
|
-
const integrations = {
|
|
6241
|
-
async call(endpoint, params) {
|
|
6242
|
-
if (!hmacSecret) {
|
|
6243
|
-
throw new Error("INTERNAL_STORAGE_HMAC_SECRET not configured \u2014 cannot call integrations");
|
|
6244
|
-
}
|
|
6245
|
-
const payload = { userId: ownerUserId, ...params };
|
|
6246
|
-
const payloadStr = buildInternalPayload(payload);
|
|
6247
|
-
const { timestamp, signature } = await signInternalPayload({
|
|
6248
|
-
secret: hmacSecret,
|
|
6249
|
-
payload: payloadStr
|
|
6250
|
-
});
|
|
6251
|
-
const response = await fetch(`${apiBase}/api/internal/integrations/${endpoint}`, {
|
|
6252
|
-
method: "POST",
|
|
6253
|
-
headers: {
|
|
6254
|
-
"Content-Type": "application/json",
|
|
6255
|
-
"x-internal-timestamp": timestamp,
|
|
6256
|
-
"x-internal-signature": signature
|
|
6257
|
-
},
|
|
6258
|
-
body: payloadStr
|
|
6259
|
-
});
|
|
6260
|
-
if (!response.ok) {
|
|
6261
|
-
const errText = await response.text();
|
|
6262
|
-
throw new Error(`Integration call ${endpoint} failed (${response.status}): ${errText}`);
|
|
6263
|
-
}
|
|
6264
|
-
return response.json();
|
|
6265
|
-
}
|
|
6266
|
-
};
|
|
6267
|
-
return { records, integrations, ownerUserId };
|
|
6168
|
+
if (!response.ok || !result.success) {
|
|
6169
|
+
const detail = result.message || result.error || text || `HTTP ${response.status}`;
|
|
6170
|
+
throw new Error(`Integration call ${endpoint} failed: ${detail}`);
|
|
6171
|
+
}
|
|
6172
|
+
return result.data;
|
|
6173
|
+
}
|
|
6174
|
+
};
|
|
6175
|
+
return { records, integrations, ownerUserId };
|
|
6268
6176
|
}
|
|
6269
6177
|
|
|
6270
6178
|
// src/server/utils/ai.ts
|
|
6271
6179
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
6272
6180
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
6273
6181
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
6274
|
-
|
|
6275
|
-
// src/server/utils/proxies.ts
|
|
6276
|
-
function resolveApiTransport(env) {
|
|
6277
|
-
if (env.API_WORKER) return { kind: "binding", fetcher: env.API_WORKER };
|
|
6278
|
-
if (env.API_WORKER_URL) {
|
|
6279
|
-
return { kind: "url", baseUrl: env.API_WORKER_URL.replace(/\/$/, "") };
|
|
6280
|
-
}
|
|
6281
|
-
throw new Error(
|
|
6282
|
-
"apiWorkerFetch: neither env.API_WORKER nor env.API_WORKER_URL is set. Add a [[services]] binding in wrangler.toml for production, or let `deepspace dev` write API_WORKER_URL into .dev.vars for local development."
|
|
6283
|
-
);
|
|
6284
|
-
}
|
|
6285
|
-
function resolvePlatformTransport(env) {
|
|
6286
|
-
if (env.PLATFORM_WORKER) return { kind: "binding", fetcher: env.PLATFORM_WORKER };
|
|
6287
|
-
if (env.PLATFORM_WORKER_URL) {
|
|
6288
|
-
return { kind: "url", baseUrl: env.PLATFORM_WORKER_URL.replace(/\/$/, "") };
|
|
6289
|
-
}
|
|
6290
|
-
throw new Error(
|
|
6291
|
-
"platformWorkerFetch: neither env.PLATFORM_WORKER nor env.PLATFORM_WORKER_URL is set. Add a [[services]] binding in wrangler.toml for production, or let `deepspace dev` write PLATFORM_WORKER_URL into .dev.vars for local development."
|
|
6292
|
-
);
|
|
6293
|
-
}
|
|
6294
|
-
function buildTargetUrl(transport, path, internalHost) {
|
|
6295
|
-
let pathAndSearch;
|
|
6296
|
-
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
6297
|
-
const u = new URL(path);
|
|
6298
|
-
pathAndSearch = u.pathname + u.search;
|
|
6299
|
-
} else if (path.startsWith("/")) {
|
|
6300
|
-
pathAndSearch = path;
|
|
6301
|
-
} else {
|
|
6302
|
-
pathAndSearch = "/" + path;
|
|
6303
|
-
}
|
|
6304
|
-
if (transport.kind === "binding") {
|
|
6305
|
-
return `https://${internalHost}${pathAndSearch}`;
|
|
6306
|
-
}
|
|
6307
|
-
return `${transport.baseUrl}${pathAndSearch}`;
|
|
6308
|
-
}
|
|
6309
|
-
function apiWorkerFetch(env, path, init) {
|
|
6310
|
-
const transport = resolveApiTransport(env);
|
|
6311
|
-
const url = buildTargetUrl(transport, path, "api-worker");
|
|
6312
|
-
if (transport.kind === "binding") {
|
|
6313
|
-
return transport.fetcher.fetch(url, init);
|
|
6314
|
-
}
|
|
6315
|
-
return fetch(url, init);
|
|
6316
|
-
}
|
|
6317
|
-
function platformWorkerFetch(env, pathOrRequest, init) {
|
|
6318
|
-
const transport = resolvePlatformTransport(env);
|
|
6319
|
-
if (typeof pathOrRequest === "string") {
|
|
6320
|
-
const url2 = buildTargetUrl(transport, pathOrRequest, "platform-worker");
|
|
6321
|
-
if (transport.kind === "binding") {
|
|
6322
|
-
return transport.fetcher.fetch(url2, init);
|
|
6323
|
-
}
|
|
6324
|
-
return fetch(url2, init);
|
|
6325
|
-
}
|
|
6326
|
-
const req = pathOrRequest;
|
|
6327
|
-
const url = buildTargetUrl(transport, req.url, "platform-worker");
|
|
6328
|
-
const rewritten = new Request(url, req);
|
|
6329
|
-
if (transport.kind === "binding") {
|
|
6330
|
-
return transport.fetcher.fetch(rewritten);
|
|
6331
|
-
}
|
|
6332
|
-
return fetch(rewritten);
|
|
6333
|
-
}
|
|
6334
|
-
function authWorkerFetch(env, path, init) {
|
|
6335
|
-
if (!env.AUTH_WORKER_URL) {
|
|
6336
|
-
throw new Error(
|
|
6337
|
-
"authWorkerFetch: env.AUTH_WORKER_URL is not set. `deepspace dev` should write this into .dev.vars; for production, set it as a wrangler var or secret."
|
|
6338
|
-
);
|
|
6339
|
-
}
|
|
6340
|
-
const baseUrl = env.AUTH_WORKER_URL.replace(/\/$/, "");
|
|
6341
|
-
let pathAndSearch;
|
|
6342
|
-
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
6343
|
-
const u = new URL(path);
|
|
6344
|
-
pathAndSearch = u.pathname + u.search;
|
|
6345
|
-
} else if (path.startsWith("/")) {
|
|
6346
|
-
pathAndSearch = path;
|
|
6347
|
-
} else {
|
|
6348
|
-
pathAndSearch = "/" + path;
|
|
6349
|
-
}
|
|
6350
|
-
return fetch(`${baseUrl}${pathAndSearch}`, init);
|
|
6351
|
-
}
|
|
6352
|
-
|
|
6353
|
-
// src/server/utils/ai.ts
|
|
6354
6182
|
var ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 4096;
|
|
6355
6183
|
function withDefaultAnthropicMaxTokens(body) {
|
|
6356
6184
|
if (typeof body !== "string") return body;
|
|
@@ -6944,6 +6772,215 @@ async function runMigrations(db, migrations) {
|
|
|
6944
6772
|
function splitStatements(sql) {
|
|
6945
6773
|
return sql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6946
6774
|
}
|
|
6775
|
+
|
|
6776
|
+
// src/server/auth/jwtVerifier.ts
|
|
6777
|
+
import { importSPKI, jwtVerify } from "jose";
|
|
6778
|
+
|
|
6779
|
+
// src/server/auth/utils.ts
|
|
6780
|
+
var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
|
|
6781
|
+
function bufferToHex(bytes) {
|
|
6782
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
6783
|
+
let hex = "";
|
|
6784
|
+
for (const byte of view) {
|
|
6785
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
6786
|
+
}
|
|
6787
|
+
return hex;
|
|
6788
|
+
}
|
|
6789
|
+
function normalizeArray(value) {
|
|
6790
|
+
if (value == null) return void 0;
|
|
6791
|
+
if (Array.isArray(value)) {
|
|
6792
|
+
return value.length ? value : void 0;
|
|
6793
|
+
}
|
|
6794
|
+
return [value];
|
|
6795
|
+
}
|
|
6796
|
+
function decodeJwtPayload(token) {
|
|
6797
|
+
if (!token) return void 0;
|
|
6798
|
+
const parts = token.split(".");
|
|
6799
|
+
if (parts.length < 2) return void 0;
|
|
6800
|
+
const payloadSegment = parts[1];
|
|
6801
|
+
if (!payloadSegment) return void 0;
|
|
6802
|
+
if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
|
|
6803
|
+
try {
|
|
6804
|
+
const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
|
|
6805
|
+
const bytes = base64ToUint8Array(normalized);
|
|
6806
|
+
const json = new TextDecoder().decode(bytes);
|
|
6807
|
+
const payload = JSON.parse(json);
|
|
6808
|
+
return {
|
|
6809
|
+
iss: payload.iss ?? null,
|
|
6810
|
+
aud: payload.aud ?? null,
|
|
6811
|
+
azp: payload?.azp ?? null,
|
|
6812
|
+
exp: payload.exp ?? null,
|
|
6813
|
+
iat: payload.iat ?? null
|
|
6814
|
+
};
|
|
6815
|
+
} catch {
|
|
6816
|
+
return void 0;
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
function base64ToUint8Array(value) {
|
|
6820
|
+
if (typeof atob === "function") {
|
|
6821
|
+
const binary = atob(value);
|
|
6822
|
+
const out = new Uint8Array(binary.length);
|
|
6823
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
6824
|
+
out[i] = binary.charCodeAt(i);
|
|
6825
|
+
}
|
|
6826
|
+
return out;
|
|
6827
|
+
}
|
|
6828
|
+
if (typeof Buffer !== "undefined") {
|
|
6829
|
+
return Uint8Array.from(Buffer.from(value, "base64"));
|
|
6830
|
+
}
|
|
6831
|
+
throw new Error("No base64 decoder available in current environment");
|
|
6832
|
+
}
|
|
6833
|
+
|
|
6834
|
+
// src/server/auth/jwtVerifier.ts
|
|
6835
|
+
var DEFAULT_CLOCK_SKEW_MS = 5e3;
|
|
6836
|
+
function matchesAuthorizedParty(azp, patterns) {
|
|
6837
|
+
if (!azp) return true;
|
|
6838
|
+
for (const pattern of patterns) {
|
|
6839
|
+
if (pattern === azp) return true;
|
|
6840
|
+
if (pattern.includes("*")) {
|
|
6841
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
6842
|
+
const regexPattern = escaped.replace(/\*/g, "[^/]*");
|
|
6843
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
6844
|
+
if (regex.test(azp)) return true;
|
|
6845
|
+
if (pattern.includes("*.")) {
|
|
6846
|
+
const rootPattern = pattern.replace("*.", "");
|
|
6847
|
+
if (rootPattern === azp) return true;
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
return false;
|
|
6852
|
+
}
|
|
6853
|
+
var keyCache = /* @__PURE__ */ new Map();
|
|
6854
|
+
async function getPublicKey(pem) {
|
|
6855
|
+
const cached = keyCache.get(pem);
|
|
6856
|
+
if (cached) return cached;
|
|
6857
|
+
const normalized = pem.replace(/\\n/g, "\n");
|
|
6858
|
+
const key = await importSPKI(normalized, "ES256");
|
|
6859
|
+
keyCache.set(pem, key);
|
|
6860
|
+
return key;
|
|
6861
|
+
}
|
|
6862
|
+
async function verifyJwt(config, token) {
|
|
6863
|
+
if (!token) {
|
|
6864
|
+
return { result: null };
|
|
6865
|
+
}
|
|
6866
|
+
try {
|
|
6867
|
+
const publicKey = await getPublicKey(config.publicKey);
|
|
6868
|
+
const audience = normalizeArray(config.audience);
|
|
6869
|
+
const clockTolerance = Math.floor(
|
|
6870
|
+
(config.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS) / 1e3
|
|
6871
|
+
);
|
|
6872
|
+
const { payload } = await jwtVerify(token, publicKey, {
|
|
6873
|
+
issuer: config.issuer,
|
|
6874
|
+
audience: audience && audience.length === 1 ? audience[0] : audience,
|
|
6875
|
+
clockTolerance
|
|
6876
|
+
});
|
|
6877
|
+
const claims = payload;
|
|
6878
|
+
if (!claims.sub) {
|
|
6879
|
+
throw new Error("JWT verification succeeded but subject claim is missing");
|
|
6880
|
+
}
|
|
6881
|
+
const authorizedParties = normalizeArray(config.authorizedParties);
|
|
6882
|
+
if (authorizedParties) {
|
|
6883
|
+
const azp = claims.azp;
|
|
6884
|
+
if (!matchesAuthorizedParty(azp, authorizedParties)) {
|
|
6885
|
+
throw new Error(
|
|
6886
|
+
`Authorized party '${azp}' does not match any allowed pattern`
|
|
6887
|
+
);
|
|
6888
|
+
}
|
|
6889
|
+
}
|
|
6890
|
+
return {
|
|
6891
|
+
result: {
|
|
6892
|
+
userId: claims.sub,
|
|
6893
|
+
claims
|
|
6894
|
+
}
|
|
6895
|
+
};
|
|
6896
|
+
} catch (error) {
|
|
6897
|
+
return {
|
|
6898
|
+
result: null,
|
|
6899
|
+
error,
|
|
6900
|
+
debug: decodeJwtPayload(token)
|
|
6901
|
+
};
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6904
|
+
|
|
6905
|
+
// src/server/auth/internalAuth.ts
|
|
6906
|
+
var nodeCryptoPromise = null;
|
|
6907
|
+
async function loadNodeCrypto() {
|
|
6908
|
+
if (!nodeCryptoPromise) {
|
|
6909
|
+
nodeCryptoPromise = import("crypto").then((module) => module).catch(() => null);
|
|
6910
|
+
}
|
|
6911
|
+
return nodeCryptoPromise;
|
|
6912
|
+
}
|
|
6913
|
+
function hasSubtleCrypto() {
|
|
6914
|
+
return typeof globalThis.crypto?.subtle?.importKey === "function";
|
|
6915
|
+
}
|
|
6916
|
+
async function computeHmacHex(secret, payload) {
|
|
6917
|
+
if (hasSubtleCrypto()) {
|
|
6918
|
+
const encoder = new TextEncoder();
|
|
6919
|
+
const key = await crypto.subtle.importKey(
|
|
6920
|
+
"raw",
|
|
6921
|
+
encoder.encode(secret),
|
|
6922
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
6923
|
+
false,
|
|
6924
|
+
["sign"]
|
|
6925
|
+
);
|
|
6926
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
|
|
6927
|
+
return bufferToHex(signature);
|
|
6928
|
+
}
|
|
6929
|
+
const nodeCrypto = await loadNodeCrypto();
|
|
6930
|
+
if (!nodeCrypto) {
|
|
6931
|
+
throw new Error("Crypto implementation unavailable for HMAC computation");
|
|
6932
|
+
}
|
|
6933
|
+
return nodeCrypto.createHmac("sha256", secret).update(payload).digest("hex");
|
|
6934
|
+
}
|
|
6935
|
+
async function timingSafeEqualHex(a, b) {
|
|
6936
|
+
if (a.length !== b.length) {
|
|
6937
|
+
return false;
|
|
6938
|
+
}
|
|
6939
|
+
const nodeCrypto = await loadNodeCrypto();
|
|
6940
|
+
if (nodeCrypto?.timingSafeEqual) {
|
|
6941
|
+
const aBuf = Buffer.from(a, "hex");
|
|
6942
|
+
const bBuf = Buffer.from(b, "hex");
|
|
6943
|
+
try {
|
|
6944
|
+
return nodeCrypto.timingSafeEqual(aBuf, bBuf);
|
|
6945
|
+
} catch {
|
|
6946
|
+
return false;
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
let diff = 0;
|
|
6950
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
6951
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
6952
|
+
}
|
|
6953
|
+
return diff === 0;
|
|
6954
|
+
}
|
|
6955
|
+
|
|
6956
|
+
// src/server/auth/betterAuth.ts
|
|
6957
|
+
import { betterAuth } from "better-auth";
|
|
6958
|
+
import { organization, twoFactor } from "better-auth/plugins";
|
|
6959
|
+
function createDeepSpaceAuth(config) {
|
|
6960
|
+
const socialProviders = {};
|
|
6961
|
+
if (config.google) {
|
|
6962
|
+
socialProviders.google = config.google;
|
|
6963
|
+
}
|
|
6964
|
+
if (config.github) {
|
|
6965
|
+
socialProviders.github = config.github;
|
|
6966
|
+
}
|
|
6967
|
+
return betterAuth({
|
|
6968
|
+
database: config.database,
|
|
6969
|
+
baseURL: config.baseURL,
|
|
6970
|
+
secret: config.secret,
|
|
6971
|
+
emailAndPassword: {
|
|
6972
|
+
enabled: config.emailAndPassword ?? true
|
|
6973
|
+
},
|
|
6974
|
+
socialProviders,
|
|
6975
|
+
trustedOrigins: config.trustedOrigins ?? [
|
|
6976
|
+
"https://deep.space",
|
|
6977
|
+
"https://*.deep.space",
|
|
6978
|
+
"https://*.app.space",
|
|
6979
|
+
"http://localhost:*"
|
|
6980
|
+
],
|
|
6981
|
+
plugins: [organization(), twoFactor()]
|
|
6982
|
+
});
|
|
6983
|
+
}
|
|
6947
6984
|
export {
|
|
6948
6985
|
AI_CHATS_SCHEMA,
|
|
6949
6986
|
AI_MESSAGES_SCHEMA,
|
|
@@ -6964,7 +7001,6 @@ export {
|
|
|
6964
7001
|
CronRoom,
|
|
6965
7002
|
DEFAULT_CONTEXT_CONFIG,
|
|
6966
7003
|
DEFAULT_DO_MANIFEST,
|
|
6967
|
-
DEFAULT_MAX_SKEW_MS,
|
|
6968
7004
|
DIRECTORY_SCHEMAS,
|
|
6969
7005
|
GLOBAL_DO_TYPES,
|
|
6970
7006
|
GLOBAL_DO_TYPE_NAMES,
|
|
@@ -7002,7 +7038,6 @@ export {
|
|
|
7002
7038
|
authWorkerFetch,
|
|
7003
7039
|
bindingManifestFromOutputConfig,
|
|
7004
7040
|
buildCronContext,
|
|
7005
|
-
buildInternalPayload,
|
|
7006
7041
|
buildTableSelect,
|
|
7007
7042
|
buildUiParts,
|
|
7008
7043
|
canCreate,
|
|
@@ -7061,7 +7096,6 @@ export {
|
|
|
7061
7096
|
rowToData,
|
|
7062
7097
|
runMigrations,
|
|
7063
7098
|
serverBuild,
|
|
7064
|
-
signInternalPayload,
|
|
7065
7099
|
timingSafeEqualHex,
|
|
7066
7100
|
toUint8Array,
|
|
7067
7101
|
totalChars,
|
|
@@ -7072,7 +7106,6 @@ export {
|
|
|
7072
7106
|
validateAppName,
|
|
7073
7107
|
validateBindingManifest,
|
|
7074
7108
|
validateDoManifest,
|
|
7075
|
-
verifyInternalSignature,
|
|
7076
7109
|
verifyJwt,
|
|
7077
7110
|
workspaceAccountsSchema,
|
|
7078
7111
|
workspaceContentSharesSchema,
|