@invokable/server 0.1.0 → 0.3.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 CHANGED
@@ -74,7 +74,7 @@ Also not handled here, and yours to add:
74
74
  add your framework's CSRF protection.
75
75
  - **Rate limiting on `/device/start`.** Nothing stops an attacker minting device
76
76
  codes in bulk.
77
- - **Durable storage.** `memoryStore()` loses every token on restart. It is for
78
- development and tests. A Postgres store is on the roadmap.
77
+ - **Durable storage** — but `postgresAuthStore()` and `postgresCheckpointStore()`
78
+ are provided; see below. `memoryStore()` is for development only.
79
79
  - **Audit logging.** The store records who approved what and when; surfacing it
80
80
  is the host application's job.
package/dist/index.d.ts CHANGED
@@ -8,4 +8,8 @@ export type { CheckpointRoutesOptions, VerifyCheckpointOptions } from './checkpo
8
8
  export { CheckpointVerifier, computeFingerprint, hashSummary, memoryCheckpointStore, parseCheckpointHeader, } from './checkpoints.js';
9
9
  export type { CheckpointRecord, CheckpointStore, CheckpointVerifierOptions, CheckpointFailure, VerifyResult, } from './checkpoints.js';
10
10
  export { stableStringify } from './stable-json.js';
11
+ export { postgresAuthStore, postgresCheckpointStore, purgeExpired, createSchema, } from './postgres-store.js';
12
+ export type { PostgresStoreOptions, PurgeResult } from './postgres-store.js';
13
+ export { SCHEMA_SQL, SCHEMA_STATEMENTS } from './sql.js';
14
+ export type { SqlExecutor } from './sql.js';
11
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,WAAW,EACX,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEpF,OAAO,EACL,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC3F,YAAY,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAE/F,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,WAAW,EACX,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEpF,OAAO,EACL,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC3F,YAAY,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAE/F,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,YAAY,EACZ,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -4,4 +4,6 @@ export { generateToken, hashToken, generateUserCode, generateDeviceCode, safeEqu
4
4
  export { checkpointRoutes, verifyCheckpoint, staleResponse } from './checkpoint-routes.js';
5
5
  export { CheckpointVerifier, computeFingerprint, hashSummary, memoryCheckpointStore, parseCheckpointHeader, } from './checkpoints.js';
6
6
  export { stableStringify } from './stable-json.js';
7
+ export { postgresAuthStore, postgresCheckpointStore, purgeExpired, createSchema, } from './postgres-store.js';
8
+ export { SCHEMA_SQL, SCHEMA_STATEMENTS } from './sql.js';
7
9
  //# 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,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAQ7C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,OAAO,EACL,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG3F,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAQ7C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,OAAO,EACL,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG3F,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,YAAY,EACZ,YAAY,GACb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { AuthStore } from './store.js';
2
+ import type { CheckpointStore } from './checkpoints.js';
3
+ import { createSchema, type SqlExecutor } from './sql.js';
4
+ export interface PostgresStoreOptions {
5
+ exec: SqlExecutor;
6
+ }
7
+ export interface PurgeResult {
8
+ devices: number;
9
+ checkpoints: number;
10
+ }
11
+ /**
12
+ * Durable auth store.
13
+ *
14
+ * Required anywhere the process is not the only one holding state: a restart,
15
+ * a second instance behind a load balancer, or a serverless platform where
16
+ * every request may reach a fresh worker. With the in-memory store, a login
17
+ * started on one instance and polled on another simply never completes.
18
+ */
19
+ export declare function postgresAuthStore(options: PostgresStoreOptions): AuthStore;
20
+ /** Durable checkpoint store. See `consumeCheckpoint` for the part that matters. */
21
+ export declare function postgresCheckpointStore(options: PostgresStoreOptions): CheckpointStore;
22
+ /**
23
+ * Deletes rows that can no longer be used. Nothing depends on this for
24
+ * correctness — expiry is enforced on read — but without it the tables grow
25
+ * forever. Run it from a scheduled job.
26
+ */
27
+ export declare function purgeExpired(exec: SqlExecutor, options?: {
28
+ now?: number;
29
+ graceMs?: number;
30
+ }): Promise<PurgeResult>;
31
+ export { createSchema };
32
+ export type { SqlExecutor };
33
+ //# sourceMappingURL=postgres-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../src/postgres-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAA0C,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,YAAY,EAIZ,KAAK,WAAW,EACjB,MAAM,UAAU,CAAC;AA6GlB,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAgG1E;AAED,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,GAAG,eAAe,CAqDtF;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/C,OAAO,CAAC,WAAW,CAAC,CAgBtB;AAED,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,YAAY,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,211 @@
1
+ import { createSchema, toNumber, toOptionalNumber, toOptionalString, } from './sql.js';
2
+ function toDevice(row) {
3
+ const record = {
4
+ deviceCode: row.device_code,
5
+ userCode: row.user_code,
6
+ state: row.state,
7
+ clientName: row.client_name,
8
+ hostname: row.hostname,
9
+ toolVersion: row.tool_version,
10
+ createdAt: toNumber(row.created_at),
11
+ expiresAt: toNumber(row.expires_at),
12
+ };
13
+ const subject = toOptionalString(row.subject);
14
+ const orgId = toOptionalString(row.org_id);
15
+ const lastPolledAt = toOptionalNumber(row.last_polled_at);
16
+ if (subject !== undefined)
17
+ record.subject = subject;
18
+ if (orgId !== undefined)
19
+ record.orgId = orgId;
20
+ if (lastPolledAt !== undefined)
21
+ record.lastPolledAt = lastPolledAt;
22
+ return record;
23
+ }
24
+ function toToken(row) {
25
+ const record = {
26
+ tokenHash: row.token_hash,
27
+ tokenPrefix: row.token_prefix,
28
+ subject: row.subject,
29
+ clientName: row.client_name,
30
+ hostname: row.hostname,
31
+ createdAt: toNumber(row.created_at),
32
+ expiresAt: row.expires_at === null || row.expires_at === undefined ? null : toNumber(row.expires_at),
33
+ };
34
+ const orgId = toOptionalString(row.org_id);
35
+ const revokedAt = toOptionalNumber(row.revoked_at);
36
+ if (orgId !== undefined)
37
+ record.orgId = orgId;
38
+ if (revokedAt !== undefined)
39
+ record.revokedAt = revokedAt;
40
+ return record;
41
+ }
42
+ function toCheckpoint(row) {
43
+ const record = {
44
+ fingerprint: row.fingerprint,
45
+ gate: row.gate,
46
+ subject: row.subject,
47
+ summaryHash: row.summary_hash,
48
+ issuedAt: toNumber(row.issued_at),
49
+ expiresAt: toNumber(row.expires_at),
50
+ consumed: Boolean(row.consumed),
51
+ };
52
+ const consumedAt = toOptionalNumber(row.consumed_at);
53
+ const issuedTo = toOptionalString(row.issued_to);
54
+ if (consumedAt !== undefined)
55
+ record.consumedAt = consumedAt;
56
+ if (issuedTo !== undefined)
57
+ record.issuedTo = issuedTo;
58
+ return record;
59
+ }
60
+ /** Column name for each patchable device field, for the partial UPDATE. */
61
+ const DEVICE_COLUMNS = {
62
+ deviceCode: 'device_code',
63
+ userCode: 'user_code',
64
+ state: 'state',
65
+ clientName: 'client_name',
66
+ hostname: 'hostname',
67
+ toolVersion: 'tool_version',
68
+ createdAt: 'created_at',
69
+ expiresAt: 'expires_at',
70
+ subject: 'subject',
71
+ orgId: 'org_id',
72
+ lastPolledAt: 'last_polled_at',
73
+ };
74
+ /**
75
+ * Durable auth store.
76
+ *
77
+ * Required anywhere the process is not the only one holding state: a restart,
78
+ * a second instance behind a load balancer, or a serverless platform where
79
+ * every request may reach a fresh worker. With the in-memory store, a login
80
+ * started on one instance and polled on another simply never completes.
81
+ */
82
+ export function postgresAuthStore(options) {
83
+ const { exec } = options;
84
+ return {
85
+ async createDevice(record) {
86
+ await exec.query(`INSERT INTO invokable_devices
87
+ (device_code, user_code, state, client_name, hostname, tool_version,
88
+ created_at, expires_at, subject, org_id, last_polled_at)
89
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)`, [
90
+ record.deviceCode,
91
+ record.userCode,
92
+ record.state,
93
+ record.clientName,
94
+ record.hostname,
95
+ record.toolVersion,
96
+ record.createdAt,
97
+ record.expiresAt,
98
+ record.subject ?? null,
99
+ record.orgId ?? null,
100
+ record.lastPolledAt ?? null,
101
+ ]);
102
+ },
103
+ async findDeviceByDeviceCode(deviceCode) {
104
+ const { rows } = await exec.query('SELECT * FROM invokable_devices WHERE device_code = $1', [deviceCode]);
105
+ return rows[0] ? toDevice(rows[0]) : null;
106
+ },
107
+ async findDeviceByUserCode(userCode) {
108
+ // Newest first: an expired code may linger until the sweeper runs, and the
109
+ // live one is the one a person is looking at.
110
+ const { rows } = await exec.query('SELECT * FROM invokable_devices WHERE user_code = $1 ORDER BY created_at DESC LIMIT 1', [userCode]);
111
+ return rows[0] ? toDevice(rows[0]) : null;
112
+ },
113
+ async updateDevice(deviceCode, patch) {
114
+ const sets = [];
115
+ const params = [];
116
+ for (const [key, value] of Object.entries(patch)) {
117
+ const column = DEVICE_COLUMNS[key];
118
+ if (!column)
119
+ continue;
120
+ params.push(value ?? null);
121
+ sets.push(`${column} = $${params.length}`);
122
+ }
123
+ if (sets.length === 0)
124
+ return;
125
+ params.push(deviceCode);
126
+ await exec.query(`UPDATE invokable_devices SET ${sets.join(', ')} WHERE device_code = $${params.length}`, params);
127
+ },
128
+ async createToken(record) {
129
+ await exec.query(`INSERT INTO invokable_tokens
130
+ (token_hash, token_prefix, subject, org_id, client_name, hostname,
131
+ created_at, expires_at, revoked_at)
132
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, [
133
+ record.tokenHash,
134
+ record.tokenPrefix,
135
+ record.subject,
136
+ record.orgId ?? null,
137
+ record.clientName,
138
+ record.hostname,
139
+ record.createdAt,
140
+ record.expiresAt,
141
+ record.revokedAt ?? null,
142
+ ]);
143
+ },
144
+ async findTokenByHash(tokenHash) {
145
+ const { rows } = await exec.query('SELECT * FROM invokable_tokens WHERE token_hash = $1', [tokenHash]);
146
+ return rows[0] ? toToken(rows[0]) : null;
147
+ },
148
+ async revokeToken(tokenHash, at) {
149
+ await exec.query('UPDATE invokable_tokens SET revoked_at = $1 WHERE token_hash = $2 AND revoked_at IS NULL', [at, tokenHash]);
150
+ },
151
+ };
152
+ }
153
+ /** Durable checkpoint store. See `consumeCheckpoint` for the part that matters. */
154
+ export function postgresCheckpointStore(options) {
155
+ const { exec } = options;
156
+ return {
157
+ async createCheckpoint(record) {
158
+ await exec.query(`INSERT INTO invokable_checkpoints
159
+ (fingerprint, gate, subject, summary_hash, issued_at, expires_at,
160
+ consumed, consumed_at, issued_to)
161
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, [
162
+ record.fingerprint,
163
+ record.gate,
164
+ record.subject,
165
+ record.summaryHash,
166
+ record.issuedAt,
167
+ record.expiresAt,
168
+ record.consumed,
169
+ record.consumedAt ?? null,
170
+ record.issuedTo ?? null,
171
+ ]);
172
+ },
173
+ async findCheckpoint(fingerprint) {
174
+ const { rows } = await exec.query('SELECT * FROM invokable_checkpoints WHERE fingerprint = $1', [fingerprint]);
175
+ return rows[0] ? toCheckpoint(rows[0]) : null;
176
+ },
177
+ /**
178
+ * One statement, not a read followed by a write.
179
+ *
180
+ * `WHERE consumed = FALSE` makes the database the arbiter: of two concurrent
181
+ * requests carrying the same approval, exactly one UPDATE matches a row and
182
+ * the other returns nothing. A SELECT-then-UPDATE would let both pass and
183
+ * bill the user twice for one approval — the precise thing the gate exists
184
+ * to prevent. The in-memory store gets away without this only because a
185
+ * single Node process cannot interleave.
186
+ */
187
+ async consumeCheckpoint(fingerprint, at) {
188
+ const { rows } = await exec.query(`UPDATE invokable_checkpoints
189
+ SET consumed = TRUE, consumed_at = $2
190
+ WHERE fingerprint = $1 AND consumed = FALSE
191
+ RETURNING fingerprint`, [fingerprint, at]);
192
+ return rows.length === 1;
193
+ },
194
+ };
195
+ }
196
+ /**
197
+ * Deletes rows that can no longer be used. Nothing depends on this for
198
+ * correctness — expiry is enforced on read — but without it the tables grow
199
+ * forever. Run it from a scheduled job.
200
+ */
201
+ export async function purgeExpired(exec, options = {}) {
202
+ const now = options.now ?? Date.now();
203
+ // Kept past expiry so a recently expired approval can still be explained to
204
+ // whoever asks why it was refused.
205
+ const cutoff = now - (options.graceMs ?? 24 * 60 * 60 * 1000);
206
+ const devices = await exec.query('DELETE FROM invokable_devices WHERE expires_at < $1 RETURNING device_code', [cutoff]);
207
+ const checkpoints = await exec.query('DELETE FROM invokable_checkpoints WHERE expires_at < $1 RETURNING fingerprint', [cutoff]);
208
+ return { devices: devices.rows.length, checkpoints: checkpoints.rows.length };
209
+ }
210
+ export { createSchema };
211
+ //# sourceMappingURL=postgres-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-store.js","sourceRoot":"","sources":["../src/postgres-store.ts"],"names":[],"mappings":"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"}
package/dist/sql.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The database seam.
3
+ *
4
+ * Deliberately not a driver. Any client with a `query(text, params)` that
5
+ * returns `{ rows }` satisfies this — `pg`, `postgres.js`,
6
+ * `@neondatabase/serverless`, PGlite. That keeps `@invokable/server` free of a
7
+ * database dependency, and lets the same store run on a serverless platform
8
+ * (where an HTTP driver avoids exhausting connections) and on a plain server.
9
+ */
10
+ export interface SqlExecutor {
11
+ query<T = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<{
12
+ rows: T[];
13
+ }>;
14
+ }
15
+ /**
16
+ * Timestamps are stored as epoch milliseconds in `bigint` rather than
17
+ * `timestamptz`, because the record types are `number` and a round trip through
18
+ * a date type loses the exact value the fingerprint was signed over.
19
+ */
20
+ /**
21
+ * Each statement separately, not one blob.
22
+ *
23
+ * A multi-statement string only works over the simple query protocol. Drivers
24
+ * that use the extended protocol — PGlite, and HTTP drivers such as Neon's
25
+ * serverless client — reject it with "cannot insert multiple commands into a
26
+ * prepared statement", which is exactly the setup this store exists for.
27
+ *
28
+ * Timestamps are epoch milliseconds in `bigint` rather than `timestamptz`,
29
+ * because the record types are `number` and a round trip through a date type
30
+ * loses the exact value the fingerprint was signed over.
31
+ */
32
+ export declare const SCHEMA_STATEMENTS: readonly string[];
33
+ /** The whole schema as one script, for running by hand with psql. */
34
+ export declare const SCHEMA_SQL: string;
35
+ /** Creates the tables if they are absent. Safe to run on every boot. */
36
+ export declare function createSchema(exec: SqlExecutor): Promise<void>;
37
+ /**
38
+ * `bigint` arrives as a string from most drivers, because it can exceed
39
+ * `Number.MAX_SAFE_INTEGER`. Epoch milliseconds cannot, so converting is exact.
40
+ */
41
+ export declare function toNumber(value: unknown): number;
42
+ export declare function toOptionalNumber(value: unknown): number | undefined;
43
+ export declare function toOptionalString(value: unknown): string | undefined;
44
+ //# sourceMappingURL=sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAC1B,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3B;AAED;;;;GAIG;AACH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EA8C9C,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,UAAU,QAA0C,CAAC;AAElE,wEAAwE;AACxE,wBAAsB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAInE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAG/C;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGnE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEnE"}
package/dist/sql.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Timestamps are stored as epoch milliseconds in `bigint` rather than
3
+ * `timestamptz`, because the record types are `number` and a round trip through
4
+ * a date type loses the exact value the fingerprint was signed over.
5
+ */
6
+ /**
7
+ * Each statement separately, not one blob.
8
+ *
9
+ * A multi-statement string only works over the simple query protocol. Drivers
10
+ * that use the extended protocol — PGlite, and HTTP drivers such as Neon's
11
+ * serverless client — reject it with "cannot insert multiple commands into a
12
+ * prepared statement", which is exactly the setup this store exists for.
13
+ *
14
+ * Timestamps are epoch milliseconds in `bigint` rather than `timestamptz`,
15
+ * because the record types are `number` and a round trip through a date type
16
+ * loses the exact value the fingerprint was signed over.
17
+ */
18
+ export const SCHEMA_STATEMENTS = [
19
+ `CREATE TABLE IF NOT EXISTS invokable_devices (
20
+ device_code TEXT PRIMARY KEY,
21
+ user_code TEXT NOT NULL,
22
+ state TEXT NOT NULL,
23
+ client_name TEXT NOT NULL,
24
+ hostname TEXT NOT NULL,
25
+ tool_version TEXT NOT NULL,
26
+ created_at BIGINT NOT NULL,
27
+ expires_at BIGINT NOT NULL,
28
+ subject TEXT,
29
+ org_id TEXT,
30
+ last_polled_at BIGINT
31
+ )`,
32
+ // One pending device per user code: the code is what a human types, and two
33
+ // live devices answering to it would make approval ambiguous.
34
+ `CREATE UNIQUE INDEX IF NOT EXISTS invokable_devices_user_code_pending
35
+ ON invokable_devices (user_code) WHERE state = 'pending'`,
36
+ `CREATE INDEX IF NOT EXISTS invokable_devices_user_code ON invokable_devices (user_code)`,
37
+ `CREATE INDEX IF NOT EXISTS invokable_devices_expires_at ON invokable_devices (expires_at)`,
38
+ `CREATE TABLE IF NOT EXISTS invokable_tokens (
39
+ token_hash TEXT PRIMARY KEY,
40
+ token_prefix TEXT NOT NULL,
41
+ subject TEXT NOT NULL,
42
+ org_id TEXT,
43
+ client_name TEXT NOT NULL,
44
+ hostname TEXT NOT NULL,
45
+ created_at BIGINT NOT NULL,
46
+ expires_at BIGINT,
47
+ revoked_at BIGINT
48
+ )`,
49
+ `CREATE INDEX IF NOT EXISTS invokable_tokens_subject ON invokable_tokens (subject)`,
50
+ `CREATE TABLE IF NOT EXISTS invokable_checkpoints (
51
+ fingerprint TEXT PRIMARY KEY,
52
+ gate TEXT NOT NULL,
53
+ subject TEXT NOT NULL,
54
+ summary_hash TEXT NOT NULL,
55
+ issued_at BIGINT NOT NULL,
56
+ expires_at BIGINT NOT NULL,
57
+ consumed BOOLEAN NOT NULL DEFAULT FALSE,
58
+ consumed_at BIGINT,
59
+ issued_to TEXT
60
+ )`,
61
+ `CREATE INDEX IF NOT EXISTS invokable_checkpoints_expires_at ON invokable_checkpoints (expires_at)`,
62
+ ];
63
+ /** The whole schema as one script, for running by hand with psql. */
64
+ export const SCHEMA_SQL = SCHEMA_STATEMENTS.join(';\n\n') + ';\n';
65
+ /** Creates the tables if they are absent. Safe to run on every boot. */
66
+ export async function createSchema(exec) {
67
+ for (const statement of SCHEMA_STATEMENTS) {
68
+ await exec.query(statement);
69
+ }
70
+ }
71
+ /**
72
+ * `bigint` arrives as a string from most drivers, because it can exceed
73
+ * `Number.MAX_SAFE_INTEGER`. Epoch milliseconds cannot, so converting is exact.
74
+ */
75
+ export function toNumber(value) {
76
+ if (value === null || value === undefined)
77
+ return 0;
78
+ return typeof value === 'number' ? value : Number(value);
79
+ }
80
+ export function toOptionalNumber(value) {
81
+ if (value === null || value === undefined)
82
+ return undefined;
83
+ return toNumber(value);
84
+ }
85
+ export function toOptionalString(value) {
86
+ return value === null || value === undefined ? undefined : String(value);
87
+ }
88
+ //# sourceMappingURL=sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.js","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD;;;;;;;;;;;;IAYE;IACF,4EAA4E;IAC5E,8DAA8D;IAC9D;8DAC4D;IAC5D,yFAAyF;IACzF,2FAA2F;IAE3F;;;;;;;;;;IAUE;IACF,mFAAmF;IAEnF;;;;;;;;;;IAUE;IACF,mGAAmG;CACpG,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;AAElE,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAiB;IAClD,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE,CAAC;QAC1C,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IACpD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5D,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEzE,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACzE,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACrE,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED,wBAAgB,WAAW,IAAI,SAAS,GAAG;IAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAoCpH"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEzE,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACzE,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACrE,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED,wBAAgB,WAAW,IAAI,SAAS,GAAG;IAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAwCpH"}
package/dist/store.js CHANGED
@@ -30,8 +30,11 @@ export function memoryStore() {
30
30
  },
31
31
  async revokeToken(tokenHash, at) {
32
32
  const existing = tokens.get(tokenHash);
33
- if (existing)
33
+ // First revocation wins: that is the moment the token stopped working,
34
+ // and overwriting it would misreport when access actually ended.
35
+ if (existing && existing.revokedAt === undefined) {
34
36
  tokens.set(tokenHash, { ...existing, revokedAt: at });
37
+ }
35
38
  },
36
39
  };
