@oaath/server 0.1.0 → 0.3.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.
Files changed (50) hide show
  1. package/README.md +239 -5
  2. package/dist/apns.d.ts +2 -2
  3. package/dist/apns.js +2 -2
  4. package/dist/{challenge-AGTmUfj2.js → challenge-DmpGSogj.js} +2 -2
  5. package/dist/{challenge-AGTmUfj2.js.map → challenge-DmpGSogj.js.map} +1 -1
  6. package/dist/inbox-B80EUVht.js +249 -0
  7. package/dist/inbox-B80EUVht.js.map +1 -0
  8. package/dist/index.d.ts +9 -255
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +631 -48
  11. package/dist/index.js.map +1 -1
  12. package/dist/interface-DHpwP0wr.d.ts +245 -0
  13. package/dist/interface-DHpwP0wr.d.ts.map +1 -0
  14. package/dist/kernel.d.ts +42 -0
  15. package/dist/kernel.d.ts.map +1 -0
  16. package/dist/kernel.js +80 -0
  17. package/dist/kernel.js.map +1 -0
  18. package/dist/kms-BWkywdJM.d.ts +18 -0
  19. package/dist/kms-BWkywdJM.d.ts.map +1 -0
  20. package/dist/native.d.ts +3 -30
  21. package/dist/native.js +115 -2
  22. package/dist/native.js.map +1 -0
  23. package/dist/postgres.d.ts +38 -3
  24. package/dist/postgres.d.ts.map +1 -1
  25. package/dist/postgres.js +301 -14
  26. package/dist/postgres.js.map +1 -1
  27. package/dist/projection-B7IB9irs.d.ts +228 -0
  28. package/dist/projection-B7IB9irs.d.ts.map +1 -0
  29. package/dist/{records-Bgr-9PUh.js → records-BQNDxbPg.js} +32 -8
  30. package/dist/records-BQNDxbPg.js.map +1 -0
  31. package/dist/records-BUvqmp0D.js +70 -0
  32. package/dist/records-BUvqmp0D.js.map +1 -0
  33. package/dist/service-Cf4f-9KP.js +706 -0
  34. package/dist/service-Cf4f-9KP.js.map +1 -0
  35. package/dist/service-J51bOVe6.d.ts +483 -0
  36. package/dist/service-J51bOVe6.d.ts.map +1 -0
  37. package/package.json +39 -8
  38. package/LICENSE +0 -201
  39. package/dist/authentication-HUYWk5Y2.d.ts +0 -46
  40. package/dist/authentication-HUYWk5Y2.d.ts.map +0 -1
  41. package/dist/decision-dEe9n108.d.ts +0 -53
  42. package/dist/decision-dEe9n108.d.ts.map +0 -1
  43. package/dist/interface-WwvwWs8e.d.ts +0 -114
  44. package/dist/interface-WwvwWs8e.d.ts.map +0 -1
  45. package/dist/native.d.ts.map +0 -1
  46. package/dist/projection-dGIkE0rI.js +0 -516
  47. package/dist/projection-dGIkE0rI.js.map +0 -1
  48. package/dist/projection-g6k_CCUK.d.ts +0 -136
  49. package/dist/projection-g6k_CCUK.d.ts.map +0 -1
  50. package/dist/records-Bgr-9PUh.js.map +0 -1
@@ -1,5 +1,23 @@
1
- import { t as RelayStore } from "./interface-WwvwWs8e.js";
1
+ import { t as RelayStore } from "./interface-DHpwP0wr.js";
2
+ import { R as OwnerDeviceCredentialStore, i as ServiceDirectoryStore } from "./service-J51bOVe6.js";
2
3
  import { Pool } from "pg";
4
+ import { OperationStoreAdapter } from "@oaath/sdk/advanced";
5
+ //#region src/directory/postgres.d.ts
6
+ /** Creates the current directory schema; existing/older tables are never migrated. */
7
+ declare function createPostgresServiceDirectorySchema(pool: Pick<Pool, "query">): Promise<void>;
8
+ /** The deployment owns this pool's connection policy and shutdown. */
9
+ declare function createPostgresServiceDirectoryStore(options: {
10
+ readonly pool: Pool;
11
+ }): ServiceDirectoryStore;
12
+ //#endregion
13
+ //#region src/native/authentication-postgres.d.ts
14
+ /** Provision once in a new schema. Older credential schemas are never migrated. */
15
+ declare function createPostgresOwnerDeviceCredentialSchema(pool: Pick<Pool, "query">): Promise<void>;
16
+ /** Deployment owns the pool. Every write is one statement and is never retried. */
17
+ declare function createPostgresOwnerDeviceCredentialStore(options: {
18
+ readonly pool: Pool;
19
+ }): OwnerDeviceCredentialStore;
20
+ //#endregion
3
21
  //#region src/store/postgres/schema.d.ts
4
22
  /**
5
23
  * The current relay schema. There is no migration runner and no reader for any
@@ -10,7 +28,7 @@ import { Pool } from "pg";
10
28
  *
11
29
  * @author taek <leekt216@gmail.com>
12
30
  */
13
- declare const OAATH_RELAY_POSTGRES_SCHEMA_VERSION: "oaath.relay-postgres-schema/v1";
31
+ declare const OAATH_RELAY_POSTGRES_SCHEMA_VERSION: "oaath.relay-postgres-schema/v4";
14
32
  declare const OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS: readonly string[];
