@yielded/auth-persistence 0.1.0-beta.23 → 0.1.0-beta.25

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 (182) hide show
  1. package/README.md +11 -0
  2. package/dist/Adapter.d.mts +12 -6
  3. package/dist/Adapter.mjs +8 -3
  4. package/dist/OAuthPersistence.d.mts +87 -0
  5. package/dist/OAuthPersistence.mjs +125 -0
  6. package/dist/OAuthPersistence.mjs.map +1 -0
  7. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  8. package/dist/index.d.mts +3 -1
  9. package/dist/index.mjs +3 -1
  10. package/dist/internal/configuration.d.mts +1 -1
  11. package/dist/internal/configuration.mjs.map +1 -1
  12. package/dist/internal/email-kernel.d.mts +1 -1
  13. package/dist/internal/email-kernel.mjs +1 -1
  14. package/dist/internal/email-kernel.mjs.map +1 -1
  15. package/dist/internal/models/oauth-connected-model.d.mts +294 -0
  16. package/dist/internal/models/oauth-connected-model.mjs +22 -0
  17. package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
  18. package/dist/internal/models/oauth-model.d.mts +467 -0
  19. package/dist/internal/models/oauth-model.mjs +49 -0
  20. package/dist/internal/models/oauth-model.mjs.map +1 -0
  21. package/dist/internal/models/password-model.d.mts +6 -48
  22. package/dist/internal/models/password-model.mjs +0 -1
  23. package/dist/internal/models/password-model.mjs.map +1 -1
  24. package/dist/internal/native-sql-table.d.mts +4 -0
  25. package/dist/internal/native-sql-table.mjs +7 -0
  26. package/dist/internal/native-sql-table.mjs.map +1 -1
  27. package/dist/internal/oauth/accounts.d.mts +8 -0
  28. package/dist/internal/oauth/accounts.mjs +379 -0
  29. package/dist/internal/oauth/accounts.mjs.map +1 -0
  30. package/dist/internal/oauth/connected-access.d.mts +9 -0
  31. package/dist/internal/oauth/connected-access.mjs +229 -0
  32. package/dist/internal/oauth/connected-access.mjs.map +1 -0
  33. package/dist/internal/oauth/connected-collection.d.mts +7 -0
  34. package/dist/internal/oauth/connected-collection.mjs +214 -0
  35. package/dist/internal/oauth/connected-collection.mjs.map +1 -0
  36. package/dist/internal/oauth/connected-custody.d.mts +6 -0
  37. package/dist/internal/oauth/connected-custody.mjs +165 -0
  38. package/dist/internal/oauth/connected-custody.mjs.map +1 -0
  39. package/dist/internal/oauth/connected-flow.d.mts +7 -0
  40. package/dist/internal/oauth/connected-flow.mjs +324 -0
  41. package/dist/internal/oauth/connected-flow.mjs.map +1 -0
  42. package/dist/internal/oauth/connected-input.mjs +122 -0
  43. package/dist/internal/oauth/connected-input.mjs.map +1 -0
  44. package/dist/internal/oauth/connected-keys.d.mts +2 -0
  45. package/dist/internal/oauth/connected-keys.mjs +22 -0
  46. package/dist/internal/oauth/connected-keys.mjs.map +1 -0
  47. package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
  48. package/dist/internal/oauth/connected-maintenance.mjs +226 -0
  49. package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
  50. package/dist/internal/oauth/connected-management.d.mts +9 -0
  51. package/dist/internal/oauth/connected-management.mjs +243 -0
  52. package/dist/internal/oauth/connected-management.mjs.map +1 -0
  53. package/dist/internal/oauth/connected-reference.mjs +63 -0
  54. package/dist/internal/oauth/connected-reference.mjs.map +1 -0
  55. package/dist/internal/oauth/connected-settlement.d.mts +8 -0
  56. package/dist/internal/oauth/connected-settlement.mjs +141 -0
  57. package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
  58. package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
  59. package/dist/internal/oauth/connected-sign-in.mjs +281 -0
  60. package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
  61. package/dist/internal/oauth/connected-state.d.mts +14 -0
  62. package/dist/internal/oauth/connected-state.mjs +469 -0
  63. package/dist/internal/oauth/connected-state.mjs.map +1 -0
  64. package/dist/internal/oauth/connected-target.d.mts +21 -0
  65. package/dist/internal/oauth/connected-target.mjs +117 -0
  66. package/dist/internal/oauth/connected-target.mjs.map +1 -0
  67. package/dist/internal/oauth/flow.d.mts +5 -0
  68. package/dist/internal/oauth/flow.mjs +279 -0
  69. package/dist/internal/oauth/flow.mjs.map +1 -0
  70. package/dist/internal/oauth/input.mjs +127 -0
  71. package/dist/internal/oauth/input.mjs.map +1 -0
  72. package/dist/internal/oauth/owner.d.mts +10 -0
  73. package/dist/internal/oauth/owner.mjs +10 -0
  74. package/dist/internal/oauth/owner.mjs.map +1 -0
  75. package/dist/internal/oauth/query-compiler.d.mts +7 -0
  76. package/dist/internal/oauth/query-compiler.mjs +9 -0
  77. package/dist/internal/oauth/query-compiler.mjs.map +1 -0
  78. package/dist/internal/oauth/registration.d.mts +7 -0
  79. package/dist/internal/oauth/registration.mjs +393 -0
  80. package/dist/internal/oauth/registration.mjs.map +1 -0
  81. package/dist/internal/oauth/state.mjs +107 -0
  82. package/dist/internal/oauth/state.mjs.map +1 -0
  83. package/dist/internal/oauth/target.d.mts +31 -0
  84. package/dist/internal/oauth/target.mjs +273 -0
  85. package/dist/internal/oauth/target.mjs.map +1 -0
  86. package/dist/internal/oauth-kernel.d.mts +1423 -0
  87. package/dist/internal/oauth-kernel.mjs +187 -0
  88. package/dist/internal/oauth-kernel.mjs.map +1 -0
  89. package/dist/internal/oauth-proxy.d.mts +31 -0
  90. package/dist/internal/oauth-proxy.mjs +129 -0
  91. package/dist/internal/oauth-proxy.mjs.map +1 -0
  92. package/dist/internal/passkey/write-state.mjs +9 -1
  93. package/dist/internal/passkey/write-state.mjs.map +1 -1
  94. package/dist/internal/passkey-kernel.d.mts +9 -9
  95. package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
  96. package/dist/internal/password-credentials.mjs +121 -0
  97. package/dist/internal/password-credentials.mjs.map +1 -0
  98. package/dist/internal/password-kernel.d.mts +8 -24
  99. package/dist/internal/password-kernel.mjs +5 -43
  100. package/dist/internal/password-kernel.mjs.map +1 -1
  101. package/dist/internal/persistence.d.mts +1 -1
  102. package/dist/internal/persistence.mjs +2 -2
  103. package/dist/internal/persistence.mjs.map +1 -1
  104. package/dist/internal/query-operations.d.mts +2 -0
  105. package/dist/internal/session-kernel.d.mts +2 -2
  106. package/dist/internal/session-kernel.mjs +74 -18
  107. package/dist/internal/session-kernel.mjs.map +1 -1
  108. package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
  109. package/dist/internal/sql-oauth-connected-model.mjs +7 -0
  110. package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
  111. package/dist/internal/sql-oauth-model.d.mts +60 -0
  112. package/dist/internal/sql-oauth-model.mjs +10 -0
  113. package/dist/internal/sql-oauth-model.mjs.map +1 -0
  114. package/dist/internal/sql-query.d.mts +12 -0
  115. package/dist/internal/sql-query.mjs +15 -18
  116. package/dist/internal/sql-query.mjs.map +1 -1
  117. package/dist/internal/sql-storage-validation.mjs +54 -0
  118. package/dist/internal/sql-storage-validation.mjs.map +1 -0
  119. package/dist/internal/storage-mapping.mjs +1 -37
  120. package/dist/internal/storage-mapping.mjs.map +1 -1
  121. package/dist/internal/storage-plans.d.mts +7 -0
  122. package/dist/internal/storage-plans.mjs +189 -0
  123. package/dist/internal/storage-plans.mjs.map +1 -0
  124. package/dist/internal/storage-tables.d.mts +0 -56
  125. package/dist/internal/storage-tables.mjs +0 -17
  126. package/dist/internal/storage-tables.mjs.map +1 -1
  127. package/dist/internal/storage-validation.d.mts +6 -1
  128. package/dist/internal/storage-validation.mjs +42 -1
  129. package/dist/internal/storage-validation.mjs.map +1 -1
  130. package/dist/internal/transaction-kernel.mjs +3 -2
  131. package/dist/internal/transaction-kernel.mjs.map +1 -1
  132. package/package.json +1 -1
  133. package/src/Adapter.ts +89 -5
  134. package/src/OAuthPersistence.ts +427 -0
  135. package/src/index.ts +3 -0
  136. package/src/internal/configuration.ts +1 -3
  137. package/src/internal/email-kernel.ts +2 -1
  138. package/src/internal/models/oauth-connected-model.ts +396 -0
  139. package/src/internal/models/oauth-model.ts +708 -0
  140. package/src/internal/models/password-model.ts +2 -57
  141. package/src/internal/native-sql-table.ts +14 -0
  142. package/src/internal/oauth/accounts.ts +839 -0
  143. package/src/internal/oauth/connected-access.ts +388 -0
  144. package/src/internal/oauth/connected-collection.ts +345 -0
  145. package/src/internal/oauth/connected-custody.ts +308 -0
  146. package/src/internal/oauth/connected-flow.ts +562 -0
  147. package/src/internal/oauth/connected-input.ts +147 -0
  148. package/src/internal/oauth/connected-keys.ts +31 -0
  149. package/src/internal/oauth/connected-maintenance.ts +379 -0
  150. package/src/internal/oauth/connected-management.ts +391 -0
  151. package/src/internal/oauth/connected-reference.ts +135 -0
  152. package/src/internal/oauth/connected-settlement.ts +278 -0
  153. package/src/internal/oauth/connected-sign-in.ts +472 -0
  154. package/src/internal/oauth/connected-state.ts +924 -0
  155. package/src/internal/oauth/connected-target.ts +307 -0
  156. package/src/internal/oauth/flow.ts +505 -0
  157. package/src/internal/oauth/input.ts +201 -0
  158. package/src/internal/oauth/owner.ts +22 -0
  159. package/src/internal/oauth/query-compiler.ts +15 -0
  160. package/src/internal/oauth/registration.ts +676 -0
  161. package/src/internal/oauth/state.ts +218 -0
  162. package/src/internal/oauth/target.ts +803 -0
  163. package/src/internal/oauth-kernel.ts +294 -0
  164. package/src/internal/oauth-proxy.ts +176 -0
  165. package/src/internal/passkey/write-state.ts +18 -4
  166. package/src/internal/password-credentials.ts +245 -0
  167. package/src/internal/password-kernel.ts +7 -99
  168. package/src/internal/persistence.ts +1 -1
  169. package/src/internal/query-operations.ts +2 -0
  170. package/src/internal/session-kernel.ts +143 -55
  171. package/src/internal/sql-oauth-connected-model.ts +145 -0
  172. package/src/internal/sql-oauth-model.ts +263 -0
  173. package/src/internal/sql-query.ts +16 -13
  174. package/src/internal/sql-storage-validation.ts +64 -0
  175. package/src/internal/storage-mapping.ts +0 -37
  176. package/src/internal/storage-plans.ts +218 -0
  177. package/src/internal/storage-tables.ts +0 -20
  178. package/src/internal/storage-validation.ts +58 -0
  179. package/src/internal/transaction-kernel.ts +6 -2
  180. package/dist/internal/password-attempts.mjs +0 -197
  181. package/dist/internal/password-attempts.mjs.map +0 -1
  182. package/src/internal/password-attempts.ts +0 -466
