shadok-ai 0.15.37 → 0.15.38

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
@@ -392,7 +392,7 @@ know before opening it up:
392
392
  - **That password is the `admin` account.** With it set, the login page gains a
393
393
  user field — leave it blank to sign in as the admin. An admin can then invite
394
394
  other people from the **Users** panel: each invitation is a single-use link,
395
- valid 7 days, on which the invitee chooses their own password, so the admin
395
+ valid 48 hours, on which the invitee chooses their own password, so the admin
396
396
  never learns it — and redeeming the link signs them in straight away. The ⋯
397
397
  menu names who you are and lets you sign out. Two roles: `admin` manages
398
398
  accounts, `member` does everything
@@ -414,11 +414,30 @@ know before opening it up:
414
414
  new device, sign in with the password, choose *use a code*, register. A
415
415
  password alone never registers a passkey — that is what stops a stolen
416
416
  password from enrolling the thief's phone.
417
- - **Lost password or lost device**: another admin clicks *reset access* on your
418
- row (confirming with their own passkey). Your password and every passkey are
419
- removed and they hand you a fresh invitation link, on which you choose a new
420
- password and register the device you have. Nobody can reset themselves. An
421
- admin can also hand a member a *device code* directly.
417
+ - **Lost something**: another admin opens *recover access…* on your row and
418
+ picks what you lost (always confirming with their own passkey). Nobody can
419
+ recover themselves.
420
+ - *lost password* — one admin; you get a link (48 h), choose a new password,
421
+ then confirm with one of your passkeys, which are all kept;
422
+ - *lost passkey* — a **second admin approves** from their Security panel; you
423
+ then get a device code (48 h) to register a new device;
424
+ - *lost everything* — password and passkeys are wiped **24 hours** later, or
425
+ as soon as a second admin approves; you, the asker or any admin can cancel
426
+ it meanwhile.
427
+ Making or unmaking an admin also needs a second admin's approval. A device
428
+ code for someone who has no passkey yet is still one admin's act. Every
429
+ session opened before a recovery stops working — including one on the lost
430
+ device; removing a passkey or demoting an admin does the same.
431
+ - While it is on, inviting, deleting an account and changing a role also ask
432
+ the admin for their passkey.
433
+ - **Security panel** (🛡 in the header, a badge when something is new): what
434
+ was done to your account — invited, made admin, reset, a passkey added or
435
+ removed, a recovery asked about you — and, for admins, the whole log, the
436
+ requests waiting for their approval, and their own requests with the link or
437
+ code to hand over once done. On anything you don't recognise,
438
+ **This wasn't me** ends your other sessions, removes a passkey you did not
439
+ add, and flags it to every admin. There is no mail yet: this panel is the
440
+ channel, so keep an eye on its badge.
422
441
  - While it is on, the instance password (`admin`) **cannot sign in to the
423
442
  browser** — it lives in every agent's environment and no one could reset it;
