@yielded/auth-persistence-drizzle 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 (189) hide show
  1. package/README.md +3 -0
  2. package/dist/D1.d.mts +9 -9
  3. package/dist/D1.mjs +2 -1
  4. package/dist/D1.mjs.map +1 -1
  5. package/dist/Libsql.d.mts +48 -64
  6. package/dist/Libsql.mjs +2 -1
  7. package/dist/Libsql.mjs.map +1 -1
  8. package/dist/Mysql2.d.mts +26 -27
  9. package/dist/Pglite.d.mts +28 -28
  10. package/dist/Pglite.mjs +2 -1
  11. package/dist/Pglite.mjs.map +1 -1
  12. package/dist/Postgres.d.mts +29 -29
  13. package/dist/Postgres.mjs +2 -1
  14. package/dist/Postgres.mjs.map +1 -1
  15. package/dist/SqliteBun.d.mts +49 -65
  16. package/dist/SqliteBun.mjs +2 -1
  17. package/dist/SqliteBun.mjs.map +1 -1
  18. package/dist/SqliteDo.d.mts +47 -63
  19. package/dist/SqliteDo.mjs +2 -1
  20. package/dist/SqliteDo.mjs.map +1 -1
  21. package/dist/SqliteNode.d.mts +51 -67
  22. package/dist/SqliteNode.mjs +2 -1
  23. package/dist/SqliteNode.mjs.map +1 -1
  24. package/dist/SqliteWasm.d.mts +47 -63
  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 +15 -15
  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 +1 -1
  33. package/dist/drizzle/d1-password-prepared.mjs.map +1 -1
  34. package/dist/drizzle/d1-passwords.d.mts +17 -33
  35. package/dist/drizzle/d1-passwords.mjs +57 -199
  36. package/dist/drizzle/d1-passwords.mjs.map +1 -1
  37. package/dist/drizzle/d1-phone.d.mts +1 -1
  38. package/dist/drizzle/d1-proofs.d.mts +1 -1
  39. package/dist/drizzle/d1-sessions.d.mts +2 -2
  40. package/dist/drizzle/d1-sessions.mjs +11 -6
  41. package/dist/drizzle/d1-sessions.mjs.map +1 -1
  42. package/dist/drizzle/d1-totp.d.mts +1 -1
  43. package/dist/drizzle/mysql-password-prepared.d.mts +5 -5
  44. package/dist/drizzle/mysql-password-prepared.mjs.map +1 -1
  45. package/dist/drizzle/mysql-passwords.d.mts +16 -32
  46. package/dist/drizzle/mysql-passwords.mjs.map +1 -1
  47. package/dist/drizzle/mysql-sessions.d.mts +1 -1
  48. package/dist/drizzle/native-sql-table.mjs +26 -1
  49. package/dist/drizzle/native-sql-table.mjs.map +1 -1
  50. package/dist/drizzle/oauth-connected-model.d.mts +20 -281
  51. package/dist/drizzle/oauth-connected-model.mjs +3 -17
  52. package/dist/drizzle/oauth-connected-model.mjs.map +1 -1
  53. package/dist/drizzle/oauth-connected-reference.d.mts +1 -3
  54. package/dist/drizzle/oauth-connected-reference.mjs +4 -53
  55. package/dist/drizzle/oauth-connected-reference.mjs.map +1 -1
  56. package/dist/drizzle/oauth-connected-target.d.mts +3 -990
  57. package/dist/drizzle/oauth-connected-target.mjs +8 -92
  58. package/dist/drizzle/oauth-connected-target.mjs.map +1 -1
  59. package/dist/drizzle/oauth-kernel.mjs +11 -0
  60. package/dist/drizzle/oauth-kernel.mjs.map +1 -0
  61. package/dist/drizzle/oauth-model.d.mts +35 -438
  62. package/dist/drizzle/oauth-model.mjs +6 -44
  63. package/dist/drizzle/oauth-model.mjs.map +1 -1
  64. package/dist/drizzle/oauth-mysql.mjs +2 -2
  65. package/dist/drizzle/oauth-mysql.mjs.map +1 -1
  66. package/dist/drizzle/oauth-proxy.d.mts +10 -0
  67. package/dist/drizzle/oauth-proxy.mjs +54 -0
  68. package/dist/drizzle/oauth-proxy.mjs.map +1 -0
  69. package/dist/drizzle/oauth-target.d.mts +6 -107
  70. package/dist/drizzle/oauth-target.mjs +15 -245
  71. package/dist/drizzle/oauth-target.mjs.map +1 -1
  72. package/dist/drizzle/password-model.d.mts +5 -6
  73. package/dist/drizzle/password-prepared-model.d.mts +5 -5
  74. package/dist/drizzle/password-prepared-model.mjs.map +1 -1
  75. package/dist/drizzle/password-prepared-sql.mjs +1 -1
  76. package/dist/drizzle/password-prepared-sql.mjs.map +1 -1
  77. package/dist/drizzle/password-prepared-state.mjs +1 -1
  78. package/dist/drizzle/pg-password-prepared.d.mts +5 -5
  79. package/dist/drizzle/pg-password-prepared.mjs.map +1 -1
  80. package/dist/drizzle/pg-passwords.d.mts +16 -32
  81. package/dist/drizzle/pg-passwords.mjs.map +1 -1
  82. package/dist/drizzle/pg-sessions.d.mts +1 -1
  83. package/dist/drizzle/phone-target.d.mts +1 -1
  84. package/dist/drizzle/phone-target.mjs +1 -1
  85. package/dist/drizzle/query-operations.mjs +3 -1
  86. package/dist/drizzle/query-operations.mjs.map +1 -1
  87. package/dist/drizzle/sqlite-password-prepared.mjs.map +1 -1
  88. package/dist/drizzle/sqlite-passwords.mjs.map +1 -1
  89. package/dist/drizzle/storage-validation.mjs +10 -185
  90. package/dist/drizzle/storage-validation.mjs.map +1 -1
  91. package/dist/drizzle/totp-target.d.mts +2 -2
  92. package/dist/drizzle/totp-target.mjs +2 -2
  93. package/dist/drizzle/transaction-execution.d.mts +2 -2
  94. package/dist/drizzle/transaction-owner.d.mts +1 -2
  95. package/dist/index.d.mts +3 -2
  96. package/dist/internal/drizzle-postgres-oauth-proxy.d.mts +138 -0
  97. package/dist/internal/drizzle-postgres-oauth-proxy.mjs +29 -0
  98. package/dist/internal/drizzle-postgres-oauth-proxy.mjs.map +1 -0
  99. package/dist/internal/drizzle-sqlite-oauth-proxy.d.mts +146 -0
  100. package/dist/internal/drizzle-sqlite-oauth-proxy.mjs +29 -0
  101. package/dist/internal/drizzle-sqlite-oauth-proxy.mjs.map +1 -0
  102. package/package.json +1 -1
  103. package/src/D1.ts +2 -0
  104. package/src/Libsql.ts +2 -0
  105. package/src/Pglite.ts +2 -0
  106. package/src/Postgres.ts +2 -0
  107. package/src/SqliteBun.ts +2 -0
  108. package/src/SqliteDo.ts +2 -0
  109. package/src/SqliteNode.ts +2 -0
  110. package/src/SqliteWasm.ts +2 -0
  111. package/src/drizzle/d1-password-prepared.ts +4 -6
  112. package/src/drizzle/d1-passwords.ts +125 -459
  113. package/src/drizzle/d1-sessions.ts +21 -6
  114. package/src/drizzle/mysql-password-prepared.ts +4 -8
  115. package/src/drizzle/mysql-passwords.ts +5 -10
  116. package/src/drizzle/native-sql-table.ts +30 -0
  117. package/src/drizzle/oauth-connected-model.ts +97 -315
  118. package/src/drizzle/oauth-connected-reference.ts +13 -93
  119. package/src/drizzle/oauth-connected-target.ts +14 -271
  120. package/src/drizzle/oauth-kernel.ts +10 -0
  121. package/src/drizzle/oauth-model.ts +188 -541
  122. package/src/drizzle/oauth-mysql.ts +2 -2
  123. package/src/drizzle/oauth-proxy.ts +101 -0
  124. package/src/drizzle/oauth-target.ts +33 -737
  125. package/src/drizzle/password-model.ts +0 -13
  126. package/src/drizzle/password-prepared-model.ts +0 -6
  127. package/src/drizzle/password-prepared-sql.ts +2 -1
  128. package/src/drizzle/pg-password-prepared.ts +4 -8
  129. package/src/drizzle/pg-passwords.ts +5 -10
  130. package/src/drizzle/query-operations.ts +4 -0
  131. package/src/drizzle/sqlite-password-prepared.ts +4 -8
  132. package/src/drizzle/sqlite-passwords.ts +7 -14
  133. package/src/drizzle/storage-validation.ts +13 -214
  134. package/src/index.ts +2 -4
  135. package/src/internal/drizzle-postgres-oauth-proxy.ts +37 -0
  136. package/src/internal/drizzle-sqlite-oauth-proxy.ts +44 -0
  137. package/dist/_virtual/_rolldown/runtime.mjs +0 -13
  138. package/dist/drizzle/oauth-accounts.mjs +0 -359
  139. package/dist/drizzle/oauth-accounts.mjs.map +0 -1
  140. package/dist/drizzle/oauth-connected-access.mjs +0 -212
  141. package/dist/drizzle/oauth-connected-access.mjs.map +0 -1
  142. package/dist/drizzle/oauth-connected-collection.mjs +0 -202
  143. package/dist/drizzle/oauth-connected-collection.mjs.map +0 -1
  144. package/dist/drizzle/oauth-connected-custody.mjs +0 -147
  145. package/dist/drizzle/oauth-connected-custody.mjs.map +0 -1
  146. package/dist/drizzle/oauth-connected-flow.mjs +0 -297
  147. package/dist/drizzle/oauth-connected-flow.mjs.map +0 -1
  148. package/dist/drizzle/oauth-connected-input.mjs +0 -122
  149. package/dist/drizzle/oauth-connected-input.mjs.map +0 -1
  150. package/dist/drizzle/oauth-connected-maintenance.mjs +0 -214
  151. package/dist/drizzle/oauth-connected-maintenance.mjs.map +0 -1
  152. package/dist/drizzle/oauth-connected-management.mjs +0 -229
  153. package/dist/drizzle/oauth-connected-management.mjs.map +0 -1
  154. package/dist/drizzle/oauth-connected-settlement.mjs +0 -128
  155. package/dist/drizzle/oauth-connected-settlement.mjs.map +0 -1
  156. package/dist/drizzle/oauth-connected-sign-in.mjs +0 -265
  157. package/dist/drizzle/oauth-connected-sign-in.mjs.map +0 -1
  158. package/dist/drizzle/oauth-connected-state.d.mts +0 -2062
  159. package/dist/drizzle/oauth-connected-state.mjs +0 -430
  160. package/dist/drizzle/oauth-connected-state.mjs.map +0 -1
  161. package/dist/drizzle/oauth-flow.d.mts +0 -22
  162. package/dist/drizzle/oauth-flow.mjs +0 -253
  163. package/dist/drizzle/oauth-flow.mjs.map +0 -1
  164. package/dist/drizzle/oauth-input.mjs +0 -127
  165. package/dist/drizzle/oauth-input.mjs.map +0 -1
  166. package/dist/drizzle/oauth-owner.d.mts +0 -10
  167. package/dist/drizzle/oauth-owner.mjs +0 -10
  168. package/dist/drizzle/oauth-owner.mjs.map +0 -1
  169. package/dist/drizzle/oauth-registration.d.mts +0 -5
  170. package/dist/drizzle/oauth-registration.mjs +0 -374
  171. package/dist/drizzle/oauth-registration.mjs.map +0 -1
  172. package/dist/drizzle/oauth-state.mjs +0 -107
  173. package/dist/drizzle/oauth-state.mjs.map +0 -1
  174. package/src/drizzle/oauth-accounts.ts +0 -805
  175. package/src/drizzle/oauth-connected-access.ts +0 -361
  176. package/src/drizzle/oauth-connected-collection.ts +0 -324
  177. package/src/drizzle/oauth-connected-custody.ts +0 -279
  178. package/src/drizzle/oauth-connected-flow.ts +0 -529
  179. package/src/drizzle/oauth-connected-input.ts +0 -147
  180. package/src/drizzle/oauth-connected-maintenance.ts +0 -346
  181. package/src/drizzle/oauth-connected-management.ts +0 -365
  182. package/src/drizzle/oauth-connected-settlement.ts +0 -251
  183. package/src/drizzle/oauth-connected-sign-in.ts +0 -443
  184. package/src/drizzle/oauth-connected-state.ts +0 -872
  185. package/src/drizzle/oauth-flow.ts +0 -462
  186. package/src/drizzle/oauth-input.ts +0 -201
  187. package/src/drizzle/oauth-owner.ts +0 -29
  188. package/src/drizzle/oauth-registration.ts +0 -640
  189. package/src/drizzle/oauth-state.ts +0 -218
