fest-gateway 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 +274 -0
- package/dist/cli/claude.js +326 -0
- package/dist/cli/claude.js.map +1 -0
- package/dist/cli/config.js +59 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/login.js +130 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/logout.js +20 -0
- package/dist/cli/logout.js.map +1 -0
- package/dist/cli/whoami.js +34 -0
- package/dist/cli/whoami.js.map +1 -0
- package/dist/server/adapters/anthropic.js +74 -0
- package/dist/server/adapters/anthropic.js.map +1 -0
- package/dist/server/adapters/fireworks.js +54 -0
- package/dist/server/adapters/fireworks.js.map +1 -0
- package/dist/server/adapters/index.js +16 -0
- package/dist/server/adapters/index.js.map +1 -0
- package/dist/server/adapters/registry.js +38 -0
- package/dist/server/adapters/registry.js.map +1 -0
- package/dist/server/adapters/rewrite.js +152 -0
- package/dist/server/adapters/rewrite.js.map +1 -0
- package/dist/server/adapters/url.js +24 -0
- package/dist/server/adapters/url.js.map +1 -0
- package/dist/server/api/auth.js +213 -0
- package/dist/server/api/auth.js.map +1 -0
- package/dist/server/api/live.js +108 -0
- package/dist/server/api/live.js.map +1 -0
- package/dist/server/api/models.js +129 -0
- package/dist/server/api/models.js.map +1 -0
- package/dist/server/api/oauth.js +300 -0
- package/dist/server/api/oauth.js.map +1 -0
- package/dist/server/api/routes.js +164 -0
- package/dist/server/api/routes.js.map +1 -0
- package/dist/server/api/routing-view.js +47 -0
- package/dist/server/api/routing-view.js.map +1 -0
- package/dist/server/auth/accounts.js +111 -0
- package/dist/server/auth/accounts.js.map +1 -0
- package/dist/server/auth/gateway-401.js +85 -0
- package/dist/server/auth/gateway-401.js.map +1 -0
- package/dist/server/auth/guard.js +74 -0
- package/dist/server/auth/guard.js.map +1 -0
- package/dist/server/auth/oauth.js +43 -0
- package/dist/server/auth/oauth.js.map +1 -0
- package/dist/server/auth/password.js +104 -0
- package/dist/server/auth/password.js.map +1 -0
- package/dist/server/auth/posture.js +145 -0
- package/dist/server/auth/posture.js.map +1 -0
- package/dist/server/auth/session.js +161 -0
- package/dist/server/auth/session.js.map +1 -0
- package/dist/server/bin/fest.js +589 -0
- package/dist/server/bin/fest.js.map +1 -0
- package/dist/server/config.js +109 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/credentials/provider.js +70 -0
- package/dist/server/credentials/provider.js.map +1 -0
- package/dist/server/credentials/resolve.js +89 -0
- package/dist/server/credentials/resolve.js.map +1 -0
- package/dist/server/http/body.js +66 -0
- package/dist/server/http/body.js.map +1 -0
- package/dist/server/http/errors.js +144 -0
- package/dist/server/http/errors.js.map +1 -0
- package/dist/server/http/headers.js +189 -0
- package/dist/server/http/headers.js.map +1 -0
- package/dist/server/http/pipe.js +97 -0
- package/dist/server/http/pipe.js.map +1 -0
- package/dist/server/http/server.js +226 -0
- package/dist/server/http/server.js.map +1 -0
- package/dist/server/http/sse.js +167 -0
- package/dist/server/http/sse.js.map +1 -0
- package/dist/server/http/static.js +138 -0
- package/dist/server/http/static.js.map +1 -0
- package/dist/server/ingest/live-bus.js +67 -0
- package/dist/server/ingest/live-bus.js.map +1 -0
- package/dist/server/ingest/sink.js +141 -0
- package/dist/server/ingest/sink.js.map +1 -0
- package/dist/server/log.js +31 -0
- package/dist/server/log.js.map +1 -0
- package/dist/server/pipeline/count-tokens.js +48 -0
- package/dist/server/pipeline/count-tokens.js.map +1 -0
- package/dist/server/pipeline/dispatch.js +138 -0
- package/dist/server/pipeline/dispatch.js.map +1 -0
- package/dist/server/pipeline/passthrough.js +392 -0
- package/dist/server/pipeline/passthrough.js.map +1 -0
- package/dist/server/pipeline/preflight.js +66 -0
- package/dist/server/pipeline/preflight.js.map +1 -0
- package/dist/server/pipeline/record.js +49 -0
- package/dist/server/pipeline/record.js.map +1 -0
- package/dist/server/pipeline/substitute.js +287 -0
- package/dist/server/pipeline/substitute.js.map +1 -0
- package/dist/server/routes/resolve.js +176 -0
- package/dist/server/routes/resolve.js.map +1 -0
- package/dist/server/routes/table.js +247 -0
- package/dist/server/routes/table.js.map +1 -0
- package/dist/server/routes/watch.js +101 -0
- package/dist/server/routes/watch.js.map +1 -0
- package/dist/server/secret/fingerprint.js +107 -0
- package/dist/server/secret/fingerprint.js.map +1 -0
- package/dist/server/secret/non-persistable.js +69 -0
- package/dist/server/secret/non-persistable.js.map +1 -0
- package/dist/server/store/audit.js +53 -0
- package/dist/server/store/audit.js.map +1 -0
- package/dist/server/store/bootstrap.js +53 -0
- package/dist/server/store/bootstrap.js.map +1 -0
- package/dist/server/store/db.js +157 -0
- package/dist/server/store/db.js.map +1 -0
- package/dist/server/store/ids.js +14 -0
- package/dist/server/store/ids.js.map +1 -0
- package/dist/server/store/migrations/001-init.sql +213 -0
- package/dist/server/store/migrations/002-feed-and-error-indexes.sql +26 -0
- package/dist/server/store/migrations/003-routing.sql +42 -0
- package/dist/server/store/migrations/004-admin-auth.sql +72 -0
- package/dist/server/store/migrations/005-notional-cost.sql +28 -0
- package/dist/server/store/queries.js +904 -0
- package/dist/server/store/queries.js.map +1 -0
- package/dist/server/store/retention.js +79 -0
- package/dist/server/store/retention.js.map +1 -0
- package/dist/server/store/seed.js +203 -0
- package/dist/server/store/seed.js.map +1 -0
- package/dist/server/store/tokens.js +133 -0
- package/dist/server/store/tokens.js.map +1 -0
- package/dist/server/store/write.js +330 -0
- package/dist/server/store/write.js.map +1 -0
- package/dist/server/usage/accumulator.js +209 -0
- package/dist/server/usage/accumulator.js.map +1 -0
- package/dist/server/usage/cost.js +57 -0
- package/dist/server/usage/cost.js.map +1 -0
- package/dist/server/usage/prices/catalog.js +136 -0
- package/dist/server/usage/prices/catalog.js.map +1 -0
- package/dist/server/usage/prices/refresh.js +125 -0
- package/dist/server/usage/prices/refresh.js.map +1 -0
- package/dist/server/usage/prices/snapshot.json.gz +0 -0
- package/dist/server/usage/prices/table.js +102 -0
- package/dist/server/usage/prices/table.js.map +1 -0
- package/dist/server/usage/pricing.js +103 -0
- package/dist/server/usage/pricing.js.map +1 -0
- package/dist/shared/api.js +19 -0
- package/dist/shared/api.js.map +1 -0
- package/dist/shared/base-models.js +28 -0
- package/dist/shared/base-models.js.map +1 -0
- package/dist/shared/demotion-vars.js +17 -0
- package/dist/shared/demotion-vars.js.map +1 -0
- package/dist/shared/series.js +50 -0
- package/dist/shared/series.js.map +1 -0
- package/dist/shared/types.js +40 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/web/dist/assets/index-8HlKsJqt.css +2 -0
- package/dist/web/dist/assets/index-DQ7fyPVm.js +56 -0
- package/dist/web/dist/assets/index-DQ7fyPVm.js.map +1 -0
- package/dist/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist/web/dist/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist/web/dist/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/dist/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/dist/web/dist/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/dist/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/dist/web/dist/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
- package/dist/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/dist/web/dist/index.html +44 -0
- package/dist/web/dist/nearform-icon.svg +4 -0
- package/package.json +82 -0
- package/routes.example.json +22 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../server/auth/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fest CLI.
|
|
4
|
+
*
|
|
5
|
+
* Server operator commands (need FEST_* server env, run on the host):
|
|
6
|
+
* fest serve run the gateway
|
|
7
|
+
* fest migrate apply schema migrations
|
|
8
|
+
* fest token create <email> [name] mint an identity token (shown once)
|
|
9
|
+
* fest token list
|
|
10
|
+
* fest token revoke <token-id>
|
|
11
|
+
* fest admin create <email> [--role owner|admin|member] [--password-stdin]
|
|
12
|
+
* grant dashboard sign-in (password shown once)
|
|
13
|
+
* fest admin list
|
|
14
|
+
* fest admin passwd <email> [--password-stdin]
|
|
15
|
+
* fest admin disable <email> | fest admin enable <email>
|
|
16
|
+
* fest seed [--requests N] [--hours N] [--force]
|
|
17
|
+
* synthetic traffic, for looking at the
|
|
18
|
+
* dashboard without a live session
|
|
19
|
+
* fest seed --reset discard the database, then seed it fresh
|
|
20
|
+
* fest seed --clear discard the database and stop
|
|
21
|
+
*
|
|
22
|
+
* Developer commands (run on your own machine, no server env needed):
|
|
23
|
+
* fest login [--provider google|github] [--server <url>]
|
|
24
|
+
* sign in via OAuth, store a token in ~/.fest
|
|
25
|
+
* fest whoami show the locally stored login
|
|
26
|
+
* fest claude [gw] [-- claude args...]
|
|
27
|
+
* run `claude` pointed at your Fest gateway.
|
|
28
|
+
* Posture is auto-detected: your own Anthropic
|
|
29
|
+
* login pays if you have one, org credentials
|
|
30
|
+
* if you do not. `gw` forces org credentials
|
|
31
|
+
* and the gateway model menu.
|
|
32
|
+
* fest logout forget the local login
|
|
33
|
+
*/
|
|
34
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
35
|
+
import { dirname } from "node:path";
|
|
36
|
+
import { loadConfig, describeConfig } from "../config.js";
|
|
37
|
+
import { createUsageSink } from "../ingest/sink.js";
|
|
38
|
+
import { createLiveBus } from "../ingest/live-bus.js";
|
|
39
|
+
import { createServer } from "../http/server.js";
|
|
40
|
+
import { log, setLogLevel } from "../log.js";
|
|
41
|
+
import { openStore, migrate } from "../store/db.js";
|
|
42
|
+
import { ensureOrg, ensureUser, findUserByEmail } from "../store/bootstrap.js";
|
|
43
|
+
import { createToken, listTokens, revokeToken, resolveToken, createLastUsedTracker } from "../store/tokens.js";
|
|
44
|
+
import { grantDashboardAccess, hasAnyOwner, normaliseEmail } from "../auth/accounts.js";
|
|
45
|
+
import { revokeUserSessions } from "../auth/session.js";
|
|
46
|
+
import { isLoopbackHost } from "../auth/guard.js";
|
|
47
|
+
import { generatePassword } from "../auth/password.js";
|
|
48
|
+
import { recordAudit } from "../store/audit.js";
|
|
49
|
+
import { createRequestWriter } from "../store/write.js";
|
|
50
|
+
import { startRetention, DEFAULT_RETENTION } from "../store/retention.js";
|
|
51
|
+
import { loadPrices, startPriceRefresh, pricingOptionsFromEnv } from "../usage/prices/refresh.js";
|
|
52
|
+
import { seed, existingRequestCount, isDefaultDatabase } from "../store/seed.js";
|
|
53
|
+
import { parseRouteTable, EMPTY_ROUTE_TABLE } from "../routes/table.js";
|
|
54
|
+
import { watchRoutes } from "../routes/watch.js";
|
|
55
|
+
import { readFile } from "node:fs/promises";
|
|
56
|
+
import { runLogin } from "../../cli/login.js";
|
|
57
|
+
import { runWhoami } from "../../cli/whoami.js";
|
|
58
|
+
import { runClaude } from "../../cli/claude.js";
|
|
59
|
+
import { runLogout } from "../../cli/logout.js";
|
|
60
|
+
import { readCliConfig } from "../../cli/config.js";
|
|
61
|
+
const out = (s) => void process.stdout.write(s + "\n");
|
|
62
|
+
async function withStore(cfg) {
|
|
63
|
+
await mkdir(dirname(cfg.dbPath), { recursive: true });
|
|
64
|
+
const store = openStore(cfg.dbPath);
|
|
65
|
+
// Migrations run before anything else touches the schema, so a fresh volume
|
|
66
|
+
// and an upgraded one behave identically.
|
|
67
|
+
const { applied, version } = migrate(store);
|
|
68
|
+
if (applied > 0)
|
|
69
|
+
log.info("migrations applied", { applied, version });
|
|
70
|
+
return store;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Load and validate the routing table at BOOT, not on first use.
|
|
74
|
+
*
|
|
75
|
+
* A config error must stop the process here, where an operator is watching,
|
|
76
|
+
* rather than surfacing as a failed request to a developer hours later. And a
|
|
77
|
+
* table that fails to parse must never degrade to "no routing": that would
|
|
78
|
+
* silently send substituted traffic back onto developers' subscriptions, which
|
|
79
|
+
* is the exact silent-billing-substitution failure this phase is designed
|
|
80
|
+
* against.
|
|
81
|
+
*/
|
|
82
|
+
async function loadRoutes(cfg) {
|
|
83
|
+
if (cfg.routesPath === null)
|
|
84
|
+
return EMPTY_ROUTE_TABLE;
|
|
85
|
+
const text = await readFile(cfg.routesPath, "utf8");
|
|
86
|
+
const table = parseRouteTable(text);
|
|
87
|
+
log.info("routing table loaded", {
|
|
88
|
+
path: cfg.routesPath,
|
|
89
|
+
version: table.version,
|
|
90
|
+
routes: table.routes.length,
|
|
91
|
+
upstreams: [...table.upstreams.keys()],
|
|
92
|
+
});
|
|
93
|
+
return table;
|
|
94
|
+
}
|
|
95
|
+
async function cmdServe(cfg) {
|
|
96
|
+
const routes = await loadRoutes(cfg);
|
|
97
|
+
// Edits to routes.json take effect without a restart. `node --watch` only
|
|
98
|
+
// tracks imported .ts files, so without this an edit appeared to do nothing —
|
|
99
|
+
// which reads as "my config is wrong" rather than "it has not been loaded".
|
|
100
|
+
const routeWatcher = cfg.routesPath === null ? null : watchRoutes(cfg.routesPath, routes);
|
|
101
|
+
const store = await withStore(cfg);
|
|
102
|
+
const org = ensureOrg(store);
|
|
103
|
+
/**
|
|
104
|
+
* Refuse to serve an unauthenticated dashboard off loopback.
|
|
105
|
+
*
|
|
106
|
+
* The API exposes every developer's usage. On 127.0.0.1 that is a
|
|
107
|
+
* single-developer trial and asking for a password first would be friction
|
|
108
|
+
* for no one's benefit; on any other interface it is a data leak waiting for
|
|
109
|
+
* someone to find the port. Failing at boot — where an operator is watching —
|
|
110
|
+
* beats failing as a quiet exposure nobody notices.
|
|
111
|
+
*/
|
|
112
|
+
if (!isLoopbackHost(cfg.host) && !hasAnyOwner(store, org.id)) {
|
|
113
|
+
store.close();
|
|
114
|
+
throw new Error(`refusing to listen on ${cfg.host} with no owner account.\n` +
|
|
115
|
+
"The dashboard would serve every developer's usage to anyone who finds the port.\n" +
|
|
116
|
+
"Create one first:\n" +
|
|
117
|
+
" node server/bin/fest.ts admin create you@corp.test\n" +
|
|
118
|
+
" (in Docker: docker compose run --rm fest admin create you@corp.test)\n" +
|
|
119
|
+
"or bind to loopback (FEST_HOST=127.0.0.1) for a single-developer trial.");
|
|
120
|
+
}
|
|
121
|
+
// Prices load synchronously from the vendored snapshot before the first
|
|
122
|
+
// request can arrive, so a call is never metered against an empty table. The
|
|
123
|
+
// refresh that follows is best-effort and off the hot path — see
|
|
124
|
+
// usage/prices/refresh.ts for why boot must never wait on it.
|
|
125
|
+
const pricing = pricingOptionsFromEnv(cfg.dbPath);
|
|
126
|
+
loadPrices(pricing);
|
|
127
|
+
const priceRefresh = startPriceRefresh(pricing);
|
|
128
|
+
const writer = createRequestWriter(store);
|
|
129
|
+
const lastUsed = createLastUsedTracker(store);
|
|
130
|
+
// Hourly sweep, on a timer rather than at boot: a restart loop must not turn
|
|
131
|
+
// into a delete loop.
|
|
132
|
+
const retention = startRetention(store, DEFAULT_RETENTION);
|
|
133
|
+
await mkdir(dirname(cfg.usageLogPath), { recursive: true });
|
|
134
|
+
const bus = createLiveBus();
|
|
135
|
+
const sink = createUsageSink({
|
|
136
|
+
path: cfg.usageLogPath,
|
|
137
|
+
// The store is the system of record; JSONL stays as a cheap, greppable
|
|
138
|
+
// trail. Pricing and persistence both happen here in the flush, never on
|
|
139
|
+
// the request path.
|
|
140
|
+
//
|
|
141
|
+
// The live feed is published AFTER the write, so a dashboard never shows a
|
|
142
|
+
// row that then fails to persist. A throwing write requeues the batch and
|
|
143
|
+
// publishes on the retry instead, which is the right way round: the feed
|
|
144
|
+
// may lag the truth, but it must not contradict it.
|
|
145
|
+
onBatch: (records) => {
|
|
146
|
+
writer.writeBatch(org.id, records);
|
|
147
|
+
bus.publish(records);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const server = createServer({
|
|
151
|
+
config: cfg,
|
|
152
|
+
sink,
|
|
153
|
+
bus,
|
|
154
|
+
routes: routeWatcher === null ? routes : () => routeWatcher.current(),
|
|
155
|
+
orgId: org.id,
|
|
156
|
+
store,
|
|
157
|
+
resolveIdentity: (raw) => resolveToken(store, raw),
|
|
158
|
+
touchToken: (id) => lastUsed.touch(id),
|
|
159
|
+
});
|
|
160
|
+
/**
|
|
161
|
+
* How long an in-flight request may keep the process alive on shutdown.
|
|
162
|
+
*
|
|
163
|
+
* Long enough not to guillotine a developer mid-turn; short enough that
|
|
164
|
+
* `node --watch` feels like a restart rather than a hang.
|
|
165
|
+
*/
|
|
166
|
+
const DRAIN_GRACE_MS = 5_000;
|
|
167
|
+
let shuttingDown = false;
|
|
168
|
+
const shutdown = (signal) => {
|
|
169
|
+
if (shuttingDown)
|
|
170
|
+
return;
|
|
171
|
+
shuttingDown = true;
|
|
172
|
+
log.info("shutting down", { signal });
|
|
173
|
+
const finish = () => {
|
|
174
|
+
void sink.close().then(() => {
|
|
175
|
+
retention.stop();
|
|
176
|
+
priceRefresh.stop();
|
|
177
|
+
routeWatcher?.stop();
|
|
178
|
+
lastUsed.stop();
|
|
179
|
+
store.close();
|
|
180
|
+
log.info("drained", { sink: sink.stats() });
|
|
181
|
+
process.exit(0);
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
server.close(finish);
|
|
185
|
+
// `server.close()` stops accepting but WAITS for every open connection.
|
|
186
|
+
// An SSE stream never ends by itself, so a single open dashboard would
|
|
187
|
+
// block shutdown indefinitely — which under `node --watch` looks like a
|
|
188
|
+
// restart stuck on "Waiting for graceful termination". Hang up on the
|
|
189
|
+
// dashboards explicitly; they reconnect on their own.
|
|
190
|
+
bus.closeAll();
|
|
191
|
+
server.closeIdleConnections();
|
|
192
|
+
// Whatever is left is a request genuinely in flight. Give it a bounded
|
|
193
|
+
// grace period, then stop waiting: a developer's next keystroke restarting
|
|
194
|
+
// the server matters more than the tail of one turn, and the sink has
|
|
195
|
+
// already been told to drain.
|
|
196
|
+
const forced = setTimeout(() => {
|
|
197
|
+
log.warn("shutdown grace expired; closing remaining connections", {
|
|
198
|
+
afterMs: DRAIN_GRACE_MS,
|
|
199
|
+
});
|
|
200
|
+
server.closeAllConnections();
|
|
201
|
+
finish();
|
|
202
|
+
}, DRAIN_GRACE_MS);
|
|
203
|
+
forced.unref();
|
|
204
|
+
};
|
|
205
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
206
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
207
|
+
server.listen(cfg.port, cfg.host, () => {
|
|
208
|
+
log.info("fest listening", describeConfig(cfg));
|
|
209
|
+
out(`fest: http://${cfg.host}:${cfg.port} -> ${cfg.upstreamBaseUrl}\n` +
|
|
210
|
+
` db: ${cfg.dbPath} identity required: ${cfg.requireIdentity}\n` +
|
|
211
|
+
` retention: requests ${DEFAULT_RETENTION.requestDays}d, rollups ${DEFAULT_RETENTION.rollupDays}d\n` +
|
|
212
|
+
` routing: ${routes.routes.length === 0
|
|
213
|
+
? "none — every request passes through on the caller's own credential"
|
|
214
|
+
: `${routes.routes.length} route(s) via ${cfg.routesPath} (v${routes.version})`}\n` +
|
|
215
|
+
`point Claude Code at it with:\n` +
|
|
216
|
+
` ANTHROPIC_BASE_URL=http://${cfg.host}:${cfg.port}/t/<your-token>\n` +
|
|
217
|
+
`and do NOT set ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN (either disables your subscription)`);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function cmdToken(cfg, argv) {
|
|
221
|
+
const store = await withStore(cfg);
|
|
222
|
+
const org = ensureOrg(store);
|
|
223
|
+
const sub = argv[0] ?? "list";
|
|
224
|
+
if (sub === "create") {
|
|
225
|
+
const email = argv[1];
|
|
226
|
+
if (email === undefined)
|
|
227
|
+
throw new Error("usage: fest token create <email> [name]");
|
|
228
|
+
const user = ensureUser(store, { orgId: org.id, email, role: "member" });
|
|
229
|
+
const created = createToken(store, { orgId: org.id, userId: user.id, name: argv[2] ?? "" });
|
|
230
|
+
recordAudit(store, {
|
|
231
|
+
orgId: org.id,
|
|
232
|
+
actorLabel: "cli",
|
|
233
|
+
action: "token.create",
|
|
234
|
+
target: created.id,
|
|
235
|
+
outcome: "ok",
|
|
236
|
+
detail: { user: user.email, name: argv[2] ?? "" },
|
|
237
|
+
});
|
|
238
|
+
out(`token id : ${created.id}`);
|
|
239
|
+
out(`user : ${user.email}`);
|
|
240
|
+
out("");
|
|
241
|
+
// Shown exactly once: only the hash is stored.
|
|
242
|
+
out(` ${created.raw}`);
|
|
243
|
+
out("");
|
|
244
|
+
out("This is the only time the token is shown. Use it as:");
|
|
245
|
+
out(` ANTHROPIC_BASE_URL=http://${cfg.host}:${cfg.port}/t/${created.raw}`);
|
|
246
|
+
}
|
|
247
|
+
else if (sub === "list") {
|
|
248
|
+
const rows = listTokens(store, org.id);
|
|
249
|
+
if (rows.length === 0)
|
|
250
|
+
out("(no tokens)");
|
|
251
|
+
for (const r of rows) {
|
|
252
|
+
const state = r.revokedAt !== null ? "revoked" : "active";
|
|
253
|
+
out(`${r.id} ${r.displayPrefix}… ${state.padEnd(7)} user=${r.userId} ` +
|
|
254
|
+
`last-used=${r.lastUsedAt === null ? "never" : new Date(r.lastUsedAt).toISOString()} ${r.name}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else if (sub === "revoke") {
|
|
258
|
+
const id = argv[1];
|
|
259
|
+
if (id === undefined)
|
|
260
|
+
throw new Error("usage: fest token revoke <token-id>");
|
|
261
|
+
const revoked = revokeToken(store, org.id, id);
|
|
262
|
+
recordAudit(store, {
|
|
263
|
+
orgId: org.id,
|
|
264
|
+
actorLabel: "cli",
|
|
265
|
+
action: "token.revoke",
|
|
266
|
+
target: id,
|
|
267
|
+
outcome: revoked ? "ok" : "denied",
|
|
268
|
+
});
|
|
269
|
+
out(revoked ? `revoked ${id}` : `not found or already revoked: ${id}`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
throw new Error(`unknown token subcommand: ${sub}`);
|
|
273
|
+
}
|
|
274
|
+
store.close();
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Read a password from stdin when asked to, otherwise generate one.
|
|
278
|
+
*
|
|
279
|
+
* Never from a command-line argument: `fest admin create x --password hunter2`
|
|
280
|
+
* puts the password in the shell history, in `ps` output for every user on the
|
|
281
|
+
* box, and often in a CI log. `--password-stdin` is the automation path; the
|
|
282
|
+
* default is a generated password shown once, which is the one most operators
|
|
283
|
+
* should take.
|
|
284
|
+
*/
|
|
285
|
+
async function readPassword(argv) {
|
|
286
|
+
if (!argv.includes("--password-stdin"))
|
|
287
|
+
return { password: generatePassword(), generated: true };
|
|
288
|
+
const chunks = [];
|
|
289
|
+
for await (const chunk of process.stdin)
|
|
290
|
+
chunks.push(chunk);
|
|
291
|
+
const password = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
292
|
+
if (password === "")
|
|
293
|
+
throw new Error("--password-stdin was given but stdin was empty");
|
|
294
|
+
return { password, generated: false };
|
|
295
|
+
}
|
|
296
|
+
function flagValue(argv, name) {
|
|
297
|
+
const i = argv.indexOf(`--${name}`);
|
|
298
|
+
return i === -1 ? undefined : argv[i + 1];
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Did this user already have console access — a password of their own —
|
|
302
|
+
* before this call? `ensureUser` also runs for metering-only rows (an
|
|
303
|
+
* identity token, or an OAuth self-service login that never went through
|
|
304
|
+
* `admin create`), so "a row exists" is not the same question.
|
|
305
|
+
*/
|
|
306
|
+
function hadConsoleAccess(store, userId) {
|
|
307
|
+
const row = store.db.prepare(`SELECT password_hash FROM users WHERE id = ?`).get(userId);
|
|
308
|
+
return row !== undefined && row.password_hash !== null;
|
|
309
|
+
}
|
|
310
|
+
async function cmdAdmin(cfg, argv) {
|
|
311
|
+
const store = await withStore(cfg);
|
|
312
|
+
const org = ensureOrg(store);
|
|
313
|
+
const sub = argv[0] ?? "list";
|
|
314
|
+
try {
|
|
315
|
+
if (sub === "create" || sub === "passwd") {
|
|
316
|
+
const email = argv[1];
|
|
317
|
+
if (email === undefined)
|
|
318
|
+
throw new Error(`usage: fest admin ${sub} <email> [--role R] [--password-stdin]`);
|
|
319
|
+
const roleFlag = flagValue(argv, "role");
|
|
320
|
+
if (roleFlag !== undefined && !["owner", "admin", "member"].includes(roleFlag)) {
|
|
321
|
+
throw new Error("--role must be owner, admin or member");
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* An existing user keeps their role unless `--role` says otherwise.
|
|
325
|
+
*
|
|
326
|
+
* Found by running it: `admin passwd` on the only owner silently demoted
|
|
327
|
+
* them to `admin`, which left the deployment with no owner — and an
|
|
328
|
+
* ownerless deployment on loopback serves the API unauthenticated. A
|
|
329
|
+
* routine password rotation therefore turned the dashboard's auth off.
|
|
330
|
+
* Changing a password must change exactly the password.
|
|
331
|
+
*
|
|
332
|
+
* Only when the user is new to the CONSOLE does the default apply, and
|
|
333
|
+
* then the FIRST such account is the owner whatever it asked for: a
|
|
334
|
+
* deployment with only an `admin` has nobody able to grant the owner
|
|
335
|
+
* role afterwards. A metering-only row (minted by `fest token create`,
|
|
336
|
+
* or by an OAuth `fest login` that never touched the console) does not
|
|
337
|
+
* count as "existing" for this purpose — granting it console access for
|
|
338
|
+
* the first time is exactly the bootstrap case, not a rotation to
|
|
339
|
+
* protect.
|
|
340
|
+
*/
|
|
341
|
+
const existing = findUserByEmail(store, org.id, normaliseEmail(email));
|
|
342
|
+
const existingConsoleRole = existing !== null && hadConsoleAccess(store, existing.id) ? existing.role : undefined;
|
|
343
|
+
const role = (roleFlag ?? existingConsoleRole ?? (hasAnyOwner(store, org.id) ? "admin" : "owner"));
|
|
344
|
+
const { password, generated } = await readPassword(argv);
|
|
345
|
+
const user = await grantDashboardAccess(store, {
|
|
346
|
+
orgId: org.id,
|
|
347
|
+
email,
|
|
348
|
+
password,
|
|
349
|
+
role,
|
|
350
|
+
});
|
|
351
|
+
recordAudit(store, {
|
|
352
|
+
orgId: org.id,
|
|
353
|
+
actorLabel: "cli",
|
|
354
|
+
action: sub === "create" ? "admin.create" : "admin.passwd",
|
|
355
|
+
target: user.email,
|
|
356
|
+
outcome: "ok",
|
|
357
|
+
detail: { role },
|
|
358
|
+
});
|
|
359
|
+
out(`user : ${user.email}`);
|
|
360
|
+
out(`role : ${user.role}`);
|
|
361
|
+
if (generated) {
|
|
362
|
+
out("");
|
|
363
|
+
// Shown exactly once: only the scrypt hash is stored.
|
|
364
|
+
out(` ${password}`);
|
|
365
|
+
out("");
|
|
366
|
+
out("This is the only time the password is shown. Store it in a password manager.");
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
out("password set from stdin");
|
|
370
|
+
}
|
|
371
|
+
out("Existing sessions for this user have been signed out.");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (sub === "list") {
|
|
375
|
+
const rows = store.db
|
|
376
|
+
.prepare(`SELECT email, role, password_hash IS NOT NULL AS can_sign_in, disabled_at, password_set_at
|
|
377
|
+
FROM users WHERE org_id = ? ORDER BY role, email`)
|
|
378
|
+
.all(org.id);
|
|
379
|
+
if (rows.length === 0)
|
|
380
|
+
out("(no users)");
|
|
381
|
+
for (const r of rows) {
|
|
382
|
+
const state = r["disabled_at"] !== null ? "disabled" : Number(r["can_sign_in"]) === 1 ? "sign-in" : "metered-only";
|
|
383
|
+
out(`${String(r["email"]).padEnd(32)} ${String(r["role"]).padEnd(7)} ${state}`);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (sub === "disable" || sub === "enable") {
|
|
388
|
+
const email = argv[1];
|
|
389
|
+
if (email === undefined)
|
|
390
|
+
throw new Error(`usage: fest admin ${sub} <email>`);
|
|
391
|
+
const disabling = sub === "disable";
|
|
392
|
+
const res = store.db
|
|
393
|
+
.prepare(`UPDATE users SET disabled_at = ? WHERE org_id = ? AND email = ?`)
|
|
394
|
+
.run(disabling ? Date.now() : null, org.id, normaliseEmail(email));
|
|
395
|
+
if (Number(res.changes) === 0)
|
|
396
|
+
throw new Error(`no such user: ${email}`);
|
|
397
|
+
// Disabling has to take effect now, not at the next sign-in. A live
|
|
398
|
+
// cookie outliving the account is the whole reason this command exists.
|
|
399
|
+
if (disabling) {
|
|
400
|
+
const user = store.db
|
|
401
|
+
.prepare(`SELECT id FROM users WHERE org_id = ? AND email = ?`)
|
|
402
|
+
.get(org.id, normaliseEmail(email));
|
|
403
|
+
if (user !== undefined)
|
|
404
|
+
revokeUserSessions(store, user.id);
|
|
405
|
+
}
|
|
406
|
+
recordAudit(store, {
|
|
407
|
+
orgId: org.id,
|
|
408
|
+
actorLabel: "cli",
|
|
409
|
+
action: disabling ? "admin.disable" : "admin.enable",
|
|
410
|
+
target: normaliseEmail(email),
|
|
411
|
+
outcome: "ok",
|
|
412
|
+
});
|
|
413
|
+
out(`${disabling ? "disabled" : "enabled"} ${normaliseEmail(email)}`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
throw new Error(`unknown admin subcommand: ${sub}`);
|
|
417
|
+
}
|
|
418
|
+
finally {
|
|
419
|
+
store.close();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function intFlag(argv, name) {
|
|
423
|
+
const i = argv.indexOf(`--${name}`);
|
|
424
|
+
if (i === -1)
|
|
425
|
+
return undefined;
|
|
426
|
+
const n = Number(argv[i + 1]);
|
|
427
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
428
|
+
throw new Error(`--${name} needs a positive integer`);
|
|
429
|
+
return n;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Discard a database entirely, rather than deleting rows from it.
|
|
433
|
+
*
|
|
434
|
+
* Unlink and not `DELETE FROM`: once synthetic and observed rows share a table
|
|
435
|
+
* there is no honest predicate that separates them, so a partial wipe would
|
|
436
|
+
* have to guess. Removing the file is total and unambiguous — you know exactly
|
|
437
|
+
* what you have afterwards, which is nothing.
|
|
438
|
+
*
|
|
439
|
+
* Refuses the default database without `--force`, because the one thing this
|
|
440
|
+
* command must never do is silently destroy a real record of a team's usage on
|
|
441
|
+
* the way to showing someone a demo.
|
|
442
|
+
*/
|
|
443
|
+
async function discardDatabase(cfg, force) {
|
|
444
|
+
if (isDefaultDatabase(cfg.dbPath) && !force) {
|
|
445
|
+
throw new Error(`refusing to delete the default database (${cfg.dbPath}).\n` +
|
|
446
|
+
"That is where real traffic is recorded. Point at a scratch database:\n" +
|
|
447
|
+
" npm run demo:clean (uses ./data/demo.db)\n" +
|
|
448
|
+
" FEST_DB=./data/scratch.db node server/bin/fest.ts seed --clear\n" +
|
|
449
|
+
"or pass --force if you genuinely mean this one.");
|
|
450
|
+
}
|
|
451
|
+
// The -wal and -shm siblings hold committed pages. Leaving them behind next
|
|
452
|
+
// to a deleted main file is how a "cleared" database comes back populated.
|
|
453
|
+
await Promise.all([cfg.dbPath, `${cfg.dbPath}-wal`, `${cfg.dbPath}-shm`].map((f) => rm(f, { force: true })));
|
|
454
|
+
}
|
|
455
|
+
async function cmdSeed(cfg, argv) {
|
|
456
|
+
const force = argv.includes("--force");
|
|
457
|
+
const clear = argv.includes("--clear");
|
|
458
|
+
const reset = argv.includes("--reset");
|
|
459
|
+
if (clear || reset) {
|
|
460
|
+
await discardDatabase(cfg, force);
|
|
461
|
+
out(`discarded ${cfg.dbPath}`);
|
|
462
|
+
if (clear)
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const store = await withStore(cfg);
|
|
466
|
+
const existing = existingRequestCount(store);
|
|
467
|
+
// Invented numbers must never be blended into observed ones. A dashboard is
|
|
468
|
+
// only worth anything if you can trust that what it shows was measured.
|
|
469
|
+
if (existing > 0 && !force) {
|
|
470
|
+
store.close();
|
|
471
|
+
throw new Error(`${cfg.dbPath} already holds ${existing} request(s).\n` +
|
|
472
|
+
"Seeding would mix synthetic rows into real traffic. Either start clean:\n" +
|
|
473
|
+
" npm run demo (scratch database, reset each time)\n" +
|
|
474
|
+
" node server/bin/fest.ts seed --reset\n" +
|
|
475
|
+
"or pass --force if you are certain this database is disposable.");
|
|
476
|
+
}
|
|
477
|
+
const requests = intFlag(argv, "requests");
|
|
478
|
+
const hours = intFlag(argv, "hours");
|
|
479
|
+
const result = seed(store, {
|
|
480
|
+
...(requests === undefined ? {} : { requests }),
|
|
481
|
+
...(hours === undefined ? {} : { hours }),
|
|
482
|
+
});
|
|
483
|
+
store.close();
|
|
484
|
+
out(`seeded ${result.written} synthetic requests into ${cfg.dbPath}`);
|
|
485
|
+
out(`developers: ${result.users.join(", ")}`);
|
|
486
|
+
out("");
|
|
487
|
+
out("These rows are INVENTED. Discard them with --clear before trusting any");
|
|
488
|
+
out("number in this database.");
|
|
489
|
+
}
|
|
490
|
+
function printHelp() {
|
|
491
|
+
out("server operator:\n" +
|
|
492
|
+
" fest serve | migrate |\n" +
|
|
493
|
+
" seed [--requests N] [--hours N] [--reset | --clear] [--force] |\n" +
|
|
494
|
+
" token create <email> [name] | token list | token revoke <id> |\n" +
|
|
495
|
+
" admin create <email> [--role R] [--password-stdin] | admin list |\n" +
|
|
496
|
+
" admin passwd <email> | admin disable <email> | admin enable <email>\n" +
|
|
497
|
+
"\n" +
|
|
498
|
+
"developer (run on your own machine, no server env needed):\n" +
|
|
499
|
+
" fest login [--provider google|github] [--server <url>]\n" +
|
|
500
|
+
" fest whoami\n" +
|
|
501
|
+
" fest claude [gw] [-- claude args...] (gw: force org credentials + gateway models)\n" +
|
|
502
|
+
" fest logout");
|
|
503
|
+
}
|
|
504
|
+
async function cmdLogin(argv) {
|
|
505
|
+
const providerFlag = flagValue(argv, "provider") ?? "google";
|
|
506
|
+
if (providerFlag !== "google" && providerFlag !== "github") {
|
|
507
|
+
throw new Error("--provider must be google or github");
|
|
508
|
+
}
|
|
509
|
+
const serverFlag = flagValue(argv, "server");
|
|
510
|
+
const serverUrl = serverFlag ?? (await readCliConfig())?.serverUrl;
|
|
511
|
+
if (serverUrl === undefined) {
|
|
512
|
+
throw new Error("usage: fest login --server <url> [--provider google|github]\n(--server is required the first time)");
|
|
513
|
+
}
|
|
514
|
+
await runLogin({ provider: providerFlag, serverUrl });
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Commands a developer runs on their own machine. Dispatched before
|
|
518
|
+
* `loadConfig()` is ever called: a laptop has no reason to have any FEST_*
|
|
519
|
+
* server env var set, and these commands must not imply otherwise.
|
|
520
|
+
*/
|
|
521
|
+
const CLIENT_COMMANDS = new Set(["login", "whoami", "claude", "logout"]);
|
|
522
|
+
async function main() {
|
|
523
|
+
const argv = process.argv.slice(2);
|
|
524
|
+
const cmd = argv[0] ?? "help";
|
|
525
|
+
if (CLIENT_COMMANDS.has(cmd)) {
|
|
526
|
+
switch (cmd) {
|
|
527
|
+
case "login":
|
|
528
|
+
await cmdLogin(argv.slice(1));
|
|
529
|
+
return;
|
|
530
|
+
case "whoami":
|
|
531
|
+
await runWhoami();
|
|
532
|
+
return;
|
|
533
|
+
case "claude": {
|
|
534
|
+
let claudeArgs = argv.slice(1);
|
|
535
|
+
// `gw` is the one word this subcommand owns, and only before `--`:
|
|
536
|
+
// `fest claude -- gw` still forwards `gw` to the child.
|
|
537
|
+
const posture = claudeArgs[0] === "gw" ? "key" : "auto";
|
|
538
|
+
if (posture === "key")
|
|
539
|
+
claudeArgs = claudeArgs.slice(1);
|
|
540
|
+
// `fest claude -- --resume` and `fest claude --resume` are both fine:
|
|
541
|
+
// apart from `gw` this subcommand defines no flags of its own, so a
|
|
542
|
+
// leading `--` is only ever there by convention and is stripped rather
|
|
543
|
+
// than forwarded.
|
|
544
|
+
if (claudeArgs[0] === "--")
|
|
545
|
+
claudeArgs = claudeArgs.slice(1);
|
|
546
|
+
await runClaude(claudeArgs, posture);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
case "logout":
|
|
550
|
+
await runLogout();
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
555
|
+
printHelp();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const cfg = loadConfig();
|
|
559
|
+
setLogLevel(cfg.logLevel);
|
|
560
|
+
switch (cmd) {
|
|
561
|
+
case "serve":
|
|
562
|
+
await cmdServe(cfg);
|
|
563
|
+
return;
|
|
564
|
+
case "migrate": {
|
|
565
|
+
const store = await withStore(cfg);
|
|
566
|
+
ensureOrg(store);
|
|
567
|
+
const version = store.db.prepare("PRAGMA user_version").get();
|
|
568
|
+
out(`migrated: ${cfg.dbPath} (user_version=${Object.values(version)[0]})`);
|
|
569
|
+
store.close();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
case "token":
|
|
573
|
+
await cmdToken(cfg, argv.slice(1));
|
|
574
|
+
return;
|
|
575
|
+
case "admin":
|
|
576
|
+
await cmdAdmin(cfg, argv.slice(1));
|
|
577
|
+
return;
|
|
578
|
+
case "seed":
|
|
579
|
+
await cmdSeed(cfg, argv.slice(1));
|
|
580
|
+
return;
|
|
581
|
+
default:
|
|
582
|
+
throw new Error(`unknown command: ${cmd}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
main().catch((err) => {
|
|
586
|
+
process.stderr.write(`fest: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
});
|
|
589
|
+
//# sourceMappingURL=fest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fest.js","sourceRoot":"","sources":["../../../server/bin/fest.ts"],"names":[],"mappings":"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"}
|