@yielded/auth-persistence-drizzle 0.1.0-beta.22 → 0.1.0-beta.24

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 (200) hide show
  1. package/README.md +3 -0
  2. package/dist/D1.d.mts +11 -11
  3. package/dist/D1.mjs +2 -1
  4. package/dist/D1.mjs.map +1 -1
  5. package/dist/Libsql.d.mts +40 -56
  6. package/dist/Libsql.mjs +2 -1
  7. package/dist/Libsql.mjs.map +1 -1
  8. package/dist/Mysql2.d.mts +16 -17
  9. package/dist/Pglite.d.mts +18 -18
  10. package/dist/Pglite.mjs +2 -1
  11. package/dist/Pglite.mjs.map +1 -1
  12. package/dist/Postgres.d.mts +24 -24
  13. package/dist/Postgres.mjs +2 -1
  14. package/dist/Postgres.mjs.map +1 -1
  15. package/dist/SqliteBun.d.mts +39 -55
  16. package/dist/SqliteBun.mjs +2 -1
  17. package/dist/SqliteBun.mjs.map +1 -1
  18. package/dist/SqliteDo.d.mts +48 -64
  19. package/dist/SqliteDo.mjs +2 -1
  20. package/dist/SqliteDo.mjs.map +1 -1
  21. package/dist/SqliteNode.d.mts +45 -61
  22. package/dist/SqliteNode.mjs +2 -1
  23. package/dist/SqliteNode.mjs.map +1 -1
  24. package/dist/SqliteWasm.d.mts +48 -64
  25. package/dist/SqliteWasm.mjs +2 -1
  26. package/dist/SqliteWasm.mjs.map +1 -1
  27. package/dist/drizzle/crypto.mjs +1 -1
  28. package/dist/drizzle/d1-emails.d.mts +1 -1
  29. package/dist/drizzle/d1-oauth.d.mts +14 -14
  30. package/dist/drizzle/d1-passkey.d.mts +1 -1
  31. package/dist/drizzle/d1-password-prepared.d.mts +4 -4
  32. package/dist/drizzle/d1-password-prepared.mjs.map +1 -1
  33. package/dist/drizzle/d1-passwords.d.mts +18 -35
  34. package/dist/drizzle/d1-passwords.mjs +78 -290
  35. package/dist/drizzle/d1-passwords.mjs.map +1 -1
  36. package/dist/drizzle/d1-phone.d.mts +1 -1
  37. package/dist/drizzle/d1-proofs.d.mts +2 -2
  38. package/dist/drizzle/d1-sessions.d.mts +3 -3
  39. package/dist/drizzle/d1-sessions.mjs +11 -6
  40. package/dist/drizzle/d1-sessions.mjs.map +1 -1
  41. package/dist/drizzle/d1-totp.d.mts +1 -1
  42. package/dist/drizzle/mysql-password-prepared.d.mts +5 -5
  43. package/dist/drizzle/mysql-password-prepared.mjs.map +1 -1
  44. package/dist/drizzle/mysql-passwords.d.mts +16 -32
  45. package/dist/drizzle/mysql-passwords.mjs.map +1 -1
  46. package/dist/drizzle/mysql-sessions.d.mts +1 -1
  47. package/dist/drizzle/native-sql-table.mjs +135 -0
  48. package/dist/drizzle/native-sql-table.mjs.map +1 -0
  49. package/dist/drizzle/oauth-connected-model.d.mts +20 -281
  50. package/dist/drizzle/oauth-connected-model.mjs +3 -17
  51. package/dist/drizzle/oauth-connected-model.mjs.map +1 -1
  52. package/dist/drizzle/oauth-connected-reference.d.mts +1 -3
  53. package/dist/drizzle/oauth-connected-reference.mjs +4 -53
  54. package/dist/drizzle/oauth-connected-reference.mjs.map +1 -1
  55. package/dist/drizzle/oauth-connected-target.d.mts +3 -990
  56. package/dist/drizzle/oauth-connected-target.mjs +8 -92
  57. package/dist/drizzle/oauth-connected-target.mjs.map +1 -1
  58. package/dist/drizzle/oauth-kernel.mjs +11 -0
  59. package/dist/drizzle/oauth-kernel.mjs.map +1 -0
  60. package/dist/drizzle/oauth-model.d.mts +35 -438
  61. package/dist/drizzle/oauth-model.mjs +6 -44
  62. package/dist/drizzle/oauth-model.mjs.map +1 -1
  63. package/dist/drizzle/oauth-mysql.mjs +2 -2
  64. package/dist/drizzle/oauth-mysql.mjs.map +1 -1
  65. package/dist/drizzle/oauth-proxy.d.mts +10 -0
  66. package/dist/drizzle/oauth-proxy.mjs +54 -0
  67. package/dist/drizzle/oauth-proxy.mjs.map +1 -0
  68. package/dist/drizzle/oauth-target.d.mts +6 -107
  69. package/dist/drizzle/oauth-target.mjs +15 -245
  70. package/dist/drizzle/oauth-target.mjs.map +1 -1
  71. package/dist/drizzle/password-model.d.mts +5 -8
  72. package/dist/drizzle/password-prepared-model.d.mts +5 -5
  73. package/dist/drizzle/password-prepared-model.mjs.map +1 -1
  74. package/dist/drizzle/password-prepared-state.mjs +1 -1
  75. package/dist/drizzle/password-sql.mjs +2 -1
  76. package/dist/drizzle/password-sql.mjs.map +1 -1
  77. package/dist/drizzle/password-target.mjs +13 -2
  78. package/dist/drizzle/password-target.mjs.map +1 -1
  79. package/dist/drizzle/pg-password-prepared.d.mts +5 -5
  80. package/dist/drizzle/pg-password-prepared.mjs.map +1 -1
  81. package/dist/drizzle/pg-passwords.d.mts +16 -32
  82. package/dist/drizzle/pg-passwords.mjs.map +1 -1
  83. package/dist/drizzle/pg-sessions.d.mts +1 -1
  84. package/dist/drizzle/phone-target.d.mts +2 -2
  85. package/dist/drizzle/phone-target.mjs +1 -1
  86. package/dist/drizzle/query-operations.mjs +3 -1
  87. package/dist/drizzle/query-operations.mjs.map +1 -1
  88. package/dist/drizzle/session-sql.mjs +2 -1
  89. package/dist/drizzle/session-sql.mjs.map +1 -1
  90. package/dist/drizzle/sqlite-password-prepared.mjs.map +1 -1
  91. package/dist/drizzle/sqlite-passwords.mjs.map +1 -1
  92. package/dist/drizzle/storage-validation.mjs +10 -198
  93. package/dist/drizzle/storage-validation.mjs.map +1 -1
  94. package/dist/drizzle/totp-target.d.mts +3 -3
  95. package/dist/drizzle/totp-target.mjs +2 -2
  96. package/dist/drizzle/transaction-execution.d.mts +2 -2
  97. package/dist/drizzle/transaction-owner.d.mts +1 -2
  98. package/dist/index.d.mts +3 -2
  99. package/dist/internal/drizzle-postgres-oauth-proxy.d.mts +138 -0
  100. package/dist/internal/drizzle-postgres-oauth-proxy.mjs +29 -0
  101. package/dist/internal/drizzle-postgres-oauth-proxy.mjs.map +1 -0
  102. package/dist/internal/drizzle-postgres.mjs +2 -0
  103. package/dist/internal/drizzle-postgres.mjs.map +1 -1
  104. package/dist/internal/drizzle-sqlite-oauth-proxy.d.mts +146 -0
  105. package/dist/internal/drizzle-sqlite-oauth-proxy.mjs +29 -0
  106. package/dist/internal/drizzle-sqlite-oauth-proxy.mjs.map +1 -0
  107. package/dist/internal/drizzle-sqlite.mjs +2 -0
  108. package/dist/internal/drizzle-sqlite.mjs.map +1 -1
  109. package/package.json +1 -1
  110. package/src/D1.ts +2 -0
  111. package/src/Libsql.ts +2 -0
  112. package/src/Pglite.ts +2 -0
  113. package/src/Postgres.ts +2 -0
  114. package/src/SqliteBun.ts +2 -0
  115. package/src/SqliteDo.ts +2 -0
  116. package/src/SqliteNode.ts +2 -0
  117. package/src/SqliteWasm.ts +2 -0
  118. package/src/drizzle/d1-password-prepared.ts +2 -23
  119. package/src/drizzle/d1-passwords.ts +158 -780
  120. package/src/drizzle/d1-sessions.ts +21 -6
  121. package/src/drizzle/mysql-password-prepared.ts +4 -16
  122. package/src/drizzle/mysql-passwords.ts +5 -20
  123. package/src/drizzle/native-sql-table.ts +259 -0
  124. package/src/drizzle/oauth-connected-model.ts +97 -315
  125. package/src/drizzle/oauth-connected-reference.ts +13 -93
  126. package/src/drizzle/oauth-connected-target.ts +14 -271
  127. package/src/drizzle/oauth-kernel.ts +10 -0
  128. package/src/drizzle/oauth-model.ts +188 -541
  129. package/src/drizzle/oauth-mysql.ts +2 -2
  130. package/src/drizzle/oauth-proxy.ts +101 -0
  131. package/src/drizzle/oauth-target.ts +33 -737
  132. package/src/drizzle/password-model.ts +0 -33
  133. package/src/drizzle/password-prepared-model.ts +0 -18
  134. package/src/drizzle/password-sql.ts +3 -0
  135. package/src/drizzle/password-target.ts +41 -4
  136. package/src/drizzle/pg-password-prepared.ts +4 -16
  137. package/src/drizzle/pg-passwords.ts +5 -20
  138. package/src/drizzle/query-operations.ts +4 -0
  139. package/src/drizzle/session-sql.ts +2 -1
  140. package/src/drizzle/sqlite-password-prepared.ts +4 -52
  141. package/src/drizzle/sqlite-passwords.ts +7 -28
  142. package/src/drizzle/storage-validation.ts +13 -222
  143. package/src/index.ts +2 -8
  144. package/src/internal/drizzle-postgres-oauth-proxy.ts +37 -0
  145. package/src/internal/drizzle-postgres.ts +2 -0
  146. package/src/internal/drizzle-sqlite-oauth-proxy.ts +44 -0
  147. package/src/internal/drizzle-sqlite.ts +2 -0
  148. package/dist/_virtual/_rolldown/runtime.mjs +0 -13
  149. package/dist/drizzle/oauth-accounts.mjs +0 -359
  150. package/dist/drizzle/oauth-accounts.mjs.map +0 -1
  151. package/dist/drizzle/oauth-connected-access.mjs +0 -212
  152. package/dist/drizzle/oauth-connected-access.mjs.map +0 -1
  153. package/dist/drizzle/oauth-connected-collection.mjs +0 -202
  154. package/dist/drizzle/oauth-connected-collection.mjs.map +0 -1
  155. package/dist/drizzle/oauth-connected-custody.mjs +0 -147
  156. package/dist/drizzle/oauth-connected-custody.mjs.map +0 -1
  157. package/dist/drizzle/oauth-connected-flow.mjs +0 -297
  158. package/dist/drizzle/oauth-connected-flow.mjs.map +0 -1
  159. package/dist/drizzle/oauth-connected-input.mjs +0 -122
  160. package/dist/drizzle/oauth-connected-input.mjs.map +0 -1
  161. package/dist/drizzle/oauth-connected-maintenance.mjs +0 -214
  162. package/dist/drizzle/oauth-connected-maintenance.mjs.map +0 -1
  163. package/dist/drizzle/oauth-connected-management.mjs +0 -229
  164. package/dist/drizzle/oauth-connected-management.mjs.map +0 -1
  165. package/dist/drizzle/oauth-connected-settlement.mjs +0 -128
  166. package/dist/drizzle/oauth-connected-settlement.mjs.map +0 -1
  167. package/dist/drizzle/oauth-connected-sign-in.mjs +0 -265
  168. package/dist/drizzle/oauth-connected-sign-in.mjs.map +0 -1
  169. package/dist/drizzle/oauth-connected-state.d.mts +0 -2062
  170. package/dist/drizzle/oauth-connected-state.mjs +0 -430
  171. package/dist/drizzle/oauth-connected-state.mjs.map +0 -1
  172. package/dist/drizzle/oauth-flow.d.mts +0 -22
  173. package/dist/drizzle/oauth-flow.mjs +0 -253
  174. package/dist/drizzle/oauth-flow.mjs.map +0 -1
  175. package/dist/drizzle/oauth-input.mjs +0 -127
  176. package/dist/drizzle/oauth-input.mjs.map +0 -1
  177. package/dist/drizzle/oauth-owner.d.mts +0 -10
  178. package/dist/drizzle/oauth-owner.mjs +0 -10
  179. package/dist/drizzle/oauth-owner.mjs.map +0 -1
  180. package/dist/drizzle/oauth-registration.d.mts +0 -5
  181. package/dist/drizzle/oauth-registration.mjs +0 -374
  182. package/dist/drizzle/oauth-registration.mjs.map +0 -1
  183. package/dist/drizzle/oauth-state.mjs +0 -107
  184. package/dist/drizzle/oauth-state.mjs.map +0 -1
  185. package/src/drizzle/oauth-accounts.ts +0 -805
  186. package/src/drizzle/oauth-connected-access.ts +0 -361
  187. package/src/drizzle/oauth-connected-collection.ts +0 -324
  188. package/src/drizzle/oauth-connected-custody.ts +0 -279
  189. package/src/drizzle/oauth-connected-flow.ts +0 -529
  190. package/src/drizzle/oauth-connected-input.ts +0 -147
  191. package/src/drizzle/oauth-connected-maintenance.ts +0 -346
  192. package/src/drizzle/oauth-connected-management.ts +0 -365
  193. package/src/drizzle/oauth-connected-settlement.ts +0 -251
  194. package/src/drizzle/oauth-connected-sign-in.ts +0 -443
  195. package/src/drizzle/oauth-connected-state.ts +0 -872
  196. package/src/drizzle/oauth-flow.ts +0 -462
  197. package/src/drizzle/oauth-input.ts +0 -201
  198. package/src/drizzle/oauth-owner.ts +0 -29
  199. package/src/drizzle/oauth-registration.ts +0 -640
  200. package/src/drizzle/oauth-state.ts +0 -218
