@barryfan2045/dsh-matrix 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/THIRD_PARTY_NOTICES.md +53 -0
- package/assets/avatars/instance-01.png +0 -0
- package/assets/avatars/instance-02.png +0 -0
- package/assets/avatars/instance-03.png +0 -0
- package/assets/avatars/instance-04.png +0 -0
- package/assets/avatars/instance-05.png +0 -0
- package/assets/avatars/instance-06.png +0 -0
- package/assets/avatars/instance-07.png +0 -0
- package/assets/avatars/instance-08.png +0 -0
- package/assets/avatars/instance-09.png +0 -0
- package/assets/avatars/instance-10.png +0 -0
- package/assets/avatars/instance-11.png +0 -0
- package/assets/avatars/instance-12.png +0 -0
- package/assets/avatars/management.png +0 -0
- package/cordis.patch.yml +13 -0
- package/dist/auth/admin-commands.d.ts +47 -0
- package/dist/auth/admin-commands.d.ts.map +1 -0
- package/dist/auth/admin-commands.js +112 -0
- package/dist/auth/admin-commands.js.map +1 -0
- package/dist/auth/challenge.d.ts +93 -0
- package/dist/auth/challenge.d.ts.map +1 -0
- package/dist/auth/challenge.js +104 -0
- package/dist/auth/challenge.js.map +1 -0
- package/dist/auth/store.d.ts +55 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +124 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/bridge/agent-commands.d.ts +39 -0
- package/dist/bridge/agent-commands.d.ts.map +1 -0
- package/dist/bridge/agent-commands.js +243 -0
- package/dist/bridge/agent-commands.js.map +1 -0
- package/dist/bridge/attachments-ledger.d.ts +54 -0
- package/dist/bridge/attachments-ledger.d.ts.map +1 -0
- package/dist/bridge/attachments-ledger.js +68 -0
- package/dist/bridge/attachments-ledger.js.map +1 -0
- package/dist/bridge/commands.d.ts +28 -0
- package/dist/bridge/commands.d.ts.map +1 -0
- package/dist/bridge/commands.js +18 -0
- package/dist/bridge/commands.js.map +1 -0
- package/dist/bridge/crypto-health.d.ts +37 -0
- package/dist/bridge/crypto-health.d.ts.map +1 -0
- package/dist/bridge/crypto-health.js +38 -0
- package/dist/bridge/crypto-health.js.map +1 -0
- package/dist/bridge/group-gate.d.ts +38 -0
- package/dist/bridge/group-gate.d.ts.map +1 -0
- package/dist/bridge/group-gate.js +49 -0
- package/dist/bridge/group-gate.js.map +1 -0
- package/dist/bridge/instance-lock.d.ts +10 -0
- package/dist/bridge/instance-lock.d.ts.map +1 -0
- package/dist/bridge/instance-lock.js +82 -0
- package/dist/bridge/instance-lock.js.map +1 -0
- package/dist/bridge/interactions.d.ts +62 -0
- package/dist/bridge/interactions.d.ts.map +1 -0
- package/dist/bridge/interactions.js +237 -0
- package/dist/bridge/interactions.js.map +1 -0
- package/dist/bridge/management-room.d.ts +13 -0
- package/dist/bridge/management-room.d.ts.map +1 -0
- package/dist/bridge/management-room.js +27 -0
- package/dist/bridge/management-room.js.map +1 -0
- package/dist/bridge/multi-project.d.ts +34 -0
- package/dist/bridge/multi-project.d.ts.map +1 -0
- package/dist/bridge/multi-project.js +44 -0
- package/dist/bridge/multi-project.js.map +1 -0
- package/dist/bridge/pipeline.d.ts +151 -0
- package/dist/bridge/pipeline.d.ts.map +1 -0
- package/dist/bridge/pipeline.js +502 -0
- package/dist/bridge/pipeline.js.map +1 -0
- package/dist/bridge/pmctl.d.ts +50 -0
- package/dist/bridge/pmctl.d.ts.map +1 -0
- package/dist/bridge/pmctl.js +272 -0
- package/dist/bridge/pmctl.js.map +1 -0
- package/dist/bridge/power-levels.d.ts +57 -0
- package/dist/bridge/power-levels.d.ts.map +1 -0
- package/dist/bridge/power-levels.js +92 -0
- package/dist/bridge/power-levels.js.map +1 -0
- package/dist/bridge/power-sync.d.ts +64 -0
- package/dist/bridge/power-sync.d.ts.map +1 -0
- package/dist/bridge/power-sync.js +121 -0
- package/dist/bridge/power-sync.js.map +1 -0
- package/dist/bridge/projects.d.ts +97 -0
- package/dist/bridge/projects.d.ts.map +1 -0
- package/dist/bridge/projects.js +288 -0
- package/dist/bridge/projects.js.map +1 -0
- package/dist/bridge/quote-cache.d.ts +38 -0
- package/dist/bridge/quote-cache.d.ts.map +1 -0
- package/dist/bridge/quote-cache.js +56 -0
- package/dist/bridge/quote-cache.js.map +1 -0
- package/dist/bridge/reply.d.ts +31 -0
- package/dist/bridge/reply.d.ts.map +1 -0
- package/dist/bridge/reply.js +122 -0
- package/dist/bridge/reply.js.map +1 -0
- package/dist/bridge/space-identity.d.ts +43 -0
- package/dist/bridge/space-identity.d.ts.map +1 -0
- package/dist/bridge/space-identity.js +92 -0
- package/dist/bridge/space-identity.js.map +1 -0
- package/dist/bridge/space-sync.d.ts +94 -0
- package/dist/bridge/space-sync.d.ts.map +1 -0
- package/dist/bridge/space-sync.js +137 -0
- package/dist/bridge/space-sync.js.map +1 -0
- package/dist/bridge/turn-commands.d.ts +28 -0
- package/dist/bridge/turn-commands.d.ts.map +1 -0
- package/dist/bridge/turn-commands.js +97 -0
- package/dist/bridge/turn-commands.js.map +1 -0
- package/dist/cli/config-store.d.ts +32 -0
- package/dist/cli/config-store.d.ts.map +1 -0
- package/dist/cli/config-store.js +118 -0
- package/dist/cli/config-store.js.map +1 -0
- package/dist/cli/crypto-store.d.ts +42 -0
- package/dist/cli/crypto-store.d.ts.map +1 -0
- package/dist/cli/crypto-store.js +70 -0
- package/dist/cli/crypto-store.js.map +1 -0
- package/dist/cli/device-prune.d.ts +105 -0
- package/dist/cli/device-prune.d.ts.map +1 -0
- package/dist/cli/device-prune.js +163 -0
- package/dist/cli/device-prune.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +584 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/log-filter.d.ts +32 -0
- package/dist/cli/log-filter.d.ts.map +1 -0
- package/dist/cli/log-filter.js +40 -0
- package/dist/cli/log-filter.js.map +1 -0
- package/dist/cli/pnpm-builds.d.ts +32 -0
- package/dist/cli/pnpm-builds.d.ts.map +1 -0
- package/dist/cli/pnpm-builds.js +73 -0
- package/dist/cli/pnpm-builds.js.map +1 -0
- package/dist/cli/profile-patch.d.ts +26 -0
- package/dist/cli/profile-patch.d.ts.map +1 -0
- package/dist/cli/profile-patch.js +56 -0
- package/dist/cli/profile-patch.js.map +1 -0
- package/dist/cli/setup.d.ts +39 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +234 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/systemd-hint.d.ts +11 -0
- package/dist/cli/systemd-hint.d.ts.map +1 -0
- package/dist/cli/systemd-hint.js +46 -0
- package/dist/cli/systemd-hint.js.map +1 -0
- package/dist/cli/systemd.d.ts +32 -0
- package/dist/cli/systemd.d.ts.map +1 -0
- package/dist/cli/systemd.js +58 -0
- package/dist/cli/systemd.js.map +1 -0
- package/dist/config.d.ts +54 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/dsh/backend.d.ts +110 -0
- package/dist/dsh/backend.d.ts.map +1 -0
- package/dist/dsh/backend.js +481 -0
- package/dist/dsh/backend.js.map +1 -0
- package/dist/dsh/interactions.d.ts +50 -0
- package/dist/dsh/interactions.d.ts.map +1 -0
- package/dist/dsh/interactions.js +141 -0
- package/dist/dsh/interactions.js.map +1 -0
- package/dist/dsh/stream.d.ts +32 -0
- package/dist/dsh/stream.d.ts.map +1 -0
- package/dist/dsh/stream.js +109 -0
- package/dist/dsh/stream.js.map +1 -0
- package/dist/formatting.d.ts +14 -0
- package/dist/formatting.d.ts.map +1 -0
- package/dist/formatting.js +63 -0
- package/dist/formatting.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +28 -0
- package/dist/logger.js.map +1 -0
- package/dist/matrix/matrix-events.d.ts +49 -0
- package/dist/matrix/matrix-events.d.ts.map +1 -0
- package/dist/matrix/matrix-events.js +89 -0
- package/dist/matrix/matrix-events.js.map +1 -0
- package/dist/matrix/transport.d.ts +192 -0
- package/dist/matrix/transport.d.ts.map +1 -0
- package/dist/matrix/transport.js +510 -0
- package/dist/matrix/transport.js.map +1 -0
- package/dist/ports/attachments.d.ts +51 -0
- package/dist/ports/attachments.d.ts.map +1 -0
- package/dist/ports/attachments.js +27 -0
- package/dist/ports/attachments.js.map +1 -0
- package/dist/ports/backend.d.ts +159 -0
- package/dist/ports/backend.d.ts.map +1 -0
- package/dist/ports/backend.js +25 -0
- package/dist/ports/backend.js.map +1 -0
- package/dist/ports/interactions.d.ts +65 -0
- package/dist/ports/interactions.d.ts.map +1 -0
- package/dist/ports/interactions.js +15 -0
- package/dist/ports/interactions.js.map +1 -0
- package/dist/ports/streaming.d.ts +45 -0
- package/dist/ports/streaming.d.ts.map +1 -0
- package/dist/ports/streaming.js +23 -0
- package/dist/ports/streaming.js.map +1 -0
- package/dist/ports/transport.d.ts +68 -0
- package/dist/ports/transport.d.ts.map +1 -0
- package/dist/ports/transport.js +2 -0
- package/dist/ports/transport.js.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T2: challenge-based DM authentication - pure strategy ported from
|
|
3
|
+
* pi-courier src/auth/challenge-auth.ts (MIT), Matrix-free and fully
|
|
4
|
+
* data-in/data-out:
|
|
5
|
+
*
|
|
6
|
+
* - unknown DM user → gateDm issues a one-shot 6-digit challenge; the code
|
|
7
|
+
* only ever leaves through the onCode log callback, the chat gets the
|
|
8
|
+
* prompt text (sent by the caller via Transport.send);
|
|
9
|
+
* - code replies are validated with submitCode (whole-message 6 digits);
|
|
10
|
+
* - 2-minute expiry, 3 wrong attempts → 5-minute lockout, one active
|
|
11
|
+
* challenge per user;
|
|
12
|
+
* - trust list + first-trusted-becomes-admin live in the injected AuthStore.
|
|
13
|
+
*
|
|
14
|
+
* Divergences from pi-courier (deliberate):
|
|
15
|
+
* - no `transport:` namespacing (identity is the raw MXID - single transport);
|
|
16
|
+
* - the admin is assigned AT GRANT TIME (first user ever trusted), not
|
|
17
|
+
* lazily on the next message;
|
|
18
|
+
* - clock/RNG are injectable so the lifecycle is table-testable.
|
|
19
|
+
*/
|
|
20
|
+
import type { AuthStore } from './store.ts';
|
|
21
|
+
export declare const CHALLENGE_TTL_MS: number;
|
|
22
|
+
export declare const MAX_ATTEMPTS = 3;
|
|
23
|
+
export declare const LOCKOUT_MS: number;
|
|
24
|
+
/** Room-facing prompt; the code itself only goes to the log (onCode). */
|
|
25
|
+
export declare const CHALLENGE_PROMPT = "\uD83D\uDD10 \u4F60\u662F\u964C\u751F\u7528\u6237\u3002\u6865\u7684\u5BBF\u4E3B\u65E5\u5FD7\u91CC\u521A\u6253\u5370\u4E86\u4E00\u4E32\u5C5E\u4E8E\u4F60\u7684 6 \u4F4D\u9A8C\u8BC1\u7801,\u8BF7\u628A\u5B83\u4F5C\u4E3A\u6D88\u606F\u53D1\u56DE\u6765\u3002\n\u23F1\uFE0F 2 \u5206\u949F\u5185\u6709\u6548\u3002";
|
|
26
|
+
export interface ChallengeDeps {
|
|
27
|
+
/** Clock injection point (epoch ms). Defaults to Date.now. */
|
|
28
|
+
now?: () => number;
|
|
29
|
+
/** RNG injection point in [0,1), used when codeSource is absent. */
|
|
30
|
+
random?: () => number;
|
|
31
|
+
/** RNG injection point for the whole code; wins over random (tests). */
|
|
32
|
+
codeSource?: () => string;
|
|
33
|
+
/** The fresh code is logged host-side only - never sent to the room. */
|
|
34
|
+
onCode?: (userId: string, code: string) => void;
|
|
35
|
+
/** Host-side lifecycle chatter (admin grant, lockout). */
|
|
36
|
+
notify?: (message: string, level?: 'info' | 'warn') => void;
|
|
37
|
+
/**
|
|
38
|
+
* Config-owner bootstrap: when the row config already ships a trustedUsers
|
|
39
|
+
* list, its first entry is seeded as admin instead of the first challenge
|
|
40
|
+
* winner (bridge rule: admin = AuthStore.adminUserId ?? 静态名单第一人).
|
|
41
|
+
*/
|
|
42
|
+
seedAdmin?: string;
|
|
43
|
+
}
|
|
44
|
+
export type DmDecision = {
|
|
45
|
+
kind: 'trusted';
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'challenged';
|
|
48
|
+
prompt: string;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'pending';
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'locked';
|
|
53
|
+
retryInMs: number;
|
|
54
|
+
};
|
|
55
|
+
export type CodeResult = {
|
|
56
|
+
kind: 'none';
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'authenticated';
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'expired';
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'wrong';
|
|
63
|
+
attemptsLeft: number;
|
|
64
|
+
} | {
|
|
65
|
+
kind: 'locked';
|
|
66
|
+
retryInMs: number;
|
|
67
|
+
};
|
|
68
|
+
export declare class ChallengeEngine {
|
|
69
|
+
private readonly store;
|
|
70
|
+
private challenges;
|
|
71
|
+
private lockedUntil;
|
|
72
|
+
private readonly now;
|
|
73
|
+
private readonly codeSource;
|
|
74
|
+
private readonly onCode?;
|
|
75
|
+
private readonly notify;
|
|
76
|
+
private readonly seedAdmin?;
|
|
77
|
+
constructor(store: AuthStore, deps?: ChallengeDeps);
|
|
78
|
+
isTrusted(userId: string): boolean;
|
|
79
|
+
/** Trust gate + challenge lifecycle for a DM message from a stranger. */
|
|
80
|
+
gateDm(userId: string): DmDecision;
|
|
81
|
+
/**
|
|
82
|
+
* Try to consume a message as a challenge-code reply. Returns none when
|
|
83
|
+
* the text is not a 6-digit code or the user has no active challenge
|
|
84
|
+
* (never counts as an attempt).
|
|
85
|
+
*/
|
|
86
|
+
submitCode(userId: string, text: string): CodeResult;
|
|
87
|
+
/** Drop any pending challenge/prompt state for a user (e.g. after revoke). */
|
|
88
|
+
reset(userId: string): void;
|
|
89
|
+
private grantTrust;
|
|
90
|
+
/** Lock expiry, or undefined when not locked (auto-cleans stale entries). */
|
|
91
|
+
private activeLock;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=challenge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"challenge.d.ts","sourceRoot":"","sources":["../../src/auth/challenge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,eAAO,MAAM,gBAAgB,QAAgB,CAAA;AAC7C,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,UAAU,QAAgB,CAAA;AAEvC,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,qTACkC,CAAA;AAE/D,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,MAAM,CAAA;IACzB,wEAAwE;IACxE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,0DAA0D;IAC1D,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAA;IAC3D;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzC,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AAQzC,qBAAa,eAAe;IAUd,OAAO,CAAC,QAAQ,CAAC,KAAK;IATlC,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,WAAW,CAA4B;IAE/C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAwC;IAChE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoD;IAC3E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAQ;gBAEN,KAAK,EAAE,SAAS,EAAE,IAAI,GAAE,aAAkB;IAUvE,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIlC,yEAAyE;IACzE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU;IAkBlC;;;;OAIG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU;IA+BpD,8EAA8E;IAC9E,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,CAAC,UAAU;IAYlB,6EAA6E;IAC7E,OAAO,CAAC,UAAU;CAOnB"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export const CHALLENGE_TTL_MS = 2 * 60 * 1000;
|
|
2
|
+
export const MAX_ATTEMPTS = 3;
|
|
3
|
+
export const LOCKOUT_MS = 5 * 60 * 1000;
|
|
4
|
+
/** Room-facing prompt; the code itself only goes to the log (onCode). */
|
|
5
|
+
export const CHALLENGE_PROMPT = '🔐 你是陌生用户。桥的宿主日志里刚打印了一串属于你的 6 位验证码,请把它作为消息发回来。\n⏱️ 2 分钟内有效。';
|
|
6
|
+
export class ChallengeEngine {
|
|
7
|
+
store;
|
|
8
|
+
challenges = new Map();
|
|
9
|
+
lockedUntil = new Map();
|
|
10
|
+
now;
|
|
11
|
+
codeSource;
|
|
12
|
+
onCode;
|
|
13
|
+
notify;
|
|
14
|
+
seedAdmin;
|
|
15
|
+
constructor(store, deps = {}) {
|
|
16
|
+
this.store = store;
|
|
17
|
+
this.now = deps.now ?? (() => Date.now());
|
|
18
|
+
const random = deps.random ?? Math.random;
|
|
19
|
+
this.codeSource = deps.codeSource
|
|
20
|
+
?? (() => String(Math.floor(100000 + random() * 900000)));
|
|
21
|
+
this.onCode = deps.onCode;
|
|
22
|
+
this.notify = deps.notify ?? (() => { });
|
|
23
|
+
this.seedAdmin = deps.seedAdmin;
|
|
24
|
+
}
|
|
25
|
+
isTrusted(userId) {
|
|
26
|
+
return this.store.get().trustedUsers.includes(userId);
|
|
27
|
+
}
|
|
28
|
+
/** Trust gate + challenge lifecycle for a DM message from a stranger. */
|
|
29
|
+
gateDm(userId) {
|
|
30
|
+
const lock = this.activeLock(userId);
|
|
31
|
+
if (lock !== undefined)
|
|
32
|
+
return { kind: 'locked', retryInMs: lock - this.now() };
|
|
33
|
+
if (this.isTrusted(userId))
|
|
34
|
+
return { kind: 'trusted' };
|
|
35
|
+
const active = this.challenges.get(userId);
|
|
36
|
+
if (active) {
|
|
37
|
+
if (this.now() <= active.expiresAt)
|
|
38
|
+
return { kind: 'pending' };
|
|
39
|
+
this.challenges.delete(userId); // expired: fall through to a fresh code
|
|
40
|
+
}
|
|
41
|
+
const code = this.codeSource();
|
|
42
|
+
this.challenges.set(userId, { code, expiresAt: this.now() + CHALLENGE_TTL_MS, attempts: 0 });
|
|
43
|
+
this.onCode?.(userId, code);
|
|
44
|
+
return { kind: 'challenged', prompt: CHALLENGE_PROMPT };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Try to consume a message as a challenge-code reply. Returns none when
|
|
48
|
+
* the text is not a 6-digit code or the user has no active challenge
|
|
49
|
+
* (never counts as an attempt).
|
|
50
|
+
*/
|
|
51
|
+
submitCode(userId, text) {
|
|
52
|
+
const trimmed = text.trim();
|
|
53
|
+
if (!/^\d{6}$/.test(trimmed))
|
|
54
|
+
return { kind: 'none' };
|
|
55
|
+
const lock = this.activeLock(userId);
|
|
56
|
+
if (lock !== undefined)
|
|
57
|
+
return { kind: 'locked', retryInMs: lock - this.now() };
|
|
58
|
+
const challenge = this.challenges.get(userId);
|
|
59
|
+
if (!challenge)
|
|
60
|
+
return { kind: 'none' };
|
|
61
|
+
if (this.now() > challenge.expiresAt) {
|
|
62
|
+
this.challenges.delete(userId);
|
|
63
|
+
return { kind: 'expired' };
|
|
64
|
+
}
|
|
65
|
+
if (trimmed === challenge.code) {
|
|
66
|
+
this.challenges.delete(userId);
|
|
67
|
+
this.grantTrust(userId);
|
|
68
|
+
return { kind: 'authenticated' };
|
|
69
|
+
}
|
|
70
|
+
challenge.attempts++;
|
|
71
|
+
if (challenge.attempts >= MAX_ATTEMPTS) {
|
|
72
|
+
this.challenges.delete(userId);
|
|
73
|
+
this.lockedUntil.set(userId, this.now() + LOCKOUT_MS);
|
|
74
|
+
this.notify(`🚫 ${userId} 连错 ${MAX_ATTEMPTS} 次,锁定 5 分钟`, 'warn');
|
|
75
|
+
return { kind: 'locked', retryInMs: LOCKOUT_MS };
|
|
76
|
+
}
|
|
77
|
+
return { kind: 'wrong', attemptsLeft: MAX_ATTEMPTS - challenge.attempts };
|
|
78
|
+
}
|
|
79
|
+
/** Drop any pending challenge/prompt state for a user (e.g. after revoke). */
|
|
80
|
+
reset(userId) {
|
|
81
|
+
this.challenges.delete(userId);
|
|
82
|
+
}
|
|
83
|
+
grantTrust(userId) {
|
|
84
|
+
const state = this.store.get();
|
|
85
|
+
if (!state.trustedUsers.includes(userId))
|
|
86
|
+
state.trustedUsers.push(userId);
|
|
87
|
+
const becameAdmin = state.adminUserId === undefined;
|
|
88
|
+
if (becameAdmin)
|
|
89
|
+
state.adminUserId = this.seedAdmin ?? userId;
|
|
90
|
+
this.store.set(state);
|
|
91
|
+
this.notify(`✅ ${userId} 认证通过${becameAdmin && state.adminUserId === userId ? ',成为管理员' : ''}`, 'info');
|
|
92
|
+
}
|
|
93
|
+
/** Lock expiry, or undefined when not locked (auto-cleans stale entries). */
|
|
94
|
+
activeLock(userId) {
|
|
95
|
+
const until = this.lockedUntil.get(userId);
|
|
96
|
+
if (until === undefined)
|
|
97
|
+
return undefined;
|
|
98
|
+
if (this.now() < until)
|
|
99
|
+
return until;
|
|
100
|
+
this.lockedUntil.delete(userId);
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=challenge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"challenge.js","sourceRoot":"","sources":["../../src/auth/challenge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type ChannelMode = 'all' | 'mentions' | 'trusted-only';
|
|
2
|
+
export interface ChannelAuth {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
mode: ChannelMode;
|
|
5
|
+
}
|
|
6
|
+
export interface AuthState {
|
|
7
|
+
/** Matrix MXIDs granted trust at runtime (union'd with the static config list). */
|
|
8
|
+
trustedUsers: string[];
|
|
9
|
+
/** First runtime-authenticated user becomes admin (pi-courier rule). */
|
|
10
|
+
adminUserId?: string;
|
|
11
|
+
/** Group-room policy map; schema only in T2, T8 consumes it. */
|
|
12
|
+
channels: Record<string, ChannelAuth>;
|
|
13
|
+
}
|
|
14
|
+
export declare function emptyAuthState(): AuthState;
|
|
15
|
+
/** Lenient parse: keeps well-formed fields, drops junk - never throws. */
|
|
16
|
+
export declare function parseAuthState(raw: unknown): AuthState;
|
|
17
|
+
export interface AuthStore {
|
|
18
|
+
/** Pull persisted state into memory (missing/corrupt source → empty state). */
|
|
19
|
+
load(): Promise<AuthState>;
|
|
20
|
+
/** Current in-memory snapshot (a copy; callers never mutate it in place). */
|
|
21
|
+
get(): AuthState;
|
|
22
|
+
/** Replace state; implementations schedule persistence (non-blocking). */
|
|
23
|
+
set(next: AuthState): void;
|
|
24
|
+
/** Flush the current state now and await durability. */
|
|
25
|
+
save(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/** Test fake: in-memory only; `saves` lets tests assert persistence points. */
|
|
28
|
+
export declare class MemoryAuthStore implements AuthStore {
|
|
29
|
+
readonly saves: AuthState[];
|
|
30
|
+
private state;
|
|
31
|
+
constructor(initial?: AuthState);
|
|
32
|
+
load(): Promise<AuthState>;
|
|
33
|
+
get(): AuthState;
|
|
34
|
+
set(next: AuthState): void;
|
|
35
|
+
save(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export declare function expandHome(p: string): string;
|
|
38
|
+
export declare const DEFAULT_AUTH_STORE_PATH = "~/.dsh/dsh-matrix-auth.json";
|
|
39
|
+
export interface JsonFileStoreOptions {
|
|
40
|
+
/** Receive non-fatal I/O / parse errors (wire to the bridge logger). */
|
|
41
|
+
onError?: (error: unknown) => void;
|
|
42
|
+
}
|
|
43
|
+
export declare class JsonFileStore implements AuthStore {
|
|
44
|
+
private readonly path;
|
|
45
|
+
private readonly options;
|
|
46
|
+
private state;
|
|
47
|
+
private pending;
|
|
48
|
+
constructor(path?: string, options?: JsonFileStoreOptions);
|
|
49
|
+
load(): Promise<AuthState>;
|
|
50
|
+
get(): AuthState;
|
|
51
|
+
set(next: AuthState): void;
|
|
52
|
+
save(): Promise<void>;
|
|
53
|
+
private write;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/auth/store.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,UAAU,GAAG,cAAc,CAAA;AAE7D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,WAAW,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,mFAAmF;IACnF,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CACtC;AAED,wBAAgB,cAAc,IAAI,SAAS,CAE1C;AASD,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,CActD;AAED,MAAM,WAAW,SAAS;IACxB,+EAA+E;IAC/E,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC,CAAA;IAC1B,6EAA6E;IAC7E,GAAG,IAAI,SAAS,CAAA;IAChB,0EAA0E;IAC1E,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;IAC1B,wDAAwD;IACxD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAWD,+EAA+E;AAC/E,qBAAa,eAAgB,YAAW,SAAS;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,CAAK;IAChC,OAAO,CAAC,KAAK,CAAW;gBAEZ,OAAO,GAAE,SAA4B;IAI3C,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC;IAGhC,GAAG,IAAI,SAAS;IAGhB,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAIpB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI5C;AAED,eAAO,MAAM,uBAAuB,gCAAgC,CAAA;AAEpE,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACnC;AAED,qBAAa,aAAc,YAAW,SAAS;IAK3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,OAAO,CAAmC;gBAG/B,IAAI,GAAE,MAAgC,EACtC,OAAO,GAAE,oBAAyB;IAG/C,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC;IAahC,GAAG,IAAI,SAAS;IAIhB,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAOpB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAKb,KAAK;CAQpB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T2: runtime auth state persistence.
|
|
3
|
+
*
|
|
4
|
+
* The row config's trustedUsers is a BOOT SNAPSHOT (read-only at start);
|
|
5
|
+
* everything the challenge flow and admin commands change at runtime lives
|
|
6
|
+
* here, persisted to ~/.dsh/dsh-matrix-auth.json (0600, tmp+rename atomic).
|
|
7
|
+
*
|
|
8
|
+
* AuthStore is the interface the bridge codes against; MemoryAuthStore is
|
|
9
|
+
* the test fake, JsonFileStore the production implementation.
|
|
10
|
+
*/
|
|
11
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
export function emptyAuthState() {
|
|
15
|
+
return { trustedUsers: [], channels: {} };
|
|
16
|
+
}
|
|
17
|
+
function isChannelAuth(value) {
|
|
18
|
+
if (typeof value !== 'object' || value === null)
|
|
19
|
+
return false;
|
|
20
|
+
const v = value;
|
|
21
|
+
return typeof v.enabled === 'boolean'
|
|
22
|
+
&& (v.mode === 'all' || v.mode === 'mentions' || v.mode === 'trusted-only');
|
|
23
|
+
}
|
|
24
|
+
/** Lenient parse: keeps well-formed fields, drops junk - never throws. */
|
|
25
|
+
export function parseAuthState(raw) {
|
|
26
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
27
|
+
return emptyAuthState();
|
|
28
|
+
const v = raw;
|
|
29
|
+
const state = emptyAuthState();
|
|
30
|
+
if (Array.isArray(v.trustedUsers)) {
|
|
31
|
+
state.trustedUsers = v.trustedUsers.filter((x) => typeof x === 'string' && x.length > 0);
|
|
32
|
+
}
|
|
33
|
+
if (typeof v.adminUserId === 'string' && v.adminUserId)
|
|
34
|
+
state.adminUserId = v.adminUserId;
|
|
35
|
+
if (typeof v.channels === 'object' && v.channels !== null && !Array.isArray(v.channels)) {
|
|
36
|
+
for (const [roomId, entry] of Object.entries(v.channels)) {
|
|
37
|
+
if (isChannelAuth(entry))
|
|
38
|
+
state.channels[roomId] = { enabled: entry.enabled, mode: entry.mode };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return state;
|
|
42
|
+
}
|
|
43
|
+
function cloneState(s) {
|
|
44
|
+
return {
|
|
45
|
+
trustedUsers: [...s.trustedUsers],
|
|
46
|
+
...(s.adminUserId !== undefined ? { adminUserId: s.adminUserId } : {}),
|
|
47
|
+
channels: Object.fromEntries(Object.entries(s.channels).map(([k, c]) => [k, { enabled: c.enabled, mode: c.mode }])),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Test fake: in-memory only; `saves` lets tests assert persistence points. */
|
|
51
|
+
export class MemoryAuthStore {
|
|
52
|
+
saves = [];
|
|
53
|
+
state;
|
|
54
|
+
constructor(initial = emptyAuthState()) {
|
|
55
|
+
this.state = cloneState(initial);
|
|
56
|
+
}
|
|
57
|
+
async load() {
|
|
58
|
+
return cloneState(this.state);
|
|
59
|
+
}
|
|
60
|
+
get() {
|
|
61
|
+
return cloneState(this.state);
|
|
62
|
+
}
|
|
63
|
+
set(next) {
|
|
64
|
+
this.state = cloneState(next);
|
|
65
|
+
void this.save();
|
|
66
|
+
}
|
|
67
|
+
async save() {
|
|
68
|
+
this.saves.push(cloneState(this.state));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function expandHome(p) {
|
|
72
|
+
if (p === '~')
|
|
73
|
+
return homedir();
|
|
74
|
+
if (p.startsWith('~/'))
|
|
75
|
+
return join(homedir(), p.slice(2));
|
|
76
|
+
return p;
|
|
77
|
+
}
|
|
78
|
+
export const DEFAULT_AUTH_STORE_PATH = '~/.dsh/dsh-matrix-auth.json';
|
|
79
|
+
export class JsonFileStore {
|
|
80
|
+
path;
|
|
81
|
+
options;
|
|
82
|
+
state = emptyAuthState();
|
|
83
|
+
pending = Promise.resolve();
|
|
84
|
+
constructor(path = DEFAULT_AUTH_STORE_PATH, options = {}) {
|
|
85
|
+
this.path = path;
|
|
86
|
+
this.options = options;
|
|
87
|
+
}
|
|
88
|
+
async load() {
|
|
89
|
+
try {
|
|
90
|
+
const raw = await readFile(expandHome(this.path), 'utf8');
|
|
91
|
+
this.state = parseAuthState(JSON.parse(raw));
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// ENOENT is the normal first-run case; anything else (corrupt JSON)
|
|
95
|
+
// degrades to empty state so the bridge still boots.
|
|
96
|
+
if (error?.code !== 'ENOENT')
|
|
97
|
+
this.options.onError?.(error);
|
|
98
|
+
this.state = emptyAuthState();
|
|
99
|
+
}
|
|
100
|
+
return cloneState(this.state);
|
|
101
|
+
}
|
|
102
|
+
get() {
|
|
103
|
+
return cloneState(this.state);
|
|
104
|
+
}
|
|
105
|
+
set(next) {
|
|
106
|
+
this.state = cloneState(next);
|
|
107
|
+
// Fire-and-persist: set() is on the message hot path; a failed write is
|
|
108
|
+
// logged, never thrown back into the pipeline (pi-courier semantics).
|
|
109
|
+
this.pending = this.pending.then(() => this.write()).catch((error) => this.options.onError?.(error));
|
|
110
|
+
}
|
|
111
|
+
async save() {
|
|
112
|
+
this.pending = this.pending.then(() => this.write()).catch((error) => this.options.onError?.(error));
|
|
113
|
+
await this.pending;
|
|
114
|
+
}
|
|
115
|
+
async write() {
|
|
116
|
+
const path = expandHome(this.path);
|
|
117
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
118
|
+
// Atomic: full write into a unique tmp sibling, then rename over.
|
|
119
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
120
|
+
await writeFile(tmp, JSON.stringify(this.state, null, 2), { mode: 0o600 });
|
|
121
|
+
await rename(tmp, path);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/auth/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T4: bridge-local command surface + DSH registry passthrough.
|
|
3
|
+
*
|
|
4
|
+
* The pipeline hands every slash line here (after the trust gate, before
|
|
5
|
+
* any prompt). Bridge-owned commands (model / sessions / …) run against the
|
|
6
|
+
* Backend port; everything else is passed through to ctx.commands.execute
|
|
7
|
+
* against the room's agent. A line the DSH registry does not know is NEVER
|
|
8
|
+
* forwarded to the model — it settles as an "unknown command" reply.
|
|
9
|
+
*
|
|
10
|
+
* Kept in its own file so parallel tickets touching pipeline.ts stay away
|
|
11
|
+
* from this route table (pipeline only holds the commandRouter hook).
|
|
12
|
+
*/
|
|
13
|
+
import type { Backend } from '../ports/backend.ts';
|
|
14
|
+
import type { Transport } from '../ports/transport.ts';
|
|
15
|
+
import type { Logger } from '../types.ts';
|
|
16
|
+
export interface CommandRouteContext {
|
|
17
|
+
roomId: string;
|
|
18
|
+
sender: string;
|
|
19
|
+
}
|
|
20
|
+
/** The narrow slice of this module the pipeline depends on. */
|
|
21
|
+
export interface CommandRouter {
|
|
22
|
+
isCommand(text: string): boolean;
|
|
23
|
+
handle(text: string, ctx: CommandRouteContext): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export interface CommandRouterDeps {
|
|
26
|
+
backend: Backend;
|
|
27
|
+
transport: Transport;
|
|
28
|
+
logger: Logger;
|
|
29
|
+
}
|
|
30
|
+
interface BridgeCommandSpec {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly args?: string;
|
|
33
|
+
readonly description: string;
|
|
34
|
+
}
|
|
35
|
+
/** Table doubles as /help rendering; handlers register by the same names. */
|
|
36
|
+
export declare const BRIDGE_COMMANDS: readonly BridgeCommandSpec[];
|
|
37
|
+
export declare function createCommandRouter(deps: CommandRouterDeps): CommandRouter;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=agent-commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-commands.d.ts","sourceRoot":"","sources":["../../src/bridge/agent-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9D;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,iBAAiB;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,EAAE,SAAS,iBAAiB,EASvD,CAAA;AAaD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAuO1E"}
|