@seele00/seele 0.1.63 → 0.1.65
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 +5 -3
- package/bin/seele-codex +1 -1
- package/libexec/seele-server +0 -0
- package/libexec/seele-worker +0 -0
- package/package.json +1 -4
- package/scripts/deploy/bootstrap.sh +14 -3
- package/scripts/deploy/start.sh +14 -9
- package/web/dist/assets/{abnfDiagram-VRR7QNED-DXJQwnn-.js → abnfDiagram-VRR7QNED-BB7DT0BN.js} +1 -1
- package/web/dist/assets/{arc-D_47-IET.js → arc-BlEU_mit.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-CKUldIzR.js → architectureDiagram-ZJ3FMSHR-Dy9cu2Mg.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-BD4Ym8Xi.js → blockDiagram-677ZJIJ3-DP4jMSfo.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-Cn268vLB.js → c4Diagram-LMCZKHZV-BtqFzX1w.js} +1 -1
- package/web/dist/assets/channel-mhSuRKJT.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-D00lQ7mJ.js → chunk-2Q5K7J3B-BK9F5a0L.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-z7gv8Mry.js → chunk-32BRIVSS-nuQI6gtU.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-BZJeO_rz.js → chunk-5VM5RSS4-D4IT05bt.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-DoIg9UGx.js → chunk-EX3LRPZG-CrOtgxcr.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-C1LcQus2.js → chunk-JWPE2WC7-Dn9JHm4W.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN--JPbFu4n.js → chunk-MOJQB5TN-VtD0_fGF.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-BVv30W60.js → chunk-RYQCIY6F-TqRYsCot.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-DYoSFgRT.js → chunk-V7JOEXUC-U9b5a7vL.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-C8BRkTOa.js → chunk-VR4S4FIN-B3qLfeNJ.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-CDfofIob.js → chunk-XXDRQBXY-DazVAZe-.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-DYQSxD4g.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-DYQSxD4g.js +1 -0
- package/web/dist/assets/{core-C10OFTno.js → core-2RdBW0rZ.js} +1 -1
- package/web/dist/assets/{cose-bilkent-JH36ORCC-g9E8J4Fz.js → cose-bilkent-JH36ORCC-vvKttQXU.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-Dc689veM.js → cynefin-VYW2F7L2-BKMZmA-n.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-BAAq_jvr.js → cynefinDiagram-TSTJHNR4-ckdzxzlr.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-CPtiniZQ.js → dagre-VKFMJZFB-CZw8DTb1.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-CvbIL3lu.js → diagram-FQU43EPY-C06X0Kru.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-u5m78Utg.js → diagram-G47NLZAW-HX9CdauD.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-4f-Ipubv.js → diagram-NH7WQ7WH-Dlo1tIod.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-SUl1EQ56.js → diagram-OA4YK3LP-C9GXFhgw.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-C_Upd46l.js → diagram-WEI45ONY-Ch1Xx234.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-CHp2D7Q9.js → ebnfDiagram-CCIWWBDH-Q0_rEUgk.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-DQliHxOw.js → erDiagram-Q63AITRT-BsDRdpx6.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-BcOYSJFM.js → flowDiagram-23GEKE2U-DDFuMRJN.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-CDLHtJq0.js → ganttDiagram-NO4QXBWP-Cjpm08-d.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-2j8C3mlk.js → gitGraphDiagram-IHSO6WYX-CnIosh2K.js} +1 -1
- package/web/dist/assets/index-eN6ddNRA.js +111 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-ClxdyQnM.js → infoDiagram-FWYZ7A6U-CH163HyC.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-Bc2nIRgb.js → ishikawaDiagram-FXEZZL3T-DkT-3n3D.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-R-vJ00LW.js → journeyDiagram-5HDEW3XC-34TnH3wG.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-CR0s_Mo8.js → kanban-definition-HUTT4EX6-CJbOm0lN.js} +1 -1
- package/web/dist/assets/{linear-BQgrawZS.js → linear-CTKXTLwe.js} +1 -1
- package/web/dist/assets/{mermaid.core-faLMbjSU.js → mermaid.core-CAVP16_-.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-BZULnQui.js → mindmap-definition-LN4V7U3C-rlppHZj_.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-DUm1p_1o.js → pegDiagram-2B236MQR-MbcWBNJi.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-r3LCASRK.js → pieDiagram-ENE6RG2P-DKU7a4tv.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CGaCp0Px.js → quadrantDiagram-ABIIQ3AL-DpDsJVLi.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-DmEKGO1A.js → railroadDiagram-RFXS5EU6-DrPM3aKC.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-Byc4ivmp.js → requirementDiagram-TGXJPOKE-ZzS_iVag.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-BtJiL4zX.js → sankeyDiagram-HTMAVEWB-DrRrN0Lc.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-Bg3vdDGW.js → sequenceDiagram-DBY2YBRQ-CP8GBGZM.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-CCRwjaWU.js → sizeCapture-X5ZJPWSS-BYVg_dQd.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-CQU-udox.js → stateDiagram-2N3HPSRC-CsJ3M80-.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-CaFOY1Xb.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-r3tJnQLJ.js → swimlanes-5IMT3BWC-XgRKQ20Q.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-DaKP_iDy.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-DryYrdts.js → timeline-definition-FHXFAJF6-CU_Cq10d.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-ZLPc0qvQ.js → vennDiagram-L72KCM5P-CLSDbg6o.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-RmgA6vQ9.js → wardleyDiagram-EHGQE667-DnAOuvzv.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-BGIzispI.js → xychartDiagram-FW5EYKEG-CyC6V11l.js} +1 -1
- package/web/dist/index.html +1 -1
- package/codex/dist/app-server.js +0 -63
- package/codex/dist/errors.js +0 -16
- package/codex/dist/index.js +0 -4
- package/codex/dist/rpc-client.js +0 -96
- package/codex/dist/transports/stdio.js +0 -81
- package/codex/dist/types.js +0 -1
- package/codex/package.json +0 -12
- package/scripts/deploy/npm/postinstall.mjs +0 -10
- package/server/dist/auth/session.js +0 -71
- package/server/dist/auth/users.js +0 -276
- package/server/dist/codex-runtime/run-streamed-turn.js +0 -587
- package/server/dist/config/config.js +0 -71
- package/server/dist/contracts/artifact-rendering.js +0 -14
- package/server/dist/contracts/global-environment.js +0 -23
- package/server/dist/contracts/index.js +0 -114
- package/server/dist/contracts/managed-assets.js +0 -13
- package/server/dist/contracts/response-style.js +0 -23
- package/server/dist/contracts/task-clarification.js +0 -13
- package/server/dist/contracts/types.js +0 -1
- package/server/dist/contracts/wakeup.js +0 -13
- package/server/dist/contracts/workspace-boundary.js +0 -20
- package/server/dist/contracts/workspace-knowledge.js +0 -48
- package/server/dist/contracts/workspace-memory.js +0 -32
- package/server/dist/contracts/workspace-responsibility.js +0 -23
- package/server/dist/contracts/workspace-transcript.js +0 -26
- package/server/dist/conversations/knowledge.js +0 -380
- package/server/dist/conversations/store.js +0 -2466
- package/server/dist/doctor.js +0 -105
- package/server/dist/realtime/sse.js +0 -12
- package/server/dist/runtime-worker/client.js +0 -405
- package/server/dist/runtime-worker/process.js +0 -216
- package/server/dist/runtime-worker/protocol.js +0 -1
- package/server/dist/server.js +0 -4605
- package/server/dist/storage/sqlite.js +0 -39
- package/web/dist/assets/channel-Uz6zF6Xw.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-D6mpE1t2.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-D6mpE1t2.js +0 -1
- package/web/dist/assets/index-BqSosI7W.js +0 -111
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-YNSWW4ip.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-BGZAc11_.js +0 -8
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
-
const COOKIE_NAME = "seele_session";
|
|
3
|
-
export function readSessionPayload(request, secret) {
|
|
4
|
-
const token = parseCookies(request.headers.cookie)[COOKIE_NAME];
|
|
5
|
-
if (!token) {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
const [encodedPayload, signature] = token.split(".");
|
|
9
|
-
if (!encodedPayload || !signature) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
if (!safeEqual(signature, sign(encodedPayload, secret))) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
const payload = JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"));
|
|
17
|
-
if (typeof payload.userId !== "string" ||
|
|
18
|
-
!Number.isInteger(payload.sessionVersion) ||
|
|
19
|
-
!Number.isFinite(payload.expiresAt) ||
|
|
20
|
-
payload.expiresAt <= Date.now()) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
return payload;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export function setSessionCookie(response, request, input) {
|
|
30
|
-
const payload = Buffer.from(JSON.stringify({
|
|
31
|
-
userId: input.userId,
|
|
32
|
-
sessionVersion: input.sessionVersion,
|
|
33
|
-
expiresAt: Date.now() + input.ttlSeconds * 1000,
|
|
34
|
-
})).toString("base64url");
|
|
35
|
-
const token = `${payload}.${sign(payload, input.secret)}`;
|
|
36
|
-
const secure = isSecureRequest(request) ? "; Secure" : "";
|
|
37
|
-
response.setHeader("Set-Cookie", `${COOKIE_NAME}=${token}; Path=${input.path}; HttpOnly; SameSite=Strict; Max-Age=${input.ttlSeconds}${secure}`);
|
|
38
|
-
}
|
|
39
|
-
export function clearSessionCookie(response, request, path) {
|
|
40
|
-
const secure = isSecureRequest(request) ? "; Secure" : "";
|
|
41
|
-
response.setHeader("Set-Cookie", `${COOKIE_NAME}=; Path=${path}; HttpOnly; SameSite=Strict; Max-Age=0${secure}`);
|
|
42
|
-
}
|
|
43
|
-
function sign(value, secret) {
|
|
44
|
-
return createHmac("sha256", secret).update(value).digest("base64url");
|
|
45
|
-
}
|
|
46
|
-
function safeEqual(left, right) {
|
|
47
|
-
const leftBuffer = Buffer.from(left);
|
|
48
|
-
const rightBuffer = Buffer.from(right);
|
|
49
|
-
return (leftBuffer.length === rightBuffer.length &&
|
|
50
|
-
timingSafeEqual(leftBuffer, rightBuffer));
|
|
51
|
-
}
|
|
52
|
-
function parseCookies(header) {
|
|
53
|
-
if (!header) {
|
|
54
|
-
return {};
|
|
55
|
-
}
|
|
56
|
-
return Object.fromEntries(header.split(";").flatMap((part) => {
|
|
57
|
-
const separator = part.indexOf("=");
|
|
58
|
-
if (separator < 0) {
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
const key = part.slice(0, separator).trim();
|
|
62
|
-
const value = part.slice(separator + 1).trim();
|
|
63
|
-
return key ? [[key, value]] : [];
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
66
|
-
function isSecureRequest(request) {
|
|
67
|
-
const forwardedProto = request.headers["x-forwarded-proto"];
|
|
68
|
-
const protocol = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
|
|
69
|
-
return (protocol?.split(",")[0]?.trim() === "https" ||
|
|
70
|
-
Boolean(request.socket.encrypted));
|
|
71
|
-
}
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import { randomBytes, randomUUID, scrypt, scryptSync, timingSafeEqual, } from "node:crypto";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { mkdirSync } from "node:fs";
|
|
4
|
-
import { openSqliteDatabase, runInTransaction, } from "../storage/sqlite.js";
|
|
5
|
-
export class UserStore {
|
|
6
|
-
sessionSecret;
|
|
7
|
-
db;
|
|
8
|
-
dummyPasswordHash;
|
|
9
|
-
constructor(databasePath, bootstrap) {
|
|
10
|
-
mkdirSync(path.dirname(databasePath), { recursive: true });
|
|
11
|
-
this.db = openSqliteDatabase(databasePath);
|
|
12
|
-
this.db.exec("pragma journal_mode = WAL");
|
|
13
|
-
this.initializeSchema();
|
|
14
|
-
this.sessionSecret = this.ensureSessionSecret();
|
|
15
|
-
this.dummyPasswordHash = this.ensureDummyPasswordHash();
|
|
16
|
-
if (this.countUsers() === 0 && bootstrap?.password) {
|
|
17
|
-
this.createUser({
|
|
18
|
-
username: bootstrap.username,
|
|
19
|
-
password: bootstrap.password,
|
|
20
|
-
role: "admin",
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
get enabled() {
|
|
25
|
-
return this.countUsers() > 0;
|
|
26
|
-
}
|
|
27
|
-
listUsers() {
|
|
28
|
-
const rows = this.db
|
|
29
|
-
.prepare("select * from auth_users order by created_at asc")
|
|
30
|
-
.all();
|
|
31
|
-
return rows.map(userFromRow);
|
|
32
|
-
}
|
|
33
|
-
getUser(id) {
|
|
34
|
-
const row = this.db
|
|
35
|
-
.prepare("select * from auth_users where id = ?")
|
|
36
|
-
.get(id);
|
|
37
|
-
return row ? userFromRow(row) : null;
|
|
38
|
-
}
|
|
39
|
-
getUserByUsername(username) {
|
|
40
|
-
const row = this.db
|
|
41
|
-
.prepare("select * from auth_users where username = ? collate nocase")
|
|
42
|
-
.get(username.trim());
|
|
43
|
-
return row ? userFromRow(row) : null;
|
|
44
|
-
}
|
|
45
|
-
async authenticate(username, password) {
|
|
46
|
-
const row = this.db
|
|
47
|
-
.prepare("select * from auth_users where username = ? collate nocase")
|
|
48
|
-
.get(username.trim());
|
|
49
|
-
const passwordMatches = await verifyPassword(password, row?.password_hash ?? this.dummyPasswordHash);
|
|
50
|
-
if (!row || !row.active || !passwordMatches) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
return userFromRow(row);
|
|
54
|
-
}
|
|
55
|
-
loginBlockedUntil(keys, now = Date.now()) {
|
|
56
|
-
let blockedUntil = 0;
|
|
57
|
-
const statement = this.db.prepare("select locked_until from auth_login_attempts where key = ?");
|
|
58
|
-
for (const key of keys) {
|
|
59
|
-
const row = statement.get(key);
|
|
60
|
-
blockedUntil = Math.max(blockedUntil, row?.locked_until ?? 0);
|
|
61
|
-
}
|
|
62
|
-
return blockedUntil > now ? blockedUntil : 0;
|
|
63
|
-
}
|
|
64
|
-
recordLoginFailure(buckets, windowMs, lockoutMs, now = Date.now()) {
|
|
65
|
-
runInTransaction(this.db, () => {
|
|
66
|
-
const read = this.db.prepare(`
|
|
67
|
-
select failure_count, window_started_at, locked_until
|
|
68
|
-
from auth_login_attempts where key = ?
|
|
69
|
-
`);
|
|
70
|
-
const write = this.db.prepare(`
|
|
71
|
-
insert into auth_login_attempts (
|
|
72
|
-
key, failure_count, window_started_at, locked_until, updated_at
|
|
73
|
-
) values (?, ?, ?, ?, ?)
|
|
74
|
-
on conflict(key) do update set
|
|
75
|
-
failure_count = excluded.failure_count,
|
|
76
|
-
window_started_at = excluded.window_started_at,
|
|
77
|
-
locked_until = excluded.locked_until,
|
|
78
|
-
updated_at = excluded.updated_at
|
|
79
|
-
`);
|
|
80
|
-
for (const bucket of buckets) {
|
|
81
|
-
const current = read.get(bucket.key);
|
|
82
|
-
const expired = !current ||
|
|
83
|
-
(current.locked_until <= now &&
|
|
84
|
-
now - current.window_started_at >= windowMs);
|
|
85
|
-
const failureCount = expired ? 1 : current.failure_count + 1;
|
|
86
|
-
const windowStartedAt = expired ? now : current.window_started_at;
|
|
87
|
-
const lockedUntil = failureCount >= bucket.maxFailures ? now + lockoutMs : 0;
|
|
88
|
-
write.run(bucket.key, failureCount, windowStartedAt, lockedUntil, now);
|
|
89
|
-
}
|
|
90
|
-
this.db
|
|
91
|
-
.prepare("delete from auth_login_attempts where updated_at < ?")
|
|
92
|
-
.run(now - Math.max(windowMs, lockoutMs) * 4);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
clearLoginFailures(keys) {
|
|
96
|
-
const statement = this.db.prepare("delete from auth_login_attempts where key = ?");
|
|
97
|
-
runInTransaction(this.db, () => {
|
|
98
|
-
for (const key of keys) {
|
|
99
|
-
statement.run(key);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
createUser(input) {
|
|
104
|
-
const username = normalizeUsername(input.username);
|
|
105
|
-
validatePassword(input.password);
|
|
106
|
-
const now = new Date().toISOString();
|
|
107
|
-
const row = {
|
|
108
|
-
id: randomUUID(),
|
|
109
|
-
username,
|
|
110
|
-
password_hash: hashPassword(input.password),
|
|
111
|
-
role: input.role,
|
|
112
|
-
active: 1,
|
|
113
|
-
session_version: 1,
|
|
114
|
-
created_at: now,
|
|
115
|
-
updated_at: now,
|
|
116
|
-
};
|
|
117
|
-
this.db.prepare(`
|
|
118
|
-
insert into auth_users (
|
|
119
|
-
id, username, password_hash, role, active, session_version, created_at, updated_at
|
|
120
|
-
) values (
|
|
121
|
-
@id, @username, @password_hash, @role, @active, @session_version, @created_at, @updated_at
|
|
122
|
-
)
|
|
123
|
-
`).run(row);
|
|
124
|
-
return userFromRow(row);
|
|
125
|
-
}
|
|
126
|
-
updateUser(id, input) {
|
|
127
|
-
const current = this.db
|
|
128
|
-
.prepare("select * from auth_users where id = ?")
|
|
129
|
-
.get(id);
|
|
130
|
-
if (!current) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
const username = input.username === undefined
|
|
134
|
-
? current.username
|
|
135
|
-
: normalizeUsername(input.username);
|
|
136
|
-
const passwordChanged = input.password !== undefined && input.password.length > 0;
|
|
137
|
-
if (passwordChanged) {
|
|
138
|
-
validatePassword(input.password);
|
|
139
|
-
}
|
|
140
|
-
const next = {
|
|
141
|
-
...current,
|
|
142
|
-
username,
|
|
143
|
-
password_hash: passwordChanged ? hashPassword(input.password) : current.password_hash,
|
|
144
|
-
role: input.role ?? current.role,
|
|
145
|
-
active: input.active === undefined ? current.active : input.active ? 1 : 0,
|
|
146
|
-
session_version: passwordChanged || input.active === false
|
|
147
|
-
? current.session_version + 1
|
|
148
|
-
: current.session_version,
|
|
149
|
-
updated_at: new Date().toISOString(),
|
|
150
|
-
};
|
|
151
|
-
this.db.prepare(`
|
|
152
|
-
update auth_users set
|
|
153
|
-
username = @username,
|
|
154
|
-
password_hash = @password_hash,
|
|
155
|
-
role = @role,
|
|
156
|
-
active = @active,
|
|
157
|
-
session_version = @session_version,
|
|
158
|
-
updated_at = @updated_at
|
|
159
|
-
where id = @id
|
|
160
|
-
`).run(next);
|
|
161
|
-
return userFromRow(next);
|
|
162
|
-
}
|
|
163
|
-
deleteUser(id) {
|
|
164
|
-
return this.db.prepare("delete from auth_users where id = ?").run(id).changes > 0;
|
|
165
|
-
}
|
|
166
|
-
countUsers() {
|
|
167
|
-
const row = this.db.prepare("select count(*) as count from auth_users").get();
|
|
168
|
-
return row.count;
|
|
169
|
-
}
|
|
170
|
-
initializeSchema() {
|
|
171
|
-
this.db.exec(`
|
|
172
|
-
create table if not exists auth_users (
|
|
173
|
-
id text primary key,
|
|
174
|
-
username text not null unique collate nocase,
|
|
175
|
-
password_hash text not null,
|
|
176
|
-
role text not null check (role in ('admin', 'user')),
|
|
177
|
-
active integer not null default 1,
|
|
178
|
-
session_version integer not null default 1,
|
|
179
|
-
created_at text not null,
|
|
180
|
-
updated_at text not null
|
|
181
|
-
);
|
|
182
|
-
create table if not exists auth_metadata (
|
|
183
|
-
key text primary key,
|
|
184
|
-
value text not null
|
|
185
|
-
);
|
|
186
|
-
create table if not exists auth_login_attempts (
|
|
187
|
-
key text primary key,
|
|
188
|
-
failure_count integer not null,
|
|
189
|
-
window_started_at integer not null,
|
|
190
|
-
locked_until integer not null,
|
|
191
|
-
updated_at integer not null
|
|
192
|
-
);
|
|
193
|
-
create index if not exists auth_login_attempts_updated_at
|
|
194
|
-
on auth_login_attempts(updated_at);
|
|
195
|
-
`);
|
|
196
|
-
}
|
|
197
|
-
ensureSessionSecret() {
|
|
198
|
-
const existing = this.db
|
|
199
|
-
.prepare("select value from auth_metadata where key = 'session_secret'")
|
|
200
|
-
.get();
|
|
201
|
-
if (existing) {
|
|
202
|
-
return existing.value;
|
|
203
|
-
}
|
|
204
|
-
const value = randomBytes(32).toString("base64url");
|
|
205
|
-
this.db
|
|
206
|
-
.prepare("insert into auth_metadata (key, value) values ('session_secret', ?)")
|
|
207
|
-
.run(value);
|
|
208
|
-
return value;
|
|
209
|
-
}
|
|
210
|
-
ensureDummyPasswordHash() {
|
|
211
|
-
const existing = this.db
|
|
212
|
-
.prepare("select value from auth_metadata where key = 'dummy_password_hash'")
|
|
213
|
-
.get();
|
|
214
|
-
if (existing) {
|
|
215
|
-
return existing.value;
|
|
216
|
-
}
|
|
217
|
-
const value = hashPassword(randomBytes(32).toString("base64url"));
|
|
218
|
-
this.db
|
|
219
|
-
.prepare("insert into auth_metadata (key, value) values ('dummy_password_hash', ?)")
|
|
220
|
-
.run(value);
|
|
221
|
-
return value;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function userFromRow(row) {
|
|
225
|
-
return {
|
|
226
|
-
id: row.id,
|
|
227
|
-
username: row.username,
|
|
228
|
-
role: row.role,
|
|
229
|
-
active: row.active === 1,
|
|
230
|
-
sessionVersion: row.session_version,
|
|
231
|
-
createdAt: row.created_at,
|
|
232
|
-
updatedAt: row.updated_at,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function normalizeUsername(value) {
|
|
236
|
-
const username = value.trim();
|
|
237
|
-
if (!/^[A-Za-z0-9_.-]{2,64}$/.test(username)) {
|
|
238
|
-
throw new Error("invalid_username");
|
|
239
|
-
}
|
|
240
|
-
return username;
|
|
241
|
-
}
|
|
242
|
-
function validatePassword(value) {
|
|
243
|
-
if (value.length < 8 || value.length > 512) {
|
|
244
|
-
throw new Error("invalid_password");
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
function hashPassword(password) {
|
|
248
|
-
const salt = randomBytes(16);
|
|
249
|
-
const digest = scryptSync(password, salt, 64);
|
|
250
|
-
return `scrypt:${salt.toString("base64url")}:${digest.toString("base64url")}`;
|
|
251
|
-
}
|
|
252
|
-
async function verifyPassword(password, stored) {
|
|
253
|
-
const [algorithm, encodedSalt, encodedDigest] = stored.split(":");
|
|
254
|
-
if (algorithm !== "scrypt" || !encodedSalt || !encodedDigest) {
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
try {
|
|
258
|
-
const expected = Buffer.from(encodedDigest, "base64url");
|
|
259
|
-
const actual = await derivePassword(password, Buffer.from(encodedSalt, "base64url"), expected.length);
|
|
260
|
-
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function derivePassword(password, salt, length) {
|
|
267
|
-
return new Promise((resolve, reject) => {
|
|
268
|
-
scrypt(password, salt, length, (error, derivedKey) => {
|
|
269
|
-
if (error) {
|
|
270
|
-
reject(error);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
resolve(derivedKey);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
}
|