@@ -1,872 +0,0 @@
1
- import { type OAuthAccountRevision, snapshotOAuthSync } from "@yielded/auth/OAuth";
2
- import * as M from "@yielded/auth/OAuth";
3
- import { AuthenticationRequirement } from "@yielded/auth/Sessions";
4
- /* oxlint-disable no-explicit-any -- private table erasure retains declared callback error/requirements channels. */
5
- import { and, eq, isNull, sql, type SQL, type Table } from "drizzle-orm";
6
- import type { EffectDrizzleQueryError } from "drizzle-orm/effect-core";
7
- import { DateTime, Effect, Schema } from "effect";
8
-
9
- import type {
10
- OAuthConnectedAuthorityMapping,
11
- OAuthConnectedClientRegistrationTable,
12
- OAuthConnectedCohortTable,
13
- OAuthConnectedMapping,
14
- OAuthConnectedPolicyInput,
15
- OAuthConnectedRevocationMapping,
16
- } from "./oauth-connected-model";
17
- import { currentSubject } from "./oauth-flow";
18
- import {
19
- both,
20
- col,
21
- copiedRow,
22
- equal,
23
- CurrentOAuthTransaction,
24
- type OAuthOwner,
25
- type Row,
26
- } from "./oauth-owner";
27
- import {
28
- digest,
29
- invariant,
30
- oauthIdentityKey,
31
- sameRevision,
32
- satisfies,
33
- storage,
34
- unavailable,
35
- } from "./oauth-state";
36
-
37
- export type Authority = OAuthConnectedAuthorityMapping<any, any, any, any, any, any, any> & {
38
- readonly client: Partial<Pick<OAuthConnectedClientRegistrationTable<any>, "encodeInsert">>;
39
- readonly cohort: Partial<Pick<OAuthConnectedCohortTable<any>, "encodeInsert">>;
40
- };
41
-
42
- export type Mapping = OAuthConnectedMapping<
43
- any,
44
- any,
45
- any,
46
- any,
47
- any,
48
- any,
49
- any,
50
- any,
51
- any,
52
- any,
53
- any,
54
- any
55
- >;
56
-
57
- export type Revocations = OAuthConnectedRevocationMapping<any, any, any, any, any, any, any, any>;
58
- export type Current = NonNullable<Effect.Success<ReturnType<typeof currentSubject>>>;
59
- export const contextStorage = storage(M.OAuthConnectedTransactionContext);
60
- export const flowStorage = storage(M.OAuthConnectedPendingFlow);
61
- export const claimStorage = storage(M.OAuthConnectedClaim);
62
- export const tokenContextStorage = storage(M.OAuthConnectedTokenContext);
63
- export const sealedStorage = storage(M.OAuthConnectedSealedTokens);
64
- export const grantStorage = storage(M.OAuthConnectedStoredGrant);
65
-
66
- export const admissionStorage = storage(
67
- Schema.Struct({
68
- grant: M.OAuthConnectedStoredGrant,
69
- authorization: M.OAuthConnectedUseAuthorization,
70
- }),
71
- );
72
-
73
- export const commandStorage = storage(
74
- Schema.Struct({
75
- context: M.OAuthConnectedTokenContext,
76
- originalDigest: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/)),
77
- revocationJobId: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(256))),
78
- }),
79
- );
80
-
81
- export const disconnectStorage = storage(M.OAuthConnectedDisconnectGrant);
82
- export const refreshStorage = storage(M.OAuthConnectedRefreshClaim);
83
- export const jobStorage = storage(M.OAuthConnectedRevocationJob);
84
- export const summaryStorage = storage(M.OAuthConnectedSummary);
85
- export const disconnectedStorage = storage(M.OAuthConnectedDisconnected);
86
- const strings = storage(Schema.Array(Schema.String));
87
-
88
- const safeInteger = Schema.Int.check(
89
- Schema.isBetween({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
90
- );
91
-
92
- const orderParser = Schema.decodeSync(safeInteger);
93
- const canonicalOrder = Schema.String.check(Schema.isPattern(/^(0|[1-9][0-9]{0,15})$/));
94
- const orderTextParser = Schema.decodeSync(canonicalOrder);
95
-
96
- export const orderNumber = (value: string) => {
97
- const number = orderParser(Number(orderTextParser(value)));
98
-
99
- invariant(String(number) === value);
100
-
101
- return number;
102
- };
103
-
104
- export const nativeOrder = (mapping: Authority, value: unknown) =>
105
- orderParser(mapping.order.decode(value));
106
-
107
- const encoder = new TextEncoder();
108
- const decoder = new TextDecoder("utf-8", { fatal: true });
109
-
110
- const key = Effect.fnUntraced(function* (domain: string, fields: ReadonlyArray<string>) {
111
- for (const value of fields) invariant(decoder.decode(encoder.encode(value)) === value);
112
-
113
- return "v1:" + (yield* digest(strings.encode([domain, ...fields])));
114
- });
115
-
116
- export const clientKey = (configuration: M.OAuthConnectedConfiguration) =>
117
- key("effect-auth/oauth-connected-client/v1", [
118
- configuration.provider,
119
- configuration.issuer,
120
- configuration.profile.clientRegistrationId,
121
- ]);
122
-
123
- export const cohortKey = (client: string, identity: string) =>
124
- key("effect-auth/oauth-connected-cohort/v1", [client, identity]);
125
-
126
- export const initialGeneration = (cohort: string) =>
127
- digest(cohort).pipe(
128
- Effect.map((value) => M.OAuthConnectedTarget.fields.cohortGeneration.make("initial:" + value)),
129
- );
130
-
131
- export const configuration = (flow: M.OAuthConnectedPendingFlow): M.OAuthConnectedConfiguration =>
132
- snapshotOAuthSync(M.OAuthConnectedConfiguration, flow.context);
133
-
134
- export const retainedUntil = (mapping: Authority, now: number) => {
135
- invariant(
136
- Number.isSafeInteger(mapping.retentionMillis) &&
137
- mapping.retentionMillis >= 120000 &&
138
- mapping.retentionMillis <= 2592000000,
139
- );
140
-
141
- return orderParser(now + mapping.retentionMillis);
142
- };
143
-
144
- export const rowCount = Effect.fn("oauthConnected.rowCount")(function* (table: Table, where: SQL) {
145
- const owner = yield* CurrentOAuthTransaction;
146
-
147
- const rows = yield* (
148
- owner.database
149
- .select({ value: sql<number>`count(*)`.mapWith(Number) })
150
- .from(table)
151
- .where(where) as Effect.Effect<ReadonlyArray<{ value: number }>, EffectDrizzleQueryError>
152
- ).pipe(Effect.mapError(unavailable));
153
-
154
- invariant(rows.length === 1);
155
-
156
- return orderParser(rows[0]!.value);
157
- });
158
-
159
- export const countCondition = (table: Table, where: SQL, count: number) =>
160
- sql`(select count(*) from ${table} where ${where}) = ${count}`;
161
-
162
- export const current = Effect.fn("oauthConnected.current")(function* (
163
- mapping: Mapping,
164
- subject: OAuthAccountRevision["subjectId"],
165
- ) {
166
- const native = yield* mapping.subjectId.toNative(subject);
167
-
168
- return yield* currentSubject(mapping, native);
169
- });
170
-
171
- export const nativeCopy = <N>(value: N): N => copiedRow({ value }).value as N;
172
-
173
- const policyData = (input: OAuthConnectedPolicyInput<any>): OAuthConnectedPolicyInput<any> =>
174
- Object.freeze({
175
- ...input,
176
- subjectId: nativeCopy(input.subjectId),
177
- revision: snapshotOAuthSync(M.OAuthConnectedUseAuthorization.fields.revision, input.revision),
178
- ...(input.kind === "sign-in"
179
- ? {
180
- credential: snapshotOAuthSync(M.OAuthCredentialSnapshot, input.credential),
181
- configuration: snapshotOAuthSync(M.OAuthConnectedConfiguration, input.configuration),
182
- }
183
- : {
184
- authorization:
185
- input.kind === "action"
186
- ? snapshotOAuthSync(M.OAuthConnectedActionAuthorization, input.authorization)
187
- : snapshotOAuthSync(M.OAuthConnectedUseAuthorization, input.authorization),
188
- ...(input.kind === "action"
189
- ? {
190
- configuration: snapshotOAuthSync(
191
- M.OAuthConnectedConfiguration,
192
- input.configuration,
193
- ),
194
- }
195
- : {}),
196
- }),
197
- ...(input.grant === undefined
198
- ? {}
199
- : { grant: snapshotOAuthSync(M.OAuthConnectedTokenContext, input.grant) }),
200
- }) as OAuthConnectedPolicyInput<any>;
201
-
202
- export const jobTable = (mapping: Authority) => {
203
- const direct = (mapping as Partial<Revocations>).job;
204
- const configured = (mapping as Partial<Mapping>).revocation;
205
-
206
- return direct ?? (configured?.mode === "cohort" ? configured.job : undefined);
207
- };
208
-
209
- export const policy = Effect.fn("oauthConnected.policy")(function* (
210
- mapping: Mapping,
211
- input: OAuthConnectedPolicyInput<any>,
212
- ) {
213
- const owner = yield* CurrentOAuthTransaction;
214
-
215
- invariant((mapping.policy.guards?.length ?? 0) <= 32);
216
- for (const guard of mapping.policy.guards ?? []) {
217
- const read = yield* owner.read(guard.table, guard.condition(policyData(input)), {
218
- orderBy: col(guard.table, guard.orderBy),
219
- });
220
-
221
- invariant(read.rows.length > 0);
222
- }
223
- const condition = mapping.policy.condition(policyData(input));
224
- const accepted = yield* owner.check(condition);
225
-
226
- if (accepted) owner.postconditions.push(condition);
227
-
228
- return accepted;
229
- });
230
-
231
- export const action = Effect.fn("oauthConnected.action")(function* (
232
- mapping: Mapping,
233
- found: Current,
234
- authorization: M.OAuthConnectedActionAuthorization,
235
- operation: "issue" | "claim" | "settle" | "disconnect",
236
- expected: {
237
- readonly moduleId: string;
238
- readonly flowId: string;
239
- readonly commandId: string;
240
- readonly revision: OAuthAccountRevision;
241
- readonly intent: string;
242
- readonly maximumAgeMillis: number;
243
- readonly configuration: M.OAuthConnectedConfiguration;
244
- readonly grant?: M.OAuthConnectedTokenContext;
245
- },
246
- ) {
247
- const owner = yield* CurrentOAuthTransaction;
248
-
249
- const challenge = authorization.challenge,
250
- evidence = authorization.evidence;
251
-
252
- const name =
253
- operation === "issue"
254
- ? "connected-begin"
255
- : operation === "disconnect"
256
- ? "connected-disconnect"
257
- : "connected-complete";
258
-
259
- const intentDigest = yield* digest(expected.intent);
260
-
261
- const binding = yield* digest(
262
- strings.encode([
263
- "effect-auth/oauth-connected-action/v1",
264
- expected.moduleId,
265
- name,
266
- expected.flowId,
267
- expected.commandId,
268
- intentDigest,
269
- ]),
270
- );
271
-
272
- if (
273
- challenge.moduleId !== expected.moduleId ||
274
- challenge.action !== name ||
275
- challenge.flowId !== expected.flowId ||
276
- challenge.commandId !== expected.commandId ||
277
- challenge.intentDigest !== intentDigest ||
278
- challenge.bindingDigest !== binding ||
279
- evidence.flowId !== expected.flowId ||
280
- evidence.bindingDigest !== binding ||
281
- !sameRevision(challenge.revision, expected.revision) ||
282
- !sameRevision(evidence.revision, expected.revision) ||
283
- !sameRevision(found.revision, expected.revision)
284
- )
285
- return false;
286
- const now = yield* owner.now(mapping.clock);
287
-
288
- const requirement = snapshotOAuthSync(
289
- AuthenticationRequirement,
290
- mapping.subject.decodeActionRequirement(copiedRow(found.row), name),
291
- );
292
-
293
- const revisions = new Map(
294
- found.revision.credentials.map((item) => [item.credentialId, item.revision]),
295
- );
296
-
297
- if (
298
- evidence.proofs.some(
299
- (proof) =>
300
- !revisions.has(proof.credentialId) || DateTime.toEpochMillis(proof.verifiedAt) > now,
301
- )
302
- )
303
- return false;
304
- for (const required of [authorization.requirement, requirement]) {
305
- const age = Math.min(expected.maximumAgeMillis, required.maximumAgeMillis);
306
-
307
- const fresh = evidence.proofs.filter(
308
- (proof) => now - DateTime.toEpochMillis(proof.verifiedAt) < age,
309
- );
310
-
311
- if (!satisfies(fresh, required)) return false;
312
- owner.postconditions.push(
313
- sql`${mapping.clock.engineNowMillis} >= ${now} and ${both(...fresh.map((proof) => sql`${mapping.clock.engineNowMillis} < ${DateTime.toEpochMillis(proof.verifiedAt) + age}`))}`,
314
- );
315
- }
316
-
317
- return yield* policy(mapping, {
318
- subjectId: found.nativeId,
319
- revision: found.revision,
320
- kind: "action",
321
- operation,
322
- authorization,
323
- configuration: expected.configuration,
324
- ...(expected.grant === undefined ? {} : { grant: expected.grant }),
325
- });
326
- });
327
-
328
- export const useAuthority = Effect.fn("oauthConnected.useAuthority")(function* (
329
- mapping: Mapping,
330
- authorization: M.OAuthConnectedUseAuthorization,
331
- kind: "metadata" | "use",
332
- grant?: M.OAuthConnectedTokenContext,
333
- ) {
334
- const owner = yield* CurrentOAuthTransaction;
335
- const found = yield* current(mapping, authorization.revision.subjectId);
336
- const now = yield* owner.now(mapping.clock);
337
-
338
- if (
339
- found === undefined ||
340
- authorization.purpose !== kind ||
341
- !sameRevision(found.revision, authorization.revision) ||
342
- now >= authorization.expiresAtMillis ||
343
- (grant !== undefined &&
344
- (grant.moduleId !== authorization.moduleId ||
345
- grant.subjectId !== authorization.revision.subjectId ||
346
- (authorization.grantId !== undefined && authorization.grantId !== grant.grantId) ||
347
- (authorization.profileKey !== undefined &&
348
- authorization.profileKey !== grant.configuration.profile.key)))
349
- )
350
- return undefined;
351
- if (
352
- !(yield* policy(mapping, {
353
- subjectId: found.nativeId,
354
- revision: found.revision,
355
- kind,
356
- authorization,
357
- ...(grant === undefined ? {} : { grant }),
358
- }))
359
- )
360
- return undefined;
361
- owner.postconditions.push(
362
- sql`${mapping.clock.engineNowMillis} >= ${now} and ${mapping.clock.engineNowMillis} < ${authorization.expiresAtMillis}`,
363
- );
364
-
365
- return found;
366
- });
367
-
368
- export const scopeKey = (provider: string, issuer: string) =>
369
- key("effect-auth/oauth-connected-provider-issuer/v1", [provider, issuer]).pipe(
370
- Effect.map((value) => "scope:" + value),
371
- );
372
-
373
- export interface ScopeAnchor {
374
- readonly id: string;
375
- row: Row;
376
- }
377
-
378
- export interface ClientAnchor {
379
- readonly id: string;
380
- row: Row;
381
- counter: number;
382
- readonly scope: ScopeAnchor;
383
- }
384
-
385
- const scopeCache = new WeakMap<OAuthOwner, Map<Table, Map<string, ScopeAnchor>>>();
386
- const clientCache = new WeakMap<OAuthOwner, Map<Table, Map<string, ClientAnchor>>>();
387
-
388
- const anchors = Effect.fn("oauthConnected.anchors")(function* <A>(
389
- cache: WeakMap<OAuthOwner, Map<Table, Map<string, A>>>,
390
- table: Table,
391
- ) {
392
- const owner = yield* CurrentOAuthTransaction;
393
- let tables = cache.get(owner);
394
-
395
- if (tables === undefined) {
396
- tables = new Map();
397
- cache.set(owner, tables);
398
- }
399
- let rows = tables.get(table);
400
-
401
- if (rows === undefined) {
402
- rows = new Map();
403
- tables.set(table, rows);
404
- }
405
-
406
- return rows;
407
- });
408
-
409
- /** Empty registration ID is impossible in the public profile label schema. The
410
- * distinct 52-byte key domain shares the physical table, never a client counter. */
411
- export const scope = Effect.fn("oauthConnected.scope")(function* (
412
- mapping: Authority,
413
- config: M.OAuthConnectedConfiguration,
414
- create: boolean,
415
- ) {
416
- const owner = yield* CurrentOAuthTransaction;
417
-
418
- const c = mapping.client,
419
- id = yield* scopeKey(config.provider, config.issuer),
420
- cache = yield* anchors(scopeCache, c.table);
421
-
422
- const existing = cache.get(id);
423
-
424
- if (existing !== undefined) return existing;
425
- const read = yield* owner.read(c.table, equal(c.table, { [c.clientKey]: id }), { limit: 1 });
426
-
427
- if (read.rows.length === 0) {
428
- const related = [
429
- mapping.flow,
430
- mapping.grant,
431
- mapping.cohort,
432
- ...(jobTable(mapping) === undefined ? [] : [jobTable(mapping)!]),
433
- ];
434
-
435
- invariant(
436
- yield* owner.check(
437
- both(
438
- sql`not exists(select 1 from ${c.table} where ${owner.exact(c.table, { [c.provider]: config.provider, [c.issuer]: config.issuer })})`,
439
- ...related.map(
440
- (v) =>
441
- sql`not exists(select 1 from ${v.table} where not exists(select 1 from ${c.table} where ${col(c.table, c.clientKey)} = ${col(v.table, v.clientKey)}))`,
442
- ),
443
- ),
444
- ),
445
- );
446
- if (!create) return undefined;
447
- invariant(c.encodeInsert !== undefined);
448
-
449
- const inserted = yield* owner.insert(
450
- c.table,
451
- {
452
- ...c.encodeInsert(snapshotOAuthSync(M.OAuthConnectedConfiguration, config)),
453
- [c.clientKey]: id,
454
- [c.provider]: config.provider,
455
- [c.issuer]: config.issuer,
456
- [c.clientRegistrationId]: "",
457
- [c.counter]: mapping.order.encode(0),
458
- [c.version]: owner.marker,
459
- },
460
- { [c.clientKey]: id },
461
- true,
462
- );
463
-
464
- read.rows = inserted.rows;
465
- }
466
- const row = read.rows[0]!;
467
-
468
- invariant(
469
- row[c.provider] === config.provider &&
470
- row[c.issuer] === config.issuer &&
471
- row[c.clientRegistrationId] === "" &&
472
- nativeOrder(mapping, row[c.counter]) === 0,
473
- );
474
- const found = { id, row };
475
-
476
- cache.set(id, found);
477
-
478
- return found;
479
- });
480
-
481
- export const touchScope = Effect.fn("oauthConnected.touchScope")(function* (
482
- mapping: Authority,
483
- found: ScopeAnchor,
484
- ) {
485
- const owner = yield* CurrentOAuthTransaction;
486
- const c = mapping.client;
487
-
488
- yield* owner.update(
489
- c.table,
490
- { [c.clientKey]: found.id, [c.version]: found.row[c.version] },
491
- { [c.version]: owner.marker },
492
- );
493
- found.row = { ...found.row, [c.version]: owner.marker };
494
- });
495
-
496
- export const readScope = Effect.fn("oauthConnected.readScope")(function* (
497
- mapping: Authority,
498
- provider: string,
499
- issuer: string,
500
- ) {
501
- const owner = yield* CurrentOAuthTransaction;
502
-
503
- const c = mapping.client,
504
- id = yield* scopeKey(provider, issuer),
505
- cache = yield* anchors(scopeCache, c.table),
506
- cached = cache.get(id);
507
-
508
- if (cached !== undefined) return cached;
509
-
510
- const read = yield* owner.read(c.table, equal(c.table, { [c.clientKey]: id }), { limit: 1 }),
511
- row = read.rows[0];
512
-
513
- invariant(
514
- row !== undefined &&
515
- row[c.provider] === provider &&
516
- row[c.issuer] === issuer &&
517
- row[c.clientRegistrationId] === "" &&
518
- nativeOrder(mapping, row[c.counter]) === 0,
519
- );
520
- const found = { id, row };
521
-
522
- cache.set(id, found);
523
-
524
- return found;
525
- });
526
-
527
- export const lockClientById = Effect.fn("oauthConnected.lockClientById")(function* (
528
- mapping: Authority,
529
- id: string,
530
- ) {
531
- const owner = yield* CurrentOAuthTransaction;
532
-
533
- const c = mapping.client,
534
- cache = yield* anchors(clientCache, c.table),
535
- cached = cache.get(id);
536
-
537
- if (cached !== undefined) return cached;
538
-
539
- const discovery = yield* owner.read(c.table, equal(c.table, { [c.clientKey]: id }), {
540
- limit: 1,
541
- observe: false,
542
- lock: false,
543
- }),
544
- initial = discovery.rows[0];
545
-
546
- invariant(initial !== undefined);
547
- const scope = yield* readScope(mapping, initial[c.provider], initial[c.issuer]);
548
-
549
- const read = yield* owner.read(c.table, equal(c.table, { [c.clientKey]: id }), { limit: 1 }),
550
- row = read.rows[0];
551
-
552
- invariant(
553
- row !== undefined &&
554
- row[c.clientRegistrationId] !== "" &&
555
- id ===
556
- (yield* key("effect-auth/oauth-connected-client/v1", [
557
- row[c.provider],
558
- row[c.issuer],
559
- row[c.clientRegistrationId],
560
- ])) &&
561
- scope.id === (yield* scopeKey(row[c.provider], row[c.issuer])),
562
- );
563
- const found = { id, row, counter: nativeOrder(mapping, row[c.counter]), scope };
564
-
565
- cache.set(id, found);
566
-
567
- return found;
568
- });
569
-
570
- export const heldScope = Effect.fnUntraced(function* (
571
- mapping: Authority,
572
- provider: string,
573
- issuer: string,
574
- ) {
575
- const cache = yield* anchors(scopeCache, mapping.client.table);
576
-
577
- return cache.get(yield* scopeKey(provider, issuer));
578
- });
579
-
580
- /** Cleanup obtains every scope then every client before entering any tuple. */
581
- export const prelockClients = Effect.fn("oauthConnected.prelockClients")(function* (
582
- mapping: Authority,
583
- ids: ReadonlyArray<string>,
584
- releaseScopes: ReadonlyArray<{ readonly provider: string; readonly issuer: string }> = [],
585
- ) {
586
- const owner = yield* CurrentOAuthTransaction;
587
-
588
- const unique = [...new Set(ids)].sort(),
589
- c = mapping.client;
590
-
591
- invariant(unique.length <= 4000);
592
- const scopes = new Map<string, { provider: string; issuer: string; required: boolean }>();
593
-
594
- for (const value of releaseScopes)
595
- scopes.set(yield* scopeKey(value.provider, value.issuer), { ...value, required: false });
596
- for (const id of unique) {
597
- const read = yield* owner.read(c.table, equal(c.table, { [c.clientKey]: id }), {
598
- limit: 1,
599
- lock: false,
600
- observe: false,
601
- }),
602
- row = read.rows[0];
603
-
604
- invariant(row !== undefined);
605
- scopes.set(yield* scopeKey(row[c.provider], row[c.issuer]), {
606
- provider: row[c.provider],
607
- issuer: row[c.issuer],
608
- required: true,
609
- });
610
- }
611
- for (const [id, { provider, issuer, required }] of [...scopes].sort(([a], [b]) =>
612
- a < b ? -1 : a > b ? 1 : 0,
613
- )) {
614
- if (required) yield* readScope(mapping, provider, issuer);
615
- else {
616
- const read = yield* owner.read(c.table, equal(c.table, { [c.clientKey]: id }), { limit: 1 }),
617
- row = read.rows[0];
618
-
619
- if (row !== undefined) {
620
- invariant(
621
- row[c.provider] === provider &&
622
- row[c.issuer] === issuer &&
623
- row[c.clientRegistrationId] === "" &&
624
- nativeOrder(mapping, row[c.counter]) === 0,
625
- );
626
- (yield* anchors(scopeCache, c.table)).set(id, { id, row });
627
- }
628
- }
629
- }
630
- for (const id of unique) yield* lockClientById(mapping, id);
631
- });
632
-
633
- export const client = Effect.fn("oauthConnected.client")(function* (
634
- mapping: Authority,
635
- config: M.OAuthConnectedConfiguration,
636
- create: boolean,
637
- ) {
638
- const owner = yield* CurrentOAuthTransaction;
639
-
640
- const c = mapping.client,
641
- id = yield* clientKey(config),
642
- where = { [c.clientKey]: id };
643
-
644
- const heldScope = yield* scope(mapping, config, create);
645
-
646
- if (heldScope === undefined) return undefined;
647
-
648
- const cache = yield* anchors(clientCache, c.table),
649
- cached = cache.get(id);
650
-
651
- if (cached !== undefined) return cached;
652
- let read = yield* owner.read(c.table, equal(c.table, where), { limit: 1 });
653
-
654
- if (read.rows.length === 0) {
655
- const related = [
656
- mapping.flow,
657
- mapping.grant,
658
- mapping.cohort,
659
- ...(jobTable(mapping) === undefined ? [] : [jobTable(mapping)!]),
660
- ];
661
-
662
- invariant(
663
- yield* owner.check(
664
- both(
665
- ...related.map(
666
- (v) =>
667
- sql`not exists(select 1 from ${v.table} where ${eq(col(v.table, v.clientKey), id)})`,
668
- ),
669
- ),
670
- ),
671
- );
672
- }
673
- if (read.rows.length === 0 && create) {
674
- const inserted = yield* owner.insert(
675
- c.table,
676
- {
677
- ...(invariant(c.encodeInsert !== undefined),
678
- c.encodeInsert(snapshotOAuthSync(M.OAuthConnectedConfiguration, config))),
679
- ...where,
680
- [c.provider]: config.provider,
681
- [c.issuer]: config.issuer,
682
- [c.clientRegistrationId]: config.profile.clientRegistrationId,
683
- [c.counter]: mapping.order.encode(0),
684
- [c.version]: owner.marker,
685
- },
686
- where,
687
- true,
688
- );
689
-
690
- read.rows = inserted.rows;
691
- }
692
- const row = read.rows[0];
693
-
694
- if (row === undefined) return undefined;
695
- invariant(
696
- row[c.provider] === config.provider &&
697
- row[c.issuer] === config.issuer &&
698
- row[c.clientRegistrationId] === config.profile.clientRegistrationId,
699
- );
700
- const found = { id, row, counter: nativeOrder(mapping, row[c.counter]), scope: heldScope };
701
-
702
- cache.set(id, found);
703
-
704
- return found;
705
- });
706
-
707
- export const nextOrder = Effect.fn("oauthConnected.nextOrder")(function* (
708
- mapping: Authority,
709
- found: NonNullable<Effect.Success<ReturnType<typeof client>>>,
710
- ) {
711
- const owner = yield* CurrentOAuthTransaction;
712
-
713
- const next = orderParser(found.counter + 1),
714
- c = mapping.client;
715
-
716
- yield* owner.update(
717
- c.table,
718
- { [c.clientKey]: found.id, [c.counter]: mapping.order.encode(found.counter) },
719
- { [c.counter]: mapping.order.encode(next), [c.version]: owner.marker },
720
- );
721
- found.counter = next;
722
-
723
- return next;
724
- });
725
-
726
- export const cohort = Effect.fn("oauthConnected.cohort")(function* (
727
- mapping: Authority,
728
- clientId: string,
729
- identityId: string,
730
- create: boolean,
731
- ) {
732
- const owner = yield* CurrentOAuthTransaction;
733
-
734
- const c = mapping.cohort,
735
- id = yield* cohortKey(clientId, identityId),
736
- where = { [c.cohortKey]: id };
737
-
738
- let read = yield* owner.read(c.table, equal(c.table, where), { limit: 1 });
739
-
740
- if (read.rows.length === 0) {
741
- const related = [
742
- mapping.flow,
743
- mapping.grant,
744
- ...(jobTable(mapping) === undefined ? [] : [jobTable(mapping)!]),
745
- ];
746
-
747
- invariant(
748
- yield* owner.check(
749
- both(
750
- ...related.map(
751
- (v) =>
752
- sql`not exists(select 1 from ${v.table} where ${eq(col(v.table, v.cohortKey), id)})`,
753
- ),
754
- ),
755
- ),
756
- );
757
- }
758
- if (read.rows.length === 0 && create) {
759
- const inserted = yield* owner.insert(
760
- c.table,
761
- {
762
- ...(invariant(c.encodeInsert !== undefined),
763
- c.encodeInsert({ clientKey: clientId, identityKey: identityId })),
764
- ...where,
765
- [c.clientKey]: clientId,
766
- [c.identityKey]: identityId,
767
- [c.generation]: yield* initialGeneration(id),
768
- [c.cutoff]: mapping.order.encode(0),
769
- [c.state]: "Open",
770
- [c.version]: owner.marker,
771
- },
772
- where,
773
- true,
774
- );
775
-
776
- read.rows = inserted.rows;
777
- }
778
- const row = read.rows[0];
779
-
780
- if (row === undefined)
781
- return {
782
- id,
783
- row: undefined,
784
- generation: yield* initialGeneration(id),
785
- cutoff: 0,
786
- blocked: false,
787
- };
788
- invariant(
789
- row[c.clientKey] === clientId &&
790
- row[c.identityKey] === identityId &&
791
- ["Open", "Blocked"].includes(row[c.state]),
792
- );
793
-
794
- const generation = snapshotOAuthSync(
795
- M.OAuthConnectedTarget.fields.cohortGeneration,
796
- row[c.generation],
797
- );
798
-
799
- return {
800
- id,
801
- row,
802
- generation,
803
- cutoff: nativeOrder(mapping, row[c.cutoff]),
804
- blocked: row[c.state] === "Blocked",
805
- };
806
- });
807
-
808
- export const readGrant = Effect.fn("oauthConnected.readGrant")(function* (
809
- mapping: Authority,
810
- moduleId: string,
811
- grantId: string,
812
- lock = true,
813
- ) {
814
- const owner = yield* CurrentOAuthTransaction;
815
- const g = mapping.grant;
816
-
817
- const read = yield* owner.read(
818
- g.table,
819
- equal(g.table, { [g.moduleId]: moduleId, [g.grantId]: grantId }),
820
- { limit: 1, lock, admissionOnly: lock, observe: false },
821
- );
822
-
823
- const row = read.rows[0];
824
-
825
- if (row === undefined) return undefined;
826
- if (lock) owner.observations.push(read);
827
- const context = tokenContextStorage.decode(row[g.context]);
828
- const native = yield* mapping.subjectId.toNative(context.subjectId);
829
-
830
- invariant(
831
- context.moduleId === moduleId &&
832
- context.grantId === grantId &&
833
- mapping.subjectId.equals(native, row[g.subjectId]) &&
834
- row[g.identityKey] === (yield* oauthIdentityKey(context.identity)) &&
835
- row[g.clientKey] === (yield* clientKey(context.configuration)) &&
836
- row[g.cohortKey] === (yield* cohortKey(row[g.clientKey], row[g.identityKey])) &&
837
- row[g.profileKey] === context.configuration.profile.key &&
838
- row[g.grantVersion] === context.grantVersion &&
839
- row[g.tokenVersion] === context.tokenVersion &&
840
- row[g.cohortGeneration] === context.cohortGeneration,
841
- );
842
-
843
- return {
844
- row,
845
- context,
846
- native,
847
- sealed: row[g.sealed] === null ? undefined : sealedStorage.decode(row[g.sealed]),
848
- };
849
- });
850
-
851
- export const unknownWork = (mapping: Authority, clientId: string | SQL, cutoff?: number) => {
852
- const f = mapping.flow;
853
-
854
- return both(
855
- eq(col(f.table, f.clientKey), clientId),
856
- eq(col(f.table, f.work), "Unresolved"),
857
- isNull(col(f.table, f.cohortKey)),
858
- cutoff === undefined
859
- ? undefined
860
- : sql`${col(f.table, f.claimOrder)} <= ${mapping.order.encode(cutoff)}`,
861
- );
862
- };
863
-
864
- export const cohortWork = (mapping: Authority, id: string | SQL) => {
865
- const f = mapping.flow,
866
- g = mapping.grant;
867
-
868
- return both(
869
- sql`not exists(select 1 from ${f.table} where ${and(eq(col(f.table, f.cohortKey), id), eq(col(f.table, f.work), "Unresolved"))})`,
870
- sql`not exists(select 1 from ${g.table} where ${and(eq(col(g.table, g.cohortKey), id), eq(col(g.table, g.refreshWork), "Unresolved"))})`,
871
- );
872
- };