@@ -1,529 +0,0 @@
1
- import {
2
- OAuthAccountRevision,
3
- snapshotOAuthSync,
4
- type OAuthConnectedPersistence,
5
- } from "@yielded/auth/OAuth";
6
- import * as M from "@yielded/auth/OAuth";
7
- /* oxlint-disable no-explicit-any -- private mapped rows; public adapters retain native IDs and closed Effects. */
8
- import { sql } from "drizzle-orm";
9
- import { Effect } from "effect";
10
-
11
- import * as S from "./oauth-connected-state";
12
- import { both, copiedRow, equal, CurrentOAuthTransaction } from "./oauth-owner";
13
- import { acquireTuple, readTuple } from "./oauth-registration";
14
- import { digest, invariant, oauthIdentityKey, sameIdentity, sameRevision } from "./oauth-state";
15
-
16
- export type Input<K extends keyof OAuthConnectedPersistence["Service"]> = Parameters<
17
- OAuthConnectedPersistence["Service"][K]
18
- >[0];
19
-
20
- export const pending = Effect.fn("oauthConnected.pending")(function* (
21
- mapping: S.Authority,
22
- access: M.OAuthConnectedAccess,
23
- lock = true,
24
- ) {
25
- const owner = yield* CurrentOAuthTransaction;
26
- const f = mapping.flow;
27
-
28
- const read = yield* owner.read(
29
- f.table,
30
- equal(f.table, { [f.moduleId]: access.moduleId, [f.flowId]: access.flowId }),
31
- { limit: 1, lock, observe: lock },
32
- );
33
-
34
- const row = read.rows[0];
35
-
36
- if (row === undefined || row[f.snapshot] === null) return undefined;
37
-
38
- const flow = S.flowStorage.decode(row[f.snapshot]),
39
- c = flow.context;
40
-
41
- const native = yield* mapping.subjectId.toNative(c.revision.subjectId);
42
-
43
- invariant(
44
- row[f.moduleId] === c.moduleId &&
45
- row[f.flowId] === c.flowId &&
46
- row[f.commandId] === c.commandId &&
47
- mapping.subjectId.equals(native, row[f.subjectId]) &&
48
- row[f.clientKey] === (yield* S.clientKey(S.configuration(flow))) &&
49
- row[f.stateDigest] === c.stateDigest &&
50
- mapping.clock.decodeInstant(row[f.expiresAt]) === c.expiresAtMillis &&
51
- mapping.clock.decodeInstant(row[f.retentionUntil]) === flow.retentionUntilMillis,
52
- );
53
-
54
- const matches =
55
- c.moduleId === access.moduleId &&
56
- c.generation === access.generation &&
57
- c.flowId === access.flowId &&
58
- c.revision.subjectId === access.subjectId &&
59
- c.provider === access.provider &&
60
- c.callbackId === access.callbackId &&
61
- c.stateDigest === access.stateDigest &&
62
- c.requestBindingVerifier === access.requestBindingVerifier &&
63
- c.requestBindingExpiresAtMillis === access.requestBindingExpiresAtMillis &&
64
- (c.responseIssuerMode === "required"
65
- ? access.responseIssuer === c.issuer
66
- : access.responseIssuer === undefined);
67
-
68
- return matches ? { row, flow, native } : undefined;
69
- });
70
-
71
- export const live = (flow: M.OAuthConnectedPendingFlow, now: number) =>
72
- flow.context.issuedAtMillis <= now &&
73
- now < flow.context.expiresAtMillis &&
74
- flow.context.expiresAtMillis <= flow.context.requestBindingExpiresAtMillis &&
75
- flow.retentionUntilMillis >= flow.context.expiresAtMillis + flow.context.claimLifetimeMillis;
76
-
77
- export const expected = (flow: M.OAuthConnectedPendingFlow) => ({
78
- moduleId: flow.context.moduleId,
79
- flowId: flow.context.flowId,
80
- commandId: flow.context.commandId,
81
- revision: flow.context.revision,
82
- intent: S.contextStorage.encode(flow.context),
83
- maximumAgeMillis: flow.context.maximumEvidenceAgeMillis,
84
- configuration: S.configuration(flow),
85
- });
86
-
87
- const retain = Effect.fn("oauthConnected.retain")(function* (
88
- mapping: S.Mapping,
89
- input:
90
- | ({ readonly _tag: "Prepare" } & Input<"prepare">)
91
- | ({ readonly _tag: "Issue" } & Input<"issue">),
92
- ) {
93
- const owner = yield* CurrentOAuthTransaction;
94
-
95
- const flow = snapshotOAuthSync(M.OAuthConnectedPendingFlow, input.flow),
96
- c = flow.context;
97
-
98
- const found = yield* S.current(mapping, c.revision.subjectId);
99
-
100
- if (found === undefined) return { _tag: "Rejected" } as const;
101
- if (input._tag === "Prepare") {
102
- if (
103
- input.authorization.moduleId !== c.moduleId ||
104
- !sameRevision(input.authorization.revision, c.revision) ||
105
- !sameRevision(found.revision, c.revision) ||
106
- (yield* S.useAuthority(mapping, input.authorization, "metadata")) === undefined
107
- )
108
- return { _tag: "Rejected" } as const;
109
- } else if (!(yield* S.action(mapping, found, input.authorization, "issue", expected(flow))))
110
- return { _tag: "Rejected" } as const;
111
- if (c.profile.revocation === "cohort") invariant(mapping.revocation.mode === "cohort");
112
- const client = yield* S.client(mapping, S.configuration(flow), true);
113
-
114
- invariant(client !== undefined);
115
-
116
- const now = yield* owner.now(mapping.clock),
117
- f = mapping.flow;
118
-
119
- if (!live(flow, now)) return { _tag: "Rejected" } as const;
120
- if (c.reconnect !== undefined) {
121
- const original = yield* S.readGrant(mapping, c.moduleId, c.reconnect.grantId, false);
122
-
123
- if (
124
- original === undefined ||
125
- original.context.subjectId !== c.revision.subjectId ||
126
- !sameTarget(c.reconnect, original.context)
127
- )
128
- return { _tag: "Rejected" } as const;
129
- owner.postconditions.push(
130
- sql`exists(select 1 from ${mapping.grant.table} where ${owner.exact(mapping.grant.table, original.row)})`,
131
- );
132
- }
133
- const key = { [f.moduleId]: c.moduleId, [f.flowId]: c.flowId };
134
- const prior = yield* owner.read(f.table, equal(f.table, key), { limit: 1 });
135
-
136
- const command = yield* owner.read(
137
- f.table,
138
- equal(f.table, { [f.moduleId]: c.moduleId, [f.commandId]: c.commandId }),
139
- { limit: 1 },
140
- );
141
-
142
- owner.postconditions.push(
143
- sql`${mapping.clock.engineNowMillis} >= ${c.issuedAtMillis} and ${mapping.clock.engineNowMillis} < ${c.expiresAtMillis}`,
144
- );
145
- if (input._tag === "Issue") {
146
- const row = prior.rows[0];
147
-
148
- if (
149
- row === undefined ||
150
- command.rows[0] === undefined ||
151
- row[f.state] !== "Prepared" ||
152
- row[f.snapshot] !== S.flowStorage.encode(flow) ||
153
- command.rows[0][f.flowId] !== c.flowId
154
- )
155
- return { _tag: "Rejected" } as const;
156
- yield* owner.update(
157
- f.table,
158
- { ...key, [f.state]: "Prepared", [f.version]: row[f.version] },
159
- { [f.state]: "Pending", [f.version]: owner.marker },
160
- );
161
- owner.postconditions.push(
162
- sql`exists(select 1 from ${f.table} where ${equal(f.table, { ...key, [f.state]: "Pending", [f.version]: owner.marker })})`,
163
- );
164
-
165
- return { _tag: "Issued", flow } as const;
166
- }
167
- if (prior.rows.length || command.rows.length) return { _tag: "Rejected" } as const;
168
-
169
- const values = {
170
- ...f.encodeInsert({
171
- flow: snapshotOAuthSync(M.OAuthConnectedPendingFlow, flow),
172
- subjectId: S.nativeCopy(found.nativeId),
173
- }),
174
- ...key,
175
- [f.commandId]: c.commandId,
176
- [f.subjectId]: found.nativeId,
177
- [f.clientKey]: client.id,
178
- [f.cohortKey]: null,
179
- [f.state]: "Prepared",
180
- [f.version]: owner.marker,
181
- [f.stateDigest]: c.stateDigest,
182
- [f.snapshot]: S.flowStorage.encode(flow),
183
- [f.claimId]: null,
184
- [f.claimDigest]: null,
185
- [f.claimOrder]: null,
186
- [f.claimedAt]: null,
187
- [f.claimExpiresAt]: null,
188
- [f.expiresAt]: mapping.clock.encodeInstant(c.expiresAtMillis),
189
- [f.retentionUntil]: mapping.clock.encodeInstant(flow.retentionUntilMillis),
190
- [f.work]: "None",
191
- [f.custody]: null,
192
- };
193
-
194
- const inserted = yield* owner.insert(f.table, values, key, true);
195
-
196
- prior.rows = inserted.rows;
197
- command.rows = inserted.rows;
198
- if (inserted.rows[0]?.[f.version] !== owner.marker) return { _tag: "Rejected" } as const;
199
-
200
- return { _tag: "Issued", flow } as const;
201
- });
202
-
203
- export const prepare = (mapping: S.Mapping, input: Input<"prepare">) =>
204
- retain(mapping, { _tag: "Prepare", ...input });
205
-
206
- export const issue = (mapping: S.Mapping, input: Input<"issue">) =>
207
- retain(mapping, { _tag: "Issue", ...input });
208
-
209
- export const inspectPrepared = Effect.fn("oauthConnected.inspectPrepared")(function* (
210
- mapping: S.Mapping,
211
- access: M.OAuthConnectedPreparedAccess,
212
- ) {
213
- const owner = yield* CurrentOAuthTransaction;
214
- const found = yield* S.current(mapping, access.subjectId);
215
-
216
- if (found === undefined) return undefined;
217
- const f = mapping.flow;
218
-
219
- const read = yield* owner.read(
220
- f.table,
221
- equal(f.table, {
222
- [f.moduleId]: access.moduleId,
223
- [f.flowId]: access.flowId,
224
- [f.subjectId]: found.nativeId,
225
- }),
226
- { limit: 1 },
227
- );
228
-
229
- const row = read.rows[0];
230
-
231
- if (row === undefined || row[f.state] !== "Prepared" || row[f.snapshot] === null)
232
- return undefined;
233
- const flow = S.flowStorage.decode(row[f.snapshot]);
234
- const c = flow.context;
235
-
236
- invariant(
237
- row[f.moduleId] === c.moduleId &&
238
- row[f.flowId] === c.flowId &&
239
- row[f.commandId] === c.commandId &&
240
- mapping.subjectId.equals(found.nativeId, row[f.subjectId]) &&
241
- row[f.clientKey] === (yield* S.clientKey(S.configuration(flow))) &&
242
- row[f.stateDigest] === c.stateDigest &&
243
- mapping.clock.decodeInstant(row[f.expiresAt]) === c.expiresAtMillis &&
244
- mapping.clock.decodeInstant(row[f.retentionUntil]) === flow.retentionUntilMillis,
245
- );
246
- const now = yield* owner.now(mapping.clock);
247
-
248
- return c.moduleId === access.moduleId &&
249
- c.generation === access.generation &&
250
- c.flowId === access.flowId &&
251
- c.commandId === access.commandId &&
252
- c.revision.subjectId === access.subjectId &&
253
- sameRevision(found.revision, c.revision) &&
254
- c.requestBindingVerifier === access.requestBindingVerifier &&
255
- c.requestBindingExpiresAtMillis === access.requestBindingExpiresAtMillis &&
256
- live(flow, now)
257
- ? flow
258
- : undefined;
259
- });
260
-
261
- export const sameTarget = (target: M.OAuthConnectedTarget, context: M.OAuthConnectedTokenContext) =>
262
- target.grantId === context.grantId &&
263
- target.grantVersion === context.grantVersion &&
264
- target.tokenVersion === context.tokenVersion &&
265
- target.cohortGeneration === context.cohortGeneration &&
266
- sameIdentity(target.identity, context.identity) &&
267
- S.tokenContextStorage.encode({ ...context, configuration: target.configuration }) ===
268
- S.tokenContextStorage.encode(context);
269
-
270
- export const capture = Effect.fn("oauthConnected.capture")(function* (
271
- mapping: S.Mapping,
272
- input: Input<"capture">,
273
- ) {
274
- const found = yield* S.current(mapping, input.subjectId);
275
-
276
- if (found === undefined) return undefined;
277
- if (input.grantId === undefined)
278
- return { revision: snapshotOAuthSync(OAuthAccountRevision, found.revision) };
279
- const grant = yield* S.readGrant(mapping, input.moduleId, input.grantId, false);
280
-
281
- if (grant === undefined || grant.context.subjectId !== input.subjectId) return undefined;
282
-
283
- return {
284
- revision: snapshotOAuthSync(OAuthAccountRevision, found.revision),
285
- target: snapshotOAuthSync(M.OAuthConnectedTarget, grant.context),
286
- };
287
- });
288
-
289
- export const preflight = Effect.fn("oauthConnected.preflight")(function* (
290
- mapping: S.Mapping,
291
- input: Input<"preflight">,
292
- ) {
293
- const owner = yield* CurrentOAuthTransaction;
294
- const read = yield* pending(mapping, input);
295
- const now = yield* owner.now(mapping.clock);
296
-
297
- if (read === undefined || read.row[mapping.flow.state] !== "Pending" || !live(read.flow, now))
298
- return undefined;
299
-
300
- return read.flow;
301
- });
302
-
303
- export const claim = Effect.fn("oauthConnected.claim")(function* (
304
- mapping: S.Mapping,
305
- input: Input<"claim">,
306
- ) {
307
- const owner = yield* CurrentOAuthTransaction;
308
-
309
- const flow = snapshotOAuthSync(M.OAuthConnectedPendingFlow, input.flow),
310
- c = flow.context;
311
-
312
- const found = yield* S.current(mapping, c.revision.subjectId);
313
-
314
- if (
315
- found === undefined ||
316
- !(yield* S.action(mapping, found, input.authorization, "claim", expected(flow)))
317
- )
318
- return { _tag: "Rejected" } as const;
319
- const client = yield* S.client(mapping, S.configuration(flow), false);
320
-
321
- if (client === undefined) return { _tag: "Rejected" } as const;
322
-
323
- const read = yield* pending(mapping, input.access),
324
- now = yield* owner.now(mapping.clock),
325
- f = mapping.flow;
326
-
327
- if (
328
- read === undefined ||
329
- read.row[f.state] !== "Pending" ||
330
- S.flowStorage.encode(read.flow) !== S.flowStorage.encode(flow) ||
331
- !live(flow, now)
332
- )
333
- return { _tag: "Rejected" } as const;
334
- const expires = now + c.claimLifetimeMillis;
335
-
336
- invariant(Number.isSafeInteger(expires) && expires <= flow.retentionUntilMillis);
337
- yield* S.touchScope(mapping, client.scope);
338
- const order = yield* S.nextOrder(mapping, client);
339
-
340
- const claimed = snapshotOAuthSync(M.OAuthConnectedClaim, {
341
- flow,
342
- claimId: input.claimId,
343
- claimedAtMillis: now,
344
- claimExpiresAtMillis: expires,
345
- order: String(order),
346
- });
347
-
348
- yield* owner.update(
349
- f.table,
350
- {
351
- [f.moduleId]: c.moduleId,
352
- [f.flowId]: c.flowId,
353
- [f.state]: "Pending",
354
- [f.version]: read.row[f.version],
355
- },
356
- {
357
- [f.state]: "Claimed",
358
- [f.claimId]: claimed.claimId,
359
- [f.claimDigest]: yield* digest(S.claimStorage.encode(claimed)),
360
- [f.claimOrder]: mapping.order.encode(order),
361
- [f.claimedAt]: mapping.clock.encodeInstant(now),
362
- [f.claimExpiresAt]: mapping.clock.encodeInstant(expires),
363
- [f.work]: "Unresolved",
364
- [f.version]: owner.marker,
365
- },
366
- );
367
- owner.postconditions.push(
368
- sql`${mapping.clock.engineNowMillis} >= ${now} and ${mapping.clock.engineNowMillis} < ${Math.min(c.expiresAtMillis, expires)}`,
369
- );
370
-
371
- return { _tag: "Claimed", claim: claimed } as const;
372
- });
373
-
374
- export const exact = Effect.fn("oauthConnected.exactClaim")(function* (
375
- mapping: S.Authority,
376
- claim: M.OAuthConnectedClaim,
377
- ) {
378
- const owner = yield* CurrentOAuthTransaction;
379
-
380
- const f = mapping.flow,
381
- c = claim.flow.context;
382
-
383
- const read = yield* owner.read(
384
- f.table,
385
- equal(f.table, { [f.moduleId]: c.moduleId, [f.flowId]: c.flowId }),
386
- { limit: 1 },
387
- );
388
-
389
- const row = read.rows[0];
390
-
391
- if (
392
- row === undefined ||
393
- row[f.work] !== "Unresolved" ||
394
- row[f.claimId] !== claim.claimId ||
395
- row[f.claimDigest] !== (yield* digest(S.claimStorage.encode(claim)))
396
- )
397
- return undefined;
398
- const native = yield* mapping.subjectId.toNative(c.revision.subjectId);
399
-
400
- invariant(
401
- row[f.commandId] === c.commandId &&
402
- mapping.subjectId.equals(native, row[f.subjectId]) &&
403
- row[f.stateDigest] === c.stateDigest &&
404
- mapping.clock.decodeInstant(row[f.expiresAt]) === c.expiresAtMillis &&
405
- mapping.clock.decodeInstant(row[f.retentionUntil]) === claim.flow.retentionUntilMillis &&
406
- row[f.clientKey] === (yield* S.clientKey(S.configuration(claim.flow))) &&
407
- S.nativeOrder(mapping, row[f.claimOrder]) === S.orderNumber(claim.order) &&
408
- mapping.clock.decodeInstant(row[f.claimedAt]) === claim.claimedAtMillis &&
409
- mapping.clock.decodeInstant(row[f.claimExpiresAt]) === claim.claimExpiresAtMillis,
410
- );
411
- const now = yield* owner.now(mapping.clock);
412
-
413
- invariant(now >= claim.claimedAtMillis);
414
-
415
- return { row, now, active: row[f.state] === "Claimed" && now < claim.claimExpiresAtMillis };
416
- });
417
-
418
- export const terminal = Effect.fn("oauthConnected.terminal")(function* (
419
- mapping: S.Authority,
420
- claim: M.OAuthConnectedClaim,
421
- state: string,
422
- work: "Resolved" | "Unresolved",
423
- custody?: { cohort: string; sealed?: M.OAuthConnectedStoredGrant },
424
- ) {
425
- const owner = yield* CurrentOAuthTransaction;
426
-
427
- const f = mapping.flow,
428
- c = claim.flow.context;
429
-
430
- yield* owner.update(
431
- f.table,
432
- { [f.moduleId]: c.moduleId, [f.flowId]: c.flowId, [f.claimId]: claim.claimId },
433
- {
434
- [f.state]: state,
435
- [f.snapshot]: null,
436
- [f.work]: work,
437
- [f.cohortKey]: custody?.cohort ?? null,
438
- [f.custody]: custody?.sealed === undefined ? null : S.grantStorage.encode(custody.sealed),
439
- [f.version]: owner.marker,
440
- },
441
- );
442
- });
443
-
444
- export const inspectTuple = Effect.fn("oauthConnected.inspectTuple")(function* (
445
- mapping: S.Authority,
446
- identity: M.OAuthConnectedTokenContext["identity"],
447
- ) {
448
- const owner = yield* CurrentOAuthTransaction;
449
-
450
- const t = mapping.ownership.tuple,
451
- key = yield* oauthIdentityKey(identity);
452
-
453
- const read = yield* owner.read(t.table, equal(t.table, { [t.identityKey]: key }), { limit: 1 });
454
- const row = read.rows[0];
455
-
456
- if (row !== undefined) {
457
- invariant(
458
- row[t.provider] === identity.provider &&
459
- row[t.issuer] === identity.issuer &&
460
- row[t.externalSubject] === identity.subject &&
461
- ["Owned", "Unowned", "Reserved"].includes(row[t.state]),
462
- );
463
- }
464
- if (mapping.ownership.mode === "separate") {
465
- const o = mapping.ownership.external;
466
-
467
- const external = yield* owner.read(o.table, equal(o.table, { [o.identityKey]: key }), {
468
- limit: 1,
469
- });
470
-
471
- if (row?.[t.state] === "Owned") {
472
- const e = external.rows[0];
473
-
474
- invariant(
475
- e !== undefined &&
476
- e[o.provider] === identity.provider &&
477
- e[o.issuer] === identity.issuer &&
478
- e[o.externalSubject] === identity.subject &&
479
- mapping.subjectId.equals(o.decodeSubjectId(copiedRow(e)), row[t.subjectId]),
480
- );
481
- const condition = sql`exists(select 1 from ${o.table} where ${both(equal(o.table, { [o.identityKey]: key }), o.ownedCondition)})`;
482
-
483
- invariant(yield* owner.check(condition));
484
- owner.postconditions.push(condition);
485
- } else invariant(external.rows.length === 0);
486
- }
487
-
488
- return { key, row };
489
- });
490
-
491
- export const inspectGrant = Effect.fn("oauthConnected.inspectGrant")(function* (
492
- mapping: S.Mapping,
493
- input: Input<"inspectGrant">,
494
- ) {
495
- const c = input.claim.flow.context;
496
-
497
- if (input.identity.provider !== c.provider || input.identity.issuer !== c.issuer)
498
- return { _tag: "Rejected" } as const;
499
- const found = yield* S.current(mapping, c.revision.subjectId);
500
-
501
- if (found === undefined || !sameRevision(found.revision, c.revision))
502
- return { _tag: "Rejected" } as const;
503
- const cl = yield* S.client(mapping, S.configuration(input.claim.flow), false);
504
-
505
- if (cl === undefined) return { _tag: "Rejected" } as const;
506
-
507
- const tuple = yield* inspectTuple(mapping, input.identity),
508
- t = mapping.ownership.tuple;
509
-
510
- if (
511
- tuple.row?.[t.state] === "Reserved" ||
512
- (tuple.row?.[t.state] === "Owned" &&
513
- !mapping.subjectId.equals(tuple.row[t.subjectId], found.nativeId))
514
- )
515
- return { _tag: "Conflict" } as const;
516
- const co = yield* S.cohort(mapping, cl.id, tuple.key, false);
517
- const claimed = yield* exact(mapping, input.claim);
518
-
519
- if (claimed === undefined) return { _tag: "Rejected" } as const;
520
- if (c.reconnect !== undefined && !sameIdentity(c.reconnect.identity, input.identity))
521
- return { _tag: "Conflict" } as const;
522
-
523
- return co.blocked || S.orderNumber(input.claim.order) <= co.cutoff || !claimed.active
524
- ? ({ _tag: "Quarantine", cohortGeneration: co.generation } as const)
525
- : ({ _tag: "Target", cohortGeneration: co.generation } as const);
526
- });
527
-
528
- /** Global tuple acquisition is shared with registration/accounts. */
529
- export { acquireTuple, readTuple };
@@ -1,147 +0,0 @@
1
- import * as M from "@yielded/auth/OAuth";
2
- import {
3
- OAuthConnectedRevocationClaim,
4
- OAuthClaimId,
5
- OAuthCleanupInput,
6
- OAuthCommandId,
7
- OAuthExternalIdentity,
8
- OAuthInstant,
9
- OAuthModuleId,
10
- snapshotOAuthSync,
11
- } from "@yielded/auth/OAuth";
12
- import { Schema } from "effect";
13
-
14
- const capture = Schema.Struct({
15
- moduleId: OAuthModuleId,
16
- subjectId: M.OAuthConnectedUseAuthorization.fields.revision.fields.subjectId,
17
- grantId: Schema.optionalKey(M.OAuthGrantId),
18
- });
19
-
20
- const issue = Schema.Struct({
21
- flow: M.OAuthConnectedPendingFlow,
22
- authorization: M.OAuthConnectedActionAuthorization,
23
- });
24
-
25
- const preparation = Schema.Struct({
26
- flow: M.OAuthConnectedPendingFlow,
27
- authorization: M.OAuthConnectedUseAuthorization,
28
- });
29
-
30
- const claim = Schema.Struct({
31
- ...issue.fields,
32
- access: M.OAuthConnectedAccess,
33
- claimId: OAuthClaimId,
34
- });
35
-
36
- const inspectGrant = Schema.Struct({
37
- claim: M.OAuthConnectedClaim,
38
- identity: OAuthExternalIdentity,
39
- });
40
-
41
- const settle = Schema.Struct({
42
- claim: M.OAuthConnectedClaim,
43
- outcome: M.OAuthConnectedOutcome,
44
- authorization: M.OAuthConnectedActionAuthorization,
45
- nowMillis: OAuthInstant,
46
- });
47
-
48
- const list = Schema.Struct({
49
- ...M.OAuthConnectedList.fields,
50
- authorization: M.OAuthConnectedUseAuthorization,
51
- });
52
-
53
- const inspectDisconnect = Schema.Struct({
54
- moduleId: OAuthModuleId,
55
- subjectId: capture.fields.subjectId,
56
- authorization: M.OAuthConnectedUseAuthorization,
57
- commandId: OAuthCommandId,
58
- grantId: M.OAuthGrantId,
59
- });
60
-
61
- const disconnect = Schema.Struct({
62
- moduleId: OAuthModuleId,
63
- commandId: OAuthCommandId,
64
- grant: M.OAuthConnectedDisconnectGrant,
65
- authorization: M.OAuthConnectedActionAuthorization,
66
- revocation: Schema.optionalKey(M.OAuthConnectedRevocationJob),
67
- retentionUntilMillis: OAuthInstant,
68
- });
69
-
70
- const inspectAccess = Schema.Struct({
71
- authorization: M.OAuthConnectedUseAuthorization,
72
- ...M.OAuthConnectedUse.fields,
73
- });
74
-
75
- const claimRefresh = Schema.Struct({
76
- grant: M.OAuthConnectedStoredGrant,
77
- authorization: M.OAuthConnectedUseAuthorization,
78
- claimId: OAuthClaimId,
79
- nextTokenVersion: M.OAuthConnectedRefreshClaim.fields.nextTokenVersion,
80
- lifetimeMillis: M.OAuthConnectedPolicy.fields.refreshClaimLifetimeMillis,
81
- });
82
-
83
- const settleRefresh = Schema.Struct({
84
- claim: M.OAuthConnectedRefreshClaim,
85
- authorization: M.OAuthConnectedUseAuthorization,
86
- outcome: M.OAuthConnectedRefreshOutcome,
87
- });
88
-
89
- const admitUse = Schema.Struct({
90
- grant: M.OAuthConnectedStoredGrant,
91
- authorization: M.OAuthConnectedUseAuthorization,
92
- admissionId: OAuthClaimId,
93
- lifetimeMillis: M.OAuthConnectedPolicy.fields.useAdmissionLifetimeMillis,
94
- });
95
-
96
- const revocationClaim = Schema.Struct({
97
- moduleId: OAuthModuleId,
98
- claimId: OAuthClaimId,
99
- lifetimeMillis: M.OAuthConnectedPolicy.fields.refreshClaimLifetimeMillis,
100
- });
101
-
102
- const revocationSettle = Schema.Struct({
103
- claim: OAuthConnectedRevocationClaim,
104
- outcome: Schema.Literals(["Confirmed", "Unknown"]),
105
- });
106
-
107
- const snapshot =
108
- <A, I>(schema: Schema.Codec<A, I, never, never>) =>
109
- (input: A) =>
110
- snapshotOAuthSync(schema, input);
111
-
112
- export const connectedInputs = {
113
- claimSignIn: snapshot(
114
- Schema.Struct({ claim: M.OAuthClaim, configuration: M.OAuthConnectedConfiguration }),
115
- ),
116
- inspectSignIn: snapshot(
117
- Schema.Struct({
118
- reservation: M.OAuthSignInAccessClaim,
119
- credential: M.OAuthCredentialSnapshot,
120
- grantId: M.OAuthGrantId,
121
- }),
122
- ),
123
- settleSignIn: snapshot(
124
- Schema.Struct({ reservation: M.OAuthSignInAccessClaim, outcome: M.OAuthSignInAccessOutcome }),
125
- ),
126
- capture: snapshot(capture),
127
- issue: snapshot(issue),
128
- prepare: snapshot(preparation),
129
- inspectPrepared: snapshot(M.OAuthConnectedPreparedAccess),
130
- preflight: snapshot(M.OAuthConnectedAccess),
131
- claim: snapshot(claim),
132
- inspectGrant: snapshot(inspectGrant),
133
- settle: snapshot(settle),
134
- list: snapshot(list),
135
- inspectDisconnect: snapshot(inspectDisconnect),
136
- disconnect: snapshot(disconnect),
137
- inspectAccess: snapshot(inspectAccess),
138
- claimRefresh: snapshot(claimRefresh),
139
- settleRefresh: snapshot(settleRefresh),
140
- admitUse: snapshot(admitUse),
141
- cleanup: snapshot(OAuthCleanupInput),
142
- };
143
-
144
- export const connectedRevocationInputs = {
145
- claim: snapshot(revocationClaim),
146
- settle: snapshot(revocationSettle),
147
- };