@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,462 +0,0 @@
1
- import {
2
- type OAuthLinkClaim,
3
- type OAuthClaim,
4
- OAuthCleanupInput,
5
- OAuthCredentialSnapshot,
6
- OAuthPendingFlow,
7
- OAuthModuleId,
8
- type OAuthVerifiedExternalIdentity,
9
- OAuthAccountRevision,
10
- OAuthLinkPendingFlow,
11
- snapshotOAuthSync,
12
- } from "@yielded/auth/OAuth";
13
- /* oxlint-disable no-explicit-any -- private kernel shared by concrete driver mappings. */
14
- import { eq, sql } from "drizzle-orm";
15
- import { Effect } from "effect";
16
-
17
- import type { SubjectIdCodec } from "./model";
18
- import { CurrentOAuthTransaction, type Row, both, col, copiedRow, equal } from "./oauth-owner";
19
- import { invariant, oauthIdentityKey, storage } from "./oauth-state";
20
-
21
- export const pendingStorage = storage(OAuthPendingFlow);
22
- export const linkStorage = storage(OAuthLinkPendingFlow);
23
- export const credentialStorage = storage(OAuthCredentialSnapshot);
24
- export const revisionStorage = storage(OAuthAccountRevision);
25
- export type Flow = typeof OAuthPendingFlow.Type | typeof OAuthLinkPendingFlow.Type;
26
- export type Claim = typeof OAuthClaim.Type | typeof OAuthLinkClaim.Type;
27
-
28
- export const flowKey = (mapping: any, context: Flow["context"]) => ({
29
- [mapping.moduleId]: context.moduleId,
30
- [mapping.flowId]: context.flowId,
31
- });
32
-
33
- export const flowStorage = (purpose: "sign-in" | "link") =>
34
- purpose === "link" ? linkStorage : pendingStorage;
35
-
36
- const instant = (mapping: any, value: unknown): number => {
37
- const millis = mapping.clock.decodeInstant(value);
38
-
39
- invariant(Number.isSafeInteger(millis) && millis >= 0);
40
-
41
- return millis;
42
- };
43
-
44
- export const readFlow = (
45
- mapping: any,
46
- input: { readonly moduleId: string; readonly flowId: string },
47
- purpose: "sign-in" | "link",
48
- ) =>
49
- Effect.flatMap(CurrentOAuthTransaction, (owner) =>
50
- Effect.gen(function* () {
51
- const f = mapping.flow;
52
-
53
- const observation = yield* owner.read(
54
- f.table,
55
- equal(f.table, {
56
- [f.moduleId]: input.moduleId,
57
- [f.flowId]: input.flowId,
58
- }),
59
- { limit: 1 },
60
- );
61
-
62
- const row = observation.rows[0];
63
-
64
- if (row === undefined || row[f.purpose] !== purpose || row[f.snapshot] === null)
65
- return { observation };
66
- const flow = flowStorage(purpose).decode(row[f.snapshot]) as Flow;
67
- const c = flow.context;
68
-
69
- invariant(
70
- row[f.moduleId] === c.moduleId &&
71
- row[f.flowId] === c.flowId &&
72
- row[f.commandId] === c.commandId &&
73
- row[f.generation] === c.generation &&
74
- row[f.stateDigest] === c.stateDigest &&
75
- row[f.binderVerifier] === c.requestBindingVerifier &&
76
- instant(mapping, row[f.binderExpiresAt]) === c.requestBindingExpiresAtMillis &&
77
- instant(mapping, row[f.issuedAt]) === c.issuedAtMillis &&
78
- instant(mapping, row[f.expiresAt]) === c.expiresAtMillis &&
79
- instant(mapping, row[f.retentionUntil]) === flow.retentionUntilMillis,
80
- );
81
-
82
- return { observation, row, flow };
83
- }),
84
- );
85
-
86
- export const liveFlow = (flow: Flow, now: number) =>
87
- flow.context.issuedAtMillis <= now &&
88
- now < flow.context.expiresAtMillis &&
89
- flow.context.expiresAtMillis <= flow.context.requestBindingExpiresAtMillis &&
90
- flow.retentionUntilMillis >= flow.context.expiresAtMillis + flow.context.claimLifetimeMillis;
91
-
92
- export const issueFlow = (mapping: any, original: Flow, purpose: "sign-in" | "link") =>
93
- Effect.flatMap(CurrentOAuthTransaction, (owner) =>
94
- Effect.gen(function* () {
95
- const codec = flowStorage(purpose);
96
- const flow = codec.decode(codec.encode(original as never)) as Flow;
97
-
98
- const f = mapping.flow,
99
- c = flow.context,
100
- now = yield* owner.now(mapping.clock);
101
-
102
- if (!liveFlow(flow, now)) return { _tag: "Rejected" } as const;
103
- const byFlow = yield* owner.read(f.table, equal(f.table, flowKey(f, c)), { limit: 1 });
104
-
105
- const byCommand = yield* owner.read(
106
- f.table,
107
- equal(f.table, {
108
- [f.moduleId]: c.moduleId,
109
- [f.commandId]: c.commandId,
110
- }),
111
- { limit: 1 },
112
- );
113
-
114
- if (byFlow.rows.length || byCommand.rows.length) return { _tag: "Rejected" } as const;
115
- const encode = mapping.clock.encodeInstant;
116
-
117
- const values = {
118
- ...f.encodeInsert({ moduleId: c.moduleId, flowId: c.flowId, purpose }),
119
- ...flowKey(f, c),
120
- [f.commandId]: c.commandId,
121
- [f.purpose]: purpose,
122
- [f.generation]: c.generation,
123
- [f.state]: "Pending",
124
- [f.version]: owner.marker,
125
- [f.stateDigest]: c.stateDigest,
126
- [f.binderVerifier]: c.requestBindingVerifier,
127
- [f.binderExpiresAt]: encode(c.requestBindingExpiresAtMillis),
128
- [f.snapshot]: codec.encode(flow as never),
129
- [f.issuedAt]: encode(c.issuedAtMillis),
130
- [f.expiresAt]: encode(c.expiresAtMillis),
131
- [f.claimId]: null,
132
- [f.claimedAt]: null,
133
- [f.claimExpiresAt]: null,
134
- [f.retentionUntil]: encode(flow.retentionUntilMillis),
135
- };
136
-
137
- const inserted = yield* owner.insert(f.table, values, flowKey(f, c), true);
138
-
139
- byFlow.rows = inserted.rows;
140
- byCommand.rows = inserted.rows;
141
- if (inserted.rows[0]?.[f.version] !== owner.marker) return { _tag: "Rejected" } as const;
142
- owner.postconditions.push(sql`${mapping.clock.engineNowMillis} >= ${c.issuedAtMillis}
143
- and ${mapping.clock.engineNowMillis} < ${c.expiresAtMillis}`);
144
-
145
- return { _tag: "Issued", flow } as const;
146
- }),
147
- );
148
-
149
- export const matchesAccess = (flow: Flow, input: any) => {
150
- const c = flow.context;
151
-
152
- return (
153
- c.moduleId === input.moduleId &&
154
- c.generation === input.generation &&
155
- c.flowId === input.flowId &&
156
- c.provider === input.provider &&
157
- c.callbackId === input.callbackId &&
158
- c.stateDigest === input.stateDigest &&
159
- c.requestBindingVerifier === input.requestBindingVerifier &&
160
- c.requestBindingExpiresAtMillis === input.requestBindingExpiresAtMillis &&
161
- (c.responseIssuerMode === "required"
162
- ? input.responseIssuer === c.issuer
163
- : input.responseIssuer === undefined)
164
- );
165
- };
166
-
167
- export const claimFlow = (mapping: any, inspected: { row: Row; flow: Flow }, claimId: string) =>
168
- Effect.flatMap(CurrentOAuthTransaction, (owner) =>
169
- Effect.gen(function* () {
170
- const now = yield* owner.now(mapping.clock),
171
- flow = inspected.flow,
172
- c = flow.context;
173
-
174
- if (inspected.row[mapping.flow.state] !== "Pending" || !liveFlow(flow, now)) return undefined;
175
-
176
- const claim = {
177
- flow,
178
- claimId,
179
- claimedAtMillis: now,
180
- claimExpiresAtMillis: now + c.claimLifetimeMillis,
181
- };
182
-
183
- invariant(
184
- Number.isSafeInteger(claim.claimExpiresAtMillis) &&
185
- claim.claimExpiresAtMillis <= flow.retentionUntilMillis,
186
- );
187
-
188
- const f = mapping.flow,
189
- encode = mapping.clock.encodeInstant;
190
-
191
- yield* owner.update(f.table, flowKey(f, c), {
192
- [f.state]: "Claimed",
193
- [f.version]: owner.marker,
194
- [f.claimId]: claimId,
195
- [f.claimedAt]: encode(now),
196
- [f.claimExpiresAt]: encode(claim.claimExpiresAtMillis),
197
- });
198
- owner.postconditions.push(sql`${mapping.clock.engineNowMillis} >= ${now} and
199
- ${mapping.clock.engineNowMillis} < ${Math.min(c.expiresAtMillis, claim.claimExpiresAtMillis)}`);
200
-
201
- return claim;
202
- }),
203
- );
204
-
205
- export const exactClaim = (mapping: any, claim: Claim, purpose: "sign-in" | "link") =>
206
- Effect.flatMap(CurrentOAuthTransaction, (owner) =>
207
- Effect.gen(function* () {
208
- const read = yield* readFlow(mapping, claim.flow.context, purpose);
209
-
210
- const now = yield* owner.now(mapping.clock),
211
- f = mapping.flow,
212
- row = read.row;
213
-
214
- if (
215
- row === undefined ||
216
- read.flow === undefined ||
217
- row[f.state] !== "Claimed" ||
218
- row[f.claimId] !== claim.claimId ||
219
- instant(mapping, row[f.claimedAt]) !== claim.claimedAtMillis ||
220
- instant(mapping, row[f.claimExpiresAt]) !== claim.claimExpiresAtMillis ||
221
- claim.claimExpiresAtMillis !==
222
- claim.claimedAtMillis + claim.flow.context.claimLifetimeMillis ||
223
- now < claim.claimedAtMillis ||
224
- now >= claim.claimExpiresAtMillis ||
225
- flowStorage(purpose).encode(read.flow as never) !==
226
- flowStorage(purpose).encode(claim.flow as never)
227
- )
228
- return undefined;
229
- owner.postconditions.push(sql`${mapping.clock.engineNowMillis} >= ${claim.claimedAtMillis} and
230
- ${mapping.clock.engineNowMillis} < ${claim.claimExpiresAtMillis}`);
231
-
232
- return { ...read, now };
233
- }),
234
- );
235
-
236
- export const terminalFlow = (mapping: any, flow: Flow, state: string) =>
237
- Effect.flatMap(CurrentOAuthTransaction, (owner) =>
238
- owner.update(mapping.flow.table, flowKey(mapping.flow, flow.context), {
239
- [mapping.flow.state]: state,
240
- [mapping.flow.version]: owner.marker,
241
- [mapping.flow.snapshot]: null,
242
- }),
243
- );
244
-
245
- /** Subject precedes credentials; the complete active credential vector is retained. */
246
- export const currentSubject = (mapping: any, nativeId: unknown) =>
247
- Effect.flatMap(CurrentOAuthTransaction, (owner) =>
248
- Effect.gen(function* () {
249
- const s = mapping.subject,
250
- a = mapping.authority;
251
-
252
- const subject = yield* owner.read(s.table, eq(col(s.table, s.id), nativeId), { limit: 1 });
253
- const row = subject.rows[0];
254
- const subjectActive = sql`exists(select 1 from ${s.table} where ${both(eq(col(s.table, s.id), nativeId), s.activeCondition)})`;
255
-
256
- if (
257
- row === undefined ||
258
- !s.isActiveStatus(row[s.status]) ||
259
- !(yield* owner.check(subjectActive))
260
- )
261
- return undefined;
262
- owner.postconditions.push(subjectActive);
263
-
264
- const authorities = yield* owner.read(a.table, eq(col(a.table, a.subjectId), nativeId), {
265
- orderBy: col(a.table, a.credentialId),
266
- });
267
-
268
- const active = authorities.rows.filter((item) => a.isActiveStatus(item[a.status]));
269
-
270
- const activeGuard = sql`(select count(*) from ${a.table} where ${both(
271
- eq(col(a.table, a.subjectId), nativeId),
272
- a.activeCondition,
273
- )}) = ${active.length}`;
274
-
275
- invariant(yield* owner.check(activeGuard));
276
- owner.postconditions.push(
277
- () => sql`(select count(*) from ${a.table} where ${both(
278
- eq(col(a.table, a.subjectId), nativeId),
279
- a.activeCondition,
280
- )}) =
281
- ${authorities.rows.filter((item) => a.isActiveStatus(item[a.status])).length}`,
282
- );
283
-
284
- const credentials = active
285
- .map((item) => ({ credentialId: item[a.credentialId], revision: item[a.revision] }))
286
- .sort((left, right) => left.credentialId.localeCompare(right.credentialId));
287
-
288
- invariant(new Set(credentials.map((item) => item.credentialId)).size === credentials.length);
289
- const subjectId = yield* (mapping.subjectId as SubjectIdCodec<unknown>).toSubject(nativeId);
290
-
291
- const revision = snapshotOAuthSync(OAuthAccountRevision, {
292
- subjectId,
293
- securityRevision: row[s.securityRevision],
294
- credentials,
295
- });
296
-
297
- return { subject, row, authorities, revision, nativeId };
298
- }),
299
- );
300
-
301
- export const discoverOwned = (mapping: any, identity: OAuthVerifiedExternalIdentity["identity"]) =>
302
- Effect.flatMap(CurrentOAuthTransaction, (owner) =>
303
- Effect.gen(function* () {
304
- const key = yield* oauthIdentityKey(identity),
305
- o = mapping.ownership;
306
-
307
- const where = both(eq(col(o.table, o.identityKey), key), o.ownedCondition);
308
-
309
- const discovered = yield* owner.read(o.table, where, {
310
- lock: false,
311
- observe: false,
312
- limit: 1,
313
- });
314
-
315
- const row = discovered.rows[0];
316
-
317
- if (row === undefined) return { key };
318
- invariant(
319
- row[o.provider] === identity.provider &&
320
- row[o.issuer] === identity.issuer &&
321
- row[o.externalSubject] === identity.subject,
322
- );
323
- const nativeId = o.decodeSubjectId(copiedRow(row));
324
- const current = yield* currentSubject(mapping, nativeId);
325
- const owned = yield* owner.read(o.table, where, { limit: 1 });
326
- const final = owned.rows[0];
327
-
328
- if (
329
- current === undefined ||
330
- final === undefined ||
331
- !mapping.subjectId.equals(nativeId, o.decodeSubjectId(copiedRow(final)))
332
- )
333
- return { key, owned: true as const };
334
- invariant(
335
- final[o.provider] === identity.provider &&
336
- final[o.issuer] === identity.issuer &&
337
- final[o.externalSubject] === identity.subject,
338
- );
339
- const c = mapping.credential;
340
-
341
- const credential = yield* owner.read(c.table, eq(col(c.table, c.identityKey), key), {
342
- limit: 1,
343
- });
344
-
345
- let login: Row | undefined = credential.rows[0];
346
-
347
- if (login !== undefined && c.isActiveStatus(login[c.status])) {
348
- const usable = sql`exists(select 1 from ${c.table} where ${both(
349
- eq(col(c.table, c.identityKey), key),
350
- c.activeCondition,
351
- )})`;
352
-
353
- if (yield* owner.check(usable)) owner.postconditions.push(usable);
354
- else login = undefined;
355
- }
356
-
357
- return { key, owned: true as const, current, credential, login };
358
- }),
359
- );
360
-
361
- export const resolveOwned = (
362
- mapping: any,
363
- moduleId: string,
364
- identity: OAuthVerifiedExternalIdentity["identity"],
365
- found: any,
366
- ) => {
367
- const login = found.login,
368
- current = found.current,
369
- c = mapping.credential;
370
-
371
- if (
372
- login === undefined ||
373
- current === undefined ||
374
- login[c.moduleId] !== moduleId ||
375
- !mapping.subjectId.equals(login[c.subjectId], current.nativeId) ||
376
- !c.isActiveStatus(login[c.status])
377
- )
378
- return undefined;
379
-
380
- const shared = current.revision.credentials.find(
381
- (item: any) => item.credentialId === login[c.credentialId],
382
- );
383
-
384
- if (shared?.revision !== login[c.credentialRevision]) return undefined;
385
-
386
- return snapshotOAuthSync(OAuthCredentialSnapshot, {
387
- moduleId: OAuthModuleId.make(moduleId),
388
- identity,
389
- credentialId: login[c.credentialId],
390
- credentialRevision: login[c.credentialRevision],
391
- revision: current.revision,
392
- });
393
- };
394
-
395
- export const cleanupFlows = (
396
- mapping: any,
397
- original: typeof OAuthCleanupInput.Type,
398
- purpose: "sign-in" | "link",
399
- ) =>
400
- Effect.flatMap(CurrentOAuthTransaction, (owner) =>
401
- Effect.gen(function* () {
402
- const input = snapshotOAuthSync(OAuthCleanupInput, original),
403
- f = mapping.flow;
404
-
405
- const now = yield* owner.now(mapping.clock);
406
-
407
- const candidates = yield* owner.read(
408
- f.table,
409
- both(
410
- eq(col(f.table, f.moduleId), input.moduleId),
411
- eq(col(f.table, f.purpose), purpose),
412
- sql`((${col(f.table, f.state)} = 'Pending' and ${col(f.table, f.expiresAt)} <= ${mapping.clock.encodeInstant(now)})
413
- or (${col(f.table, f.state)} = 'Claimed' and ${col(f.table, f.claimExpiresAt)} <= ${mapping.clock.encodeInstant(now)})
414
- or (${col(f.table, f.state)} not in ('Pending','Claimed') and ${col(f.table, f.retentionUntil)} < ${mapping.clock.encodeInstant(now)}))`,
415
- ),
416
- { limit: input.limit, observe: false, takeOnly: true },
417
- );
418
-
419
- let terminalized = 0,
420
- removed = 0;
421
-
422
- for (const candidate of candidates.rows) {
423
- const key = { [f.moduleId]: candidate[f.moduleId], [f.flowId]: candidate[f.flowId] };
424
- const current = yield* owner.read(f.table, equal(f.table, key), { limit: 1 });
425
-
426
- if (current.rows.length !== 1) continue;
427
-
428
- const row = current.rows[0]!,
429
- transitionNow = yield* owner.now(mapping.clock);
430
-
431
- const state = row[f.state];
432
-
433
- if (state === "Pending" || state === "Claimed") {
434
- const deadline = instant(
435
- mapping,
436
- row[state === "Pending" ? f.expiresAt : f.claimExpiresAt],
437
- );
438
-
439
- if (transitionNow < deadline) continue;
440
- yield* owner.update(f.table, key, {
441
- [f.state]: state === "Pending" ? "Rejected" : "Ambiguous",
442
- [f.snapshot]: null,
443
- [f.version]: owner.marker,
444
- });
445
- owner.postconditions.push(sql`${mapping.clock.engineNowMillis} >= ${deadline}`);
446
- terminalized++;
447
- } else {
448
- const retainUntil = Math.max(
449
- instant(mapping, row[f.retentionUntil]),
450
- row[f.claimExpiresAt] === null ? 0 : instant(mapping, row[f.claimExpiresAt]),
451
- );
452
-
453
- if (transitionNow <= retainUntil) continue;
454
- yield* owner.remove(f.table, key);
455
- owner.postconditions.push(sql`${mapping.clock.engineNowMillis} > ${retainUntil}`);
456
- removed++;
457
- }
458
- }
459
-
460
- return { terminalized, removed, hasMore: candidates.rows.length === input.limit };
461
- }),
462
- );
@@ -1,201 +0,0 @@
1
- import {
2
- OAuthAccountRevision,
3
- OAuthActionAuthorization,
4
- OAuthLinkAccess,
5
- OAuthLinkClaim,
6
- OAuthLinkOutcome,
7
- OAuthLinkPendingFlow,
8
- OAuthRegistrationAccess,
9
- OAuthRegistrationFingerprint,
10
- OAuthRegistrationIntent,
11
- OAuthClaim,
12
- OAuthClaimId,
13
- OAuthCleanupInput,
14
- OAuthCommandId,
15
- OAuthCredentialSnapshot,
16
- OAuthInstant,
17
- OAuthModuleId,
18
- OAuthPendingFlow,
19
- OAuthSignInTransactionContext,
20
- OAuthVerifiedExternalIdentity,
21
- snapshotOAuthSync,
22
- type OAuthUnavailable,
23
- } from "@yielded/auth/OAuth";
24
- import { SessionInvalidationWindow } from "@yielded/auth/Sessions";
25
- /* oxlint-disable no-explicit-any -- private adapter wraps only the Effect-valued semantic ports. */
26
- import { Effect, Schema } from "effect";
27
-
28
- import { invariant, unavailable } from "./oauth-state";
29
-
30
- const fields = OAuthSignInTransactionContext.fields;
31
-
32
- const claimInput = Schema.Struct({
33
- moduleId: fields.moduleId,
34
- generation: fields.generation,
35
- flowId: fields.flowId,
36
- provider: fields.provider,
37
- callbackId: fields.callbackId,
38
- stateDigest: fields.stateDigest,
39
- requestBindingVerifier: fields.requestBindingVerifier,
40
- requestBindingExpiresAtMillis: fields.requestBindingExpiresAtMillis,
41
- responseIssuer: Schema.optionalKey(fields.issuer),
42
- claimId: OAuthClaimId,
43
- nowMillis: OAuthInstant,
44
- });
45
-
46
- const signInOutcome = Schema.Union([
47
- Schema.TaggedStruct("Verified", { identity: OAuthVerifiedExternalIdentity }),
48
- Schema.TaggedStruct("Cancelled", {}),
49
- Schema.TaggedStruct("Rejected", {}),
50
- Schema.TaggedStruct("Ambiguous", {}),
51
- ]);
52
-
53
- const signInSettle = Schema.Struct({
54
- claim: OAuthClaim,
55
- outcome: signInOutcome,
56
- nowMillis: OAuthInstant,
57
- });
58
-
59
- const registrationSettle = Schema.Struct({
60
- claim: OAuthClaim,
61
- identity: OAuthVerifiedExternalIdentity,
62
- intent: Schema.optionalKey(OAuthRegistrationIntent),
63
- nowMillis: OAuthInstant,
64
- });
65
-
66
- const registerPrivate = Schema.Struct({
67
- access: OAuthRegistrationAccess,
68
- intent: OAuthRegistrationIntent,
69
- commandId: OAuthCommandId,
70
- fingerprint: OAuthRegistrationFingerprint,
71
- });
72
-
73
- const capture = Schema.Struct({
74
- moduleId: OAuthModuleId,
75
- subjectId: OAuthAccountRevision.fields.subjectId,
76
- });
77
-
78
- const linkIssue = Schema.Struct({
79
- flow: OAuthLinkPendingFlow,
80
- authorization: OAuthActionAuthorization,
81
- });
82
-
83
- const linkClaim = Schema.Struct({
84
- access: OAuthLinkAccess,
85
- flow: OAuthLinkPendingFlow,
86
- claimId: OAuthClaimId,
87
- authorization: OAuthActionAuthorization,
88
- });
89
-
90
- const linkSettle = Schema.Struct({
91
- claim: OAuthLinkClaim,
92
- outcome: OAuthLinkOutcome,
93
- authorization: OAuthActionAuthorization,
94
- invalidation: SessionInvalidationWindow,
95
- nowMillis: OAuthInstant,
96
- });
97
-
98
- const unlinkInspect = Schema.Struct({
99
- moduleId: OAuthModuleId,
100
- subjectId: OAuthAccountRevision.fields.subjectId,
101
- commandId: OAuthCommandId,
102
- credentialId: OAuthCredentialSnapshot.fields.credentialId,
103
- });
104
-
105
- const unlink = Schema.Struct({
106
- moduleId: OAuthModuleId,
107
- commandId: OAuthCommandId,
108
- credential: OAuthCredentialSnapshot,
109
- authorization: OAuthActionAuthorization,
110
- invalidation: SessionInvalidationWindow,
111
- nowMillis: OAuthInstant,
112
- retentionUntilMillis: OAuthInstant,
113
- });
114
-
115
- const snapshot = (schema: Schema.Codec<any, any, never, never>) => (input: any) =>
116
- snapshotOAuthSync(schema, input);
117
-
118
- export const signInInputs = {
119
- issue: snapshot(OAuthPendingFlow),
120
- claim: snapshot(claimInput),
121
- settle: snapshot(signInSettle),
122
- cleanup: snapshot(OAuthCleanupInput),
123
- };
124
-
125
- export const registrationIntentInputs = { settle: snapshot(registrationSettle) };
126
-
127
- const capturedApplication = (
128
- application: { readonly encode: (data: any) => string; readonly decode: (stored: string) => any },
129
- data: any,
130
- ) => {
131
- const encoded = application.encode(data);
132
-
133
- invariant(typeof encoded === "string" && new TextEncoder().encode(encoded).length <= 1048576);
134
- const retained = application.decode(encoded);
135
-
136
- invariant(application.encode(retained) === encoded);
137
-
138
- return retained;
139
- };
140
-
141
- export const registrationInputs = (application: {
142
- readonly encode: (data: any) => string;
143
- readonly decode: (stored: string) => any;
144
- }) => ({
145
- read: snapshot(OAuthRegistrationAccess),
146
- inspect: (input: any) => ({
147
- intent: snapshotOAuthSync(OAuthRegistrationIntent, input.intent),
148
- registration: capturedApplication(application, input.registration),
149
- }),
150
- register: (input: any) => ({
151
- ...snapshotOAuthSync(registerPrivate, input),
152
- registration: capturedApplication(application, input.registration),
153
- }),
154
- cleanup: snapshot(OAuthCleanupInput),
155
- });
156
-
157
- export const accountsInputs = {
158
- capture: snapshot(capture),
159
- issue: snapshot(linkIssue),
160
- preflight: snapshot(OAuthLinkAccess),
161
- claim: snapshot(linkClaim),
162
- settle: snapshot(linkSettle),
163
- inspectUnlink: snapshot(unlinkInspect),
164
- unlink: snapshot(unlink),
165
- cleanup: snapshot(OAuthCleanupInput),
166
- };
167
-
168
- /** Snapshot before the first asynchronous owner/allocator step; closed bound
169
- * authorities reject before running even an application codec or inspector. */
170
- export const capturedOAuthService = <
171
- S extends { [K in keyof S]: (...args: any[]) => Effect.Effect<any, OAuthUnavailable> },
172
- >(
173
- service: S,
174
- inputs: { readonly [K in keyof S]: (input: any) => any },
175
- active: () => boolean,
176
- poison: () => void,
177
- ): S =>
178
- Object.freeze(
179
- Object.fromEntries(
180
- Object.entries(service).map(([key, method]) => [
181
- key,
182
- (input: any, prepare?: any) =>
183
- Effect.suspend(() => {
184
- if (!active()) return Effect.fail(unavailable());
185
- const retained = inputs[key as keyof S](input);
186
-
187
- return (method as (...args: any[]) => Effect.Effect<any, OAuthUnavailable>)(
188
- retained,
189
- prepare,
190
- );
191
- }).pipe(
192
- Effect.onExit((exit) =>
193
- Effect.sync(() => {
194
- if (exit._tag === "Failure") poison();
195
- }),
196
- ),
197
- Effect.catchDefect(() => Effect.fail(unavailable())),
198
- ),
199
- ]),
200
- ),
201
- ) as S;
@@ -1,29 +0,0 @@
1
- import type { CommitJournal } from "@yielded/auth/Hooks";
2
- import type { OAuthUnavailable } from "@yielded/auth/OAuth";
3
- /* oxlint-disable no-explicit-any -- private adapter preserves the existing driver boundary. */
4
- import { Context } from "effect";
5
-
6
- import { unavailable } from "./oauth-state";
7
- import {
8
- makeTransactionOwner,
9
- makeTransactionRows,
10
- type TransactionNativeDatabase,
11
- type TransactionOwner,
12
- } from "./transaction-owner";
13
-
14
- export { both, type Observation, type Row } from "./transaction-owner";
15
- export const { col, equal, copiedRow, matchesNativeRow } = makeTransactionRows(unavailable);
16
- export type OAuthOwner = TransactionOwner<OAuthUnavailable>;
17
-
18
- export class CurrentOAuthTransaction extends Context.Service<CurrentOAuthTransaction, OAuthOwner>()(
19
- "effect-auth/drizzle/CurrentOAuthTransaction",
20
- ) {}
21
-
22
- export type OAuthNativeDatabase = TransactionNativeDatabase;
23
-
24
- export const makeOAuthOwner = (
25
- database: any,
26
- journal: CommitJournal,
27
- marker: string,
28
- configuration: Parameters<typeof makeTransactionOwner>[4],
29
- ): OAuthOwner => makeTransactionOwner(database, journal, marker, unavailable, configuration);