@oaath/server 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 +201 -0
- package/README.md +80 -0
- package/dist/apns.d.ts +172 -0
- package/dist/apns.d.ts.map +1 -0
- package/dist/apns.js +488 -0
- package/dist/apns.js.map +1 -0
- package/dist/authentication-HUYWk5Y2.d.ts +46 -0
- package/dist/authentication-HUYWk5Y2.d.ts.map +1 -0
- package/dist/challenge-AGTmUfj2.js +71 -0
- package/dist/challenge-AGTmUfj2.js.map +1 -0
- package/dist/decision-dEe9n108.d.ts +53 -0
- package/dist/decision-dEe9n108.d.ts.map +1 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1135 -0
- package/dist/index.js.map +1 -0
- package/dist/interface-WwvwWs8e.d.ts +114 -0
- package/dist/interface-WwvwWs8e.d.ts.map +1 -0
- package/dist/native.d.ts +30 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +2 -0
- package/dist/postgres.d.ts +37 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +401 -0
- package/dist/postgres.js.map +1 -0
- package/dist/projection-dGIkE0rI.js +516 -0
- package/dist/projection-dGIkE0rI.js.map +1 -0
- package/dist/projection-g6k_CCUK.d.ts +136 -0
- package/dist/projection-g6k_CCUK.d.ts.map +1 -0
- package/dist/records-Bgr-9PUh.js +231 -0
- package/dist/records-Bgr-9PUh.js.map +1 -0
- package/package.json +62 -0
package/dist/postgres.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { b as relayFailure, d as parseAuthorizationRequestRecord, f as parseCapabilityInvalidationRecord, l as parseAuthorizationCodeRecord, p as parseEncryptedArtifactRecord, u as parseAuthorizationDecisionRecord } from "./records-Bgr-9PUh.js";
|
|
2
|
+
import pg from "pg";
|
|
3
|
+
//#region src/store/postgres/schema.ts
|
|
4
|
+
/**
|
|
5
|
+
* The current relay schema. There is no migration runner and no reader for any
|
|
6
|
+
* older schema: an obsolete database is dropped and recreated.
|
|
7
|
+
*
|
|
8
|
+
* Constraints that the database can own are owned by the database — one decision
|
|
9
|
+
* per request, one code and one artifact per request, monotonic timestamps.
|
|
10
|
+
*
|
|
11
|
+
* @author taek <leekt216@gmail.com>
|
|
12
|
+
*/
|
|
13
|
+
const OAATH_RELAY_POSTGRES_SCHEMA_VERSION = "oaath.relay-postgres-schema/v1";
|
|
14
|
+
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
|
15
|
+
const OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS = Object.freeze([
|
|
16
|
+
`CREATE TABLE oaath_relay_schema_v1 (
|
|
17
|
+
schema_id text PRIMARY KEY CHECK (schema_id = 'oaath'),
|
|
18
|
+
version text NOT NULL
|
|
19
|
+
)`,
|
|
20
|
+
`INSERT INTO oaath_relay_schema_v1 (schema_id, version)
|
|
21
|
+
VALUES ('oaath', '${OAATH_RELAY_POSTGRES_SCHEMA_VERSION}')`,
|
|
22
|
+
`CREATE TABLE oaath_relay_authorization_request_v1 (
|
|
23
|
+
request_id text PRIMARY KEY,
|
|
24
|
+
record_version text NOT NULL,
|
|
25
|
+
client_id text NOT NULL,
|
|
26
|
+
subject text NOT NULL,
|
|
27
|
+
redirect_uri text NOT NULL,
|
|
28
|
+
code_challenge text NOT NULL,
|
|
29
|
+
requested_scope text NOT NULL,
|
|
30
|
+
created_at bigint NOT NULL CHECK (created_at >= 0 AND created_at <= ${MAX_SAFE_INTEGER}),
|
|
31
|
+
expires_at bigint NOT NULL CHECK (expires_at >= created_at AND expires_at <= ${MAX_SAFE_INTEGER})
|
|
32
|
+
)`,
|
|
33
|
+
`CREATE TABLE oaath_relay_authorization_decision_v1 (
|
|
34
|
+
request_id text PRIMARY KEY
|
|
35
|
+
REFERENCES oaath_relay_authorization_request_v1 (request_id),
|
|
36
|
+
record_version text NOT NULL,
|
|
37
|
+
outcome text NOT NULL CHECK (outcome IN ('approved', 'rejected')),
|
|
38
|
+
decided_at bigint NOT NULL CHECK (decided_at >= 0 AND decided_at <= ${MAX_SAFE_INTEGER}),
|
|
39
|
+
code_ref text,
|
|
40
|
+
code_expires_at bigint CHECK (code_expires_at >= 0 AND code_expires_at <= ${MAX_SAFE_INTEGER}),
|
|
41
|
+
CHECK ((outcome = 'approved') = (code_ref IS NOT NULL)),
|
|
42
|
+
CHECK ((code_ref IS NULL) = (code_expires_at IS NULL))
|
|
43
|
+
)`,
|
|
44
|
+
`CREATE TABLE oaath_relay_capability_invalidation_v1 (
|
|
45
|
+
grant_id text PRIMARY KEY,
|
|
46
|
+
record_version text NOT NULL,
|
|
47
|
+
client_id text NOT NULL,
|
|
48
|
+
capability_hash text NOT NULL,
|
|
49
|
+
invalidated_at bigint NOT NULL CHECK (invalidated_at >= 0 AND invalidated_at <= ${MAX_SAFE_INTEGER})
|
|
50
|
+
)`,
|
|
51
|
+
`CREATE TABLE oaath_relay_authorization_code_v1 (
|
|
52
|
+
code_hash text PRIMARY KEY,
|
|
53
|
+
record_version text NOT NULL,
|
|
54
|
+
request_id text NOT NULL UNIQUE
|
|
55
|
+
REFERENCES oaath_relay_authorization_request_v1 (request_id),
|
|
56
|
+
client_id text NOT NULL,
|
|
57
|
+
redirect_uri text NOT NULL,
|
|
58
|
+
code_challenge text NOT NULL,
|
|
59
|
+
artifact_id text NOT NULL UNIQUE,
|
|
60
|
+
created_at bigint NOT NULL CHECK (created_at >= 0 AND created_at <= ${MAX_SAFE_INTEGER}),
|
|
61
|
+
expires_at bigint NOT NULL CHECK (expires_at >= created_at AND expires_at <= ${MAX_SAFE_INTEGER}),
|
|
62
|
+
consumed_at bigint CHECK (consumed_at >= created_at AND consumed_at <= ${MAX_SAFE_INTEGER})
|
|
63
|
+
)`,
|
|
64
|
+
`CREATE TABLE oaath_relay_encrypted_artifact_v1 (
|
|
65
|
+
artifact_id text PRIMARY KEY,
|
|
66
|
+
record_version text NOT NULL,
|
|
67
|
+
request_id text NOT NULL UNIQUE
|
|
68
|
+
REFERENCES oaath_relay_authorization_request_v1 (request_id),
|
|
69
|
+
client_id text NOT NULL,
|
|
70
|
+
ciphertext_ref text NOT NULL,
|
|
71
|
+
created_at bigint NOT NULL CHECK (created_at >= 0 AND created_at <= ${MAX_SAFE_INTEGER}),
|
|
72
|
+
claimed_at bigint CHECK (claimed_at >= created_at AND claimed_at <= ${MAX_SAFE_INTEGER})
|
|
73
|
+
)`
|
|
74
|
+
]);
|
|
75
|
+
/**
|
|
76
|
+
* Creates the current schema in the executor's `search_path`. It is not a
|
|
77
|
+
* migration: it fails if the objects already exist.
|
|
78
|
+
*/
|
|
79
|
+
async function createPostgresRelaySchema(executor) {
|
|
80
|
+
for (const statement of OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS) await executor.query(statement);
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/store/postgres/queries.ts
|
|
84
|
+
/**
|
|
85
|
+
* Relay SQL. Text only: no state-machine decision lives in this file.
|
|
86
|
+
*
|
|
87
|
+
* Names are unqualified so a deployment (or a disposable test schema) selects
|
|
88
|
+
* them with `search_path`.
|
|
89
|
+
*
|
|
90
|
+
* @author taek <leekt216@gmail.com>
|
|
91
|
+
*/
|
|
92
|
+
const REQUEST_COLUMNS = `
|
|
93
|
+
request_id, record_version, client_id, subject, redirect_uri,
|
|
94
|
+
code_challenge, requested_scope, created_at, expires_at
|
|
95
|
+
`;
|
|
96
|
+
const CODE_COLUMNS = `
|
|
97
|
+
code_hash, record_version, request_id, client_id, redirect_uri,
|
|
98
|
+
code_challenge, artifact_id, created_at, expires_at, consumed_at
|
|
99
|
+
`;
|
|
100
|
+
const ARTIFACT_COLUMNS = `
|
|
101
|
+
artifact_id, record_version, request_id, client_id, ciphertext_ref,
|
|
102
|
+
created_at, claimed_at
|
|
103
|
+
`;
|
|
104
|
+
const LOCK_AUTHORIZATION_REQUEST = `
|
|
105
|
+
SELECT ${REQUEST_COLUMNS}
|
|
106
|
+
FROM oaath_relay_authorization_request_v1
|
|
107
|
+
WHERE request_id = $1
|
|
108
|
+
FOR UPDATE
|
|
109
|
+
`;
|
|
110
|
+
const INSERT_AUTHORIZATION_REQUEST = `
|
|
111
|
+
INSERT INTO oaath_relay_authorization_request_v1 (${REQUEST_COLUMNS})
|
|
112
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
113
|
+
ON CONFLICT (request_id) DO NOTHING
|
|
114
|
+
`;
|
|
115
|
+
const LOCK_AUTHORIZATION_DECISION = `
|
|
116
|
+
SELECT request_id, record_version, outcome, decided_at, code_ref, code_expires_at
|
|
117
|
+
FROM oaath_relay_authorization_decision_v1
|
|
118
|
+
WHERE request_id = $1
|
|
119
|
+
FOR UPDATE
|
|
120
|
+
`;
|
|
121
|
+
const INSERT_AUTHORIZATION_DECISION = `
|
|
122
|
+
INSERT INTO oaath_relay_authorization_decision_v1 (
|
|
123
|
+
request_id, record_version, outcome, decided_at, code_ref, code_expires_at
|
|
124
|
+
) VALUES ($1, $2, $3, $4, $5, $6)
|
|
125
|
+
ON CONFLICT (request_id) DO NOTHING
|
|
126
|
+
`;
|
|
127
|
+
const LOCK_CAPABILITY_INVALIDATION = `
|
|
128
|
+
SELECT grant_id, record_version, client_id, capability_hash, invalidated_at
|
|
129
|
+
FROM oaath_relay_capability_invalidation_v1
|
|
130
|
+
WHERE grant_id = $1
|
|
131
|
+
FOR UPDATE
|
|
132
|
+
`;
|
|
133
|
+
const INSERT_CAPABILITY_INVALIDATION = `
|
|
134
|
+
INSERT INTO oaath_relay_capability_invalidation_v1 (
|
|
135
|
+
grant_id, record_version, client_id, capability_hash, invalidated_at
|
|
136
|
+
) VALUES ($1, $2, $3, $4, $5)
|
|
137
|
+
ON CONFLICT (grant_id) DO NOTHING
|
|
138
|
+
`;
|
|
139
|
+
const LOCK_AUTHORIZATION_CODE = `
|
|
140
|
+
SELECT ${CODE_COLUMNS}
|
|
141
|
+
FROM oaath_relay_authorization_code_v1
|
|
142
|
+
WHERE code_hash = $1
|
|
143
|
+
FOR UPDATE
|
|
144
|
+
`;
|
|
145
|
+
const INSERT_AUTHORIZATION_CODE = `
|
|
146
|
+
INSERT INTO oaath_relay_authorization_code_v1 (${CODE_COLUMNS})
|
|
147
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
148
|
+
ON CONFLICT DO NOTHING
|
|
149
|
+
`;
|
|
150
|
+
/** One-shot: the guard makes a second consume affect zero rows. */
|
|
151
|
+
const CONSUME_AUTHORIZATION_CODE = `
|
|
152
|
+
UPDATE oaath_relay_authorization_code_v1
|
|
153
|
+
SET consumed_at = $2
|
|
154
|
+
WHERE code_hash = $1 AND consumed_at IS NULL
|
|
155
|
+
`;
|
|
156
|
+
const LOCK_ENCRYPTED_ARTIFACT = `
|
|
157
|
+
SELECT ${ARTIFACT_COLUMNS}
|
|
158
|
+
FROM oaath_relay_encrypted_artifact_v1
|
|
159
|
+
WHERE artifact_id = $1
|
|
160
|
+
FOR UPDATE
|
|
161
|
+
`;
|
|
162
|
+
const INSERT_ENCRYPTED_ARTIFACT = `
|
|
163
|
+
INSERT INTO oaath_relay_encrypted_artifact_v1 (${ARTIFACT_COLUMNS})
|
|
164
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
165
|
+
ON CONFLICT DO NOTHING
|
|
166
|
+
`;
|
|
167
|
+
/** One-shot: the guard makes a second claim affect zero rows. */
|
|
168
|
+
const CLAIM_ENCRYPTED_ARTIFACT = `
|
|
169
|
+
UPDATE oaath_relay_encrypted_artifact_v1
|
|
170
|
+
SET claimed_at = $2
|
|
171
|
+
WHERE artifact_id = $1 AND claimed_at IS NULL
|
|
172
|
+
`;
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/store/postgres/store.ts
|
|
175
|
+
/** `bigint` arrives as a decimal string; the record parser validates the result. */
|
|
176
|
+
function sqlTimestamp(value) {
|
|
177
|
+
if (typeof value !== "string") return value;
|
|
178
|
+
const parsed = Number(value);
|
|
179
|
+
return Number.isFinite(parsed) ? parsed : value;
|
|
180
|
+
}
|
|
181
|
+
function requestRecord(row) {
|
|
182
|
+
return parseAuthorizationRequestRecord({
|
|
183
|
+
version: row.record_version,
|
|
184
|
+
requestId: row.request_id,
|
|
185
|
+
clientId: row.client_id,
|
|
186
|
+
subject: row.subject,
|
|
187
|
+
redirectUri: row.redirect_uri,
|
|
188
|
+
codeChallenge: row.code_challenge,
|
|
189
|
+
requestedScope: row.requested_scope,
|
|
190
|
+
createdAt: sqlTimestamp(row.created_at),
|
|
191
|
+
expiresAt: sqlTimestamp(row.expires_at)
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function decisionRecord(row) {
|
|
195
|
+
return parseAuthorizationDecisionRecord({
|
|
196
|
+
version: row.record_version,
|
|
197
|
+
requestId: row.request_id,
|
|
198
|
+
outcome: row.outcome,
|
|
199
|
+
decidedAt: sqlTimestamp(row.decided_at),
|
|
200
|
+
codeRef: row.code_ref,
|
|
201
|
+
codeExpiresAt: row.code_expires_at === null ? null : sqlTimestamp(row.code_expires_at)
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function invalidationRecord(row) {
|
|
205
|
+
return parseCapabilityInvalidationRecord({
|
|
206
|
+
version: row.record_version,
|
|
207
|
+
grantId: row.grant_id,
|
|
208
|
+
clientId: row.client_id,
|
|
209
|
+
capabilityHash: row.capability_hash,
|
|
210
|
+
invalidatedAt: sqlTimestamp(row.invalidated_at)
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function codeRecord(row) {
|
|
214
|
+
return parseAuthorizationCodeRecord({
|
|
215
|
+
version: row.record_version,
|
|
216
|
+
codeHash: row.code_hash,
|
|
217
|
+
requestId: row.request_id,
|
|
218
|
+
clientId: row.client_id,
|
|
219
|
+
redirectUri: row.redirect_uri,
|
|
220
|
+
codeChallenge: row.code_challenge,
|
|
221
|
+
artifactId: row.artifact_id,
|
|
222
|
+
createdAt: sqlTimestamp(row.created_at),
|
|
223
|
+
expiresAt: sqlTimestamp(row.expires_at),
|
|
224
|
+
consumedAt: sqlTimestamp(row.consumed_at)
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function artifactRecord(row) {
|
|
228
|
+
return parseEncryptedArtifactRecord({
|
|
229
|
+
version: row.record_version,
|
|
230
|
+
artifactId: row.artifact_id,
|
|
231
|
+
requestId: row.request_id,
|
|
232
|
+
clientId: row.client_id,
|
|
233
|
+
ciphertextRef: row.ciphertext_ref,
|
|
234
|
+
createdAt: sqlTimestamp(row.created_at),
|
|
235
|
+
claimedAt: sqlTimestamp(row.claimed_at)
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function createTransaction(client) {
|
|
239
|
+
let settled = false;
|
|
240
|
+
async function run(sql, parameters) {
|
|
241
|
+
if (settled) relayFailure("relay_internal", "relay transaction already settled");
|
|
242
|
+
try {
|
|
243
|
+
const result = await client.query(sql, [...parameters]);
|
|
244
|
+
return {
|
|
245
|
+
rows: result.rows,
|
|
246
|
+
rowCount: result.rowCount ?? 0
|
|
247
|
+
};
|
|
248
|
+
} catch {
|
|
249
|
+
return relayFailure("relay_store_unavailable", "relay statement failed");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function first(sql, parameters, map) {
|
|
253
|
+
const { rows } = await run(sql, parameters);
|
|
254
|
+
if (rows.length === 0) return void 0;
|
|
255
|
+
if (rows.length > 1) return relayFailure("relay_record_unreadable", "relay key matched multiple rows");
|
|
256
|
+
const row = rows[0];
|
|
257
|
+
return row === void 0 ? void 0 : map(row);
|
|
258
|
+
}
|
|
259
|
+
async function inserted(sql, parameters) {
|
|
260
|
+
return (await run(sql, parameters)).rowCount === 1;
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
lockAuthorizationRequest(requestId) {
|
|
264
|
+
return first(LOCK_AUTHORIZATION_REQUEST, [requestId], requestRecord);
|
|
265
|
+
},
|
|
266
|
+
insertAuthorizationRequest(record) {
|
|
267
|
+
return inserted(INSERT_AUTHORIZATION_REQUEST, [
|
|
268
|
+
record.requestId,
|
|
269
|
+
record.version,
|
|
270
|
+
record.clientId,
|
|
271
|
+
record.subject,
|
|
272
|
+
record.redirectUri,
|
|
273
|
+
record.codeChallenge,
|
|
274
|
+
record.requestedScope,
|
|
275
|
+
record.createdAt,
|
|
276
|
+
record.expiresAt
|
|
277
|
+
]);
|
|
278
|
+
},
|
|
279
|
+
lockAuthorizationDecision(requestId) {
|
|
280
|
+
return first(LOCK_AUTHORIZATION_DECISION, [requestId], decisionRecord);
|
|
281
|
+
},
|
|
282
|
+
insertAuthorizationDecision(record) {
|
|
283
|
+
return inserted(INSERT_AUTHORIZATION_DECISION, [
|
|
284
|
+
record.requestId,
|
|
285
|
+
record.version,
|
|
286
|
+
record.outcome,
|
|
287
|
+
record.decidedAt,
|
|
288
|
+
record.codeRef,
|
|
289
|
+
record.codeExpiresAt
|
|
290
|
+
]);
|
|
291
|
+
},
|
|
292
|
+
lockCapabilityInvalidation(grantId) {
|
|
293
|
+
return first(LOCK_CAPABILITY_INVALIDATION, [grantId], invalidationRecord);
|
|
294
|
+
},
|
|
295
|
+
insertCapabilityInvalidation(record) {
|
|
296
|
+
return inserted(INSERT_CAPABILITY_INVALIDATION, [
|
|
297
|
+
record.grantId,
|
|
298
|
+
record.version,
|
|
299
|
+
record.clientId,
|
|
300
|
+
record.capabilityHash,
|
|
301
|
+
record.invalidatedAt
|
|
302
|
+
]);
|
|
303
|
+
},
|
|
304
|
+
lockAuthorizationCode(codeHash) {
|
|
305
|
+
return first(LOCK_AUTHORIZATION_CODE, [codeHash], codeRecord);
|
|
306
|
+
},
|
|
307
|
+
insertAuthorizationCode(record) {
|
|
308
|
+
return inserted(INSERT_AUTHORIZATION_CODE, [
|
|
309
|
+
record.codeHash,
|
|
310
|
+
record.version,
|
|
311
|
+
record.requestId,
|
|
312
|
+
record.clientId,
|
|
313
|
+
record.redirectUri,
|
|
314
|
+
record.codeChallenge,
|
|
315
|
+
record.artifactId,
|
|
316
|
+
record.createdAt,
|
|
317
|
+
record.expiresAt,
|
|
318
|
+
record.consumedAt
|
|
319
|
+
]);
|
|
320
|
+
},
|
|
321
|
+
consumeAuthorizationCode(codeHash, consumedAt) {
|
|
322
|
+
return inserted(CONSUME_AUTHORIZATION_CODE, [codeHash, consumedAt]);
|
|
323
|
+
},
|
|
324
|
+
lockEncryptedArtifact(artifactId) {
|
|
325
|
+
return first(LOCK_ENCRYPTED_ARTIFACT, [artifactId], artifactRecord);
|
|
326
|
+
},
|
|
327
|
+
insertEncryptedArtifact(record) {
|
|
328
|
+
return inserted(INSERT_ENCRYPTED_ARTIFACT, [
|
|
329
|
+
record.artifactId,
|
|
330
|
+
record.version,
|
|
331
|
+
record.requestId,
|
|
332
|
+
record.clientId,
|
|
333
|
+
record.ciphertextRef,
|
|
334
|
+
record.createdAt,
|
|
335
|
+
record.claimedAt
|
|
336
|
+
]);
|
|
337
|
+
},
|
|
338
|
+
claimEncryptedArtifact(artifactId, claimedAt) {
|
|
339
|
+
return inserted(CLAIM_ENCRYPTED_ARTIFACT, [artifactId, claimedAt]);
|
|
340
|
+
},
|
|
341
|
+
async commit() {
|
|
342
|
+
if (settled) relayFailure("relay_internal", "relay transaction already settled");
|
|
343
|
+
settled = true;
|
|
344
|
+
let command;
|
|
345
|
+
try {
|
|
346
|
+
command = (await client.query("COMMIT")).command;
|
|
347
|
+
} catch {
|
|
348
|
+
client.release(true);
|
|
349
|
+
return relayFailure("relay_state_ambiguous", "relay commit outcome is unproven");
|
|
350
|
+
}
|
|
351
|
+
if (command !== "COMMIT") {
|
|
352
|
+
client.release(true);
|
|
353
|
+
return relayFailure("relay_state_ambiguous", "relay commit did not commit");
|
|
354
|
+
}
|
|
355
|
+
client.release();
|
|
356
|
+
},
|
|
357
|
+
async rollback() {
|
|
358
|
+
if (settled) return;
|
|
359
|
+
settled = true;
|
|
360
|
+
try {
|
|
361
|
+
await client.query("ROLLBACK");
|
|
362
|
+
client.release();
|
|
363
|
+
} catch {
|
|
364
|
+
client.release(true);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function createPostgresRelayStore(options) {
|
|
370
|
+
const owned = !("pool" in options);
|
|
371
|
+
const pool = "pool" in options ? options.pool : new pg.Pool({ connectionString: options.connectionString });
|
|
372
|
+
return {
|
|
373
|
+
async begin() {
|
|
374
|
+
let client;
|
|
375
|
+
try {
|
|
376
|
+
client = await pool.connect();
|
|
377
|
+
} catch {
|
|
378
|
+
return relayFailure("relay_store_unavailable", "relay connection is unavailable");
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
await client.query("BEGIN");
|
|
382
|
+
} catch {
|
|
383
|
+
client.release(true);
|
|
384
|
+
return relayFailure("relay_store_unavailable", "relay transaction could not start");
|
|
385
|
+
}
|
|
386
|
+
return createTransaction(client);
|
|
387
|
+
},
|
|
388
|
+
async close() {
|
|
389
|
+
if (!owned) return;
|
|
390
|
+
try {
|
|
391
|
+
await pool.end();
|
|
392
|
+
} catch {
|
|
393
|
+
relayFailure("relay_store_unavailable", "relay pool could not be closed");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
//#endregion
|
|
399
|
+
export { OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS, OAATH_RELAY_POSTGRES_SCHEMA_VERSION, createPostgresRelaySchema, createPostgresRelayStore };
|
|
400
|
+
|
|
401
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.js","names":[],"sources":["../src/store/postgres/schema.ts","../src/store/postgres/queries.ts","../src/store/postgres/store.ts"],"sourcesContent":["/**\n * The current relay schema. There is no migration runner and no reader for any\n * older schema: an obsolete database is dropped and recreated.\n *\n * Constraints that the database can own are owned by the database — one decision\n * per request, one code and one artifact per request, monotonic timestamps.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nexport const OAATH_RELAY_POSTGRES_SCHEMA_VERSION = \"oaath.relay-postgres-schema/v1\" as const;\n\nconst MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;\n\nexport const OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS: readonly string[] = Object.freeze([\n `CREATE TABLE oaath_relay_schema_v1 (\n schema_id text PRIMARY KEY CHECK (schema_id = 'oaath'),\n version text NOT NULL\n )`,\n `INSERT INTO oaath_relay_schema_v1 (schema_id, version)\n VALUES ('oaath', '${OAATH_RELAY_POSTGRES_SCHEMA_VERSION}')`,\n `CREATE TABLE oaath_relay_authorization_request_v1 (\n request_id text PRIMARY KEY,\n record_version text NOT NULL,\n client_id text NOT NULL,\n subject text NOT NULL,\n redirect_uri text NOT NULL,\n code_challenge text NOT NULL,\n requested_scope text NOT NULL,\n created_at bigint NOT NULL CHECK (created_at >= 0 AND created_at <= ${MAX_SAFE_INTEGER}),\n expires_at bigint NOT NULL CHECK (expires_at >= created_at AND expires_at <= ${MAX_SAFE_INTEGER})\n )`,\n `CREATE TABLE oaath_relay_authorization_decision_v1 (\n request_id text PRIMARY KEY\n REFERENCES oaath_relay_authorization_request_v1 (request_id),\n record_version text NOT NULL,\n outcome text NOT NULL CHECK (outcome IN ('approved', 'rejected')),\n decided_at bigint NOT NULL CHECK (decided_at >= 0 AND decided_at <= ${MAX_SAFE_INTEGER}),\n code_ref text,\n code_expires_at bigint CHECK (code_expires_at >= 0 AND code_expires_at <= ${MAX_SAFE_INTEGER}),\n CHECK ((outcome = 'approved') = (code_ref IS NOT NULL)),\n CHECK ((code_ref IS NULL) = (code_expires_at IS NULL))\n )`,\n `CREATE TABLE oaath_relay_capability_invalidation_v1 (\n grant_id text PRIMARY KEY,\n record_version text NOT NULL,\n client_id text NOT NULL,\n capability_hash text NOT NULL,\n invalidated_at bigint NOT NULL CHECK (invalidated_at >= 0 AND invalidated_at <= ${MAX_SAFE_INTEGER})\n )`,\n `CREATE TABLE oaath_relay_authorization_code_v1 (\n code_hash text PRIMARY KEY,\n record_version text NOT NULL,\n request_id text NOT NULL UNIQUE\n REFERENCES oaath_relay_authorization_request_v1 (request_id),\n client_id text NOT NULL,\n redirect_uri text NOT NULL,\n code_challenge text NOT NULL,\n artifact_id text NOT NULL UNIQUE,\n created_at bigint NOT NULL CHECK (created_at >= 0 AND created_at <= ${MAX_SAFE_INTEGER}),\n expires_at bigint NOT NULL CHECK (expires_at >= created_at AND expires_at <= ${MAX_SAFE_INTEGER}),\n consumed_at bigint CHECK (consumed_at >= created_at AND consumed_at <= ${MAX_SAFE_INTEGER})\n )`,\n `CREATE TABLE oaath_relay_encrypted_artifact_v1 (\n artifact_id text PRIMARY KEY,\n record_version text NOT NULL,\n request_id text NOT NULL UNIQUE\n REFERENCES oaath_relay_authorization_request_v1 (request_id),\n client_id text NOT NULL,\n ciphertext_ref text NOT NULL,\n created_at bigint NOT NULL CHECK (created_at >= 0 AND created_at <= ${MAX_SAFE_INTEGER}),\n claimed_at bigint CHECK (claimed_at >= created_at AND claimed_at <= ${MAX_SAFE_INTEGER})\n )`,\n]);\n\nexport interface RelaySchemaExecutor {\n query(sql: string): Promise<unknown>;\n}\n\n/**\n * Creates the current schema in the executor's `search_path`. It is not a\n * migration: it fails if the objects already exist.\n */\nexport async function createPostgresRelaySchema(executor: RelaySchemaExecutor): Promise<void> {\n for (const statement of OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS) {\n await executor.query(statement);\n }\n}\n","/**\n * Relay SQL. Text only: no state-machine decision lives in this file.\n *\n * Names are unqualified so a deployment (or a disposable test schema) selects\n * them with `search_path`.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nconst REQUEST_COLUMNS = `\n request_id, record_version, client_id, subject, redirect_uri,\n code_challenge, requested_scope, created_at, expires_at\n`;\n\nconst CODE_COLUMNS = `\n code_hash, record_version, request_id, client_id, redirect_uri,\n code_challenge, artifact_id, created_at, expires_at, consumed_at\n`;\n\nconst ARTIFACT_COLUMNS = `\n artifact_id, record_version, request_id, client_id, ciphertext_ref,\n created_at, claimed_at\n`;\n\nexport const LOCK_AUTHORIZATION_REQUEST = `\n SELECT ${REQUEST_COLUMNS}\n FROM oaath_relay_authorization_request_v1\n WHERE request_id = $1\n FOR UPDATE\n`;\n\nexport const INSERT_AUTHORIZATION_REQUEST = `\n INSERT INTO oaath_relay_authorization_request_v1 (${REQUEST_COLUMNS})\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ON CONFLICT (request_id) DO NOTHING\n`;\n\nexport const LOCK_AUTHORIZATION_DECISION = `\n SELECT request_id, record_version, outcome, decided_at, code_ref, code_expires_at\n FROM oaath_relay_authorization_decision_v1\n WHERE request_id = $1\n FOR UPDATE\n`;\n\nexport const INSERT_AUTHORIZATION_DECISION = `\n INSERT INTO oaath_relay_authorization_decision_v1 (\n request_id, record_version, outcome, decided_at, code_ref, code_expires_at\n ) VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (request_id) DO NOTHING\n`;\n\nexport const LOCK_CAPABILITY_INVALIDATION = `\n SELECT grant_id, record_version, client_id, capability_hash, invalidated_at\n FROM oaath_relay_capability_invalidation_v1\n WHERE grant_id = $1\n FOR UPDATE\n`;\n\nexport const INSERT_CAPABILITY_INVALIDATION = `\n INSERT INTO oaath_relay_capability_invalidation_v1 (\n grant_id, record_version, client_id, capability_hash, invalidated_at\n ) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (grant_id) DO NOTHING\n`;\n\nexport const LOCK_AUTHORIZATION_CODE = `\n SELECT ${CODE_COLUMNS}\n FROM oaath_relay_authorization_code_v1\n WHERE code_hash = $1\n FOR UPDATE\n`;\n\nexport const INSERT_AUTHORIZATION_CODE = `\n INSERT INTO oaath_relay_authorization_code_v1 (${CODE_COLUMNS})\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n ON CONFLICT DO NOTHING\n`;\n\n/** One-shot: the guard makes a second consume affect zero rows. */\nexport const CONSUME_AUTHORIZATION_CODE = `\n UPDATE oaath_relay_authorization_code_v1\n SET consumed_at = $2\n WHERE code_hash = $1 AND consumed_at IS NULL\n`;\n\nexport const LOCK_ENCRYPTED_ARTIFACT = `\n SELECT ${ARTIFACT_COLUMNS}\n FROM oaath_relay_encrypted_artifact_v1\n WHERE artifact_id = $1\n FOR UPDATE\n`;\n\nexport const INSERT_ENCRYPTED_ARTIFACT = `\n INSERT INTO oaath_relay_encrypted_artifact_v1 (${ARTIFACT_COLUMNS})\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT DO NOTHING\n`;\n\n/** One-shot: the guard makes a second claim affect zero rows. */\nexport const CLAIM_ENCRYPTED_ARTIFACT = `\n UPDATE oaath_relay_encrypted_artifact_v1\n SET claimed_at = $2\n WHERE artifact_id = $1 AND claimed_at IS NULL\n`;\n","/**\n * PostgreSQL relay store.\n *\n * Every transition reads its row with `SELECT ... FOR UPDATE` and writes through\n * a guarded statement, so concurrent workers cannot both perform a one-shot\n * transition. A `COMMIT` whose outcome cannot be proven is reported as\n * `relay_state_ambiguous`: the caller neither assumes the transition applied nor\n * retries it.\n *\n * @author taek <leekt216@gmail.com>\n */\n\nimport type { Pool, PoolClient } from \"pg\";\nimport pg from \"pg\";\nimport { relayFailure } from \"../../relay/errors.js\";\nimport type { RelayStore, RelayTransaction } from \"../interface.js\";\nimport {\n type AuthorizationCodeRecord,\n type AuthorizationDecisionRecord,\n type AuthorizationRequestRecord,\n type CapabilityInvalidationRecord,\n type EncryptedArtifactRecord,\n parseAuthorizationCodeRecord,\n parseAuthorizationDecisionRecord,\n parseAuthorizationRequestRecord,\n parseCapabilityInvalidationRecord,\n parseEncryptedArtifactRecord,\n} from \"../records.js\";\nimport {\n CLAIM_ENCRYPTED_ARTIFACT,\n CONSUME_AUTHORIZATION_CODE,\n INSERT_AUTHORIZATION_CODE,\n INSERT_AUTHORIZATION_DECISION,\n INSERT_AUTHORIZATION_REQUEST,\n INSERT_CAPABILITY_INVALIDATION,\n INSERT_ENCRYPTED_ARTIFACT,\n LOCK_AUTHORIZATION_CODE,\n LOCK_AUTHORIZATION_DECISION,\n LOCK_AUTHORIZATION_REQUEST,\n LOCK_CAPABILITY_INVALIDATION,\n LOCK_ENCRYPTED_ARTIFACT,\n} from \"./queries.js\";\n\nexport type PostgresRelayStoreOptions =\n /** The deployment owns the pool: credentials, TLS, limits, and shutdown. */\n | Readonly<{ pool: Pool }>\n /** The store owns a pool created from this connection string. */\n | Readonly<{ connectionString: string }>;\n\ntype Row = Record<string, unknown>;\n\n/** `bigint` arrives as a decimal string; the record parser validates the result. */\nfunction sqlTimestamp(value: unknown): unknown {\n if (typeof value !== \"string\") return value;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : value;\n}\n\nfunction requestRecord(row: Row): AuthorizationRequestRecord {\n return parseAuthorizationRequestRecord({\n version: row.record_version,\n requestId: row.request_id,\n clientId: row.client_id,\n subject: row.subject,\n redirectUri: row.redirect_uri,\n codeChallenge: row.code_challenge,\n requestedScope: row.requested_scope,\n createdAt: sqlTimestamp(row.created_at),\n expiresAt: sqlTimestamp(row.expires_at),\n });\n}\n\nfunction decisionRecord(row: Row): AuthorizationDecisionRecord {\n return parseAuthorizationDecisionRecord({\n version: row.record_version,\n requestId: row.request_id,\n outcome: row.outcome,\n decidedAt: sqlTimestamp(row.decided_at),\n codeRef: row.code_ref,\n codeExpiresAt: row.code_expires_at === null ? null : sqlTimestamp(row.code_expires_at),\n });\n}\n\nfunction invalidationRecord(row: Row): CapabilityInvalidationRecord {\n return parseCapabilityInvalidationRecord({\n version: row.record_version,\n grantId: row.grant_id,\n clientId: row.client_id,\n capabilityHash: row.capability_hash,\n invalidatedAt: sqlTimestamp(row.invalidated_at),\n });\n}\n\nfunction codeRecord(row: Row): AuthorizationCodeRecord {\n return parseAuthorizationCodeRecord({\n version: row.record_version,\n codeHash: row.code_hash,\n requestId: row.request_id,\n clientId: row.client_id,\n redirectUri: row.redirect_uri,\n codeChallenge: row.code_challenge,\n artifactId: row.artifact_id,\n createdAt: sqlTimestamp(row.created_at),\n expiresAt: sqlTimestamp(row.expires_at),\n consumedAt: sqlTimestamp(row.consumed_at),\n });\n}\n\nfunction artifactRecord(row: Row): EncryptedArtifactRecord {\n return parseEncryptedArtifactRecord({\n version: row.record_version,\n artifactId: row.artifact_id,\n requestId: row.request_id,\n clientId: row.client_id,\n ciphertextRef: row.ciphertext_ref,\n createdAt: sqlTimestamp(row.created_at),\n claimedAt: sqlTimestamp(row.claimed_at),\n });\n}\n\nfunction createTransaction(client: PoolClient): RelayTransaction {\n let settled = false;\n\n async function run(\n sql: string,\n parameters: readonly unknown[],\n ): Promise<{ readonly rows: readonly Row[]; readonly rowCount: number }> {\n if (settled) relayFailure(\"relay_internal\", \"relay transaction already settled\");\n try {\n const result = await client.query(sql, [...parameters]);\n return { rows: result.rows, rowCount: result.rowCount ?? 0 };\n } catch {\n // Nothing has committed, so nothing is ambiguous yet.\n return relayFailure(\"relay_store_unavailable\", \"relay statement failed\");\n }\n }\n\n async function first<Value>(\n sql: string,\n parameters: readonly unknown[],\n map: (row: Row) => Value,\n ): Promise<Value | undefined> {\n const { rows } = await run(sql, parameters);\n if (rows.length === 0) return undefined;\n if (rows.length > 1) {\n return relayFailure(\"relay_record_unreadable\", \"relay key matched multiple rows\");\n }\n const row = rows[0];\n return row === undefined ? undefined : map(row);\n }\n\n async function inserted(sql: string, parameters: readonly unknown[]): Promise<boolean> {\n return (await run(sql, parameters)).rowCount === 1;\n }\n\n return {\n lockAuthorizationRequest(requestId) {\n return first(LOCK_AUTHORIZATION_REQUEST, [requestId], requestRecord);\n },\n insertAuthorizationRequest(record) {\n return inserted(INSERT_AUTHORIZATION_REQUEST, [\n record.requestId,\n record.version,\n record.clientId,\n record.subject,\n record.redirectUri,\n record.codeChallenge,\n record.requestedScope,\n record.createdAt,\n record.expiresAt,\n ]);\n },\n lockAuthorizationDecision(requestId) {\n return first(LOCK_AUTHORIZATION_DECISION, [requestId], decisionRecord);\n },\n insertAuthorizationDecision(record) {\n return inserted(INSERT_AUTHORIZATION_DECISION, [\n record.requestId,\n record.version,\n record.outcome,\n record.decidedAt,\n record.codeRef,\n record.codeExpiresAt,\n ]);\n },\n lockCapabilityInvalidation(grantId) {\n return first(LOCK_CAPABILITY_INVALIDATION, [grantId], invalidationRecord);\n },\n insertCapabilityInvalidation(record) {\n return inserted(INSERT_CAPABILITY_INVALIDATION, [\n record.grantId,\n record.version,\n record.clientId,\n record.capabilityHash,\n record.invalidatedAt,\n ]);\n },\n lockAuthorizationCode(codeHash) {\n return first(LOCK_AUTHORIZATION_CODE, [codeHash], codeRecord);\n },\n insertAuthorizationCode(record) {\n return inserted(INSERT_AUTHORIZATION_CODE, [\n record.codeHash,\n record.version,\n record.requestId,\n record.clientId,\n record.redirectUri,\n record.codeChallenge,\n record.artifactId,\n record.createdAt,\n record.expiresAt,\n record.consumedAt,\n ]);\n },\n consumeAuthorizationCode(codeHash, consumedAt) {\n return inserted(CONSUME_AUTHORIZATION_CODE, [codeHash, consumedAt]);\n },\n lockEncryptedArtifact(artifactId) {\n return first(LOCK_ENCRYPTED_ARTIFACT, [artifactId], artifactRecord);\n },\n insertEncryptedArtifact(record) {\n return inserted(INSERT_ENCRYPTED_ARTIFACT, [\n record.artifactId,\n record.version,\n record.requestId,\n record.clientId,\n record.ciphertextRef,\n record.createdAt,\n record.claimedAt,\n ]);\n },\n claimEncryptedArtifact(artifactId, claimedAt) {\n return inserted(CLAIM_ENCRYPTED_ARTIFACT, [artifactId, claimedAt]);\n },\n async commit() {\n if (settled) relayFailure(\"relay_internal\", \"relay transaction already settled\");\n settled = true;\n let command: unknown;\n try {\n command = (await client.query(\"COMMIT\")).command;\n } catch {\n // A COMMIT that did not answer never proves its own outcome.\n client.release(true);\n return relayFailure(\"relay_state_ambiguous\", \"relay commit outcome is unproven\");\n }\n if (command !== \"COMMIT\") {\n // PostgreSQL answers an aborted transaction's COMMIT with ROLLBACK.\n client.release(true);\n return relayFailure(\"relay_state_ambiguous\", \"relay commit did not commit\");\n }\n client.release();\n },\n async rollback() {\n if (settled) return;\n settled = true;\n try {\n await client.query(\"ROLLBACK\");\n client.release();\n } catch {\n // Cleanup is secondary; discard the connection instead of reusing it.\n client.release(true);\n }\n },\n };\n}\n\nexport function createPostgresRelayStore(options: PostgresRelayStoreOptions): RelayStore {\n const owned = !(\"pool\" in options);\n const pool: Pool =\n \"pool\" in options ? options.pool : new pg.Pool({ connectionString: options.connectionString });\n\n return {\n async begin(): Promise<RelayTransaction> {\n let client: PoolClient;\n try {\n client = await pool.connect();\n } catch {\n return relayFailure(\"relay_store_unavailable\", \"relay connection is unavailable\");\n }\n try {\n await client.query(\"BEGIN\");\n } catch {\n client.release(true);\n return relayFailure(\"relay_store_unavailable\", \"relay transaction could not start\");\n }\n return createTransaction(client);\n },\n async close(): Promise<void> {\n // A borrowed pool belongs to the deployment.\n if (!owned) return;\n try {\n await pool.end();\n } catch {\n relayFailure(\"relay_store_unavailable\", \"relay pool could not be closed\");\n }\n },\n };\n}\n"],"mappings":"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"}
|