shadok-ai 0.9.5 → 0.9.7

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 CHANGED
@@ -227,6 +227,20 @@ know before opening it up:
227
227
  - `SHADOK_HOST=0.0.0.0` **requires a password** (`--password`, or
228
228
  `SHADOK_GUI_PASSWORD`); without one the server refuses to start rather than
229
229
  hand the network a shell.
230
+ - **That password is the `admin` account.** With it set, the login page gains a
231
+ user field — leave it blank to sign in as the admin. An admin can then invite
232
+ other people from the **Users** panel: each invitation is a single-use link,
233
+ valid 7 days, on which the invitee chooses their own password, so the admin
234
+ never learns it — and redeeming the link signs them in straight away. The ⋯
235
+ menu names who you are and lets you sign out. Two roles: `admin` manages
236
+ accounts, `member` does everything
237
+ else you do. Accounts belong to **one instance** (one launch directory), like
238
+ its channels and its scheduled prompts. With no password there are no
239
+ accounts and no login screen — nothing changes for a machine you use alone.
240
+ - Once people are named, **a prompt carries its sender**: the agent sees who
241
+ asked, and the other browsers show the name above the message instead of
242
+ "pilot (elsewhere)". The server takes that name from the session, never from
243
+ what the browser claims.
230
244
  - **In Docker**, `SHADOK_HOST=0.0.0.0` is the only value that works (the
231
245
  container's own loopback isn't reachable from the host). Publish the port on