@@ -0,0 +1,803 @@
1
+ import { LifecycleHooks } from "@yielded/auth/Hooks";
2
+ import {
3
+ OAuthAccountRevision,
4
+ OAuthLinkClaimDecision,
5
+ OAuthLinkDecision,
6
+ OAuthLinkIssueDecision,
7
+ OAuthLinkPendingFlow,
8
+ OAuthUnlinkDecision,
9
+ OAuthUnlinkInspection,
10
+ OAuthAccountsPersistence,
11
+ OAuthRegistrationIntents,
12
+ OAuthSignInPersistence,
13
+ type PrepareOAuthCommit,
14
+ OAuthRegistrationIntent,
15
+ OAuthRegistrationFingerprint,
16
+ OAuthClaim,
17
+ OAuthClaimDecision,
18
+ OAuthCleanupInput,
19
+ OAuthIssueDecision,
20
+ OAuthPendingFlow,
21
+ OAuthSettlementDecision,
22
+ OAuthVerifiedExternalIdentity,
23
+ OAuthCredentialSnapshot,
24
+ snapshotOAuthSync,
25
+ type OAuthUnavailable,
26
+ } from "@yielded/auth/OAuth";
27
+ import type { SecurityRevision } from "@yielded/auth/Sessions";
28
+ import { type Crypto, Context, Effect, Layer } from "effect";
29
+ import type { Statement } from "effect/sql/Statement";
30
+
31
+ import type { PersistenceMappingError } from "../models/common";
32
+ import type { OAuthRegistrationAuthority } from "../models/oauth-model";
33
+ import type {
34
+ TransactionTargetConfiguration,
35
+ TransactionCoordinatorError,
36
+ TransactionExecution,
37
+ } from "../transaction-execution-kernel";
38
+ /* oxlint-disable no-explicit-any -- compiler table shapes are erased; domain codecs and error channels remain typed. */
39
+ import type { NativeDatabase } from "../transaction-kernel";
40
+ import { OAuthAccounts } from "./accounts";
41
+ import { OAuthFlow } from "./flow";
42
+ import {
43
+ accountsInputs,
44
+ capturedOAuthService,
45
+ registrationInputs,
46
+ registrationIntentInputs,
47
+ signInInputs,
48
+ } from "./input";
49
+ import { OAuthTransactionExecution, CurrentOAuthTransaction } from "./owner";
50
+ import type { OAuthNativeDatabase } from "./owner";
51
+ import { OAuthQueryCompiler } from "./query-compiler";
52
+ import { OAuthRegistration } from "./registration";
53
+ import type { RegistrationCallbacks } from "./registration";
54
+ import { captureOAuthMapping, invariant, nonce, unavailable } from "./state";
55
+
56
+ export type OAuthTargetConfiguration = TransactionTargetConfiguration<OAuthUnavailable>;
57
+
58
+ export type OAuthCoordinatorError<E> = TransactionCoordinatorError<E, OAuthUnavailable>;
59
+
60
+ export type OAuthExecution = TransactionExecution<OAuthUnavailable, CurrentOAuthTransaction, never>;
61
+
62
+ const make = Effect.gen(function* () {
63
+ const operations = yield* OAuthQueryCompiler;
64
+ const accounts = yield* OAuthAccounts;
65
+ const flow = yield* OAuthFlow;
66
+ const registration = yield* OAuthRegistration;
67
+ const execution = yield* OAuthTransactionExecution;
68
+
69
+ const { eq, sql } = operations;
70
+
71
+ const { accountCurrent, claimLink, inspectUnlink, issueLink, preflightLink, settleLink, unlink } =
72
+ accounts;
73
+
74
+ const {
75
+ claimFlow,
76
+ cleanupFlows,
77
+ discoverOwned,
78
+ exactClaim,
79
+ issueFlow,
80
+ matchesAccess,
81
+ readFlow,
82
+ resolveOwned,
83
+ terminalFlow,
84
+ } = flow;
85
+
86
+ const { both, col, equal } = operations;
87
+
88
+ const {
89
+ inspectIntent,
90
+ register,
91
+ registrationData,
92
+ registrationResources,
93
+ settleRegistrationIntent,
94
+ } = registration;
95
+
96
+ const { coordinateTransactionOwner, makeTransactionExecution } = execution;
97
+
98
+ const makeOAuthExecution = Effect.fnUntraced(function* (
99
+ configuration: OAuthTargetConfiguration,
100
+ mapping: unknown,
101
+ ): Effect.fn.Return<
102
+ OAuthExecution,
103
+ OAuthUnavailable,
104
+ LifecycleHooks | NativeDatabase | Crypto.Crypto
105
+ > {
106
+ const hooks = yield* LifecycleHooks;
107
+
108
+ yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(
109
+ Effect.mapError(unavailable),
110
+ );
111
+
112
+ const execution = yield* makeTransactionExecution(
113
+ CurrentOAuthTransaction,
114
+ configuration,
115
+ unavailable,
116
+ nonce,
117
+ );
118
+
119
+ return {
120
+ ...execution,
121
+ run: (operation, mutation) =>
122
+ execution.run(operation, mutation).pipe(Effect.provideService(LifecycleHooks, hooks)),
123
+ };
124
+ });
125
+
126
+ const prepareValue = <Value, A>(value: Value, prepare: PrepareOAuthCommit<Value, A>) =>
127
+ Effect.flatMap(CurrentOAuthTransaction, (owner) =>
128
+ Effect.sync(() => {
129
+ owner.guards.push(owner.journal.prepare(undefined));
130
+
131
+ return prepare(value, owner.journal);
132
+ }),
133
+ );
134
+
135
+ const makeOAuthSignIn = (
136
+ mapping: any,
137
+ execute: OAuthExecution,
138
+ ): OAuthSignInPersistence["Service"] =>
139
+ capturedOAuthService<OAuthSignInPersistence["Service"]>(
140
+ {
141
+ issue: (original, prepare) =>
142
+ execute.run(
143
+ Effect.gen(function* () {
144
+ const input = snapshotOAuthSync(OAuthPendingFlow, original);
145
+ const value = yield* issueFlow(mapping, input, "sign-in");
146
+
147
+ return yield* prepareValue(
148
+ snapshotOAuthSync(OAuthIssueDecision, value as never),
149
+ prepare,
150
+ );
151
+ }),
152
+ ),
153
+ claim: (input, prepare) =>
154
+ execute.run(
155
+ Effect.gen(function* () {
156
+ const read = yield* readFlow(mapping, input, "sign-in");
157
+
158
+ const claim =
159
+ read.row !== undefined && read.flow !== undefined && matchesAccess(read.flow, input)
160
+ ? yield* claimFlow(mapping, { row: read.row, flow: read.flow }, input.claimId)
161
+ : undefined;
162
+
163
+ return yield* prepareValue(
164
+ snapshotOAuthSync(
165
+ OAuthClaimDecision,
166
+ claim === undefined
167
+ ? { _tag: "Rejected" }
168
+ : { _tag: "Claimed", claim: claim as typeof OAuthClaim.Type },
169
+ ),
170
+ prepare,
171
+ );
172
+ }),
173
+ ),
174
+ settle: (original, prepare) =>
175
+ execute.run(
176
+ Effect.gen(function* () {
177
+ const claim = snapshotOAuthSync(OAuthClaim, original.claim);
178
+ const outcome = original.outcome;
179
+
180
+ const identity =
181
+ outcome._tag === "Verified"
182
+ ? snapshotOAuthSync(OAuthVerifiedExternalIdentity, outcome.identity).identity
183
+ : undefined;
184
+
185
+ if (identity !== undefined)
186
+ invariant(
187
+ identity.provider === claim.flow.context.provider &&
188
+ identity.issuer === claim.flow.context.issuer,
189
+ );
190
+
191
+ // Discover/lock the existing subject before locking its claimed flow.
192
+ const found =
193
+ identity === undefined ? undefined : yield* discoverOwned(mapping, identity);
194
+
195
+ const exact = yield* exactClaim(mapping, claim, "sign-in");
196
+ let value: typeof OAuthSettlementDecision.Type = { _tag: "Rejected" };
197
+
198
+ if (exact !== undefined) {
199
+ const credential =
200
+ identity === undefined
201
+ ? undefined
202
+ : resolveOwned(mapping, claim.flow.context.moduleId, identity, found);
203
+
204
+ value =
205
+ outcome._tag !== "Verified"
206
+ ? { _tag: outcome._tag }
207
+ : credential === undefined
208
+ ? { _tag: "Rejected" }
209
+ : { _tag: "Verified", credential };
210
+ yield* terminalFlow(mapping, claim.flow, value._tag);
211
+ }
212
+
213
+ return yield* prepareValue(
214
+ snapshotOAuthSync(OAuthSettlementDecision, value),
215
+ prepare,
216
+ );
217
+ }),
218
+ ),
219
+ cleanup: (input, prepare) =>
220
+ execute.run(
221
+ Effect.gen(function* () {
222
+ const value = yield* cleanupFlows(mapping, input, "sign-in");
223
+
224
+ return yield* prepareValue(value, prepare);
225
+ }),
226
+ ),
227
+ },
228
+ signInInputs,
229
+ execute.active,
230
+ execute.poison,
231
+ );
232
+
233
+ const makeOAuthRegistrationIntents = (
234
+ mapping: any,
235
+ execute: OAuthExecution,
236
+ ): OAuthRegistrationIntents["Service"] =>
237
+ capturedOAuthService<OAuthRegistrationIntents["Service"]>(
238
+ {
239
+ settle: (input, prepare) =>
240
+ execute.run(
241
+ Effect.gen(function* () {
242
+ const value = yield* settleRegistrationIntent(mapping, {
243
+ claim: snapshotOAuthSync(OAuthClaim, input.claim),
244
+ identity: snapshotOAuthSync(OAuthVerifiedExternalIdentity, input.identity),
245
+ ...(input.intent === undefined
246
+ ? {}
247
+ : { intent: snapshotOAuthSync(OAuthRegistrationIntent, input.intent) }),
248
+ });
249
+
250
+ return yield* prepareValue(value, prepare);
251
+ }),
252
+ ),
253
+ },
254
+ registrationIntentInputs,
255
+ execute.active,
256
+ execute.poison,
257
+ );
258
+
259
+ const makeOAuthRegistration = <Registration>(
260
+ mapping: any,
261
+ execute: OAuthExecution,
262
+ allocated?: Awaited<ReturnType<typeof registrationResources>> extends Effect.Effect<
263
+ infer A,
264
+ any,
265
+ any
266
+ >
267
+ ? A
268
+ : never,
269
+ ): OAuthRegistrationAuthority<Registration> =>
270
+ capturedOAuthService<OAuthRegistrationAuthority<Registration>>(
271
+ {
272
+ read: (input) =>
273
+ execute.run(
274
+ Effect.map(inspectIntent(mapping, input), (result) => result?.inspection),
275
+ false,
276
+ ),
277
+ inspect: (input) =>
278
+ Effect.gen(function* () {
279
+ yield* execute.admit;
280
+ const intent = snapshotOAuthSync(OAuthRegistrationIntent, input.intent);
281
+ const stored = mapping.application.encode(input.registration);
282
+
283
+ invariant(
284
+ typeof stored === "string" && new TextEncoder().encode(stored).length <= 1048576,
285
+ );
286
+ const detached = { intent, registration: mapping.application.decode(stored) };
287
+
288
+ const checked = execute.bound
289
+ ? (invariant(mapping.inspectSync !== undefined), mapping.inspectSync(detached))
290
+ : yield* (mapping as RegistrationCallbacks<Registration>).inspect(detached);
291
+
292
+ invariant(typeof checked.eligible === "boolean");
293
+
294
+ return {
295
+ fingerprint: snapshotOAuthSync(OAuthRegistrationFingerprint, checked.fingerprint),
296
+ eligible: checked.eligible,
297
+ };
298
+ }).pipe(
299
+ (effect) => (execute.bound ? execute.run(effect, false) : effect),
300
+ Effect.onExit((exit) =>
301
+ Effect.sync(() => {
302
+ if (exit._tag === "Failure") execute.poison();
303
+ }),
304
+ ),
305
+ Effect.mapError(unavailable),
306
+ Effect.catchDefect(() => Effect.fail(unavailable())),
307
+ ) as never,
308
+ register: (input, prepare) =>
309
+ Effect.gen(function* () {
310
+ yield* execute.admit;
311
+ const data = yield* registrationData(mapping, input, execute.bound);
312
+ const resources = allocated ?? (yield* registrationResources(mapping));
313
+
314
+ return yield* execute.run(
315
+ Effect.gen(function* () {
316
+ const value = yield* register(mapping, input, data, resources);
317
+
318
+ return yield* prepareValue(value, prepare);
319
+ }),
320
+ );
321
+ }).pipe(
322
+ Effect.onExit((exit) =>
323
+ Effect.sync(() => {
324
+ if (exit._tag === "Failure") execute.poison();
325
+ }),
326
+ ),
327
+ Effect.mapError(unavailable),
328
+ Effect.catchDefect(() => Effect.fail(unavailable())),
329
+ ) as never,
330
+ cleanup: (original, prepare) =>
331
+ execute.run(
332
+ Effect.gen(function* () {
333
+ const owner = yield* CurrentOAuthTransaction;
334
+
335
+ const input = snapshotOAuthSync(OAuthCleanupInput, original),
336
+ i = mapping.intent,
337
+ r = mapping.command;
338
+
339
+ const now = yield* owner.now(mapping.clock);
340
+
341
+ const candidates = yield* owner.read(
342
+ i.table,
343
+ both(
344
+ eq(col(i.table, i.moduleId), input.moduleId),
345
+ sql`${col(i.table, i.state)} <> 'ProvisioningPending' and ${col(i.table, i.retentionUntil)} < ${mapping.clock.encodeInstant(now)}`,
346
+ ),
347
+ { limit: input.limit, observe: false, takeOnly: true },
348
+ );
349
+
350
+ let removed = 0;
351
+
352
+ for (const candidate of candidates.rows) {
353
+ const selected = yield* owner.read(
354
+ i.table,
355
+ equal(i.table, {
356
+ [i.moduleId]: input.moduleId,
357
+ [i.reference]: candidate[i.reference],
358
+ }),
359
+ { limit: 1 },
360
+ );
361
+
362
+ const row = selected.rows[0];
363
+
364
+ if (row === undefined || row[i.state] === "ProvisioningPending") continue;
365
+ let horizon = mapping.clock.decodeInstant(row[i.retentionUntil]);
366
+
367
+ if (!Number.isSafeInteger(horizon) || horizon >= (yield* owner.now(mapping.clock)))
368
+ continue;
369
+ if (row[i.commandId] !== null) {
370
+ const commands = yield* owner.read(
371
+ r.table,
372
+ equal(r.table, {
373
+ [r.moduleId]: input.moduleId,
374
+ [r.commandId]: row[i.commandId],
375
+ }),
376
+ { limit: 1 },
377
+ );
378
+
379
+ const command = commands.rows[0];
380
+
381
+ if (command === undefined) continue;
382
+ horizon = Math.max(
383
+ horizon,
384
+ mapping.clock.decodeInstant(command[r.retentionUntil]),
385
+ );
386
+ if (
387
+ !Number.isSafeInteger(horizon) ||
388
+ horizon >= (yield* owner.now(mapping.clock))
389
+ )
390
+ continue;
391
+ yield* owner.remove(r.table, {
392
+ [r.moduleId]: input.moduleId,
393
+ [r.commandId]: row[i.commandId],
394
+ });
395
+ }
396
+ yield* owner.remove(i.table, {
397
+ [i.moduleId]: input.moduleId,
398
+ [i.reference]: row[i.reference],
399
+ });
400
+ owner.postconditions.push(sql`${mapping.clock.engineNowMillis} > ${horizon}`);
401
+ removed++;
402
+ }
403
+
404
+ return yield* prepareValue(
405
+ { removed, hasMore: candidates.rows.length === input.limit },
406
+ prepare,
407
+ );
408
+ }),
409
+ ),
410
+ },
411
+ registrationInputs(mapping.application),
412
+ execute.active,
413
+ execute.poison,
414
+ );
415
+
416
+ const makeTargetOAuthSignInServices = (mapping: any, configuration: OAuthTargetConfiguration) => {
417
+ const captured = captureOAuthMapping(mapping);
418
+
419
+ return Effect.map(makeOAuthExecution(configuration, captured), (execution) => ({
420
+ oauthSignInPersistence: makeOAuthSignIn(captured, execution),
421
+ }));
422
+ };
423
+
424
+ const makeTargetOAuthRegistrationIntentServices = (
425
+ mapping: any,
426
+ configuration: OAuthTargetConfiguration,
427
+ ) => {
428
+ const captured = captureOAuthMapping(mapping);
429
+
430
+ return Effect.map(makeOAuthExecution(configuration, captured), (execution) => ({
431
+ oauthRegistrationIntents: makeOAuthRegistrationIntents(captured, execution),
432
+ }));
433
+ };
434
+
435
+ const makeTargetOAuthRegistrationServices = <Registration>(
436
+ mapping: any,
437
+ configuration: OAuthTargetConfiguration,
438
+ ) => {
439
+ const captured = captureOAuthMapping(mapping);
440
+
441
+ return Effect.map(makeOAuthExecution(configuration, captured), (execution) => ({
442
+ registrationAuthority: makeOAuthRegistration<Registration>(captured, execution),
443
+ }));
444
+ };
445
+
446
+ const coordinateOAuthOwner = <Services, Transaction, A, E, R>(
447
+ database: OAuthNativeDatabase,
448
+ mapping: any,
449
+ configuration: OAuthTargetConfiguration,
450
+ allocate: (mapping: any) => Effect.Effect<any, PersistenceMappingError>,
451
+ services: (mapping: any, execution: OAuthExecution, resources: any) => Services,
452
+ owner: (
453
+ transaction: Transaction,
454
+ services: Services,
455
+ append: (statement: Statement<any>) => void,
456
+ ) => Effect.Effect<A, E, R>,
457
+ ): Effect.Effect<
458
+ A,
459
+ OAuthCoordinatorError<E>,
460
+ R | Crypto.Crypto | LifecycleHooks | NativeDatabase
461
+ > => {
462
+ const captured = captureOAuthMapping(mapping);
463
+
464
+ return Effect.flatMap(LifecycleHooks, (hooks) =>
465
+ coordinateTransactionOwner(
466
+ database,
467
+ CurrentOAuthTransaction,
468
+ configuration,
469
+ (operations.validateStorage?.(captured) ?? Effect.void).pipe(
470
+ Effect.andThen(Effect.suspend(() => allocate(captured))),
471
+ ),
472
+ unavailable,
473
+ nonce,
474
+ (execution, resources) =>
475
+ services(
476
+ captured,
477
+ {
478
+ ...execution,
479
+ run: (operation, mutation) =>
480
+ execution
481
+ .run(operation, mutation)
482
+ .pipe(Effect.provideService(LifecycleHooks, hooks)),
483
+ },
484
+ resources,
485
+ ),
486
+ owner,
487
+ ).pipe(Effect.provideService(LifecycleHooks, hooks)),
488
+ );
489
+ };
490
+
491
+ const accountResources = (mapping: any) =>
492
+ Effect.gen(function* () {
493
+ return {
494
+ credentialId: snapshotOAuthSync(
495
+ OAuthCredentialSnapshot.fields.credentialId,
496
+ yield* mapping.allocateCredentialId as Effect.Effect<string, PersistenceMappingError>,
497
+ ),
498
+ revision: snapshotOAuthSync(
499
+ OAuthCredentialSnapshot.fields.credentialRevision,
500
+ yield* mapping.allocateRevision as Effect.Effect<
501
+ SecurityRevision,
502
+ PersistenceMappingError
503
+ >,
504
+ ),
505
+ };
506
+ });
507
+
508
+ const coordinateTargetOAuthRegistration = <Registration, Transaction, A, E, R>(
509
+ database: any,
510
+ mapping: any,
511
+ configuration: OAuthTargetConfiguration,
512
+ owner: (
513
+ transaction: Transaction,
514
+ services: { readonly registrationAuthority: OAuthRegistrationAuthority<Registration> },
515
+ append: (statement: Statement<any>) => void,
516
+ ) => Effect.Effect<A, E, R>,
517
+ ) =>
518
+ coordinateOAuthOwner(
519
+ database,
520
+ mapping,
521
+ configuration,
522
+ registrationResources,
523
+ (captured, execute, resources) => ({
524
+ registrationAuthority: makeOAuthRegistration<Registration>(captured, execute, resources),
525
+ }),
526
+ owner,
527
+ );
528
+
529
+ const coordinateTargetOAuthSignIn = <Transaction, A, E, R>(
530
+ database: any,
531
+ mapping: any,
532
+ configuration: OAuthTargetConfiguration,
533
+ owner: (
534
+ transaction: Transaction,
535
+ services: { readonly oauthSignInPersistence: OAuthSignInPersistence["Service"] },
536
+ append: (statement: Statement<any>) => void,
537
+ ) => Effect.Effect<A, E, R>,
538
+ ) =>
539
+ coordinateOAuthOwner(
540
+ database,
541
+ mapping,
542
+ configuration,
543
+ () => Effect.void,
544
+ (captured, execute) => ({ oauthSignInPersistence: makeOAuthSignIn(captured, execute) }),
545
+ owner,
546
+ );
547
+
548
+ const coordinateTargetOAuthRegistrationIntents = <Transaction, A, E, R>(
549
+ database: any,
550
+ mapping: any,
551
+ configuration: OAuthTargetConfiguration,
552
+ owner: (
553
+ transaction: Transaction,
554
+ services: { readonly oauthRegistrationIntents: OAuthRegistrationIntents["Service"] },
555
+ append: (statement: Statement<any>) => void,
556
+ ) => Effect.Effect<A, E, R>,
557
+ ) =>
558
+ coordinateOAuthOwner(
559
+ database,
560
+ mapping,
561
+ configuration,
562
+ () => Effect.void,
563
+ (captured, execute) => ({
564
+ oauthRegistrationIntents: makeOAuthRegistrationIntents(captured, execute),
565
+ }),
566
+ owner,
567
+ );
568
+
569
+ const coordinateTargetOAuthAccounts = <Transaction, A, E, R>(
570
+ database: any,
571
+ mapping: any,
572
+ configuration: OAuthTargetConfiguration,
573
+ owner: (
574
+ transaction: Transaction,
575
+ services: { readonly oauthAccountsPersistence: OAuthAccountsPersistence["Service"] },
576
+ append: (statement: Statement<any>) => void,
577
+ ) => Effect.Effect<A, E, R>,
578
+ ) =>
579
+ coordinateOAuthOwner(
580
+ database,
581
+ mapping,
582
+ configuration,
583
+ accountResources,
584
+ (captured, execute, resources) => ({
585
+ oauthAccountsPersistence: makeOAuthAccounts(captured, execute, resources),
586
+ }),
587
+ owner,
588
+ );
589
+
590
+ const makeOAuthAccounts = (
591
+ mapping: any,
592
+ execute: OAuthExecution,
593
+ resources?: any,
594
+ ): OAuthAccountsPersistence["Service"] =>
595
+ capturedOAuthService<OAuthAccountsPersistence["Service"]>(
596
+ {
597
+ capture: (input) =>
598
+ execute.run(
599
+ Effect.map(accountCurrent(mapping, input.subjectId), (current) =>
600
+ current === undefined
601
+ ? undefined
602
+ : snapshotOAuthSync(OAuthAccountRevision, current.revision),
603
+ ),
604
+ false,
605
+ ),
606
+ issue: (input, prepare) =>
607
+ execute.run(
608
+ Effect.gen(function* () {
609
+ const value = yield* issueLink(mapping, input);
610
+
611
+ return yield* prepareValue(
612
+ snapshotOAuthSync(OAuthLinkIssueDecision, value as never),
613
+ prepare,
614
+ );
615
+ }),
616
+ ),
617
+ preflight: (input) =>
618
+ execute.run(
619
+ Effect.map(preflightLink(mapping, input), (current) =>
620
+ current === undefined
621
+ ? undefined
622
+ : snapshotOAuthSync(OAuthLinkPendingFlow, current.flow),
623
+ ),
624
+ false,
625
+ ),
626
+ claim: (input, prepare) =>
627
+ execute.run(
628
+ Effect.gen(function* () {
629
+ const value = yield* claimLink(mapping, input);
630
+
631
+ return yield* prepareValue(
632
+ snapshotOAuthSync(OAuthLinkClaimDecision, value as never),
633
+ prepare,
634
+ );
635
+ }),
636
+ ),
637
+ settle: (input, prepare) =>
638
+ Effect.gen(function* () {
639
+ yield* execute.admit;
640
+ const allocated = resources ?? (yield* accountResources(mapping));
641
+
642
+ return yield* execute.run(
643
+ Effect.gen(function* () {
644
+ const value = yield* settleLink(mapping, input, allocated as never);
645
+
646
+ return yield* prepareValue(snapshotOAuthSync(OAuthLinkDecision, value), prepare);
647
+ }),
648
+ );
649
+ }).pipe(
650
+ Effect.mapError(unavailable),
651
+ Effect.catchDefect(() => Effect.fail(unavailable())),
652
+ ) as never,
653
+ inspectUnlink: (input) =>
654
+ execute.run(
655
+ Effect.map(inspectUnlink(mapping, input), (value) =>
656
+ snapshotOAuthSync(OAuthUnlinkInspection, value),
657
+ ),
658
+ false,
659
+ ),
660
+ unlink: (input, prepare) =>
661
+ execute.run(
662
+ Effect.gen(function* () {
663
+ const value = yield* unlink(mapping, input);
664
+
665
+ return yield* prepareValue(snapshotOAuthSync(OAuthUnlinkDecision, value), prepare);
666
+ }),
667
+ ),
668
+ cleanup: (input, prepare) =>
669
+ execute.run(
670
+ Effect.gen(function* () {
671
+ const owner = yield* CurrentOAuthTransaction;
672
+ const value = yield* cleanupFlows(mapping, input, "link");
673
+ const remaining = input.limit - value.terminalized - value.removed;
674
+
675
+ const now = yield* owner.now(mapping.clock),
676
+ r = mapping.command;
677
+
678
+ let removed = 0;
679
+
680
+ if (remaining > 0) {
681
+ const rows = yield* owner.read(
682
+ r.table,
683
+ both(
684
+ eq(col(r.table, r.moduleId), input.moduleId),
685
+ sql`${col(r.table, r.retentionUntil)} < ${mapping.clock.encodeInstant(now)}`,
686
+ ),
687
+ { limit: remaining, observe: false, takeOnly: true },
688
+ );
689
+
690
+ for (const row of rows.rows) {
691
+ const key = { [r.moduleId]: input.moduleId, [r.commandId]: row[r.commandId] };
692
+ const selected = yield* owner.read(r.table, equal(r.table, key), { limit: 1 });
693
+ const current = selected.rows[0];
694
+
695
+ if (current === undefined) continue;
696
+ const horizon = mapping.clock.decodeInstant(current[r.retentionUntil]);
697
+
698
+ if (
699
+ !Number.isSafeInteger(horizon) ||
700
+ horizon >= (yield* owner.now(mapping.clock))
701
+ )
702
+ continue;
703
+ yield* owner.remove(r.table, key);
704
+ owner.postconditions.push(sql`${mapping.clock.engineNowMillis} > ${horizon}`);
705
+ removed++;
706
+ }
707
+ }
708
+
709
+ return yield* prepareValue(
710
+ {
711
+ ...value,
712
+ removed: value.removed + removed,
713
+ hasMore: value.hasMore || remaining === removed,
714
+ },
715
+ prepare,
716
+ );
717
+ }),
718
+ ),
719
+ },
720
+ accountsInputs,
721
+ execute.active,
722
+ execute.poison,
723
+ );
724
+
725
+ const makeTargetOAuthAccountsServices = (
726
+ mapping: any,
727
+ configuration: OAuthTargetConfiguration,
728
+ ) => {
729
+ const captured = captureOAuthMapping(mapping);
730
+
731
+ return Effect.map(makeOAuthExecution(configuration, captured), (execution) => ({
732
+ oauthAccountsPersistence: makeOAuthAccounts(captured, execution),
733
+ }));
734
+ };
735
+
736
+ return {
737
+ makeOAuthExecution,
738
+ makeTargetOAuthSignInServices,
739
+ makeTargetOAuthRegistrationIntentServices,
740
+ makeTargetOAuthRegistrationServices,
741
+ coordinateOAuthOwner,
742
+ coordinateTargetOAuthRegistration,
743
+ coordinateTargetOAuthSignIn,
744
+ coordinateTargetOAuthRegistrationIntents,
745
+ coordinateTargetOAuthAccounts,
746
+ makeTargetOAuthAccountsServices,
747
+ };
748
+ });
749
+
750
+ export class OAuthTarget extends Context.Service<OAuthTarget, Effect.Success<typeof make>>()(
751
+ "effect-auth/persistence/OAuthTarget",
752
+ ) {
753
+ static readonly layer = Layer.effect(OAuthTarget, make);
754
+ }
755
+
756
+ export const oauthAccountsPersistenceLayer = <E, R>(
757
+ services: Effect.Effect<
758
+ { readonly oauthAccountsPersistence: OAuthAccountsPersistence["Service"] },
759
+ E,
760
+ R
761
+ >,
762
+ ) =>
763
+ Layer.effect(
764
+ OAuthAccountsPersistence,
765
+ Effect.map(services, (value) => value.oauthAccountsPersistence),
766
+ );
767
+
768
+ export const oauthSignInPersistenceLayer = <E, R>(
769
+ services: Effect.Effect<
770
+ { readonly oauthSignInPersistence: OAuthSignInPersistence["Service"] },
771
+ E,
772
+ R
773
+ >,
774
+ ) =>
775
+ Layer.effect(
776
+ OAuthSignInPersistence,
777
+ Effect.map(services, (value) => value.oauthSignInPersistence),
778
+ );
779
+
780
+ export const oauthRegistrationIntentsLayer = <E, R>(
781
+ services: Effect.Effect<
782
+ { readonly oauthRegistrationIntents: OAuthRegistrationIntents["Service"] },
783
+ E,
784
+ R
785
+ >,
786
+ ) =>
787
+ Layer.effect(
788
+ OAuthRegistrationIntents,
789
+ Effect.map(services, (value) => value.oauthRegistrationIntents),
790
+ );
791
+
792
+ export const oauthRegistrationAuthorityLayer = <Id, Registration, E, R>(
793
+ tag: Context.Key<Id, OAuthRegistrationAuthority<Registration>>,
794
+ services: Effect.Effect<
795
+ { readonly registrationAuthority: OAuthRegistrationAuthority<Registration> },
796
+ E,
797
+ R
798
+ >,
799
+ ) =>
800
+ Layer.effect(
801
+ tag,
802
+ Effect.map(services, (value) => value.registrationAuthority),
803
+ );