@effect-auth/core 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +148 -13
- package/dist/ApiKey.d.ts +207 -0
- package/dist/ApiKey.d.ts.map +1 -0
- package/dist/ApiKey.js +275 -0
- package/dist/ApiKey.js.map +1 -0
- package/dist/AuditLog.d.ts +204 -1
- package/dist/AuditLog.d.ts.map +1 -1
- package/dist/AuditLog.js +170 -0
- package/dist/AuditLog.js.map +1 -1
- package/dist/AuthConfig.d.ts +0 -9
- package/dist/AuthConfig.d.ts.map +1 -1
- package/dist/AuthConfig.js +1 -14
- package/dist/AuthConfig.js.map +1 -1
- package/dist/AuthFlow.d.ts +220 -51
- package/dist/AuthFlow.d.ts.map +1 -1
- package/dist/AuthFlow.js +550 -22
- package/dist/AuthFlow.js.map +1 -1
- package/dist/AuthKernel.d.ts +1 -1
- package/dist/Challenge.d.ts +5 -1
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +58 -48
- package/dist/Challenge.js.map +1 -1
- package/dist/Client.d.ts +242 -1
- package/dist/Client.d.ts.map +1 -1
- package/dist/Client.js +489 -1
- package/dist/Client.js.map +1 -1
- package/dist/D1Kysely.d.ts +1 -1
- package/dist/D1Kysely.d.ts.map +1 -1
- package/dist/EffectQbSqliteStorage.d.ts +156 -41
- package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
- package/dist/EffectQbSqliteStorage.js +336 -33
- package/dist/EffectQbSqliteStorage.js.map +1 -1
- package/dist/EmailOtp.d.ts +5 -2
- package/dist/EmailOtp.d.ts.map +1 -1
- package/dist/EmailOtp.js +11 -4
- package/dist/EmailOtp.js.map +1 -1
- package/dist/EmailVerification.d.ts +2 -2
- package/dist/EmailVerification.d.ts.map +1 -1
- package/dist/EmailVerification.js +2 -3
- package/dist/EmailVerification.js.map +1 -1
- package/dist/HttpApi/Api.d.ts +1161 -19
- package/dist/HttpApi/Api.d.ts.map +1 -1
- package/dist/HttpApi/Api.js +1433 -20
- package/dist/HttpApi/Api.js.map +1 -1
- package/dist/HttpApi/Endpoints.d.ts +611 -0
- package/dist/HttpApi/Endpoints.d.ts.map +1 -1
- package/dist/HttpApi/Endpoints.js +242 -1
- package/dist/HttpApi/Endpoints.js.map +1 -1
- package/dist/HttpApi/Schemas.d.ts +704 -0
- package/dist/HttpApi/Schemas.d.ts.map +1 -1
- package/dist/HttpApi/Schemas.js +435 -1
- package/dist/HttpApi/Schemas.js.map +1 -1
- package/dist/HttpApi/Security.d.ts +13 -1
- package/dist/HttpApi/Security.d.ts.map +1 -1
- package/dist/HttpApi/Security.js +107 -16
- package/dist/HttpApi/Security.js.map +1 -1
- package/dist/HttpApi/Transport.d.ts +36 -2
- package/dist/HttpApi/Transport.d.ts.map +1 -1
- package/dist/HttpApi/Transport.js +151 -9
- package/dist/HttpApi/Transport.js.map +1 -1
- package/dist/Jwt.d.ts +271 -0
- package/dist/Jwt.d.ts.map +1 -0
- package/dist/Jwt.js +512 -0
- package/dist/Jwt.js.map +1 -0
- package/dist/KyselyStorage.d.ts +134 -1
- package/dist/KyselyStorage.d.ts.map +1 -1
- package/dist/KyselyStorage.js +918 -4
- package/dist/KyselyStorage.js.map +1 -1
- package/dist/LoginApproval.d.ts +314 -0
- package/dist/LoginApproval.d.ts.map +1 -0
- package/dist/LoginApproval.js +551 -0
- package/dist/LoginApproval.js.map +1 -0
- package/dist/LoginNotification.d.ts +183 -0
- package/dist/LoginNotification.d.ts.map +1 -0
- package/dist/LoginNotification.js +329 -0
- package/dist/LoginNotification.js.map +1 -0
- package/dist/LoginRisk.d.ts +73 -0
- package/dist/LoginRisk.d.ts.map +1 -0
- package/dist/LoginRisk.js +57 -0
- package/dist/LoginRisk.js.map +1 -0
- package/dist/MagicLink.d.ts +85 -0
- package/dist/MagicLink.d.ts.map +1 -0
- package/dist/MagicLink.js +206 -0
- package/dist/MagicLink.js.map +1 -0
- package/dist/Mailer.d.ts +33 -2
- package/dist/Mailer.d.ts.map +1 -1
- package/dist/Mailer.js +107 -0
- package/dist/Mailer.js.map +1 -1
- package/dist/Passkey.d.ts +334 -0
- package/dist/Passkey.d.ts.map +1 -0
- package/dist/Passkey.js +422 -0
- package/dist/Passkey.js.map +1 -0
- package/dist/Password.d.ts +138 -8
- package/dist/Password.d.ts.map +1 -1
- package/dist/Password.js +397 -8
- package/dist/Password.js.map +1 -1
- package/dist/RecoveryCode.d.ts +169 -0
- package/dist/RecoveryCode.d.ts.map +1 -0
- package/dist/RecoveryCode.js +299 -0
- package/dist/RecoveryCode.js.map +1 -0
- package/dist/RefreshToken.d.ts +222 -0
- package/dist/RefreshToken.d.ts.map +1 -0
- package/dist/RefreshToken.js +342 -0
- package/dist/RefreshToken.js.map +1 -0
- package/dist/Sessions.d.ts +52 -2
- package/dist/Sessions.d.ts.map +1 -1
- package/dist/Sessions.js +123 -1
- package/dist/Sessions.js.map +1 -1
- package/dist/StepUp.d.ts +44 -0
- package/dist/StepUp.d.ts.map +1 -0
- package/dist/StepUp.js +82 -0
- package/dist/StepUp.js.map +1 -0
- package/dist/Storage.d.ts +14 -1
- package/dist/Storage.d.ts.map +1 -1
- package/dist/Storage.js.map +1 -1
- package/dist/StorageMigrations.d.ts +9 -1
- package/dist/StorageMigrations.d.ts.map +1 -1
- package/dist/StorageMigrations.js +159 -0
- package/dist/StorageMigrations.js.map +1 -1
- package/dist/Testing.d.ts +1 -1
- package/dist/Totp.d.ts +220 -0
- package/dist/Totp.d.ts.map +1 -0
- package/dist/Totp.js +477 -0
- package/dist/Totp.js.map +1 -0
- package/dist/TrustedDevice.d.ts +115 -0
- package/dist/TrustedDevice.d.ts.map +1 -0
- package/dist/TrustedDevice.js +222 -0
- package/dist/TrustedDevice.js.map +1 -0
- package/dist/alchemy/cloudflare/Email.d.ts +25 -0
- package/dist/alchemy/cloudflare/Email.d.ts.map +1 -0
- package/dist/alchemy/cloudflare/Email.js +117 -0
- package/dist/alchemy/cloudflare/Email.js.map +1 -0
- package/dist/cloudflare/Email.d.ts +25 -0
- package/dist/cloudflare/Email.d.ts.map +1 -0
- package/dist/cloudflare/Email.js +153 -0
- package/dist/cloudflare/Email.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/migrations/0003_auth_login_approval_review.sql +22 -0
- package/migrations/0004_auth_trusted_device.sql +12 -0
- package/migrations/0005_auth_passkey.sql +17 -0
- package/migrations/0006_auth_totp_factor.sql +17 -0
- package/migrations/0007_auth_recovery_code.sql +13 -0
- package/migrations/0008_auth_api_key.sql +17 -0
- package/migrations/0009_auth_refresh_token.sql +21 -0
- package/migrations/0010_auth_jwt_revocation.sql +8 -0
- package/package.json +57 -1
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Data, Effect, Layer, Option } from "effect";
|
|
2
|
-
import { Column, Executor, Query, Table } from "effect-qb/sqlite";
|
|
3
|
-
import { ChallengeId, CredentialId, Email, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
|
|
2
|
+
import { Column, Executor, Query, Scalar, Table } from "effect-qb/sqlite";
|
|
3
|
+
import { AuthFlowId, ChallengeId, CredentialId, Email, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
|
|
4
|
+
import { JwtError, JwtRevocationStore } from "./Jwt.js";
|
|
5
|
+
import { LoginApprovalReviewError, LoginApprovalReviewStore, } from "./LoginApproval.js";
|
|
6
|
+
import { LoginRiskContext, LoginRiskSignal } from "./LoginRisk.js";
|
|
4
7
|
import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
|
|
8
|
+
import { TrustedDeviceError, TrustedDeviceStore } from "./TrustedDevice.js";
|
|
5
9
|
export class D1EffectQbError extends Data.TaggedError("D1EffectQbError") {
|
|
6
10
|
}
|
|
7
11
|
const makeAuthUserTable = (name) => Table.make(name, {
|
|
@@ -48,6 +52,38 @@ const makeAuthVerificationTable = (name) => Table.make(name, {
|
|
|
48
52
|
metadata: Column.text().pipe(Column.nullable),
|
|
49
53
|
consumed_at: Column.int().pipe(Column.nullable),
|
|
50
54
|
});
|
|
55
|
+
const makeAuthLoginApprovalReviewTable = (name) => Table.make(name, {
|
|
56
|
+
approval_challenge_id: Column.text().pipe(Column.primaryKey),
|
|
57
|
+
flow_id: Column.text(),
|
|
58
|
+
user_id: Column.text(),
|
|
59
|
+
channel: Column.text(),
|
|
60
|
+
reason: Column.text(),
|
|
61
|
+
session_binding: Column.text(),
|
|
62
|
+
same_device_required: Column.int(),
|
|
63
|
+
status: Column.text(),
|
|
64
|
+
created_at: Column.int(),
|
|
65
|
+
expires_at: Column.int(),
|
|
66
|
+
reviewed_at: Column.int().pipe(Column.nullable),
|
|
67
|
+
reviewed_by: Column.text().pipe(Column.nullable),
|
|
68
|
+
denied_reason: Column.text().pipe(Column.nullable),
|
|
69
|
+
risk: Column.text().pipe(Column.nullable),
|
|
70
|
+
metadata: Column.text().pipe(Column.nullable),
|
|
71
|
+
review_metadata: Column.text().pipe(Column.nullable),
|
|
72
|
+
});
|
|
73
|
+
const makeAuthTrustedDeviceTable = (name) => Table.make(name, {
|
|
74
|
+
user_id: Column.text(),
|
|
75
|
+
token_hash: Column.text(),
|
|
76
|
+
created_at: Column.int(),
|
|
77
|
+
last_seen_at: Column.int(),
|
|
78
|
+
expires_at: Column.int(),
|
|
79
|
+
metadata: Column.text().pipe(Column.nullable),
|
|
80
|
+
});
|
|
81
|
+
const makeAuthJwtRevocationTable = (name) => Table.make(name, {
|
|
82
|
+
jwt_id: Column.text().pipe(Column.primaryKey),
|
|
83
|
+
revoked_at: Column.int(),
|
|
84
|
+
expires_at: Column.int().pipe(Column.nullable),
|
|
85
|
+
reason: Column.text().pipe(Column.nullable),
|
|
86
|
+
});
|
|
51
87
|
const authSqliteTablesTypeId = Symbol.for("@effect-auth/core/EffectQbSqliteStorage/AuthSqliteTables");
|
|
52
88
|
const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
|
|
53
89
|
const jsonDecode = (value) => {
|
|
@@ -73,14 +109,8 @@ const decodeBoolean = (value) => {
|
|
|
73
109
|
return value !== null && value !== undefined;
|
|
74
110
|
};
|
|
75
111
|
const mutationValues = (value) => value;
|
|
76
|
-
// Concrete effect-qb table generics are erased after `defineEffectQbAuthTables`;
|
|
77
|
-
// mapping definitions keep typed encoders at the app boundary.
|
|
78
|
-
const querySelect = (selection) => Query.select(selection);
|
|
79
|
-
const queryReturning = (selection) => Query.returning(selection);
|
|
80
|
-
const queryFrom = (table) => Query.from(table);
|
|
81
112
|
const queryInsert = (table, values) => Query.insert(table, values);
|
|
82
113
|
const queryUpdate = (table, values) => Query.update(table, values);
|
|
83
|
-
const queryDelete = (table) => Query.delete(table);
|
|
84
114
|
const queryEq = (left, right) => Query.eq(left, right);
|
|
85
115
|
const queryGt = (left, right) => Query.gt(left, right);
|
|
86
116
|
const queryLte = (left, right) => Query.lte(left, right);
|
|
@@ -90,6 +120,45 @@ const executePlan = (executor, plan) => executor.execute(plan);
|
|
|
90
120
|
const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
91
121
|
const sessionMetadataEnvelopeKey = "__effectAuthSession";
|
|
92
122
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
123
|
+
const isLoginRiskLevel = (value) => value === "unknown" ||
|
|
124
|
+
value === "low" ||
|
|
125
|
+
value === "medium" ||
|
|
126
|
+
value === "high" ||
|
|
127
|
+
value === "critical";
|
|
128
|
+
const decodeLoginRiskSignal = (value) => {
|
|
129
|
+
const record = isRecord(value) ? value : {};
|
|
130
|
+
const level = isLoginRiskLevel(record.level) ? record.level : undefined;
|
|
131
|
+
return LoginRiskSignal.make({
|
|
132
|
+
type: typeof record.type === "string" ? record.type : "unknown",
|
|
133
|
+
...(level === undefined ? {} : { level }),
|
|
134
|
+
...(isRecord(record.metadata) ? { metadata: record.metadata } : {}),
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
const decodeLoginRiskContext = (value) => {
|
|
138
|
+
const decoded = jsonDecode(value);
|
|
139
|
+
if (!isRecord(decoded)) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return LoginRiskContext.make({
|
|
143
|
+
level: isLoginRiskLevel(decoded.level) ? decoded.level : "unknown",
|
|
144
|
+
signals: Array.isArray(decoded.signals)
|
|
145
|
+
? decoded.signals.map(decodeLoginRiskSignal)
|
|
146
|
+
: [],
|
|
147
|
+
...(isRecord(decoded.metadata) ? { metadata: decoded.metadata } : {}),
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
const encodeLoginRiskSignal = (signal) => omitUndefined({
|
|
151
|
+
type: signal.type,
|
|
152
|
+
level: signal.level,
|
|
153
|
+
metadata: signal.metadata,
|
|
154
|
+
});
|
|
155
|
+
const encodeLoginRiskContext = (risk) => risk === undefined
|
|
156
|
+
? null
|
|
157
|
+
: jsonEncode(omitUndefined({
|
|
158
|
+
level: risk.level,
|
|
159
|
+
signals: risk.signals.map(encodeLoginRiskSignal),
|
|
160
|
+
metadata: risk.metadata,
|
|
161
|
+
}));
|
|
93
162
|
const decodeSessionMetadata = (value) => {
|
|
94
163
|
const decoded = jsonDecode(value);
|
|
95
164
|
const envelope = isRecord(decoded)
|
|
@@ -176,6 +245,49 @@ const decodeDefaultVerificationRow = (row) => omitUndefined({
|
|
|
176
245
|
const decodeVerificationRow = (tables, row) => tables.verification.decode === undefined
|
|
177
246
|
? decodeDefaultVerificationRow(row)
|
|
178
247
|
: tables.verification.decode(row);
|
|
248
|
+
const decodeDefaultLoginApprovalReviewRow = (row) => omitUndefined({
|
|
249
|
+
approvalChallengeId: ChallengeId(row.approvalChallengeId),
|
|
250
|
+
flowId: AuthFlowId(row.flowId),
|
|
251
|
+
userId: UserId(row.userId),
|
|
252
|
+
channel: row.channel,
|
|
253
|
+
reason: row.reason,
|
|
254
|
+
sessionBinding: row.sessionBinding,
|
|
255
|
+
sameDeviceRequired: decodeBoolean(row.sameDeviceRequired),
|
|
256
|
+
status: row.status,
|
|
257
|
+
createdAt: UnixMillis(Number(row.createdAt)),
|
|
258
|
+
expiresAt: UnixMillis(Number(row.expiresAt)),
|
|
259
|
+
reviewedAt: optionalUnixMillis(row.reviewedAt),
|
|
260
|
+
reviewedBy: row.reviewedBy === null
|
|
261
|
+
? undefined
|
|
262
|
+
: row.reviewedBy,
|
|
263
|
+
deniedReason: row.deniedReason === null ? undefined : row.deniedReason,
|
|
264
|
+
risk: decodeLoginRiskContext(row.risk),
|
|
265
|
+
metadata: jsonDecode(row.metadata),
|
|
266
|
+
reviewMetadata: jsonDecode(row.reviewMetadata),
|
|
267
|
+
});
|
|
268
|
+
const decodeLoginApprovalReviewRow = (tables, row) => tables.loginApprovalReview.decode === undefined
|
|
269
|
+
? decodeDefaultLoginApprovalReviewRow(row)
|
|
270
|
+
: tables.loginApprovalReview.decode(row);
|
|
271
|
+
const decodeDefaultTrustedDeviceRow = (row) => omitUndefined({
|
|
272
|
+
userId: UserId(row.userId),
|
|
273
|
+
tokenHash: row.tokenHash,
|
|
274
|
+
createdAt: UnixMillis(Number(row.createdAt)),
|
|
275
|
+
lastSeenAt: UnixMillis(Number(row.lastSeenAt)),
|
|
276
|
+
expiresAt: UnixMillis(Number(row.expiresAt)),
|
|
277
|
+
metadata: jsonDecode(row.metadata),
|
|
278
|
+
});
|
|
279
|
+
const decodeTrustedDeviceRow = (tables, row) => tables.trustedDevice.decode === undefined
|
|
280
|
+
? decodeDefaultTrustedDeviceRow(row)
|
|
281
|
+
: tables.trustedDevice.decode(row);
|
|
282
|
+
const decodeDefaultJwtRevocationRow = (row) => omitUndefined({
|
|
283
|
+
jwtId: row.jwtId,
|
|
284
|
+
revokedAt: UnixMillis(Number(row.revokedAt)),
|
|
285
|
+
expiresAt: optionalUnixMillis(row.expiresAt),
|
|
286
|
+
reason: row.reason ?? undefined,
|
|
287
|
+
});
|
|
288
|
+
const decodeJwtRevocationRow = (tables, row) => tables.jwtRevocation.decode === undefined
|
|
289
|
+
? decodeDefaultJwtRevocationRow(row)
|
|
290
|
+
: tables.jwtRevocation.decode(row);
|
|
179
291
|
const userInsert = (row) => ({
|
|
180
292
|
id: row.id,
|
|
181
293
|
email: row.email,
|
|
@@ -195,6 +307,10 @@ const passwordCredentialInsert = (row) => ({
|
|
|
195
307
|
revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
|
|
196
308
|
metadata: jsonEncode(row.metadata),
|
|
197
309
|
});
|
|
310
|
+
const passwordCredentialUpdatePassword = (input) => ({
|
|
311
|
+
password_hash: input.passwordHash,
|
|
312
|
+
updated_at: Number(input.updatedAt),
|
|
313
|
+
});
|
|
198
314
|
const sessionInsert = (row) => ({
|
|
199
315
|
id: row.id,
|
|
200
316
|
user_id: row.userId,
|
|
@@ -220,6 +336,24 @@ const verificationInsert = (row) => ({
|
|
|
220
336
|
metadata: jsonEncode(row.metadata),
|
|
221
337
|
consumed_at: null,
|
|
222
338
|
});
|
|
339
|
+
const loginApprovalReviewInsert = (row) => ({
|
|
340
|
+
approval_challenge_id: row.approvalChallengeId,
|
|
341
|
+
flow_id: row.flowId,
|
|
342
|
+
user_id: row.userId,
|
|
343
|
+
channel: row.channel,
|
|
344
|
+
reason: row.reason,
|
|
345
|
+
session_binding: row.sessionBinding,
|
|
346
|
+
same_device_required: row.sameDeviceRequired ? 1 : 0,
|
|
347
|
+
status: row.status,
|
|
348
|
+
created_at: Number(row.createdAt),
|
|
349
|
+
expires_at: Number(row.expiresAt),
|
|
350
|
+
reviewed_at: row.reviewedAt === undefined ? null : Number(row.reviewedAt),
|
|
351
|
+
reviewed_by: row.reviewedBy ?? null,
|
|
352
|
+
denied_reason: row.deniedReason ?? null,
|
|
353
|
+
risk: encodeLoginRiskContext(row.risk),
|
|
354
|
+
metadata: jsonEncode(row.metadata),
|
|
355
|
+
review_metadata: jsonEncode(row.reviewMetadata),
|
|
356
|
+
});
|
|
223
357
|
const userMarkEmailVerifiedUpdate = (input) => ({
|
|
224
358
|
email_verified: 1,
|
|
225
359
|
updated_at: Number(input.updatedAt),
|
|
@@ -233,10 +367,13 @@ const sessionRotateUpdate = (input) => omitUndefined({
|
|
|
233
367
|
rotated_at: Number(input.rotatedAt),
|
|
234
368
|
expires_at: input.expiresAt === undefined ? undefined : Number(input.expiresAt),
|
|
235
369
|
});
|
|
236
|
-
const sessionUpdateClaimsUpdate = (row, input) => ({
|
|
370
|
+
const sessionUpdateClaimsUpdate = (row, input) => omitUndefined({
|
|
371
|
+
aal: input.aal,
|
|
372
|
+
amr: input.amr === undefined ? undefined : JSON.stringify(input.amr),
|
|
373
|
+
mfa_verified_at: input.mfaVerifiedAt === undefined ? undefined : Number(input.mfaVerifiedAt),
|
|
237
374
|
metadata: encodeSessionMetadata({
|
|
238
375
|
...row,
|
|
239
|
-
claims: input.claims,
|
|
376
|
+
claims: input.claims ?? row.claims,
|
|
240
377
|
}),
|
|
241
378
|
});
|
|
242
379
|
const sessionRevokeUpdate = (input) => ({
|
|
@@ -245,11 +382,51 @@ const sessionRevokeUpdate = (input) => ({
|
|
|
245
382
|
const verificationConsumeUpdate = (input) => ({
|
|
246
383
|
consumed_at: Number(input.consumedAt),
|
|
247
384
|
});
|
|
385
|
+
const loginApprovalReviewApproveUpdate = (input) => ({
|
|
386
|
+
status: "approved",
|
|
387
|
+
reviewed_at: Number(input.reviewedAt),
|
|
388
|
+
reviewed_by: input.reviewedBy ?? null,
|
|
389
|
+
review_metadata: jsonEncode(input.metadata),
|
|
390
|
+
});
|
|
391
|
+
const loginApprovalReviewDenyUpdate = (input) => ({
|
|
392
|
+
status: "denied",
|
|
393
|
+
reviewed_at: Number(input.reviewedAt),
|
|
394
|
+
reviewed_by: input.reviewedBy ?? null,
|
|
395
|
+
denied_reason: input.reason ?? null,
|
|
396
|
+
review_metadata: jsonEncode(input.metadata),
|
|
397
|
+
});
|
|
398
|
+
const trustedDeviceInsert = (input, createdAt) => ({
|
|
399
|
+
user_id: input.userId,
|
|
400
|
+
token_hash: input.tokenHash,
|
|
401
|
+
created_at: Number(createdAt),
|
|
402
|
+
last_seen_at: Number(input.now),
|
|
403
|
+
expires_at: Number(input.expiresAt),
|
|
404
|
+
metadata: jsonEncode(input.metadata),
|
|
405
|
+
});
|
|
406
|
+
const trustedDeviceUpdate = (input, existing) => ({
|
|
407
|
+
last_seen_at: Number(input.now),
|
|
408
|
+
expires_at: Number(input.expiresAt),
|
|
409
|
+
metadata: jsonEncode(input.metadata ?? existing.metadata),
|
|
410
|
+
});
|
|
411
|
+
const jwtRevocationInsert = (record) => ({
|
|
412
|
+
jwt_id: record.jwtId,
|
|
413
|
+
revoked_at: Number(record.revokedAt),
|
|
414
|
+
expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
|
|
415
|
+
reason: record.reason ?? null,
|
|
416
|
+
});
|
|
417
|
+
const jwtRevocationUpdate = (record) => ({
|
|
418
|
+
revoked_at: Number(record.revokedAt),
|
|
419
|
+
expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
|
|
420
|
+
reason: record.reason ?? null,
|
|
421
|
+
});
|
|
248
422
|
const makeInternalAuthSqliteTables = (names = {}) => {
|
|
249
423
|
const user = makeAuthUserTable(names.user ?? "auth_user");
|
|
250
424
|
const credential = makeAuthCredentialTable(names.credential ?? "auth_credential");
|
|
251
425
|
const session = makeAuthSessionTable(names.session ?? "auth_session");
|
|
252
426
|
const verification = makeAuthVerificationTable(names.verification ?? "auth_verification");
|
|
427
|
+
const loginApprovalReview = makeAuthLoginApprovalReviewTable(names.loginApprovalReview ?? "auth_login_approval_review");
|
|
428
|
+
const trustedDevice = makeAuthTrustedDeviceTable(names.trustedDevice ?? "auth_trusted_device");
|
|
429
|
+
const jwtRevocation = makeAuthJwtRevocationTable(names.jwtRevocation ?? "auth_jwt_revocation");
|
|
253
430
|
return {
|
|
254
431
|
user: {
|
|
255
432
|
table: user,
|
|
@@ -278,6 +455,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
278
455
|
metadata: credential.metadata,
|
|
279
456
|
},
|
|
280
457
|
insertPassword: passwordCredentialInsert,
|
|
458
|
+
updatePassword: passwordCredentialUpdatePassword,
|
|
281
459
|
},
|
|
282
460
|
session: {
|
|
283
461
|
table: session,
|
|
@@ -317,6 +495,54 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
317
495
|
insert: verificationInsert,
|
|
318
496
|
consume: verificationConsumeUpdate,
|
|
319
497
|
},
|
|
498
|
+
loginApprovalReview: {
|
|
499
|
+
table: loginApprovalReview,
|
|
500
|
+
select: {
|
|
501
|
+
approvalChallengeId: loginApprovalReview.approval_challenge_id,
|
|
502
|
+
flowId: loginApprovalReview.flow_id,
|
|
503
|
+
userId: loginApprovalReview.user_id,
|
|
504
|
+
channel: loginApprovalReview.channel,
|
|
505
|
+
reason: loginApprovalReview.reason,
|
|
506
|
+
sessionBinding: loginApprovalReview.session_binding,
|
|
507
|
+
sameDeviceRequired: loginApprovalReview.same_device_required,
|
|
508
|
+
status: loginApprovalReview.status,
|
|
509
|
+
createdAt: loginApprovalReview.created_at,
|
|
510
|
+
expiresAt: loginApprovalReview.expires_at,
|
|
511
|
+
reviewedAt: loginApprovalReview.reviewed_at,
|
|
512
|
+
reviewedBy: loginApprovalReview.reviewed_by,
|
|
513
|
+
deniedReason: loginApprovalReview.denied_reason,
|
|
514
|
+
risk: loginApprovalReview.risk,
|
|
515
|
+
metadata: loginApprovalReview.metadata,
|
|
516
|
+
reviewMetadata: loginApprovalReview.review_metadata,
|
|
517
|
+
},
|
|
518
|
+
insertPending: loginApprovalReviewInsert,
|
|
519
|
+
approve: loginApprovalReviewApproveUpdate,
|
|
520
|
+
deny: loginApprovalReviewDenyUpdate,
|
|
521
|
+
},
|
|
522
|
+
trustedDevice: {
|
|
523
|
+
table: trustedDevice,
|
|
524
|
+
select: {
|
|
525
|
+
userId: trustedDevice.user_id,
|
|
526
|
+
tokenHash: trustedDevice.token_hash,
|
|
527
|
+
createdAt: trustedDevice.created_at,
|
|
528
|
+
lastSeenAt: trustedDevice.last_seen_at,
|
|
529
|
+
expiresAt: trustedDevice.expires_at,
|
|
530
|
+
metadata: trustedDevice.metadata,
|
|
531
|
+
},
|
|
532
|
+
insert: trustedDeviceInsert,
|
|
533
|
+
update: trustedDeviceUpdate,
|
|
534
|
+
},
|
|
535
|
+
jwtRevocation: {
|
|
536
|
+
table: jwtRevocation,
|
|
537
|
+
select: {
|
|
538
|
+
jwtId: jwtRevocation.jwt_id,
|
|
539
|
+
revokedAt: jwtRevocation.revoked_at,
|
|
540
|
+
expiresAt: jwtRevocation.expires_at,
|
|
541
|
+
reason: jwtRevocation.reason,
|
|
542
|
+
},
|
|
543
|
+
insert: jwtRevocationInsert,
|
|
544
|
+
update: jwtRevocationUpdate,
|
|
545
|
+
},
|
|
320
546
|
};
|
|
321
547
|
};
|
|
322
548
|
const asAuthSqliteTables = (tables) => tables;
|
|
@@ -327,30 +553,59 @@ const selectUserColumns = (tables) => tables.user.select;
|
|
|
327
553
|
const selectCredentialColumns = (tables) => tables.credential.select;
|
|
328
554
|
const selectSessionColumns = (tables) => tables.session.select;
|
|
329
555
|
const selectVerificationColumns = (tables) => tables.verification.select;
|
|
556
|
+
const selectLoginApprovalReviewColumns = (tables) => tables.loginApprovalReview.select;
|
|
557
|
+
const selectTrustedDeviceColumns = (tables) => tables.trustedDevice.select;
|
|
558
|
+
const selectJwtRevocationColumns = (tables) => tables.jwtRevocation.select;
|
|
330
559
|
const storageError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
|
|
331
|
-
const
|
|
332
|
-
|
|
560
|
+
const loginApprovalReviewStoreError = (operation, cause) => new LoginApprovalReviewError({
|
|
561
|
+
message: `Login approval review ${operation} failed`,
|
|
562
|
+
cause,
|
|
563
|
+
});
|
|
564
|
+
const trustedDeviceStoreError = (operation, cause) => new TrustedDeviceError({
|
|
565
|
+
operation,
|
|
566
|
+
message: `Trusted device ${operation} failed`,
|
|
567
|
+
cause,
|
|
568
|
+
});
|
|
569
|
+
const jwtRevocationStoreError = (operation, cause) => new JwtError({
|
|
570
|
+
operation: "revocation",
|
|
571
|
+
message: `JWT revocation ${operation} failed`,
|
|
572
|
+
cause,
|
|
573
|
+
});
|
|
574
|
+
const findUserById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.id, id)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
333
575
|
? Option.none()
|
|
334
576
|
: Option.some(decodeUserRow(tables, rows[0]))));
|
|
335
|
-
const findUserByEmail = (executor, tables, email, operation = "find") =>
|
|
577
|
+
const findUserByEmail = (executor, tables, email, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.email, email)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
336
578
|
? Option.none()
|
|
337
579
|
: Option.some(decodeUserRow(tables, rows[0]))));
|
|
338
|
-
const findPasswordCredentialByUserId = (executor, tables, userId, operation = "find") =>
|
|
580
|
+
const findPasswordCredentialByUserId = (executor, tables, userId, operation = "find") => executePlan(executor, Query.select(selectCredentialColumns(tables)).pipe(Query.from(tables.credential.table), Query.where(queryEq(tables.credential.select.userId, userId)), Query.where(queryEq(tables.credential.select.type, "password")))).pipe(Effect.mapError((cause) => storageError("credential", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
339
581
|
? Option.none()
|
|
340
582
|
: Option.some(decodePasswordCredentialRow(tables, rows[0]))));
|
|
341
|
-
const
|
|
583
|
+
const findJwtRevocation = (executor, tables, jwtId, operation = "find") => executePlan(executor, Query.select(selectJwtRevocationColumns(tables)).pipe(Query.from(tables.jwtRevocation.table), Query.where(queryEq(tables.jwtRevocation.select.jwtId, jwtId)))).pipe(Effect.mapError((cause) => jwtRevocationStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
584
|
+
? Option.none()
|
|
585
|
+
: Option.some(decodeJwtRevocationRow(tables, rows[0]))));
|
|
586
|
+
const findSessionById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
342
587
|
? Option.none()
|
|
343
588
|
: Option.some(decodeSessionRow(tables, rows[0]))));
|
|
344
|
-
const
|
|
589
|
+
const listSessionsByUser = (executor, tables, input, operation = "listByUser") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.map((rows) => rows.map((row) => decodeSessionRow(tables, row))));
|
|
590
|
+
const findVerificationById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectVerificationColumns(tables)).pipe(Query.from(tables.verification.table), Query.where(queryEq(tables.verification.select.id, id)))).pipe(Effect.mapError((cause) => storageError("verification", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
345
591
|
? Option.none()
|
|
346
592
|
: Option.some(decodeVerificationRow(tables, rows[0]))));
|
|
593
|
+
const findLoginApprovalReviewByApprovalChallengeId = (executor, tables, input, operation = "find") => executePlan(executor, Query.select(selectLoginApprovalReviewColumns(tables)).pipe(Query.from(tables.loginApprovalReview.table), Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
594
|
+
? Option.none()
|
|
595
|
+
: Option.some(decodeLoginApprovalReviewRow(tables, rows[0]))));
|
|
596
|
+
const findTrustedDevice = (executor, tables, input, operation = "status") => executePlan(executor, Query.select(selectTrustedDeviceColumns(tables)).pipe(Query.from(tables.trustedDevice.table), Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)), Query.where(queryGt(tables.trustedDevice.select.expiresAt, Number(input.now))))).pipe(Effect.mapError((cause) => trustedDeviceStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
597
|
+
? Option.none()
|
|
598
|
+
: Option.some(decodeTrustedDeviceRow(tables, rows[0]))));
|
|
599
|
+
const findTrustedDeviceIncludingExpired = (executor, tables, input) => executePlan(executor, Query.select(selectTrustedDeviceColumns(tables)).pipe(Query.from(tables.trustedDevice.table), Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.map((rows) => rows[0] === undefined
|
|
600
|
+
? Option.none()
|
|
601
|
+
: Option.some(decodeTrustedDeviceRow(tables, rows[0]))));
|
|
347
602
|
export const makeEffectQbSqliteUserStore = (executor, options = {}) => {
|
|
348
603
|
const tables = options.tables ?? authSqliteTables;
|
|
349
604
|
return UserStore.make({
|
|
350
605
|
findById: (id) => findUserById(executor, tables, id),
|
|
351
606
|
findByEmail: (email) => findUserByEmail(executor, tables, email),
|
|
352
|
-
insert: (row) =>
|
|
353
|
-
markEmailVerified: (input) =>
|
|
607
|
+
insert: (row) => executePlan(executor, queryInsert(tables.user.table, mutationValues(tables.user.insert(row)))).pipe(Effect.mapError((cause) => storageError("user", "insert", cause)), Effect.asVoid),
|
|
608
|
+
markEmailVerified: (input) => executePlan(executor, queryUpdate(tables.user.table, mutationValues(tables.user.markEmailVerified(input))).pipe(Query.where(queryEq(tables.user.select.id, input.userId)), Query.returning(selectUserColumns(tables)))).pipe(Effect.mapError((cause) => storageError("user", "markEmailVerified", cause)), Effect.map((rows) => rows[0] === undefined
|
|
354
609
|
? Option.none()
|
|
355
610
|
: Option.some(decodeUserRow(tables, rows[0])))),
|
|
356
611
|
});
|
|
@@ -359,23 +614,27 @@ export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
|
|
|
359
614
|
const tables = options.tables ?? authSqliteTables;
|
|
360
615
|
return CredentialStore.make({
|
|
361
616
|
findPasswordByUserId: (userId) => findPasswordCredentialByUserId(executor, tables, userId),
|
|
362
|
-
insertPassword: (row) =>
|
|
617
|
+
insertPassword: (row) => executePlan(executor, queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(row)))).pipe(Effect.mapError((cause) => storageError("credential", "insert", cause)), Effect.asVoid),
|
|
618
|
+
updatePassword: (input) => executePlan(executor, queryUpdate(tables.credential.table, mutationValues(tables.credential.updatePassword(input))).pipe(Query.where(queryEq(tables.credential.select.userId, input.userId)), Query.where(queryEq(tables.credential.select.type, "password")), Query.where(queryIsNull(tables.credential.select.revokedAt)), Query.returning({ id: tables.credential.select.id }))).pipe(Effect.mapError((cause) => storageError("credential", "updatePassword", cause)), Effect.flatMap((rows) => rows[0] === undefined
|
|
619
|
+
? Effect.succeed(Option.none())
|
|
620
|
+
: findPasswordCredentialByUserId(executor, tables, input.userId, "updatePassword"))),
|
|
363
621
|
});
|
|
364
622
|
};
|
|
365
623
|
export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
|
|
366
624
|
const tables = options.tables ?? authSqliteTables;
|
|
367
625
|
return SessionStore.make({
|
|
368
626
|
findById: (id) => findSessionById(executor, tables, id),
|
|
369
|
-
|
|
627
|
+
listByUser: (input) => listSessionsByUser(executor, tables, input),
|
|
628
|
+
insert: (row) => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row)))).pipe(Effect.mapError((cause) => storageError("session", "insert", cause)), Effect.asVoid),
|
|
370
629
|
refresh: (input) => {
|
|
371
|
-
const query = queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)),
|
|
372
|
-
return
|
|
630
|
+
const query = queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)));
|
|
631
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "refresh", cause)), Effect.map((rows) => rows[0] === undefined
|
|
373
632
|
? Option.none()
|
|
374
633
|
: Option.some(decodeSessionRow(tables, rows[0]))));
|
|
375
634
|
},
|
|
376
635
|
rotate: (input) => {
|
|
377
|
-
const query = queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)),
|
|
378
|
-
return
|
|
636
|
+
const query = queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)));
|
|
637
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "rotate", cause)), Effect.map((rows) => rows[0] === undefined
|
|
379
638
|
? Option.none()
|
|
380
639
|
: Option.some(decodeSessionRow(tables, rows[0]))));
|
|
381
640
|
},
|
|
@@ -383,49 +642,93 @@ export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
|
|
|
383
642
|
if (Option.isNone(rowOption)) {
|
|
384
643
|
return Effect.succeed(Option.none());
|
|
385
644
|
}
|
|
386
|
-
return
|
|
645
|
+
return executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.updateClaims(rowOption.value, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "updateClaims", cause)), Effect.map((rows) => rows[0] === undefined
|
|
387
646
|
? Option.none()
|
|
388
647
|
: Option.some(decodeSessionRow(tables, rows[0]))));
|
|
389
648
|
})),
|
|
390
|
-
revoke: (input) =>
|
|
391
|
-
revokeAllForUser: (input) =>
|
|
649
|
+
revoke: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)))).pipe(Effect.mapError((cause) => storageError("session", "revoke", cause)), Effect.asVoid),
|
|
650
|
+
revokeAllForUser: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", "revokeAllForUser", cause)), Effect.asVoid),
|
|
392
651
|
});
|
|
393
652
|
};
|
|
394
653
|
export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
|
|
395
654
|
const tables = options.tables ?? authSqliteTables;
|
|
396
655
|
return VerificationStore.make({
|
|
397
656
|
findById: (id) => findVerificationById(executor, tables, id),
|
|
398
|
-
insert: (row) =>
|
|
657
|
+
insert: (row) => executePlan(executor, queryInsert(tables.verification.table, mutationValues(tables.verification.insert(row)))).pipe(Effect.mapError((cause) => storageError("verification", "insert", cause)), Effect.asVoid),
|
|
399
658
|
consume: (input) => {
|
|
400
659
|
const base = queryUpdate(tables.verification.table, mutationValues(tables.verification.consume(input))).pipe(Query.where(queryEq(tables.verification.select.id, input.id)), Query.where(queryIsNull(tables.verification.select.consumedAt)), Query.where(queryGt(tables.verification.select.expiresAt, Number(input.consumedAt))));
|
|
401
660
|
const plan = input.type === undefined
|
|
402
661
|
? base
|
|
403
662
|
: base.pipe(Query.where(queryEq(tables.verification.select.type, input.type)));
|
|
404
|
-
return
|
|
663
|
+
return executePlan(executor, plan.pipe(Query.returning({ id: tables.verification.select.id }))).pipe(Effect.mapError((cause) => storageError("verification", "consume", cause)), Effect.flatMap((rows) => rows[0] === undefined
|
|
405
664
|
? Effect.succeed(Option.none())
|
|
406
665
|
: findVerificationById(executor, tables, input.id, "consume")));
|
|
407
666
|
},
|
|
408
667
|
deleteExpired: (input) => {
|
|
409
668
|
if (input.limit === undefined) {
|
|
410
|
-
return
|
|
669
|
+
return executePlan(executor, Query.delete(tables.verification.table).pipe(Query.where(queryLte(tables.verification.select.expiresAt, Number(input.now))), Query.returning({ id: tables.verification.select.id }))).pipe(Effect.mapError((cause) => storageError("verification", "deleteExpired", cause)), Effect.map((rows) => rows.length));
|
|
411
670
|
}
|
|
412
|
-
return
|
|
671
|
+
return executePlan(executor, Query.select({ id: tables.verification.select.id }).pipe(Query.from(tables.verification.table), Query.where(queryLte(tables.verification.select.expiresAt, Number(input.now))), Query.limit(input.limit))).pipe(Effect.mapError((cause) => storageError("verification", "deleteExpired", cause)), Effect.flatMap((rows) => {
|
|
413
672
|
const [first, ...rest] = rows.map((row) => row.id);
|
|
414
673
|
if (first === undefined) {
|
|
415
674
|
return Effect.succeed(0);
|
|
416
675
|
}
|
|
417
|
-
return
|
|
676
|
+
return executePlan(executor, Query.delete(tables.verification.table).pipe(Query.where(queryIn(tables.verification.select.id, first, ...rest)), Query.returning({ id: tables.verification.select.id }))).pipe(Effect.mapError((cause) => storageError("verification", "deleteExpired", cause)), Effect.map((deleted) => deleted.length));
|
|
418
677
|
}));
|
|
419
678
|
},
|
|
420
679
|
});
|
|
421
680
|
};
|
|
681
|
+
export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {}) => {
|
|
682
|
+
const tables = options.tables ?? authSqliteTables;
|
|
683
|
+
return LoginApprovalReviewStore.make({
|
|
684
|
+
insertPending: (input) => executePlan(executor, queryInsert(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.insertPending(input)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("insert", cause)), Effect.as(input)),
|
|
685
|
+
findByApprovalChallengeId: (input) => findLoginApprovalReviewByApprovalChallengeId(executor, tables, input),
|
|
686
|
+
approve: (input) => executePlan(executor, queryUpdate(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.approve(input))).pipe(Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)), Query.where(queryEq(tables.loginApprovalReview.select.status, "pending")), Query.returning(selectLoginApprovalReviewColumns(tables)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("approve", cause)), Effect.map((rows) => rows[0] === undefined
|
|
687
|
+
? Option.none()
|
|
688
|
+
: Option.some(decodeLoginApprovalReviewRow(tables, rows[0])))),
|
|
689
|
+
deny: (input) => executePlan(executor, queryUpdate(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.deny(input))).pipe(Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)), Query.where(queryEq(tables.loginApprovalReview.select.status, "pending")), Query.returning(selectLoginApprovalReviewColumns(tables)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("deny", cause)), Effect.map((rows) => rows[0] === undefined
|
|
690
|
+
? Option.none()
|
|
691
|
+
: Option.some(decodeLoginApprovalReviewRow(tables, rows[0])))),
|
|
692
|
+
});
|
|
693
|
+
};
|
|
694
|
+
export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) => {
|
|
695
|
+
const tables = options.tables ?? authSqliteTables;
|
|
696
|
+
return TrustedDeviceStore.make({
|
|
697
|
+
find: (input) => findTrustedDevice(executor, tables, input),
|
|
698
|
+
upsert: (input) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => {
|
|
699
|
+
if (Option.isNone(existing)) {
|
|
700
|
+
return executePlan(executor, queryInsert(tables.trustedDevice.table, mutationValues(tables.trustedDevice.insert(input, input.now)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
|
|
701
|
+
}
|
|
702
|
+
return executePlan(executor, queryUpdate(tables.trustedDevice.table, mutationValues(tables.trustedDevice.update(input, existing.value))).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
|
|
703
|
+
}), Effect.flatMap(() => findTrustedDeviceIncludingExpired(executor, tables, input)), Effect.flatMap((stored) => Option.isSome(stored)
|
|
704
|
+
? Effect.succeed(stored.value)
|
|
705
|
+
: Effect.fail(trustedDeviceStoreError("trust", "missing row")))),
|
|
706
|
+
});
|
|
707
|
+
};
|
|
708
|
+
export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) => {
|
|
709
|
+
const tables = options.tables ?? authSqliteTables;
|
|
710
|
+
return JwtRevocationStore.make({
|
|
711
|
+
revoke: (record) => findJwtRevocation(executor, tables, record.jwtId, "revoke").pipe(Effect.flatMap((existing) => {
|
|
712
|
+
if (Option.isNone(existing)) {
|
|
713
|
+
return executePlan(executor, queryInsert(tables.jwtRevocation.table, mutationValues(tables.jwtRevocation.insert(record))));
|
|
714
|
+
}
|
|
715
|
+
return executePlan(executor, queryUpdate(tables.jwtRevocation.table, mutationValues(tables.jwtRevocation.update(record))).pipe(Query.where(queryEq(tables.jwtRevocation.select.jwtId, record.jwtId))));
|
|
716
|
+
}), Effect.mapError((cause) => cause instanceof JwtError
|
|
717
|
+
? cause
|
|
718
|
+
: jwtRevocationStoreError("revoke", cause)), Effect.asVoid),
|
|
719
|
+
find: (jwtId) => findJwtRevocation(executor, tables, jwtId),
|
|
720
|
+
});
|
|
721
|
+
};
|
|
422
722
|
export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
|
|
423
723
|
userStore: makeEffectQbSqliteUserStore(executor, options),
|
|
424
724
|
credentialStore: makeEffectQbSqliteCredentialStore(executor, options),
|
|
425
725
|
sessionStore: makeEffectQbSqliteSessionStore(executor, options),
|
|
426
726
|
verificationStore: makeEffectQbSqliteVerificationStore(executor, options),
|
|
727
|
+
loginApprovalReviewStore: makeEffectQbSqliteLoginApprovalReviewStore(executor, options),
|
|
728
|
+
trustedDeviceStore: makeEffectQbSqliteTrustedDeviceStore(executor, options),
|
|
729
|
+
jwtRevocationStore: makeEffectQbSqliteJwtRevocationStore(executor, options),
|
|
427
730
|
});
|
|
428
|
-
export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)));
|
|
731
|
+
export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)));
|
|
429
732
|
export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
|
|
430
733
|
driver: Executor.driver((query) => Effect.tryPromise({
|
|
431
734
|
try: () => database
|