@haverstack/server 0.8.0 → 0.9.1
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/dist/app.d.ts +8 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +70 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/changeStreams.d.ts +17 -0
- package/dist/lib/changeStreams.d.ts.map +1 -0
- package/{src/lib/changeStreams.ts → dist/lib/changeStreams.js} +12 -12
- package/dist/lib/changeStreams.js.map +1 -0
- package/dist/lib/frameGate.d.ts +26 -0
- package/dist/lib/frameGate.d.ts.map +1 -0
- package/dist/lib/frameGate.js +44 -0
- package/dist/lib/frameGate.js.map +1 -0
- package/dist/lib/json.d.ts +10 -0
- package/dist/lib/json.d.ts.map +1 -0
- package/dist/lib/json.js +27 -0
- package/dist/lib/json.js.map +1 -0
- package/dist/lib/nonceStore.d.ts +50 -0
- package/dist/lib/nonceStore.d.ts.map +1 -0
- package/dist/lib/nonceStore.js +96 -0
- package/dist/lib/nonceStore.js.map +1 -0
- package/dist/lib/queryLimit.d.ts +4 -0
- package/dist/lib/queryLimit.d.ts.map +1 -0
- package/{src/lib/queryLimit.ts → dist/lib/queryLimit.js} +5 -6
- package/dist/lib/queryLimit.js.map +1 -0
- package/dist/lib/queryWorker/pool.d.ts +60 -0
- package/dist/lib/queryWorker/pool.d.ts.map +1 -0
- package/dist/lib/queryWorker/pool.js +278 -0
- package/dist/lib/queryWorker/pool.js.map +1 -0
- package/{src/lib/queryWorker/protocol.ts → dist/lib/queryWorker/protocol.d.ts} +21 -18
- package/dist/lib/queryWorker/protocol.d.ts.map +1 -0
- package/dist/lib/queryWorker/protocol.js +2 -0
- package/dist/lib/queryWorker/protocol.js.map +1 -0
- package/dist/lib/queryWorker/worker.d.ts +2 -0
- package/dist/lib/queryWorker/worker.d.ts.map +1 -0
- package/{src/lib/queryWorker/worker.ts → dist/lib/queryWorker/worker.js} +19 -22
- package/dist/lib/queryWorker/worker.js.map +1 -0
- package/dist/lib/resumeBuffer.d.ts +143 -0
- package/dist/lib/resumeBuffer.d.ts.map +1 -0
- package/dist/lib/resumeBuffer.js +216 -0
- package/dist/lib/resumeBuffer.js.map +1 -0
- package/dist/lib/resumeCursor.d.ts +16 -0
- package/dist/lib/resumeCursor.d.ts.map +1 -0
- package/{src/lib/resumeCursor.ts → dist/lib/resumeCursor.js} +22 -24
- package/dist/lib/resumeCursor.js.map +1 -0
- package/dist/lib/tokenSweep.d.ts +17 -0
- package/dist/lib/tokenSweep.d.ts.map +1 -0
- package/dist/lib/tokenSweep.js +40 -0
- package/dist/lib/tokenSweep.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +46 -0
- package/dist/main.js.map +1 -0
- package/dist/middleware/auth.d.ts +25 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +74 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/errors.d.ts +17 -0
- package/dist/middleware/errors.d.ts.map +1 -0
- package/dist/middleware/errors.js +64 -0
- package/dist/middleware/errors.js.map +1 -0
- package/dist/routes/attachments.d.ts +5 -0
- package/dist/routes/attachments.d.ts.map +1 -0
- package/dist/routes/attachments.js +108 -0
- package/dist/routes/attachments.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +110 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/changes.d.ts +22 -0
- package/dist/routes/changes.d.ts.map +1 -0
- package/dist/routes/changes.js +233 -0
- package/dist/routes/changes.js.map +1 -0
- package/dist/routes/entity.d.ts +5 -0
- package/dist/routes/entity.d.ts.map +1 -0
- package/dist/routes/entity.js +30 -0
- package/dist/routes/entity.js.map +1 -0
- package/dist/routes/health.d.ts +4 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +7 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/records.d.ts +5 -0
- package/dist/routes/records.d.ts.map +1 -0
- package/dist/routes/records.js +231 -0
- package/dist/routes/records.js.map +1 -0
- package/dist/routes/tokens.d.ts +5 -0
- package/dist/routes/tokens.d.ts.map +1 -0
- package/dist/routes/tokens.js +65 -0
- package/dist/routes/tokens.js.map +1 -0
- package/dist/routes/types.d.ts +5 -0
- package/dist/routes/types.d.ts.map +1 -0
- package/dist/routes/types.js +46 -0
- package/dist/routes/types.js.map +1 -0
- package/dist/routes/wellknown.d.ts +6 -0
- package/dist/routes/wellknown.d.ts.map +1 -0
- package/dist/routes/wellknown.js +45 -0
- package/dist/routes/wellknown.js.map +1 -0
- package/dist/shutdown.d.ts +23 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +32 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/stack.d.ts +20 -0
- package/dist/stack.d.ts.map +1 -0
- package/dist/stack.js +50 -0
- package/dist/stack.js.map +1 -0
- package/dist/testing.d.ts +49 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +130 -0
- package/dist/testing.js.map +1 -0
- package/{src/types.ts → dist/types.d.ts} +5 -5
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/{src/wireError.ts → dist/wireError.d.ts} +2 -9
- package/dist/wireError.d.ts.map +1 -0
- package/dist/wireError.js +13 -0
- package/dist/wireError.js.map +1 -0
- package/package.json +42 -17
- package/.claude/settings.local.json +0 -22
- package/.dockerignore +0 -10
- package/.env.example +0 -90
- package/.github/pull_request_template.md +0 -23
- package/.github/workflows/ci.yml +0 -55
- package/.github/workflows/release.yml +0 -99
- package/.prettierignore +0 -3
- package/.prettierrc +0 -7
- package/AGENTS.md +0 -91
- package/CONTRIBUTING.md +0 -234
- package/Dockerfile +0 -30
- package/SECURITY.md +0 -54
- package/docs/api.md +0 -277
- package/docs/deployment.md +0 -212
- package/eslint.config.mjs +0 -18
- package/pnpm-workspace.yaml +0 -13
- package/src/app.ts +0 -86
- package/src/config.ts +0 -176
- package/src/index.ts +0 -54
- package/src/lib/frameGate.ts +0 -42
- package/src/lib/json.ts +0 -27
- package/src/lib/nonceStore.ts +0 -103
- package/src/lib/queryWorker/pool.ts +0 -324
- package/src/lib/resumeBuffer.ts +0 -268
- package/src/lib/tokenSweep.ts +0 -43
- package/src/middleware/auth.ts +0 -77
- package/src/middleware/errors.ts +0 -73
- package/src/routes/attachments.ts +0 -126
- package/src/routes/auth.ts +0 -137
- package/src/routes/changes.ts +0 -279
- package/src/routes/entity.ts +0 -33
- package/src/routes/health.ts +0 -8
- package/src/routes/records.ts +0 -259
- package/src/routes/tokens.ts +0 -85
- package/src/routes/types.ts +0 -57
- package/src/routes/wellknown.ts +0 -55
- package/src/shutdown.ts +0 -56
- package/src/stack.ts +0 -96
- package/tests/app.test.ts +0 -86
- package/tests/changeFeedClient.ts +0 -156
- package/tests/config.test.ts +0 -112
- package/tests/conformance.test.ts +0 -2322
- package/tests/fixtures/blockingWorker.ts +0 -27
- package/tests/fixtures/brokenWorker.ts +0 -7
- package/tests/lib/changeFeedClient.test.ts +0 -126
- package/tests/lib/frameGate.test.ts +0 -93
- package/tests/lib/queryLimit.test.ts +0 -33
- package/tests/lib/queryWorker.test.ts +0 -60
- package/tests/lib/queryWorkerPool.test.ts +0 -129
- package/tests/lib/resumeBuffer.test.ts +0 -341
- package/tests/lib/resumeCursor.test.ts +0 -46
- package/tests/middleware/errors.test.ts +0 -176
- package/tests/routes/associations.test.ts +0 -170
- package/tests/routes/attachments.test.ts +0 -621
- package/tests/routes/auth.test.ts +0 -448
- package/tests/routes/changes.test.ts +0 -567
- package/tests/routes/contentQuery.test.ts +0 -68
- package/tests/routes/entity.test.ts +0 -170
- package/tests/routes/groups.test.ts +0 -69
- package/tests/routes/records.test.ts +0 -1965
- package/tests/routes/relatedToQuery.test.ts +0 -182
- package/tests/routes/tokens.test.ts +0 -157
- package/tests/routes/tombstones.test.ts +0 -93
- package/tests/routes/types.test.ts +0 -129
- package/tests/routes/versions.test.ts +0 -177
- package/tests/routes/wellknown.test.ts +0 -71
- package/tests/setup.ts +0 -154
- package/tests/shutdown.test.ts +0 -121
- package/tests/stack.test.ts +0 -198
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -12
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
import type { StackContext } from './stack.js';
|
|
4
|
+
import type { Config } from './config.js';
|
|
5
|
+
import type { AppEnv } from './types.js';
|
|
6
|
+
export type { AppEnv };
|
|
7
|
+
export declare function createApp(ctx: StackContext, config: Config, logger: Logger): Hono<AppEnv>;
|
|
8
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAczC,YAAY,EAAE,MAAM,EAAE,CAAC;AAEvB,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CA8DzF"}
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { cors } from 'hono/cors';
|
|
3
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
4
|
+
import { StackPayloadTooLargeError } from '@haverstack/core';
|
|
5
|
+
import { authMiddleware } from './middleware/auth.js';
|
|
6
|
+
import { errorMiddleware } from './middleware/errors.js';
|
|
7
|
+
import { wellknownRoutes } from './routes/wellknown.js';
|
|
8
|
+
import { healthRoutes } from './routes/health.js';
|
|
9
|
+
import { recordRoutes } from './routes/records.js';
|
|
10
|
+
import { changeRoutes } from './routes/changes.js';
|
|
11
|
+
import { typeRoutes } from './routes/types.js';
|
|
12
|
+
import { attachmentRoutes } from './routes/attachments.js';
|
|
13
|
+
import { entityRoutes } from './routes/entity.js';
|
|
14
|
+
import { tokenRoutes } from './routes/tokens.js';
|
|
15
|
+
import { authRoutes } from './routes/auth.js';
|
|
16
|
+
import { wireError } from './wireError.js';
|
|
17
|
+
export function createApp(ctx, config, logger) {
|
|
18
|
+
const app = new Hono();
|
|
19
|
+
// Assign a unique request ID to every request and expose it on the response.
|
|
20
|
+
app.use(async (c, next) => {
|
|
21
|
+
const id = crypto.randomUUID();
|
|
22
|
+
c.set('requestId', id);
|
|
23
|
+
await next();
|
|
24
|
+
c.header('X-Request-Id', id);
|
|
25
|
+
});
|
|
26
|
+
app.use(cors({
|
|
27
|
+
origin: config.corsOrigins === '*'
|
|
28
|
+
? '*'
|
|
29
|
+
: config.corsOrigins
|
|
30
|
+
? config.corsOrigins.split(',').map((s) => s.trim())
|
|
31
|
+
: [],
|
|
32
|
+
allowMethods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'],
|
|
33
|
+
// Last-Event-ID: a browser resuming GET /changes sends it as a plain
|
|
34
|
+
// header (fetch, not EventSource, per the feed's no-token-in-the-URL
|
|
35
|
+
// rule). Omitted here, a cross-origin resume never reaches the route
|
|
36
|
+
// that would answer it with `reset`.
|
|
37
|
+
allowHeaders: ['Authorization', 'Content-Type', 'Content-Disposition', 'Last-Event-ID'],
|
|
38
|
+
exposeHeaders: ['X-Request-Id', 'Content-Disposition'],
|
|
39
|
+
}));
|
|
40
|
+
app.onError(errorMiddleware(logger, ctx.stack));
|
|
41
|
+
app.use(authMiddleware(config.ownerToken, ctx));
|
|
42
|
+
// Global rather than per-prefix, so a route added later can't end up with
|
|
43
|
+
// no limit at all. POST /attachments is the one exception: it enforces its
|
|
44
|
+
// own, larger ceiling, and running this one first would reject legitimate
|
|
45
|
+
// uploads before that check ran. Thrown as a StackError so
|
|
46
|
+
// errorMiddleware produces the conforming body, like any other failure.
|
|
47
|
+
const jsonBodyLimit = bodyLimit({
|
|
48
|
+
maxSize: config.maxContentBytes,
|
|
49
|
+
onError: () => {
|
|
50
|
+
throw new StackPayloadTooLargeError(`Request body exceeds the ${config.maxContentBytes}-byte limit`);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
app.use('*', async (c, next) => {
|
|
54
|
+
if (c.req.method === 'POST' && c.req.path === '/attachments')
|
|
55
|
+
return next();
|
|
56
|
+
return jsonBodyLimit(c, next);
|
|
57
|
+
});
|
|
58
|
+
app.route('/.well-known', wellknownRoutes(ctx, config));
|
|
59
|
+
app.route('/health', healthRoutes());
|
|
60
|
+
app.route('/records', recordRoutes(ctx, config.queryTimeoutMs));
|
|
61
|
+
app.route('/changes', changeRoutes(ctx, config, logger));
|
|
62
|
+
app.route('/types', typeRoutes(ctx));
|
|
63
|
+
app.route('/attachments', attachmentRoutes(ctx, config.maxAttachmentBytes));
|
|
64
|
+
app.route('/entity', entityRoutes(ctx));
|
|
65
|
+
app.route('/tokens', tokenRoutes(ctx));
|
|
66
|
+
app.route('/auth', authRoutes(ctx, config.authOrigin, logger));
|
|
67
|
+
app.notFound((c) => wireError(c, 404, 'not_found', 'Not found'));
|
|
68
|
+
return app;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Config = {
|
|
2
|
+
port: number;
|
|
3
|
+
dbPath: string;
|
|
4
|
+
entityId: string | null;
|
|
5
|
+
ownerName: string | null;
|
|
6
|
+
ownerHandle: string | null;
|
|
7
|
+
timezone: string | undefined;
|
|
8
|
+
ownerToken: string;
|
|
9
|
+
corsOrigins: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
authOrigin: string;
|
|
12
|
+
maxAttachmentBytes: number;
|
|
13
|
+
maxContentBytes: number;
|
|
14
|
+
queryTimeoutMs: number;
|
|
15
|
+
queryWorkerPoolSize: number;
|
|
16
|
+
queryQueueLimit: number;
|
|
17
|
+
seedCommonsTypes: boolean;
|
|
18
|
+
shutdownTimeoutMs: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function loadConfig(): Config;
|
|
21
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAgB,UAAU,IAAI,MAAM,CAkInC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { isValidDid } from '@haverstack/core/did';
|
|
2
|
+
import { authOriginFromUrl } from '@haverstack/core/wire';
|
|
3
|
+
const DEFAULT_MAX_ATTACHMENT_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
4
|
+
const DEFAULT_MAX_CONTENT_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
5
|
+
const DEFAULT_QUERY_TIMEOUT_MS = 10_000;
|
|
6
|
+
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;
|
|
7
|
+
const DEFAULT_QUERY_WORKER_POOL_SIZE = 2;
|
|
8
|
+
// Each worker is a thread plus its own SQLite connection to the same file,
|
|
9
|
+
// so an accidental extra zero here is expensive in a way the other limits
|
|
10
|
+
// aren't. The ceiling is a sanity bound, not a tuning recommendation.
|
|
11
|
+
const MAX_QUERY_WORKER_POOL_SIZE = 32;
|
|
12
|
+
const DEFAULT_QUERY_QUEUE_LIMIT = 64;
|
|
13
|
+
const MAX_QUERY_QUEUE_LIMIT = 10_000;
|
|
14
|
+
function required(name) {
|
|
15
|
+
const val = process.env[name];
|
|
16
|
+
if (!val)
|
|
17
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
18
|
+
return val;
|
|
19
|
+
}
|
|
20
|
+
function optional(name, fallback) {
|
|
21
|
+
return process.env[name] ?? fallback;
|
|
22
|
+
}
|
|
23
|
+
export function loadConfig() {
|
|
24
|
+
const dbPath = required('DB_PATH');
|
|
25
|
+
const entityId = process.env['ENTITY_ID'] ?? null;
|
|
26
|
+
if (entityId && !isValidDid(entityId)) {
|
|
27
|
+
throw new Error(`ENTITY_ID must be a DID (e.g. "did:key:z6Mk..."), got: "${entityId}". ` +
|
|
28
|
+
'See docs/spec/identity.md for how to generate one.');
|
|
29
|
+
}
|
|
30
|
+
const ownerName = process.env['OWNER_NAME'] ?? null;
|
|
31
|
+
const ownerHandle = process.env['OWNER_HANDLE'] ?? null;
|
|
32
|
+
// No default: an absent timezone stays undefined end to end rather than
|
|
33
|
+
// asserting knowledge the stack was never actually given. See
|
|
34
|
+
// docs/spec/wire-format.md § Discovery.
|
|
35
|
+
const timezone = process.env['TIMEZONE'] ?? undefined;
|
|
36
|
+
const port = parseInt(optional('PORT', '3000'), 10);
|
|
37
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
38
|
+
throw new Error(`Invalid PORT: ${process.env['PORT']}`);
|
|
39
|
+
}
|
|
40
|
+
const maxAttachmentBytes = parseInt(optional('MAX_ATTACHMENT_BYTES', String(DEFAULT_MAX_ATTACHMENT_BYTES)), 10);
|
|
41
|
+
if (isNaN(maxAttachmentBytes) || maxAttachmentBytes < 1) {
|
|
42
|
+
throw new Error(`Invalid MAX_ATTACHMENT_BYTES: ${process.env['MAX_ATTACHMENT_BYTES']}`);
|
|
43
|
+
}
|
|
44
|
+
const maxContentBytes = parseInt(optional('MAX_CONTENT_BYTES', String(DEFAULT_MAX_CONTENT_BYTES)), 10);
|
|
45
|
+
if (isNaN(maxContentBytes) || maxContentBytes < 1) {
|
|
46
|
+
throw new Error(`Invalid MAX_CONTENT_BYTES: ${process.env['MAX_CONTENT_BYTES']}`);
|
|
47
|
+
}
|
|
48
|
+
// Bounds a query *executing* on the worker pool, never its wait for a
|
|
49
|
+
// slot — that is QUERY_QUEUE_LIMIT's job, so ordinary contention makes a
|
|
50
|
+
// search slow rather than failed. See src/lib/queryWorker/pool.ts.
|
|
51
|
+
const queryTimeoutMs = parseInt(optional('QUERY_TIMEOUT_MS', String(DEFAULT_QUERY_TIMEOUT_MS)), 10);
|
|
52
|
+
if (isNaN(queryTimeoutMs) || queryTimeoutMs < 1) {
|
|
53
|
+
throw new Error(`Invalid QUERY_TIMEOUT_MS: ${process.env['QUERY_TIMEOUT_MS']}`);
|
|
54
|
+
}
|
|
55
|
+
const queryWorkerPoolSize = parseInt(optional('QUERY_WORKER_POOL_SIZE', String(DEFAULT_QUERY_WORKER_POOL_SIZE)), 10);
|
|
56
|
+
if (isNaN(queryWorkerPoolSize) ||
|
|
57
|
+
queryWorkerPoolSize < 1 ||
|
|
58
|
+
queryWorkerPoolSize > MAX_QUERY_WORKER_POOL_SIZE) {
|
|
59
|
+
throw new Error(`Invalid QUERY_WORKER_POOL_SIZE: ${process.env['QUERY_WORKER_POOL_SIZE']} ` +
|
|
60
|
+
`(expected 1-${MAX_QUERY_WORKER_POOL_SIZE})`);
|
|
61
|
+
}
|
|
62
|
+
// How many searches may wait for a worker before the pool sheds load
|
|
63
|
+
// with 503 `timeout` instead of queueing further. This is what keeps
|
|
64
|
+
// "slow under contention" from becoming an unbounded queue.
|
|
65
|
+
const queryQueueLimit = parseInt(optional('QUERY_QUEUE_LIMIT', String(DEFAULT_QUERY_QUEUE_LIMIT)), 10);
|
|
66
|
+
if (isNaN(queryQueueLimit) || queryQueueLimit < 1 || queryQueueLimit > MAX_QUERY_QUEUE_LIMIT) {
|
|
67
|
+
throw new Error(`Invalid QUERY_QUEUE_LIMIT: ${process.env['QUERY_QUEUE_LIMIT']} ` +
|
|
68
|
+
`(expected 1-${MAX_QUERY_QUEUE_LIMIT})`);
|
|
69
|
+
}
|
|
70
|
+
// How long shutdown() lets in-flight and keep-alive connections drain
|
|
71
|
+
// before forcing them closed and finishing the sequence anyway.
|
|
72
|
+
const shutdownTimeoutMs = parseInt(optional('SHUTDOWN_TIMEOUT_MS', String(DEFAULT_SHUTDOWN_TIMEOUT_MS)), 10);
|
|
73
|
+
if (isNaN(shutdownTimeoutMs) || shutdownTimeoutMs < 1) {
|
|
74
|
+
throw new Error(`Invalid SHUTDOWN_TIMEOUT_MS: ${process.env['SHUTDOWN_TIMEOUT_MS']}`);
|
|
75
|
+
}
|
|
76
|
+
// Opt-in: @haverstack/commons is Draft status, so its definitions may
|
|
77
|
+
// change in place, and a registered type shows up in GET /types and every
|
|
78
|
+
// app's type cache whether or not that app wants it. See
|
|
79
|
+
// docs/deployment.md § Schema Commons seeding.
|
|
80
|
+
const seedCommonsTypes = optional('SEED_COMMONS_TYPES', 'false') === 'true';
|
|
81
|
+
// Required rather than auto-detected: the handshake signs a payload
|
|
82
|
+
// scoped to this server's public origin, and taking that from a
|
|
83
|
+
// client-controlled header (Host, X-Forwarded-Host) would let a client
|
|
84
|
+
// pick the origin it signs for, reopening the relay attack the binding
|
|
85
|
+
// closes. See docs/spec/wire-format.md § Authentication.
|
|
86
|
+
const baseUrl = required('BASE_URL');
|
|
87
|
+
let authOrigin;
|
|
88
|
+
try {
|
|
89
|
+
authOrigin = authOriginFromUrl(baseUrl);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
throw new Error(`BASE_URL must be an absolute URL with an origin (e.g. "https://stack.example.com"), got: "${baseUrl}". ` +
|
|
93
|
+
(err instanceof Error ? err.message : String(err)), { cause: err });
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
port,
|
|
97
|
+
dbPath,
|
|
98
|
+
entityId,
|
|
99
|
+
ownerName,
|
|
100
|
+
ownerHandle,
|
|
101
|
+
timezone,
|
|
102
|
+
ownerToken: required('OWNER_TOKEN'),
|
|
103
|
+
corsOrigins: optional('CORS_ORIGINS', ''),
|
|
104
|
+
baseUrl,
|
|
105
|
+
authOrigin,
|
|
106
|
+
maxAttachmentBytes,
|
|
107
|
+
maxContentBytes,
|
|
108
|
+
queryTimeoutMs,
|
|
109
|
+
queryWorkerPoolSize,
|
|
110
|
+
queryQueueLimit,
|
|
111
|
+
seedCommonsTypes,
|
|
112
|
+
shutdownTimeoutMs,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @haverstack/server's library surface. The package is primarily an app
|
|
3
|
+
* (`dist/main.js`, run via `pnpm start` / the Docker image — see
|
|
4
|
+
* src/main.ts), but these exports let a consumer build and run the same
|
|
5
|
+
* Hono app in-process: embedding it behind their own listener, or driving a
|
|
6
|
+
* real instance in tests. See `@haverstack/server/testing` for the latter.
|
|
7
|
+
*/
|
|
8
|
+
export { createApp } from './app.js';
|
|
9
|
+
export type { AppEnv } from './types.js';
|
|
10
|
+
export { initStack } from './stack.js';
|
|
11
|
+
export type { StackContext } from './stack.js';
|
|
12
|
+
export { loadConfig } from './config.js';
|
|
13
|
+
export type { Config } from './config.js';
|
|
14
|
+
export { createShutdownHandler } from './shutdown.js';
|
|
15
|
+
export type { ShutdownServer } from './shutdown.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,YAAY,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @haverstack/server's library surface. The package is primarily an app
|
|
3
|
+
* (`dist/main.js`, run via `pnpm start` / the Docker image — see
|
|
4
|
+
* src/main.ts), but these exports let a consumer build and run the same
|
|
5
|
+
* Hono app in-process: embedding it behind their own listener, or driving a
|
|
6
|
+
* real instance in tests. See `@haverstack/server/testing` for the latter.
|
|
7
|
+
*/
|
|
8
|
+
export { createApp } from './app.js';
|
|
9
|
+
export { initStack } from './stack.js';
|
|
10
|
+
export { loadConfig } from './config.js';
|
|
11
|
+
export { createShutdownHandler } from './shutdown.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks every open `GET /changes` SSE connection so shutdown can end them
|
|
3
|
+
* up front. `server.close()` (src/shutdown.ts) waits for open
|
|
4
|
+
* connections to drain on their own before its callback fires — fine for an
|
|
5
|
+
* ordinary request, but a change-feed connection is meant to stay open
|
|
6
|
+
* indefinitely, so left alone it would hold the drain open for the entire
|
|
7
|
+
* shutdown grace period every time. Closing each tracked stream as shutdown
|
|
8
|
+
* begins lets that drain finish immediately instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ChangeStreamRegistry {
|
|
11
|
+
private readonly streams;
|
|
12
|
+
/** Register an open stream's close callback. Returns the deregister function. */
|
|
13
|
+
add(close: () => void): () => void;
|
|
14
|
+
/** Close every currently-open stream. Safe to call with none open. */
|
|
15
|
+
closeAll(): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=changeStreams.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changeStreams.d.ts","sourceRoot":"","sources":["../../src/lib/changeStreams.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IAEjD,iFAAiF;IACjF,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKlC,sEAAsE;IACtE,QAAQ,IAAI,IAAI;CAGjB"}
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
* begins lets that drain finish immediately instead.
|
|
9
9
|
*/
|
|
10
10
|
export class ChangeStreamRegistry {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
11
|
+
streams = new Set();
|
|
12
|
+
/** Register an open stream's close callback. Returns the deregister function. */
|
|
13
|
+
add(close) {
|
|
14
|
+
this.streams.add(close);
|
|
15
|
+
return () => this.streams.delete(close);
|
|
16
|
+
}
|
|
17
|
+
/** Close every currently-open stream. Safe to call with none open. */
|
|
18
|
+
closeAll() {
|
|
19
|
+
for (const close of this.streams)
|
|
20
|
+
close();
|
|
21
|
+
}
|
|
23
22
|
}
|
|
23
|
+
//# sourceMappingURL=changeStreams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changeStreams.js","sourceRoot":"","sources":["../../src/lib/changeStreams.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,OAAO,oBAAoB;IACd,OAAO,GAAG,IAAI,GAAG,EAAc,CAAC;IAEjD,iFAAiF;IACjF,GAAG,CAAC,KAAiB;QACnB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,sEAAsE;IACtE,QAAQ;QACN,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO;YAAE,KAAK,EAAE,CAAC;IAC5C,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds a change-feed connection's in-flight frame count. Each accepted
|
|
3
|
+
* send is paired with the write that delivers it; once `maxPending` sends
|
|
4
|
+
* are outstanding at once — a client that isn't draining as fast as
|
|
5
|
+
* changes arrive — the gate reports overflow instead of queuing further
|
|
6
|
+
* sends without bound.
|
|
7
|
+
*
|
|
8
|
+
* Close on overflow, never drop a frame silently: a client that can't tell
|
|
9
|
+
* it missed something can't repair it either, so dropping is worse than
|
|
10
|
+
* disconnecting and letting it reconnect and reconcile.
|
|
11
|
+
*/
|
|
12
|
+
export declare class FrameGate {
|
|
13
|
+
private readonly maxPending;
|
|
14
|
+
private readonly onOverflow;
|
|
15
|
+
private pending;
|
|
16
|
+
private overflowed;
|
|
17
|
+
constructor(maxPending: number, onOverflow: () => void);
|
|
18
|
+
/**
|
|
19
|
+
* Accepts one send if there's room, invoking `write` and tracking it
|
|
20
|
+
* until it settles. Returns whether it was accepted; a `false` return
|
|
21
|
+
* means this call (and every one after it) triggered — or already
|
|
22
|
+
* followed — overflow, and `onOverflow` has fired at most once.
|
|
23
|
+
*/
|
|
24
|
+
send(write: () => Promise<unknown>): boolean;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=frameGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameGate.d.ts","sourceRoot":"","sources":["../../src/lib/frameGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,qBAAa,SAAS;IAKlB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAL7B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,UAAU,CAAS;gBAGR,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,IAAI;IAGzC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;CAe7C"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds a change-feed connection's in-flight frame count. Each accepted
|
|
3
|
+
* send is paired with the write that delivers it; once `maxPending` sends
|
|
4
|
+
* are outstanding at once — a client that isn't draining as fast as
|
|
5
|
+
* changes arrive — the gate reports overflow instead of queuing further
|
|
6
|
+
* sends without bound.
|
|
7
|
+
*
|
|
8
|
+
* Close on overflow, never drop a frame silently: a client that can't tell
|
|
9
|
+
* it missed something can't repair it either, so dropping is worse than
|
|
10
|
+
* disconnecting and letting it reconnect and reconcile.
|
|
11
|
+
*/
|
|
12
|
+
export class FrameGate {
|
|
13
|
+
maxPending;
|
|
14
|
+
onOverflow;
|
|
15
|
+
pending = 0;
|
|
16
|
+
overflowed = false;
|
|
17
|
+
constructor(maxPending, onOverflow) {
|
|
18
|
+
this.maxPending = maxPending;
|
|
19
|
+
this.onOverflow = onOverflow;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Accepts one send if there's room, invoking `write` and tracking it
|
|
23
|
+
* until it settles. Returns whether it was accepted; a `false` return
|
|
24
|
+
* means this call (and every one after it) triggered — or already
|
|
25
|
+
* followed — overflow, and `onOverflow` has fired at most once.
|
|
26
|
+
*/
|
|
27
|
+
send(write) {
|
|
28
|
+
if (this.overflowed)
|
|
29
|
+
return false;
|
|
30
|
+
if (this.pending >= this.maxPending) {
|
|
31
|
+
this.overflowed = true;
|
|
32
|
+
this.onOverflow();
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
this.pending++;
|
|
36
|
+
write()
|
|
37
|
+
.catch(() => { })
|
|
38
|
+
.finally(() => {
|
|
39
|
+
this.pending--;
|
|
40
|
+
});
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=frameGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameGate.js","sourceRoot":"","sources":["../../src/lib/frameGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,SAAS;IAKD;IACA;IALX,OAAO,GAAG,CAAC,CAAC;IACZ,UAAU,GAAG,KAAK,CAAC;IAE3B,YACmB,UAAkB,EAClB,UAAsB;QADtB,eAAU,GAAV,UAAU,CAAQ;QAClB,eAAU,GAAV,UAAU,CAAY;IACtC,CAAC;IAEJ;;;;;OAKG;IACH,IAAI,CAAC,KAA6B;QAChC,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QAClC,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,KAAK,EAAE;aACJ,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;aACf,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;QACL,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import type { AppEnv } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* `c.req.json()` throws a bare `SyntaxError` on malformed input, which isn't
|
|
5
|
+
* a `StackError` — left uncaught it falls through errorMiddleware's
|
|
6
|
+
* catch-all as an unlabeled 500 instead of the 400 `bad_request` every other
|
|
7
|
+
* structurally-invalid request gets (docs/spec/wire-format.md § 400 vs 422).
|
|
8
|
+
*/
|
|
9
|
+
export declare function readJson<T = unknown>(c: Context<AppEnv>): Promise<T>;
|
|
10
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/lib/json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAgB1E"}
|
package/dist/lib/json.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { StackQueryError } from '@haverstack/core';
|
|
2
|
+
/**
|
|
3
|
+
* `c.req.json()` throws a bare `SyntaxError` on malformed input, which isn't
|
|
4
|
+
* a `StackError` — left uncaught it falls through errorMiddleware's
|
|
5
|
+
* catch-all as an unlabeled 500 instead of the 400 `bad_request` every other
|
|
6
|
+
* structurally-invalid request gets (docs/spec/wire-format.md § 400 vs 422).
|
|
7
|
+
*/
|
|
8
|
+
export async function readJson(c) {
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = await c.req.json();
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
if (err instanceof SyntaxError)
|
|
15
|
+
throw new StackQueryError('Invalid JSON in request body');
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
// `null`, a bare string or a number parses fine, but every call site
|
|
19
|
+
// indexes fields off the result, so a non-object reaches the handler and
|
|
20
|
+
// throws a bare TypeError — another unlabeled 500, unauthenticated on the
|
|
21
|
+
// /auth routes. Structurally invalid like malformed JSON, so a 400 too.
|
|
22
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
23
|
+
throw new StackQueryError('Request body must be a JSON object');
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/lib/json.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAc,CAAkB;IAC5D,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAK,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,WAAW;YAAE,MAAM,IAAI,eAAe,CAAC,8BAA8B,CAAC,CAAC;QAC1F,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,qEAAqE;IACrE,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,eAAe,CAAC,oCAAoC,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,MAAW,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Conventional sibling path for the nonce store beside a stack's main .db file. */
|
|
2
|
+
export declare const defaultNonceStorePath: (dbPath: string) => string;
|
|
3
|
+
export type NonceRedeemResult = 'ok' | 'unknown' | 'expired';
|
|
4
|
+
/**
|
|
5
|
+
* Single-use, DID-bound nonces for the DID challenge–response handshake
|
|
6
|
+
* (POST /auth/challenge, POST /auth/token). Core verifies signatures but
|
|
7
|
+
* deliberately doesn't run a server, so nonce storage, single-use
|
|
8
|
+
* enforcement, and DID binding are this server's own to implement — see
|
|
9
|
+
* docs/spec/wire-format.md § Authentication.
|
|
10
|
+
*
|
|
11
|
+
* Kept beside the token store (its own sibling file) rather than in the
|
|
12
|
+
* portable stack file or in memory, so a restart doesn't silently make
|
|
13
|
+
* every in-flight handshake unrecoverable. node:sqlite, like
|
|
14
|
+
* NativeTokenStore — no extra dependency.
|
|
15
|
+
*/
|
|
16
|
+
export declare class AuthNonceStore {
|
|
17
|
+
private readonly db;
|
|
18
|
+
private constructor();
|
|
19
|
+
static open(path: string): AuthNonceStore;
|
|
20
|
+
issue(did: string): {
|
|
21
|
+
nonce: string;
|
|
22
|
+
expiresAt: Date;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Checks and spends a nonce in one step — single-use means a redemption
|
|
26
|
+
* consumes it regardless of outcome, not merely observes it. A missing
|
|
27
|
+
* nonce and one bound to a different DID both report 'unknown': a server
|
|
28
|
+
* MUST NOT let a caller distinguish never-issued from already-spent, or
|
|
29
|
+
* spent-by-someone-else, since the difference only tells an attacker
|
|
30
|
+
* something.
|
|
31
|
+
*
|
|
32
|
+
* One `DELETE ... RETURNING` rather than a SELECT then a DELETE, which
|
|
33
|
+
* would be indivisible only for as long as nothing awaits between the two
|
|
34
|
+
* statements — an invariant no reader can see. The `did` predicate sits in
|
|
35
|
+
* the WHERE clause, so a mismatch deletes nothing.
|
|
36
|
+
*/
|
|
37
|
+
redeem(did: string, nonce: string): NonceRedeemResult;
|
|
38
|
+
/**
|
|
39
|
+
* Seeds a nonce with a caller-chosen value rather than a random one.
|
|
40
|
+
* Production code never needs this — issue() is the only way a nonce is
|
|
41
|
+
* meant to come into existence — but a conformance fixture carrying a
|
|
42
|
+
* real signature is signed over one exact, fixed nonce value, so tests
|
|
43
|
+
* need a way to get that value into the store directly. Named for what
|
|
44
|
+
* it is so no production call site can reach for it without saying so.
|
|
45
|
+
*/
|
|
46
|
+
seedForTesting(nonce: string, did: string, expiresAt: Date): void;
|
|
47
|
+
private sweepExpired;
|
|
48
|
+
close(): void;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=nonceStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nonceStore.d.ts","sourceRoot":"","sources":["../../src/lib/nonceStore.ts"],"names":[],"mappings":"AAWA,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,GAAI,QAAQ,MAAM,KAAG,MAA4B,CAAC;AAEpF,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,CAAC;AAE7D;;;;;;;;;;;GAWG;AACH,qBAAa,cAAc;IACL,OAAO,CAAC,QAAQ,CAAC,EAAE;IAAvC,OAAO;IAEP,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc;IAkBzC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE;IAUtD;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB;IASrD;;;;;;;OAOG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI;IAMjE,OAAO,CAAC,YAAY;IAIpB,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { base64urlEncode } from '@haverstack/core/wire';
|
|
3
|
+
// Matches the ~5-minute issued→expires gap in the handshake conformance
|
|
4
|
+
// fixtures (@haverstack/conformance-fixtures' authChallengeFixtures).
|
|
5
|
+
const NONCE_TTL_MS = 5 * 60 * 1000;
|
|
6
|
+
// 18 raw bytes base64url-encodes to 24 characters — short, opaque, and
|
|
7
|
+
// comfortably unguessable for a value that's live for minutes, not stored
|
|
8
|
+
// long-term like a token.
|
|
9
|
+
const NONCE_BYTES = 18;
|
|
10
|
+
/** Conventional sibling path for the nonce store beside a stack's main .db file. */
|
|
11
|
+
export const defaultNonceStorePath = (dbPath) => `${dbPath}.nonces`;
|
|
12
|
+
/**
|
|
13
|
+
* Single-use, DID-bound nonces for the DID challenge–response handshake
|
|
14
|
+
* (POST /auth/challenge, POST /auth/token). Core verifies signatures but
|
|
15
|
+
* deliberately doesn't run a server, so nonce storage, single-use
|
|
16
|
+
* enforcement, and DID binding are this server's own to implement — see
|
|
17
|
+
* docs/spec/wire-format.md § Authentication.
|
|
18
|
+
*
|
|
19
|
+
* Kept beside the token store (its own sibling file) rather than in the
|
|
20
|
+
* portable stack file or in memory, so a restart doesn't silently make
|
|
21
|
+
* every in-flight handshake unrecoverable. node:sqlite, like
|
|
22
|
+
* NativeTokenStore — no extra dependency.
|
|
23
|
+
*/
|
|
24
|
+
export class AuthNonceStore {
|
|
25
|
+
db;
|
|
26
|
+
constructor(db) {
|
|
27
|
+
this.db = db;
|
|
28
|
+
}
|
|
29
|
+
static open(path) {
|
|
30
|
+
const db = new DatabaseSync(path);
|
|
31
|
+
db.exec('PRAGMA journal_mode = WAL;');
|
|
32
|
+
db.exec(`CREATE TABLE IF NOT EXISTS nonces (
|
|
33
|
+
nonce TEXT PRIMARY KEY,
|
|
34
|
+
did TEXT NOT NULL,
|
|
35
|
+
expiresAt INTEGER NOT NULL
|
|
36
|
+
);`);
|
|
37
|
+
// sweepExpired() runs on every issue(), and POST /auth/challenge is
|
|
38
|
+
// unauthenticated — without this the sweep is a full table scan whose
|
|
39
|
+
// cost rises with the issue rate, so the endpoint would pay more the
|
|
40
|
+
// harder it is hit.
|
|
41
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_nonces_expiresAt ON nonces (expiresAt);');
|
|
42
|
+
return new AuthNonceStore(db);
|
|
43
|
+
}
|
|
44
|
+
issue(did) {
|
|
45
|
+
this.sweepExpired();
|
|
46
|
+
const nonce = base64urlEncode(crypto.getRandomValues(new Uint8Array(NONCE_BYTES)));
|
|
47
|
+
const expiresAt = new Date(Date.now() + NONCE_TTL_MS);
|
|
48
|
+
this.db
|
|
49
|
+
.prepare('INSERT INTO nonces (nonce, did, expiresAt) VALUES (?, ?, ?)')
|
|
50
|
+
.run(nonce, did, expiresAt.getTime());
|
|
51
|
+
return { nonce, expiresAt };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Checks and spends a nonce in one step — single-use means a redemption
|
|
55
|
+
* consumes it regardless of outcome, not merely observes it. A missing
|
|
56
|
+
* nonce and one bound to a different DID both report 'unknown': a server
|
|
57
|
+
* MUST NOT let a caller distinguish never-issued from already-spent, or
|
|
58
|
+
* spent-by-someone-else, since the difference only tells an attacker
|
|
59
|
+
* something.
|
|
60
|
+
*
|
|
61
|
+
* One `DELETE ... RETURNING` rather than a SELECT then a DELETE, which
|
|
62
|
+
* would be indivisible only for as long as nothing awaits between the two
|
|
63
|
+
* statements — an invariant no reader can see. The `did` predicate sits in
|
|
64
|
+
* the WHERE clause, so a mismatch deletes nothing.
|
|
65
|
+
*/
|
|
66
|
+
redeem(did, nonce) {
|
|
67
|
+
const row = this.db
|
|
68
|
+
.prepare('DELETE FROM nonces WHERE nonce = ? AND did = ? RETURNING expiresAt')
|
|
69
|
+
.get(nonce, did);
|
|
70
|
+
if (!row)
|
|
71
|
+
return 'unknown';
|
|
72
|
+
if (Date.now() > row.expiresAt)
|
|
73
|
+
return 'expired';
|
|
74
|
+
return 'ok';
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Seeds a nonce with a caller-chosen value rather than a random one.
|
|
78
|
+
* Production code never needs this — issue() is the only way a nonce is
|
|
79
|
+
* meant to come into existence — but a conformance fixture carrying a
|
|
80
|
+
* real signature is signed over one exact, fixed nonce value, so tests
|
|
81
|
+
* need a way to get that value into the store directly. Named for what
|
|
82
|
+
* it is so no production call site can reach for it without saying so.
|
|
83
|
+
*/
|
|
84
|
+
seedForTesting(nonce, did, expiresAt) {
|
|
85
|
+
this.db
|
|
86
|
+
.prepare('INSERT OR REPLACE INTO nonces (nonce, did, expiresAt) VALUES (?, ?, ?)')
|
|
87
|
+
.run(nonce, did, expiresAt.getTime());
|
|
88
|
+
}
|
|
89
|
+
sweepExpired() {
|
|
90
|
+
this.db.prepare('DELETE FROM nonces WHERE expiresAt < ?').run(Date.now());
|
|
91
|
+
}
|
|
92
|
+
close() {
|
|
93
|
+
this.db.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=nonceStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nonceStore.js","sourceRoot":"","sources":["../../src/lib/nonceStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,wEAAwE;AACxE,sEAAsE;AACtE,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AACnC,uEAAuE;AACvE,0EAA0E;AAC1E,0BAA0B;AAC1B,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB,oFAAoF;AACpF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,MAAM,SAAS,CAAC;AAIpF;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,cAAc;IACY;IAArC,YAAqC,EAAgB;QAAhB,OAAE,GAAF,EAAE,CAAc;IAAG,CAAC;IAEzD,MAAM,CAAC,IAAI,CAAC,IAAY;QACtB,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;QAClC,EAAE,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QACtC,EAAE,CAAC,IAAI,CACL;;;;SAIG,CACJ,CAAC;QACF,oEAAoE;QACpE,sEAAsE;QACtE,qEAAqE;QACrE,oBAAoB;QACpB,EAAE,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;QAClF,OAAO,IAAI,cAAc,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACnF,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC,CAAC;QACtD,IAAI,CAAC,EAAE;aACJ,OAAO,CAAC,6DAA6D,CAAC;aACtE,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QACxC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,GAAW,EAAE,KAAa;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CAAC,oEAAoE,CAAC;aAC7E,GAAG,CAAC,KAAK,EAAE,GAAG,CAAsC,CAAC;QACxD,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;OAOG;IACH,cAAc,CAAC,KAAa,EAAE,GAAW,EAAE,SAAe;QACxD,IAAI,CAAC,EAAE;aACJ,OAAO,CAAC,wEAAwE,CAAC;aACjF,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryLimit.d.ts","sourceRoot":"","sources":["../../src/lib/queryLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAGxD"}
|