424
443
  use a named account. Agents, Telegram and peers keep working (they never use
@@ -13,10 +13,13 @@ export interface Account {
13
13
  /** Absent until an invitation is redeemed. */
14
14
  passwordHash?: string;
15
15
  createdAt: number;
16
- /** Present only while the invitation is outstanding. */
16
+ /** Present only while the invitation is outstanding. `reason: "password"`
17
+ * marks a level-1 reset: the passkeys were kept, so redeeming the link sets
18
+ * a password and then asks for one of them. */
17
19
  invite?: {
18
20
  token: string;
19
21
  expiresAt: number;
22
+ reason?: "password";
20
23
  };
21
24
  /** Registered passkeys — the second factor (see `src/twofactor.ts`). */
22
25
  passkeys?: Passkey[];
@@ -27,6 +30,10 @@ export interface Account {
27
30
  expiresAt: number;
28
31
  by: string;
29
32
  };
33
+ /** A session cookie issued before this instant is refused. Set when access
34
+ * is reset, a passkey removed or an admin demoted: without it a session
35
+ * opened before the reset (a stolen one, a lost phone's) outlived it. */
36
+ sessionsAfter?: number;
30
37
  }
31
38
  export interface Passkey {
32
39
  /** Credential id, base64url. */
@@ -86,10 +93,13 @@ export declare function signSession(user: string, issuedAt: number, secret: Buff
86
93
  export declare function readSessionFull(token: string, secret: Buffer, now: number, maxAgeMs: number): {
87
94
  user: string;
88
95
  mfa: boolean;
96
+ issuedAt: number;
89
97
  } | null;
90
98
  export declare function readSession(token: string, secret: Buffer, now: number, maxAgeMs: number): string | null;
91
- /** A week: long enough to hand the link over by another channel, short enough
92
- * that a forgotten one stops working. */
99
+ /** Is a session issued at `issuedAt` still good for this account? */
100
+ export declare function sessionAlive(issuedAt: number, acct: Pick<Account, "sessionsAfter">): boolean;
101
+ /** 48 hours: enough to hand the link over by another channel, short enough
102
+ * that a link left lying in a chat stops working. Was a week. */
93
103
  export declare const INVITE_TTL_MS: number;
94
104
  export declare function newInvite(now: number): {
95
105
  token: string;
package/dist/accounts.js CHANGED
@@ -124,7 +124,7 @@ export function readSessionFull(token, secret, now, maxAgeMs) {
124
124
  return null;
125
125
  try {
126
126
  const user = Buffer.from(u, "base64url").toString("utf8");
127
- return user ? { user, mfa: mfa === "1" } : null;
127
+ return user ? { user, mfa: mfa === "1", issuedAt } : null;
128
128
  }
129
129
  catch {
130
130
  return null;
@@ -133,9 +133,13 @@ export function readSessionFull(token, secret, now, maxAgeMs) {
133
133
  export function readSession(token, secret, now, maxAgeMs) {
134
134
  return readSessionFull(token, secret, now, maxAgeMs)?.user ?? null;
135
135
  }
136
- /** A week: long enough to hand the link over by another channel, short enough
137
- * that a forgotten one stops working. */
138
- export const INVITE_TTL_MS = 7 * 24 * 3600 * 1000;
136
+ /** Is a session issued at `issuedAt` still good for this account? */
137
+ export function sessionAlive(issuedAt, acct) {
138
+ return !acct.sessionsAfter || issuedAt >= acct.sessionsAfter;
139
+ }
140
+ /** 48 hours: enough to hand the link over by another channel, short enough
141
+ * that a link left lying in a chat stops working. Was a week. */
142
+ export const INVITE_TTL_MS = 48 * 3600 * 1000;
139
143
  export function newInvite(now) {
140
144
  return { token: randomBytes(24).toString("base64url"), expiresAt: now + INVITE_TTL_MS };
141
145
  }
@@ -1 +1 @@
1
- {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../src/accounts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../src/accounts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ export type RequestKind = "passkey-reset" | "full-reset" | "promote" | "demote";
2
+ export interface AdminRequest {
3
+ id: string;
4
+ kind: RequestKind;
5
+ target: string;
6
+ /** The admin who asked. */
7
+ by: string;
8
+ createdAt: number;
9
+ /** The second admin, once approved. */
10
+ approvedBy?: string;
11
+ /** full-reset only: when it takes effect on its own. */
12
+ effectiveAt?: number;
13
+ /** Set when it took effect / was cancelled or refused; the row then only
14
+ * lingers so its asker can read the outcome. */
15
+ doneAt?: number;
16
+ outcome?: "applied" | "cancelled" | "refused" | "expired";
17
+ /** Who cancelled or refused. */
18
+ closedBy?: string;
19
+ /** What the asker hands over once applied: a reset link or a device code. */
20
+ handover?: {
21
+ kind: "link" | "code";
22
+ value: string;
23
+ expiresAt: number;
24
+ };
25
+ }
26
+ /** An unapproved request dies after this. */
27
+ export declare const REQUEST_TTL_MS: number;
28
+ /** Level 3's waiting time. */
29
+ export declare const FULL_RESET_DELAY_MS: number;
30
+ /** A closed row stays this long so its outcome (and handover) can be read. */
31
+ export declare const CLOSED_KEEP_MS: number;
32
+ type Verdict = {
33
+ ok: true;
34
+ } | {
35
+ ok: false;
36
+ error: string;
37
+ };
38
+ interface Who {
39
+ name: string;
40
+ role: string;
41
+ }
42
+ interface Acct {
43
+ name: string;
44
+ role: string;
45
+ }
46
+ export declare function newRequest(kind: RequestKind, target: string, by: string, now: number): AdminRequest;
47
+ export declare const isOpen: (r: AdminRequest) => boolean;
48
+ /** May `actor` ask for this? */
49
+ export declare function askVerdict(o: {
50
+ actor: Who | null;
51
+ kind: RequestKind;
52
+ target: string;
53
+ accounts: Acct[];
54
+ }): Verdict;
55
+ /** May `approver` approve this request now? */
56
+ export declare function approveVerdict(r: AdminRequest | undefined, approver: Who | null, now: number): Verdict;
57
+ /**
58
+ * May `who` cancel it? The asker, any OTHER admin (the 24 h of level 3 are
59
+ * worth what the other admins' attention is worth), or the target themself —
60
+ * a request made behind their back is precisely what they must be able to stop.
61
+ */
62
+ export declare function cancelVerdict(r: AdminRequest | undefined, who: Who | null): Verdict;
63
+ /** Open full-resets whose 24 h are over: to apply now. */
64
+ export declare function dueRequests(list: AdminRequest[], now: number): AdminRequest[];
65
+ /** Close open requests that ran out without approval. */
66
+ export declare function expire(list: AdminRequest[], now: number): AdminRequest[];
67
+ /** Add a request, replacing an open one of the same kind on the same target,
68
+ * and dropping closed rows past their keep window. */
69
+ export declare function upsertRequest(list: AdminRequest[], r: AdminRequest, now: number): AdminRequest[];
70
+ /** What the Security panel shows a viewer: what they may approve, and what they asked. */
71
+ export declare function requestsFor(viewer: Who, list: AdminRequest[], now: number): {
72
+ waiting: AdminRequest[];
73
+ mine: AdminRequest[];
74
+ };
75
+ export declare function describeRequest(r: AdminRequest): string;
76
+ export declare function loadRequests(): AdminRequest[];
77
+ export declare function saveRequests(list: AdminRequest[]): void;
78
+ export {};
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Two-admin requests — 2FA v2 (spec docs/superpowers/specs/2026-10-28-2fa-v2-recovery-design.md
3
+ * §§ 4–5). What one admin may no longer do alone while two-factor is on:
4
+ *
5
+ * - `passkey-reset` (level 2): let someone who lost their passkey register a
6
+ * new device — a second admin approves, then a device code is issued;
7
+ * - `full-reset` (level 3): wipe password and passkeys — effective 24 h after
8
+ * the ask, or at once when a second admin approves;
9
+ * - `promote` / `demote`: make or unmake an admin.
10
+ *
11
+ * Stored beside the accounts (`~/.shadok-ai/users/<key>.requests.json`, 600).
12
+ * No timer anywhere: a due request is APPLIED by whoever next reads the list
13
+ * (`dueRequests`), so a restart loses nothing and nothing has to stay alive.
14
+ */
15
+ import { randomBytes } from "node:crypto";
16
+ import fs from "node:fs";
17
+ import os from "node:os";
18
+ import path from "node:path";
19
+ import { instanceKey } from "./paths.js";
20
+ /** An unapproved request dies after this. */
21
+ export const REQUEST_TTL_MS = 48 * 3600 * 1000;
22
+ /** Level 3's waiting time. */
23
+ export const FULL_RESET_DELAY_MS = 24 * 3600 * 1000;
24
+ /** A closed row stays this long so its outcome (and handover) can be read. */
25
+ export const CLOSED_KEEP_MS = 7 * 24 * 3600 * 1000;
26
+ export function newRequest(kind, target, by, now) {
27
+ return {
28
+ id: randomBytes(12).toString("base64url"),
29
+ kind,
30
+ target,
31
+ by,
32
+ createdAt: now,
33
+ ...(kind === "full-reset" ? { effectiveAt: now + FULL_RESET_DELAY_MS } : {}),
34
+ };
35
+ }
36
+ export const isOpen = (r) => r.doneAt == null;
37
+ /** May `actor` ask for this? */
38
+ export function askVerdict(o) {
39
+ if (!o.actor || o.actor.role !== "admin")
40
+ return { ok: false, error: "only an admin can ask for this" };
41
+ if (o.actor.name === o.target)
42
+ return { ok: false, error: "not for yourself — ask another admin" };
43
+ const t = o.accounts.find((a) => a.name === o.target);
44
+ if (!t)
45
+ return { ok: false, error: `no such account: ${o.target}` };
46
+ if (o.kind === "promote" && t.role === "admin")
47
+ return { ok: false, error: `${o.target} is already an admin` };
48
+ if (o.kind === "demote" && t.role !== "admin")
49
+ return { ok: false, error: `${o.target} is not an admin` };
50
+ return { ok: true };
51
+ }
52
+ /** May `approver` approve this request now? */
53
+ export function approveVerdict(r, approver, now) {
54
+ if (!r || !isOpen(r))
55
+ return { ok: false, error: "no such open request" };
56
+ if (!approver || approver.role !== "admin")
57
+ return { ok: false, error: "only an admin can approve" };
58
+ if (approver.name === r.by)
59
+ return { ok: false, error: "you asked for it — a SECOND admin must approve" };
60
+ if (approver.name === r.target)
61
+ return { ok: false, error: "it concerns you — another admin must approve" };
62
+ if (r.approvedBy)
63
+ return { ok: false, error: "already approved" };
64
+ if (r.kind !== "full-reset" && now - r.createdAt > REQUEST_TTL_MS)
65
+ return { ok: false, error: "this request has expired" };
66
+ return { ok: true };
67
+ }
68
+ /**
69
+ * May `who` cancel it? The asker, any OTHER admin (the 24 h of level 3 are
70
+ * worth what the other admins' attention is worth), or the target themself —
71
+ * a request made behind their back is precisely what they must be able to stop.
72
+ */
73
+ export function cancelVerdict(r, who) {
74
+ if (!r || !isOpen(r))
75
+ return { ok: false, error: "no such open request" };
76
+ if (!who)
77
+ return { ok: false, error: "sign in first" };
78
+ if (who.name === r.by || who.name === r.target || who.role === "admin")
79
+ return { ok: true };
80
+ return { ok: false, error: "only an admin, the asker or the person concerned can cancel this" };
81
+ }
82
+ /** Open full-resets whose 24 h are over: to apply now. */
83
+ export function dueRequests(list, now) {
84
+ return list.filter((r) => isOpen(r) && r.kind === "full-reset" && r.effectiveAt != null && now >= r.effectiveAt);
85
+ }
86
+ /** Close open requests that ran out without approval. */
87
+ export function expire(list, now) {
88
+ return list.map((r) => isOpen(r) && r.kind !== "full-reset" && now - r.createdAt > REQUEST_TTL_MS
89
+ ? { ...r, doneAt: now, outcome: "expired" }
90
+ : r);
91
+ }
92
+ /** Add a request, replacing an open one of the same kind on the same target,
93
+ * and dropping closed rows past their keep window. */
94
+ export function upsertRequest(list, r, now) {
95
+ return [
96
+ ...list.filter((x) => !(isOpen(x) && x.kind === r.kind && x.target === r.target) && !(x.doneAt != null && now - x.doneAt > CLOSED_KEEP_MS)),
97
+ r,
98
+ ];
99
+ }
100
+ /** What the Security panel shows a viewer: what they may approve, and what they asked. */
101
+ export function requestsFor(viewer, list, now) {
102
+ const waiting = viewer.role === "admin"
103
+ ? list.filter((r) => approveVerdict(r, viewer, now).ok)
104
+ : [];
105
+ const mine = list.filter((r) => r.by === viewer.name).sort((a, b) => b.createdAt - a.createdAt);
106
+ // Every OTHER admin can see (and cancel) an open full reset they did not ask
107
+ // for, even though approving it is one click away in `waiting` already.
108
+ return { waiting, mine };
109
+ }
110
+ export function describeRequest(r) {
111
+ switch (r.kind) {
112
+ case "passkey-reset": return `${r.by} asks to let ${r.target} register a new device (lost passkey)`;
113
+ case "full-reset": return `${r.by} asks for a FULL reset of ${r.target} (password and passkeys)`;
114
+ case "promote": return `${r.by} asks to make ${r.target} admin`;
115
+ case "demote": return `${r.by} asks to make ${r.target} member`;
116
+ }
117
+ }
118
+ // ── Storage ──────────────────────────────────────────────────────────────────
119
+ function requestsFile() {
120
+ return path.join(os.homedir(), ".shadok-ai", "users", instanceKey() + ".requests.json");
121
+ }
122
+ export function loadRequests() {
123
+ try {
124
+ const v = JSON.parse(fs.readFileSync(requestsFile(), "utf8"));
125
+ return Array.isArray(v) ? v.filter((r) => r && typeof r.id === "string") : [];
126
+ }
127
+ catch {
128
+ return [];
129
+ }
130
+ }
131
+ export function saveRequests(list) {
132
+ const f = requestsFile();
133
+ fs.mkdirSync(path.dirname(f), { recursive: true });
134
+ fs.writeFileSync(f, JSON.stringify(list, null, 2), { mode: 0o600 });
135
+ fs.chmodSync(f, 0o600);
136
+ }
137
+ //# sourceMappingURL=admin-requests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-requests.js","sourceRoot":"","sources":["../src/admin-requests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyBzC,6CAA6C;AAC7C,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC/C,8BAA8B;AAC9B,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACpD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAMnD,MAAM,UAAU,UAAU,CAAC,IAAiB,EAAE,MAAc,EAAE,EAAU,EAAE,GAAW;IACnF,OAAO;QACL,EAAE,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QACzC,IAAI;QACJ,MAAM;QACN,EAAE;QACF,SAAS,EAAE,GAAG;QACd,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,GAAG,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7E,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAe,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC;AAE5D,gCAAgC;AAChC,MAAM,UAAU,UAAU,CAAC,CAA6E;IACtG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC;IACxG,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;IACnG,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACpE,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,MAAM,sBAAsB,EAAE,CAAC;IAC/G,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,MAAM,kBAAkB,EAAE,CAAC;IAC1G,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,cAAc,CAAC,CAA2B,EAAE,QAAoB,EAAE,GAAW;IAC3F,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAC1E,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2BAA2B,EAAE,CAAC;IACrG,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gDAAgD,EAAE,CAAC;IAC1G,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,8CAA8C,EAAE,CAAC;IAC5G,IAAI,CAAC,CAAC,UAAU;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAClE,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,GAAG,CAAC,CAAC,SAAS,GAAG,cAAc;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;IAC3H,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,CAA2B,EAAE,GAAe;IACxE,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAC1E,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;IACvD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC5F,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kEAAkE,EAAE,CAAC;AAClG,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,WAAW,CAAC,IAAoB,EAAE,GAAW;IAC3D,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,WAAW,IAAI,IAAI,IAAI,GAAG,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC;AACnH,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,MAAM,CAAC,IAAoB,EAAE,GAAW;IACtD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACpB,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,GAAG,CAAC,CAAC,SAAS,GAAG,cAAc;QACxE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,SAAkB,EAAE;QACpD,CAAC,CAAC,CAAC,CACN,CAAC;AACJ,CAAC;AAED;uDACuD;AACvD,MAAM,UAAU,aAAa,CAAC,IAAoB,EAAE,CAAe,EAAE,GAAW;IAC9E,OAAO;QACL,GAAG,IAAI,CAAC,MAAM,CACZ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC,MAAM,GAAG,cAAc,CAAC,CAC5H;QACD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,WAAW,CAAC,MAAW,EAAE,IAAoB,EAAE,GAAW;IACxE,MAAM,OAAO,GACX,MAAM,CAAC,IAAI,KAAK,OAAO;QACrB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;QACvD,CAAC,CAAC,EAAE,CAAC;IACT,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAChG,6EAA6E;IAC7E,wEAAwE;IACxE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,CAAe;IAC7C,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,eAAe,EAAE,OAAO,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,MAAM,uCAAuC,CAAC;QACpG,KAAK,YAAY,EAAE,OAAO,GAAG,CAAC,CAAC,EAAE,6BAA6B,CAAC,CAAC,MAAM,0BAA0B,CAAC;QACjG,KAAK,SAAS,EAAE,OAAO,GAAG,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,MAAM,QAAQ,CAAC;QAChE,KAAK,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,MAAM,SAAS,CAAC;IAClE,CAAC;AACH,CAAC;AAED,gFAAgF;AAEhF,SAAS,YAAY;IACnB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,gBAAgB,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAC9D,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAoB;IAC/C,MAAM,CAAC,GAAG,YAAY,EAAE,CAAC;IACzB,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,76 @@
1
+ export type SecEventKind = "invited" | "invite-redeemed" | "removed" | "role-changed" | "access-reset" | "passkey-added" | "passkey-removed" | "device-code" | "two-factor" | "request" | "not-me";
2
+ export interface SecEvent {
3
+ id: string;
4
+ at: number;
5
+ kind: SecEventKind;
6
+ /** Who did it (an account name, or "admin" for the instance password). */
7
+ actor: string;
8
+ /** Whose account it concerns. */
9
+ target: string;
10
+ /** Kind-specific detail: the new role, a passkey's label, "on"/"off". */
11
+ detail?: string;
12
+ /** The passkey concerned, so "this wasn't me" can remove it. */
13
+ passkeyId?: string;
14
+ /** For a `not-me`: the event it disowns. */
15
+ about?: string;
16
+ /** For a `request` event: the two-admin request it reports on, so "this
17
+ * wasn't me" can cancel it while it is still open. */
18
+ requestId?: string;
19
+ /** Who has opened it in the panel. */
20
+ seenBy?: string[];
21
+ /** Already disowned by its target — the button is spent. */
22
+ disowned?: boolean;
23
+ }
24
+ /** Bounds: the log is an audit trail, not an archive. */
25
+ export declare const MAX_EVENTS = 500;
26
+ export declare const MAX_AGE_MS: number;
27
+ export declare function appendEvent(list: SecEvent[], ev: Omit<SecEvent, "id" | "at">, now: number, id?: string): SecEvent[];
28
+ /**
29
+ * Which events concern the viewer as a TARGET, i.e. what they need to hear
30
+ * about: anything someone else did to their account, and every passkey added
31
+ * or removed on it even by "themselves" — whoever holds their password and a
32
+ * code IS "themselves" to the server, which is exactly the case to catch.
33
+ */
34
+ export declare function aboutYou(e: SecEvent, viewer: string): boolean;
35
+ export interface Inbox {
36
+ /** Events about the viewer, newest first. */
37
+ aboutYou: (SecEvent & {
38
+ unread: boolean;
39
+ })[];
40
+ /** Admins only: every event, newest first. Empty for a member. */
41
+ log: (SecEvent & {
42
+ unread: boolean;
43
+ })[];
44
+ /** What needs the viewer: unread notices about them, and — for an admin —
45
+ * unread "this wasn't me" flags. */
46
+ badge: number;
47
+ }
48
+ export declare function inboxFor(viewer: {
49
+ name: string;
50
+ role: string;
51
+ }, events: SecEvent[]): Inbox;
52
+ /** Mark everything the viewer can see as seen by them. */
53
+ export declare function markSeen(events: SecEvent[], viewer: {
54
+ name: string;
55
+ role: string;
56
+ }): SecEvent[];
57
+ /**
58
+ * What "this wasn't me" undoes for an event, or why it cannot. Every case ends
59
+ * the account's other sessions; a passkey nobody admits to adding is removed.
60
+ * Only the event's TARGET may disown it, once.
61
+ */
62
+ export declare function notMeEffect(e: SecEvent | undefined, viewer: string): {
63
+ ok: true;
64
+ removePasskey?: string;
65
+ cancelRequest?: string;
66
+ } | {
67
+ ok: false;
68
+ error: string;
69
+ };
70
+ /** One line a person reads, for the panel. */
71
+ export declare function describeEvent(e: SecEvent): string;
72
+ export declare function loadEvents(): SecEvent[];
73
+ export declare function saveEvents(list: SecEvent[]): void;
74
+ /** Record one event. Never throws: losing an audit line must not fail the
75
+ * action it describes (the console line beside each call still exists). */
76
+ export declare function recordEvent(ev: Omit<SecEvent, "id" | "at">): SecEvent | null;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The Security panel's events — what happened to accounts and second factors,
3
+ * kept per instance beside the accounts (`~/.shadok-ai/users/<key>.events.json`,
4
+ * 600). Spec: docs/superpowers/specs/2026-10-28-2fa-v2-recovery-design.md § 6.
5
+ *
6
+ * No mail sender exists, so this is THE channel that tells someone what was
7
+ * done to their account, and the place they can say "this wasn't me". Pure
8
+ * cores (`appendEvent`, `inboxFor`, `notMeEffect`) are tested; the file I/O is
9
+ * the thin part.
10
+ */
11
+ import { randomBytes } from "node:crypto";
12
+ import fs from "node:fs";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import { instanceKey } from "./paths.js";
16
+ /** Bounds: the log is an audit trail, not an archive. */
17
+ export const MAX_EVENTS = 500;
18
+ export const MAX_AGE_MS = 90 * 24 * 3600 * 1000;
19
+ export function appendEvent(list, ev, now, id) {
20
+ const row = { id: id ?? randomBytes(9).toString("base64url"), at: now, ...ev };
21
+ return [...list.filter((e) => now - e.at <= MAX_AGE_MS), row].slice(-MAX_EVENTS);
22
+ }
23
+ /**
24
+ * Which events concern the viewer as a TARGET, i.e. what they need to hear
25
+ * about: anything someone else did to their account, and every passkey added
26
+ * or removed on it even by "themselves" — whoever holds their password and a
27
+ * code IS "themselves" to the server, which is exactly the case to catch.
28
+ */
29
+ export function aboutYou(e, viewer) {
30
+ if (e.target !== viewer || e.kind === "not-me")
31
+ return false;
32
+ if (e.kind === "passkey-added" || e.kind === "passkey-removed")
33
+ return true;
34
+ return e.actor !== viewer;
35
+ }
36
+ export function inboxFor(viewer, events) {
37
+ const mark = (e) => ({ ...e, unread: !(e.seenBy ?? []).includes(viewer.name) });
38
+ const newest = [...events].sort((a, b) => b.at - a.at);
39
+ const mine = newest.filter((e) => aboutYou(e, viewer.name)).map(mark);
40
+ const admin = viewer.role === "admin";
41
+ const log = admin ? newest.map(mark) : [];
42
+ const flags = admin ? log.filter((e) => e.kind === "not-me" && e.unread).length : 0;
43
+ return { aboutYou: mine, log, badge: mine.filter((e) => e.unread).length + flags };
44
+ }
45
+ /** Mark everything the viewer can see as seen by them. */
46
+ export function markSeen(events, viewer) {
47
+ const visible = (e) => viewer.role === "admin" || aboutYou(e, viewer.name);
48
+ return events.map((e) => visible(e) && !(e.seenBy ?? []).includes(viewer.name) ? { ...e, seenBy: [...(e.seenBy ?? []), viewer.name] } : e);
49
+ }
50
+ /**
51
+ * What "this wasn't me" undoes for an event, or why it cannot. Every case ends
52
+ * the account's other sessions; a passkey nobody admits to adding is removed.
53
+ * Only the event's TARGET may disown it, once.
54
+ */
55
+ export function notMeEffect(e, viewer) {
56
+ if (!e || !aboutYou(e, viewer))
57
+ return { ok: false, error: "no such notice for you" };
58
+ if (e.disowned)
59
+ return { ok: false, error: "already reported" };
60
+ return {
61
+ ok: true,
62
+ ...(e.kind === "passkey-added" && e.passkeyId ? { removePasskey: e.passkeyId } : {}),
63
+ ...(e.requestId ? { cancelRequest: e.requestId } : {}),
64
+ };
65
+ }
66
+ /** One line a person reads, for the panel. */
67
+ export function describeEvent(e) {
68
+ const by = e.actor === e.target ? "you" : e.actor;
69
+ switch (e.kind) {
70
+ case "invited": return `${e.actor} invited ${e.target}${e.detail ? " as " + e.detail : ""}`;
71
+ case "invite-redeemed": return `${e.target} set their password from an invitation or reset link`;
72
+ case "removed": return `${e.actor} removed the account ${e.target}`;
73
+ case "role-changed": return `${e.actor} made ${e.target} ${e.detail ?? "?"}`;
74
+ case "access-reset": return `${e.actor} reset ${e.target}'s access (password and passkeys removed)`;
75
+ case "passkey-added": return `a passkey${e.detail ? " “" + e.detail + "”" : ""} was registered on ${e.target}'s account (by ${by})`;
76
+ case "passkey-removed": return `a passkey was removed from ${e.target}'s account (by ${by})`;
77
+ case "device-code": return `${e.actor} issued a device code for ${e.target}`;
78
+ case "two-factor": return `${e.actor} turned two-factor sign-in ${e.detail ?? ""}`.trim();
79
+ case "request": return e.detail ?? `a request about ${e.target}`;
80
+ case "not-me": return `⚠ ${e.actor} reported “this wasn't me”: ${e.detail ?? ""}`.trim();
81
+ }
82
+ }
83
+ // ── Storage ──────────────────────────────────────────────────────────────────
84
+ function eventsFile() {
85
+ return path.join(os.homedir(), ".shadok-ai", "users", instanceKey() + ".events.json");
86
+ }
87
+ export function loadEvents() {
88
+ try {
89
+ const v = JSON.parse(fs.readFileSync(eventsFile(), "utf8"));
90
+ return Array.isArray(v) ? v.filter((e) => e && typeof e.id === "string") : [];
91
+ }
92
+ catch {
93
+ return [];
94
+ }
95
+ }
96
+ export function saveEvents(list) {
97
+ const f = eventsFile();
98
+ fs.mkdirSync(path.dirname(f), { recursive: true });
99
+ fs.writeFileSync(f, JSON.stringify(list, null, 2), { mode: 0o600 });
100
+ fs.chmodSync(f, 0o600);
101
+ }
102
+ /** Record one event. Never throws: losing an audit line must not fail the
103
+ * action it describes (the console line beside each call still exists). */
104
+ export function recordEvent(ev) {
105
+ try {
106
+ const list = appendEvent(loadEvents(), ev, Date.now());
107
+ saveEvents(list);
108
+ return list[list.length - 1];
109
+ }
110
+ catch (err) {
111
+ console.log(`security: could not record ${ev.kind} — ${err.message}`);
112
+ return null;
113
+ }
114
+ }
115
+ //# sourceMappingURL=security-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-events.js","sourceRoot":"","sources":["../src/security-events.ts"],"names":[],"mappings":"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"}