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/cli.js +258 -120
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +39 -15
- package/dist/index.js +76 -45
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +72 -73
- package/dist/server.js +368 -337
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +72 -73
- package/dist/worker.js +422 -391
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
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("/");
|
|
@@ -5939,418 +6021,162 @@ async function handleDownload(bucket, key) {
|
|
|
5939
6021
|
headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
5940
6022
|
headers.set("Access-Control-Allow-Origin", "*");
|
|
5941
6023
|
if (object.customMetadata?.originalName) {
|
|
5942
|
-
headers.set("Content-Disposition",
|
|
6024
|
+
headers.set("Content-Disposition", contentDisposition(object.customMetadata.originalName));
|
|
5943
6025
|
}
|
|
5944
6026
|
return new Response(object.body, { headers });
|
|
5945
6027
|
}
|
|
5946
6028
|
|
|
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");
|
|
6029
|
+
// src/server/utils/proxies.ts
|
|
6030
|
+
function resolveApiTransport(env) {
|
|
6031
|
+
if (env.API_WORKER) return { kind: "binding", fetcher: env.API_WORKER };
|
|
6032
|
+
if (env.API_WORKER_URL) {
|
|
6033
|
+
return { kind: "url", baseUrl: env.API_WORKER_URL.replace(/\/$/, "") };
|
|
5957
6034
|
}
|
|
5958
|
-
|
|
6035
|
+
throw new Error(
|
|
6036
|
+
"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."
|
|
6037
|
+
);
|
|
5959
6038
|
}
|
|
5960
|
-
function
|
|
5961
|
-
if (
|
|
5962
|
-
if (
|
|
5963
|
-
return
|
|
6039
|
+
function resolvePlatformTransport(env) {
|
|
6040
|
+
if (env.PLATFORM_WORKER) return { kind: "binding", fetcher: env.PLATFORM_WORKER };
|
|
6041
|
+
if (env.PLATFORM_WORKER_URL) {
|
|
6042
|
+
return { kind: "url", baseUrl: env.PLATFORM_WORKER_URL.replace(/\/$/, "") };
|
|
5964
6043
|
}
|
|
5965
|
-
|
|
6044
|
+
throw new Error(
|
|
6045
|
+
"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."
|
|
6046
|
+
);
|
|
5966
6047
|
}
|
|
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;
|
|
6048
|
+
function buildTargetUrl(transport, path, internalHost) {
|
|
6049
|
+
let pathAndSearch;
|
|
6050
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
6051
|
+
const u = new URL(path);
|
|
6052
|
+
pathAndSearch = u.pathname + u.search;
|
|
6053
|
+
} else if (path.startsWith("/")) {
|
|
6054
|
+
pathAndSearch = path;
|
|
6055
|
+
} else {
|
|
6056
|
+
pathAndSearch = "/" + path;
|
|
6057
|
+
}
|
|
6058
|
+
if (transport.kind === "binding") {
|
|
6059
|
+
return `https://${internalHost}${pathAndSearch}`;
|
|
5988
6060
|
}
|
|
6061
|
+
return `${transport.baseUrl}${pathAndSearch}`;
|
|
5989
6062
|
}
|
|
5990
|
-
function
|
|
5991
|
-
|
|
6063
|
+
function apiWorkerFetch(env, path, init) {
|
|
6064
|
+
const transport = resolveApiTransport(env);
|
|
6065
|
+
const url = buildTargetUrl(transport, path, "api-worker");
|
|
6066
|
+
if (transport.kind === "binding") {
|
|
6067
|
+
return transport.fetcher.fetch(url, init);
|
|
6068
|
+
}
|
|
6069
|
+
return fetch(url, init);
|
|
5992
6070
|
}
|
|
5993
|
-
function
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
const
|
|
5997
|
-
|
|
5998
|
-
|
|
6071
|
+
function platformWorkerFetch(env, pathOrRequest, init) {
|
|
6072
|
+
const transport = resolvePlatformTransport(env);
|
|
6073
|
+
if (typeof pathOrRequest === "string") {
|
|
6074
|
+
const url2 = buildTargetUrl(transport, pathOrRequest, "platform-worker");
|
|
6075
|
+
if (transport.kind === "binding") {
|
|
6076
|
+
return transport.fetcher.fetch(url2, init);
|
|
5999
6077
|
}
|
|
6000
|
-
return
|
|
6078
|
+
return fetch(url2, init);
|
|
6001
6079
|
}
|
|
6002
|
-
|
|
6003
|
-
|
|
6080
|
+
const req = pathOrRequest;
|
|
6081
|
+
const url = buildTargetUrl(transport, req.url, "platform-worker");
|
|
6082
|
+
const rewritten = new Request(url, req);
|
|
6083
|
+
if (transport.kind === "binding") {
|
|
6084
|
+
return transport.fetcher.fetch(rewritten);
|
|
6004
6085
|
}
|
|
6005
|
-
|
|
6086
|
+
return fetch(rewritten);
|
|
6006
6087
|
}
|
|
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
|
-
}
|
|
6088
|
+
function authWorkerFetch(env, path, init) {
|
|
6089
|
+
if (!env.AUTH_WORKER_URL) {
|
|
6090
|
+
throw new Error(
|
|
6091
|
+
"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."
|
|
6092
|
+
);
|
|
6024
6093
|
}
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
if (
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6094
|
+
const baseUrl = env.AUTH_WORKER_URL.replace(/\/$/, "");
|
|
6095
|
+
let pathAndSearch;
|
|
6096
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
6097
|
+
const u = new URL(path);
|
|
6098
|
+
pathAndSearch = u.pathname + u.search;
|
|
6099
|
+
} else if (path.startsWith("/")) {
|
|
6100
|
+
pathAndSearch = path;
|
|
6101
|
+
} else {
|
|
6102
|
+
pathAndSearch = "/" + path;
|
|
6103
|
+
}
|
|
6104
|
+
return fetch(`${baseUrl}${pathAndSearch}`, init);
|
|
6035
6105
|
}
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6106
|
+
|
|
6107
|
+
// src/server/utils/cron.ts
|
|
6108
|
+
function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
6109
|
+
const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
|
|
6110
|
+
const room = env.RECORD_ROOMS.get(roomIdObj);
|
|
6111
|
+
async function executeTool3(tool, params) {
|
|
6112
|
+
const response = await room.fetch(new Request("https://internal/api/tools/execute", {
|
|
6113
|
+
method: "POST",
|
|
6114
|
+
headers: {
|
|
6115
|
+
"Content-Type": "application/json",
|
|
6116
|
+
"X-User-Id": ownerUserId,
|
|
6117
|
+
"X-App-Action": "true"
|
|
6118
|
+
},
|
|
6119
|
+
body: JSON.stringify({ tool, params })
|
|
6120
|
+
}));
|
|
6121
|
+
const result = await response.json();
|
|
6122
|
+
if (!result.success) {
|
|
6123
|
+
throw new Error(`RecordRoom tool ${tool} failed: ${result.error || "Unknown error"}`);
|
|
6124
|
+
}
|
|
6125
|
+
return result.data;
|
|
6039
6126
|
}
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
(
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
}
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6127
|
+
const records = {
|
|
6128
|
+
async query(collection, opts) {
|
|
6129
|
+
const params = { collection };
|
|
6130
|
+
if (opts?.where) params.where = opts.where;
|
|
6131
|
+
if (opts?.limit) params.limit = opts.limit;
|
|
6132
|
+
const data = await executeTool3("records.query", params);
|
|
6133
|
+
return data.records || [];
|
|
6134
|
+
},
|
|
6135
|
+
async create(collection, data) {
|
|
6136
|
+
return executeTool3("records.create", { collection, data });
|
|
6137
|
+
},
|
|
6138
|
+
async update(collection, recordId, data) {
|
|
6139
|
+
return executeTool3("records.update", { collection, recordId, data });
|
|
6140
|
+
},
|
|
6141
|
+
async delete(collection, recordId) {
|
|
6142
|
+
return executeTool3("records.delete", { collection, recordId });
|
|
6054
6143
|
}
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
if (!
|
|
6144
|
+
};
|
|
6145
|
+
const integrations = {
|
|
6146
|
+
async call(endpoint, params = {}) {
|
|
6147
|
+
if (!env.APP_OWNER_JWT) {
|
|
6059
6148
|
throw new Error(
|
|
6060
|
-
|
|
6149
|
+
"integrations.call requires env.APP_OWNER_JWT. Deployed apps receive this automatically; in dev, `deepspace dev` writes it into .dev.vars."
|
|
6061
6150
|
);
|
|
6062
6151
|
}
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6152
|
+
const response = await apiWorkerFetch(env, `/api/integrations/${endpoint}`, {
|
|
6153
|
+
method: "POST",
|
|
6154
|
+
headers: {
|
|
6155
|
+
"Content-Type": "application/json",
|
|
6156
|
+
Authorization: `Bearer ${env.APP_OWNER_JWT}`
|
|
6157
|
+
},
|
|
6158
|
+
body: JSON.stringify(params)
|
|
6159
|
+
});
|
|
6160
|
+
const text = await response.text();
|
|
6161
|
+
let result = {};
|
|
6162
|
+
try {
|
|
6163
|
+
result = text ? JSON.parse(text) : {};
|
|
6164
|
+
} catch {
|
|
6068
6165
|
}
|
|
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 };
|
|
6166
|
+
if (!response.ok || !result.success) {
|
|
6167
|
+
const detail = result.message || result.error || text || `HTTP ${response.status}`;
|
|
6168
|
+
throw new Error(`Integration call ${endpoint} failed: ${detail}`);
|
|
6169
|
+
}
|
|
6170
|
+
return result.data;
|
|
6171
|
+
}
|
|
6172
|
+
};
|
|
6173
|
+
return { records, integrations, ownerUserId };
|
|
6268
6174
|
}
|
|
6269
6175
|
|
|
6270
6176
|
// src/server/utils/ai.ts
|
|
6271
6177
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
6272
6178
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
6273
6179
|
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
6180
|
var ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 4096;
|
|
6355
6181
|
function withDefaultAnthropicMaxTokens(body) {
|
|
6356
6182
|
if (typeof body !== "string") return body;
|
|
@@ -6944,6 +6770,215 @@ async function runMigrations(db, migrations) {
|
|
|
6944
6770
|
function splitStatements(sql) {
|
|
6945
6771
|
return sql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6946
6772
|
}
|
|
6773
|
+
|
|
6774
|
+
// src/server/auth/jwtVerifier.ts
|
|
6775
|
+
import { importSPKI, jwtVerify } from "jose";
|
|
6776
|
+
|
|
6777
|
+
// src/server/auth/utils.ts
|
|
6778
|
+
var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
|
|
6779
|
+
function bufferToHex(bytes) {
|
|
6780
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
6781
|
+
let hex = "";
|
|
6782
|
+
for (const byte of view) {
|
|
6783
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
6784
|
+
}
|
|
6785
|
+
return hex;
|
|
6786
|
+
}
|
|
6787
|
+
function normalizeArray(value) {
|
|
6788
|
+
if (value == null) return void 0;
|
|
6789
|
+
if (Array.isArray(value)) {
|
|
6790
|
+
return value.length ? value : void 0;
|
|
6791
|
+
}
|
|
6792
|
+
return [value];
|
|
6793
|
+
}
|
|
6794
|
+
function decodeJwtPayload(token) {
|
|
6795
|
+
if (!token) return void 0;
|
|
6796
|
+
const parts = token.split(".");
|
|
6797
|
+
if (parts.length < 2) return void 0;
|
|
6798
|
+
const payloadSegment = parts[1];
|
|
6799
|
+
if (!payloadSegment) return void 0;
|
|
6800
|
+
if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
|
|
6801
|
+
try {
|
|
6802
|
+
const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
|
|
6803
|
+
const bytes = base64ToUint8Array(normalized);
|
|
6804
|
+
const json = new TextDecoder().decode(bytes);
|
|
6805
|
+
const payload = JSON.parse(json);
|
|
6806
|
+
return {
|
|
6807
|
+
iss: payload.iss ?? null,
|
|
6808
|
+
aud: payload.aud ?? null,
|
|
6809
|
+
azp: payload?.azp ?? null,
|
|
6810
|
+
exp: payload.exp ?? null,
|
|
6811
|
+
iat: payload.iat ?? null
|
|
6812
|
+
};
|
|
6813
|
+
} catch {
|
|
6814
|
+
return void 0;
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6817
|
+
function base64ToUint8Array(value) {
|
|
6818
|
+
if (typeof atob === "function") {
|
|
6819
|
+
const binary = atob(value);
|
|
6820
|
+
const out = new Uint8Array(binary.length);
|
|
6821
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
6822
|
+
out[i] = binary.charCodeAt(i);
|
|
6823
|
+
}
|
|
6824
|
+
return out;
|
|
6825
|
+
}
|
|
6826
|
+
if (typeof Buffer !== "undefined") {
|
|
6827
|
+
return Uint8Array.from(Buffer.from(value, "base64"));
|
|
6828
|
+
}
|
|
6829
|
+
throw new Error("No base64 decoder available in current environment");
|
|
6830
|
+
}
|
|
6831
|
+
|
|
6832
|
+
// src/server/auth/jwtVerifier.ts
|
|
6833
|
+
var DEFAULT_CLOCK_SKEW_MS = 5e3;
|
|
6834
|
+
function matchesAuthorizedParty(azp, patterns) {
|
|
6835
|
+
if (!azp) return true;
|
|
6836
|
+
for (const pattern of patterns) {
|
|
6837
|
+
if (pattern === azp) return true;
|
|
6838
|
+
if (pattern.includes("*")) {
|
|
6839
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
6840
|
+
const regexPattern = escaped.replace(/\*/g, "[^/]*");
|
|
6841
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
6842
|
+
if (regex.test(azp)) return true;
|
|
6843
|
+
if (pattern.includes("*.")) {
|
|
6844
|
+
const rootPattern = pattern.replace("*.", "");
|
|
6845
|
+
if (rootPattern === azp) return true;
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
return false;
|
|
6850
|
+
}
|
|
6851
|
+
var keyCache = /* @__PURE__ */ new Map();
|
|
6852
|
+
async function getPublicKey(pem) {
|
|
6853
|
+
const cached = keyCache.get(pem);
|
|
6854
|
+
if (cached) return cached;
|
|
6855
|
+
const normalized = pem.replace(/\\n/g, "\n");
|
|
6856
|
+
const key = await importSPKI(normalized, "ES256");
|
|
6857
|
+
keyCache.set(pem, key);
|
|
6858
|
+
return key;
|
|
6859
|
+
}
|
|
6860
|
+
async function verifyJwt(config, token) {
|
|
6861
|
+
if (!token) {
|
|
6862
|
+
return { result: null };
|
|
6863
|
+
}
|
|
6864
|
+
try {
|
|
6865
|
+
const publicKey = await getPublicKey(config.publicKey);
|
|
6866
|
+
const audience = normalizeArray(config.audience);
|
|
6867
|
+
const clockTolerance = Math.floor(
|
|
6868
|
+
(config.clockSkewMs ?? DEFAULT_CLOCK_SKEW_MS) / 1e3
|
|
6869
|
+
);
|
|
6870
|
+
const { payload } = await jwtVerify(token, publicKey, {
|
|
6871
|
+
issuer: config.issuer,
|
|
6872
|
+
audience: audience && audience.length === 1 ? audience[0] : audience,
|
|
6873
|
+
clockTolerance
|
|
6874
|
+
});
|
|
6875
|
+
const claims = payload;
|
|
6876
|
+
if (!claims.sub) {
|
|
6877
|
+
throw new Error("JWT verification succeeded but subject claim is missing");
|
|
6878
|
+
}
|
|
6879
|
+
const authorizedParties = normalizeArray(config.authorizedParties);
|
|
6880
|
+
if (authorizedParties) {
|
|
6881
|
+
const azp = claims.azp;
|
|
6882
|
+
if (!matchesAuthorizedParty(azp, authorizedParties)) {
|
|
6883
|
+
throw new Error(
|
|
6884
|
+
`Authorized party '${azp}' does not match any allowed pattern`
|
|
6885
|
+
);
|
|
6886
|
+
}
|
|
6887
|
+
}
|
|
6888
|
+
return {
|
|
6889
|
+
result: {
|
|
6890
|
+
userId: claims.sub,
|
|
6891
|
+
claims
|
|
6892
|
+
}
|
|
6893
|
+
};
|
|
6894
|
+
} catch (error) {
|
|
6895
|
+
return {
|
|
6896
|
+
result: null,
|
|
6897
|
+
error,
|
|
6898
|
+
debug: decodeJwtPayload(token)
|
|
6899
|
+
};
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
|
|
6903
|
+
// src/server/auth/internalAuth.ts
|
|
6904
|
+
var nodeCryptoPromise = null;
|
|
6905
|
+
async function loadNodeCrypto() {
|
|
6906
|
+
if (!nodeCryptoPromise) {
|
|
6907
|
+
nodeCryptoPromise = import("crypto").then((module) => module).catch(() => null);
|
|
6908
|
+
}
|
|
6909
|
+
return nodeCryptoPromise;
|
|
6910
|
+
}
|
|
6911
|
+
function hasSubtleCrypto() {
|
|
6912
|
+
return typeof globalThis.crypto?.subtle?.importKey === "function";
|
|
6913
|
+
}
|
|
6914
|
+
async function computeHmacHex(secret, payload) {
|
|
6915
|
+
if (hasSubtleCrypto()) {
|
|
6916
|
+
const encoder = new TextEncoder();
|
|
6917
|
+
const key = await crypto.subtle.importKey(
|
|
6918
|
+
"raw",
|
|
6919
|
+
encoder.encode(secret),
|
|
6920
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
6921
|
+
false,
|
|
6922
|
+
["sign"]
|
|
6923
|
+
);
|
|
6924
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
|
|
6925
|
+
return bufferToHex(signature);
|
|
6926
|
+
}
|
|
6927
|
+
const nodeCrypto = await loadNodeCrypto();
|
|
6928
|
+
if (!nodeCrypto) {
|
|
6929
|
+
throw new Error("Crypto implementation unavailable for HMAC computation");
|
|
6930
|
+
}
|
|
6931
|
+
return nodeCrypto.createHmac("sha256", secret).update(payload).digest("hex");
|
|
6932
|
+
}
|
|
6933
|
+
async function timingSafeEqualHex(a, b) {
|
|
6934
|
+
if (a.length !== b.length) {
|
|
6935
|
+
return false;
|
|
6936
|
+
}
|
|
6937
|
+
const nodeCrypto = await loadNodeCrypto();
|
|
6938
|
+
if (nodeCrypto?.timingSafeEqual) {
|
|
6939
|
+
const aBuf = Buffer.from(a, "hex");
|
|
6940
|
+
const bBuf = Buffer.from(b, "hex");
|
|
6941
|
+
try {
|
|
6942
|
+
return nodeCrypto.timingSafeEqual(aBuf, bBuf);
|
|
6943
|
+
} catch {
|
|
6944
|
+
return false;
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
let diff = 0;
|
|
6948
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
6949
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
6950
|
+
}
|
|
6951
|
+
return diff === 0;
|
|
6952
|
+
}
|
|
6953
|
+
|
|
6954
|
+
// src/server/auth/betterAuth.ts
|
|
6955
|
+
import { betterAuth } from "better-auth";
|
|
6956
|
+
import { organization, twoFactor } from "better-auth/plugins";
|
|
6957
|
+
function createDeepSpaceAuth(config) {
|
|
6958
|
+
const socialProviders = {};
|
|
6959
|
+
if (config.google) {
|
|
6960
|
+
socialProviders.google = config.google;
|
|
6961
|
+
}
|
|
6962
|
+
if (config.github) {
|
|
6963
|
+
socialProviders.github = config.github;
|
|
6964
|
+
}
|
|
6965
|
+
return betterAuth({
|
|
6966
|
+
database: config.database,
|
|
6967
|
+
baseURL: config.baseURL,
|
|
6968
|
+
secret: config.secret,
|
|
6969
|
+
emailAndPassword: {
|
|
6970
|
+
enabled: config.emailAndPassword ?? true
|
|
6971
|
+
},
|
|
6972
|
+
socialProviders,
|
|
6973
|
+
trustedOrigins: config.trustedOrigins ?? [
|
|
6974
|
+
"https://deep.space",
|
|
6975
|
+
"https://*.deep.space",
|
|
6976
|
+
"https://*.app.space",
|
|
6977
|
+
"http://localhost:*"
|
|
6978
|
+
],
|
|
6979
|
+
plugins: [organization(), twoFactor()]
|
|
6980
|
+
});
|
|
6981
|
+
}
|
|
6947
6982
|
export {
|
|
6948
6983
|
AI_CHATS_SCHEMA,
|
|
6949
6984
|
AI_MESSAGES_SCHEMA,
|
|
@@ -6964,7 +6999,6 @@ export {
|
|
|
6964
6999
|
CronRoom,
|
|
6965
7000
|
DEFAULT_CONTEXT_CONFIG,
|
|
6966
7001
|
DEFAULT_DO_MANIFEST,
|
|
6967
|
-
DEFAULT_MAX_SKEW_MS,
|
|
6968
7002
|
DIRECTORY_SCHEMAS,
|
|
6969
7003
|
GLOBAL_DO_TYPES,
|
|
6970
7004
|
GLOBAL_DO_TYPE_NAMES,
|
|
@@ -7002,7 +7036,6 @@ export {
|
|
|
7002
7036
|
authWorkerFetch,
|
|
7003
7037
|
bindingManifestFromOutputConfig,
|
|
7004
7038
|
buildCronContext,
|
|
7005
|
-
buildInternalPayload,
|
|
7006
7039
|
buildTableSelect,
|
|
7007
7040
|
buildUiParts,
|
|
7008
7041
|
canCreate,
|
|
@@ -7061,7 +7094,6 @@ export {
|
|
|
7061
7094
|
rowToData,
|
|
7062
7095
|
runMigrations,
|
|
7063
7096
|
serverBuild,
|
|
7064
|
-
signInternalPayload,
|
|
7065
7097
|
timingSafeEqualHex,
|
|
7066
7098
|
toUint8Array,
|
|
7067
7099
|
totalChars,
|
|
@@ -7072,7 +7104,6 @@ export {
|
|
|
7072
7104
|
validateAppName,
|
|
7073
7105
|
validateBindingManifest,
|
|
7074
7106
|
validateDoManifest,
|
|
7075
|
-
verifyInternalSignature,
|
|
7076
7107
|
verifyJwt,
|
|
7077
7108
|
workspaceAccountsSchema,
|
|
7078
7109
|
workspaceContentSharesSchema,
|