232
246
  the host's loopback: `-p 127.0.0.1:3789:3789`. Plain `-p 3789:3789` publishes
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Web accounts, PER INSTANCE (per launch directory) — the same scope as
3
+ * channels, crons and the instance lock.
4
+ *
5
+ * Per instance rather than global is the consistent choice: SHADOK_GUI_PASSWORD
6
+ * is already per process, so accounts share the scope of the door they extend.
7
+ * Profiles and the secret vault are the global exception, not the rule.
8
+ */
9
+ export type Role = "admin" | "member";
10
+ export interface Account {
11
+ name: string;
12
+ role: Role;
13
+ /** Absent until an invitation is redeemed. */
14
+ passwordHash?: string;
15
+ createdAt: number;
16
+ /** Present only while the invitation is outstanding. */
17
+ invite?: {
18
+ token: string;
19
+ expiresAt: number;
20
+ };
21
+ }
22
+ /** The account that lives in SHADOK_GUI_PASSWORD, never in the file. */
23
+ export declare const BOOTSTRAP_ADMIN = "admin";
24
+ export declare function loadAccounts(): Account[];
25
+ export declare function saveAccounts(list: Account[]): void;
26
+ /** `scrypt$<salt hex>$<derived hex>` — salted, so two identical passwords do
27
+ * not produce the same hash and cannot be spotted as identical. */
28
+ export declare function hashPassword(plain: string): string;
29
+ export declare function verifyPassword(plain: string, hash: string): boolean;
30
+ /**
31
+ * Who may change which account. Pure, so the whole policy is one testable
32
+ * place rather than a condition repeated at three endpoints.
33
+ */
34
+ export declare function userWriteVerdict(o: {
35
+ actorRole: Role | null;
36
+ action: "create" | "delete" | "role";
37
+ target: string;
38
+ exists: boolean;
39
+ }): {
40
+ ok: true;
41
+ } | {
42
+ ok: false;
43
+ error: string;
44
+ };
45
+ /**
46
+ * The key that signs sessions — per instance, drawn once, persisted.
47
+ *
48
+ * NOT derived from SHADOK_GUI_PASSWORD, and never exported into an agent's
49
+ * environment. The password reaches every agent's env today (measured on three
50
+ * production agents, 2026-08-23); signing with it would let any agent mint a
51
+ * cookie for any user. Untidy becomes impersonation the moment accounts exist.
52
+ */
53
+ export declare function sessionSecret(): Buffer;
54
+ /** `<user base64url>.<issuedAt>.<hmac>` — the name is encoded so a dot in it
55
+ * cannot shift the fields. The ROLE is deliberately absent: it is re-read from
56
+ * the account file at use time, so a demotion takes effect immediately instead
57
+ * of riding in a stale cookie. */
58
+ export declare function signSession(user: string, issuedAt: number, secret: Buffer): string;
59
+ export declare function readSession(token: string, secret: Buffer, now: number, maxAgeMs: number): string | null;
60
+ /** A week: long enough to hand the link over by another channel, short enough
61
+ * that a forgotten one stops working. */
62
+ export declare const INVITE_TTL_MS: number;
63
+ export declare function newInvite(now: number): {
64
+ token: string;
65
+ expiresAt: number;
66
+ };
67
+ /**
68
+ * Whether this link may still be redeemed.
69
+ *
70
+ * Each refusal names its own reason: "expired" tells the holder to ask for a
71
+ * new link, "already redeemed" tells them the account is live, and "invalid" is
72
+ * a real mismatch. One generic error would send all three to the wrong place.
73
+ */
74
+ export declare function inviteVerdict(account: Account | undefined, token: string, now: number): {
75
+ ok: true;
76
+ } | {
77
+ ok: false;
78
+ error: string;
79
+ };
80
+ /**
81
+ * Who a prompt is attributed to.
82
+ *
83
+ * The security property of the accounts feature, in one place: for a WEB client
84
+ * the session decides and the frame's claim is discarded, because a browser can
85
+ * put anything in `from`. The Telegram bridge is a trusted bridge that knows its
86
+ * sender, so it keeps naming them; other origins (cli, cron) are the server's
87
+ * own callers and keep whatever they supplied.
88
+ */
89
+ export declare function promptAuthor(origin: string | undefined, sessionName: string | undefined, claimed: string | undefined): string | undefined;
@@ -0,0 +1,160 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createHmac, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
5
+ import { instanceKey } from "./paths.js";
6
+ /** The account that lives in SHADOK_GUI_PASSWORD, never in the file. */
7
+ export const BOOTSTRAP_ADMIN = "admin";
8
+ function storeFile() {
9
+ return path.join(os.homedir(), ".shadok-ai", "users", instanceKey() + ".json");
10
+ }
11
+ export function loadAccounts() {
12
+ try {
13
+ const v = JSON.parse(fs.readFileSync(storeFile(), "utf8"));
14
+ return Array.isArray(v) ? v.filter((a) => a && typeof a.name === "string") : [];
15
+ }
16
+ catch {
17
+ return [];
18
+ }
19
+ }
20
+ export function saveAccounts(list) {
21
+ const f = storeFile();
22
+ fs.mkdirSync(path.dirname(f), { recursive: true });
23
+ fs.writeFileSync(f, JSON.stringify(list, null, 2), { mode: 0o600 });
24
+ fs.chmodSync(f, 0o600);
25
+ }
26
+ /** `scrypt$<salt hex>$<derived hex>` — salted, so two identical passwords do
27
+ * not produce the same hash and cannot be spotted as identical. */
28
+ export function hashPassword(plain) {
29
+ const salt = randomBytes(16);
30
+ const key = scryptSync(plain, salt, 64);
31
+ return `scrypt$${salt.toString("hex")}$${key.toString("hex")}`;
32
+ }
33
+ export function verifyPassword(plain, hash) {
34
+ const parts = String(hash ?? "").split("$");
35
+ if (parts.length !== 3 || parts[0] !== "scrypt")
36
+ return false;
37
+ try {
38
+ const salt = Buffer.from(parts[1], "hex");
39
+ const want = Buffer.from(parts[2], "hex");
40
+ if (!salt.length || !want.length)
41
+ return false;
42
+ const got = scryptSync(plain, salt, want.length);
43
+ return got.length === want.length && timingSafeEqual(got, want);
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ /**
50
+ * Who may change which account. Pure, so the whole policy is one testable
51
+ * place rather than a condition repeated at three endpoints.
52
+ */
53
+ export function userWriteVerdict(o) {
54
+ if (o.actorRole !== "admin")
55
+ return { ok: false, error: "only an admin can manage accounts" };
56
+ const name = o.target.trim();
57
+ if (!name)
58
+ return { ok: false, error: "name required" };
59
+ if (o.action === "create") {
60
+ if (name === BOOTSTRAP_ADMIN)
61
+ return { ok: false, error: `"${BOOTSTRAP_ADMIN}" is reserved for the instance password` };
62
+ return o.exists ? { ok: false, error: `${name} already exists` } : { ok: true };
63
+ }
64
+ return o.exists ? { ok: true } : { ok: false, error: `no such account: ${name}` };
65
+ }
66
+ /**
67
+ * The key that signs sessions — per instance, drawn once, persisted.
68
+ *
69
+ * NOT derived from SHADOK_GUI_PASSWORD, and never exported into an agent's
70
+ * environment. The password reaches every agent's env today (measured on three
71
+ * production agents, 2026-08-23); signing with it would let any agent mint a
72
+ * cookie for any user. Untidy becomes impersonation the moment accounts exist.
73
+ */
74
+ export function sessionSecret() {
75
+ const f = path.join(os.homedir(), ".shadok-ai", "users", instanceKey() + ".key");
76
+ try {
77
+ const hex = fs.readFileSync(f, "utf8").trim();
78
+ if (hex.length >= 32)
79
+ return Buffer.from(hex, "hex");
80
+ }
81
+ catch {
82
+ /* first run, or unreadable: draw a new one below */
83
+ }
84
+ const secret = randomBytes(32);
85
+ fs.mkdirSync(path.dirname(f), { recursive: true });
86
+ fs.writeFileSync(f, secret.toString("hex"), { mode: 0o600 });
87
+ fs.chmodSync(f, 0o600);
88
+ return secret;
89
+ }
90
+ /** `<user base64url>.<issuedAt>.<hmac>` — the name is encoded so a dot in it
91
+ * cannot shift the fields. The ROLE is deliberately absent: it is re-read from
92
+ * the account file at use time, so a demotion takes effect immediately instead
93
+ * of riding in a stale cookie. */
94
+ export function signSession(user, issuedAt, secret) {
95
+ const u = Buffer.from(user, "utf8").toString("base64url");
96
+ const body = `${u}.${issuedAt}`;
97
+ return `${body}.${createHmac("sha256", secret).update(body).digest("hex")}`;
98
+ }
99
+ export function readSession(token, secret, now, maxAgeMs) {
100
+ const parts = String(token ?? "").split(".");
101
+ if (parts.length !== 3)
102
+ return null;
103
+ const [u, at, mac] = parts;
104
+ const issuedAt = Number(at);
105
+ if (!at || !Number.isFinite(issuedAt))
106
+ return null;
107
+ const want = createHmac("sha256", secret).update(`${u}.${at}`).digest("hex");
108
+ if (mac.length !== want.length)
109
+ return null;
110
+ if (!timingSafeEqual(Buffer.from(mac), Buffer.from(want)))
111
+ return null;
112
+ if (now - issuedAt > maxAgeMs)
113
+ return null;
114
+ try {
115
+ return Buffer.from(u, "base64url").toString("utf8") || null;
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ /** A week: long enough to hand the link over by another channel, short enough
122
+ * that a forgotten one stops working. */
123
+ export const INVITE_TTL_MS = 7 * 24 * 3600 * 1000;
124
+ export function newInvite(now) {
125
+ return { token: randomBytes(24).toString("base64url"), expiresAt: now + INVITE_TTL_MS };
126
+ }
127
+ /**
128
+ * Whether this link may still be redeemed.
129
+ *
130
+ * Each refusal names its own reason: "expired" tells the holder to ask for a
131
+ * new link, "already redeemed" tells them the account is live, and "invalid" is
132
+ * a real mismatch. One generic error would send all three to the wrong place.
133
+ */
134
+ export function inviteVerdict(account, token, now) {
135
+ // Redeeming DELETES the token, so a used link and a made-up one are
136
+ // indistinguishable by then — and keeping consumed tokens around just to tell
137
+ // them apart would be clutter with no payoff. Say what is true of both.
138
+ if (!account)
139
+ return { ok: false, error: "this link is no longer valid — it may already have been used. Ask for a new one" };
140
+ if (!account.invite)
141
+ return { ok: false, error: "this invitation was already redeemed — sign in instead" };
142
+ if (account.invite.token !== token)
143
+ return { ok: false, error: "invalid invitation" };
144
+ if (now > account.invite.expiresAt)
145
+ return { ok: false, error: "this invitation has expired — ask for a new link" };
146
+ return { ok: true };
147
+ }
148
+ /**
149
+ * Who a prompt is attributed to.
150
+ *
151
+ * The security property of the accounts feature, in one place: for a WEB client
152
+ * the session decides and the frame's claim is discarded, because a browser can
153
+ * put anything in `from`. The Telegram bridge is a trusted bridge that knows its
154
+ * sender, so it keeps naming them; other origins (cli, cron) are the server's
155
+ * own callers and keep whatever they supplied.
156
+ */
157
+ export function promptAuthor(origin, sessionName, claimed) {
158
+ return origin === "web" ? sessionName : claimed;
159
+ }
160
+ //# sourceMappingURL=accounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../src/accounts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The launch directory, encoded as a filename.
3
+ *
4
+ * Channels, crons and the instance lock each key their store on this same
5
+ * expression, inlined in four different files. Anything NEW that is per
6
+ * instance uses this one instead of adding a fifth copy — a store that keyed on
7
+ * a slightly different string would silently belong to another instance.
8
+ *
9
+ * The six existing sites are deliberately left alone: rewriting them would bury
10
+ * the change that needed this.
11
+ */
12
+ export declare function instanceKey(cwd?: string): string;
package/dist/paths.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The launch directory, encoded as a filename.
3
+ *
4
+ * Channels, crons and the instance lock each key their store on this same
5
+ * expression, inlined in four different files. Anything NEW that is per
6
+ * instance uses this one instead of adding a fifth copy — a store that keyed on
7
+ * a slightly different string would silently belong to another instance.
8
+ *
9
+ * The six existing sites are deliberately left alone: rewriting them would bury
10
+ * the change that needed this.
11
+ */
12
+ export function instanceKey(cwd = process.cwd()) {
13
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
14
+ }
15
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,GAAG,GAAW,OAAO,CAAC,GAAG,EAAE;IACrD,OAAO,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC"}