weifuwu 0.23.4 → 0.24.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/README.md +431 -152
- package/cli.ts +3 -0
- package/dist/agent/types.d.ts +2 -1
- package/dist/ai/provider.d.ts +10 -1
- package/dist/analytics.d.ts +2 -2
- package/dist/cache.d.ts +4 -4
- package/dist/cli.js +3 -0
- package/dist/cors.d.ts +2 -2
- package/dist/csrf.d.ts +11 -5
- package/dist/deploy/types.d.ts +2 -2
- package/dist/env.d.ts +33 -0
- package/dist/flash.d.ts +5 -0
- package/dist/helmet.d.ts +2 -2
- package/dist/hub.d.ts +2 -1
- package/dist/i18n.d.ts +27 -2
- package/dist/iii/register-worker.d.ts +1 -1
- package/dist/iii/types.d.ts +5 -3
- package/dist/index.d.ts +8 -10
- package/dist/index.js +434 -359
- package/dist/kb/types.d.ts +8 -0
- package/dist/logdb/types.d.ts +2 -1
- package/dist/mailer.d.ts +2 -1
- package/dist/messager/types.d.ts +2 -1
- package/dist/opencode/types.d.ts +2 -1
- package/dist/permissions.d.ts +2 -2
- package/dist/postgres/module.d.ts +2 -1
- package/dist/postgres/types.d.ts +2 -2
- package/dist/queue/types.d.ts +2 -2
- package/dist/rate-limit.d.ts +3 -2
- package/dist/react.js +6 -6
- package/dist/redis/types.d.ts +2 -2
- package/dist/request-id.d.ts +16 -7
- package/dist/router.d.ts +3 -0
- package/dist/seo.d.ts +2 -2
- package/dist/serve.d.ts +1 -1
- package/dist/session.d.ts +9 -5
- package/dist/tailwind.d.ts +9 -0
- package/dist/tenant/types.d.ts +3 -3
- package/dist/theme.d.ts +25 -2
- package/dist/trace.d.ts +44 -0
- package/dist/types.d.ts +8 -17
- package/dist/upload.d.ts +9 -2
- package/dist/user/client.d.ts +11 -3
- package/dist/user/types.d.ts +21 -6
- package/dist/validate.d.ts +5 -0
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// trace.ts
|
|
8
|
+
import crypto2 from "node:crypto";
|
|
8
9
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
|
-
import { randomUUID } from "node:crypto";
|
|
10
10
|
var als = new AsyncLocalStorage();
|
|
11
11
|
function currentTraceId() {
|
|
12
12
|
return als.getStore()?.traceId;
|
|
@@ -15,7 +15,7 @@ function currentTrace() {
|
|
|
15
15
|
return als.getStore();
|
|
16
16
|
}
|
|
17
17
|
function runWithTrace(incomingTraceId, fn) {
|
|
18
|
-
const traceId = incomingTraceId || randomUUID();
|
|
18
|
+
const traceId = incomingTraceId || crypto2.randomUUID();
|
|
19
19
|
const startTime = Date.now();
|
|
20
20
|
return als.run({ traceId, startTime }, fn);
|
|
21
21
|
}
|
|
@@ -24,10 +24,43 @@ function traceElapsed() {
|
|
|
24
24
|
if (!ctx) return 0;
|
|
25
25
|
return Date.now() - ctx.startTime;
|
|
26
26
|
}
|
|
27
|
+
function trace(options) {
|
|
28
|
+
const header = options?.header ?? "X-Request-ID";
|
|
29
|
+
const gen = options?.generator ?? (() => crypto2.randomUUID());
|
|
30
|
+
return async (req, ctx, next) => {
|
|
31
|
+
const existing = req.headers.get(header);
|
|
32
|
+
const requestId2 = existing ?? gen();
|
|
33
|
+
const tc = als.getStore();
|
|
34
|
+
ctx.trace = {
|
|
35
|
+
requestId: requestId2,
|
|
36
|
+
traceId: tc?.traceId ?? requestId2,
|
|
37
|
+
startTime: tc?.startTime ?? Date.now(),
|
|
38
|
+
elapsed: () => {
|
|
39
|
+
const t = als.getStore();
|
|
40
|
+
return t ? Date.now() - t.startTime : 0;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const res = await next(req, ctx);
|
|
44
|
+
if (res.headers.has(header)) return res;
|
|
45
|
+
const h = new Headers(res.headers);
|
|
46
|
+
h.set(header, requestId2);
|
|
47
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
|
|
48
|
+
};
|
|
49
|
+
}
|
|
27
50
|
|
|
28
51
|
// env.ts
|
|
29
52
|
import { readFileSync } from "node:fs";
|
|
30
53
|
import { resolve } from "node:path";
|
|
54
|
+
var PUBLIC_PREFIX = "WEIFUWU_PUBLIC_";
|
|
55
|
+
function getPublicEnv() {
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
58
|
+
if (key.startsWith(PUBLIC_PREFIX) && value !== void 0) {
|
|
59
|
+
result[key.slice(PUBLIC_PREFIX.length)] = value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
31
64
|
function isBundled() {
|
|
32
65
|
return true ? true : false;
|
|
33
66
|
}
|
|
@@ -65,6 +98,14 @@ function loadEnv(path2) {
|
|
|
65
98
|
process.env[key] = value;
|
|
66
99
|
}
|
|
67
100
|
}
|
|
101
|
+
function env() {
|
|
102
|
+
const entries = getPublicEnv();
|
|
103
|
+
return async (req, ctx, next) => {
|
|
104
|
+
;
|
|
105
|
+
ctx.env = entries;
|
|
106
|
+
return next(req, ctx);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
68
109
|
|
|
69
110
|
// serve.ts
|
|
70
111
|
import http from "node:http";
|
|
@@ -142,8 +183,8 @@ async function sendResponse(res, response, opts) {
|
|
|
142
183
|
}
|
|
143
184
|
res.end();
|
|
144
185
|
}
|
|
145
|
-
async function createTestServer(handler) {
|
|
146
|
-
const server = serve(handler, { port: 0, shutdown: false });
|
|
186
|
+
async function createTestServer(handler, options) {
|
|
187
|
+
const server = serve(handler, { ...options, port: options?.port ?? 0, shutdown: false });
|
|
147
188
|
await server.ready;
|
|
148
189
|
return { server, url: `http://localhost:${server.port}` };
|
|
149
190
|
}
|
|
@@ -447,6 +488,10 @@ var Router = class _Router {
|
|
|
447
488
|
}
|
|
448
489
|
} else if (typeof arg1 === "function") {
|
|
449
490
|
this.globalMws.push(arg1);
|
|
491
|
+
} else if (typeof arg1 === "object" && arg1 !== null && "middleware" in arg1 && typeof arg1.middleware === "function" && arg1 instanceof _Router) {
|
|
492
|
+
const mod = arg1;
|
|
493
|
+
this.globalMws.push(mod.middleware());
|
|
494
|
+
this._mountRouter("/", mod);
|
|
450
495
|
}
|
|
451
496
|
return this;
|
|
452
497
|
}
|
|
@@ -991,114 +1036,6 @@ function cors(options) {
|
|
|
991
1036
|
};
|
|
992
1037
|
}
|
|
993
1038
|
|
|
994
|
-
// auth.ts
|
|
995
|
-
function auth(options) {
|
|
996
|
-
if (!options.token && !options.verify && !options.proxy && !options.session) {
|
|
997
|
-
throw new Error("auth() requires at least one of: token, verify, proxy, or session");
|
|
998
|
-
}
|
|
999
|
-
return async (req, ctx, next) => {
|
|
1000
|
-
if (options.session) {
|
|
1001
|
-
const sessionUserId = ctx.session?.userId;
|
|
1002
|
-
if (sessionUserId !== void 0 && sessionUserId !== null) {
|
|
1003
|
-
if (options.resolveUser) {
|
|
1004
|
-
const userData = await options.resolveUser(sessionUserId);
|
|
1005
|
-
if (userData) {
|
|
1006
|
-
ctx.user = userData;
|
|
1007
|
-
return next(req, ctx);
|
|
1008
|
-
}
|
|
1009
|
-
if (typeof ctx.session?.destroy === "function") {
|
|
1010
|
-
;
|
|
1011
|
-
ctx.session.destroy();
|
|
1012
|
-
}
|
|
1013
|
-
console.warn(`[${currentTraceId()}] auth: session userId ${sessionUserId} resolved to null`);
|
|
1014
|
-
} else {
|
|
1015
|
-
ctx.user = { id: sessionUserId };
|
|
1016
|
-
return next(req, ctx);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
const headerName = options.header ?? "Authorization";
|
|
1021
|
-
let from = "header";
|
|
1022
|
-
let header = req.headers.get(headerName);
|
|
1023
|
-
let token = "";
|
|
1024
|
-
if (header) {
|
|
1025
|
-
token = header.trim();
|
|
1026
|
-
if (headerName.toLowerCase() === "authorization") {
|
|
1027
|
-
const parts = header.split(" ");
|
|
1028
|
-
if (parts[0]?.toLowerCase() === "bearer") {
|
|
1029
|
-
token = parts.slice(1).join(" ").trim();
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
} else if (!options.header) {
|
|
1033
|
-
const url = new URL(req.url);
|
|
1034
|
-
const qsToken = url.searchParams.get("access_token");
|
|
1035
|
-
if (qsToken) {
|
|
1036
|
-
token = qsToken;
|
|
1037
|
-
from = "query";
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
if (!token) {
|
|
1041
|
-
return new Response("Unauthorized", {
|
|
1042
|
-
status: 401,
|
|
1043
|
-
headers: headerName.toLowerCase() === "authorization" ? { "WWW-Authenticate": "Bearer" } : void 0
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
if (options.proxy) {
|
|
1047
|
-
let proxyUrl;
|
|
1048
|
-
try {
|
|
1049
|
-
proxyUrl = typeof options.proxy === "string" ? new URL(options.proxy) : options.proxy;
|
|
1050
|
-
} catch {
|
|
1051
|
-
return new Response("Invalid proxy URL", { status: 500 });
|
|
1052
|
-
}
|
|
1053
|
-
const proxyHeaders = {};
|
|
1054
|
-
if (from === "header" && header) {
|
|
1055
|
-
proxyHeaders[headerName] = header;
|
|
1056
|
-
} else {
|
|
1057
|
-
proxyUrl.searchParams.set("access_token", token);
|
|
1058
|
-
}
|
|
1059
|
-
for (const name of ["x-forwarded-for", "x-real-ip", "user-agent", "content-type"]) {
|
|
1060
|
-
const v = req.headers.get(name);
|
|
1061
|
-
if (v) proxyHeaders[name] = v;
|
|
1062
|
-
}
|
|
1063
|
-
const proxyRes = await fetch(proxyUrl.href, { headers: proxyHeaders });
|
|
1064
|
-
if (proxyRes.status >= 400) {
|
|
1065
|
-
return new Response(await proxyRes.text() || "Forbidden", { status: proxyRes.status });
|
|
1066
|
-
}
|
|
1067
|
-
let userData = void 0;
|
|
1068
|
-
if (proxyRes.status === 200) {
|
|
1069
|
-
const ct = proxyRes.headers.get("content-type");
|
|
1070
|
-
if (ct?.includes("application/json")) {
|
|
1071
|
-
try {
|
|
1072
|
-
userData = await proxyRes.json();
|
|
1073
|
-
} catch {
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
ctx.user = userData;
|
|
1078
|
-
return next(req, ctx);
|
|
1079
|
-
}
|
|
1080
|
-
if (options.token) {
|
|
1081
|
-
if (token !== options.token) {
|
|
1082
|
-
console.warn(`[${currentTraceId()}] auth: invalid static token`);
|
|
1083
|
-
return new Response("Forbidden", { status: 403 });
|
|
1084
|
-
}
|
|
1085
|
-
return next(req, ctx);
|
|
1086
|
-
}
|
|
1087
|
-
if (options.verify) {
|
|
1088
|
-
const result = await options.verify(token, req);
|
|
1089
|
-
if (!result) {
|
|
1090
|
-
console.warn(`[${currentTraceId()}] auth: verify failed for token`);
|
|
1091
|
-
return new Response("Forbidden", { status: 403 });
|
|
1092
|
-
}
|
|
1093
|
-
if (typeof result === "object" && result !== null) {
|
|
1094
|
-
ctx.user = result;
|
|
1095
|
-
}
|
|
1096
|
-
return next(req, ctx);
|
|
1097
|
-
}
|
|
1098
|
-
return next(req, ctx);
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
1039
|
// static.ts
|
|
1103
1040
|
import { open, realpath } from "node:fs/promises";
|
|
1104
1041
|
import { extname, resolve as resolve2, normalize, sep } from "node:path";
|
|
@@ -1382,7 +1319,7 @@ function deleteCookie(res, name, options) {
|
|
|
1382
1319
|
|
|
1383
1320
|
// upload.ts
|
|
1384
1321
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
1385
|
-
import { randomUUID
|
|
1322
|
+
import { randomUUID } from "node:crypto";
|
|
1386
1323
|
import { join, extname as extname2 } from "node:path";
|
|
1387
1324
|
var extensionMimeMap = {
|
|
1388
1325
|
".jpg": "image/jpeg",
|
|
@@ -1454,7 +1391,7 @@ function upload(options) {
|
|
|
1454
1391
|
};
|
|
1455
1392
|
if (saveDir) {
|
|
1456
1393
|
const safeName = value.name.replace(/[/\\\0]/g, "_").replace(/\.\./g, "_");
|
|
1457
|
-
const filePath = join(saveDir, `${
|
|
1394
|
+
const filePath = join(saveDir, `${randomUUID()}-${safeName}`);
|
|
1458
1395
|
await writeFile(filePath, buf);
|
|
1459
1396
|
uf.path = filePath;
|
|
1460
1397
|
}
|
|
@@ -1551,10 +1488,11 @@ function rateLimit(options) {
|
|
|
1551
1488
|
const res = await next(req, ctx);
|
|
1552
1489
|
return addRateLimitHeaders(res, max, remaining, reset);
|
|
1553
1490
|
};
|
|
1554
|
-
mw.
|
|
1491
|
+
mw.close = () => {
|
|
1555
1492
|
if (interval) clearInterval(interval);
|
|
1556
1493
|
hits.clear();
|
|
1557
1494
|
};
|
|
1495
|
+
mw.stop = mw.close;
|
|
1558
1496
|
mw.stats = () => ({
|
|
1559
1497
|
store: storeType,
|
|
1560
1498
|
entries: storeType === "memory" ? hits.size : void 0,
|
|
@@ -1672,10 +1610,10 @@ var DEFAULTS = {
|
|
|
1672
1610
|
};
|
|
1673
1611
|
|
|
1674
1612
|
// request-id.ts
|
|
1675
|
-
import
|
|
1613
|
+
import crypto3 from "node:crypto";
|
|
1676
1614
|
function requestId(options) {
|
|
1677
1615
|
const header = options?.header ?? "X-Request-ID";
|
|
1678
|
-
const gen = options?.generator ?? (() =>
|
|
1616
|
+
const gen = options?.generator ?? (() => crypto3.randomUUID());
|
|
1679
1617
|
return async (req, ctx, next) => {
|
|
1680
1618
|
const existing = req.headers.get(header);
|
|
1681
1619
|
const id2 = existing ?? gen();
|
|
@@ -2425,7 +2363,7 @@ function aiProvider(options) {
|
|
|
2425
2363
|
const client = createOpenAI({ baseURL, apiKey });
|
|
2426
2364
|
let _model;
|
|
2427
2365
|
let _embedModel;
|
|
2428
|
-
|
|
2366
|
+
const provider = {
|
|
2429
2367
|
get dimension() {
|
|
2430
2368
|
return dimension;
|
|
2431
2369
|
},
|
|
@@ -2454,6 +2392,12 @@ function aiProvider(options) {
|
|
|
2454
2392
|
return aiStreamText({ ...params, model: this.model() });
|
|
2455
2393
|
}
|
|
2456
2394
|
};
|
|
2395
|
+
const mw = async (req, ctx, next) => {
|
|
2396
|
+
;
|
|
2397
|
+
ctx.ai = provider;
|
|
2398
|
+
return next(req, ctx);
|
|
2399
|
+
};
|
|
2400
|
+
return Object.assign(mw, provider);
|
|
2457
2401
|
}
|
|
2458
2402
|
|
|
2459
2403
|
// ai-sdk.ts
|
|
@@ -2751,9 +2695,10 @@ var Table = class {
|
|
|
2751
2695
|
_buildSET(data) {
|
|
2752
2696
|
const sets = [];
|
|
2753
2697
|
const values = [];
|
|
2698
|
+
const d = data;
|
|
2754
2699
|
for (const { prop, db } of this.colEntries) {
|
|
2755
|
-
if (prop in
|
|
2756
|
-
const val =
|
|
2700
|
+
if (prop in d && d[prop] !== void 0) {
|
|
2701
|
+
const val = d[prop];
|
|
2757
2702
|
if (val instanceof SQL) {
|
|
2758
2703
|
sets.push(`"${db}" = ${val.toSQL()}`);
|
|
2759
2704
|
} else {
|
|
@@ -2762,7 +2707,7 @@ var Table = class {
|
|
|
2762
2707
|
}
|
|
2763
2708
|
}
|
|
2764
2709
|
}
|
|
2765
|
-
if (this.hasColumn("updated_at") && !
|
|
2710
|
+
if (this.hasColumn("updated_at") && !d.updated_at) {
|
|
2766
2711
|
sets.push('"updated_at" = NOW()');
|
|
2767
2712
|
}
|
|
2768
2713
|
return { sets, values };
|
|
@@ -3126,7 +3071,7 @@ import jwt2 from "jsonwebtoken";
|
|
|
3126
3071
|
import { z as z2 } from "zod";
|
|
3127
3072
|
|
|
3128
3073
|
// user/oauth2.ts
|
|
3129
|
-
import
|
|
3074
|
+
import crypto4 from "node:crypto";
|
|
3130
3075
|
import jwt from "jsonwebtoken";
|
|
3131
3076
|
function createOAuth2Server(deps) {
|
|
3132
3077
|
const { pg, users, jwtSecret, expiresIn } = deps;
|
|
@@ -3145,8 +3090,8 @@ function createOAuth2Server(deps) {
|
|
|
3145
3090
|
};
|
|
3146
3091
|
}
|
|
3147
3092
|
async function registerClient(data) {
|
|
3148
|
-
const clientId =
|
|
3149
|
-
const clientSecret =
|
|
3093
|
+
const clientId = crypto4.randomUUID();
|
|
3094
|
+
const clientSecret = crypto4.randomBytes(32).toString("hex");
|
|
3150
3095
|
const [row] = await pg.sql`
|
|
3151
3096
|
INSERT INTO "_oauth2_clients" ("name", "client_id", "client_secret", "redirect_uris")
|
|
3152
3097
|
VALUES (${data.name}, ${clientId}, ${clientSecret}, ${pg.sql.array(data.redirectUris)})
|
|
@@ -3287,18 +3232,18 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3287
3232
|
const redirectUri = form.get("redirect_uri") || "";
|
|
3288
3233
|
const scope = form.get("scope") || "";
|
|
3289
3234
|
const state = form.get("state") || "";
|
|
3290
|
-
const
|
|
3235
|
+
const userId2 = parseInt(form.get("user_id") || "0", 10);
|
|
3291
3236
|
const codeChallenge = form.get("code_challenge") || "";
|
|
3292
3237
|
const codeChallengeMethod = form.get("code_challenge_method") || "";
|
|
3293
3238
|
if (!approve) {
|
|
3294
3239
|
const loc2 = `${redirectUri}?error=access_denied${state ? `&state=${state}` : ""}`;
|
|
3295
3240
|
return Response.redirect(loc2, 302);
|
|
3296
3241
|
}
|
|
3297
|
-
const code =
|
|
3242
|
+
const code = crypto4.randomUUID();
|
|
3298
3243
|
const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
|
|
3299
3244
|
await pg.sql`
|
|
3300
3245
|
INSERT INTO "_oauth2_codes" ("code", "client_id", "user_id", "redirect_uri", "code_challenge", "code_challenge_method", "scope", "expires_at")
|
|
3301
|
-
VALUES (${code}, ${clientId}, ${
|
|
3246
|
+
VALUES (${code}, ${clientId}, ${userId2}, ${redirectUri}, ${codeChallenge || null}, ${codeChallengeMethod || null}, ${scope || null}, ${expiresAt})
|
|
3302
3247
|
`;
|
|
3303
3248
|
const loc = `${redirectUri}?code=${code}${state ? `&state=${state}` : ""}`;
|
|
3304
3249
|
return Response.redirect(loc, 302);
|
|
@@ -3359,7 +3304,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3359
3304
|
if (stored.code_challenge_method === "plain") {
|
|
3360
3305
|
expected = codeVerifier;
|
|
3361
3306
|
} else {
|
|
3362
|
-
expected =
|
|
3307
|
+
expected = crypto4.createHash("sha256").update(codeVerifier).digest().toString("base64url");
|
|
3363
3308
|
}
|
|
3364
3309
|
if (expected !== stored.code_challenge) {
|
|
3365
3310
|
return Response.json({ error: "invalid_grant", error_description: "code_verifier mismatch" }, { status: 400 });
|
|
@@ -3376,7 +3321,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3376
3321
|
jwtSecret,
|
|
3377
3322
|
{ expiresIn }
|
|
3378
3323
|
);
|
|
3379
|
-
const refreshToken =
|
|
3324
|
+
const refreshToken = crypto4.randomUUID();
|
|
3380
3325
|
const refreshExpires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3);
|
|
3381
3326
|
await pg.sql`
|
|
3382
3327
|
INSERT INTO "_oauth2_tokens" ("token", "client_id", "user_id", "scope", "expires_at")
|
|
@@ -3414,7 +3359,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3414
3359
|
}
|
|
3415
3360
|
|
|
3416
3361
|
// user/oauth-login.ts
|
|
3417
|
-
import
|
|
3362
|
+
import crypto5 from "node:crypto";
|
|
3418
3363
|
var BUILTIN_PROVIDERS = {
|
|
3419
3364
|
google: {
|
|
3420
3365
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
@@ -3474,12 +3419,12 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3474
3419
|
);
|
|
3475
3420
|
return row ?? null;
|
|
3476
3421
|
}
|
|
3477
|
-
async function linkProvider(
|
|
3422
|
+
async function linkProvider(userId2, provider, providerId, email, name, avatarUrl) {
|
|
3478
3423
|
await sql2.unsafe(
|
|
3479
3424
|
`INSERT INTO ${escapeIdent(providerTable)} (user_id, provider, provider_id, email, name, avatar_url)
|
|
3480
3425
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
3481
3426
|
ON CONFLICT (provider, provider_id) DO NOTHING`,
|
|
3482
|
-
[
|
|
3427
|
+
[userId2, provider, providerId, email, name, avatarUrl]
|
|
3483
3428
|
);
|
|
3484
3429
|
}
|
|
3485
3430
|
async function findOrCreateUser(provider, providerId, email, name, avatarUrl) {
|
|
@@ -3526,7 +3471,7 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3526
3471
|
return Response.json({ error: `Unsupported provider: ${providerName}` }, { status: 400 });
|
|
3527
3472
|
}
|
|
3528
3473
|
const { config, meta } = resolved;
|
|
3529
|
-
const state =
|
|
3474
|
+
const state = crypto5.randomUUID();
|
|
3530
3475
|
const redirectUri = new URL(req.url);
|
|
3531
3476
|
redirectUri.pathname = redirectUri.pathname.replace(/\/[^/]+$/, "/") + providerName + "/callback";
|
|
3532
3477
|
if (ctx.session) {
|
|
@@ -3657,14 +3602,40 @@ function verifyPassword(password, stored) {
|
|
|
3657
3602
|
if (hash.length !== verify.length) return false;
|
|
3658
3603
|
return timingSafeEqual(Buffer.from(hash), Buffer.from(verify));
|
|
3659
3604
|
}
|
|
3605
|
+
function extractToken(req, headerName, cookieName) {
|
|
3606
|
+
const header = req.headers.get(headerName);
|
|
3607
|
+
if (header) {
|
|
3608
|
+
if (headerName.toLowerCase() === "authorization") {
|
|
3609
|
+
const parts = header.split(" ");
|
|
3610
|
+
if (parts[0]?.toLowerCase() === "bearer") {
|
|
3611
|
+
return parts.slice(1).join(" ").trim();
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
return header.trim();
|
|
3615
|
+
}
|
|
3616
|
+
if (headerName.toLowerCase() === "authorization") {
|
|
3617
|
+
const url = new URL(req.url);
|
|
3618
|
+
const qsToken = url.searchParams.get("access_token");
|
|
3619
|
+
if (qsToken) return qsToken;
|
|
3620
|
+
}
|
|
3621
|
+
if (cookieName) {
|
|
3622
|
+
const cookies = req.headers.get("cookie")?.split(";").map((c) => c.trim()).filter(Boolean) || [];
|
|
3623
|
+
for (const c of cookies) {
|
|
3624
|
+
const eq2 = c.indexOf("=");
|
|
3625
|
+
if (eq2 > 0 && c.slice(0, eq2) === cookieName) return c.slice(eq2 + 1);
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
return null;
|
|
3629
|
+
}
|
|
3660
3630
|
function user(options) {
|
|
3631
|
+
const hasDb = !!options.pg;
|
|
3661
3632
|
const table = options.table ?? "_users";
|
|
3662
3633
|
const pg = options.pg;
|
|
3663
3634
|
const secret = options.jwtSecret;
|
|
3664
3635
|
const expiresIn = options.expiresIn ?? "24h";
|
|
3665
3636
|
const oauth2Enabled = options.oauth2?.server ?? false;
|
|
3666
|
-
const base = new PgModule(pg);
|
|
3667
|
-
const users = pg.table(table, {
|
|
3637
|
+
const base = hasDb ? new PgModule(pg) : null;
|
|
3638
|
+
const users = hasDb ? pg.table(table, {
|
|
3668
3639
|
id: serial("id").primaryKey(),
|
|
3669
3640
|
email: text("email").unique().notNull(),
|
|
3670
3641
|
password: text("password").notNull(),
|
|
@@ -3672,15 +3643,17 @@ function user(options) {
|
|
|
3672
3643
|
role: text("role").default("user"),
|
|
3673
3644
|
created_at: timestamptz("created_at").default(sql`NOW()`),
|
|
3674
3645
|
updated_at: timestamptz("updated_at").default(sql`NOW()`)
|
|
3675
|
-
});
|
|
3646
|
+
}) : null;
|
|
3647
|
+
const _pg = pg;
|
|
3648
|
+
const _users = users;
|
|
3676
3649
|
let oauth2 = null;
|
|
3677
3650
|
if (oauth2Enabled) {
|
|
3678
|
-
oauth2 = createOAuth2Server({ pg, users, jwtSecret: secret, expiresIn });
|
|
3651
|
+
oauth2 = createOAuth2Server({ pg: _pg, users: _users, jwtSecret: secret, expiresIn });
|
|
3679
3652
|
}
|
|
3680
3653
|
async function migrate() {
|
|
3681
|
-
await
|
|
3654
|
+
await _users.create();
|
|
3682
3655
|
if (options.oauthLogin) {
|
|
3683
|
-
await
|
|
3656
|
+
await _pg.sql.unsafe(`
|
|
3684
3657
|
CREATE TABLE IF NOT EXISTS "_auth_providers" (
|
|
3685
3658
|
id SERIAL PRIMARY KEY,
|
|
3686
3659
|
user_id INTEGER NOT NULL REFERENCES ${escapeIdent2(table)}(id) ON DELETE CASCADE,
|
|
@@ -3693,13 +3666,13 @@ function user(options) {
|
|
|
3693
3666
|
UNIQUE(provider, provider_id)
|
|
3694
3667
|
)
|
|
3695
3668
|
`);
|
|
3696
|
-
await
|
|
3669
|
+
await _pg.sql.unsafe(`
|
|
3697
3670
|
CREATE INDEX IF NOT EXISTS "_auth_providers_user_idx"
|
|
3698
3671
|
ON "_auth_providers"(user_id)
|
|
3699
3672
|
`);
|
|
3700
3673
|
}
|
|
3701
3674
|
if (!oauth2Enabled) return;
|
|
3702
|
-
const clients3 =
|
|
3675
|
+
const clients3 = _pg.table("_oauth2_clients", {
|
|
3703
3676
|
id: serial("id").primaryKey(),
|
|
3704
3677
|
name: text("name").notNull(),
|
|
3705
3678
|
client_id: text("client_id").unique().notNull(),
|
|
@@ -3709,7 +3682,7 @@ function user(options) {
|
|
|
3709
3682
|
created_at: timestamptz("created_at").default(sql`NOW()`)
|
|
3710
3683
|
});
|
|
3711
3684
|
await clients3.create();
|
|
3712
|
-
const codes =
|
|
3685
|
+
const codes = _pg.table("_oauth2_codes", {
|
|
3713
3686
|
id: serial("id").primaryKey(),
|
|
3714
3687
|
code: text("code").unique().notNull(),
|
|
3715
3688
|
client_id: text("client_id").notNull(),
|
|
@@ -3722,7 +3695,7 @@ function user(options) {
|
|
|
3722
3695
|
used: boolean_("used").default(false)
|
|
3723
3696
|
});
|
|
3724
3697
|
await codes.create();
|
|
3725
|
-
const tokens =
|
|
3698
|
+
const tokens = _pg.table("_oauth2_tokens", {
|
|
3726
3699
|
id: serial("id").primaryKey(),
|
|
3727
3700
|
token: text("token").unique().notNull(),
|
|
3728
3701
|
client_id: text("client_id").notNull(),
|
|
@@ -3745,15 +3718,15 @@ function user(options) {
|
|
|
3745
3718
|
return user2;
|
|
3746
3719
|
}
|
|
3747
3720
|
async function findByEmail(email) {
|
|
3748
|
-
const { data: rows } = await
|
|
3721
|
+
const { data: rows } = await _users.readMany({ email });
|
|
3749
3722
|
return rows[0];
|
|
3750
3723
|
}
|
|
3751
3724
|
async function findById(id2) {
|
|
3752
|
-
return await
|
|
3725
|
+
return await _users.read(id2);
|
|
3753
3726
|
}
|
|
3754
3727
|
async function createPlaceholderUser(email, name) {
|
|
3755
3728
|
const randomPassword = randomBytes(32).toString("hex");
|
|
3756
|
-
const row = await
|
|
3729
|
+
const row = await _users.insert({ email, password: randomPassword, name });
|
|
3757
3730
|
return row;
|
|
3758
3731
|
}
|
|
3759
3732
|
async function register(data) {
|
|
@@ -3765,14 +3738,14 @@ function user(options) {
|
|
|
3765
3738
|
throw err;
|
|
3766
3739
|
}
|
|
3767
3740
|
const hashed = hashPassword(password);
|
|
3768
|
-
const row = await
|
|
3741
|
+
const row = await _users.insert({ email, password: hashed, name });
|
|
3769
3742
|
const userData = row;
|
|
3770
3743
|
const token = signToken(userData);
|
|
3771
3744
|
return { user: stripPassword(userData), token };
|
|
3772
3745
|
}
|
|
3773
3746
|
async function login(data) {
|
|
3774
3747
|
const { email, password } = LoginSchema.parse(data);
|
|
3775
|
-
const { data: rows } = await
|
|
3748
|
+
const { data: rows } = await _users.readMany({ email });
|
|
3776
3749
|
const row = rows[0];
|
|
3777
3750
|
if (!row) {
|
|
3778
3751
|
const err = new Error("Invalid email or password");
|
|
@@ -3792,61 +3765,121 @@ function user(options) {
|
|
|
3792
3765
|
try {
|
|
3793
3766
|
const payload = jwt2.verify(token, secret);
|
|
3794
3767
|
if (payload.token_type === "client_credentials") return null;
|
|
3795
|
-
|
|
3768
|
+
if (!hasDb || !findById) return null;
|
|
3769
|
+
const row = await findById(Number(payload.sub));
|
|
3796
3770
|
if (!row) return null;
|
|
3797
3771
|
return stripPassword(row);
|
|
3798
3772
|
} catch {
|
|
3799
3773
|
return null;
|
|
3800
3774
|
}
|
|
3801
3775
|
}
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
const eq2 = c.indexOf("=");
|
|
3809
|
-
if (eq2 > 0 && c.slice(0, eq2) === cookieName) return c.slice(eq2 + 1);
|
|
3810
|
-
}
|
|
3811
|
-
}
|
|
3812
|
-
return null;
|
|
3813
|
-
}
|
|
3814
|
-
function middleware() {
|
|
3815
|
-
return async (req, ctx, next) => {
|
|
3816
|
-
const sessionUserId = ctx.session?.userId;
|
|
3817
|
-
if (sessionUserId) {
|
|
3776
|
+
const headerName = options.header ?? "Authorization";
|
|
3777
|
+
async function resolveUser(req, ctx) {
|
|
3778
|
+
const s = ctx;
|
|
3779
|
+
const sessionUserId = s.session?.userId;
|
|
3780
|
+
if (sessionUserId !== void 0 && sessionUserId !== null) {
|
|
3781
|
+
if (hasDb) {
|
|
3818
3782
|
const row = await findById(sessionUserId);
|
|
3819
3783
|
if (row) {
|
|
3820
|
-
|
|
3821
|
-
return next(req, ctx);
|
|
3784
|
+
return stripPassword(row);
|
|
3822
3785
|
}
|
|
3823
|
-
if (typeof
|
|
3824
|
-
;
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
delete ctx.session?.userId;
|
|
3786
|
+
if (typeof s.session?.destroy === "function") {
|
|
3787
|
+
s.session.destroy();
|
|
3788
|
+
} else if (s.session) {
|
|
3789
|
+
delete s.session.userId;
|
|
3828
3790
|
}
|
|
3829
|
-
}
|
|
3830
|
-
|
|
3831
|
-
if (token) {
|
|
3832
|
-
const userData = await verify(token);
|
|
3791
|
+
} else if (options.resolveUser) {
|
|
3792
|
+
const userData = await options.resolveUser(sessionUserId);
|
|
3833
3793
|
if (userData) {
|
|
3834
|
-
|
|
3835
|
-
|
|
3794
|
+
return userData;
|
|
3795
|
+
}
|
|
3796
|
+
if (typeof s.session?.destroy === "function") {
|
|
3797
|
+
s.session.destroy();
|
|
3798
|
+
}
|
|
3799
|
+
console.warn(`[${currentTraceId()}] user: session userId ${sessionUserId} resolved to null`);
|
|
3800
|
+
} else {
|
|
3801
|
+
return { id: sessionUserId };
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
const token = extractToken(req, headerName);
|
|
3805
|
+
if (!token) return null;
|
|
3806
|
+
if (options.tokens?.length) {
|
|
3807
|
+
if (options.tokens.includes(token)) {
|
|
3808
|
+
return { id: token };
|
|
3809
|
+
}
|
|
3810
|
+
console.warn(`[${currentTraceId()}] user: invalid static token`);
|
|
3811
|
+
return null;
|
|
3812
|
+
}
|
|
3813
|
+
if (options.verify) {
|
|
3814
|
+
const result = await options.verify(token, req);
|
|
3815
|
+
if (!result) {
|
|
3816
|
+
console.warn(`[${currentTraceId()}] user: verify failed for token`);
|
|
3817
|
+
return null;
|
|
3818
|
+
}
|
|
3819
|
+
return result;
|
|
3820
|
+
}
|
|
3821
|
+
if (options.proxy) {
|
|
3822
|
+
let proxyUrl;
|
|
3823
|
+
try {
|
|
3824
|
+
proxyUrl = typeof options.proxy === "string" ? new URL(options.proxy) : options.proxy;
|
|
3825
|
+
} catch {
|
|
3826
|
+
return null;
|
|
3827
|
+
}
|
|
3828
|
+
const proxyHeaders = {};
|
|
3829
|
+
proxyHeaders[headerName] = req.headers.get(headerName) ?? `Bearer ${token}`;
|
|
3830
|
+
for (const name of ["x-forwarded-for", "x-real-ip", "user-agent", "content-type"]) {
|
|
3831
|
+
const v = req.headers.get(name);
|
|
3832
|
+
if (v) proxyHeaders[name] = v;
|
|
3833
|
+
}
|
|
3834
|
+
try {
|
|
3835
|
+
const proxyRes = await fetch(proxyUrl.href, { headers: proxyHeaders });
|
|
3836
|
+
if (proxyRes.status >= 400) {
|
|
3837
|
+
console.warn(`[${currentTraceId()}] user: proxy auth rejected (${proxyRes.status})`);
|
|
3838
|
+
return null;
|
|
3836
3839
|
}
|
|
3840
|
+
const ct = proxyRes.headers.get("content-type");
|
|
3841
|
+
if (ct?.includes("application/json")) {
|
|
3842
|
+
try {
|
|
3843
|
+
return await proxyRes.json();
|
|
3844
|
+
} catch {
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
return { id: token };
|
|
3848
|
+
} catch (err) {
|
|
3849
|
+
console.warn(`[${currentTraceId()}] user: proxy auth error: ${err}`);
|
|
3850
|
+
return null;
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
if (secret && hasDb) {
|
|
3854
|
+
try {
|
|
3855
|
+
const payload = jwt2.verify(token, secret);
|
|
3856
|
+
if (payload.token_type === "client_credentials") return null;
|
|
3857
|
+
const row = await findById(Number(payload.sub));
|
|
3858
|
+
if (row) return stripPassword(row);
|
|
3859
|
+
} catch {
|
|
3860
|
+
}
|
|
3861
|
+
return null;
|
|
3862
|
+
}
|
|
3863
|
+
return null;
|
|
3864
|
+
}
|
|
3865
|
+
function middleware() {
|
|
3866
|
+
return async (req, ctx, next) => {
|
|
3867
|
+
const userData = await resolveUser(req, ctx);
|
|
3868
|
+
if (userData) {
|
|
3869
|
+
ctx.user = userData;
|
|
3870
|
+
return next(req, ctx);
|
|
3837
3871
|
}
|
|
3838
|
-
return new Response("Unauthorized", {
|
|
3872
|
+
return new Response("Unauthorized", {
|
|
3873
|
+
status: 401,
|
|
3874
|
+
headers: headerName.toLowerCase() === "authorization" ? { "WWW-Authenticate": "Bearer" } : void 0
|
|
3875
|
+
});
|
|
3839
3876
|
};
|
|
3840
3877
|
}
|
|
3841
|
-
function middlewareOptional(
|
|
3842
|
-
const cookieName = opts?.cookie;
|
|
3878
|
+
function middlewareOptional(_opts) {
|
|
3843
3879
|
return async (req, ctx, next) => {
|
|
3844
|
-
const
|
|
3845
|
-
if (
|
|
3846
|
-
|
|
3847
|
-
if (userData) {
|
|
3848
|
-
ctx.user = userData;
|
|
3849
|
-
}
|
|
3880
|
+
const userData = await resolveUser(req, ctx);
|
|
3881
|
+
if (userData) {
|
|
3882
|
+
ctx.user = userData;
|
|
3850
3883
|
}
|
|
3851
3884
|
return next(req, ctx);
|
|
3852
3885
|
};
|
|
@@ -3863,9 +3896,9 @@ function user(options) {
|
|
|
3863
3896
|
}
|
|
3864
3897
|
return obj;
|
|
3865
3898
|
}
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3899
|
+
const r = new Router();
|
|
3900
|
+
if (hasDb) {
|
|
3901
|
+
r.post("/register", async (req) => {
|
|
3869
3902
|
try {
|
|
3870
3903
|
const body = await parseBody2(req);
|
|
3871
3904
|
const result = await register(body);
|
|
@@ -3878,17 +3911,17 @@ function user(options) {
|
|
|
3878
3911
|
return Response.json({ error: err.message }, { status });
|
|
3879
3912
|
}
|
|
3880
3913
|
});
|
|
3881
|
-
|
|
3914
|
+
r.post("/login", async (req, ctx) => {
|
|
3882
3915
|
try {
|
|
3883
3916
|
const body = await parseBody2(req);
|
|
3884
3917
|
const result = await login(body);
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3918
|
+
const s = ctx;
|
|
3919
|
+
if (s.session) {
|
|
3920
|
+
s.session.userId = result.user.id;
|
|
3921
|
+
s.session.role = result.user.role;
|
|
3889
3922
|
}
|
|
3890
3923
|
const res = Response.json(result);
|
|
3891
|
-
if (!
|
|
3924
|
+
if (!s.session) {
|
|
3892
3925
|
res.headers.set("Set-Cookie", `session=${result.token}; HttpOnly; SameSite=Lax; Path=/`);
|
|
3893
3926
|
}
|
|
3894
3927
|
return res;
|
|
@@ -3900,17 +3933,15 @@ function user(options) {
|
|
|
3900
3933
|
return Response.json({ error: err.message }, { status });
|
|
3901
3934
|
}
|
|
3902
3935
|
});
|
|
3903
|
-
if (oauth2) {
|
|
3904
|
-
r2.get("/oauth/authorize", (req, ctx) => oauth2.authorizeHandler(req, ctx));
|
|
3905
|
-
r2.post("/oauth/consent", (req) => oauth2.consentHandler(req));
|
|
3906
|
-
r2.post("/oauth/token", (req) => oauth2.tokenHandler(req));
|
|
3907
|
-
}
|
|
3908
|
-
return r2;
|
|
3909
3936
|
}
|
|
3910
|
-
|
|
3911
|
-
|
|
3937
|
+
if (oauth2) {
|
|
3938
|
+
r.get("/oauth/authorize", (req, ctx) => oauth2.authorizeHandler(req, ctx));
|
|
3939
|
+
r.post("/oauth/consent", (req) => oauth2.consentHandler(req));
|
|
3940
|
+
r.post("/oauth/token", (req) => oauth2.tokenHandler(req));
|
|
3941
|
+
}
|
|
3942
|
+
if (hasDb && options.oauthLogin) {
|
|
3912
3943
|
registerOAuthLoginRoutes(r, {
|
|
3913
|
-
sql:
|
|
3944
|
+
sql: _pg.sql,
|
|
3914
3945
|
jwtSecret: secret,
|
|
3915
3946
|
expiresIn,
|
|
3916
3947
|
usersTable: table,
|
|
@@ -3925,10 +3956,15 @@ function user(options) {
|
|
|
3925
3956
|
const mod = r;
|
|
3926
3957
|
mod.middleware = middleware;
|
|
3927
3958
|
mod.middlewareOptional = middlewareOptional;
|
|
3928
|
-
mod.migrate = migrate
|
|
3929
|
-
|
|
3930
|
-
mod.
|
|
3931
|
-
|
|
3959
|
+
mod.migrate = hasDb ? migrate : async () => {
|
|
3960
|
+
};
|
|
3961
|
+
mod.register = hasDb ? register : async () => {
|
|
3962
|
+
throw new Error("user(): pg required for register");
|
|
3963
|
+
};
|
|
3964
|
+
mod.login = hasDb ? login : async () => {
|
|
3965
|
+
throw new Error("user(): pg required for login");
|
|
3966
|
+
};
|
|
3967
|
+
mod.verify = hasDb ? verify : async () => null;
|
|
3932
3968
|
mod.registerClient = oauth2 ? (data) => oauth2.registerClient(data) : async () => {
|
|
3933
3969
|
throw new Error("OAuth2 server is not enabled");
|
|
3934
3970
|
};
|
|
@@ -3938,7 +3974,8 @@ function user(options) {
|
|
|
3938
3974
|
mod.revokeClient = oauth2 ? (clientId) => oauth2.revokeClient(clientId) : async () => {
|
|
3939
3975
|
throw new Error("OAuth2 server is not enabled");
|
|
3940
3976
|
};
|
|
3941
|
-
mod.close = () => base.close()
|
|
3977
|
+
mod.close = hasDb ? () => base.close() : async () => {
|
|
3978
|
+
};
|
|
3942
3979
|
return mod;
|
|
3943
3980
|
}
|
|
3944
3981
|
|
|
@@ -3960,7 +3997,7 @@ function redis(opts) {
|
|
|
3960
3997
|
|
|
3961
3998
|
// queue/index.ts
|
|
3962
3999
|
import { Redis as IORedis2 } from "ioredis";
|
|
3963
|
-
import
|
|
4000
|
+
import crypto6 from "node:crypto";
|
|
3964
4001
|
|
|
3965
4002
|
// cron-utils.ts
|
|
3966
4003
|
function parseField(field, min, max) {
|
|
@@ -4046,7 +4083,7 @@ function escapeIdent3(s) {
|
|
|
4046
4083
|
function attachCron(q, handlers) {
|
|
4047
4084
|
;
|
|
4048
4085
|
q.cron = function(pattern, handler) {
|
|
4049
|
-
const id2 = "__cron_" + pattern.replace(/[^a-zA-Z0-9]/g, "_") + "_" +
|
|
4086
|
+
const id2 = "__cron_" + pattern.replace(/[^a-zA-Z0-9]/g, "_") + "_" + crypto6.randomUUID().slice(0, 8);
|
|
4050
4087
|
q.process(id2, async () => {
|
|
4051
4088
|
await handler();
|
|
4052
4089
|
});
|
|
@@ -4085,7 +4122,7 @@ function createMemoryQueue(opts) {
|
|
|
4085
4122
|
}
|
|
4086
4123
|
if (job.schedule) {
|
|
4087
4124
|
try {
|
|
4088
|
-
insertJob({ ...job, id:
|
|
4125
|
+
insertJob({ ...job, id: crypto6.randomUUID(), runAt: cronNext(job.schedule), createdAt: Date.now() });
|
|
4089
4126
|
} catch (e) {
|
|
4090
4127
|
console.error("[queue] cron re-queue failed:", e.message);
|
|
4091
4128
|
}
|
|
@@ -4107,7 +4144,7 @@ function createMemoryQueue(opts) {
|
|
|
4107
4144
|
});
|
|
4108
4145
|
const q = mw;
|
|
4109
4146
|
mw.add = function add(type, payload, opts2) {
|
|
4110
|
-
const id2 =
|
|
4147
|
+
const id2 = crypto6.randomUUID();
|
|
4111
4148
|
let runAt;
|
|
4112
4149
|
if (opts2?.schedule) {
|
|
4113
4150
|
try {
|
|
@@ -4209,7 +4246,7 @@ function createPgQueue(opts) {
|
|
|
4209
4246
|
if (job.schedule) {
|
|
4210
4247
|
try {
|
|
4211
4248
|
const nextRun = cronNext(job.schedule);
|
|
4212
|
-
await sql2.unsafe(`INSERT INTO ${escapeIdent3(table)} (id, type, payload, run_at, schedule) VALUES ($1, $2, $3::jsonb, $4, $5)`, [
|
|
4249
|
+
await sql2.unsafe(`INSERT INTO ${escapeIdent3(table)} (id, type, payload, run_at, schedule) VALUES ($1, $2, $3::jsonb, $4, $5)`, [crypto6.randomUUID(), job.type, JSON.stringify(job.payload), new Date(nextRun).toISOString(), job.schedule]);
|
|
4213
4250
|
} catch (e) {
|
|
4214
4251
|
console.error("[queue] cron re-queue failed:", e.message);
|
|
4215
4252
|
}
|
|
@@ -4243,7 +4280,7 @@ function createPgQueue(opts) {
|
|
|
4243
4280
|
const q = mw;
|
|
4244
4281
|
mw.add = function add(type, payload, opts2) {
|
|
4245
4282
|
return (async () => {
|
|
4246
|
-
const id2 =
|
|
4283
|
+
const id2 = crypto6.randomUUID();
|
|
4247
4284
|
let runAt;
|
|
4248
4285
|
if (opts2?.schedule) {
|
|
4249
4286
|
try {
|
|
@@ -4330,7 +4367,7 @@ function createRedisQueue(opts) {
|
|
|
4330
4367
|
if (job.schedule) {
|
|
4331
4368
|
try {
|
|
4332
4369
|
const nextRun = cronNext(job.schedule);
|
|
4333
|
-
await redis2.zadd(jobKey, nextRun, JSON.stringify({ ...job, id:
|
|
4370
|
+
await redis2.zadd(jobKey, nextRun, JSON.stringify({ ...job, id: crypto6.randomUUID(), runAt: nextRun, createdAt: Date.now() }));
|
|
4334
4371
|
} catch (e) {
|
|
4335
4372
|
console.error("[queue] cron re-queue failed:", e.message);
|
|
4336
4373
|
}
|
|
@@ -4369,7 +4406,7 @@ function createRedisQueue(opts) {
|
|
|
4369
4406
|
});
|
|
4370
4407
|
const q = mw;
|
|
4371
4408
|
mw.add = function add(type, payload, opts2) {
|
|
4372
|
-
const id2 =
|
|
4409
|
+
const id2 = crypto6.randomUUID();
|
|
4373
4410
|
let runAt;
|
|
4374
4411
|
if (opts2?.schedule) {
|
|
4375
4412
|
runAt = cronNext(opts2.schedule);
|
|
@@ -4658,6 +4695,27 @@ function buildColumnDDL(tenantId, field) {
|
|
|
4658
4695
|
}
|
|
4659
4696
|
|
|
4660
4697
|
// tenant/rest.ts
|
|
4698
|
+
function userId(ctx) {
|
|
4699
|
+
return ctx.user?.id ?? null;
|
|
4700
|
+
}
|
|
4701
|
+
function extractCount(rows) {
|
|
4702
|
+
return Number(rows[0]?.count ?? 0);
|
|
4703
|
+
}
|
|
4704
|
+
function asJson(val) {
|
|
4705
|
+
return val;
|
|
4706
|
+
}
|
|
4707
|
+
function tableRef(s, name) {
|
|
4708
|
+
return s(name);
|
|
4709
|
+
}
|
|
4710
|
+
function withTenant(ctx, data) {
|
|
4711
|
+
;
|
|
4712
|
+
data.tenant_id = ctx.tenant.id;
|
|
4713
|
+
return data;
|
|
4714
|
+
}
|
|
4715
|
+
function withoutTenant(data) {
|
|
4716
|
+
delete data.tenant_id;
|
|
4717
|
+
return data;
|
|
4718
|
+
}
|
|
4661
4719
|
function zodType(field) {
|
|
4662
4720
|
let t;
|
|
4663
4721
|
switch (field.type) {
|
|
@@ -4715,7 +4773,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4715
4773
|
`;
|
|
4716
4774
|
await sql2`
|
|
4717
4775
|
INSERT INTO "_tenant_members" ("tenant_id", "user_id", "role")
|
|
4718
|
-
VALUES (${tenant2.id}, ${ctx
|
|
4776
|
+
VALUES (${tenant2.id}, ${userId(ctx)}, 'admin')
|
|
4719
4777
|
`;
|
|
4720
4778
|
return Response.json(tenant2, { status: 201 });
|
|
4721
4779
|
});
|
|
@@ -4723,7 +4781,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4723
4781
|
const rows = await sql2`
|
|
4724
4782
|
SELECT t.*, tm.role FROM "_tenants" t
|
|
4725
4783
|
JOIN "_tenant_members" tm ON tm.tenant_id = t.id
|
|
4726
|
-
WHERE tm.user_id = ${ctx
|
|
4784
|
+
WHERE tm.user_id = ${userId(ctx)}
|
|
4727
4785
|
`;
|
|
4728
4786
|
return Response.json(rows);
|
|
4729
4787
|
});
|
|
@@ -4732,7 +4790,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4732
4790
|
if (err) return err;
|
|
4733
4791
|
const { email, role = "member" } = await req.json();
|
|
4734
4792
|
const [user2] = await sql2`
|
|
4735
|
-
SELECT id FROM ${sql2
|
|
4793
|
+
SELECT id FROM ${tableRef(sql2, usersTable)} WHERE "email" = ${email} LIMIT 1
|
|
4736
4794
|
`;
|
|
4737
4795
|
if (!user2) return Response.json({ error: "User not found" }, { status: 404 });
|
|
4738
4796
|
const [existing] = await sql2`
|
|
@@ -4749,10 +4807,10 @@ function buildRouter(sql2, usersTable) {
|
|
|
4749
4807
|
r.delete("/sys/tenants/members/:userId", async (req, ctx) => {
|
|
4750
4808
|
const err = requireAdmin(ctx);
|
|
4751
4809
|
if (err) return err;
|
|
4752
|
-
const
|
|
4810
|
+
const userId2 = parseInt(ctx.params.userId, 10);
|
|
4753
4811
|
await sql2`
|
|
4754
4812
|
DELETE FROM "_tenant_members"
|
|
4755
|
-
WHERE tenant_id = ${ctx.tenant.id} AND user_id = ${
|
|
4813
|
+
WHERE tenant_id = ${ctx.tenant.id} AND user_id = ${userId2}
|
|
4756
4814
|
`;
|
|
4757
4815
|
return Response.json({ ok: true });
|
|
4758
4816
|
});
|
|
@@ -4778,7 +4836,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4778
4836
|
}
|
|
4779
4837
|
const [row] = await sql2`
|
|
4780
4838
|
INSERT INTO "_user_tables" ("tenant_id", "slug", "label", "fields")
|
|
4781
|
-
VALUES (${ctx.tenant.id}, ${body.slug}, ${body.label || ""}, ${body.fields})
|
|
4839
|
+
VALUES (${ctx.tenant.id}, ${body.slug}, ${body.label || ""}, ${asJson(body.fields)})
|
|
4782
4840
|
RETURNING *
|
|
4783
4841
|
`;
|
|
4784
4842
|
return Response.json(row, { status: 201 });
|
|
@@ -4813,7 +4871,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4813
4871
|
const merged = [...table.fields, ...newFields];
|
|
4814
4872
|
await sql2`
|
|
4815
4873
|
UPDATE "_user_tables"
|
|
4816
|
-
SET fields = ${merged}
|
|
4874
|
+
SET fields = ${asJson(merged)}
|
|
4817
4875
|
WHERE id = ${table.id}
|
|
4818
4876
|
`;
|
|
4819
4877
|
return Response.json({ ...table, fields: merged });
|
|
@@ -4861,7 +4919,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4861
4919
|
[ctx.tenant.id]
|
|
4862
4920
|
)
|
|
4863
4921
|
]);
|
|
4864
|
-
return Response.json({ rows: rows2, count:
|
|
4922
|
+
return Response.json({ rows: rows2, count: extractCount(countResult2) });
|
|
4865
4923
|
} catch {
|
|
4866
4924
|
return Response.json({ error: "Invalid search_vector" }, { status: 400 });
|
|
4867
4925
|
}
|
|
@@ -4877,7 +4935,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4877
4935
|
[ctx.tenant.id]
|
|
4878
4936
|
)
|
|
4879
4937
|
]);
|
|
4880
|
-
return Response.json({ rows, count:
|
|
4938
|
+
return Response.json({ rows, count: extractCount(countResult) });
|
|
4881
4939
|
});
|
|
4882
4940
|
r.post("/:_slug", async (req, ctx) => {
|
|
4883
4941
|
const table = await resolveTable(ctx);
|
|
@@ -4888,11 +4946,10 @@ function buildRouter(sql2, usersTable) {
|
|
|
4888
4946
|
shape[f.name] = zodType(f);
|
|
4889
4947
|
}
|
|
4890
4948
|
const zodSchema = z3.object(shape);
|
|
4891
|
-
const parsed = zodSchema.parse(data);
|
|
4892
|
-
parsed.tenant_id = ctx.tenant.id;
|
|
4949
|
+
const parsed = withTenant(ctx, zodSchema.parse(data));
|
|
4893
4950
|
delete parsed.id;
|
|
4894
4951
|
const name = internalName(ctx);
|
|
4895
|
-
const [row] = await sql2`INSERT INTO ${sql2
|
|
4952
|
+
const [row] = await sql2`INSERT INTO ${tableRef(sql2, name)} ${sql2(parsed)} RETURNING *`;
|
|
4896
4953
|
return Response.json(row, { status: 201 });
|
|
4897
4954
|
});
|
|
4898
4955
|
r.get("/:_slug/:id", async (_req, ctx) => {
|
|
@@ -4900,7 +4957,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4900
4957
|
if (!table) return Response.json({ error: "Table not found" }, { status: 404 });
|
|
4901
4958
|
const name = internalName(ctx);
|
|
4902
4959
|
const [row] = await sql2`
|
|
4903
|
-
SELECT * FROM ${sql2
|
|
4960
|
+
SELECT * FROM ${tableRef(sql2, name)}
|
|
4904
4961
|
WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
|
|
4905
4962
|
LIMIT 1
|
|
4906
4963
|
`;
|
|
@@ -4916,13 +4973,12 @@ function buildRouter(sql2, usersTable) {
|
|
|
4916
4973
|
shape[f.name] = zodType(f);
|
|
4917
4974
|
}
|
|
4918
4975
|
const zodSchema = z3.object(shape).partial();
|
|
4919
|
-
const parsed = zodSchema.parse(data);
|
|
4976
|
+
const parsed = withoutTenant(zodSchema.parse(data));
|
|
4920
4977
|
delete parsed.id;
|
|
4921
|
-
delete parsed.tenant_id;
|
|
4922
4978
|
if (Object.keys(parsed).length === 0) {
|
|
4923
4979
|
const name2 = internalName(ctx);
|
|
4924
4980
|
const [row2] = await sql2`
|
|
4925
|
-
SELECT * FROM ${sql2
|
|
4981
|
+
SELECT * FROM ${tableRef(sql2, name2)}
|
|
4926
4982
|
WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
|
|
4927
4983
|
LIMIT 1
|
|
4928
4984
|
`;
|
|
@@ -4930,7 +4986,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4930
4986
|
}
|
|
4931
4987
|
const name = internalName(ctx);
|
|
4932
4988
|
const [row] = await sql2`
|
|
4933
|
-
UPDATE ${sql2
|
|
4989
|
+
UPDATE ${tableRef(sql2, name)} SET ${sql2(parsed)}
|
|
4934
4990
|
WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
|
|
4935
4991
|
RETURNING *
|
|
4936
4992
|
`;
|
|
@@ -4940,7 +4996,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4940
4996
|
r.delete("/:_slug/:id", async (_req, ctx) => {
|
|
4941
4997
|
const name = internalName(ctx);
|
|
4942
4998
|
const result = await sql2`
|
|
4943
|
-
DELETE FROM ${sql2
|
|
4999
|
+
DELETE FROM ${tableRef(sql2, name)}
|
|
4944
5000
|
WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
|
|
4945
5001
|
RETURNING 1
|
|
4946
5002
|
`;
|
|
@@ -4981,7 +5037,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4981
5037
|
[parentId2, ctx.tenant.id]
|
|
4982
5038
|
)
|
|
4983
5039
|
]);
|
|
4984
|
-
return Response.json({ rows, count:
|
|
5040
|
+
return Response.json({ rows, count: extractCount(countResult) });
|
|
4985
5041
|
}
|
|
4986
5042
|
return Response.json({ error: "POST not supported on M2M nested routes" }, { status: 400 });
|
|
4987
5043
|
}
|
|
@@ -4998,7 +5054,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4998
5054
|
[parentId, ctx.tenant.id]
|
|
4999
5055
|
)
|
|
5000
5056
|
]);
|
|
5001
|
-
return Response.json({ rows, count:
|
|
5057
|
+
return Response.json({ rows, count: extractCount(countResult) });
|
|
5002
5058
|
}
|
|
5003
5059
|
const body = await req.json();
|
|
5004
5060
|
const shape = {};
|
|
@@ -5010,7 +5066,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
5010
5066
|
parsed.tenant_id = ctx.tenant.id;
|
|
5011
5067
|
parsed[relField.name] = parentId;
|
|
5012
5068
|
delete parsed.id;
|
|
5013
|
-
const [row] = await sql2`INSERT INTO ${sql2
|
|
5069
|
+
const [row] = await sql2`INSERT INTO ${tableRef(sql2, childName)} ${sql2(parsed)} RETURNING *`;
|
|
5014
5070
|
return Response.json(row, { status: 201 });
|
|
5015
5071
|
}
|
|
5016
5072
|
r.get("/:_slug/:id/:_nested", async (req, ctx) => handleNested(req, ctx, "GET"));
|
|
@@ -5080,7 +5136,6 @@ function buildObjectType(table, ctx) {
|
|
|
5080
5136
|
if (other.id === table.id) continue;
|
|
5081
5137
|
const relField = findRelation(other.fields, table.slug);
|
|
5082
5138
|
if (relField) {
|
|
5083
|
-
const otherName = pascalCase(other.slug);
|
|
5084
5139
|
fields[other.slug] = {
|
|
5085
5140
|
type: new GraphQLList(new GraphQLNonNull(buildObjectType(other, ctx))),
|
|
5086
5141
|
args: {
|
|
@@ -5595,7 +5650,7 @@ async function loadAgent(agents, agentId) {
|
|
|
5595
5650
|
return row ?? null;
|
|
5596
5651
|
}
|
|
5597
5652
|
function createRunner(deps) {
|
|
5598
|
-
const { sql: sql2, agents, runs, provider,
|
|
5653
|
+
const { sql: sql2, agents, runs, provider, userTools } = deps;
|
|
5599
5654
|
function truncate(s, max = 200) {
|
|
5600
5655
|
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
5601
5656
|
}
|
|
@@ -5905,32 +5960,32 @@ function createWSHandler(deps) {
|
|
|
5905
5960
|
prefix: "messager:"
|
|
5906
5961
|
});
|
|
5907
5962
|
const userConnections = /* @__PURE__ */ new Map();
|
|
5908
|
-
function trackConnection(
|
|
5909
|
-
let conns = userConnections.get(
|
|
5963
|
+
function trackConnection(userId2, ws) {
|
|
5964
|
+
let conns = userConnections.get(userId2);
|
|
5910
5965
|
if (!conns) {
|
|
5911
5966
|
conns = /* @__PURE__ */ new Set();
|
|
5912
|
-
userConnections.set(
|
|
5967
|
+
userConnections.set(userId2, conns);
|
|
5913
5968
|
}
|
|
5914
5969
|
conns.add(ws);
|
|
5915
5970
|
}
|
|
5916
5971
|
function untrackConnection(ws) {
|
|
5917
|
-
for (const [
|
|
5972
|
+
for (const [userId2, conns] of userConnections) {
|
|
5918
5973
|
conns.delete(ws);
|
|
5919
|
-
if (conns.size === 0) userConnections.delete(
|
|
5974
|
+
if (conns.size === 0) userConnections.delete(userId2);
|
|
5920
5975
|
}
|
|
5921
5976
|
}
|
|
5922
5977
|
return {
|
|
5923
5978
|
handler: {
|
|
5924
5979
|
open(ws, ctx) {
|
|
5925
|
-
const
|
|
5926
|
-
if (!
|
|
5980
|
+
const userId2 = ctx.user?.id;
|
|
5981
|
+
if (!userId2) {
|
|
5927
5982
|
ws.close(4001, "Unauthorized");
|
|
5928
5983
|
return;
|
|
5929
5984
|
}
|
|
5930
5985
|
},
|
|
5931
5986
|
async message(ws, ctx, data) {
|
|
5932
|
-
const
|
|
5933
|
-
if (!
|
|
5987
|
+
const userId2 = ctx.user?.id;
|
|
5988
|
+
if (!userId2) return;
|
|
5934
5989
|
let msg;
|
|
5935
5990
|
try {
|
|
5936
5991
|
msg = JSON.parse(data.toString());
|
|
@@ -5944,12 +5999,12 @@ function createWSHandler(deps) {
|
|
|
5944
5999
|
if (!content || !channel_id) return;
|
|
5945
6000
|
const [row] = await sql2`
|
|
5946
6001
|
INSERT INTO "_messages" ("channel_id", "sender_id", "sender_type", "content")
|
|
5947
|
-
VALUES (${channel_id}, ${
|
|
6002
|
+
VALUES (${channel_id}, ${userId2}, 'user', ${content})
|
|
5948
6003
|
RETURNING *
|
|
5949
6004
|
`;
|
|
5950
6005
|
const message = row;
|
|
5951
6006
|
hub.join(`messager:${channel_id}`, ws);
|
|
5952
|
-
trackConnection(
|
|
6007
|
+
trackConnection(userId2, ws);
|
|
5953
6008
|
broadcastToChannel(hub, channel_id, { type: "message", data: message });
|
|
5954
6009
|
if (agents) {
|
|
5955
6010
|
const insertMsg = (data2) => sql2`
|
|
@@ -5968,7 +6023,7 @@ function createWSHandler(deps) {
|
|
|
5968
6023
|
broadcastToChannel(hub, channel_id, {
|
|
5969
6024
|
type: "typing",
|
|
5970
6025
|
channel_id,
|
|
5971
|
-
user_id:
|
|
6026
|
+
user_id: userId2,
|
|
5972
6027
|
is_typing: is_typing ?? false
|
|
5973
6028
|
});
|
|
5974
6029
|
break;
|
|
@@ -5979,12 +6034,12 @@ function createWSHandler(deps) {
|
|
|
5979
6034
|
await sql2`
|
|
5980
6035
|
UPDATE "_channel_members"
|
|
5981
6036
|
SET last_read_id = ${last_message_id}, last_read_at = NOW()
|
|
5982
|
-
WHERE channel_id = ${channel_id} AND member_id = ${
|
|
6037
|
+
WHERE channel_id = ${channel_id} AND member_id = ${userId2} AND member_type = 'user'
|
|
5983
6038
|
`;
|
|
5984
6039
|
broadcastToChannel(hub, channel_id, {
|
|
5985
6040
|
type: "read",
|
|
5986
6041
|
channel_id,
|
|
5987
|
-
user_id:
|
|
6042
|
+
user_id: userId2,
|
|
5988
6043
|
last_message_id
|
|
5989
6044
|
});
|
|
5990
6045
|
break;
|
|
@@ -6038,7 +6093,7 @@ function buildRouter3(deps) {
|
|
|
6038
6093
|
return Response.json(channel, { status: 201 });
|
|
6039
6094
|
});
|
|
6040
6095
|
r.get("/channels", async (_req, ctx) => {
|
|
6041
|
-
const
|
|
6096
|
+
const userId2 = ctx.user?.id ?? 1;
|
|
6042
6097
|
const rows = await sql2`
|
|
6043
6098
|
SELECT c.*, (
|
|
6044
6099
|
SELECT content FROM "_messages"
|
|
@@ -6047,7 +6102,7 @@ function buildRouter3(deps) {
|
|
|
6047
6102
|
) AS last_message
|
|
6048
6103
|
FROM "_channels" c
|
|
6049
6104
|
JOIN "_channel_members" m ON m.channel_id = c.id
|
|
6050
|
-
WHERE m.member_id = ${
|
|
6105
|
+
WHERE m.member_id = ${userId2} AND m.member_type = 'user'
|
|
6051
6106
|
ORDER BY c.created_at DESC
|
|
6052
6107
|
`;
|
|
6053
6108
|
return Response.json(rows);
|
|
@@ -6120,9 +6175,9 @@ function buildRouter3(deps) {
|
|
|
6120
6175
|
r.post("/channels/:id/read", async (req, ctx) => {
|
|
6121
6176
|
const channelId = parseInt(ctx.params.id, 10);
|
|
6122
6177
|
const body = await req.json();
|
|
6123
|
-
const
|
|
6178
|
+
const userId2 = body.user_id ?? ctx.user?.id ?? 1;
|
|
6124
6179
|
await members.updateMany(
|
|
6125
|
-
[eq("channel_id", channelId), eq("member_id",
|
|
6180
|
+
[eq("channel_id", channelId), eq("member_id", userId2), eq("member_type", "user")],
|
|
6126
6181
|
{ last_read_id: body.last_message_id }
|
|
6127
6182
|
);
|
|
6128
6183
|
return Response.json({ ok: true });
|
|
@@ -6294,7 +6349,7 @@ function createGateway(config, getPort) {
|
|
|
6294
6349
|
}
|
|
6295
6350
|
|
|
6296
6351
|
// deploy/manager.ts
|
|
6297
|
-
import
|
|
6352
|
+
import crypto7 from "node:crypto";
|
|
6298
6353
|
|
|
6299
6354
|
// deploy/process.ts
|
|
6300
6355
|
import { fork } from "node:child_process";
|
|
@@ -6347,27 +6402,27 @@ async function healthCheck(port, path2 = "/") {
|
|
|
6347
6402
|
// deploy/manager.ts
|
|
6348
6403
|
function createManager(config, apps, manager) {
|
|
6349
6404
|
const router = new Router();
|
|
6350
|
-
const
|
|
6405
|
+
const auth = (req, ctx, next) => {
|
|
6351
6406
|
if (!config.deployToken) return next(req, ctx);
|
|
6352
6407
|
const header = req.headers.get("authorization") ?? "";
|
|
6353
6408
|
const token = header.replace("Bearer ", "");
|
|
6354
6409
|
const tokenBuf = Buffer.from(token);
|
|
6355
6410
|
const secretBuf = Buffer.from(config.deployToken);
|
|
6356
|
-
if (tokenBuf.length !== secretBuf.length || !
|
|
6411
|
+
if (tokenBuf.length !== secretBuf.length || !crypto7.timingSafeEqual(tokenBuf, secretBuf)) {
|
|
6357
6412
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
6358
6413
|
}
|
|
6359
6414
|
return next(req, ctx);
|
|
6360
6415
|
};
|
|
6361
|
-
router.get("/apps",
|
|
6416
|
+
router.get("/apps", auth, () => {
|
|
6362
6417
|
const list = Array.from(apps.values()).map((a) => a.status);
|
|
6363
6418
|
return Response.json(list);
|
|
6364
6419
|
});
|
|
6365
|
-
router.get("/apps/:name",
|
|
6420
|
+
router.get("/apps/:name", auth, (req, ctx) => {
|
|
6366
6421
|
const app = apps.get(ctx.params.name);
|
|
6367
6422
|
if (!app) return new Response("Not Found", { status: 404 });
|
|
6368
6423
|
return Response.json(app.status);
|
|
6369
6424
|
});
|
|
6370
|
-
router.post("/apps/:name/deploy",
|
|
6425
|
+
router.post("/apps/:name/deploy", auth, async (req, ctx) => {
|
|
6371
6426
|
const app = apps.get(ctx.params.name);
|
|
6372
6427
|
if (!app) return new Response("Not Found", { status: 404 });
|
|
6373
6428
|
try {
|
|
@@ -6378,7 +6433,7 @@ function createManager(config, apps, manager) {
|
|
|
6378
6433
|
return Response.json({ error: msg }, { status: 500 });
|
|
6379
6434
|
}
|
|
6380
6435
|
});
|
|
6381
|
-
router.post("/apps/:name/restart",
|
|
6436
|
+
router.post("/apps/:name/restart", auth, async (req, ctx) => {
|
|
6382
6437
|
const app = apps.get(ctx.params.name);
|
|
6383
6438
|
if (!app) return new Response("Not Found", { status: 404 });
|
|
6384
6439
|
try {
|
|
@@ -6389,7 +6444,7 @@ function createManager(config, apps, manager) {
|
|
|
6389
6444
|
return Response.json({ error: msg }, { status: 500 });
|
|
6390
6445
|
}
|
|
6391
6446
|
});
|
|
6392
|
-
router.post("/apps/:name/stop",
|
|
6447
|
+
router.post("/apps/:name/stop", auth, async (req, ctx) => {
|
|
6393
6448
|
const app = apps.get(ctx.params.name);
|
|
6394
6449
|
if (!app) return new Response("Not Found", { status: 404 });
|
|
6395
6450
|
if (app.process) {
|
|
@@ -6399,7 +6454,7 @@ function createManager(config, apps, manager) {
|
|
|
6399
6454
|
app.status = { ...app.status, status: "stopped", pid: void 0 };
|
|
6400
6455
|
return Response.json({ success: true });
|
|
6401
6456
|
});
|
|
6402
|
-
router.post("/apps/:name/start",
|
|
6457
|
+
router.post("/apps/:name/start", auth, async (req, ctx) => {
|
|
6403
6458
|
const app = apps.get(ctx.params.name);
|
|
6404
6459
|
if (!app) return new Response("Not Found", { status: 404 });
|
|
6405
6460
|
try {
|
|
@@ -6410,7 +6465,7 @@ function createManager(config, apps, manager) {
|
|
|
6410
6465
|
return Response.json({ error: msg }, { status: 500 });
|
|
6411
6466
|
}
|
|
6412
6467
|
});
|
|
6413
|
-
router.get("/apps/:name/logs",
|
|
6468
|
+
router.get("/apps/:name/logs", auth, (req, ctx) => {
|
|
6414
6469
|
const app = apps.get(ctx.params.name);
|
|
6415
6470
|
if (!app) return new Response("Not Found", { status: 404 });
|
|
6416
6471
|
let index = app.logs.length;
|
|
@@ -6462,9 +6517,9 @@ function defineConfig(config) {
|
|
|
6462
6517
|
async function deploy(config) {
|
|
6463
6518
|
const apps = /* @__PURE__ */ new Map();
|
|
6464
6519
|
let httpServer;
|
|
6465
|
-
async function forkAndCheck(name, cwd, entry, port,
|
|
6520
|
+
async function forkAndCheck(name, cwd, entry, port, env2, onLog, healthEndpoint) {
|
|
6466
6521
|
try {
|
|
6467
|
-
const mp = forkApp({ cwd, entry, port, env, onLog });
|
|
6522
|
+
const mp = forkApp({ cwd, entry, port, env: env2, onLog });
|
|
6468
6523
|
onLog(`[deploy] forked ${name} (pid ${mp.child.pid}) on port ${mp.port}`);
|
|
6469
6524
|
const healthy = await healthCheck(port, healthEndpoint ?? "/");
|
|
6470
6525
|
if (healthy) onLog(`[deploy] health check passed`);
|
|
@@ -6903,7 +6958,7 @@ async function compileHotComponent(path2) {
|
|
|
6903
6958
|
// stream.ts
|
|
6904
6959
|
import { TextDecoder as TextDecoder2, TextEncoder as TextEncoder2 } from "node:util";
|
|
6905
6960
|
var _publicEnv = null;
|
|
6906
|
-
function
|
|
6961
|
+
function getPublicEnv2() {
|
|
6907
6962
|
if (_publicEnv) return _publicEnv;
|
|
6908
6963
|
_publicEnv = {};
|
|
6909
6964
|
for (const key of Object.keys(process.env)) {
|
|
@@ -6954,7 +7009,7 @@ function buildHeadPayload(opts) {
|
|
|
6954
7009
|
}
|
|
6955
7010
|
ctxData.user = safeUser;
|
|
6956
7011
|
}
|
|
6957
|
-
const publicEnv =
|
|
7012
|
+
const publicEnv = getPublicEnv2();
|
|
6958
7013
|
if (Object.keys(publicEnv).length > 0) {
|
|
6959
7014
|
ctxData.env = publicEnv;
|
|
6960
7015
|
}
|
|
@@ -7312,8 +7367,9 @@ function errorBoundary(errorPath) {
|
|
|
7312
7367
|
const mod = await compile(errorPath);
|
|
7313
7368
|
const ErrorComponent = mod.default;
|
|
7314
7369
|
if (!ErrorComponent) throw err;
|
|
7315
|
-
const
|
|
7316
|
-
const
|
|
7370
|
+
const ctx2 = ctx;
|
|
7371
|
+
const layouts = (ctx2.layoutStack || []).map((l) => l.component);
|
|
7372
|
+
const base = (ctx2.mountPath || "").replace(/\/$/, "");
|
|
7317
7373
|
let element = createElement2(ErrorComponent, {
|
|
7318
7374
|
error: err instanceof Error ? err : new Error(String(err)),
|
|
7319
7375
|
reset: () => {
|
|
@@ -7323,10 +7379,10 @@ function errorBoundary(errorPath) {
|
|
|
7323
7379
|
const { renderToReadableStream } = await import("react-dom/server");
|
|
7324
7380
|
const stream = await renderToReadableStream(element);
|
|
7325
7381
|
return streamResponse(stream, {
|
|
7326
|
-
ctx,
|
|
7382
|
+
ctx: ctx2,
|
|
7327
7383
|
base,
|
|
7328
7384
|
isDev: isDev(),
|
|
7329
|
-
tailwind:
|
|
7385
|
+
tailwind: ctx2.tailwind,
|
|
7330
7386
|
status: 500
|
|
7331
7387
|
});
|
|
7332
7388
|
}
|
|
@@ -7662,7 +7718,7 @@ function ssr(opts) {
|
|
|
7662
7718
|
}
|
|
7663
7719
|
|
|
7664
7720
|
// opencode/session.ts
|
|
7665
|
-
import { randomUUID as
|
|
7721
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7666
7722
|
import { join as join6 } from "node:path";
|
|
7667
7723
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
7668
7724
|
var sessions = pgTable("_opencode_sessions", {
|
|
@@ -7688,7 +7744,7 @@ var messages = pgTable("_opencode_messages", {
|
|
|
7688
7744
|
created_at: timestamptz("created_at")
|
|
7689
7745
|
});
|
|
7690
7746
|
async function createSession(sql2, opts, cwd, mountPath) {
|
|
7691
|
-
const id2 =
|
|
7747
|
+
const id2 = randomUUID2();
|
|
7692
7748
|
const ws = computeSessionWorkspace(cwd, mountPath, id2);
|
|
7693
7749
|
await mkdir2(ws, { recursive: true });
|
|
7694
7750
|
const [row] = await sql2`
|
|
@@ -7706,10 +7762,10 @@ async function getSession(sql2, id2) {
|
|
|
7706
7762
|
const { data: rows } = await sessions.readMany(sql2, { id: id2, active: true });
|
|
7707
7763
|
return rows[0] ?? null;
|
|
7708
7764
|
}
|
|
7709
|
-
async function listSessions(sql2,
|
|
7765
|
+
async function listSessions(sql2, userId2) {
|
|
7710
7766
|
const opts = { orderBy: { updated_at: "desc" } };
|
|
7711
|
-
if (
|
|
7712
|
-
const { data: rows2 } = await sessions.readMany(sql2, { user_id:
|
|
7767
|
+
if (userId2 !== void 0) {
|
|
7768
|
+
const { data: rows2 } = await sessions.readMany(sql2, { user_id: userId2, active: true }, opts);
|
|
7713
7769
|
return rows2;
|
|
7714
7770
|
}
|
|
7715
7771
|
const { data: rows } = await sessions.readMany(sql2, { active: true }, opts);
|
|
@@ -8330,9 +8386,9 @@ function createWSHandler2(deps) {
|
|
|
8330
8386
|
const { sql: sql2, model, workspace, systemPrompt, skills, skillsRegistry, permissions: permissions2, pendingQuestions } = deps;
|
|
8331
8387
|
return {
|
|
8332
8388
|
open(ws, ctx) {
|
|
8333
|
-
const
|
|
8389
|
+
const userId2 = ctx.user?.id ?? 0;
|
|
8334
8390
|
const mountPath = ctx.mountPath ?? "";
|
|
8335
|
-
clients2.set(ws, { userId, mountPath });
|
|
8391
|
+
clients2.set(ws, { userId: userId2, mountPath });
|
|
8336
8392
|
},
|
|
8337
8393
|
async message(ws, ctx, data) {
|
|
8338
8394
|
const client = clients2.get(ws);
|
|
@@ -8841,7 +8897,7 @@ function analytics(options) {
|
|
|
8841
8897
|
if (pg) await migratePg(pg.sql, pg.table);
|
|
8842
8898
|
};
|
|
8843
8899
|
const close = async () => {
|
|
8844
|
-
|
|
8900
|
+
store2?.stopCleanup();
|
|
8845
8901
|
};
|
|
8846
8902
|
const mod = r;
|
|
8847
8903
|
mod.middleware = middleware;
|
|
@@ -8860,30 +8916,37 @@ function makeSetTheme(cookie, location) {
|
|
|
8860
8916
|
}
|
|
8861
8917
|
function theme(options) {
|
|
8862
8918
|
const opts = { default: "system", cookie: "theme", ...options };
|
|
8863
|
-
|
|
8864
|
-
const url = new URL(req.url);
|
|
8865
|
-
const match = url.pathname.match(/^\/__theme\/([\w-]+)$/);
|
|
8866
|
-
if (match && req.method === "GET") {
|
|
8867
|
-
const value = match[1];
|
|
8868
|
-
const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
8869
|
-
const accept = req.headers.get("accept") ?? "";
|
|
8870
|
-
if (accept.includes("application/json")) {
|
|
8871
|
-
return Response.json({ ok: true, theme: value }, { headers: { "Set-Cookie": cookie } });
|
|
8872
|
-
}
|
|
8873
|
-
const referer = req.headers.get("referer") || "/";
|
|
8874
|
-
return new Response(null, { status: 302, headers: { Location: referer, "Set-Cookie": cookie } });
|
|
8875
|
-
}
|
|
8919
|
+
const mw = async (req, ctx, next) => {
|
|
8876
8920
|
let themeValue = opts.default;
|
|
8877
8921
|
if (opts.cookie) {
|
|
8878
8922
|
const fromCookie = getCookies(req)[opts.cookie];
|
|
8879
8923
|
if (fromCookie) themeValue = fromCookie;
|
|
8880
8924
|
}
|
|
8925
|
+
;
|
|
8881
8926
|
ctx.theme = {
|
|
8882
8927
|
value: themeValue,
|
|
8883
8928
|
set: makeSetTheme(opts.cookie, req.headers.get("referer") || "/")
|
|
8884
8929
|
};
|
|
8885
8930
|
return next(req, ctx);
|
|
8886
8931
|
};
|
|
8932
|
+
class ThemeRouter extends Router {
|
|
8933
|
+
middleware() {
|
|
8934
|
+
return mw;
|
|
8935
|
+
}
|
|
8936
|
+
}
|
|
8937
|
+
const router = new ThemeRouter();
|
|
8938
|
+
router.get("/__theme/:value", (req) => {
|
|
8939
|
+
const url = new URL(req.url);
|
|
8940
|
+
const value = url.pathname.split("/__theme/")[1] ?? "";
|
|
8941
|
+
const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
8942
|
+
const accept = req.headers.get("accept") ?? "";
|
|
8943
|
+
if (accept.includes("application/json")) {
|
|
8944
|
+
return Response.json({ ok: true, theme: value }, { headers: { "Set-Cookie": cookie } });
|
|
8945
|
+
}
|
|
8946
|
+
const referer = req.headers.get("referer") || "/";
|
|
8947
|
+
return new Response(null, { status: 302, headers: { Location: referer, "Set-Cookie": cookie } });
|
|
8948
|
+
});
|
|
8949
|
+
return router;
|
|
8887
8950
|
}
|
|
8888
8951
|
|
|
8889
8952
|
// i18n.ts
|
|
@@ -8949,23 +9012,7 @@ function i18n(options) {
|
|
|
8949
9012
|
}
|
|
8950
9013
|
return opts.default;
|
|
8951
9014
|
}
|
|
8952
|
-
|
|
8953
|
-
const url = new URL(req.url);
|
|
8954
|
-
const match = url.pathname.match(/^\/__lang\/([\w-]+)$/);
|
|
8955
|
-
if (match && req.method === "GET") {
|
|
8956
|
-
const value = match[1];
|
|
8957
|
-
const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
8958
|
-
const messages2 = await loadMessages(value);
|
|
8959
|
-
const accept = req.headers.get("accept") ?? "";
|
|
8960
|
-
if (accept.includes("application/json")) {
|
|
8961
|
-
return Response.json(
|
|
8962
|
-
{ ok: true, locale: value, messages: Object.keys(messages2).length > 0 ? messages2 : void 0 },
|
|
8963
|
-
{ headers: { "Set-Cookie": cookie } }
|
|
8964
|
-
);
|
|
8965
|
-
}
|
|
8966
|
-
const referer = req.headers.get("referer") || "/";
|
|
8967
|
-
return new Response(null, { status: 302, headers: { Location: referer, "Set-Cookie": cookie } });
|
|
8968
|
-
}
|
|
9015
|
+
const mw = async (req, ctx, next) => {
|
|
8969
9016
|
const locale = detectLocale(req);
|
|
8970
9017
|
const msgs = await loadMessages(locale);
|
|
8971
9018
|
ctx.i18n = {
|
|
@@ -8980,6 +9027,28 @@ function i18n(options) {
|
|
|
8980
9027
|
};
|
|
8981
9028
|
return next(req, ctx);
|
|
8982
9029
|
};
|
|
9030
|
+
class I18nRouter extends Router {
|
|
9031
|
+
middleware() {
|
|
9032
|
+
return mw;
|
|
9033
|
+
}
|
|
9034
|
+
}
|
|
9035
|
+
const router = new I18nRouter();
|
|
9036
|
+
router.get("/__lang/:locale", async (req) => {
|
|
9037
|
+
const url = new URL(req.url);
|
|
9038
|
+
const value = url.pathname.split("/__lang/")[1] ?? "";
|
|
9039
|
+
const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
9040
|
+
const messages2 = await loadMessages(value);
|
|
9041
|
+
const accept = req.headers.get("accept") ?? "";
|
|
9042
|
+
if (accept.includes("application/json")) {
|
|
9043
|
+
return Response.json(
|
|
9044
|
+
{ ok: true, locale: value, messages: Object.keys(messages2).length > 0 ? messages2 : void 0 },
|
|
9045
|
+
{ headers: { "Set-Cookie": cookie } }
|
|
9046
|
+
);
|
|
9047
|
+
}
|
|
9048
|
+
const referer = req.headers.get("referer") || "/";
|
|
9049
|
+
return new Response(null, { status: 302, headers: { Location: referer, "Set-Cookie": cookie } });
|
|
9050
|
+
});
|
|
9051
|
+
return router;
|
|
8983
9052
|
}
|
|
8984
9053
|
|
|
8985
9054
|
// flash.ts
|
|
@@ -9209,13 +9278,13 @@ function csrf(options) {
|
|
|
9209
9278
|
let token = getCookies(req)[cookieName];
|
|
9210
9279
|
if (!token) {
|
|
9211
9280
|
token = crypto.randomUUID();
|
|
9212
|
-
ctx.
|
|
9281
|
+
ctx.csrf = { token };
|
|
9213
9282
|
} else {
|
|
9214
9283
|
;
|
|
9215
|
-
ctx.
|
|
9284
|
+
ctx.csrf = { token };
|
|
9216
9285
|
}
|
|
9217
9286
|
const res = await next(req, ctx);
|
|
9218
|
-
const tokenToSet = ctx.
|
|
9287
|
+
const tokenToSet = ctx.csrf?.token;
|
|
9219
9288
|
if (tokenToSet && !getCookies(req)[cookieName]) {
|
|
9220
9289
|
return setCookie(res, cookieName, tokenToSet, {
|
|
9221
9290
|
httpOnly: true,
|
|
@@ -9400,7 +9469,7 @@ function logdb(options) {
|
|
|
9400
9469
|
}
|
|
9401
9470
|
|
|
9402
9471
|
// iii/client.ts
|
|
9403
|
-
import
|
|
9472
|
+
import crypto8 from "node:crypto";
|
|
9404
9473
|
|
|
9405
9474
|
// iii/stream.ts
|
|
9406
9475
|
function notify(channels, stream, group, item, event, data) {
|
|
@@ -9929,7 +9998,7 @@ function iii(opts = {}) {
|
|
|
9929
9998
|
registerBuiltin("stream::send", (p) => stream.send(p.stream_name, p.group_id, p.type, p.data, p.id));
|
|
9930
9999
|
registerBuiltin("stream::update", (p) => stream.update(p.stream_name, p.group_id, p.item_id, p.ops));
|
|
9931
10000
|
function addLocalWorker(worker) {
|
|
9932
|
-
const workerId =
|
|
10001
|
+
const workerId = crypto8.randomUUID();
|
|
9933
10002
|
const reg = {
|
|
9934
10003
|
id: workerId,
|
|
9935
10004
|
name: worker.name,
|
|
@@ -9944,7 +10013,7 @@ function iii(opts = {}) {
|
|
|
9944
10013
|
const triggerIds = [];
|
|
9945
10014
|
for (const t of worker.getTriggers()) {
|
|
9946
10015
|
if (t.input.function_id === fn.id) {
|
|
9947
|
-
const tid =
|
|
10016
|
+
const tid = crypto8.randomUUID();
|
|
9948
10017
|
triggers.set(tid, {
|
|
9949
10018
|
id: tid,
|
|
9950
10019
|
type: t.input.type,
|
|
@@ -9973,7 +10042,7 @@ function iii(opts = {}) {
|
|
|
9973
10042
|
if (!worker) return;
|
|
9974
10043
|
const handler = async (payload) => {
|
|
9975
10044
|
if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
|
|
9976
|
-
const invocationId =
|
|
10045
|
+
const invocationId = crypto8.randomUUID();
|
|
9977
10046
|
return new Promise((resolve14, reject) => {
|
|
9978
10047
|
const timer = setTimeout(() => {
|
|
9979
10048
|
pending.delete(invocationId);
|
|
@@ -10008,7 +10077,7 @@ function iii(opts = {}) {
|
|
|
10008
10077
|
let engineRef = null;
|
|
10009
10078
|
const wsHandler = createWsHandler({
|
|
10010
10079
|
registerRemoteWorker(ws, name) {
|
|
10011
|
-
const id2 =
|
|
10080
|
+
const id2 = crypto8.randomUUID();
|
|
10012
10081
|
workers.set(id2, { id: id2, name, ws, functions: [], triggers: [] });
|
|
10013
10082
|
return id2;
|
|
10014
10083
|
},
|
|
@@ -10019,7 +10088,7 @@ function iii(opts = {}) {
|
|
|
10019
10088
|
addRemoteFunction(workerId, id2);
|
|
10020
10089
|
},
|
|
10021
10090
|
registerRemoteTrigger(workerId, input) {
|
|
10022
|
-
const tid =
|
|
10091
|
+
const tid = crypto8.randomUUID();
|
|
10023
10092
|
const reg = { id: tid, ...input, workerId };
|
|
10024
10093
|
triggers.set(tid, reg);
|
|
10025
10094
|
const worker = workers.get(workerId);
|
|
@@ -10143,7 +10212,7 @@ function iii(opts = {}) {
|
|
|
10143
10212
|
mod.migrate = async () => {
|
|
10144
10213
|
await stream.migrate();
|
|
10145
10214
|
};
|
|
10146
|
-
mod.
|
|
10215
|
+
mod.close = async () => {
|
|
10147
10216
|
for (const [, p] of pending) {
|
|
10148
10217
|
clearTimeout(p.timer);
|
|
10149
10218
|
p.reject(new Error("Engine shutting down"));
|
|
@@ -10359,7 +10428,7 @@ function registerWorker(url) {
|
|
|
10359
10428
|
onStream(handler) {
|
|
10360
10429
|
handlers.set("__stream__", handler);
|
|
10361
10430
|
},
|
|
10362
|
-
|
|
10431
|
+
close() {
|
|
10363
10432
|
intentionalClose = true;
|
|
10364
10433
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
10365
10434
|
ws?.close();
|
|
@@ -10369,7 +10438,7 @@ function registerWorker(url) {
|
|
|
10369
10438
|
}
|
|
10370
10439
|
|
|
10371
10440
|
// session.ts
|
|
10372
|
-
import
|
|
10441
|
+
import crypto9 from "node:crypto";
|
|
10373
10442
|
var kSaved = /* @__PURE__ */ Symbol("session.saved");
|
|
10374
10443
|
var kDestroyed = /* @__PURE__ */ Symbol("session.destroyed");
|
|
10375
10444
|
var kId = /* @__PURE__ */ Symbol("session.id");
|
|
@@ -10408,7 +10477,7 @@ var MemoryStore = class {
|
|
|
10408
10477
|
if (entry.expires < now) this.store.delete(key);
|
|
10409
10478
|
}
|
|
10410
10479
|
}
|
|
10411
|
-
close() {
|
|
10480
|
+
async close() {
|
|
10412
10481
|
clearInterval(this.interval);
|
|
10413
10482
|
this.store.clear();
|
|
10414
10483
|
}
|
|
@@ -10444,10 +10513,13 @@ var RedisStore = class {
|
|
|
10444
10513
|
async destroy(sid) {
|
|
10445
10514
|
await this.redis.del(this.key(sid));
|
|
10446
10515
|
}
|
|
10516
|
+
async close() {
|
|
10517
|
+
this.redis.disconnect();
|
|
10518
|
+
}
|
|
10447
10519
|
};
|
|
10448
10520
|
var COOKIE_SEPARATOR = ".";
|
|
10449
10521
|
function signSessionId(sid, secret) {
|
|
10450
|
-
const hmac =
|
|
10522
|
+
const hmac = crypto9.createHmac("sha256", secret).update(sid).digest("base64url").slice(0, 16);
|
|
10451
10523
|
return sid + COOKIE_SEPARATOR + hmac;
|
|
10452
10524
|
}
|
|
10453
10525
|
function unsignSessionId(value, secret) {
|
|
@@ -10455,10 +10527,10 @@ function unsignSessionId(value, secret) {
|
|
|
10455
10527
|
if (dot === -1) return null;
|
|
10456
10528
|
const sid = value.slice(0, dot);
|
|
10457
10529
|
const sig = value.slice(dot + 1);
|
|
10458
|
-
const expected =
|
|
10530
|
+
const expected = crypto9.createHmac("sha256", secret).update(sid).digest("base64url").slice(0, 16);
|
|
10459
10531
|
if (sig.length !== expected.length) return null;
|
|
10460
10532
|
try {
|
|
10461
|
-
return
|
|
10533
|
+
return crypto9.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)) ? sid : null;
|
|
10462
10534
|
} catch {
|
|
10463
10535
|
return null;
|
|
10464
10536
|
}
|
|
@@ -10517,6 +10589,7 @@ function session(options) {
|
|
|
10517
10589
|
} else if (options?.store === "redis") {
|
|
10518
10590
|
if (!options.redis) throw new Error('session: redis client required when store: "redis"');
|
|
10519
10591
|
store2 = new RedisStore(options.redis);
|
|
10592
|
+
closeStore = () => store2.close();
|
|
10520
10593
|
} else {
|
|
10521
10594
|
const mem = new MemoryStore();
|
|
10522
10595
|
store2 = mem;
|
|
@@ -10546,11 +10619,11 @@ function session(options) {
|
|
|
10546
10619
|
}
|
|
10547
10620
|
} else {
|
|
10548
10621
|
loadedSid = null;
|
|
10549
|
-
session2 = createSessionObject({},
|
|
10622
|
+
session2 = createSessionObject({}, crypto9.randomUUID(), store2, ttl, Date.now());
|
|
10550
10623
|
}
|
|
10551
10624
|
} else {
|
|
10552
10625
|
loadedSid = null;
|
|
10553
|
-
session2 = createSessionObject({},
|
|
10626
|
+
session2 = createSessionObject({}, crypto9.randomUUID(), store2, ttl, Date.now());
|
|
10554
10627
|
}
|
|
10555
10628
|
const snapshot = isSessionActive(session2) ? JSON.stringify(session2) : null;
|
|
10556
10629
|
ctx.session = session2;
|
|
@@ -10563,7 +10636,7 @@ function session(options) {
|
|
|
10563
10636
|
return deleteCookie(res, cookieName, cookieOpts);
|
|
10564
10637
|
}
|
|
10565
10638
|
if (needsRotation && loadedSid) {
|
|
10566
|
-
const newId =
|
|
10639
|
+
const newId = crypto9.randomUUID();
|
|
10567
10640
|
const data = JSON.parse(JSON.stringify(currentSession));
|
|
10568
10641
|
data[kCreatedAt] = Date.now();
|
|
10569
10642
|
await store2.set(newId, data, ttl);
|
|
@@ -10596,15 +10669,15 @@ function session(options) {
|
|
|
10596
10669
|
}
|
|
10597
10670
|
return res;
|
|
10598
10671
|
});
|
|
10599
|
-
mw.close = () => {
|
|
10600
|
-
closeStore?.();
|
|
10672
|
+
mw.close = async () => {
|
|
10673
|
+
await closeStore?.();
|
|
10601
10674
|
};
|
|
10602
10675
|
mw.store = store2;
|
|
10603
10676
|
return mw;
|
|
10604
10677
|
}
|
|
10605
10678
|
|
|
10606
10679
|
// cache.ts
|
|
10607
|
-
import
|
|
10680
|
+
import crypto10 from "node:crypto";
|
|
10608
10681
|
var BINARY_PREFIXES = [
|
|
10609
10682
|
"image/",
|
|
10610
10683
|
"audio/",
|
|
@@ -10624,7 +10697,7 @@ function isCacheableStatus(status, allowed) {
|
|
|
10624
10697
|
return allowed.includes(status);
|
|
10625
10698
|
}
|
|
10626
10699
|
function defaultCacheKey(req) {
|
|
10627
|
-
const hash =
|
|
10700
|
+
const hash = crypto10.createHash("sha256");
|
|
10628
10701
|
hash.update(req.method);
|
|
10629
10702
|
hash.update(req.url);
|
|
10630
10703
|
return hash.digest("hex");
|
|
@@ -10691,7 +10764,7 @@ var MemoryCache = class {
|
|
|
10691
10764
|
}
|
|
10692
10765
|
}
|
|
10693
10766
|
}
|
|
10694
|
-
close() {
|
|
10767
|
+
async close() {
|
|
10695
10768
|
clearInterval(this.interval);
|
|
10696
10769
|
this.store.clear();
|
|
10697
10770
|
this.tagIndex.clear();
|
|
@@ -10832,17 +10905,17 @@ function cache2(options) {
|
|
|
10832
10905
|
mw.store = store2;
|
|
10833
10906
|
mw.invalidate = async (tag) => store2.invalidate(tag);
|
|
10834
10907
|
mw.flush = async () => store2.flush();
|
|
10835
|
-
mw.close = () => {
|
|
10836
|
-
closeStore?.();
|
|
10908
|
+
mw.close = async () => {
|
|
10909
|
+
await closeStore?.();
|
|
10837
10910
|
};
|
|
10838
10911
|
return mw;
|
|
10839
10912
|
}
|
|
10840
10913
|
|
|
10841
10914
|
// webhook.ts
|
|
10842
|
-
import
|
|
10915
|
+
import crypto11 from "node:crypto";
|
|
10843
10916
|
function timingSafeEqual2(a, b) {
|
|
10844
10917
|
try {
|
|
10845
|
-
return
|
|
10918
|
+
return crypto11.timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
10846
10919
|
} catch {
|
|
10847
10920
|
return false;
|
|
10848
10921
|
}
|
|
@@ -10860,7 +10933,7 @@ function createStripeVerifier(config) {
|
|
|
10860
10933
|
const signature = parts["v1"];
|
|
10861
10934
|
if (!timestamp || !signature) return { valid: false, provider: "stripe", event: "", id: void 0 };
|
|
10862
10935
|
const signed = `${timestamp}.${body}`;
|
|
10863
|
-
const expected =
|
|
10936
|
+
const expected = crypto11.createHmac("sha256", config.secret).update(signed).digest("hex");
|
|
10864
10937
|
const valid = timingSafeEqual2(signature, expected);
|
|
10865
10938
|
let event = "";
|
|
10866
10939
|
let id2;
|
|
@@ -10877,7 +10950,7 @@ function createGitHubVerifier(config) {
|
|
|
10877
10950
|
return (body, headers) => {
|
|
10878
10951
|
const sig = headers["x-hub-signature-256"];
|
|
10879
10952
|
if (!sig) return { valid: false, provider: "github", event: "", id: void 0 };
|
|
10880
|
-
const expected = `sha256=${
|
|
10953
|
+
const expected = `sha256=${crypto11.createHmac("sha256", config.secret).update(body).digest("hex")}`;
|
|
10881
10954
|
const valid = timingSafeEqual2(sig, expected);
|
|
10882
10955
|
let event = headers["x-github-event"] ?? "";
|
|
10883
10956
|
let id2;
|
|
@@ -10900,7 +10973,7 @@ function createSlackVerifier(config) {
|
|
|
10900
10973
|
return { valid: false, provider: "slack", event: "", id: void 0 };
|
|
10901
10974
|
}
|
|
10902
10975
|
const sigBase = `v0:${timestamp}:${body}`;
|
|
10903
|
-
const expected = `v0=${
|
|
10976
|
+
const expected = `v0=${crypto11.createHmac("sha256", config.secret).update(sigBase).digest("hex")}`;
|
|
10904
10977
|
const valid = timingSafeEqual2(signature, expected);
|
|
10905
10978
|
let event = "";
|
|
10906
10979
|
let id2;
|
|
@@ -11417,17 +11490,17 @@ function permissions(options) {
|
|
|
11417
11490
|
);
|
|
11418
11491
|
return created.id;
|
|
11419
11492
|
}
|
|
11420
|
-
async function assignRole(
|
|
11493
|
+
async function assignRole(userId2, role) {
|
|
11421
11494
|
const roleId = await ensureRole(role);
|
|
11422
11495
|
await sql2.unsafe(
|
|
11423
11496
|
`INSERT INTO ${escapeIdent6(userRolesTable)} (user_id, role_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
|
|
11424
|
-
[
|
|
11497
|
+
[userId2, roleId]
|
|
11425
11498
|
);
|
|
11426
11499
|
}
|
|
11427
|
-
async function removeRole(
|
|
11500
|
+
async function removeRole(userId2, role) {
|
|
11428
11501
|
await sql2.unsafe(
|
|
11429
11502
|
`DELETE FROM ${escapeIdent6(userRolesTable)} WHERE user_id = $1 AND role_id = (SELECT id FROM ${escapeIdent6(rolesTable)} WHERE name = $2)`,
|
|
11430
|
-
[
|
|
11503
|
+
[userId2, role]
|
|
11431
11504
|
);
|
|
11432
11505
|
}
|
|
11433
11506
|
async function grantPermission(role, permission) {
|
|
@@ -11443,31 +11516,31 @@ function permissions(options) {
|
|
|
11443
11516
|
[role, permission]
|
|
11444
11517
|
);
|
|
11445
11518
|
}
|
|
11446
|
-
async function getUserRoles(
|
|
11519
|
+
async function getUserRoles(userId2) {
|
|
11447
11520
|
const rows = await sql2.unsafe(
|
|
11448
11521
|
`SELECT r.name FROM ${escapeIdent6(userRolesTable)} ur
|
|
11449
11522
|
JOIN ${escapeIdent6(rolesTable)} r ON r.id = ur.role_id
|
|
11450
11523
|
WHERE ur.user_id = $1 ORDER BY r.name`,
|
|
11451
|
-
[
|
|
11524
|
+
[userId2]
|
|
11452
11525
|
);
|
|
11453
11526
|
return rows.map((r) => r.name);
|
|
11454
11527
|
}
|
|
11455
|
-
async function getUserPermissions(
|
|
11528
|
+
async function getUserPermissions(userId2) {
|
|
11456
11529
|
const rows = await sql2.unsafe(
|
|
11457
11530
|
`SELECT DISTINCT rp.permission FROM ${escapeIdent6(userRolesTable)} ur
|
|
11458
11531
|
JOIN ${escapeIdent6(rolePermsTable)} rp ON rp.role_id = ur.role_id
|
|
11459
11532
|
WHERE ur.user_id = $1 ORDER BY rp.permission`,
|
|
11460
|
-
[
|
|
11533
|
+
[userId2]
|
|
11461
11534
|
);
|
|
11462
11535
|
return rows.map((r) => r.permission);
|
|
11463
11536
|
}
|
|
11464
11537
|
const mw = (async (req, ctx, next) => {
|
|
11465
|
-
const
|
|
11538
|
+
const userId2 = ctx.user?.id;
|
|
11466
11539
|
let roles = /* @__PURE__ */ new Set();
|
|
11467
11540
|
let perms = /* @__PURE__ */ new Set();
|
|
11468
|
-
if (
|
|
11469
|
-
const userRoles = await getUserRoles(
|
|
11470
|
-
const userPerms =
|
|
11541
|
+
if (userId2) {
|
|
11542
|
+
const userRoles = await getUserRoles(userId2);
|
|
11543
|
+
const userPerms = userId2 ? await getUserPermissions(userId2) : [];
|
|
11471
11544
|
roles = new Set(userRoles);
|
|
11472
11545
|
perms = new Set(userPerms);
|
|
11473
11546
|
const hasWildcard = userPerms.includes("*");
|
|
@@ -11534,7 +11607,6 @@ export {
|
|
|
11534
11607
|
aiProvider,
|
|
11535
11608
|
aiStream,
|
|
11536
11609
|
analytics,
|
|
11537
|
-
auth,
|
|
11538
11610
|
cache2 as cache,
|
|
11539
11611
|
compress,
|
|
11540
11612
|
cors,
|
|
@@ -11552,6 +11624,7 @@ export {
|
|
|
11552
11624
|
deploy,
|
|
11553
11625
|
embed,
|
|
11554
11626
|
embedMany,
|
|
11627
|
+
env,
|
|
11555
11628
|
flash,
|
|
11556
11629
|
formatSSE,
|
|
11557
11630
|
formatSSEData,
|
|
@@ -11559,6 +11632,7 @@ export {
|
|
|
11559
11632
|
generateObject,
|
|
11560
11633
|
generateText2 as generateText,
|
|
11561
11634
|
getCookies,
|
|
11635
|
+
getPublicEnv,
|
|
11562
11636
|
graphql,
|
|
11563
11637
|
health,
|
|
11564
11638
|
helmet,
|
|
@@ -11600,6 +11674,7 @@ export {
|
|
|
11600
11674
|
testApp,
|
|
11601
11675
|
theme,
|
|
11602
11676
|
tool2 as tool,
|
|
11677
|
+
trace,
|
|
11603
11678
|
traceElapsed,
|
|
11604
11679
|
upload,
|
|
11605
11680
|
user,
|