@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,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
- };