15
33
  interface RelaySchemaExecutor {
16
34
  query(sql: string): Promise<unknown>;
@@ -21,6 +39,23 @@ interface RelaySchemaExecutor {
21
39
  */
22
40
  declare function createPostgresRelaySchema(executor: RelaySchemaExecutor): Promise<void>;
23
41
  //#endregion
42
+ //#region src/store/postgres/operation.d.ts
43
+ /**
44
+ * Current schema; obsolete state is recreated, never migrated. Records remain
45
+ * SDK-owned v3. Lane 0 is the default lane; a reserved lane key is positive.
46
+ */
47
+ declare function createPostgresOperationSchema(executor: RelaySchemaExecutor): Promise<void>;
48
+ /**
49
+ * The deployment owns the pool. SDK OperationStore captures records and validates
50
+ * lifecycle transitions; this adapter owns atomic revision/absence guards and
51
+ * archive publication. A permanent lane row also locks initially absent state.
52
+ * A conclusive loss returns false. An uncertain COMMIT throws and is never
53
+ * retried. Rollback/connection release are local cleanup; close borrows no pool.
54
+ */
55
+ declare function createPostgresOperationStoreAdapter({ pool }: {
56
+ readonly pool: Pool;
57
+ }): OperationStoreAdapter;
58
+ //#endregion
24
59
  //#region src/store/postgres/store.d.ts
25
60
  type PostgresRelayStoreOptions =
26
61
  /** The deployment owns the pool: credentials, TLS, limits, and shutdown. */
@@ -33,5 +68,5 @@ Readonly<{
33
68
  }>;
34
69
  declare function createPostgresRelayStore(options: PostgresRelayStoreOptions): RelayStore;
35
70
  //#endregion
36
- export { OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS, OAATH_RELAY_POSTGRES_SCHEMA_VERSION, type PostgresRelayStoreOptions, type RelaySchemaExecutor, createPostgresRelaySchema, createPostgresRelayStore };
71
+ export { OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS, OAATH_RELAY_POSTGRES_SCHEMA_VERSION, type PostgresRelayStoreOptions, type RelaySchemaExecutor, createPostgresOperationSchema, createPostgresOperationStoreAdapter, createPostgresOwnerDeviceCredentialSchema, createPostgresOwnerDeviceCredentialStore, createPostgresRelaySchema, createPostgresRelayStore, createPostgresServiceDirectorySchema, createPostgresServiceDirectoryStore };
37
72
  //# sourceMappingURL=postgres.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.d.ts","names":[],"sources":["../src/store/postgres/schema.ts","../src/store/postgres/store.ts"],"mappings":";;;;;;;;;;;;cAUa;cAIA;UA6DI;EACf,MAAM,cAAc;;;;;;iBAOA,0BAA0B,UAAU,sBAAsB;;;KCxCpE;;AAER;EAAW,MAAM;;;AAEjB;EAAW;;iBA2NC,yBAAyB,SAAS,4BAA4B"}
1
+ {"version":3,"file":"postgres.d.ts","names":[],"sources":["../src/directory/postgres.ts","../src/native/authentication-postgres.ts","../src/store/postgres/schema.ts","../src/store/postgres/operation.ts","../src/store/postgres/store.ts"],"mappings":";;;;;;iBAKsB,qCACpB,MAAM,KAAK,iBACV;;iBASa,oCAAoC;WACzC,MAAM;IACb;;;;iBCbkB,0CACpB,MAAM,KAAK,iBACV;;iBAYa,yCAAyC;WAC9C,MAAM;IACb;;;;;;;;;;;;cCXS;cAIA;UA4EI;EACf,MAAM,cAAc;;;;;;iBAOA,0BAA0B,UAAU,sBAAsB;;;;;;;iBCpF1D,8BAA8B,UAAU,sBAAsB;;;;;;;;iBA0BpE,sCACd;WAES,MAAM;IACb;;;KCUQ;;AAER;EAAW,MAAM;;;AAEjB;EAAW;;iBA8QC,yBAAyB,SAAS,4BAA4B"}
package/dist/postgres.js CHANGED
@@ -1,5 +1,196 @@
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";
1
+ import { b as relayFailure, d as parseAuthorizationRequestRecord, f as parseCapabilityInvalidationRecord, l as parseAuthorizationCodeRecord, p as parseEncryptedArtifactRecord, u as parseAuthorizationDecisionRecord } from "./records-BQNDxbPg.js";
2
+ import { a as parseRevocationRequestRecord, i as parseRevocationDecisionRecord } from "./records-BUvqmp0D.js";
2
3
  import pg from "pg";
4
+ //#region src/directory/postgres.ts
5
+ /** Creates the current directory schema; existing/older tables are never migrated. */
6
+ async function createPostgresServiceDirectorySchema(pool) {
7
+ await pool.query(`CREATE TABLE oaath_service_directory_v1 (
8
+ directory_id text PRIMARY KEY CHECK (directory_id = 'oaath'),
9
+ revision bigint NOT NULL CHECK (revision >= 1 AND revision <= ${Number.MAX_SAFE_INTEGER}),
10
+ directory jsonb NOT NULL
11
+ )`);
12
+ }
13
+ /** The deployment owns this pool's connection policy and shutdown. */
14
+ function createPostgresServiceDirectoryStore(options) {
15
+ const { pool } = options;
16
+ return {
17
+ async read() {
18
+ try {
19
+ const row = (await pool.query("SELECT revision, directory FROM oaath_service_directory_v1 WHERE directory_id = 'oaath'")).rows[0];
20
+ return row === void 0 ? null : {
21
+ revision: Number(row.revision),
22
+ directory: row.directory
23
+ };
24
+ } catch {
25
+ return relayFailure("relay_store_unavailable", "directory store is unavailable");
26
+ }
27
+ },
28
+ async compareAndSwap(expectedRevision, directory) {
29
+ const serialized = JSON.stringify(directory);
30
+ try {
31
+ return (expectedRevision === null ? await pool.query("INSERT INTO oaath_service_directory_v1 (directory_id, revision, directory) VALUES ('oaath', 1, $1::jsonb) ON CONFLICT DO NOTHING RETURNING revision", [serialized]) : await pool.query("UPDATE oaath_service_directory_v1 SET revision = revision + 1, directory = $1::jsonb WHERE directory_id = 'oaath' AND revision = $2 RETURNING revision", [serialized, expectedRevision])).rows.length === 1;
32
+ } catch {
33
+ return relayFailure("relay_state_ambiguous", "directory write outcome is unknown");
34
+ }
35
+ }
36
+ };
37
+ }
38
+ //#endregion
39
+ //#region src/native/authentication-postgres.ts
40
+ /** Provision once in a new schema. Older credential schemas are never migrated. */
41
+ async function createPostgresOwnerDeviceCredentialSchema(pool) {
42
+ await pool.query(`CREATE TABLE oaath_owner_device_credentials_v1 (
43
+ credential_hash text PRIMARY KEY,
44
+ version text NOT NULL CHECK (version = 'oaath.owner-device-credential/v1'),
45
+ workspace_id text NOT NULL,
46
+ owner_device_id text NOT NULL,
47
+ subject text NOT NULL,
48
+ revoked boolean NOT NULL
49
+ )`);
50
+ }
51
+ /** Deployment owns the pool. Every write is one statement and is never retried. */
52
+ function createPostgresOwnerDeviceCredentialStore(options) {
53
+ const { pool } = options;
54
+ return {
55
+ async read(hash) {
56
+ try {
57
+ return (await pool.query(`SELECT version, credential_hash AS "credentialHash",
58
+ workspace_id AS "workspaceId", owner_device_id AS "ownerDeviceId", subject, revoked
59
+ FROM oaath_owner_device_credentials_v1 WHERE credential_hash = $1`, [hash])).rows[0] ?? null;
60
+ } catch {
61
+ return relayFailure("relay_store_unavailable", "owner device credential store is unavailable");
62
+ }
63
+ },
64
+ async insert(record) {
65
+ try {
66
+ return (await pool.query(`INSERT INTO oaath_owner_device_credentials_v1
67
+ (credential_hash, version, workspace_id, owner_device_id, subject, revoked)
68
+ VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT DO NOTHING RETURNING credential_hash`, [
69
+ record.credentialHash,
70
+ record.version,
71
+ record.workspaceId,
72
+ record.ownerDeviceId,
73
+ record.subject,
74
+ record.revoked
75
+ ])).rows.length === 1;
76
+ } catch {
77
+ return relayFailure("relay_state_ambiguous", "owner device credential write outcome is unknown");
78
+ }
79
+ },
80
+ async revoke(device) {
81
+ try {
82
+ await pool.query(`UPDATE oaath_owner_device_credentials_v1 SET revoked = TRUE
83
+ WHERE workspace_id = $1 AND owner_device_id = $2 AND revoked = FALSE`, [device.workspaceId, device.ownerDeviceId]);
84
+ } catch {
85
+ return relayFailure("relay_state_ambiguous", "owner device credential revocation outcome is unknown");
86
+ }
87
+ }
88
+ };
89
+ }
90
+ //#endregion
91
+ //#region src/store/postgres/operation.ts
92
+ /**
93
+ * Current schema; obsolete state is recreated, never migrated. Records remain
94
+ * SDK-owned v3. Lane 0 is the default lane; a reserved lane key is positive.
95
+ */
96
+ async function createPostgresOperationSchema(executor) {
97
+ await executor.query(`CREATE TABLE oaath_operation_lane_v2 (
98
+ grant_id text NOT NULL, chain_id bigint NOT NULL, kind text NOT NULL,
99
+ lane integer NOT NULL CHECK (lane >= 0 AND (lane = 0 OR kind = 'execution')),
100
+ record jsonb,
101
+ PRIMARY KEY (grant_id, chain_id, kind, lane)
102
+ )`);
103
+ await executor.query(`CREATE TABLE oaath_operation_archive_v2 (
104
+ grant_id text NOT NULL, chain_id bigint NOT NULL, kind text NOT NULL, lane integer NOT NULL,
105
+ user_operation_hash text NOT NULL, record jsonb NOT NULL,
106
+ PRIMARY KEY (grant_id, chain_id, kind, lane, user_operation_hash),
107
+ FOREIGN KEY (grant_id, chain_id, kind, lane) REFERENCES oaath_operation_lane_v2
108
+ )`);
109
+ }
110
+ const lane = "grant_id = $1 AND chain_id = $2 AND kind = $3 AND lane = $4";
111
+ function keyParts(key) {
112
+ return [
113
+ key.grantId,
114
+ key.chainId,
115
+ key.kind,
116
+ key.lane ?? 0
117
+ ];
118
+ }
119
+ /**
120
+ * The deployment owns the pool. SDK OperationStore captures records and validates
121
+ * lifecycle transitions; this adapter owns atomic revision/absence guards and
122
+ * archive publication. A permanent lane row also locks initially absent state.
123
+ * A conclusive loss returns false. An uncertain COMMIT throws and is never
124
+ * retried. Rollback/connection release are local cleanup; close borrows no pool.
125
+ */
126
+ function createPostgresOperationStoreAdapter({ pool }) {
127
+ async function read(sql, args) {
128
+ try {
129
+ return (await pool.query(sql, [...args])).rows[0]?.record ?? void 0;
130
+ } catch {
131
+ return relayFailure("relay_store_unavailable", "operation read unavailable");
132
+ }
133
+ }
134
+ return Object.freeze({
135
+ get: (key) => read(`SELECT record FROM oaath_operation_lane_v2 WHERE ${lane}`, keyParts(key)),
136
+ async list(scope) {
137
+ try {
138
+ return (await pool.query(`SELECT record FROM oaath_operation_lane_v2 WHERE grant_id = $1 AND chain_id = $2 AND kind = $3 AND record IS NOT NULL ORDER BY lane`, [
139
+ scope.grantId,
140
+ scope.chainId,
141
+ scope.kind
142
+ ])).rows.map((row) => row.record);
143
+ } catch {
144
+ return relayFailure("relay_store_unavailable", "operation list unavailable");
145
+ }
146
+ },
147
+ getArchived: (input) => read(`SELECT record FROM oaath_operation_archive_v2 WHERE ${lane} AND user_operation_hash = $5`, [...keyParts(input.key), input.userOperationHash]),
148
+ async compareAndSwap(input) {
149
+ const client = await pool.connect().catch(() => relayFailure("relay_store_unavailable", "operation connection unavailable"));
150
+ let committing = false;
151
+ let discard = false;
152
+ try {
153
+ await client.query("BEGIN");
154
+ const parts = keyParts(input.key);
155
+ await client.query(`INSERT INTO oaath_operation_lane_v2 (grant_id, chain_id, kind, lane) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING`, parts);
156
+ const row = (await client.query(`SELECT record, record = $5::jsonb AS matches_archive FROM oaath_operation_lane_v2 WHERE ${lane} FOR UPDATE`, [...parts, input.archive === null ? null : JSON.stringify(input.archive.record)])).rows[0];
157
+ let accepted = (row?.record?.storeRevision ?? null) === input.expectedStoreRevision;
158
+ if ((await client.query(`SELECT 1 FROM oaath_operation_archive_v2 WHERE ${lane} AND user_operation_hash = $5`, [...parts, input.expectedArchiveAbsentUserOperationHash])).rowCount !== 0) accepted = false;
159
+ if (input.archive !== null) {
160
+ if (row?.matches_archive !== true || input.archive.userOperationHash === input.expectedArchiveAbsentUserOperationHash) accepted = false;
161
+ if (accepted) accepted = (await client.query(`INSERT INTO oaath_operation_archive_v2 (grant_id, chain_id, kind, lane, user_operation_hash, record) VALUES ($1, $2, $3, $4, $5, $6::jsonb) ON CONFLICT DO NOTHING`, [
162
+ ...parts,
163
+ input.archive.userOperationHash,
164
+ JSON.stringify(input.archive.record)
165
+ ])).rowCount === 1;
166
+ }
167
+ if (!accepted) {
168
+ await client.query("ROLLBACK");
169
+ return false;
170
+ }
171
+ await client.query(`UPDATE oaath_operation_lane_v2 SET record = $5::jsonb WHERE ${lane}`, [...parts, JSON.stringify(input.next)]);
172
+ committing = true;
173
+ if ((await client.query("COMMIT")).command !== "COMMIT") throw new Error("unproven commit");
174
+ return true;
175
+ } catch {
176
+ if (committing) {
177
+ discard = true;
178
+ return relayFailure("relay_state_ambiguous", "operation commit outcome is unproven");
179
+ }
180
+ try {
181
+ await client.query("ROLLBACK");
182
+ } catch {
183
+ discard = true;
184
+ }
185
+ return relayFailure("relay_store_unavailable", "operation transition unavailable");
186
+ } finally {
187
+ client.release(discard);
188
+ }
189
+ },
190
+ async close() {}
191
+ });
192
+ }
193
+ //#endregion
3
194
  //#region src/store/postgres/schema.ts
4
195
  /**
5
196
  * The current relay schema. There is no migration runner and no reader for any
@@ -10,20 +201,23 @@ import pg from "pg";
10
201
  *
11
202
  * @author taek <leekt216@gmail.com>
12
203
  */
13
- const OAATH_RELAY_POSTGRES_SCHEMA_VERSION = "oaath.relay-postgres-schema/v1";
204
+ const OAATH_RELAY_POSTGRES_SCHEMA_VERSION = "oaath.relay-postgres-schema/v4";
14
205
  const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
15
206
  const OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS = Object.freeze([
16
- `CREATE TABLE oaath_relay_schema_v1 (
207
+ `CREATE TABLE oaath_relay_schema_v4 (
17
208
  schema_id text PRIMARY KEY CHECK (schema_id = 'oaath'),
18
209
  version text NOT NULL
19
210
  )`,
20
- `INSERT INTO oaath_relay_schema_v1 (schema_id, version)
211
+ `INSERT INTO oaath_relay_schema_v4 (schema_id, version)
21
212
  VALUES ('oaath', '${OAATH_RELAY_POSTGRES_SCHEMA_VERSION}')`,
22
- `CREATE TABLE oaath_relay_authorization_request_v1 (
213
+ `CREATE TABLE oaath_relay_authorization_request_v2 (
23
214
  request_id text PRIMARY KEY,
24
215
  record_version text NOT NULL,
25
216
  client_id text NOT NULL,
26
217
  subject text NOT NULL,
218
+ owner_device_id text NOT NULL,
219
+ owner_subject text NOT NULL,
220
+ organization_audience text,
27
221
  redirect_uri text NOT NULL,
28
222
  code_challenge text NOT NULL,
29
223
  requested_scope text NOT NULL,
@@ -32,7 +226,7 @@ const OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS = Object.freeze([
32
226
  )`,
33
227
  `CREATE TABLE oaath_relay_authorization_decision_v1 (
34
228
  request_id text PRIMARY KEY
35
- REFERENCES oaath_relay_authorization_request_v1 (request_id),
229
+ REFERENCES oaath_relay_authorization_request_v2 (request_id),
36
230
  record_version text NOT NULL,
37
231
  outcome text NOT NULL CHECK (outcome IN ('approved', 'rejected')),
38
232
  decided_at bigint NOT NULL CHECK (decided_at >= 0 AND decided_at <= ${MAX_SAFE_INTEGER}),
@@ -52,7 +246,7 @@ const OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS = Object.freeze([
52
246
  code_hash text PRIMARY KEY,
53
247
  record_version text NOT NULL,
54
248
  request_id text NOT NULL UNIQUE
55
- REFERENCES oaath_relay_authorization_request_v1 (request_id),
249
+ REFERENCES oaath_relay_authorization_request_v2 (request_id),
56
250
  client_id text NOT NULL,
57
251
  redirect_uri text NOT NULL,
58
252
  code_challenge text NOT NULL,
@@ -65,11 +259,23 @@ const OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS = Object.freeze([
65
259
  artifact_id text PRIMARY KEY,
66
260
  record_version text NOT NULL,
67
261
  request_id text NOT NULL UNIQUE
68
- REFERENCES oaath_relay_authorization_request_v1 (request_id),
262
+ REFERENCES oaath_relay_authorization_request_v2 (request_id),
69
263
  client_id text NOT NULL,
70
264
  ciphertext_ref text NOT NULL,
71
265
  created_at bigint NOT NULL CHECK (created_at >= 0 AND created_at <= ${MAX_SAFE_INTEGER}),
72
266
  claimed_at bigint CHECK (claimed_at >= created_at AND claimed_at <= ${MAX_SAFE_INTEGER})
267
+ )`,
268
+ `CREATE TABLE oaath_relay_revocation_request_v1 (
269
+ operation_id text PRIMARY KEY,
270
+ record jsonb NOT NULL CHECK (record->>'operationId' = operation_id)
271
+ )`,
272
+ `CREATE TABLE oaath_relay_revocation_decision_v1 (
273
+ operation_id text PRIMARY KEY REFERENCES oaath_relay_revocation_request_v1 (operation_id),
274
+ record jsonb NOT NULL CHECK (record->>'operationId' = operation_id)
275
+ )`,
276
+ `CREATE INDEX oaath_relay_revocation_scope_created_v1 ON oaath_relay_revocation_request_v1 (
277
+ (record #>> '{signingRequest,permissionRequest,requestId}'),
278
+ (record #>> '{signingRequest,chainId}'), ((record->>'createdAt')::bigint) DESC
73
279
  )`
74
280
  ]);
75
281
  /**
@@ -90,8 +296,8 @@ async function createPostgresRelaySchema(executor) {
90
296
  * @author taek <leekt216@gmail.com>
91
297
  */
92
298
  const REQUEST_COLUMNS = `
93
- request_id, record_version, client_id, subject, redirect_uri,
94
- code_challenge, requested_scope, created_at, expires_at
299
+ request_id, record_version, client_id, subject, owner_device_id, owner_subject, organization_audience,
300
+ redirect_uri, code_challenge, requested_scope, created_at, expires_at
95
301
  `;
96
302
  const CODE_COLUMNS = `
97
303
  code_hash, record_version, request_id, client_id, redirect_uri,
@@ -101,15 +307,34 @@ const ARTIFACT_COLUMNS = `
101
307
  artifact_id, record_version, request_id, client_id, ciphertext_ref,
102
308
  created_at, claimed_at
103
309
  `;
310
+ /** One read snapshot of both existing request/decision pairs; no inbox table or lease. */
311
+ const LIST_PENDING_OWNER_REQUESTS = `
312
+ SELECT kind, record FROM (
313
+ SELECT 'authorization' AS kind, to_jsonb(request) AS record,
314
+ request.request_id AS operation_id, request.expires_at
315
+ FROM oaath_relay_authorization_request_v2 AS request
316
+ WHERE request.owner_subject = $1 AND request.expires_at > $2
317
+ AND NOT EXISTS (SELECT 1 FROM oaath_relay_authorization_decision_v1 AS decision
318
+ WHERE decision.request_id = request.request_id)
319
+ UNION ALL
320
+ SELECT 'revocation' AS kind, request.record,
321
+ request.operation_id, (request.record->>'expiresAt')::bigint AS expires_at
322
+ FROM oaath_relay_revocation_request_v1 AS request
323
+ WHERE request.record->>'ownerSubject' = $1 AND (request.record->>'expiresAt')::bigint > $2
324
+ AND NOT EXISTS (SELECT 1 FROM oaath_relay_revocation_decision_v1 AS decision
325
+ WHERE decision.operation_id = request.operation_id)
326
+ ) AS pending
327
+ ORDER BY expires_at, operation_id COLLATE "C" LIMIT $3
328
+ `;
104
329
  const LOCK_AUTHORIZATION_REQUEST = `
105
330
  SELECT ${REQUEST_COLUMNS}
106
- FROM oaath_relay_authorization_request_v1
331
+ FROM oaath_relay_authorization_request_v2
107
332
  WHERE request_id = $1
108
333
  FOR UPDATE
109
334
  `;
110
335
  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)
336
+ INSERT INTO oaath_relay_authorization_request_v2 (${REQUEST_COLUMNS})
337
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
113
338
  ON CONFLICT (request_id) DO NOTHING
114
339
  `;
115
340
  const LOCK_AUTHORIZATION_DECISION = `
@@ -159,6 +384,13 @@ const LOCK_ENCRYPTED_ARTIFACT = `
159
384
  WHERE artifact_id = $1
160
385
  FOR UPDATE
161
386
  `;
387
+ /** The existing unique request_id index identifies the same sealed artifact after claim. */
388
+ const LOCK_ENCRYPTED_ARTIFACT_BY_REQUEST_ID = `
389
+ SELECT ${ARTIFACT_COLUMNS}
390
+ FROM oaath_relay_encrypted_artifact_v1
391
+ WHERE request_id = $1
392
+ FOR UPDATE
393
+ `;
162
394
  const INSERT_ENCRYPTED_ARTIFACT = `
163
395
  INSERT INTO oaath_relay_encrypted_artifact_v1 (${ARTIFACT_COLUMNS})
164
396
  VALUES ($1, $2, $3, $4, $5, $6, $7)
@@ -170,6 +402,14 @@ const CLAIM_ENCRYPTED_ARTIFACT = `
170
402
  SET claimed_at = $2
171
403
  WHERE artifact_id = $1 AND claimed_at IS NULL
172
404
  `;
405
+ const LOCK_REVOCATION_REQUEST = `SELECT record FROM oaath_relay_revocation_request_v1 WHERE operation_id = $1 FOR UPDATE`;
406
+ const LOCK_LATEST_REVOCATION_REQUEST = `SELECT record FROM oaath_relay_revocation_request_v1
407
+ WHERE record #>> '{signingRequest,permissionRequest,requestId}' = $1
408
+ AND record #>> '{signingRequest,chainId}' = $2
409
+ ORDER BY (record->>'createdAt')::bigint DESC LIMIT 1 FOR UPDATE`;
410
+ const INSERT_REVOCATION_REQUEST = `INSERT INTO oaath_relay_revocation_request_v1 (operation_id, record) VALUES ($1, $2::jsonb) ON CONFLICT (operation_id) DO NOTHING`;
411
+ const LOCK_REVOCATION_DECISION = `SELECT record FROM oaath_relay_revocation_decision_v1 WHERE operation_id = $1 FOR UPDATE`;
412
+ const INSERT_REVOCATION_DECISION = `INSERT INTO oaath_relay_revocation_decision_v1 (operation_id, record) VALUES ($1, $2::jsonb) ON CONFLICT (operation_id) DO NOTHING`;
173
413
  //#endregion
174
414
  //#region src/store/postgres/store.ts
175
415
  /** `bigint` arrives as a decimal string; the record parser validates the result. */
@@ -184,6 +424,9 @@ function requestRecord(row) {
184
424
  requestId: row.request_id,
185
425
  clientId: row.client_id,
186
426
  subject: row.subject,
427
+ ownerDeviceId: row.owner_device_id,
428
+ ownerSubject: row.owner_subject,
429
+ organizationAudience: row.organization_audience,
187
430
  redirectUri: row.redirect_uri,
188
431
  codeChallenge: row.code_challenge,
189
432
  requestedScope: row.requested_scope,
@@ -260,6 +503,44 @@ function createTransaction(client) {
260
503
  return (await run(sql, parameters)).rowCount === 1;
261
504
  }
262
505
  return {
506
+ async listPendingOwnerRequests(ownerSubject, now, limit) {
507
+ return (await run(LIST_PENDING_OWNER_REQUESTS, [
508
+ ownerSubject,
509
+ now,
510
+ limit
511
+ ])).rows.map((row) => {
512
+ if (row.kind === "authorization") {
513
+ const record = requestRecord(row.record);
514
+ return {
515
+ operationId: record.requestId,
516
+ ownerSubject: record.ownerSubject,
517
+ expiresAt: record.expiresAt
518
+ };
519
+ }
520
+ if (row.kind !== "revocation") return relayFailure("relay_record_unreadable", "pending request kind is invalid");
521
+ const record = parseRevocationRequestRecord(row.record);
522
+ return {
523
+ operationId: record.operationId,
524
+ ownerSubject: record.ownerSubject,
525
+ expiresAt: record.expiresAt
526
+ };
527
+ });
528
+ },
529
+ lockLatestRevocationRequest(grantId, chainId) {
530
+ return first(LOCK_LATEST_REVOCATION_REQUEST, [grantId, String(chainId)], (row) => parseRevocationRequestRecord(row.record));
531
+ },
532
+ lockRevocationRequest(operationId) {
533
+ return first(LOCK_REVOCATION_REQUEST, [operationId], (row) => parseRevocationRequestRecord(row.record));
534
+ },
535
+ insertRevocationRequest(record) {
536
+ return inserted(INSERT_REVOCATION_REQUEST, [record.operationId, JSON.stringify(record)]);
537
+ },
538
+ lockRevocationDecision(operationId) {
539
+ return first(LOCK_REVOCATION_DECISION, [operationId], (row) => parseRevocationDecisionRecord(row.record));
540
+ },
541
+ insertRevocationDecision(record) {
542
+ return inserted(INSERT_REVOCATION_DECISION, [record.operationId, JSON.stringify(record)]);
543
+ },
263
544
  lockAuthorizationRequest(requestId) {
264
545
  return first(LOCK_AUTHORIZATION_REQUEST, [requestId], requestRecord);
265
546
  },
@@ -269,6 +550,9 @@ function createTransaction(client) {
269
550
  record.version,
270
551
  record.clientId,
271
552
  record.subject,
553
+ record.ownerDeviceId,
554
+ record.ownerSubject,
555
+ record.organizationAudience,
272
556
  record.redirectUri,
273
557
  record.codeChallenge,
274
558
  record.requestedScope,
@@ -324,6 +608,9 @@ function createTransaction(client) {
324
608
  lockEncryptedArtifact(artifactId) {
325
609
  return first(LOCK_ENCRYPTED_ARTIFACT, [artifactId], artifactRecord);
326
610
  },
611
+ lockEncryptedArtifactByRequestId(requestId) {
612
+ return first(LOCK_ENCRYPTED_ARTIFACT_BY_REQUEST_ID, [requestId], artifactRecord);
613
+ },
327
614
  insertEncryptedArtifact(record) {
328
615
  return inserted(INSERT_ENCRYPTED_ARTIFACT, [
329
616
  record.artifactId,
@@ -396,6 +683,6 @@ function createPostgresRelayStore(options) {
396
683
  };
397
684
  }
398
685
  //#endregion
399
- export { OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS, OAATH_RELAY_POSTGRES_SCHEMA_VERSION, createPostgresRelaySchema, createPostgresRelayStore };
686
+ export { OAATH_RELAY_POSTGRES_SCHEMA_STATEMENTS, OAATH_RELAY_POSTGRES_SCHEMA_VERSION, createPostgresOperationSchema, createPostgresOperationStoreAdapter, createPostgresOwnerDeviceCredentialSchema, createPostgresOwnerDeviceCredentialStore, createPostgresRelaySchema, createPostgresRelayStore, createPostgresServiceDirectorySchema, createPostgresServiceDirectoryStore };
400
687
 
401
688
  //# sourceMappingURL=postgres.js.map
@@ -1 +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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"}
1
+ {"version":3,"file":"postgres.js","names":[],"sources":["../src/directory/postgres.ts","../src/native/authentication-postgres.ts","../src/store/postgres/operation.ts","../src/store/postgres/schema.ts","../src/store/postgres/queries.ts","../src/store/postgres/store.ts"],"sourcesContent":["import type { Pool } from \"pg\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport type { ServiceDirectoryStore } from \"./service.js\";\n\n/** Creates the current directory schema; existing/older tables are never migrated. */\nexport async function createPostgresServiceDirectorySchema(\n pool: Pick<Pool, \"query\">,\n): Promise<void> {\n await pool.query(`CREATE TABLE oaath_service_directory_v1 (\n directory_id text PRIMARY KEY CHECK (directory_id = 'oaath'),\n revision bigint NOT NULL CHECK (revision >= 1 AND revision <= ${Number.MAX_SAFE_INTEGER}),\n directory jsonb NOT NULL\n )`);\n}\n\n/** The deployment owns this pool's connection policy and shutdown. */\nexport function createPostgresServiceDirectoryStore(options: {\n readonly pool: Pool;\n}): ServiceDirectoryStore {\n const { pool } = options;\n return {\n async read() {\n try {\n const result = await pool.query(\n \"SELECT revision, directory FROM oaath_service_directory_v1 WHERE directory_id = 'oaath'\",\n );\n const row = result.rows[0];\n return row === undefined\n ? null\n : { revision: Number(row.revision), directory: row.directory };\n } catch {\n return relayFailure(\"relay_store_unavailable\", \"directory store is unavailable\");\n }\n },\n async compareAndSwap(expectedRevision, directory) {\n const serialized = JSON.stringify(directory);\n try {\n const result =\n expectedRevision === null\n ? await pool.query(\n \"INSERT INTO oaath_service_directory_v1 (directory_id, revision, directory) VALUES ('oaath', 1, $1::jsonb) ON CONFLICT DO NOTHING RETURNING revision\",\n [serialized],\n )\n : await pool.query(\n \"UPDATE oaath_service_directory_v1 SET revision = revision + 1, directory = $1::jsonb WHERE directory_id = 'oaath' AND revision = $2 RETURNING revision\",\n [serialized, expectedRevision],\n );\n return result.rows.length === 1;\n } catch {\n return relayFailure(\"relay_state_ambiguous\", \"directory write outcome is unknown\");\n }\n },\n };\n}\n","import type { Pool } from \"pg\";\nimport { relayFailure } from \"../relay/errors.js\";\nimport type { OwnerDeviceCredentialStore } from \"./authentication.js\";\n\n/** Provision once in a new schema. Older credential schemas are never migrated. */\nexport async function createPostgresOwnerDeviceCredentialSchema(\n pool: Pick<Pool, \"query\">,\n): Promise<void> {\n await pool.query(`CREATE TABLE oaath_owner_device_credentials_v1 (\n credential_hash text PRIMARY KEY,\n version text NOT NULL CHECK (version = 'oaath.owner-device-credential/v1'),\n workspace_id text NOT NULL,\n owner_device_id text NOT NULL,\n subject text NOT NULL,\n revoked boolean NOT NULL\n )`);\n}\n\n/** Deployment owns the pool. Every write is one statement and is never retried. */\nexport function createPostgresOwnerDeviceCredentialStore(options: {\n readonly pool: Pool;\n}): OwnerDeviceCredentialStore {\n const { pool } = options;\n return {\n async read(hash) {\n try {\n const result = await pool.query(\n `SELECT version, credential_hash AS \"credentialHash\",\n workspace_id AS \"workspaceId\", owner_device_id AS \"ownerDeviceId\", subject, revoked\n FROM oaath_owner_device_credentials_v1 WHERE credential_hash = $1`,\n [hash],\n );\n return result.rows[0] ?? null;\n } catch {\n return relayFailure(\n \"relay_store_unavailable\",\n \"owner device credential store is unavailable\",\n );\n }\n },\n async insert(record) {\n try {\n const result = await pool.query(\n `INSERT INTO oaath_owner_device_credentials_v1\n (credential_hash, version, workspace_id, owner_device_id, subject, revoked)\n VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT DO NOTHING RETURNING credential_hash`,\n [\n record.credentialHash,\n record.version,\n record.workspaceId,\n record.ownerDeviceId,\n record.subject,\n record.revoked,\n ],\n );\n return result.rows.length === 1;\n } catch {\n return relayFailure(\n \"relay_state_ambiguous\",\n \"owner device credential write outcome is unknown\",\n );\n }\n },\n async revoke(device) {\n try {\n await pool.query(\n `UPDATE oaath_owner_device_credentials_v1 SET revoked = TRUE\n WHERE workspace_id = $1 AND owner_device_id = $2 AND revoked = FALSE`,\n [device.workspaceId, device.ownerDeviceId],\n );\n } catch {\n return relayFailure(\n \"relay_state_ambiguous\",\n \"owner device credential revocation outcome is unknown\",\n );\n }\n },\n };\n}\n","/** PostgreSQL persistence for the existing SDK OperationStore; no second state machine. */\nimport type {\n OperationStoreAdapter,\n OperationStoreKey,\n OperationStoreScope,\n} from \"@oaath/sdk/advanced\";\nimport type { Pool } from \"pg\";\nimport { relayFailure } from \"../../relay/errors.js\";\nimport type { RelaySchemaExecutor } from \"./schema.js\";\n\n/**\n * Current schema; obsolete state is recreated, never migrated. Records remain\n * SDK-owned v3. Lane 0 is the default lane; a reserved lane key is positive.\n */\nexport async function createPostgresOperationSchema(executor: RelaySchemaExecutor): Promise<void> {\n await executor.query(`CREATE TABLE oaath_operation_lane_v2 (\n grant_id text NOT NULL, chain_id bigint NOT NULL, kind text NOT NULL,\n lane integer NOT NULL CHECK (lane >= 0 AND (lane = 0 OR kind = 'execution')),\n record jsonb,\n PRIMARY KEY (grant_id, chain_id, kind, lane)\n )`);\n await executor.query(`CREATE TABLE oaath_operation_archive_v2 (\n grant_id text NOT NULL, chain_id bigint NOT NULL, kind text NOT NULL, lane integer NOT NULL,\n user_operation_hash text NOT NULL, record jsonb NOT NULL,\n PRIMARY KEY (grant_id, chain_id, kind, lane, user_operation_hash),\n FOREIGN KEY (grant_id, chain_id, kind, lane) REFERENCES oaath_operation_lane_v2\n )`);\n}\nconst lane = \"grant_id = $1 AND chain_id = $2 AND kind = $3 AND lane = $4\";\nfunction keyParts(key: Readonly<OperationStoreKey>) {\n return [key.grantId, key.chainId, key.kind, key.lane ?? 0];\n}\n\n/**\n * The deployment owns the pool. SDK OperationStore captures records and validates\n * lifecycle transitions; this adapter owns atomic revision/absence guards and\n * archive publication. A permanent lane row also locks initially absent state.\n * A conclusive loss returns false. An uncertain COMMIT throws and is never\n * retried. Rollback/connection release are local cleanup; close borrows no pool.\n */\nexport function createPostgresOperationStoreAdapter({\n pool,\n}: {\n readonly pool: Pool;\n}): OperationStoreAdapter {\n async function read(sql: string, args: readonly unknown[]): Promise<unknown> {\n try {\n return (await pool.query(sql, [...args])).rows[0]?.record ?? undefined;\n } catch {\n return relayFailure(\"relay_store_unavailable\", \"operation read unavailable\");\n }\n }\n return Object.freeze({\n get: (key: Readonly<OperationStoreKey>) =>\n read(`SELECT record FROM oaath_operation_lane_v2 WHERE ${lane}`, keyParts(key)),\n async list(scope: Readonly<OperationStoreScope>): Promise<unknown> {\n try {\n return (\n await pool.query(\n `SELECT record FROM oaath_operation_lane_v2 WHERE grant_id = $1 AND chain_id = $2 AND kind = $3 AND record IS NOT NULL ORDER BY lane`,\n [scope.grantId, scope.chainId, scope.kind],\n )\n ).rows.map((row) => row.record);\n } catch {\n return relayFailure(\"relay_store_unavailable\", \"operation list unavailable\");\n }\n },\n getArchived: (input: Parameters<OperationStoreAdapter[\"getArchived\"]>[0]) =>\n read(\n `SELECT record FROM oaath_operation_archive_v2 WHERE ${lane} AND user_operation_hash = $5`,\n [...keyParts(input.key), input.userOperationHash],\n ),\n async compareAndSwap(\n input: Parameters<OperationStoreAdapter[\"compareAndSwap\"]>[0],\n ): Promise<boolean> {\n const client = await pool\n .connect()\n .catch(() => relayFailure(\"relay_store_unavailable\", \"operation connection unavailable\"));\n let committing = false;\n let discard = false;\n try {\n await client.query(\"BEGIN\");\n const parts = keyParts(input.key);\n await client.query(\n `INSERT INTO oaath_operation_lane_v2 (grant_id, chain_id, kind, lane) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING`,\n parts,\n );\n const row = (\n await client.query(\n `SELECT record, record = $5::jsonb AS matches_archive FROM oaath_operation_lane_v2 WHERE ${lane} FOR UPDATE`,\n [...parts, input.archive === null ? null : JSON.stringify(input.archive.record)],\n )\n ).rows[0];\n let accepted = (row?.record?.storeRevision ?? null) === input.expectedStoreRevision;\n if (\n (\n await client.query(\n `SELECT 1 FROM oaath_operation_archive_v2 WHERE ${lane} AND user_operation_hash = $5`,\n [...parts, input.expectedArchiveAbsentUserOperationHash],\n )\n ).rowCount !== 0\n )\n accepted = false;\n if (input.archive !== null) {\n if (\n row?.matches_archive !== true ||\n input.archive.userOperationHash === input.expectedArchiveAbsentUserOperationHash\n )\n accepted = false;\n if (accepted)\n accepted =\n (\n await client.query(\n `INSERT INTO oaath_operation_archive_v2 (grant_id, chain_id, kind, lane, user_operation_hash, record) VALUES ($1, $2, $3, $4, $5, $6::jsonb) ON CONFLICT DO NOTHING`,\n [...parts, input.archive.userOperationHash, JSON.stringify(input.archive.record)],\n )\n ).rowCount === 1;\n }\n if (!accepted) {\n await client.query(\"ROLLBACK\");\n return false;\n }\n await client.query(`UPDATE oaath_operation_lane_v2 SET record = $5::jsonb WHERE ${lane}`, [\n ...parts,\n JSON.stringify(input.next),\n ]);\n committing = true;\n if ((await client.query(\"COMMIT\")).command !== \"COMMIT\") throw new Error(\"unproven commit\");\n return true;\n } catch {\n if (committing) {\n discard = true;\n return relayFailure(\"relay_state_ambiguous\", \"operation commit outcome is unproven\");\n }\n try {\n await client.query(\"ROLLBACK\");\n } catch {\n discard = true;\n }\n return relayFailure(\"relay_store_unavailable\", \"operation transition unavailable\");\n } finally {\n client.release(discard);\n }\n },\n async close() {\n /* Deployment owns the shared pool. */\n },\n });\n}\n","/**\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/v4\" 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_v4 (\n schema_id text PRIMARY KEY CHECK (schema_id = 'oaath'),\n version text NOT NULL\n )`,\n `INSERT INTO oaath_relay_schema_v4 (schema_id, version)\n VALUES ('oaath', '${OAATH_RELAY_POSTGRES_SCHEMA_VERSION}')`,\n `CREATE TABLE oaath_relay_authorization_request_v2 (\n request_id text PRIMARY KEY,\n record_version text NOT NULL,\n client_id text NOT NULL,\n subject text NOT NULL,\n owner_device_id text NOT NULL,\n owner_subject text NOT NULL,\n organization_audience text,\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_v2 (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_v2 (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_v2 (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 `CREATE TABLE oaath_relay_revocation_request_v1 (\n operation_id text PRIMARY KEY,\n record jsonb NOT NULL CHECK (record->>'operationId' = operation_id)\n )`,\n `CREATE TABLE oaath_relay_revocation_decision_v1 (\n operation_id text PRIMARY KEY REFERENCES oaath_relay_revocation_request_v1 (operation_id),\n record jsonb NOT NULL CHECK (record->>'operationId' = operation_id)\n )`,\n `CREATE INDEX oaath_relay_revocation_scope_created_v1 ON oaath_relay_revocation_request_v1 (\n (record #>> '{signingRequest,permissionRequest,requestId}'),\n (record #>> '{signingRequest,chainId}'), ((record->>'createdAt')::bigint) DESC\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, owner_device_id, owner_subject, organization_audience,\n redirect_uri, 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\n/** One read snapshot of both existing request/decision pairs; no inbox table or lease. */\nexport const LIST_PENDING_OWNER_REQUESTS = `\n SELECT kind, record FROM (\n SELECT 'authorization' AS kind, to_jsonb(request) AS record,\n request.request_id AS operation_id, request.expires_at\n FROM oaath_relay_authorization_request_v2 AS request\n WHERE request.owner_subject = $1 AND request.expires_at > $2\n AND NOT EXISTS (SELECT 1 FROM oaath_relay_authorization_decision_v1 AS decision\n WHERE decision.request_id = request.request_id)\n UNION ALL\n SELECT 'revocation' AS kind, request.record,\n request.operation_id, (request.record->>'expiresAt')::bigint AS expires_at\n FROM oaath_relay_revocation_request_v1 AS request\n WHERE request.record->>'ownerSubject' = $1 AND (request.record->>'expiresAt')::bigint > $2\n AND NOT EXISTS (SELECT 1 FROM oaath_relay_revocation_decision_v1 AS decision\n WHERE decision.operation_id = request.operation_id)\n ) AS pending\n ORDER BY expires_at, operation_id COLLATE \"C\" LIMIT $3\n`;\n\nexport const LOCK_AUTHORIZATION_REQUEST = `\n SELECT ${REQUEST_COLUMNS}\n FROM oaath_relay_authorization_request_v2\n WHERE request_id = $1\n FOR UPDATE\n`;\n\nexport const INSERT_AUTHORIZATION_REQUEST = `\n INSERT INTO oaath_relay_authorization_request_v2 (${REQUEST_COLUMNS})\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\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\n/** The existing unique request_id index identifies the same sealed artifact after claim. */\nexport const LOCK_ENCRYPTED_ARTIFACT_BY_REQUEST_ID = `\n SELECT ${ARTIFACT_COLUMNS}\n FROM oaath_relay_encrypted_artifact_v1\n WHERE request_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\nexport const LOCK_REVOCATION_REQUEST = `SELECT record FROM oaath_relay_revocation_request_v1 WHERE operation_id = $1 FOR UPDATE`;\nexport const LOCK_LATEST_REVOCATION_REQUEST = `SELECT record FROM oaath_relay_revocation_request_v1\n WHERE record #>> '{signingRequest,permissionRequest,requestId}' = $1\n AND record #>> '{signingRequest,chainId}' = $2\n ORDER BY (record->>'createdAt')::bigint DESC LIMIT 1 FOR UPDATE`;\nexport const INSERT_REVOCATION_REQUEST = `INSERT INTO oaath_relay_revocation_request_v1 (operation_id, record) VALUES ($1, $2::jsonb) ON CONFLICT (operation_id) DO NOTHING`;\nexport const LOCK_REVOCATION_DECISION = `SELECT record FROM oaath_relay_revocation_decision_v1 WHERE operation_id = $1 FOR UPDATE`;\nexport const INSERT_REVOCATION_DECISION = `INSERT INTO oaath_relay_revocation_decision_v1 (operation_id, record) VALUES ($1, $2::jsonb) ON CONFLICT (operation_id) DO NOTHING`;\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 {\n parseRevocationDecisionRecord,\n parseRevocationRequestRecord,\n} from \"../../revocation/records.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 INSERT_REVOCATION_DECISION,\n INSERT_REVOCATION_REQUEST,\n LIST_PENDING_OWNER_REQUESTS,\n LOCK_AUTHORIZATION_CODE,\n LOCK_AUTHORIZATION_DECISION,\n LOCK_AUTHORIZATION_REQUEST,\n LOCK_CAPABILITY_INVALIDATION,\n LOCK_ENCRYPTED_ARTIFACT,\n LOCK_ENCRYPTED_ARTIFACT_BY_REQUEST_ID,\n LOCK_LATEST_REVOCATION_REQUEST,\n LOCK_REVOCATION_DECISION,\n LOCK_REVOCATION_REQUEST,\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 ownerDeviceId: row.owner_device_id,\n ownerSubject: row.owner_subject,\n organizationAudience: row.organization_audience,\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 async listPendingOwnerRequests(ownerSubject, now, limit) {\n const result = await run(LIST_PENDING_OWNER_REQUESTS, [ownerSubject, now, limit]);\n return result.rows.map((row) => {\n if (row.kind === \"authorization\") {\n const record = requestRecord(row.record as Row);\n return {\n operationId: record.requestId,\n ownerSubject: record.ownerSubject,\n expiresAt: record.expiresAt,\n };\n }\n if (row.kind !== \"revocation\")\n return relayFailure(\"relay_record_unreadable\", \"pending request kind is invalid\");\n const record = parseRevocationRequestRecord(row.record);\n return {\n operationId: record.operationId,\n ownerSubject: record.ownerSubject,\n expiresAt: record.expiresAt,\n };\n });\n },\n lockLatestRevocationRequest(grantId, chainId) {\n return first(LOCK_LATEST_REVOCATION_REQUEST, [grantId, String(chainId)], (row) =>\n parseRevocationRequestRecord(row.record),\n );\n },\n lockRevocationRequest(operationId) {\n return first(LOCK_REVOCATION_REQUEST, [operationId], (row) =>\n parseRevocationRequestRecord(row.record),\n );\n },\n insertRevocationRequest(record) {\n return inserted(INSERT_REVOCATION_REQUEST, [record.operationId, JSON.stringify(record)]);\n },\n lockRevocationDecision(operationId) {\n return first(LOCK_REVOCATION_DECISION, [operationId], (row) =>\n parseRevocationDecisionRecord(row.record),\n );\n },\n insertRevocationDecision(record) {\n return inserted(INSERT_REVOCATION_DECISION, [record.operationId, JSON.stringify(record)]);\n },\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.ownerDeviceId,\n record.ownerSubject,\n record.organizationAudience,\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 lockEncryptedArtifactByRequestId(requestId) {\n return first(LOCK_ENCRYPTED_ARTIFACT_BY_REQUEST_ID, [requestId], 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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"}