@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.
- package/README.md +11 -0
- package/dist/Adapter.d.mts +12 -6
- package/dist/Adapter.mjs +8 -3
- package/dist/OAuthPersistence.d.mts +87 -0
- package/dist/OAuthPersistence.mjs +125 -0
- package/dist/OAuthPersistence.mjs.map +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +13 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +3 -1
- package/dist/internal/configuration.d.mts +1 -1
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/email-kernel.d.mts +1 -1
- package/dist/internal/email-kernel.mjs +1 -1
- package/dist/internal/email-kernel.mjs.map +1 -1
- package/dist/internal/models/oauth-connected-model.d.mts +294 -0
- package/dist/internal/models/oauth-connected-model.mjs +22 -0
- package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
- package/dist/internal/models/oauth-model.d.mts +467 -0
- package/dist/internal/models/oauth-model.mjs +49 -0
- package/dist/internal/models/oauth-model.mjs.map +1 -0
- package/dist/internal/models/password-model.d.mts +6 -48
- package/dist/internal/models/password-model.mjs +0 -1
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/native-sql-table.d.mts +4 -0
- package/dist/internal/native-sql-table.mjs +7 -0
- package/dist/internal/native-sql-table.mjs.map +1 -1
- package/dist/internal/oauth/accounts.d.mts +8 -0
- package/dist/internal/oauth/accounts.mjs +379 -0
- package/dist/internal/oauth/accounts.mjs.map +1 -0
- package/dist/internal/oauth/connected-access.d.mts +9 -0
- package/dist/internal/oauth/connected-access.mjs +229 -0
- package/dist/internal/oauth/connected-access.mjs.map +1 -0
- package/dist/internal/oauth/connected-collection.d.mts +7 -0
- package/dist/internal/oauth/connected-collection.mjs +214 -0
- package/dist/internal/oauth/connected-collection.mjs.map +1 -0
- package/dist/internal/oauth/connected-custody.d.mts +6 -0
- package/dist/internal/oauth/connected-custody.mjs +165 -0
- package/dist/internal/oauth/connected-custody.mjs.map +1 -0
- package/dist/internal/oauth/connected-flow.d.mts +7 -0
- package/dist/internal/oauth/connected-flow.mjs +324 -0
- package/dist/internal/oauth/connected-flow.mjs.map +1 -0
- package/dist/internal/oauth/connected-input.mjs +122 -0
- package/dist/internal/oauth/connected-input.mjs.map +1 -0
- package/dist/internal/oauth/connected-keys.d.mts +2 -0
- package/dist/internal/oauth/connected-keys.mjs +22 -0
- package/dist/internal/oauth/connected-keys.mjs.map +1 -0
- package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
- package/dist/internal/oauth/connected-maintenance.mjs +226 -0
- package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
- package/dist/internal/oauth/connected-management.d.mts +9 -0
- package/dist/internal/oauth/connected-management.mjs +243 -0
- package/dist/internal/oauth/connected-management.mjs.map +1 -0
- package/dist/internal/oauth/connected-reference.mjs +63 -0
- package/dist/internal/oauth/connected-reference.mjs.map +1 -0
- package/dist/internal/oauth/connected-settlement.d.mts +8 -0
- package/dist/internal/oauth/connected-settlement.mjs +141 -0
- package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
- package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
- package/dist/internal/oauth/connected-sign-in.mjs +281 -0
- package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
- package/dist/internal/oauth/connected-state.d.mts +14 -0
- package/dist/internal/oauth/connected-state.mjs +469 -0
- package/dist/internal/oauth/connected-state.mjs.map +1 -0
- package/dist/internal/oauth/connected-target.d.mts +21 -0
- package/dist/internal/oauth/connected-target.mjs +117 -0
- package/dist/internal/oauth/connected-target.mjs.map +1 -0
- package/dist/internal/oauth/flow.d.mts +5 -0
- package/dist/internal/oauth/flow.mjs +279 -0
- package/dist/internal/oauth/flow.mjs.map +1 -0
- package/dist/internal/oauth/input.mjs +127 -0
- package/dist/internal/oauth/input.mjs.map +1 -0
- package/dist/internal/oauth/owner.d.mts +10 -0
- package/dist/internal/oauth/owner.mjs +10 -0
- package/dist/internal/oauth/owner.mjs.map +1 -0
- package/dist/internal/oauth/query-compiler.d.mts +7 -0
- package/dist/internal/oauth/query-compiler.mjs +9 -0
- package/dist/internal/oauth/query-compiler.mjs.map +1 -0
- package/dist/internal/oauth/registration.d.mts +7 -0
- package/dist/internal/oauth/registration.mjs +393 -0
- package/dist/internal/oauth/registration.mjs.map +1 -0
- package/dist/internal/oauth/state.mjs +107 -0
- package/dist/internal/oauth/state.mjs.map +1 -0
- package/dist/internal/oauth/target.d.mts +31 -0
- package/dist/internal/oauth/target.mjs +273 -0
- package/dist/internal/oauth/target.mjs.map +1 -0
- package/dist/internal/oauth-kernel.d.mts +1423 -0
- package/dist/internal/oauth-kernel.mjs +187 -0
- package/dist/internal/oauth-kernel.mjs.map +1 -0
- package/dist/internal/oauth-proxy.d.mts +31 -0
- package/dist/internal/oauth-proxy.mjs +129 -0
- package/dist/internal/oauth-proxy.mjs.map +1 -0
- package/dist/internal/passkey/write-state.mjs +9 -1
- package/dist/internal/passkey/write-state.mjs.map +1 -1
- package/dist/internal/passkey-kernel.d.mts +9 -9
- package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
- package/dist/internal/password-credentials.mjs +121 -0
- package/dist/internal/password-credentials.mjs.map +1 -0
- package/dist/internal/password-kernel.d.mts +8 -24
- package/dist/internal/password-kernel.mjs +5 -43
- package/dist/internal/password-kernel.mjs.map +1 -1
- package/dist/internal/persistence.d.mts +1 -1
- package/dist/internal/persistence.mjs +2 -2
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/query-operations.d.mts +2 -0
- package/dist/internal/session-kernel.d.mts +2 -2
- package/dist/internal/session-kernel.mjs +74 -18
- package/dist/internal/session-kernel.mjs.map +1 -1
- package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
- package/dist/internal/sql-oauth-connected-model.mjs +7 -0
- package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
- package/dist/internal/sql-oauth-model.d.mts +60 -0
- package/dist/internal/sql-oauth-model.mjs +10 -0
- package/dist/internal/sql-oauth-model.mjs.map +1 -0
- package/dist/internal/sql-query.d.mts +12 -0
- package/dist/internal/sql-query.mjs +15 -18
- package/dist/internal/sql-query.mjs.map +1 -1
- package/dist/internal/sql-storage-validation.mjs +54 -0
- package/dist/internal/sql-storage-validation.mjs.map +1 -0
- package/dist/internal/storage-mapping.mjs +1 -37
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-plans.d.mts +7 -0
- package/dist/internal/storage-plans.mjs +189 -0
- package/dist/internal/storage-plans.mjs.map +1 -0
- package/dist/internal/storage-tables.d.mts +0 -56
- package/dist/internal/storage-tables.mjs +0 -17
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +6 -1
- package/dist/internal/storage-validation.mjs +42 -1
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/transaction-kernel.mjs +3 -2
- package/dist/internal/transaction-kernel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Adapter.ts +89 -5
- package/src/OAuthPersistence.ts +427 -0
- package/src/index.ts +3 -0
- package/src/internal/configuration.ts +1 -3
- package/src/internal/email-kernel.ts +2 -1
- package/src/internal/models/oauth-connected-model.ts +396 -0
- package/src/internal/models/oauth-model.ts +708 -0
- package/src/internal/models/password-model.ts +2 -57
- package/src/internal/native-sql-table.ts +14 -0
- package/src/internal/oauth/accounts.ts +839 -0
- package/src/internal/oauth/connected-access.ts +388 -0
- package/src/internal/oauth/connected-collection.ts +345 -0
- package/src/internal/oauth/connected-custody.ts +308 -0
- package/src/internal/oauth/connected-flow.ts +562 -0
- package/src/internal/oauth/connected-input.ts +147 -0
- package/src/internal/oauth/connected-keys.ts +31 -0
- package/src/internal/oauth/connected-maintenance.ts +379 -0
- package/src/internal/oauth/connected-management.ts +391 -0
- package/src/internal/oauth/connected-reference.ts +135 -0
- package/src/internal/oauth/connected-settlement.ts +278 -0
- package/src/internal/oauth/connected-sign-in.ts +472 -0
- package/src/internal/oauth/connected-state.ts +924 -0
- package/src/internal/oauth/connected-target.ts +307 -0
- package/src/internal/oauth/flow.ts +505 -0
- package/src/internal/oauth/input.ts +201 -0
- package/src/internal/oauth/owner.ts +22 -0
- package/src/internal/oauth/query-compiler.ts +15 -0
- package/src/internal/oauth/registration.ts +676 -0
- package/src/internal/oauth/state.ts +218 -0
- package/src/internal/oauth/target.ts +803 -0
- package/src/internal/oauth-kernel.ts +294 -0
- package/src/internal/oauth-proxy.ts +176 -0
- package/src/internal/passkey/write-state.ts +18 -4
- package/src/internal/password-credentials.ts +245 -0
- package/src/internal/password-kernel.ts +7 -99
- package/src/internal/persistence.ts +1 -1
- package/src/internal/query-operations.ts +2 -0
- package/src/internal/session-kernel.ts +143 -55
- package/src/internal/sql-oauth-connected-model.ts +145 -0
- package/src/internal/sql-oauth-model.ts +263 -0
- package/src/internal/sql-query.ts +16 -13
- package/src/internal/sql-storage-validation.ts +64 -0
- package/src/internal/storage-mapping.ts +0 -37
- package/src/internal/storage-plans.ts +218 -0
- package/src/internal/storage-tables.ts +0 -20
- package/src/internal/storage-validation.ts +58 -0
- package/src/internal/transaction-kernel.ts +6 -2
- package/dist/internal/password-attempts.mjs +0 -197
- package/dist/internal/password-attempts.mjs.map +0 -1
- package/src/internal/password-attempts.ts +0 -466
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { Effect, Layer } from "effect";
|
|
2
|
+
import type { SqlClient } from "effect/sql/SqlClient";
|
|
3
|
+
|
|
4
|
+
import { OAuthAccounts } from "./oauth/accounts";
|
|
5
|
+
import { OAuthConnectedAccess } from "./oauth/connected-access";
|
|
6
|
+
import { OAuthConnectedCollection } from "./oauth/connected-collection";
|
|
7
|
+
import { OAuthConnectedCustody } from "./oauth/connected-custody";
|
|
8
|
+
import { OAuthConnectedFlow } from "./oauth/connected-flow";
|
|
9
|
+
import { OAuthConnectedMaintenance } from "./oauth/connected-maintenance";
|
|
10
|
+
import { OAuthConnectedManagement } from "./oauth/connected-management";
|
|
11
|
+
import { makeOAuthConnectedReferenceKernel } from "./oauth/connected-reference";
|
|
12
|
+
import { OAuthConnectedSettlement } from "./oauth/connected-settlement";
|
|
13
|
+
import { OAuthConnectedSignIn } from "./oauth/connected-sign-in";
|
|
14
|
+
import { OAuthConnectedState } from "./oauth/connected-state";
|
|
15
|
+
import {
|
|
16
|
+
OAuthConnectedTarget,
|
|
17
|
+
oauthConnectedPersistenceLayer,
|
|
18
|
+
oauthConnectedRevocationsLayer,
|
|
19
|
+
} from "./oauth/connected-target";
|
|
20
|
+
import { OAuthFlow } from "./oauth/flow";
|
|
21
|
+
import { OAuthTransactionExecution } from "./oauth/owner";
|
|
22
|
+
import { OAuthQueryCompiler } from "./oauth/query-compiler";
|
|
23
|
+
import { OAuthRegistration } from "./oauth/registration";
|
|
24
|
+
import { captureOAuthMapping, unavailable } from "./oauth/state";
|
|
25
|
+
import {
|
|
26
|
+
OAuthTarget,
|
|
27
|
+
oauthAccountsPersistenceLayer,
|
|
28
|
+
oauthSignInPersistenceLayer,
|
|
29
|
+
oauthRegistrationIntentsLayer,
|
|
30
|
+
oauthRegistrationAuthorityLayer,
|
|
31
|
+
} from "./oauth/target";
|
|
32
|
+
import type { QueryOperations } from "./query-operations";
|
|
33
|
+
import { requireStandalone } from "./standalone";
|
|
34
|
+
import { makeTransactionExecutionKernel } from "./transaction-execution-kernel";
|
|
35
|
+
import { makeTransactionKernel } from "./transaction-kernel";
|
|
36
|
+
|
|
37
|
+
export type OAuthKernel = ReturnType<typeof makeOAuthKernel>;
|
|
38
|
+
|
|
39
|
+
/** Adapter composition root. Shared operation Layers acquire their dependencies;
|
|
40
|
+
* database and transaction services are still resolved by each operation. */
|
|
41
|
+
export const makeOAuthKernel = (
|
|
42
|
+
operations: QueryOperations,
|
|
43
|
+
dialect: (table: object) => "pg" | "sqlite" | "mysql",
|
|
44
|
+
) => {
|
|
45
|
+
const transactions = makeTransactionKernel(operations);
|
|
46
|
+
const connectedReference = makeOAuthConnectedReferenceKernel(operations, dialect);
|
|
47
|
+
|
|
48
|
+
const compilerLive = Layer.succeed(OAuthQueryCompiler, {
|
|
49
|
+
...operations,
|
|
50
|
+
...transactions.makeTransactionRows(unavailable),
|
|
51
|
+
both: transactions.both,
|
|
52
|
+
connectedReferenceCondition: connectedReference.connectedReferenceCondition,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const executionLive = Layer.succeed(
|
|
56
|
+
OAuthTransactionExecution,
|
|
57
|
+
makeTransactionExecutionKernel(transactions),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const flowLive = OAuthFlow.layer.pipe(Layer.provide([compilerLive]));
|
|
61
|
+
const registrationLive = OAuthRegistration.layer.pipe(Layer.provide([compilerLive, flowLive]));
|
|
62
|
+
|
|
63
|
+
const accountsLive = OAuthAccounts.layer.pipe(
|
|
64
|
+
Layer.provide([compilerLive, flowLive, registrationLive]),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const targetLive = OAuthTarget.layer.pipe(
|
|
68
|
+
Layer.provide([compilerLive, accountsLive, flowLive, registrationLive, executionLive]),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const connectedStateLive = OAuthConnectedState.layer.pipe(
|
|
72
|
+
Layer.provide([compilerLive, flowLive]),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const connectedFlowLive = OAuthConnectedFlow.layer.pipe(
|
|
76
|
+
Layer.provide([compilerLive, connectedStateLive, registrationLive]),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const connectedCustodyLive = OAuthConnectedCustody.layer.pipe(
|
|
80
|
+
Layer.provide([compilerLive, connectedStateLive]),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const connectedCollectionLive = OAuthConnectedCollection.layer.pipe(
|
|
84
|
+
Layer.provide([compilerLive, connectedCustodyLive, connectedFlowLive, connectedStateLive]),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const connectedSettlementLive = OAuthConnectedSettlement.layer.pipe(
|
|
88
|
+
Layer.provide([compilerLive, connectedCustodyLive, connectedFlowLive, connectedStateLive]),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const connectedAccessLive = OAuthConnectedAccess.layer.pipe(
|
|
92
|
+
Layer.provide([
|
|
93
|
+
compilerLive,
|
|
94
|
+
connectedCustodyLive,
|
|
95
|
+
connectedFlowLive,
|
|
96
|
+
connectedSettlementLive,
|
|
97
|
+
connectedStateLive,
|
|
98
|
+
]),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const connectedManagementLive = OAuthConnectedManagement.layer.pipe(
|
|
102
|
+
Layer.provide([
|
|
103
|
+
compilerLive,
|
|
104
|
+
connectedAccessLive,
|
|
105
|
+
connectedCustodyLive,
|
|
106
|
+
connectedFlowLive,
|
|
107
|
+
connectedStateLive,
|
|
108
|
+
]),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const connectedMaintenanceLive = OAuthConnectedMaintenance.layer.pipe(
|
|
112
|
+
Layer.provide([
|
|
113
|
+
compilerLive,
|
|
114
|
+
connectedCollectionLive,
|
|
115
|
+
connectedCustodyLive,
|
|
116
|
+
connectedFlowLive,
|
|
117
|
+
connectedStateLive,
|
|
118
|
+
]),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const connectedSignInLive = OAuthConnectedSignIn.layer.pipe(
|
|
122
|
+
Layer.provide([
|
|
123
|
+
compilerLive,
|
|
124
|
+
connectedCustodyLive,
|
|
125
|
+
connectedFlowLive,
|
|
126
|
+
connectedSettlementLive,
|
|
127
|
+
connectedStateLive,
|
|
128
|
+
flowLive,
|
|
129
|
+
]),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const connectedTargetLive = OAuthConnectedTarget.layer.pipe(
|
|
133
|
+
Layer.provide([
|
|
134
|
+
connectedAccessLive,
|
|
135
|
+
connectedFlowLive,
|
|
136
|
+
connectedMaintenanceLive,
|
|
137
|
+
connectedManagementLive,
|
|
138
|
+
connectedSettlementLive,
|
|
139
|
+
connectedSignInLive,
|
|
140
|
+
connectedStateLive,
|
|
141
|
+
targetLive,
|
|
142
|
+
]),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Keep shared operation Layers local to this adapter compiler, even inside another
|
|
146
|
+
// coordinator. Preserve constructor-time snapshots before deferred acquisition.
|
|
147
|
+
const makeTargetOAuthSignInServices: OAuthTarget["Service"]["makeTargetOAuthSignInServices"] = (
|
|
148
|
+
mapping,
|
|
149
|
+
configuration,
|
|
150
|
+
) => {
|
|
151
|
+
const captured = captureOAuthMapping(mapping);
|
|
152
|
+
|
|
153
|
+
return Effect.flatMap(OAuthTarget, (service) =>
|
|
154
|
+
service.makeTargetOAuthSignInServices(captured, configuration),
|
|
155
|
+
).pipe(Effect.provide(targetLive, { local: true }));
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const makeTargetOAuthRegistrationIntentServices: OAuthTarget["Service"]["makeTargetOAuthRegistrationIntentServices"] =
|
|
159
|
+
(mapping, configuration) => {
|
|
160
|
+
const captured = captureOAuthMapping(mapping);
|
|
161
|
+
|
|
162
|
+
return Effect.flatMap(OAuthTarget, (service) =>
|
|
163
|
+
service.makeTargetOAuthRegistrationIntentServices(captured, configuration),
|
|
164
|
+
).pipe(Effect.provide(targetLive, { local: true }));
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const makeTargetOAuthRegistrationServices: OAuthTarget["Service"]["makeTargetOAuthRegistrationServices"] =
|
|
168
|
+
(mapping, configuration) => {
|
|
169
|
+
const captured = captureOAuthMapping(mapping);
|
|
170
|
+
|
|
171
|
+
return Effect.flatMap(OAuthTarget, (service) =>
|
|
172
|
+
service.makeTargetOAuthRegistrationServices(captured, configuration),
|
|
173
|
+
).pipe(Effect.provide(targetLive, { local: true }));
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const makeTargetOAuthAccountsServices: OAuthTarget["Service"]["makeTargetOAuthAccountsServices"] =
|
|
177
|
+
(mapping, configuration) => {
|
|
178
|
+
const captured = captureOAuthMapping(mapping);
|
|
179
|
+
|
|
180
|
+
return Effect.flatMap(OAuthTarget, (service) =>
|
|
181
|
+
service.makeTargetOAuthAccountsServices(captured, configuration),
|
|
182
|
+
).pipe(Effect.provide(targetLive, { local: true }));
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const coordinateTargetOAuthRegistration: OAuthTarget["Service"]["coordinateTargetOAuthRegistration"] =
|
|
186
|
+
(database, mapping, configuration, owner) => {
|
|
187
|
+
const captured = captureOAuthMapping(mapping);
|
|
188
|
+
|
|
189
|
+
return Effect.flatMap(OAuthTarget, (service) =>
|
|
190
|
+
service.coordinateTargetOAuthRegistration(database, captured, configuration, owner),
|
|
191
|
+
).pipe(Effect.provide(targetLive, { local: true }));
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const coordinateTargetOAuthSignIn: OAuthTarget["Service"]["coordinateTargetOAuthSignIn"] = (
|
|
195
|
+
database,
|
|
196
|
+
mapping,
|
|
197
|
+
configuration,
|
|
198
|
+
owner,
|
|
199
|
+
) => {
|
|
200
|
+
const captured = captureOAuthMapping(mapping);
|
|
201
|
+
|
|
202
|
+
return Effect.flatMap(OAuthTarget, (service) =>
|
|
203
|
+
service.coordinateTargetOAuthSignIn(database, captured, configuration, owner),
|
|
204
|
+
).pipe(Effect.provide(targetLive, { local: true }));
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const coordinateTargetOAuthRegistrationIntents: OAuthTarget["Service"]["coordinateTargetOAuthRegistrationIntents"] =
|
|
208
|
+
(database, mapping, configuration, owner) => {
|
|
209
|
+
const captured = captureOAuthMapping(mapping);
|
|
210
|
+
|
|
211
|
+
return Effect.flatMap(OAuthTarget, (service) =>
|
|
212
|
+
service.coordinateTargetOAuthRegistrationIntents(database, captured, configuration, owner),
|
|
213
|
+
).pipe(Effect.provide(targetLive, { local: true }));
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const coordinateTargetOAuthAccounts: OAuthTarget["Service"]["coordinateTargetOAuthAccounts"] = (
|
|
217
|
+
database,
|
|
218
|
+
mapping,
|
|
219
|
+
configuration,
|
|
220
|
+
owner,
|
|
221
|
+
) => {
|
|
222
|
+
const captured = captureOAuthMapping(mapping);
|
|
223
|
+
|
|
224
|
+
return Effect.flatMap(OAuthTarget, (service) =>
|
|
225
|
+
service.coordinateTargetOAuthAccounts(database, captured, configuration, owner),
|
|
226
|
+
).pipe(Effect.provide(targetLive, { local: true }));
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const makeTargetOAuthConnectedServices: OAuthConnectedTarget["Service"]["makeTargetOAuthConnectedServices"] =
|
|
230
|
+
(mapping, configuration) => {
|
|
231
|
+
const captured = captureOAuthMapping(mapping);
|
|
232
|
+
|
|
233
|
+
return Effect.flatMap(OAuthConnectedTarget, (service) =>
|
|
234
|
+
service.makeTargetOAuthConnectedServices(captured, configuration),
|
|
235
|
+
).pipe(Effect.provide(connectedTargetLive, { local: true }));
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const makeTargetOAuthConnectedRevocationServices: OAuthConnectedTarget["Service"]["makeTargetOAuthConnectedRevocationServices"] =
|
|
239
|
+
(mapping, configuration) => {
|
|
240
|
+
const captured = captureOAuthMapping(mapping);
|
|
241
|
+
|
|
242
|
+
return Effect.flatMap(OAuthConnectedTarget, (service) =>
|
|
243
|
+
service.makeTargetOAuthConnectedRevocationServices(captured, configuration),
|
|
244
|
+
).pipe(Effect.provide(connectedTargetLive, { local: true }));
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const coordinateTargetOAuthConnected: OAuthConnectedTarget["Service"]["coordinateTargetOAuthConnected"] =
|
|
248
|
+
(database, mapping, configuration, owner) => {
|
|
249
|
+
const captured = captureOAuthMapping(mapping);
|
|
250
|
+
|
|
251
|
+
return Effect.flatMap(OAuthConnectedTarget, (service) =>
|
|
252
|
+
service.coordinateTargetOAuthConnected(database, captured, configuration, owner),
|
|
253
|
+
).pipe(Effect.provide(connectedTargetLive, { local: true }));
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const coordinateTargetOAuthConnectedRevocations: OAuthConnectedTarget["Service"]["coordinateTargetOAuthConnectedRevocations"] =
|
|
257
|
+
(database, mapping, configuration, owner) => {
|
|
258
|
+
const captured = captureOAuthMapping(mapping);
|
|
259
|
+
|
|
260
|
+
return Effect.flatMap(OAuthConnectedTarget, (service) =>
|
|
261
|
+
service.coordinateTargetOAuthConnectedRevocations(database, captured, configuration, owner),
|
|
262
|
+
).pipe(Effect.provide(connectedTargetLive, { local: true }));
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const sqlClientOAuthStandaloneGuard = (service: SqlClient["transactionService"] | undefined) =>
|
|
266
|
+
requireStandalone(unavailable, service);
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
target: {
|
|
270
|
+
makeTargetOAuthSignInServices,
|
|
271
|
+
makeTargetOAuthRegistrationIntentServices,
|
|
272
|
+
makeTargetOAuthRegistrationServices,
|
|
273
|
+
makeTargetOAuthAccountsServices,
|
|
274
|
+
coordinateTargetOAuthRegistration,
|
|
275
|
+
coordinateTargetOAuthSignIn,
|
|
276
|
+
coordinateTargetOAuthRegistrationIntents,
|
|
277
|
+
coordinateTargetOAuthAccounts,
|
|
278
|
+
sqlClientOAuthStandaloneGuard,
|
|
279
|
+
oauthAccountsPersistenceLayer,
|
|
280
|
+
oauthSignInPersistenceLayer,
|
|
281
|
+
oauthRegistrationIntentsLayer,
|
|
282
|
+
oauthRegistrationAuthorityLayer,
|
|
283
|
+
},
|
|
284
|
+
connectedTarget: {
|
|
285
|
+
makeTargetOAuthConnectedServices,
|
|
286
|
+
makeTargetOAuthConnectedRevocationServices,
|
|
287
|
+
coordinateTargetOAuthConnected,
|
|
288
|
+
coordinateTargetOAuthConnectedRevocations,
|
|
289
|
+
oauthConnectedPersistenceLayer,
|
|
290
|
+
oauthConnectedRevocationsLayer,
|
|
291
|
+
},
|
|
292
|
+
connectedReference,
|
|
293
|
+
};
|
|
294
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { FlowContext, Persistence, Record, Unavailable } from "@yielded/auth/OAuthProxy";
|
|
2
|
+
import { Effect, Layer, Schema } from "effect";
|
|
3
|
+
import { SqlClient } from "effect/sql";
|
|
4
|
+
|
|
5
|
+
import { identifier } from "./sql-table";
|
|
6
|
+
import { requireStandalone } from "./standalone";
|
|
7
|
+
|
|
8
|
+
export const oauthProxyColumns = {
|
|
9
|
+
namespace: "namespace",
|
|
10
|
+
flowId: "flow_id",
|
|
11
|
+
version: "version",
|
|
12
|
+
stage: "stage",
|
|
13
|
+
context: "context",
|
|
14
|
+
expiresAtMillis: "expires_at_millis",
|
|
15
|
+
handoffExpiresAtMillis: "handoff_expires_at_millis",
|
|
16
|
+
payload: "payload",
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
/** Plain text and integer-millisecond storage; adapters supply physical names. */
|
|
20
|
+
export interface OAuthProxySqlTable {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly schema?: string;
|
|
23
|
+
readonly columns: { readonly [K in keyof typeof oauthProxyColumns]: string };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const codec = Schema.fromJsonString(Record);
|
|
27
|
+
const contextCodec = Schema.fromJsonString(FlowContext);
|
|
28
|
+
|
|
29
|
+
const Row = Schema.Struct({
|
|
30
|
+
payload: Schema.String,
|
|
31
|
+
version: Schema.String,
|
|
32
|
+
stage: Schema.String,
|
|
33
|
+
context: Schema.String,
|
|
34
|
+
expiry: Schema.String,
|
|
35
|
+
deadline: Schema.NullOr(Schema.String),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** SQLite/D1 and PostgreSQL; apply once through application migrations.
|
|
39
|
+
* Retain terminal records until expires_at_millis, then expired rows may be deleted.
|
|
40
|
+
* Context and stage columns guard each standalone transition without a prior read.
|
|
41
|
+
*/
|
|
42
|
+
const migration = `CREATE TABLE yielded_oauth_proxy (
|
|
43
|
+
namespace TEXT NOT NULL,
|
|
44
|
+
flow_id TEXT NOT NULL,
|
|
45
|
+
version TEXT NOT NULL,
|
|
46
|
+
stage TEXT NOT NULL CHECK (stage IN ('Pending', 'Exchanging', 'Ready', 'Consumed')),
|
|
47
|
+
context TEXT NOT NULL,
|
|
48
|
+
expires_at_millis BIGINT NOT NULL,
|
|
49
|
+
handoff_expires_at_millis BIGINT,
|
|
50
|
+
payload TEXT NOT NULL,
|
|
51
|
+
PRIMARY KEY (namespace, flow_id),
|
|
52
|
+
CHECK ((stage = 'Ready' AND handoff_expires_at_millis IS NOT NULL)
|
|
53
|
+
OR (stage <> 'Ready' AND handoff_expires_at_millis IS NULL))
|
|
54
|
+
)`;
|
|
55
|
+
|
|
56
|
+
export const makeOAuthProxyPersistence = Effect.fnUntraced(function* (table: OAuthProxySqlTable) {
|
|
57
|
+
const sql = (yield* SqlClient.SqlClient).withoutTransforms();
|
|
58
|
+
|
|
59
|
+
if (!sql.onDialectOrElse({ sqlite: () => true, pg: () => true, orElse: () => false }))
|
|
60
|
+
return yield* Unavailable.make({});
|
|
61
|
+
|
|
62
|
+
const names = Object.values(table.columns);
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
[table.name, table.schema ?? "main", ...names].some((name) => !name || name.includes("\0")) ||
|
|
66
|
+
new Set(names).size !== names.length
|
|
67
|
+
)
|
|
68
|
+
return yield* Unavailable.make({});
|
|
69
|
+
|
|
70
|
+
const name = sql.literal(
|
|
71
|
+
(table.schema === undefined ? "" : `${identifier(table.schema)}.`) + identifier(table.name),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const column = (key: keyof typeof oauthProxyColumns) =>
|
|
75
|
+
sql.literal(identifier(table.columns[key]));
|
|
76
|
+
|
|
77
|
+
const standalone = requireStandalone(() => Unavailable.make({}), sql.transactionService);
|
|
78
|
+
|
|
79
|
+
const failure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
80
|
+
effect.pipe(Effect.mapError(() => Unavailable.make({})));
|
|
81
|
+
|
|
82
|
+
const clock = sql.onDialectOrElse({
|
|
83
|
+
sqlite: () => sql.literal("CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)"),
|
|
84
|
+
orElse: () => sql.literal("CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)"),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return Persistence.of({
|
|
88
|
+
get: Effect.fnUntraced(function* (namespace, id) {
|
|
89
|
+
yield* standalone;
|
|
90
|
+
|
|
91
|
+
const rows = yield* sql`SELECT ${column("payload")} AS payload,
|
|
92
|
+
${column("version")} AS version, ${column("stage")} AS stage, ${column("context")} AS context,
|
|
93
|
+
CAST(${column("expiresAtMillis")} AS TEXT) AS expiry,
|
|
94
|
+
CAST(${column("handoffExpiresAtMillis")} AS TEXT) AS deadline
|
|
95
|
+
FROM ${name} WHERE ${column("namespace")} = ${namespace} AND ${column("flowId")} = ${id}`;
|
|
96
|
+
|
|
97
|
+
if (rows.length === 0) return undefined;
|
|
98
|
+
if (rows.length !== 1) return yield* Unavailable.make({});
|
|
99
|
+
const row = yield* Schema.decodeUnknownEffect(Row)(rows[0]);
|
|
100
|
+
const record = yield* Schema.decodeEffect(codec)(row.payload);
|
|
101
|
+
const context = yield* Schema.encodeEffect(contextCodec)(record.context);
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
record.context.id !== id ||
|
|
105
|
+
record.version !== row.version ||
|
|
106
|
+
record._tag !== row.stage ||
|
|
107
|
+
context !== row.context ||
|
|
108
|
+
String(record.context.expiresAtMillis) !== row.expiry ||
|
|
109
|
+
(record._tag === "Ready" ? String(record.handoffExpiresAtMillis) : null) !== row.deadline
|
|
110
|
+
)
|
|
111
|
+
return yield* Unavailable.make({});
|
|
112
|
+
|
|
113
|
+
return record;
|
|
114
|
+
}, failure),
|
|
115
|
+
insert: Effect.fnUntraced(function* (namespace, record) {
|
|
116
|
+
yield* standalone;
|
|
117
|
+
if (record._tag !== "Pending") return yield* Unavailable.make({});
|
|
118
|
+
const payload = yield* Schema.encodeEffect(codec)(record);
|
|
119
|
+
const context = yield* Schema.encodeEffect(contextCodec)(record.context);
|
|
120
|
+
|
|
121
|
+
const rows = yield* sql`INSERT INTO ${name}
|
|
122
|
+
(${column("namespace")}, ${column("flowId")}, ${column("version")}, ${column("stage")},
|
|
123
|
+
${column("context")}, ${column("expiresAtMillis")}, ${column("payload")})
|
|
124
|
+
SELECT ${namespace}, ${record.context.id}, ${record.version}, ${record._tag},
|
|
125
|
+
${context}, ${record.context.expiresAtMillis}, ${payload}
|
|
126
|
+
WHERE ${record.context.expiresAtMillis} > ${clock}
|
|
127
|
+
ON CONFLICT (${column("namespace")}, ${column("flowId")}) DO NOTHING
|
|
128
|
+
RETURNING ${column("version")}`;
|
|
129
|
+
|
|
130
|
+
return rows.length === 1;
|
|
131
|
+
}, failure),
|
|
132
|
+
compareAndSet: Effect.fnUntraced(function* (namespace, version, record) {
|
|
133
|
+
yield* standalone;
|
|
134
|
+
if (version === record.version || record._tag === "Pending")
|
|
135
|
+
return yield* Unavailable.make({});
|
|
136
|
+
|
|
137
|
+
// Explicitly omit private envelopes from both irreversible marker stages.
|
|
138
|
+
const payload = yield* Schema.encodeEffect(codec)(
|
|
139
|
+
record._tag === "Ready"
|
|
140
|
+
? record
|
|
141
|
+
: { _tag: record._tag, version: record.version, context: record.context },
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const context = yield* Schema.encodeEffect(contextCodec)(record.context);
|
|
145
|
+
|
|
146
|
+
const previous =
|
|
147
|
+
record._tag === "Exchanging" ? "Pending" : record._tag === "Ready" ? "Exchanging" : "Ready";
|
|
148
|
+
|
|
149
|
+
const deadline = record._tag === "Ready" ? record.handoffExpiresAtMillis : null;
|
|
150
|
+
|
|
151
|
+
const consume =
|
|
152
|
+
record._tag === "Consumed"
|
|
153
|
+
? sql`AND ${column("handoffExpiresAtMillis")} > ${clock}`
|
|
154
|
+
: sql.literal("");
|
|
155
|
+
|
|
156
|
+
const rows = yield* sql`UPDATE ${name}
|
|
157
|
+
SET ${column("version")} = ${record.version}, ${column("stage")} = ${record._tag},
|
|
158
|
+
${column("handoffExpiresAtMillis")} = ${deadline}, ${column("payload")} = ${payload}
|
|
159
|
+
WHERE ${column("namespace")} = ${namespace} AND ${column("flowId")} = ${record.context.id}
|
|
160
|
+
AND ${column("version")} = ${version} AND ${column("stage")} = ${previous}
|
|
161
|
+
AND ${column("context")} = ${context}
|
|
162
|
+
AND ${column("expiresAtMillis")} = ${record.context.expiresAtMillis}
|
|
163
|
+
AND ${column("expiresAtMillis")} > ${clock} ${consume}
|
|
164
|
+
RETURNING ${column("version")}`;
|
|
165
|
+
|
|
166
|
+
return rows.length === 1;
|
|
167
|
+
}, failure),
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const layer = Layer.effect(
|
|
172
|
+
Persistence,
|
|
173
|
+
makeOAuthProxyPersistence({ name: "yielded_oauth_proxy", columns: oauthProxyColumns }),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
export const OAuthProxyPersistence = { migration, layer };
|
|
@@ -427,10 +427,24 @@ export const makePasskeyWriteStateKernel = (
|
|
|
427
427
|
mapping: any,
|
|
428
428
|
supplied: SessionInvalidationWindow,
|
|
429
429
|
policy: PasskeyManagementPolicy,
|
|
430
|
-
) =>
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
430
|
+
) => {
|
|
431
|
+
if (supplied.existingSessions === "cache-expiry" && supplied.maximumExposureMillis === 0)
|
|
432
|
+
return false;
|
|
433
|
+
|
|
434
|
+
// Cookie-cache exposure does not weaken the mapping's authoritative
|
|
435
|
+
// revision bump. Compare that immediate guarantee independently of the TTL.
|
|
436
|
+
const authoritative: SessionInvalidationWindow =
|
|
437
|
+
supplied.existingSessions === "cache-expiry" &&
|
|
438
|
+
mapping.invalidation.window.existingSessions === "immediate"
|
|
439
|
+
? { ...supplied, existingSessions: "immediate", maximumExposureMillis: 0 }
|
|
440
|
+
: supplied;
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
jsonStorage(SessionInvalidationWindow).encode(authoritative) ===
|
|
444
|
+
jsonStorage(SessionInvalidationWindow).encode(mapping.invalidation.window) &&
|
|
445
|
+
(!policy.requireImmediateInvalidation || supplied.existingSessions === "immediate")
|
|
446
|
+
);
|
|
447
|
+
};
|
|
434
448
|
|
|
435
449
|
const invalidate = Effect.fn("passkey.invalidateCredentialChange")(function* (
|
|
436
450
|
mapping: any,
|