37
40
  }
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAgDA,MAAM,UAAU,WAAW;IACzB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE9C,OAAO;QACL,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,MAAM;QAEf,KAAK,CAAC,YAAY,CAAC,MAAM;YACvB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QACD,KAAK,CAAC,sBAAsB,CAAC,UAAU;YACrC,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;QACzC,CAAC;QACD,KAAK,CAAC,oBAAoB,CAAC,QAAQ;YACjC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;gBACjC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ;oBAAE,OAAO,CAAC,CAAC;YACxC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,KAAK;YAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACzC,IAAI,QAAQ;gBAAE,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,MAAM;YACtB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,CAAC,eAAe,CAAC,SAAS;YAC7B,OAAO,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;QACvC,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE;YAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACvC,IAAI,QAAQ;gBAAE,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QACtE,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAgDA,MAAM,UAAU,WAAW;IACzB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE9C,OAAO;QACL,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,MAAM;QAEf,KAAK,CAAC,YAAY,CAAC,MAAM;YACvB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QACD,KAAK,CAAC,sBAAsB,CAAC,UAAU;YACrC,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;QACzC,CAAC;QACD,KAAK,CAAC,oBAAoB,CAAC,QAAQ;YACjC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;gBACjC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ;oBAAE,OAAO,CAAC,CAAC;YACxC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,KAAK;YAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACzC,IAAI,QAAQ;gBAAE,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,MAAM;YACtB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,CAAC,eAAe,CAAC,SAAS;YAC7B,OAAO,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;QACvC,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE;YAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACvC,uEAAuE;YACvE,iEAAiE;YACjE,IAAI,QAAQ,IAAI,QAAQ,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBACjD,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invokable/server",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Self-hostable device-code auth endpoints for invokable tools.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,7 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "devDependencies": {
35
- "@invokable/core": "^0.1.0"
35
+ "@electric-sql/pglite": "^0.5.8",
36
+ "@invokable/core": "^0.3.0"
36
37
  },
37
38
  "scripts": {
38
39
  "build": "tsc -b",