@schlessera/brain-ui-server 0.13.1 → 0.15.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/README.md +51 -12
- package/dist/agent/backend.d.ts +163 -37
- package/dist/agent/backend.d.ts.map +1 -1
- package/dist/agent/backend.js +281 -286
- package/dist/agent/backend.js.map +1 -1
- package/dist/app.d.ts +46 -5
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +94 -35
- package/dist/app.js.map +1 -1
- package/dist/brain/client.d.ts +41 -30
- package/dist/brain/client.d.ts.map +1 -1
- package/dist/brain/client.js +138 -132
- package/dist/brain/client.js.map +1 -1
- package/dist/config/env.d.ts +104 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +296 -0
- package/dist/config/env.js.map +1 -0
- package/dist/cron/scheduler.d.ts +16 -8
- package/dist/cron/scheduler.d.ts.map +1 -1
- package/dist/cron/scheduler.js +54 -53
- package/dist/cron/scheduler.js.map +1 -1
- package/dist/db/brain-db.d.ts +50 -0
- package/dist/db/brain-db.d.ts.map +1 -0
- package/dist/db/brain-db.js +75 -0
- package/dist/db/brain-db.js.map +1 -0
- package/dist/db/client.d.ts +8 -6
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +12 -25
- package/dist/db/client.js.map +1 -1
- package/dist/db/settings.d.ts +5 -4
- package/dist/db/settings.d.ts.map +1 -1
- package/dist/db/settings.js +9 -12
- package/dist/db/settings.js.map +1 -1
- package/dist/files/walker.d.ts +6 -7
- package/dist/files/walker.d.ts.map +1 -1
- package/dist/files/walker.js +6 -9
- package/dist/files/walker.js.map +1 -1
- package/dist/graph/reader.d.ts +6 -6
- package/dist/graph/reader.d.ts.map +1 -1
- package/dist/graph/reader.js +22 -12
- package/dist/graph/reader.js.map +1 -1
- package/dist/index.d.ts +15 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -10
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts +17 -6
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +34 -43
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/origin.d.ts +1 -2
- package/dist/middleware/origin.d.ts.map +1 -1
- package/dist/middleware/origin.js +1 -7
- package/dist/middleware/origin.js.map +1 -1
- package/dist/middleware/passkeys.d.ts +29 -5
- package/dist/middleware/passkeys.d.ts.map +1 -1
- package/dist/middleware/passkeys.js +47 -76
- package/dist/middleware/passkeys.js.map +1 -1
- package/dist/middleware/tailscale.d.ts +14 -11
- package/dist/middleware/tailscale.d.ts.map +1 -1
- package/dist/middleware/tailscale.js +11 -10
- package/dist/middleware/tailscale.js.map +1 -1
- package/dist/routes/brain.d.ts +9 -154
- package/dist/routes/brain.d.ts.map +1 -1
- package/dist/routes/brain.js +187 -188
- package/dist/routes/brain.js.map +1 -1
- package/dist/routes/files.d.ts +4 -88
- package/dist/routes/files.d.ts.map +1 -1
- package/dist/routes/files.js +74 -71
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/graph.d.ts +4 -255
- package/dist/routes/graph.d.ts.map +1 -1
- package/dist/routes/graph.js +67 -64
- package/dist/routes/graph.js.map +1 -1
- package/dist/routes/health.d.ts +8 -22
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +10 -10
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/models.d.ts +7 -90
- package/dist/routes/models.d.ts.map +1 -1
- package/dist/routes/models.js +53 -51
- package/dist/routes/models.js.map +1 -1
- package/dist/routes/providers.d.ts +5 -34
- package/dist/routes/providers.d.ts.map +1 -1
- package/dist/routes/providers.js +13 -11
- package/dist/routes/providers.js.map +1 -1
- package/dist/routes/sessions.d.ts +7 -78
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +35 -34
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/share.d.ts +7 -60
- package/dist/routes/share.d.ts.map +1 -1
- package/dist/routes/share.js +80 -77
- package/dist/routes/share.js.map +1 -1
- package/dist/routes/voice.d.ts +8 -91
- package/dist/routes/voice.d.ts.map +1 -1
- package/dist/routes/voice.js +62 -59
- package/dist/routes/voice.js.map +1 -1
- package/dist/share/staging.d.ts +4 -4
- package/dist/share/staging.d.ts.map +1 -1
- package/dist/share/staging.js +7 -8
- package/dist/share/staging.js.map +1 -1
- package/dist/voice/deepgram-token.d.ts +1 -1
- package/dist/voice/deepgram-token.d.ts.map +1 -1
- package/dist/voice/deepgram-token.js +1 -2
- package/dist/voice/deepgram-token.js.map +1 -1
- package/dist/voice/keyterm-builder.d.ts +23 -8
- package/dist/voice/keyterm-builder.d.ts.map +1 -1
- package/dist/voice/keyterm-builder.js +67 -43
- package/dist/voice/keyterm-builder.js.map +1 -1
- package/dist/voice/speech-providers.d.ts +6 -4
- package/dist/voice/speech-providers.d.ts.map +1 -1
- package/dist/voice/speech-providers.js +28 -25
- package/dist/voice/speech-providers.js.map +1 -1
- package/dist/ws/bridge.d.ts.map +1 -1
- package/dist/ws/bridge.js +2 -3
- package/dist/ws/bridge.js.map +1 -1
- package/dist/ws/clients.d.ts +18 -10
- package/dist/ws/clients.d.ts.map +1 -1
- package/dist/ws/clients.js +35 -37
- package/dist/ws/clients.js.map +1 -1
- package/dist/ws/connection.d.ts.map +1 -1
- package/dist/ws/connection.js +5 -7
- package/dist/ws/connection.js.map +1 -1
- package/dist/ws/dispatch.d.ts.map +1 -1
- package/dist/ws/dispatch.js +1 -2
- package/dist/ws/dispatch.js.map +1 -1
- package/dist/ws/host.d.ts +16 -12
- package/dist/ws/host.d.ts.map +1 -1
- package/dist/ws/host.js +14 -23
- package/dist/ws/host.js.map +1 -1
- package/dist/ws/routing.d.ts +2 -1
- package/dist/ws/routing.d.ts.map +1 -1
- package/dist/ws/routing.js +5 -6
- package/dist/ws/routing.js.map +1 -1
- package/dist/ws/run-session.js +1 -1
- package/dist/ws/run-session.js.map +1 -1
- package/dist/ws/session-catalog.d.ts +2 -2
- package/dist/ws/session-catalog.d.ts.map +1 -1
- package/dist/ws/session-catalog.js +2 -3
- package/dist/ws/session-catalog.js.map +1 -1
- package/package.json +8 -5
- package/src/agent/backend.ts +509 -340
- package/src/app.ts +157 -36
- package/src/brain/client.ts +166 -143
- package/src/config/env.ts +401 -0
- package/src/cron/scheduler.ts +77 -59
- package/src/db/brain-db.ts +104 -0
- package/src/db/client.ts +12 -31
- package/src/db/settings.ts +12 -14
- package/src/files/walker.ts +6 -10
- package/src/graph/reader.ts +26 -15
- package/src/index.ts +56 -17
- package/src/middleware/auth.ts +51 -45
- package/src/middleware/origin.ts +1 -8
- package/src/middleware/passkeys.ts +72 -68
- package/src/middleware/tailscale.ts +22 -17
- package/src/routes/brain.ts +31 -30
- package/src/routes/files.ts +10 -7
- package/src/routes/graph.ts +9 -6
- package/src/routes/health.ts +17 -10
- package/src/routes/models.ts +19 -15
- package/src/routes/providers.ts +14 -15
- package/src/routes/sessions.ts +18 -11
- package/src/routes/share.ts +35 -24
- package/src/routes/voice.ts +78 -68
- package/src/share/staging.ts +10 -8
- package/src/voice/deepgram-token.ts +4 -2
- package/src/voice/keyterm-builder.ts +90 -56
- package/src/voice/speech-providers.ts +28 -24
- package/src/ws/bridge.ts +2 -3
- package/src/ws/clients.ts +38 -41
- package/src/ws/connection.ts +5 -7
- package/src/ws/dispatch.ts +1 -2
- package/src/ws/host.ts +24 -27
- package/src/ws/routing.ts +6 -10
- package/src/ws/run-session.ts +1 -1
- package/src/ws/session-catalog.ts +2 -3
- package/dist/ws/handler.d.ts +0 -34
- package/dist/ws/handler.d.ts.map +0 -1
- package/dist/ws/handler.js +0 -33
- package/dist/ws/handler.js.map +0 -1
- package/src/ws/handler.ts +0 -50
package/dist/index.js
CHANGED
|
@@ -5,24 +5,34 @@
|
|
|
5
5
|
* decides the port/idleTimeout, wires SIGTERM, and injects deployment-only
|
|
6
6
|
* pieces (static client build, PNG/PDF renderer). Everything else — routes,
|
|
7
7
|
* auth, passkeys, the WebSocket turn coordinator, the session catalog — lives
|
|
8
|
-
* behind createApp()
|
|
8
|
+
* behind createApp(), which returns a handle carrying the app's own resources
|
|
9
|
+
* (config, database, ws host) instead of module-level singletons.
|
|
9
10
|
*/
|
|
10
|
-
export { createApp } from "./app.js";
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
export {
|
|
11
|
+
export { createApp, } from "./app.js";
|
|
12
|
+
// Configuration: the package's single environment chokepoint. The descriptor
|
|
13
|
+
// (ENV_VARS) is the artifact the env-parity gate diffs against documentation.
|
|
14
|
+
export { ENV_VARS, resolveServerConfig, } from "./config/env.js";
|
|
14
15
|
// Boot-time diagnostics (fail fast on a bad AGENT_BACKEND / profile config,
|
|
15
16
|
// log the resolved auth mode).
|
|
16
|
-
export {
|
|
17
|
-
export { resolveAuthMode } from "./middleware/auth.js";
|
|
17
|
+
export { assertBackendResolvable, createBackendRegistry, createStaticBackendRegistry, } from "./agent/backend.js";
|
|
18
|
+
export { resolveAuthMode, } from "./middleware/auth.js";
|
|
19
|
+
// The app's own SQLite database (sessions, passkeys, settings).
|
|
20
|
+
export { createUiDb } from "./db/client.js";
|
|
21
|
+
// Brain database access (read-only, schema-gated) for embedders adding
|
|
22
|
+
// their own readers.
|
|
23
|
+
export { openBrainDb, withBrainDb, BrainDbUnavailableError, MIN_BRAIN_SCHEMA_VERSION, } from "./db/brain-db.js";
|
|
18
24
|
// WebSocket internals for embedders and tests.
|
|
19
|
-
export { WsHost
|
|
25
|
+
export { WsHost } from "./ws/host.js";
|
|
26
|
+
export { createWsUpgrade, websocket } from "./ws/connection.js";
|
|
27
|
+
export { handleClientMessage } from "./ws/dispatch.js";
|
|
28
|
+
export { resolveTurnTarget } from "./ws/routing.js";
|
|
29
|
+
export { createSessionCatalog, } from "./ws/session-catalog.js";
|
|
20
30
|
// Brain repo access (spawned CLI wrapper) — useful for embedders that add
|
|
21
31
|
// their own routes on top.
|
|
22
|
-
export
|
|
32
|
+
export { createBrainClient } from "./brain/client.js";
|
|
23
33
|
// Share staging: a deployment can sweep expired staging dirs at boot; the
|
|
24
34
|
// intake route also sweeps opportunistically on every share.
|
|
25
35
|
export { pruneShareStaging, shareStagingRoot } from "./share/staging.js";
|
|
26
36
|
// Voice keyterm cache rebuild (used by deployments after `brain sync`).
|
|
27
|
-
export { buildKeyterms, writeCache } from "./voice/keyterm-builder.js";
|
|
37
|
+
export { buildKeyterms, writeCache, } from "./voice/keyterm-builder.js";
|
|
28
38
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,SAAS,GAIV,MAAM,UAAU,CAAC;AAElB,6EAA6E;AAC7E,8EAA8E;AAC9E,OAAO,EACL,QAAQ,EACR,mBAAmB,GAOpB,MAAM,iBAAiB,CAAC;AAEzB,4EAA4E;AAC5E,+BAA+B;AAC/B,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,2BAA2B,GAI5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,eAAe,GAGhB,MAAM,sBAAsB,CAAC;AAE9B,gEAAgE;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,uEAAuE;AACvE,qBAAqB;AACrB,OAAO,EACL,WAAW,EACX,WAAW,EACX,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAE1B,+CAA+C;AAC/C,OAAO,EAAE,MAAM,EAAsB,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EACL,oBAAoB,GAErB,MAAM,yBAAyB,CAAC;AAEjC,0EAA0E;AAC1E,2BAA2B;AAC3B,OAAO,EAAE,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAExE,0EAA0E;AAC1E,6DAA6D;AAC7D,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEzE,wEAAwE;AACxE,OAAO,EACL,aAAa,EACb,UAAU,GAEX,MAAM,4BAA4B,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import type { Context, MiddlewareHandler } from "hono";
|
|
3
|
+
import type { AuthConfig } from "../config/env.js";
|
|
3
4
|
/**
|
|
4
5
|
* Authentication for a remote surface to an agent with write access to the
|
|
5
6
|
* user's files. Modes (AUTH_MODE):
|
|
@@ -17,18 +18,28 @@ import type { Context, MiddlewareHandler } from "hono";
|
|
|
17
18
|
* explicit BRAIN_UI_DANGEROUSLY_DISABLE_AUTH=1 escape hatch.
|
|
18
19
|
*
|
|
19
20
|
* Default: `password` when BRAIN_UI_PASSWORD_HASH is set, else `tailscale`.
|
|
21
|
+
*
|
|
22
|
+
* All configuration is injected as the resolved {@link AuthRuntime} — this
|
|
23
|
+
* module never reads the environment, so two apps with different auth
|
|
24
|
+
* configuration can coexist and tests vary it without global mutation. The
|
|
25
|
+
* validation semantics themselves are unchanged.
|
|
20
26
|
*/
|
|
21
27
|
export type AuthMode = "password" | "tailscale" | "proxy" | "none";
|
|
28
|
+
/** What the auth layer needs from the resolved server config. */
|
|
29
|
+
export interface AuthRuntime extends AuthConfig {
|
|
30
|
+
/** Bind host, for the loopback check on AUTH_MODE=none. */
|
|
31
|
+
host: string;
|
|
32
|
+
}
|
|
22
33
|
export declare const LOGIN_RATE_LIMIT = 5;
|
|
23
34
|
export declare const GLOBAL_LOGIN_RATE_LIMIT = 20;
|
|
24
|
-
export declare function resolveAuthMode(): AuthMode;
|
|
35
|
+
export declare function resolveAuthMode(auth: AuthRuntime): AuthMode;
|
|
25
36
|
/**
|
|
26
37
|
* Validate the auth configuration at startup. Throws (refusing to boot) on an
|
|
27
38
|
* unsafe or unusable configuration.
|
|
28
39
|
*/
|
|
29
|
-
export declare function assertAuthConfig(mode: AuthMode): void;
|
|
40
|
+
export declare function assertAuthConfig(mode: AuthMode, auth: AuthRuntime): void;
|
|
30
41
|
/** Middleware guarding /api/* according to the resolved mode. */
|
|
31
|
-
export declare function authGuard(mode: AuthMode): MiddlewareHandler;
|
|
42
|
+
export declare function authGuard(mode: AuthMode, auth: AuthRuntime): MiddlewareHandler;
|
|
32
43
|
/**
|
|
33
44
|
* Authorize a WebSocket upgrade. The upgrade handler must call this itself —
|
|
34
45
|
* browsers can't set headers on the WS handshake, so the session cookie (sent
|
|
@@ -36,18 +47,18 @@ export declare function authGuard(mode: AuthMode): MiddlewareHandler;
|
|
|
36
47
|
* middleware may sit on the WS route (immutable-header errors). Mirrors
|
|
37
48
|
* {@link authGuard} without emitting a response.
|
|
38
49
|
*/
|
|
39
|
-
export declare function isWsAuthorized(c: Context, mode: AuthMode): Promise<boolean>;
|
|
50
|
+
export declare function isWsAuthorized(c: Context, mode: AuthMode, auth: AuthRuntime): Promise<boolean>;
|
|
40
51
|
/**
|
|
41
52
|
* Mint the session cookie. The single place a session comes into existence —
|
|
42
53
|
* both password login and passkey login (middleware/passkeys.ts) call this, so
|
|
43
54
|
* authGuard / isWsAuthorized / TTL semantics stay identical across methods.
|
|
44
55
|
*/
|
|
45
|
-
export declare function issueSessionCookie(c: Context): Promise<void>;
|
|
56
|
+
export declare function issueSessionCookie(c: Context, auth: AuthRuntime): Promise<void>;
|
|
46
57
|
/** Test-only: clear all rate-limit buckets (they are module-global state). */
|
|
47
58
|
export declare function resetLoginRateLimiter(): void;
|
|
48
59
|
export declare function consumeLoginToken(key: string, limit: number): boolean;
|
|
49
60
|
/** Login/logout routes. Only functional in `password` mode. */
|
|
50
|
-
export declare function authRoutes(mode: AuthMode, deps?: {
|
|
61
|
+
export declare function authRoutes(mode: AuthMode, auth: AuthRuntime, deps?: {
|
|
51
62
|
/**
|
|
52
63
|
* When provided and returning true, password login is refused (the app
|
|
53
64
|
* injects passkeys' passwordLoginDisabled so the shared password dies
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnE,iEAAiE;AACjE,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;CACd;AAID,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAQlC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAS3D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAwDxE;AAED,iEAAiE;AACjE,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,iBAAiB,CAqC9E;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,CAAC,EAAE,OAAO,EACV,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,OAAO,CAAC,CAWlB;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAYrF;AAwCD,8EAA8E;AAC9E,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAUrE;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CACxB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,WAAW,EACjB,IAAI,GAAE;IACJ;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;CACvC,GACL,IAAI,CAkDN"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -12,19 +12,13 @@ const LOGIN_RATE_WINDOW_MS = 60_000; // per minute
|
|
|
12
12
|
// verify cost, makes online guessing infeasible without being a lockout an
|
|
13
13
|
// attacker could weaponize to deny the owner access.
|
|
14
14
|
export const GLOBAL_LOGIN_RATE_LIMIT = 20; // attempts per window, all IPs combined
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
explicit === "proxy" ||
|
|
21
|
-
explicit === "none") {
|
|
22
|
-
return explicit;
|
|
15
|
+
export function resolveAuthMode(auth) {
|
|
16
|
+
if (auth.mode)
|
|
17
|
+
return auth.mode;
|
|
18
|
+
if (auth.invalidMode) {
|
|
19
|
+
console.warn(`[auth] Unknown AUTH_MODE="${auth.invalidMode}"; auto-detecting instead.`);
|
|
23
20
|
}
|
|
24
|
-
if (
|
|
25
|
-
console.warn(`[auth] Unknown AUTH_MODE="${explicit}"; auto-detecting instead.`);
|
|
26
|
-
}
|
|
27
|
-
if (process.env.BRAIN_UI_PASSWORD_HASH)
|
|
21
|
+
if (auth.passwordHash)
|
|
28
22
|
return "password";
|
|
29
23
|
return "tailscale";
|
|
30
24
|
}
|
|
@@ -32,15 +26,15 @@ export function resolveAuthMode() {
|
|
|
32
26
|
* Validate the auth configuration at startup. Throws (refusing to boot) on an
|
|
33
27
|
* unsafe or unusable configuration.
|
|
34
28
|
*/
|
|
35
|
-
export function assertAuthConfig(mode) {
|
|
36
|
-
const host =
|
|
29
|
+
export function assertAuthConfig(mode, auth) {
|
|
30
|
+
const host = auth.host;
|
|
37
31
|
const loopback = host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
38
32
|
if (mode === "password") {
|
|
39
|
-
if (!
|
|
33
|
+
if (!auth.passwordHash) {
|
|
40
34
|
throw new Error("AUTH_MODE=password requires BRAIN_UI_PASSWORD_HASH. Generate one with:\n" +
|
|
41
35
|
" bun -e 'console.log(await Bun.password.hash(process.argv[1]))' 'your-password'");
|
|
42
36
|
}
|
|
43
|
-
if (!
|
|
37
|
+
if (!auth.cookieSecret) {
|
|
44
38
|
throw new Error("AUTH_MODE=password requires COOKIE_SECRET (a long random string used to " +
|
|
45
39
|
"sign the session cookie). Generate one with:\n openssl rand -hex 32");
|
|
46
40
|
}
|
|
@@ -49,7 +43,7 @@ export function assertAuthConfig(mode) {
|
|
|
49
43
|
// difference between "auth required" and "agent with file-write access
|
|
50
44
|
// exposed to the network".
|
|
51
45
|
if (mode === "none" && !loopback) {
|
|
52
|
-
if (
|
|
46
|
+
if (auth.dangerouslyDisableAuth) {
|
|
53
47
|
console.warn("[auth] AUTH_MODE=none on a non-loopback host, allowed by " +
|
|
54
48
|
"BRAIN_UI_DANGEROUSLY_DISABLE_AUTH=1 — every network peer has full " +
|
|
55
49
|
"agent access. Do not run this on anything but a trusted network.");
|
|
@@ -64,23 +58,23 @@ export function assertAuthConfig(mode) {
|
|
|
64
58
|
}
|
|
65
59
|
}
|
|
66
60
|
if (mode === "proxy") {
|
|
67
|
-
if (
|
|
61
|
+
if (!auth.trustProxy) {
|
|
68
62
|
throw new Error("AUTH_MODE=proxy requires TRUST_PROXY=1 — the proxy-auth header is only " +
|
|
69
63
|
"trustworthy when a fronting proxy is guaranteed to set it and strip " +
|
|
70
64
|
"any client-supplied copy. Set TRUST_PROXY=1 once that holds.");
|
|
71
65
|
}
|
|
72
|
-
console.log(`[auth] mode: proxy (trusting header "${
|
|
66
|
+
console.log(`[auth] mode: proxy (trusting header "${auth.proxyAuthHeader}"; ensure your ` +
|
|
73
67
|
"upstream proxy sets it and strips any client-supplied copy)");
|
|
74
68
|
return;
|
|
75
69
|
}
|
|
76
70
|
console.log(`[auth] mode: ${mode}`);
|
|
77
71
|
}
|
|
78
72
|
/** Middleware guarding /api/* according to the resolved mode. */
|
|
79
|
-
export function authGuard(mode) {
|
|
73
|
+
export function authGuard(mode, auth) {
|
|
80
74
|
switch (mode) {
|
|
81
75
|
case "tailscale":
|
|
82
76
|
return async (c, next) => {
|
|
83
|
-
if (isTailscaleAllowed(c,
|
|
77
|
+
if (isTailscaleAllowed(c, auth.trustProxy, auth.trustProxyHops)) {
|
|
84
78
|
await next();
|
|
85
79
|
}
|
|
86
80
|
else {
|
|
@@ -89,7 +83,7 @@ export function authGuard(mode) {
|
|
|
89
83
|
};
|
|
90
84
|
case "proxy":
|
|
91
85
|
return async (c, next) => {
|
|
92
|
-
if (hasProxyAuth(c)) {
|
|
86
|
+
if (hasProxyAuth(c, auth)) {
|
|
93
87
|
await next();
|
|
94
88
|
}
|
|
95
89
|
else {
|
|
@@ -98,7 +92,7 @@ export function authGuard(mode) {
|
|
|
98
92
|
};
|
|
99
93
|
case "password":
|
|
100
94
|
return async (c, next) => {
|
|
101
|
-
if (await hasValidSession(c)) {
|
|
95
|
+
if (await hasValidSession(c, auth)) {
|
|
102
96
|
await next();
|
|
103
97
|
}
|
|
104
98
|
else {
|
|
@@ -118,16 +112,16 @@ export function authGuard(mode) {
|
|
|
118
112
|
* middleware may sit on the WS route (immutable-header errors). Mirrors
|
|
119
113
|
* {@link authGuard} without emitting a response.
|
|
120
114
|
*/
|
|
121
|
-
export async function isWsAuthorized(c, mode) {
|
|
115
|
+
export async function isWsAuthorized(c, mode, auth) {
|
|
122
116
|
switch (mode) {
|
|
123
117
|
case "none":
|
|
124
118
|
return true;
|
|
125
119
|
case "tailscale":
|
|
126
|
-
return isTailscaleAllowed(c,
|
|
120
|
+
return isTailscaleAllowed(c, auth.trustProxy, auth.trustProxyHops);
|
|
127
121
|
case "proxy":
|
|
128
|
-
return hasProxyAuth(c);
|
|
122
|
+
return hasProxyAuth(c, auth);
|
|
129
123
|
case "password":
|
|
130
|
-
return hasValidSession(c);
|
|
124
|
+
return hasValidSession(c, auth);
|
|
131
125
|
}
|
|
132
126
|
}
|
|
133
127
|
// --- password mode helpers ---
|
|
@@ -136,8 +130,8 @@ export async function isWsAuthorized(c, mode) {
|
|
|
136
130
|
* both password login and passkey login (middleware/passkeys.ts) call this, so
|
|
137
131
|
* authGuard / isWsAuthorized / TTL semantics stay identical across methods.
|
|
138
132
|
*/
|
|
139
|
-
export async function issueSessionCookie(c) {
|
|
140
|
-
const secret =
|
|
133
|
+
export async function issueSessionCookie(c, auth) {
|
|
134
|
+
const secret = auth.cookieSecret ?? "";
|
|
141
135
|
await setSignedCookie(c, COOKIE_NAME, String(Date.now()), secret, {
|
|
142
136
|
httpOnly: true,
|
|
143
137
|
sameSite: "Strict",
|
|
@@ -149,8 +143,8 @@ export async function issueSessionCookie(c) {
|
|
|
149
143
|
maxAge: SESSION_TTL_SECONDS,
|
|
150
144
|
});
|
|
151
145
|
}
|
|
152
|
-
async function hasValidSession(c) {
|
|
153
|
-
const secret =
|
|
146
|
+
async function hasValidSession(c, auth) {
|
|
147
|
+
const secret = auth.cookieSecret ?? "";
|
|
154
148
|
if (!secret)
|
|
155
149
|
return false;
|
|
156
150
|
try {
|
|
@@ -171,17 +165,14 @@ async function hasValidSession(c) {
|
|
|
171
165
|
}
|
|
172
166
|
}
|
|
173
167
|
// --- proxy mode helpers ---
|
|
174
|
-
function
|
|
175
|
-
return (process.env.PROXY_AUTH_HEADER || DEFAULT_PROXY_HEADER).toLowerCase();
|
|
176
|
-
}
|
|
177
|
-
function hasProxyAuth(c) {
|
|
168
|
+
function hasProxyAuth(c, auth) {
|
|
178
169
|
// The proxy-auth header is only meaningful when a trusted proxy fronts the app
|
|
179
170
|
// and TRUST_PROXY says so; otherwise a client could set it directly. Gate on
|
|
180
|
-
//
|
|
171
|
+
// trustProxy, consistent with tailscale-mode XFF trust. (assertAuthConfig
|
|
181
172
|
// already refuses to boot proxy mode without it — this is belt-and-braces.)
|
|
182
|
-
if (
|
|
173
|
+
if (!auth.trustProxy)
|
|
183
174
|
return false;
|
|
184
|
-
const user = c.req.header(
|
|
175
|
+
const user = c.req.header(auth.proxyAuthHeader);
|
|
185
176
|
return !!user && user.trim().length > 0;
|
|
186
177
|
}
|
|
187
178
|
const loginBuckets = new Map();
|
|
@@ -202,13 +193,13 @@ export function consumeLoginToken(key, limit) {
|
|
|
202
193
|
return true;
|
|
203
194
|
}
|
|
204
195
|
/** Login/logout routes. Only functional in `password` mode. */
|
|
205
|
-
export function authRoutes(mode, deps = {}) {
|
|
196
|
+
export function authRoutes(mode, auth, deps = {}) {
|
|
206
197
|
const app = new Hono();
|
|
207
198
|
app.post("/auth/login", async (c) => {
|
|
208
199
|
if (mode !== "password") {
|
|
209
200
|
return c.json({ error: "Password login is not enabled" }, 400);
|
|
210
201
|
}
|
|
211
|
-
const key = clientIp(c,
|
|
202
|
+
const key = clientIp(c, auth.trustProxy, auth.trustProxyHops) || "unknown";
|
|
212
203
|
const perIpOk = consumeLoginToken(`ip:${key}`, LOGIN_RATE_LIMIT);
|
|
213
204
|
const globalOk = consumeLoginToken("global", GLOBAL_LOGIN_RATE_LIMIT);
|
|
214
205
|
if (!perIpOk || !globalOk) {
|
|
@@ -217,8 +208,8 @@ export function authRoutes(mode, deps = {}) {
|
|
|
217
208
|
if (deps.passwordDisabled?.(c)) {
|
|
218
209
|
return c.json({ error: "Password login is disabled — sign in with a passkey." }, 403);
|
|
219
210
|
}
|
|
220
|
-
const hash =
|
|
221
|
-
const secret =
|
|
211
|
+
const hash = auth.passwordHash ?? "";
|
|
212
|
+
const secret = auth.cookieSecret ?? "";
|
|
222
213
|
let body;
|
|
223
214
|
try {
|
|
224
215
|
body = await c.req.json();
|
|
@@ -234,7 +225,7 @@ export function authRoutes(mode, deps = {}) {
|
|
|
234
225
|
if (!ok) {
|
|
235
226
|
return c.json({ error: "Invalid credentials" }, 401);
|
|
236
227
|
}
|
|
237
|
-
await issueSessionCookie(c);
|
|
228
|
+
await issueSessionCookie(c, auth);
|
|
238
229
|
return c.json({ ok: true });
|
|
239
230
|
});
|
|
240
231
|
app.post("/auth/logout", (c) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,5 @@ import type { Context } from "hono";
|
|
|
12
12
|
* The check is free because the routes that use it are only ever called by the
|
|
13
13
|
* app itself, same-origin. Same shape as the WebSocket upgrade's origin guard.
|
|
14
14
|
*/
|
|
15
|
-
export declare function
|
|
16
|
-
export declare function isSameOriginRequest(c: Context, allowed?: string[]): boolean;
|
|
15
|
+
export declare function isSameOriginRequest(c: Context, allowed: string[]): boolean;
|
|
17
16
|
//# sourceMappingURL=origin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../../src/middleware/origin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;;;;;;;;GAYG;AACH,wBAAgB,
|
|
1
|
+
{"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../../src/middleware/origin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAgB1E"}
|
|
@@ -11,13 +11,7 @@
|
|
|
11
11
|
* The check is free because the routes that use it are only ever called by the
|
|
12
12
|
* app itself, same-origin. Same shape as the WebSocket upgrade's origin guard.
|
|
13
13
|
*/
|
|
14
|
-
export function
|
|
15
|
-
return (process.env.ALLOWED_ORIGINS ?? "")
|
|
16
|
-
.split(",")
|
|
17
|
-
.map((origin) => origin.trim())
|
|
18
|
-
.filter(Boolean);
|
|
19
|
-
}
|
|
20
|
-
export function isSameOriginRequest(c, allowed = allowedOriginList()) {
|
|
14
|
+
export function isSameOriginRequest(c, allowed) {
|
|
21
15
|
// Chromium and Firefox send this, and it is not settable by script.
|
|
22
16
|
const site = c.req.header("sec-fetch-site");
|
|
23
17
|
if (site)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/middleware/origin.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/middleware/origin.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAU,EAAE,OAAiB;IAC/D,oEAAoE;IACpE,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC5C,IAAI,IAAI;QAAE,OAAO,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,MAAM,CAAC;IAE3D,4EAA4E;IAC5E,0CAA0C;IAC1C,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,6CAA6C;IACvE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import type { Context } from "hono";
|
|
3
|
+
import type { Database } from "bun:sqlite";
|
|
3
4
|
import { verifyAuthenticationResponse as realVerifyAuthentication, verifyRegistrationResponse as realVerifyRegistration } from "@simplewebauthn/server";
|
|
4
|
-
import { type AuthMode } from "./auth.js";
|
|
5
|
+
import { type AuthMode, type AuthRuntime } from "./auth.js";
|
|
6
|
+
import type { WebAuthnConfig } from "../config/env.js";
|
|
7
|
+
/**
|
|
8
|
+
* WebAuthn passkeys as an extension of `password` mode: the password bootstraps
|
|
9
|
+
* the first registration (and stays as recovery), passkeys are the day-to-day
|
|
10
|
+
* login. A successful assertion mints the same session cookie as password login
|
|
11
|
+
* (issueSessionCookie), so authGuard / isWsAuthorized / TTL are untouched.
|
|
12
|
+
*
|
|
13
|
+
* Everything environmental is injected as a {@link PasskeyContext} — the
|
|
14
|
+
* resolved WebAuthn identity, the auth runtime (for trust/proxy semantics and
|
|
15
|
+
* the cookie secret), the origin allowlist, and the app's own database.
|
|
16
|
+
*
|
|
17
|
+
* Credentials are RP-scoped: a credential registered on localhost does not
|
|
18
|
+
* exist for the production domain and vice versa. Rows carry rp_id so all
|
|
19
|
+
* environments share the table.
|
|
20
|
+
*/
|
|
21
|
+
/** Injected per-app dependencies for every passkey route and helper. */
|
|
22
|
+
export interface PasskeyContext {
|
|
23
|
+
db: Database;
|
|
24
|
+
webauthn: WebAuthnConfig;
|
|
25
|
+
auth: AuthRuntime;
|
|
26
|
+
/** ALLOWED_ORIGINS — split-topology origins also valid for ceremonies. */
|
|
27
|
+
allowedOrigins: string[];
|
|
28
|
+
}
|
|
5
29
|
/**
|
|
6
30
|
* Refuse to boot on a WebAuthn user handle the browser would reject. Called
|
|
7
31
|
* from createApp(): an oversized handle otherwise produces registration
|
|
8
32
|
* options that look fine server-side and fail silently in every browser.
|
|
9
33
|
*/
|
|
10
|
-
export declare function assertPasskeyConfig(): void;
|
|
34
|
+
export declare function assertPasskeyConfig(webauthn: WebAuthnConfig): void;
|
|
11
35
|
/** Injectable seams for tests; production callers pass nothing. */
|
|
12
36
|
export interface PasskeyDeps {
|
|
13
37
|
verifyRegistrationResponse?: typeof realVerifyRegistration;
|
|
@@ -22,15 +46,15 @@ export interface PasskeyDeps {
|
|
|
22
46
|
* until it has its own credential. Host-derived rpID is fine here: a forged
|
|
23
47
|
* Host can at most re-enable password auth, which still requires the password.
|
|
24
48
|
*/
|
|
25
|
-
export declare function passwordLoginDisabled(c: Context): boolean;
|
|
49
|
+
export declare function passwordLoginDisabled(c: Context, ctx: PasskeyContext): boolean;
|
|
26
50
|
/**
|
|
27
51
|
* Unauthenticated passkey routes. Mount BEFORE the auth guard, next to
|
|
28
52
|
* authRoutes.
|
|
29
53
|
*/
|
|
30
|
-
export declare function passkeyPublicRoutes(mode: AuthMode, deps?: PasskeyDeps): Hono;
|
|
54
|
+
export declare function passkeyPublicRoutes(mode: AuthMode, ctx: PasskeyContext, deps?: PasskeyDeps): Hono;
|
|
31
55
|
/**
|
|
32
56
|
* Session-gated passkey routes. Mount AFTER the auth guard — gating comes from
|
|
33
57
|
* mount position, not per-route checks.
|
|
34
58
|
*/
|
|
35
|
-
export declare function passkeyManagementRoutes(mode: AuthMode, deps?: PasskeyDeps): Hono;
|
|
59
|
+
export declare function passkeyManagementRoutes(mode: AuthMode, ctx: PasskeyContext, deps?: PasskeyDeps): Hono;
|
|
36
60
|
//# sourceMappingURL=passkeys.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkeys.d.ts","sourceRoot":"","sources":["../../src/middleware/passkeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAGL,4BAA4B,IAAI,wBAAwB,EACxD,0BAA0B,IAAI,sBAAsB,EACrD,MAAM,wBAAwB,CAAC;AAMhC,OAAO,EACL,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"passkeys.d.ts","sourceRoot":"","sources":["../../src/middleware/passkeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAGL,4BAA4B,IAAI,wBAAwB,EACxD,0BAA0B,IAAI,sBAAsB,EACrD,MAAM,wBAAwB,CAAC;AAMhC,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,WAAW,EAKjB,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AAEH,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,QAAQ,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,0EAA0E;IAC1E,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAyBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAQlE;AAQD,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,0BAA0B,CAAC,EAAE,OAAO,sBAAsB,CAAC;IAC3D,4BAA4B,CAAC,EAAE,OAAO,wBAAwB,CAAC;IAC/D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AA0JD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAM9E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,GAAG,EAAE,cAAc,EACnB,IAAI,GAAE,WAAgB,GACrB,IAAI,CAsGN;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,EACd,GAAG,EAAE,cAAc,EACnB,IAAI,GAAE,WAAgB,GACrB,IAAI,CA6HN"}
|