@yielded/auth-persistence-drizzle 0.1.0-beta.0 → 0.1.0-beta.1
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 +6 -1
- package/dist/D1.d.mts +135 -25
- package/dist/D1.mjs +1 -3
- package/dist/D1.mjs.map +1 -1
- package/dist/Libsql.d.mts +192 -75
- package/dist/Libsql.mjs +1 -7
- package/dist/Libsql.mjs.map +1 -1
- package/dist/Mysql2.d.mts +177 -37
- package/dist/Mysql2.mjs +1 -3
- package/dist/Mysql2.mjs.map +1 -1
- package/dist/Pglite.d.mts +171 -30
- package/dist/Pglite.mjs +2 -3
- package/dist/Pglite.mjs.map +1 -1
- package/dist/Postgres.d.mts +177 -36
- package/dist/Postgres.mjs +2 -3
- package/dist/Postgres.mjs.map +1 -1
- package/dist/SqliteBun.d.mts +192 -74
- package/dist/SqliteBun.mjs +1 -5
- package/dist/SqliteBun.mjs.map +1 -1
- package/dist/SqliteDo.d.mts +194 -110
- package/dist/SqliteDo.mjs +2 -32
- package/dist/SqliteDo.mjs.map +1 -1
- package/dist/SqliteNode.d.mts +190 -72
- package/dist/SqliteNode.mjs +1 -5
- package/dist/SqliteNode.mjs.map +1 -1
- package/dist/SqliteWasm.d.mts +192 -74
- package/dist/SqliteWasm.mjs +1 -5
- package/dist/SqliteWasm.mjs.map +1 -1
- package/dist/drizzle/D1BatchStatements.d.mts +1 -1
- package/dist/drizzle/D1BatchStatements.mjs.map +1 -1
- package/dist/drizzle/d1-emails.mjs.map +1 -1
- package/dist/drizzle/d1-generated-statement.mjs.map +1 -1
- package/dist/drizzle/d1-identity.d.mts +1 -1
- package/dist/drizzle/d1-oauth.d.mts +35 -5
- package/dist/drizzle/d1-oauth.mjs +1 -1
- package/dist/drizzle/d1-password-prepared.mjs.map +1 -1
- package/dist/drizzle/d1-passwords.d.mts +1 -1
- package/dist/drizzle/d1-passwords.mjs.map +1 -1
- package/dist/drizzle/d1-proofs.d.mts +1 -1
- package/dist/drizzle/d1-proofs.mjs.map +1 -1
- package/dist/drizzle/d1-sessions.d.mts +3 -3
- package/dist/drizzle/d1-sessions.mjs.map +1 -1
- package/dist/drizzle/email-registration.d.mts +1 -1
- package/dist/drizzle/email-target.d.mts +1 -1
- package/dist/drizzle/email-target.mjs.map +1 -1
- package/dist/drizzle/model.d.mts +2 -126
- package/dist/drizzle/model.mjs +2 -21
- package/dist/drizzle/model.mjs.map +1 -1
- package/dist/drizzle/mysql-emails.d.mts +1 -1
- package/dist/drizzle/mysql-emails.mjs.map +1 -1
- package/dist/drizzle/mysql-identity.d.mts +1 -1
- package/dist/drizzle/mysql-password-prepared.d.mts +1 -1
- package/dist/drizzle/mysql-password-prepared.mjs.map +1 -1
- package/dist/drizzle/mysql-passwords.d.mts +1 -1
- package/dist/drizzle/mysql-passwords.mjs.map +1 -1
- package/dist/drizzle/mysql-proofs.d.mts +1 -1
- package/dist/drizzle/mysql-proofs.mjs.map +1 -1
- package/dist/drizzle/mysql-sessions.d.mts +1 -1
- package/dist/drizzle/mysql-sessions.mjs.map +1 -1
- package/dist/drizzle/oauth-accounts.mjs +1 -1
- package/dist/drizzle/oauth-connected-access.mjs +14 -12
- package/dist/drizzle/oauth-connected-access.mjs.map +1 -1
- package/dist/drizzle/oauth-connected-custody.mjs +2 -2
- package/dist/drizzle/oauth-connected-drivers.mjs +4 -1
- package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -1
- package/dist/drizzle/oauth-connected-flow.mjs +2 -2
- package/dist/drizzle/oauth-connected-input.mjs +14 -1
- package/dist/drizzle/oauth-connected-input.mjs.map +1 -1
- package/dist/drizzle/oauth-connected-maintenance.mjs +1 -1
- package/dist/drizzle/oauth-connected-management.mjs +2 -2
- package/dist/drizzle/oauth-connected-model.d.mts +17 -3
- package/dist/drizzle/oauth-connected-model.mjs.map +1 -1
- package/dist/drizzle/oauth-connected-settlement.mjs +51 -41
- package/dist/drizzle/oauth-connected-settlement.mjs.map +1 -1
- package/dist/drizzle/oauth-connected-sign-in.mjs +263 -0
- package/dist/drizzle/oauth-connected-sign-in.mjs.map +1 -0
- package/dist/drizzle/oauth-connected-state.d.mts +110 -2
- package/dist/drizzle/oauth-connected-state.mjs +9 -4
- package/dist/drizzle/oauth-connected-state.mjs.map +1 -1
- package/dist/drizzle/oauth-connected-target.d.mts +137 -9
- package/dist/drizzle/oauth-connected-target.mjs +6 -2
- package/dist/drizzle/oauth-connected-target.mjs.map +1 -1
- package/dist/drizzle/oauth-drivers.mjs +1 -1
- package/dist/drizzle/oauth-flow.d.mts +1 -1
- package/dist/drizzle/oauth-flow.mjs +1 -1
- package/dist/drizzle/oauth-input.mjs +1 -1
- package/dist/drizzle/oauth-model.d.mts +1 -1
- package/dist/drizzle/oauth-owner.d.mts +1 -1
- package/dist/drizzle/oauth-registration.d.mts +2 -2
- package/dist/drizzle/oauth-registration.mjs +1 -1
- package/dist/drizzle/oauth-state.mjs +5 -4
- package/dist/drizzle/oauth-state.mjs.map +1 -1
- package/dist/drizzle/oauth-target.d.mts +2 -2
- package/dist/drizzle/oauth-target.mjs +1 -1
- package/dist/drizzle/oauth-target.mjs.map +1 -1
- package/dist/drizzle/passkey-write-target.d.mts +1 -1
- package/dist/drizzle/password-prepared-state.mjs +1 -1
- package/dist/drizzle/password-prepared-target.d.mts +1 -1
- package/dist/drizzle/password-prepared-target.mjs.map +1 -1
- package/dist/drizzle/password-target.d.mts +1 -1
- package/dist/drizzle/password-target.mjs.map +1 -1
- package/dist/drizzle/pg-emails.d.mts +1 -1
- package/dist/drizzle/pg-emails.mjs.map +1 -1
- package/dist/drizzle/pg-identity.d.mts +22 -0
- package/dist/drizzle/pg-identity.mjs +89 -0
- package/dist/drizzle/pg-identity.mjs.map +1 -0
- package/dist/drizzle/pg-password-prepared.d.mts +1 -1
- package/dist/drizzle/pg-password-prepared.mjs.map +1 -1
- package/dist/drizzle/pg-passwords.d.mts +1 -1
- package/dist/drizzle/pg-passwords.mjs.map +1 -1
- package/dist/drizzle/pg-proofs.d.mts +1 -1
- package/dist/drizzle/pg-proofs.mjs.map +1 -1
- package/dist/drizzle/pg-sessions.d.mts +1 -1
- package/dist/drizzle/pg-sessions.mjs.map +1 -1
- package/dist/drizzle/phone-model.d.mts +1 -1
- package/dist/drizzle/phone-target.d.mts +1 -1
- package/dist/drizzle/phone-target.mjs.map +1 -1
- package/dist/drizzle/proof-target.d.mts +1 -1
- package/dist/drizzle/proof-target.mjs.map +1 -1
- package/dist/drizzle/session-target.d.mts +1 -1
- package/dist/drizzle/session-target.mjs.map +1 -1
- package/dist/drizzle/sqlite-emails.mjs.map +1 -1
- package/dist/drizzle/sqlite-password-prepared.mjs.map +1 -1
- package/dist/drizzle/sqlite-passwords.mjs.map +1 -1
- package/dist/drizzle/sqlite-proofs.mjs.map +1 -1
- package/dist/drizzle/sqlite-sessions.mjs.map +1 -1
- package/dist/drizzle/standalone-guard.mjs.map +1 -1
- package/dist/drizzle/totp-model.d.mts +1 -1
- package/dist/drizzle/totp-target.d.mts +1 -1
- package/dist/drizzle/totp-target.mjs.map +1 -1
- package/dist/drizzle/transaction-mysql.mjs +1 -1
- package/dist/drizzle/transaction-mysql.mjs.map +1 -1
- package/dist/index.d.mts +2 -4
- package/dist/index.mjs +2 -4
- package/dist/internal/drizzle-postgres.mjs.map +1 -1
- package/dist/internal/drizzle-sqlite.mjs.map +1 -1
- package/package.json +1 -1
- package/src/D1.ts +1 -17
- package/src/Libsql.ts +0 -44
- package/src/Mysql2.ts +1 -23
- package/src/Pglite.ts +2 -18
- package/src/Postgres.ts +2 -18
- package/src/SqliteBun.ts +1 -38
- package/src/SqliteDo.ts +1 -256
- package/src/SqliteNode.ts +1 -38
- package/src/SqliteWasm.ts +1 -38
- package/src/drizzle/D1BatchStatements.ts +1 -1
- package/src/drizzle/d1-emails.ts +2 -4
- package/src/drizzle/d1-generated-statement.ts +1 -1
- package/src/drizzle/d1-password-prepared.ts +2 -5
- package/src/drizzle/d1-passwords.ts +2 -4
- package/src/drizzle/d1-proofs.ts +2 -4
- package/src/drizzle/d1-sessions.ts +2 -2
- package/src/drizzle/email-target.ts +1 -1
- package/src/drizzle/model.ts +1 -208
- package/src/drizzle/mysql-emails.ts +1 -1
- package/src/drizzle/mysql-password-prepared.ts +1 -1
- package/src/drizzle/mysql-passwords.ts +1 -1
- package/src/drizzle/mysql-proofs.ts +1 -1
- package/src/drizzle/mysql-sessions.ts +1 -1
- package/src/drizzle/oauth-connected-access.ts +8 -1
- package/src/drizzle/oauth-connected-drivers.ts +3 -0
- package/src/drizzle/oauth-connected-input.ts +13 -0
- package/src/drizzle/oauth-connected-model.ts +17 -0
- package/src/drizzle/oauth-connected-settlement.ts +65 -42
- package/src/drizzle/oauth-connected-sign-in.ts +438 -0
- package/src/drizzle/oauth-connected-state.ts +19 -7
- package/src/drizzle/oauth-connected-target.ts +10 -1
- package/src/drizzle/oauth-state.ts +5 -4
- package/src/drizzle/oauth-target.ts +1 -1
- package/src/drizzle/password-prepared-target.ts +1 -1
- package/src/drizzle/password-target.ts +1 -1
- package/src/drizzle/pg-emails.ts +1 -1
- package/src/drizzle/pg-identity.ts +301 -0
- package/src/drizzle/pg-password-prepared.ts +1 -1
- package/src/drizzle/pg-passwords.ts +1 -1
- package/src/drizzle/pg-proofs.ts +1 -1
- package/src/drizzle/pg-sessions.ts +1 -1
- package/src/drizzle/phone-target.ts +1 -1
- package/src/drizzle/proof-target.ts +1 -1
- package/src/drizzle/session-target.ts +1 -1
- package/src/drizzle/sqlite-emails.ts +1 -1
- package/src/drizzle/sqlite-password-prepared.ts +1 -1
- package/src/drizzle/sqlite-passwords.ts +1 -1
- package/src/drizzle/sqlite-proofs.ts +1 -1
- package/src/drizzle/sqlite-sessions.ts +1 -1
- package/src/drizzle/standalone-guard.ts +1 -1
- package/src/drizzle/totp-target.ts +1 -1
- package/src/drizzle/transaction-mysql.ts +1 -1
- package/src/index.ts +0 -24
- package/src/internal/drizzle-postgres.ts +1 -1
- package/src/internal/drizzle-sqlite.ts +1 -1
- package/dist/drizzle/mysql.d.mts +0 -69
- package/dist/drizzle/mysql.mjs +0 -218
- package/dist/drizzle/mysql.mjs.map +0 -1
- package/dist/drizzle/pg.d.mts +0 -80
- package/dist/drizzle/pg.mjs +0 -289
- package/dist/drizzle/pg.mjs.map +0 -1
- package/dist/drizzle/sqlite.d.mts +0 -77
- package/dist/drizzle/sqlite.mjs +0 -217
- package/dist/drizzle/sqlite.mjs.map +0 -1
- package/src/drizzle/mysql.ts +0 -687
- package/src/drizzle/pg.ts +0 -984
- package/src/drizzle/sqlite.ts +0 -705
package/src/drizzle/mysql.ts
DELETED
|
@@ -1,687 +0,0 @@
|
|
|
1
|
-
import { AuthStore, EmailOtpRejected } from "@yielded/auth/AuthStore";
|
|
2
|
-
import { AuthRateLimited, AuthStoreError, InvalidRegistration } from "@yielded/auth/Errors";
|
|
3
|
-
import {
|
|
4
|
-
InvalidOAuthState,
|
|
5
|
-
OAuthStateDecisions,
|
|
6
|
-
OAuthStateStore,
|
|
7
|
-
type OAuthState,
|
|
8
|
-
} from "@yielded/auth/OAuth";
|
|
9
|
-
import { AuthStoreDecisions, reportPersistenceFailure } from "@yielded/auth/Persistence";
|
|
10
|
-
import {
|
|
11
|
-
timingSafeStringEqual,
|
|
12
|
-
VerifiedEmail,
|
|
13
|
-
type ConsumeChallenge,
|
|
14
|
-
type ConsumeRegistration,
|
|
15
|
-
type NewChallenge,
|
|
16
|
-
type NewRegistration,
|
|
17
|
-
type TokenDigest,
|
|
18
|
-
} from "@yielded/auth/Schema";
|
|
19
|
-
/* oxlint-disable no-explicit-any -- Drizzle's generic query builders lose the concrete consumer table through a runtime column map. Assertions stay in this adapter. */
|
|
20
|
-
import { and, eq, gt, sql, type AnyRelations, type InferSelectModel } from "drizzle-orm";
|
|
21
|
-
import type { EffectMysql2Database } from "drizzle-orm/effect-mysql2";
|
|
22
|
-
import type { AnyMySqlTable, MySqlColumn } from "drizzle-orm/mysql-core";
|
|
23
|
-
import { Cause, Context, DateTime, Duration, Effect, Schema } from "effect";
|
|
24
|
-
import type { SqlError } from "effect/unstable/sql/SqlError";
|
|
25
|
-
|
|
26
|
-
import {
|
|
27
|
-
column,
|
|
28
|
-
requireStandaloneConsume,
|
|
29
|
-
updateValues,
|
|
30
|
-
type AuthTables,
|
|
31
|
-
type AuthStoreTables,
|
|
32
|
-
type ChallengeConsumeDecision,
|
|
33
|
-
type ConsumeDecision,
|
|
34
|
-
type OAuthStateTables,
|
|
35
|
-
} from "./model";
|
|
36
|
-
import type { SuppliedService } from "./SuppliedService";
|
|
37
|
-
|
|
38
|
-
type Database = EffectMysql2Database<AnyRelations>;
|
|
39
|
-
type RuntimeDatabase = EffectMysql2Database<any> & Parameters<typeof requireStandaloneConsume>[0];
|
|
40
|
-
type TransactionOf<D extends Database> = Parameters<Parameters<D["transaction"]>[0]>[0];
|
|
41
|
-
|
|
42
|
-
const selected = <T extends AnyMySqlTable>(row: unknown): InferSelectModel<T> =>
|
|
43
|
-
row as InferSelectModel<T>;
|
|
44
|
-
|
|
45
|
-
const readError = () => AuthStoreError.make({ message: "Auth store read failed" });
|
|
46
|
-
const writeError = () => AuthStoreError.make({ message: "Auth store write failed" });
|
|
47
|
-
const isStoreFailure = Schema.is(AuthStoreError);
|
|
48
|
-
|
|
49
|
-
const mapStoreFailure =
|
|
50
|
-
(failure: () => AuthStoreError) =>
|
|
51
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, AuthStoreError, R> =>
|
|
52
|
-
reportPersistenceFailure(effect, isStoreFailure).pipe(
|
|
53
|
-
Effect.catchCause((cause) => Effect.failCause(Cause.map(cause, failure))),
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
const mapStoreReadFailure = mapStoreFailure(readError);
|
|
57
|
-
const mapStoreWriteFailure = mapStoreFailure(writeError);
|
|
58
|
-
|
|
59
|
-
/** MySQL/InnoDB implementation. Multi-step decisions stay values until commit. */
|
|
60
|
-
export const makeMysqlAuthStoreServices = <C extends AnyMySqlTable, R extends AnyMySqlTable>(
|
|
61
|
-
database: Database,
|
|
62
|
-
mapping: AuthStoreTables<C, R>,
|
|
63
|
-
) => {
|
|
64
|
-
const db = database as RuntimeDatabase;
|
|
65
|
-
const challengeTable = mapping.challenge.table;
|
|
66
|
-
const ck = mapping.challenge.columns;
|
|
67
|
-
|
|
68
|
-
const c = Object.fromEntries(
|
|
69
|
-
Object.entries(ck).map(([name, key]) => [name, column(challengeTable, key)]),
|
|
70
|
-
) as Record<keyof typeof ck, MySqlColumn>;
|
|
71
|
-
|
|
72
|
-
const registrationTable = mapping.registration.table;
|
|
73
|
-
const rk = mapping.registration.columns;
|
|
74
|
-
|
|
75
|
-
const r = Object.fromEntries(
|
|
76
|
-
Object.entries(rk).map(([name, key]) => [name, column(registrationTable, key)]),
|
|
77
|
-
) as Record<keyof typeof rk, MySqlColumn>;
|
|
78
|
-
|
|
79
|
-
const issueChallenge = Effect.fn("DrizzleMysqlAuthStore.issueChallenge")(function* (
|
|
80
|
-
input: NewChallenge,
|
|
81
|
-
) {
|
|
82
|
-
const result = yield* Effect.suspend(() =>
|
|
83
|
-
db.transaction((tx) =>
|
|
84
|
-
Effect.gen(function* () {
|
|
85
|
-
// A locking read of a missing InnoDB key takes a gap lock. Concurrent
|
|
86
|
-
// first issuances would then deadlock when each transaction inserts.
|
|
87
|
-
// The no-op upsert serializes on the declared series/digest unique
|
|
88
|
-
// indexes before the row is read and evaluated below.
|
|
89
|
-
yield* tx
|
|
90
|
-
.insert(challengeTable as any)
|
|
91
|
-
.values(
|
|
92
|
-
mapping.challenge.encodeInsert(input, {
|
|
93
|
-
namespace: mapping.challenge.identifierNamespace,
|
|
94
|
-
failedAttempts: 0,
|
|
95
|
-
consumed: false,
|
|
96
|
-
}) as any,
|
|
97
|
-
)
|
|
98
|
-
.onDuplicateKeyUpdate({
|
|
99
|
-
set: updateValues<C>([[ck.challengeId, c.challengeId]]) as any,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const rows = yield* tx
|
|
103
|
-
.select()
|
|
104
|
-
.from(challengeTable as any)
|
|
105
|
-
.where(
|
|
106
|
-
and(
|
|
107
|
-
eq(c.namespace, mapping.challenge.identifierNamespace),
|
|
108
|
-
eq(c.value, input.email),
|
|
109
|
-
eq(c.purpose, input.purpose),
|
|
110
|
-
),
|
|
111
|
-
)
|
|
112
|
-
.for("update")
|
|
113
|
-
.limit(1);
|
|
114
|
-
|
|
115
|
-
// A digest collision against another series is not a replay and must
|
|
116
|
-
// never overwrite that row.
|
|
117
|
-
if (rows[0] === undefined)
|
|
118
|
-
return yield* AuthStoreError.make({ message: "Challenge digest conflict" });
|
|
119
|
-
const existing = yield* mapping.challenge.decode(selected<C>(rows[0]));
|
|
120
|
-
const now = yield* DateTime.now;
|
|
121
|
-
|
|
122
|
-
const exactReplay =
|
|
123
|
-
existing.challenge.challengeId === input.challengeId &&
|
|
124
|
-
timingSafeStringEqual(existing.challenge.tokenDigest, input.tokenDigest) &&
|
|
125
|
-
existing.challenge.email === input.email &&
|
|
126
|
-
existing.challenge.purpose === input.purpose &&
|
|
127
|
-
existing.challenge.otpKeyId === input.otpKeyId &&
|
|
128
|
-
timingSafeStringEqual(existing.challenge.otpDigest, input.otpDigest) &&
|
|
129
|
-
DateTime.toEpochMillis(existing.challenge.issuedAt) ===
|
|
130
|
-
DateTime.toEpochMillis(input.issuedAt) &&
|
|
131
|
-
DateTime.toEpochMillis(existing.challenge.expiresAt) ===
|
|
132
|
-
DateTime.toEpochMillis(input.expiresAt) &&
|
|
133
|
-
existing.challenge.attemptLimit === input.attemptLimit &&
|
|
134
|
-
Duration.toMillis(existing.challenge.resendCooldown) ===
|
|
135
|
-
Duration.toMillis(input.resendCooldown);
|
|
136
|
-
|
|
137
|
-
// Retrying an identical still-live request is idempotent. In
|
|
138
|
-
// particular, it does not reset the persisted wrong-attempt budget.
|
|
139
|
-
if (
|
|
140
|
-
exactReplay &&
|
|
141
|
-
!existing.consumed &&
|
|
142
|
-
DateTime.toEpochMillis(existing.challenge.expiresAt) > DateTime.toEpochMillis(now)
|
|
143
|
-
)
|
|
144
|
-
return { _tag: "issued" } as const;
|
|
145
|
-
if (exactReplay) return { _tag: "limited", retryAfterSeconds: 1 } as const;
|
|
146
|
-
if (
|
|
147
|
-
existing.challenge.challengeId === input.challengeId ||
|
|
148
|
-
DateTime.toEpochMillis(input.issuedAt) <=
|
|
149
|
-
DateTime.toEpochMillis(existing.challenge.issuedAt)
|
|
150
|
-
)
|
|
151
|
-
return { _tag: "limited", retryAfterSeconds: 1 } as const;
|
|
152
|
-
|
|
153
|
-
const eligibleAt =
|
|
154
|
-
DateTime.toEpochMillis(existing.challenge.issuedAt) +
|
|
155
|
-
Duration.toMillis(input.resendCooldown);
|
|
156
|
-
|
|
157
|
-
if (!existing.consumed && DateTime.toEpochMillis(now) < eligibleAt) {
|
|
158
|
-
return {
|
|
159
|
-
_tag: "limited",
|
|
160
|
-
retryAfterSeconds: Math.max(
|
|
161
|
-
1,
|
|
162
|
-
Math.ceil((eligibleAt - DateTime.toEpochMillis(now)) / 1000),
|
|
163
|
-
),
|
|
164
|
-
} as const;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const failedAttempts =
|
|
168
|
-
DateTime.toEpochMillis(existing.challenge.expiresAt) > DateTime.toEpochMillis(now)
|
|
169
|
-
? existing.failedAttempts
|
|
170
|
-
: 0;
|
|
171
|
-
|
|
172
|
-
yield* tx
|
|
173
|
-
.update(challengeTable as any)
|
|
174
|
-
.set(
|
|
175
|
-
mapping.challenge.encodeUpdate(input, {
|
|
176
|
-
namespace: mapping.challenge.identifierNamespace,
|
|
177
|
-
failedAttempts,
|
|
178
|
-
consumed: false,
|
|
179
|
-
}) as any,
|
|
180
|
-
)
|
|
181
|
-
.where(
|
|
182
|
-
and(
|
|
183
|
-
eq(c.namespace, mapping.challenge.identifierNamespace),
|
|
184
|
-
eq(c.value, input.email),
|
|
185
|
-
eq(c.purpose, input.purpose),
|
|
186
|
-
),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
return { _tag: "issued" } as const;
|
|
190
|
-
}),
|
|
191
|
-
),
|
|
192
|
-
).pipe(mapStoreWriteFailure);
|
|
193
|
-
|
|
194
|
-
if (result._tag === "limited")
|
|
195
|
-
return yield* AuthRateLimited.make({ retryAfterSeconds: result.retryAfterSeconds });
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const consumeChallengeDecision = Effect.fn("DrizzleMysqlAuthStore.consumeChallengeDecision")(
|
|
199
|
-
function* (
|
|
200
|
-
input: ConsumeChallenge,
|
|
201
|
-
): Effect.fn.Return<ChallengeConsumeDecision, AuthStoreError> {
|
|
202
|
-
return yield* Effect.suspend(() =>
|
|
203
|
-
db.transaction((tx) =>
|
|
204
|
-
Effect.gen(function* () {
|
|
205
|
-
const rows = yield* tx
|
|
206
|
-
.select()
|
|
207
|
-
.from(challengeTable as any)
|
|
208
|
-
.where(eq(c.tokenDigest, input.tokenDigest))
|
|
209
|
-
.for("update")
|
|
210
|
-
.limit(1);
|
|
211
|
-
|
|
212
|
-
if (rows[0] === undefined)
|
|
213
|
-
return { _tag: "rejected", reason: "missing_challenge" } as const;
|
|
214
|
-
const row = yield* mapping.challenge.decode(selected<C>(rows[0]));
|
|
215
|
-
const now = yield* DateTime.now;
|
|
216
|
-
|
|
217
|
-
if (row.consumed) return { _tag: "rejected", reason: "consumed_challenge" } as const;
|
|
218
|
-
if (DateTime.toEpochMillis(row.challenge.expiresAt) <= DateTime.toEpochMillis(now))
|
|
219
|
-
return { _tag: "rejected", reason: "expired_challenge" } as const;
|
|
220
|
-
if (row.failedAttempts >= row.challenge.attemptLimit)
|
|
221
|
-
return { _tag: "rejected", reason: "attempts_exhausted" } as const;
|
|
222
|
-
const candidate = input.otpDigests[row.challenge.otpKeyId];
|
|
223
|
-
|
|
224
|
-
const matches =
|
|
225
|
-
candidate !== undefined && timingSafeStringEqual(candidate, row.challenge.otpDigest);
|
|
226
|
-
|
|
227
|
-
yield* tx
|
|
228
|
-
.update(challengeTable as any)
|
|
229
|
-
.set(
|
|
230
|
-
updateValues<C>(
|
|
231
|
-
matches
|
|
232
|
-
? [[ck.consumed, true]]
|
|
233
|
-
: [[ck.failedAttempts, sql`${c.failedAttempts} + 1`]],
|
|
234
|
-
) as any,
|
|
235
|
-
)
|
|
236
|
-
.where(eq(c.tokenDigest, input.tokenDigest));
|
|
237
|
-
|
|
238
|
-
return matches
|
|
239
|
-
? ({
|
|
240
|
-
_tag: "accepted",
|
|
241
|
-
value: VerifiedEmail.make({
|
|
242
|
-
email: row.challenge.email,
|
|
243
|
-
purpose: row.challenge.purpose,
|
|
244
|
-
}),
|
|
245
|
-
} as const)
|
|
246
|
-
: ({ _tag: "rejected", reason: "bad_code" } as const);
|
|
247
|
-
}),
|
|
248
|
-
),
|
|
249
|
-
).pipe(mapStoreWriteFailure);
|
|
250
|
-
},
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
const consumeChallenge = Effect.fn("DrizzleMysqlAuthStore.consumeChallenge")(function* (
|
|
254
|
-
input: ConsumeChallenge,
|
|
255
|
-
) {
|
|
256
|
-
yield* requireStandaloneConsume(db);
|
|
257
|
-
const decision = yield* consumeChallengeDecision(input);
|
|
258
|
-
|
|
259
|
-
return decision._tag === "accepted"
|
|
260
|
-
? decision.value
|
|
261
|
-
: yield* EmailOtpRejected.make({ reason: decision.reason });
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const issueRegistration = Effect.fn("DrizzleMysqlAuthStore.issueRegistration")(function* (
|
|
265
|
-
input: NewRegistration,
|
|
266
|
-
) {
|
|
267
|
-
yield* Effect.suspend(() =>
|
|
268
|
-
db
|
|
269
|
-
.insert(registrationTable as any)
|
|
270
|
-
.values(mapping.registration.encodeInsert(input, false) as any),
|
|
271
|
-
).pipe(mapStoreWriteFailure);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
const inspectRegistration = Effect.fn("DrizzleMysqlAuthStore.inspectRegistration")(function* (
|
|
275
|
-
digest: TokenDigest,
|
|
276
|
-
) {
|
|
277
|
-
const now = yield* DateTime.now;
|
|
278
|
-
|
|
279
|
-
const rows = yield* Effect.suspend(() =>
|
|
280
|
-
db
|
|
281
|
-
.select()
|
|
282
|
-
.from(registrationTable as any)
|
|
283
|
-
.where(
|
|
284
|
-
and(
|
|
285
|
-
eq(r.tokenDigest, digest),
|
|
286
|
-
eq(r.consumed, false),
|
|
287
|
-
gt(r.expiresAt, mapping.registration.encodeInstant(now)),
|
|
288
|
-
),
|
|
289
|
-
)
|
|
290
|
-
.limit(1),
|
|
291
|
-
).pipe(mapStoreReadFailure);
|
|
292
|
-
|
|
293
|
-
if (rows[0] === undefined) return yield* InvalidRegistration.make();
|
|
294
|
-
|
|
295
|
-
return (yield* Effect.suspend(() => mapping.registration.decode(selected<R>(rows[0]))).pipe(
|
|
296
|
-
mapStoreReadFailure,
|
|
297
|
-
)).registration;
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const consumeRegistrationDecision = Effect.fn(
|
|
301
|
-
"DrizzleMysqlAuthStore.consumeRegistrationDecision",
|
|
302
|
-
)(function* (
|
|
303
|
-
input: ConsumeRegistration,
|
|
304
|
-
): Effect.fn.Return<ConsumeDecision<void>, AuthStoreError> {
|
|
305
|
-
return yield* Effect.suspend(() =>
|
|
306
|
-
db.transaction((tx) =>
|
|
307
|
-
Effect.gen(function* () {
|
|
308
|
-
const rows = yield* tx
|
|
309
|
-
.select()
|
|
310
|
-
.from(registrationTable as any)
|
|
311
|
-
.where(eq(r.tokenDigest, input.tokenDigest))
|
|
312
|
-
.for("update")
|
|
313
|
-
.limit(1);
|
|
314
|
-
|
|
315
|
-
if (rows[0] === undefined) return { _tag: "rejected" } as const;
|
|
316
|
-
const row = yield* mapping.registration.decode(selected<R>(rows[0]));
|
|
317
|
-
const now = yield* DateTime.now;
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
row.consumed ||
|
|
321
|
-
DateTime.toEpochMillis(row.registration.expiresAt) <= DateTime.toEpochMillis(now)
|
|
322
|
-
)
|
|
323
|
-
return { _tag: "rejected" } as const;
|
|
324
|
-
yield* tx
|
|
325
|
-
.update(registrationTable as any)
|
|
326
|
-
.set(updateValues<R>([[rk.consumed, true]]) as any)
|
|
327
|
-
.where(eq(r.tokenDigest, input.tokenDigest));
|
|
328
|
-
|
|
329
|
-
return { _tag: "accepted", value: undefined } as const;
|
|
330
|
-
}),
|
|
331
|
-
),
|
|
332
|
-
).pipe(mapStoreWriteFailure);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const consumeRegistration = Effect.fn("DrizzleMysqlAuthStore.consumeRegistration")(function* (
|
|
336
|
-
input: ConsumeRegistration,
|
|
337
|
-
) {
|
|
338
|
-
yield* requireStandaloneConsume(db);
|
|
339
|
-
const decision = yield* consumeRegistrationDecision(input);
|
|
340
|
-
|
|
341
|
-
if (decision._tag === "rejected") return yield* InvalidRegistration.make();
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
return {
|
|
345
|
-
authStore: AuthStore.of({
|
|
346
|
-
issueChallenge,
|
|
347
|
-
consumeChallenge,
|
|
348
|
-
issueRegistration,
|
|
349
|
-
inspectRegistration,
|
|
350
|
-
consumeRegistration,
|
|
351
|
-
}),
|
|
352
|
-
decisions: {
|
|
353
|
-
consumeChallenge: consumeChallengeDecision,
|
|
354
|
-
consumeRegistration: consumeRegistrationDecision,
|
|
355
|
-
},
|
|
356
|
-
} as const;
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
export const makeMysqlOAuthStateServices = <S extends AnyMySqlTable>(
|
|
360
|
-
database: Database,
|
|
361
|
-
mapping: OAuthStateTables<S>,
|
|
362
|
-
) => {
|
|
363
|
-
const db = database as RuntimeDatabase;
|
|
364
|
-
const stateTable = mapping.oauthState.table;
|
|
365
|
-
const sk = mapping.oauthState.columns;
|
|
366
|
-
|
|
367
|
-
const s = Object.fromEntries(
|
|
368
|
-
Object.entries(sk).map(([name, key]) => [name, column(stateTable, key)]),
|
|
369
|
-
) as Record<keyof typeof sk, MySqlColumn>;
|
|
370
|
-
|
|
371
|
-
const issue = Effect.fn("DrizzleMysqlOAuthStateStore.issue")(function* (input: OAuthState) {
|
|
372
|
-
yield* Effect.suspend(() =>
|
|
373
|
-
db.insert(stateTable as any).values(mapping.oauthState.encodeInsert(input, false) as any),
|
|
374
|
-
).pipe(mapStoreWriteFailure);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
const consumeDecision = Effect.fn("DrizzleMysqlOAuthStateStore.consumeDecision")(function* (
|
|
378
|
-
digest: TokenDigest,
|
|
379
|
-
): Effect.fn.Return<ConsumeDecision<OAuthState>, AuthStoreError> {
|
|
380
|
-
return yield* Effect.suspend(() =>
|
|
381
|
-
db.transaction((tx) =>
|
|
382
|
-
Effect.gen(function* () {
|
|
383
|
-
const rows = yield* tx
|
|
384
|
-
.select()
|
|
385
|
-
.from(stateTable as any)
|
|
386
|
-
.where(eq(s.stateDigest, digest))
|
|
387
|
-
.for("update")
|
|
388
|
-
.limit(1);
|
|
389
|
-
|
|
390
|
-
if (rows[0] === undefined) return { _tag: "rejected" } as const;
|
|
391
|
-
const row = yield* mapping.oauthState.decode(selected<S>(rows[0]));
|
|
392
|
-
const now = yield* DateTime.now;
|
|
393
|
-
|
|
394
|
-
if (
|
|
395
|
-
row.consumed ||
|
|
396
|
-
DateTime.toEpochMillis(row.state.expiresAt) <= DateTime.toEpochMillis(now)
|
|
397
|
-
)
|
|
398
|
-
return { _tag: "rejected" } as const;
|
|
399
|
-
yield* tx
|
|
400
|
-
.update(stateTable as any)
|
|
401
|
-
.set(updateValues<S>([[sk.consumed, true]]) as any)
|
|
402
|
-
.where(eq(s.stateDigest, digest));
|
|
403
|
-
|
|
404
|
-
return { _tag: "accepted", value: row.state } as const;
|
|
405
|
-
}),
|
|
406
|
-
),
|
|
407
|
-
).pipe(mapStoreWriteFailure);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
const consume = Effect.fn("DrizzleMysqlOAuthStateStore.consume")(function* (digest: TokenDigest) {
|
|
411
|
-
yield* requireStandaloneConsume(db);
|
|
412
|
-
const decision = yield* consumeDecision(digest);
|
|
413
|
-
|
|
414
|
-
return decision._tag === "accepted" ? decision.value : yield* InvalidOAuthState.make();
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
return {
|
|
418
|
-
oauthStateStore: OAuthStateStore.of({ issue, consume }),
|
|
419
|
-
decisions: { consumeOAuthState: consumeDecision },
|
|
420
|
-
} as const;
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
export const makeMysqlAuthServices = <
|
|
424
|
-
C extends AnyMySqlTable,
|
|
425
|
-
R extends AnyMySqlTable,
|
|
426
|
-
S extends AnyMySqlTable,
|
|
427
|
-
>(
|
|
428
|
-
database: Database,
|
|
429
|
-
mapping: AuthTables<C, R, S>,
|
|
430
|
-
) => {
|
|
431
|
-
const auth = makeMysqlAuthStoreServices(database, mapping);
|
|
432
|
-
const oauth = makeMysqlOAuthStateServices(database, mapping);
|
|
433
|
-
|
|
434
|
-
return { ...auth, ...oauth, decisions: { ...auth.decisions, ...oauth.decisions } } as const;
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Owns the native outer commit. Keep rejection as a decision value inside the
|
|
439
|
-
* owner; interruption before commit rolls back. This adapter never retries a
|
|
440
|
-
* deadlock inside the caller's transaction.
|
|
441
|
-
*/
|
|
442
|
-
export function coordinateMysqlAuthStoreTransaction<
|
|
443
|
-
D extends Database,
|
|
444
|
-
C extends AnyMySqlTable,
|
|
445
|
-
R extends AnyMySqlTable,
|
|
446
|
-
A,
|
|
447
|
-
E,
|
|
448
|
-
Requirements,
|
|
449
|
-
DatabaseError,
|
|
450
|
-
DatabaseRequirements,
|
|
451
|
-
>(
|
|
452
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
453
|
-
options: { readonly mapping: AuthStoreTables<C, R>; readonly transaction?: never },
|
|
454
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
455
|
-
): Effect.Effect<
|
|
456
|
-
A,
|
|
457
|
-
E | DatabaseError | SqlError,
|
|
458
|
-
Exclude<Requirements, AuthStoreDecisions> | DatabaseRequirements
|
|
459
|
-
>;
|
|
460
|
-
|
|
461
|
-
export function coordinateMysqlAuthStoreTransaction<
|
|
462
|
-
D extends Database,
|
|
463
|
-
C extends AnyMySqlTable,
|
|
464
|
-
R extends AnyMySqlTable,
|
|
465
|
-
A,
|
|
466
|
-
E,
|
|
467
|
-
Requirements,
|
|
468
|
-
DatabaseError,
|
|
469
|
-
DatabaseRequirements,
|
|
470
|
-
TxId,
|
|
471
|
-
TxShape,
|
|
472
|
-
>(
|
|
473
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
474
|
-
options: {
|
|
475
|
-
readonly mapping: AuthStoreTables<C, R>;
|
|
476
|
-
readonly transaction: SuppliedService<TxId, NoInfer<TransactionOf<D>>, TxShape>;
|
|
477
|
-
},
|
|
478
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
479
|
-
): Effect.Effect<
|
|
480
|
-
A,
|
|
481
|
-
E | DatabaseError | SqlError,
|
|
482
|
-
Exclude<Requirements, AuthStoreDecisions | TxId> | DatabaseRequirements
|
|
483
|
-
>;
|
|
484
|
-
|
|
485
|
-
export function coordinateMysqlAuthStoreTransaction<
|
|
486
|
-
D extends Database,
|
|
487
|
-
C extends AnyMySqlTable,
|
|
488
|
-
R extends AnyMySqlTable,
|
|
489
|
-
A,
|
|
490
|
-
E,
|
|
491
|
-
Requirements,
|
|
492
|
-
DatabaseError,
|
|
493
|
-
DatabaseRequirements,
|
|
494
|
-
TxId,
|
|
495
|
-
TxShape,
|
|
496
|
-
>(
|
|
497
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
498
|
-
options: {
|
|
499
|
-
readonly mapping: AuthStoreTables<C, R>;
|
|
500
|
-
readonly transaction?: SuppliedService<TxId, NoInfer<TransactionOf<D>>, TxShape>;
|
|
501
|
-
},
|
|
502
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
503
|
-
) {
|
|
504
|
-
return Effect.flatMap(acquire, (database) =>
|
|
505
|
-
database.transaction<A, E, Exclude<Requirements, AuthStoreDecisions>>((transaction) => {
|
|
506
|
-
const decisions = makeMysqlAuthStoreServices(transaction as never, options.mapping).decisions;
|
|
507
|
-
const provided = Context.make(AuthStoreDecisions, decisions);
|
|
508
|
-
const work = Effect.provideContext(body, provided);
|
|
509
|
-
|
|
510
|
-
return options.transaction === undefined
|
|
511
|
-
? work
|
|
512
|
-
: Effect.provideService(
|
|
513
|
-
work,
|
|
514
|
-
options.transaction,
|
|
515
|
-
options.transaction.of(transaction as TransactionOf<D>),
|
|
516
|
-
);
|
|
517
|
-
}),
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
export function coordinateMysqlOAuthStateTransaction<
|
|
522
|
-
D extends Database,
|
|
523
|
-
S extends AnyMySqlTable,
|
|
524
|
-
A,
|
|
525
|
-
E,
|
|
526
|
-
Requirements,
|
|
527
|
-
DatabaseError,
|
|
528
|
-
DatabaseRequirements,
|
|
529
|
-
>(
|
|
530
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
531
|
-
options: { readonly mapping: OAuthStateTables<S>; readonly transaction?: never },
|
|
532
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
533
|
-
): Effect.Effect<
|
|
534
|
-
A,
|
|
535
|
-
E | DatabaseError | SqlError,
|
|
536
|
-
Exclude<Requirements, OAuthStateDecisions> | DatabaseRequirements
|
|
537
|
-
>;
|
|
538
|
-
|
|
539
|
-
export function coordinateMysqlOAuthStateTransaction<
|
|
540
|
-
D extends Database,
|
|
541
|
-
S extends AnyMySqlTable,
|
|
542
|
-
A,
|
|
543
|
-
E,
|
|
544
|
-
Requirements,
|
|
545
|
-
DatabaseError,
|
|
546
|
-
DatabaseRequirements,
|
|
547
|
-
TxId,
|
|
548
|
-
TxShape,
|
|
549
|
-
>(
|
|
550
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
551
|
-
options: {
|
|
552
|
-
readonly mapping: OAuthStateTables<S>;
|
|
553
|
-
readonly transaction: SuppliedService<TxId, NoInfer<TransactionOf<D>>, TxShape>;
|
|
554
|
-
},
|
|
555
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
556
|
-
): Effect.Effect<
|
|
557
|
-
A,
|
|
558
|
-
E | DatabaseError | SqlError,
|
|
559
|
-
Exclude<Requirements, OAuthStateDecisions | TxId> | DatabaseRequirements
|
|
560
|
-
>;
|
|
561
|
-
|
|
562
|
-
export function coordinateMysqlOAuthStateTransaction<
|
|
563
|
-
D extends Database,
|
|
564
|
-
S extends AnyMySqlTable,
|
|
565
|
-
A,
|
|
566
|
-
E,
|
|
567
|
-
Requirements,
|
|
568
|
-
DatabaseError,
|
|
569
|
-
DatabaseRequirements,
|
|
570
|
-
TxId,
|
|
571
|
-
TxShape,
|
|
572
|
-
>(
|
|
573
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
574
|
-
options: {
|
|
575
|
-
readonly mapping: OAuthStateTables<S>;
|
|
576
|
-
readonly transaction?: SuppliedService<TxId, NoInfer<TransactionOf<D>>, TxShape>;
|
|
577
|
-
},
|
|
578
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
579
|
-
) {
|
|
580
|
-
return Effect.flatMap(acquire, (database) =>
|
|
581
|
-
database.transaction<A, E, Exclude<Requirements, OAuthStateDecisions>>((transaction) => {
|
|
582
|
-
const decisions = makeMysqlOAuthStateServices(
|
|
583
|
-
transaction as never,
|
|
584
|
-
options.mapping,
|
|
585
|
-
).decisions;
|
|
586
|
-
|
|
587
|
-
const provided = Context.make(OAuthStateDecisions, decisions);
|
|
588
|
-
const work = Effect.provideContext(body, provided);
|
|
589
|
-
|
|
590
|
-
return options.transaction === undefined
|
|
591
|
-
? work
|
|
592
|
-
: Effect.provideService(
|
|
593
|
-
work,
|
|
594
|
-
options.transaction,
|
|
595
|
-
options.transaction.of(transaction as TransactionOf<D>),
|
|
596
|
-
);
|
|
597
|
-
}),
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
export function coordinateMysqlAuthTransaction<
|
|
602
|
-
D extends Database,
|
|
603
|
-
C extends AnyMySqlTable,
|
|
604
|
-
R extends AnyMySqlTable,
|
|
605
|
-
S extends AnyMySqlTable,
|
|
606
|
-
A,
|
|
607
|
-
E,
|
|
608
|
-
Requirements,
|
|
609
|
-
DatabaseError,
|
|
610
|
-
DatabaseRequirements,
|
|
611
|
-
>(
|
|
612
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
613
|
-
options: { readonly mapping: AuthTables<C, R, S>; readonly transaction?: never },
|
|
614
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
615
|
-
): Effect.Effect<
|
|
616
|
-
A,
|
|
617
|
-
E | DatabaseError | SqlError,
|
|
618
|
-
Exclude<Requirements, AuthStoreDecisions | OAuthStateDecisions> | DatabaseRequirements
|
|
619
|
-
>;
|
|
620
|
-
|
|
621
|
-
export function coordinateMysqlAuthTransaction<
|
|
622
|
-
D extends Database,
|
|
623
|
-
C extends AnyMySqlTable,
|
|
624
|
-
R extends AnyMySqlTable,
|
|
625
|
-
S extends AnyMySqlTable,
|
|
626
|
-
A,
|
|
627
|
-
E,
|
|
628
|
-
Requirements,
|
|
629
|
-
DatabaseError,
|
|
630
|
-
DatabaseRequirements,
|
|
631
|
-
TxId,
|
|
632
|
-
TxShape,
|
|
633
|
-
>(
|
|
634
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
635
|
-
options: {
|
|
636
|
-
readonly mapping: AuthTables<C, R, S>;
|
|
637
|
-
readonly transaction: SuppliedService<TxId, NoInfer<TransactionOf<D>>, TxShape>;
|
|
638
|
-
},
|
|
639
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
640
|
-
): Effect.Effect<
|
|
641
|
-
A,
|
|
642
|
-
E | DatabaseError | SqlError,
|
|
643
|
-
Exclude<Requirements, AuthStoreDecisions | OAuthStateDecisions | TxId> | DatabaseRequirements
|
|
644
|
-
>;
|
|
645
|
-
|
|
646
|
-
export function coordinateMysqlAuthTransaction<
|
|
647
|
-
D extends Database,
|
|
648
|
-
C extends AnyMySqlTable,
|
|
649
|
-
R extends AnyMySqlTable,
|
|
650
|
-
S extends AnyMySqlTable,
|
|
651
|
-
A,
|
|
652
|
-
E,
|
|
653
|
-
Requirements,
|
|
654
|
-
DatabaseError,
|
|
655
|
-
DatabaseRequirements,
|
|
656
|
-
TxId,
|
|
657
|
-
TxShape,
|
|
658
|
-
>(
|
|
659
|
-
acquire: Effect.Effect<D, DatabaseError, DatabaseRequirements>,
|
|
660
|
-
options: {
|
|
661
|
-
readonly mapping: AuthTables<C, R, S>;
|
|
662
|
-
readonly transaction?: SuppliedService<TxId, NoInfer<TransactionOf<D>>, TxShape>;
|
|
663
|
-
},
|
|
664
|
-
body: Effect.Effect<A, E, Requirements>,
|
|
665
|
-
) {
|
|
666
|
-
return Effect.flatMap(acquire, (database) =>
|
|
667
|
-
database.transaction<A, E, Exclude<Requirements, AuthStoreDecisions | OAuthStateDecisions>>(
|
|
668
|
-
(transaction) => {
|
|
669
|
-
const decisions = makeMysqlAuthServices(transaction as never, options.mapping).decisions;
|
|
670
|
-
|
|
671
|
-
const provided = Context.make(AuthStoreDecisions, decisions).pipe(
|
|
672
|
-
Context.add(OAuthStateDecisions, decisions),
|
|
673
|
-
);
|
|
674
|
-
|
|
675
|
-
const work = Effect.provideContext(body, provided);
|
|
676
|
-
|
|
677
|
-
return options.transaction === undefined
|
|
678
|
-
? work
|
|
679
|
-
: Effect.provideService(
|
|
680
|
-
work,
|
|
681
|
-
options.transaction,
|
|
682
|
-
options.transaction.of(transaction as TransactionOf<D>),
|
|
683
|
-
);
|
|
684
|
-
},
|
|
685
|
-
),
|
|
686
|
-
);
|
|
687
|
-
}
|