@oaath/server 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +239 -5
- package/dist/apns.d.ts +2 -2
- package/dist/apns.js +2 -2
- package/dist/{challenge-AGTmUfj2.js → challenge-DmpGSogj.js} +2 -2
- package/dist/{challenge-AGTmUfj2.js.map → challenge-DmpGSogj.js.map} +1 -1
- package/dist/inbox-B80EUVht.js +249 -0
- package/dist/inbox-B80EUVht.js.map +1 -0
- package/dist/index.d.ts +9 -255
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +631 -48
- package/dist/index.js.map +1 -1
- package/dist/interface-DHpwP0wr.d.ts +245 -0
- package/dist/interface-DHpwP0wr.d.ts.map +1 -0
- package/dist/kernel.d.ts +42 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +80 -0
- package/dist/kernel.js.map +1 -0
- package/dist/kms-BWkywdJM.d.ts +18 -0
- package/dist/kms-BWkywdJM.d.ts.map +1 -0
- package/dist/native.d.ts +3 -30
- package/dist/native.js +115 -2
- package/dist/native.js.map +1 -0
- package/dist/postgres.d.ts +38 -3
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +301 -14
- package/dist/postgres.js.map +1 -1
- package/dist/projection-B7IB9irs.d.ts +228 -0
- package/dist/projection-B7IB9irs.d.ts.map +1 -0
- package/dist/{records-Bgr-9PUh.js → records-BQNDxbPg.js} +32 -8
- package/dist/records-BQNDxbPg.js.map +1 -0
- package/dist/records-BUvqmp0D.js +70 -0
- package/dist/records-BUvqmp0D.js.map +1 -0
- package/dist/service-Cf4f-9KP.js +706 -0
- package/dist/service-Cf4f-9KP.js.map +1 -0
- package/dist/service-J51bOVe6.d.ts +483 -0
- package/dist/service-J51bOVe6.d.ts.map +1 -0
- package/package.json +39 -8
- package/LICENSE +0 -201
- package/dist/authentication-HUYWk5Y2.d.ts +0 -46
- package/dist/authentication-HUYWk5Y2.d.ts.map +0 -1
- package/dist/decision-dEe9n108.d.ts +0 -53
- package/dist/decision-dEe9n108.d.ts.map +0 -1
- package/dist/interface-WwvwWs8e.d.ts +0 -114
- package/dist/interface-WwvwWs8e.d.ts.map +0 -1
- package/dist/native.d.ts.map +0 -1
- package/dist/projection-dGIkE0rI.js +0 -516
- package/dist/projection-dGIkE0rI.js.map +0 -1
- package/dist/projection-g6k_CCUK.d.ts +0 -136
- package/dist/projection-g6k_CCUK.d.ts.map +0 -1
- package/dist/records-Bgr-9PUh.js.map +0 -1
package/dist/postgres.d.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import { t as RelayStore } from "./interface-
|
|
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/
|
|
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
|
package/dist/postgres.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.d.ts","names":[],"sources":["../src/store/postgres/schema.ts","../src/store/postgres/store.ts"],"mappings":";;;;;;;;;;;;
|
|
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-
|
|
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/
|
|
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
|
|
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
|
|
211
|
+
`INSERT INTO oaath_relay_schema_v4 (schema_id, version)
|
|
21
212
|
VALUES ('oaath', '${OAATH_RELAY_POSTGRES_SCHEMA_VERSION}')`,
|
|
22
|
-
`CREATE TABLE
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
package/dist/postgres.js.map
CHANGED
|
@@ -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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"}
|