@effect-auth/core 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +728 -72
- 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 +275 -1
- package/dist/AuditLog.d.ts.map +1 -1
- package/dist/AuditLog.js +213 -1
- package/dist/AuditLog.js.map +1 -1
- package/dist/AuthConfig.d.ts +42 -0
- package/dist/AuthConfig.d.ts.map +1 -0
- package/dist/AuthConfig.js +43 -0
- package/dist/AuthConfig.js.map +1 -0
- package/dist/AuthFlow.d.ts +462 -36
- package/dist/AuthFlow.d.ts.map +1 -1
- package/dist/AuthFlow.js +657 -5
- package/dist/AuthFlow.js.map +1 -1
- package/dist/AuthKernel.d.ts +3 -0
- package/dist/AuthKernel.d.ts.map +1 -0
- package/dist/AuthKernel.js +9 -0
- package/dist/AuthKernel.js.map +1 -0
- package/dist/Challenge.d.ts +24 -4
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +167 -1
- package/dist/Challenge.js.map +1 -1
- package/dist/Client.d.ts +378 -0
- package/dist/Client.d.ts.map +1 -0
- package/dist/Client.js +768 -0
- package/dist/Client.js.map +1 -0
- package/dist/Crypto.d.ts +6 -1
- package/dist/Crypto.d.ts.map +1 -1
- package/dist/Crypto.js +62 -1
- package/dist/Crypto.js.map +1 -1
- package/dist/EffectQbSqliteStorage.d.ts +600 -0
- package/dist/EffectQbSqliteStorage.d.ts.map +1 -0
- package/dist/EffectQbSqliteStorage.js +1637 -0
- package/dist/EffectQbSqliteStorage.js.map +1 -0
- package/dist/EmailOtp.d.ts +120 -0
- package/dist/EmailOtp.d.ts.map +1 -0
- package/dist/EmailOtp.js +286 -0
- package/dist/EmailOtp.js.map +1 -0
- package/dist/EmailVerification.d.ts +121 -0
- package/dist/EmailVerification.d.ts.map +1 -0
- package/dist/EmailVerification.js +200 -0
- package/dist/EmailVerification.js.map +1 -0
- package/dist/HttpApi/Api.d.ts +1956 -0
- package/dist/HttpApi/Api.d.ts.map +1 -0
- package/dist/HttpApi/Api.js +2325 -0
- package/dist/HttpApi/Api.js.map +1 -0
- package/dist/HttpApi/Endpoints.d.ts +945 -0
- package/dist/HttpApi/Endpoints.d.ts.map +1 -0
- package/dist/HttpApi/Endpoints.js +373 -0
- package/dist/HttpApi/Endpoints.js.map +1 -0
- package/dist/HttpApi/Errors.d.ts +82 -0
- package/dist/HttpApi/Errors.d.ts.map +1 -0
- package/dist/HttpApi/Errors.js +184 -0
- package/dist/HttpApi/Errors.js.map +1 -0
- package/dist/HttpApi/Schemas.d.ts +1113 -0
- package/dist/HttpApi/Schemas.d.ts.map +1 -0
- package/dist/HttpApi/Schemas.js +734 -0
- package/dist/HttpApi/Schemas.js.map +1 -0
- package/dist/HttpApi/Security.d.ts +159 -0
- package/dist/HttpApi/Security.d.ts.map +1 -0
- package/dist/HttpApi/Security.js +530 -0
- package/dist/HttpApi/Security.js.map +1 -0
- package/dist/HttpApi/Transport.d.ts +65 -0
- package/dist/HttpApi/Transport.d.ts.map +1 -0
- package/dist/HttpApi/Transport.js +311 -0
- package/dist/HttpApi/Transport.js.map +1 -0
- package/dist/HttpApi/index.d.ts +8 -0
- package/dist/HttpApi/index.d.ts.map +1 -0
- package/dist/HttpApi/index.js +7 -0
- package/dist/HttpApi/index.js.map +1 -0
- package/dist/HttpApi.d.ts +2 -0
- package/dist/HttpApi.d.ts.map +1 -0
- package/dist/HttpApi.js +2 -0
- package/dist/HttpApi.js.map +1 -0
- package/dist/Identifiers.d.ts.map +1 -1
- package/dist/Identifiers.js.map +1 -1
- package/dist/Internal.d.ts +7 -0
- package/dist/Internal.d.ts.map +1 -0
- package/dist/Internal.js +12 -0
- package/dist/Internal.js.map +1 -0
- 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/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 +147 -0
- package/dist/Mailer.d.ts.map +1 -0
- package/dist/Mailer.js +208 -0
- package/dist/Mailer.js.map +1 -0
- package/dist/OAuth.d.ts +934 -0
- package/dist/OAuth.d.ts.map +1 -0
- package/dist/OAuth.js +1661 -0
- package/dist/OAuth.js.map +1 -0
- 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/PasskeySimpleWebAuthn.d.ts +11 -0
- package/dist/PasskeySimpleWebAuthn.d.ts.map +1 -0
- package/dist/PasskeySimpleWebAuthn.js +181 -0
- package/dist/PasskeySimpleWebAuthn.js.map +1 -0
- package/dist/Password.d.ts +237 -0
- package/dist/Password.d.ts.map +1 -0
- package/dist/Password.js +641 -0
- package/dist/Password.js.map +1 -0
- package/dist/Policy.d.ts +7 -4
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +28 -5
- package/dist/Policy.js.map +1 -1
- package/dist/Privacy.d.ts +15 -0
- package/dist/Privacy.d.ts.map +1 -1
- package/dist/Privacy.js +58 -1
- package/dist/Privacy.js.map +1 -1
- package/dist/RateLimiter.d.ts +104 -0
- package/dist/RateLimiter.d.ts.map +1 -0
- package/dist/RateLimiter.js +118 -0
- package/dist/RateLimiter.js.map +1 -0
- 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/SecurityTimeline.d.ts +72 -0
- package/dist/SecurityTimeline.d.ts.map +1 -0
- package/dist/SecurityTimeline.js +386 -0
- package/dist/SecurityTimeline.js.map +1 -0
- package/dist/Sessions.d.ts +123 -11
- package/dist/Sessions.d.ts.map +1 -1
- package/dist/Sessions.js +534 -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 +73 -5
- package/dist/Storage.d.ts.map +1 -1
- package/dist/Storage.js +11 -1
- package/dist/Storage.js.map +1 -1
- package/dist/StorageMigrations.d.ts +13 -1
- package/dist/StorageMigrations.d.ts.map +1 -1
- package/dist/StorageMigrations.js +256 -2
- package/dist/StorageMigrations.js.map +1 -1
- package/dist/Testing.d.ts +9 -1
- package/dist/Testing.d.ts.map +1 -1
- package/dist/Testing.js +30 -2
- package/dist/Testing.js.map +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 +127 -0
- package/dist/TrustedDevice.d.ts.map +1 -0
- package/dist/TrustedDevice.js +252 -0
- package/dist/TrustedDevice.js.map +1 -0
- package/dist/WaitUntil.d.ts.map +1 -1
- package/dist/WaitUntil.js +1 -1
- package/dist/WaitUntil.js.map +1 -1
- package/dist/Webhook.d.ts +72 -0
- package/dist/Webhook.d.ts.map +1 -0
- package/dist/Webhook.js +82 -0
- package/dist/Webhook.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/alchemy/cloudflare/RateLimitDurableObject.d.ts +34 -0
- package/dist/alchemy/cloudflare/RateLimitDurableObject.d.ts.map +1 -0
- package/dist/alchemy/cloudflare/RateLimitDurableObject.js +30 -0
- package/dist/alchemy/cloudflare/RateLimitDurableObject.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/cloudflare/RateLimitDurableObject.d.ts +50 -0
- package/dist/cloudflare/RateLimitDurableObject.d.ts.map +1 -0
- package/dist/cloudflare/RateLimitDurableObject.js +51 -0
- package/dist/cloudflare/RateLimitDurableObject.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/RateLimitDurableObject.d.ts +32 -0
- package/dist/internal/RateLimitDurableObject.d.ts.map +1 -0
- package/dist/internal/RateLimitDurableObject.js +80 -0
- package/dist/internal/RateLimitDurableObject.js.map +1 -0
- package/migrations/0002_auth_password.sql +25 -0
- 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/migrations/0011_auth_oauth_account.sql +16 -0
- package/migrations/0012_auth_oauth_provider_token_vault.sql +20 -0
- package/migrations/0013_auth_security_timeline.sql +16 -0
- package/package.json +146 -26
- package/dist/D1Kysely.d.ts +0 -40
- package/dist/D1Kysely.d.ts.map +0 -1
- package/dist/D1Kysely.js +0 -75
- package/dist/D1Kysely.js.map +0 -1
- package/dist/KyselyStorage.d.ts +0 -41
- package/dist/KyselyStorage.d.ts.map +0 -1
- package/dist/KyselyStorage.js +0 -176
- package/dist/KyselyStorage.js.map +0 -1
package/dist/Password.js
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
import { Context, Data, Duration, Effect, Encoding, Layer, Option, Redacted, Result, } from "effect";
|
|
2
|
+
import { AuthFlow, AuthResult } from "./AuthFlow.js";
|
|
3
|
+
import { Challenge } from "./Challenge.js";
|
|
4
|
+
import { Crypto } from "./Crypto.js";
|
|
5
|
+
import { CredentialId, Email, UserId } from "./Identifiers.js";
|
|
6
|
+
import { addMillis, currentUnixMillis, timingSafeEqualString, } from "./Internal.js";
|
|
7
|
+
import { AuthMailer } from "./Mailer.js";
|
|
8
|
+
import { CredentialStore, SessionStore, UserStore } from "./Storage.js";
|
|
9
|
+
export class PasswordHashError extends Data.TaggedError("PasswordHashError") {
|
|
10
|
+
}
|
|
11
|
+
export class PasswordHasher extends Context.Service()("auth/PasswordHasher") {
|
|
12
|
+
}
|
|
13
|
+
(function (PasswordHasher) {
|
|
14
|
+
PasswordHasher.make = (service) => PasswordHasher.of(service);
|
|
15
|
+
})(PasswordHasher || (PasswordHasher = {}));
|
|
16
|
+
const textEncoder = new TextEncoder();
|
|
17
|
+
const passwordHashPrefix = "pbkdf2-sha256";
|
|
18
|
+
const getWebCrypto = (options, operation) => Effect.gen(function* () {
|
|
19
|
+
const crypto = options.webCrypto ?? globalThis.crypto;
|
|
20
|
+
if (crypto === undefined) {
|
|
21
|
+
return yield* new PasswordHashError({
|
|
22
|
+
operation,
|
|
23
|
+
message: "Web Crypto is not available",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return crypto;
|
|
27
|
+
});
|
|
28
|
+
const decodeBase64Url = (value, operation) => Result.match(Encoding.decodeBase64Url(value), {
|
|
29
|
+
onSuccess: Effect.succeed,
|
|
30
|
+
onFailure: (cause) => Effect.fail(new PasswordHashError({
|
|
31
|
+
operation,
|
|
32
|
+
message: "Invalid password hash encoding",
|
|
33
|
+
cause,
|
|
34
|
+
})),
|
|
35
|
+
});
|
|
36
|
+
const parsePasswordHash = (hash) => {
|
|
37
|
+
const [prefix, iterations, salt, derivedKey] = hash.split("$");
|
|
38
|
+
if (prefix !== passwordHashPrefix ||
|
|
39
|
+
iterations === undefined ||
|
|
40
|
+
salt === undefined ||
|
|
41
|
+
derivedKey === undefined) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const parsedIterations = Number(iterations);
|
|
45
|
+
if (!Number.isInteger(parsedIterations) || parsedIterations <= 0) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
iterations: parsedIterations,
|
|
50
|
+
salt,
|
|
51
|
+
derivedKey,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const derivePbkdf2Sha256 = (input) => Effect.tryPromise({
|
|
55
|
+
try: async () => {
|
|
56
|
+
const key = await input.webCrypto.subtle.importKey("raw", textEncoder.encode(Redacted.value(input.password)), { name: "PBKDF2" }, false, ["deriveBits"]);
|
|
57
|
+
const bits = await input.webCrypto.subtle.deriveBits({
|
|
58
|
+
name: "PBKDF2",
|
|
59
|
+
hash: "SHA-256",
|
|
60
|
+
salt: input.salt,
|
|
61
|
+
iterations: input.iterations,
|
|
62
|
+
}, key, input.hashBytes * 8);
|
|
63
|
+
return new Uint8Array(bits);
|
|
64
|
+
},
|
|
65
|
+
catch: (cause) => new PasswordHashError({
|
|
66
|
+
operation: input.operation,
|
|
67
|
+
message: "Failed to derive PBKDF2 password hash",
|
|
68
|
+
cause,
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
export const makePbkdf2PasswordHasher = (dependencies) => {
|
|
72
|
+
const iterations = dependencies.iterations ?? 210_000;
|
|
73
|
+
const saltBytes = dependencies.saltBytes ?? 16;
|
|
74
|
+
const hashBytes = dependencies.hashBytes ?? 32;
|
|
75
|
+
return PasswordHasher.make({
|
|
76
|
+
hash: Effect.fn("auth.password_hash.pbkdf2.hash")(function* (input) {
|
|
77
|
+
const webCrypto = yield* getWebCrypto(dependencies, "hash");
|
|
78
|
+
const salt = yield* dependencies.crypto.randomBytes(saltBytes).pipe(Effect.mapError((cause) => new PasswordHashError({
|
|
79
|
+
operation: "hash",
|
|
80
|
+
message: "Failed to generate password salt",
|
|
81
|
+
cause,
|
|
82
|
+
})));
|
|
83
|
+
const derivedKey = yield* derivePbkdf2Sha256({
|
|
84
|
+
webCrypto,
|
|
85
|
+
password: input.password,
|
|
86
|
+
salt,
|
|
87
|
+
iterations,
|
|
88
|
+
hashBytes,
|
|
89
|
+
operation: "hash",
|
|
90
|
+
});
|
|
91
|
+
return [
|
|
92
|
+
passwordHashPrefix,
|
|
93
|
+
String(iterations),
|
|
94
|
+
Encoding.encodeBase64Url(salt),
|
|
95
|
+
Encoding.encodeBase64Url(derivedKey),
|
|
96
|
+
].join("$");
|
|
97
|
+
}),
|
|
98
|
+
verify: Effect.fn("auth.password_hash.pbkdf2.verify")(function* (input) {
|
|
99
|
+
const parsed = parsePasswordHash(input.hash);
|
|
100
|
+
if (parsed === undefined) {
|
|
101
|
+
return yield* new PasswordHashError({
|
|
102
|
+
operation: "verify",
|
|
103
|
+
message: "Invalid password hash format",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const webCrypto = yield* getWebCrypto(dependencies, "verify");
|
|
107
|
+
const salt = yield* decodeBase64Url(parsed.salt, "verify");
|
|
108
|
+
const expected = yield* decodeBase64Url(parsed.derivedKey, "verify");
|
|
109
|
+
const derivedKey = yield* derivePbkdf2Sha256({
|
|
110
|
+
webCrypto,
|
|
111
|
+
password: input.password,
|
|
112
|
+
salt,
|
|
113
|
+
iterations: parsed.iterations,
|
|
114
|
+
hashBytes: expected.byteLength,
|
|
115
|
+
operation: "verify",
|
|
116
|
+
});
|
|
117
|
+
return yield* timingSafeEqualString(dependencies.crypto, Encoding.encodeBase64Url(derivedKey), parsed.derivedKey).pipe(Effect.mapError((cause) => new PasswordHashError({
|
|
118
|
+
operation: "verify",
|
|
119
|
+
message: "Failed to compare password hash",
|
|
120
|
+
cause,
|
|
121
|
+
})));
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
export const Pbkdf2PasswordHasherLive = (options) => Layer.effect(PasswordHasher)(Effect.gen(function* () {
|
|
126
|
+
const crypto = yield* Crypto;
|
|
127
|
+
const resolvedOptions = options ?? {};
|
|
128
|
+
return makePbkdf2PasswordHasher({
|
|
129
|
+
...resolvedOptions,
|
|
130
|
+
crypto,
|
|
131
|
+
});
|
|
132
|
+
}));
|
|
133
|
+
export class PasswordLogin extends Context.Service()("auth/login/Password") {
|
|
134
|
+
}
|
|
135
|
+
(function (PasswordLogin) {
|
|
136
|
+
PasswordLogin.make = (service) => PasswordLogin.of(service);
|
|
137
|
+
})(PasswordLogin || (PasswordLogin = {}));
|
|
138
|
+
export const makePasswordLogin = (dependencies) => PasswordLogin.make({
|
|
139
|
+
signIn: Effect.fn("auth.password.sign_in")(function* (input) {
|
|
140
|
+
yield* Effect.annotateCurrentSpan("auth.method", "password");
|
|
141
|
+
const user = yield* dependencies.users.findByEmail(input.email);
|
|
142
|
+
if (Option.isNone(user)) {
|
|
143
|
+
yield* Effect.annotateCurrentSpan("auth.result", "InvalidCredentials");
|
|
144
|
+
return AuthResult.InvalidCredentials();
|
|
145
|
+
}
|
|
146
|
+
if (user.value.disabledAt !== undefined) {
|
|
147
|
+
yield* Effect.annotateCurrentSpan("auth.result", "AccountDisabled");
|
|
148
|
+
return AuthResult.AccountDisabled();
|
|
149
|
+
}
|
|
150
|
+
const credential = yield* dependencies.credentials.findPasswordByUserId(user.value.id);
|
|
151
|
+
if (Option.isNone(credential) ||
|
|
152
|
+
credential.value.revokedAt !== undefined) {
|
|
153
|
+
yield* Effect.annotateCurrentSpan("auth.result", "InvalidCredentials");
|
|
154
|
+
return AuthResult.InvalidCredentials();
|
|
155
|
+
}
|
|
156
|
+
const verified = yield* dependencies.hasher.verify({
|
|
157
|
+
hash: credential.value.passwordHash,
|
|
158
|
+
password: input.password,
|
|
159
|
+
});
|
|
160
|
+
if (!verified) {
|
|
161
|
+
yield* Effect.annotateCurrentSpan("auth.result", "InvalidCredentials");
|
|
162
|
+
return AuthResult.InvalidCredentials();
|
|
163
|
+
}
|
|
164
|
+
return yield* dependencies.authFlow.completePrimaryFactor({
|
|
165
|
+
userId: user.value.id,
|
|
166
|
+
email: user.value.email,
|
|
167
|
+
intent: "sign-in",
|
|
168
|
+
method: "password",
|
|
169
|
+
amr: ["pwd"],
|
|
170
|
+
aal: "aal1",
|
|
171
|
+
claims: { emailVerified: user.value.emailVerified },
|
|
172
|
+
...(input.trustedDeviceToken === undefined
|
|
173
|
+
? {}
|
|
174
|
+
: { trustedDeviceToken: input.trustedDeviceToken }),
|
|
175
|
+
...(input.request === undefined ? {} : { request: input.request }),
|
|
176
|
+
...(input.metadata === undefined ? {} : { metadata: input.metadata }),
|
|
177
|
+
});
|
|
178
|
+
}),
|
|
179
|
+
});
|
|
180
|
+
export const PasswordLoginLive = Layer.effect(PasswordLogin)(Effect.gen(function* () {
|
|
181
|
+
const users = yield* UserStore;
|
|
182
|
+
const credentials = yield* CredentialStore;
|
|
183
|
+
const hasher = yield* PasswordHasher;
|
|
184
|
+
const authFlow = yield* AuthFlow;
|
|
185
|
+
return makePasswordLogin({
|
|
186
|
+
users,
|
|
187
|
+
credentials,
|
|
188
|
+
hasher,
|
|
189
|
+
authFlow,
|
|
190
|
+
});
|
|
191
|
+
}));
|
|
192
|
+
export class EmailAlreadyRegisteredError extends Data.TaggedError("EmailAlreadyRegisteredError") {
|
|
193
|
+
}
|
|
194
|
+
export class PasswordRegistration extends Context.Service()("auth/registration/Password") {
|
|
195
|
+
}
|
|
196
|
+
(function (PasswordRegistration) {
|
|
197
|
+
PasswordRegistration.make = (service) => PasswordRegistration.of(service);
|
|
198
|
+
})(PasswordRegistration || (PasswordRegistration = {}));
|
|
199
|
+
export const makePasswordRegistration = (dependencies) => {
|
|
200
|
+
const userIdBytes = dependencies.userIdBytes ?? 16;
|
|
201
|
+
const credentialIdBytes = dependencies.credentialIdBytes ?? 16;
|
|
202
|
+
return PasswordRegistration.make({
|
|
203
|
+
signUp: Effect.fn("auth.password.sign_up")(function* (input) {
|
|
204
|
+
yield* Effect.annotateCurrentSpan("auth.method", "password");
|
|
205
|
+
const existing = yield* dependencies.users.findByEmail(input.email);
|
|
206
|
+
if (Option.isSome(existing)) {
|
|
207
|
+
yield* Effect.annotateCurrentSpan("auth.result", "EmailAlreadyRegistered");
|
|
208
|
+
return yield* new EmailAlreadyRegisteredError({ email: input.email });
|
|
209
|
+
}
|
|
210
|
+
const now = yield* currentUnixMillis;
|
|
211
|
+
const emailVerified = input.emailVerified ?? false;
|
|
212
|
+
const userId = UserId(yield* dependencies.crypto.randomToken(userIdBytes));
|
|
213
|
+
const credentialId = CredentialId(yield* dependencies.crypto.randomToken(credentialIdBytes));
|
|
214
|
+
const passwordHash = yield* dependencies.hasher.hash({
|
|
215
|
+
password: input.password,
|
|
216
|
+
});
|
|
217
|
+
yield* dependencies.users.insert({
|
|
218
|
+
id: userId,
|
|
219
|
+
email: input.email,
|
|
220
|
+
emailVerified,
|
|
221
|
+
createdAt: now,
|
|
222
|
+
updatedAt: now,
|
|
223
|
+
metadata: input.metadata,
|
|
224
|
+
});
|
|
225
|
+
yield* dependencies.credentials.insertPassword({
|
|
226
|
+
id: credentialId,
|
|
227
|
+
userId,
|
|
228
|
+
passwordHash,
|
|
229
|
+
createdAt: now,
|
|
230
|
+
updatedAt: now,
|
|
231
|
+
});
|
|
232
|
+
yield* Effect.annotateCurrentSpan("auth.result", "registered");
|
|
233
|
+
return yield* dependencies.authFlow.completePrimaryFactor({
|
|
234
|
+
userId,
|
|
235
|
+
email: input.email,
|
|
236
|
+
intent: "sign-up",
|
|
237
|
+
method: "password",
|
|
238
|
+
amr: ["pwd"],
|
|
239
|
+
aal: "aal1",
|
|
240
|
+
claims: { emailVerified },
|
|
241
|
+
...(input.request === undefined ? {} : { request: input.request }),
|
|
242
|
+
...(input.metadata === undefined ? {} : { metadata: input.metadata }),
|
|
243
|
+
});
|
|
244
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
export const PasswordRegistrationLive = Layer.effect(PasswordRegistration)(Effect.gen(function* () {
|
|
248
|
+
const users = yield* UserStore;
|
|
249
|
+
const credentials = yield* CredentialStore;
|
|
250
|
+
const hasher = yield* PasswordHasher;
|
|
251
|
+
const authFlow = yield* AuthFlow;
|
|
252
|
+
const crypto = yield* Crypto;
|
|
253
|
+
return makePasswordRegistration({
|
|
254
|
+
users,
|
|
255
|
+
credentials,
|
|
256
|
+
hasher,
|
|
257
|
+
authFlow,
|
|
258
|
+
crypto,
|
|
259
|
+
});
|
|
260
|
+
}));
|
|
261
|
+
export class PasswordResetStartError extends Data.TaggedError("PasswordResetStartError") {
|
|
262
|
+
}
|
|
263
|
+
export class PasswordResetVerifyError extends Data.TaggedError("PasswordResetVerifyError") {
|
|
264
|
+
}
|
|
265
|
+
export class PasswordReset extends Context.Service()("auth/password/Reset") {
|
|
266
|
+
}
|
|
267
|
+
(function (PasswordReset) {
|
|
268
|
+
PasswordReset.make = (service) => PasswordReset.of(service);
|
|
269
|
+
})(PasswordReset || (PasswordReset = {}));
|
|
270
|
+
export const defaultPasswordResetTtl = Duration.minutes(15);
|
|
271
|
+
export const defaultPasswordResetSecretBytes = 32;
|
|
272
|
+
const passwordResetUserIdFromMetadata = (metadata) => {
|
|
273
|
+
const userId = metadata?.userId;
|
|
274
|
+
return typeof userId === "string" && userId.length > 0
|
|
275
|
+
? UserId(userId)
|
|
276
|
+
: undefined;
|
|
277
|
+
};
|
|
278
|
+
const passwordResetTtlMillis = (ttl) => {
|
|
279
|
+
const millis = Duration.toMillis(ttl);
|
|
280
|
+
return Number.isFinite(millis) && millis > 0
|
|
281
|
+
? Effect.succeed(millis)
|
|
282
|
+
: Effect.fail(new PasswordResetStartError({
|
|
283
|
+
message: "Password reset ttl must be a positive finite duration",
|
|
284
|
+
}));
|
|
285
|
+
};
|
|
286
|
+
const passwordResetStartSecret = (crypto, input, secretBytes) => {
|
|
287
|
+
if (input.secret !== undefined) {
|
|
288
|
+
return Effect.succeed(Redacted.make(Redacted.value(input.secret).trim()));
|
|
289
|
+
}
|
|
290
|
+
return crypto.randomToken(secretBytes).pipe(Effect.map(Redacted.make), Effect.mapError((cause) => new PasswordResetStartError({
|
|
291
|
+
message: "Failed to generate password reset secret",
|
|
292
|
+
cause,
|
|
293
|
+
})));
|
|
294
|
+
};
|
|
295
|
+
const passwordResetVerifySecret = (secret) => Redacted.make(Redacted.value(secret).trim());
|
|
296
|
+
const makePasswordResetUrl = (dependencies, input) => Effect.try({
|
|
297
|
+
try: () => dependencies.makeUrl(input),
|
|
298
|
+
catch: (cause) => new PasswordResetStartError({
|
|
299
|
+
message: "Failed to build password reset URL",
|
|
300
|
+
cause,
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
const invalidatePasswordResetChallenge = (challenge, id) => challenge
|
|
304
|
+
.consume(id)
|
|
305
|
+
.pipe(Effect.catchTag("ChallengeConsumeError", () => Effect.void));
|
|
306
|
+
export const makePasswordReset = (dependencies) => {
|
|
307
|
+
const ttl = dependencies.ttl ?? defaultPasswordResetTtl;
|
|
308
|
+
const secretBytes = dependencies.secretBytes ?? defaultPasswordResetSecretBytes;
|
|
309
|
+
const credentialIdBytes = dependencies.credentialIdBytes ?? 16;
|
|
310
|
+
return PasswordReset.make({
|
|
311
|
+
start: Effect.fn("auth.password_reset.start")(function* (input) {
|
|
312
|
+
yield* Effect.annotateCurrentSpan("auth.method", "password-reset");
|
|
313
|
+
const resolvedTtl = input.ttl ?? ttl;
|
|
314
|
+
const expiresIn = yield* passwordResetTtlMillis(resolvedTtl);
|
|
315
|
+
const now = yield* currentUnixMillis;
|
|
316
|
+
const genericStarted = {
|
|
317
|
+
email: input.email,
|
|
318
|
+
expiresAt: addMillis(now, expiresIn),
|
|
319
|
+
};
|
|
320
|
+
const userOption = yield* dependencies.users.findByEmail(input.email).pipe(Effect.mapError((cause) => new PasswordResetStartError({
|
|
321
|
+
message: "Failed to load user for password reset",
|
|
322
|
+
cause,
|
|
323
|
+
})));
|
|
324
|
+
if (Option.isNone(userOption)) {
|
|
325
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PasswordResetIgnored");
|
|
326
|
+
return genericStarted;
|
|
327
|
+
}
|
|
328
|
+
const user = userOption.value;
|
|
329
|
+
if (user.disabledAt !== undefined) {
|
|
330
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PasswordResetIgnored");
|
|
331
|
+
return genericStarted;
|
|
332
|
+
}
|
|
333
|
+
const credentialOption = yield* dependencies.credentials
|
|
334
|
+
.findPasswordByUserId(user.id)
|
|
335
|
+
.pipe(Effect.mapError((cause) => new PasswordResetStartError({
|
|
336
|
+
message: "Failed to load password credential for reset",
|
|
337
|
+
cause,
|
|
338
|
+
})));
|
|
339
|
+
if (Option.isSome(credentialOption) &&
|
|
340
|
+
credentialOption.value.revokedAt !== undefined) {
|
|
341
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PasswordResetIgnored");
|
|
342
|
+
return genericStarted;
|
|
343
|
+
}
|
|
344
|
+
const secret = yield* passwordResetStartSecret(dependencies.crypto, input, secretBytes);
|
|
345
|
+
const issued = yield* dependencies.challenge
|
|
346
|
+
.issue({
|
|
347
|
+
type: "reset-password",
|
|
348
|
+
subject: user.email,
|
|
349
|
+
ttl: resolvedTtl,
|
|
350
|
+
secret,
|
|
351
|
+
metadata: {
|
|
352
|
+
...input.metadata,
|
|
353
|
+
userId: user.id,
|
|
354
|
+
},
|
|
355
|
+
})
|
|
356
|
+
.pipe(Effect.mapError((cause) => new PasswordResetStartError({
|
|
357
|
+
message: "Failed to issue password reset challenge",
|
|
358
|
+
cause,
|
|
359
|
+
})));
|
|
360
|
+
const started = {
|
|
361
|
+
email: user.email,
|
|
362
|
+
expiresAt: issued.expiresAt,
|
|
363
|
+
};
|
|
364
|
+
const url = yield* makePasswordResetUrl(dependencies, {
|
|
365
|
+
...started,
|
|
366
|
+
challengeId: issued.id,
|
|
367
|
+
secret,
|
|
368
|
+
locale: input.locale,
|
|
369
|
+
metadata: input.metadata,
|
|
370
|
+
}).pipe(Effect.matchEffect({
|
|
371
|
+
onSuccess: Effect.succeed,
|
|
372
|
+
onFailure: (error) => invalidatePasswordResetChallenge(dependencies.challenge, issued.id).pipe(Effect.flatMap(() => Effect.fail(error))),
|
|
373
|
+
}));
|
|
374
|
+
yield* dependencies.authMailer
|
|
375
|
+
.send({
|
|
376
|
+
_tag: "PasswordReset",
|
|
377
|
+
to: user.email,
|
|
378
|
+
challengeId: issued.id,
|
|
379
|
+
url,
|
|
380
|
+
expiresAt: issued.expiresAt,
|
|
381
|
+
locale: input.locale,
|
|
382
|
+
metadata: input.metadata,
|
|
383
|
+
})
|
|
384
|
+
.pipe(Effect.matchEffect({
|
|
385
|
+
onSuccess: () => Effect.void,
|
|
386
|
+
onFailure: (cause) => invalidatePasswordResetChallenge(dependencies.challenge, issued.id).pipe(Effect.flatMap(() => Effect.fail(new PasswordResetStartError({
|
|
387
|
+
message: "Failed to send password reset email",
|
|
388
|
+
cause,
|
|
389
|
+
})))),
|
|
390
|
+
}));
|
|
391
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PasswordResetStarted");
|
|
392
|
+
return started;
|
|
393
|
+
}),
|
|
394
|
+
verify: Effect.fn("auth.password_reset.verify")(function* (input) {
|
|
395
|
+
yield* Effect.annotateCurrentSpan("auth.method", "password-reset");
|
|
396
|
+
const verified = yield* dependencies.challenge
|
|
397
|
+
.verify({
|
|
398
|
+
challengeId: input.challengeId,
|
|
399
|
+
type: "reset-password",
|
|
400
|
+
secret: passwordResetVerifySecret(input.secret),
|
|
401
|
+
})
|
|
402
|
+
.pipe(Effect.mapError((cause) => new PasswordResetVerifyError({
|
|
403
|
+
message: "Invalid password reset challenge",
|
|
404
|
+
cause,
|
|
405
|
+
})));
|
|
406
|
+
const userId = passwordResetUserIdFromMetadata(verified.metadata);
|
|
407
|
+
if (userId === undefined) {
|
|
408
|
+
return yield* new PasswordResetVerifyError({
|
|
409
|
+
message: "Password reset challenge is missing user metadata",
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
const userOption = yield* dependencies.users.findById(userId).pipe(Effect.mapError((cause) => new PasswordResetVerifyError({
|
|
413
|
+
message: "Failed to load user for password reset",
|
|
414
|
+
cause,
|
|
415
|
+
})));
|
|
416
|
+
if (Option.isNone(userOption) ||
|
|
417
|
+
userOption.value.disabledAt !== undefined ||
|
|
418
|
+
userOption.value.email !== Email(verified.subject)) {
|
|
419
|
+
return yield* new PasswordResetVerifyError({
|
|
420
|
+
message: "Invalid password reset challenge",
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
const credentialOption = yield* dependencies.credentials
|
|
424
|
+
.findPasswordByUserId(userId)
|
|
425
|
+
.pipe(Effect.mapError((cause) => new PasswordResetVerifyError({
|
|
426
|
+
message: "Failed to load password credential for reset",
|
|
427
|
+
cause,
|
|
428
|
+
})));
|
|
429
|
+
if (Option.isSome(credentialOption) &&
|
|
430
|
+
credentialOption.value.revokedAt !== undefined) {
|
|
431
|
+
return yield* new PasswordResetVerifyError({
|
|
432
|
+
message: "Password credential not found",
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
const now = yield* currentUnixMillis;
|
|
436
|
+
const passwordHash = yield* dependencies.hasher
|
|
437
|
+
.hash({ password: input.password })
|
|
438
|
+
.pipe(Effect.mapError((cause) => new PasswordResetVerifyError({
|
|
439
|
+
message: "Failed to hash password reset password",
|
|
440
|
+
cause,
|
|
441
|
+
})));
|
|
442
|
+
if (Option.isSome(credentialOption)) {
|
|
443
|
+
const updated = yield* dependencies.credentials
|
|
444
|
+
.updatePassword({
|
|
445
|
+
userId,
|
|
446
|
+
passwordHash,
|
|
447
|
+
updatedAt: now,
|
|
448
|
+
})
|
|
449
|
+
.pipe(Effect.mapError((cause) => new PasswordResetVerifyError({
|
|
450
|
+
message: "Failed to update password credential",
|
|
451
|
+
cause,
|
|
452
|
+
})));
|
|
453
|
+
if (Option.isNone(updated)) {
|
|
454
|
+
return yield* new PasswordResetVerifyError({
|
|
455
|
+
message: "Password credential not found",
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const credentialId = CredentialId(yield* dependencies.crypto.randomToken(credentialIdBytes).pipe(Effect.mapError((cause) => new PasswordResetVerifyError({
|
|
461
|
+
message: "Failed to generate password credential id",
|
|
462
|
+
cause,
|
|
463
|
+
}))));
|
|
464
|
+
yield* dependencies.credentials
|
|
465
|
+
.insertPassword({
|
|
466
|
+
id: credentialId,
|
|
467
|
+
userId,
|
|
468
|
+
passwordHash,
|
|
469
|
+
createdAt: now,
|
|
470
|
+
updatedAt: now,
|
|
471
|
+
})
|
|
472
|
+
.pipe(Effect.mapError((cause) => new PasswordResetVerifyError({
|
|
473
|
+
message: "Failed to create password credential",
|
|
474
|
+
cause,
|
|
475
|
+
})));
|
|
476
|
+
}
|
|
477
|
+
yield* dependencies.sessionStore
|
|
478
|
+
.revokeAllForUser({
|
|
479
|
+
userId,
|
|
480
|
+
revokedAt: now,
|
|
481
|
+
reason: "password_reset",
|
|
482
|
+
})
|
|
483
|
+
.pipe(Effect.mapError((cause) => new PasswordResetVerifyError({
|
|
484
|
+
message: "Failed to revoke sessions after password reset",
|
|
485
|
+
cause,
|
|
486
|
+
})));
|
|
487
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PasswordResetVerified");
|
|
488
|
+
}),
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
|
+
export const PasswordResetLive = (options) => Layer.effect(PasswordReset)(Effect.gen(function* () {
|
|
492
|
+
const challenge = yield* Challenge;
|
|
493
|
+
const users = yield* UserStore;
|
|
494
|
+
const credentials = yield* CredentialStore;
|
|
495
|
+
const hasher = yield* PasswordHasher;
|
|
496
|
+
const crypto = yield* Crypto;
|
|
497
|
+
const authMailer = yield* AuthMailer;
|
|
498
|
+
const sessionStore = yield* SessionStore;
|
|
499
|
+
return makePasswordReset({
|
|
500
|
+
...options,
|
|
501
|
+
challenge,
|
|
502
|
+
users,
|
|
503
|
+
credentials,
|
|
504
|
+
hasher,
|
|
505
|
+
crypto,
|
|
506
|
+
authMailer,
|
|
507
|
+
sessionStore,
|
|
508
|
+
});
|
|
509
|
+
}));
|
|
510
|
+
export const PasswordResetFeatureLive = (options) => {
|
|
511
|
+
const { passwordHasher, ...resetOptions } = options;
|
|
512
|
+
return PasswordResetLive(resetOptions).pipe(Layer.provideMerge(Pbkdf2PasswordHasherLive(passwordHasher)));
|
|
513
|
+
};
|
|
514
|
+
export class PasswordSetError extends Data.TaggedError("PasswordSetError") {
|
|
515
|
+
}
|
|
516
|
+
export class PasswordChangeError extends Data.TaggedError("PasswordChangeError") {
|
|
517
|
+
}
|
|
518
|
+
export class PasswordManagement extends Context.Service()("auth/password/Management") {
|
|
519
|
+
}
|
|
520
|
+
(function (PasswordManagement) {
|
|
521
|
+
PasswordManagement.make = (service) => PasswordManagement.of(service);
|
|
522
|
+
})(PasswordManagement || (PasswordManagement = {}));
|
|
523
|
+
export const makePasswordManagement = (dependencies) => {
|
|
524
|
+
const credentialIdBytes = dependencies.credentialIdBytes ?? 16;
|
|
525
|
+
return PasswordManagement.make({
|
|
526
|
+
set: Effect.fn("auth.password.set")(function* (input) {
|
|
527
|
+
const credential = yield* dependencies.credentials
|
|
528
|
+
.findPasswordByUserId(input.userId)
|
|
529
|
+
.pipe(Effect.mapError((cause) => new PasswordSetError({
|
|
530
|
+
reason: "internal",
|
|
531
|
+
message: "Failed to load password credential",
|
|
532
|
+
cause,
|
|
533
|
+
})));
|
|
534
|
+
if (Option.isSome(credential)) {
|
|
535
|
+
return yield* new PasswordSetError({
|
|
536
|
+
reason: credential.value.revokedAt === undefined
|
|
537
|
+
? "password_exists"
|
|
538
|
+
: "password_revoked",
|
|
539
|
+
message: "Password credential already exists",
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
const now = yield* currentUnixMillis;
|
|
543
|
+
const credentialId = CredentialId(yield* dependencies.crypto.randomToken(credentialIdBytes).pipe(Effect.mapError((cause) => new PasswordSetError({
|
|
544
|
+
reason: "internal",
|
|
545
|
+
message: "Failed to generate password credential id",
|
|
546
|
+
cause,
|
|
547
|
+
}))));
|
|
548
|
+
const passwordHash = yield* dependencies.hasher
|
|
549
|
+
.hash({ password: input.password })
|
|
550
|
+
.pipe(Effect.mapError((cause) => new PasswordSetError({
|
|
551
|
+
reason: "internal",
|
|
552
|
+
message: "Failed to hash password",
|
|
553
|
+
cause,
|
|
554
|
+
})));
|
|
555
|
+
yield* dependencies.credentials
|
|
556
|
+
.insertPassword({
|
|
557
|
+
id: credentialId,
|
|
558
|
+
userId: input.userId,
|
|
559
|
+
passwordHash,
|
|
560
|
+
createdAt: now,
|
|
561
|
+
updatedAt: now,
|
|
562
|
+
metadata: input.metadata,
|
|
563
|
+
})
|
|
564
|
+
.pipe(Effect.mapError((cause) => new PasswordSetError({
|
|
565
|
+
reason: "internal",
|
|
566
|
+
message: "Failed to create password credential",
|
|
567
|
+
cause,
|
|
568
|
+
})));
|
|
569
|
+
}),
|
|
570
|
+
change: Effect.fn("auth.password.change")(function* (input) {
|
|
571
|
+
const credential = yield* dependencies.credentials
|
|
572
|
+
.findPasswordByUserId(input.userId)
|
|
573
|
+
.pipe(Effect.mapError((cause) => new PasswordChangeError({
|
|
574
|
+
reason: "internal",
|
|
575
|
+
message: "Failed to load password credential",
|
|
576
|
+
cause,
|
|
577
|
+
})));
|
|
578
|
+
if (Option.isNone(credential)) {
|
|
579
|
+
return yield* new PasswordChangeError({
|
|
580
|
+
reason: "password_missing",
|
|
581
|
+
message: "Password credential not found",
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (credential.value.revokedAt !== undefined) {
|
|
585
|
+
return yield* new PasswordChangeError({
|
|
586
|
+
reason: "password_revoked",
|
|
587
|
+
message: "Password credential not found",
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
const verified = yield* dependencies.hasher
|
|
591
|
+
.verify({
|
|
592
|
+
hash: credential.value.passwordHash,
|
|
593
|
+
password: input.currentPassword,
|
|
594
|
+
})
|
|
595
|
+
.pipe(Effect.mapError((cause) => new PasswordChangeError({
|
|
596
|
+
reason: "internal",
|
|
597
|
+
message: "Failed to verify current password",
|
|
598
|
+
cause,
|
|
599
|
+
})));
|
|
600
|
+
if (!verified) {
|
|
601
|
+
return yield* new PasswordChangeError({
|
|
602
|
+
reason: "invalid_current_password",
|
|
603
|
+
message: "Current password is invalid",
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
const now = yield* currentUnixMillis;
|
|
607
|
+
const passwordHash = yield* dependencies.hasher
|
|
608
|
+
.hash({ password: input.newPassword })
|
|
609
|
+
.pipe(Effect.mapError((cause) => new PasswordChangeError({
|
|
610
|
+
reason: "internal",
|
|
611
|
+
message: "Failed to hash new password",
|
|
612
|
+
cause,
|
|
613
|
+
})));
|
|
614
|
+
const updated = yield* dependencies.credentials
|
|
615
|
+
.updatePassword({
|
|
616
|
+
userId: input.userId,
|
|
617
|
+
passwordHash,
|
|
618
|
+
updatedAt: now,
|
|
619
|
+
})
|
|
620
|
+
.pipe(Effect.mapError((cause) => new PasswordChangeError({
|
|
621
|
+
reason: "internal",
|
|
622
|
+
message: "Failed to update password credential",
|
|
623
|
+
cause,
|
|
624
|
+
})));
|
|
625
|
+
if (Option.isNone(updated)) {
|
|
626
|
+
return yield* new PasswordChangeError({
|
|
627
|
+
reason: "password_missing",
|
|
628
|
+
message: "Password credential not found",
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}),
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
export const PasswordManagementLive = Layer.effect(PasswordManagement)(Effect.gen(function* () {
|
|
635
|
+
const credentials = yield* CredentialStore;
|
|
636
|
+
const hasher = yield* PasswordHasher;
|
|
637
|
+
const crypto = yield* Crypto;
|
|
638
|
+
return makePasswordManagement({ credentials, hasher, crypto });
|
|
639
|
+
}));
|
|
640
|
+
export const PasswordFeatureLive = (options) => Layer.mergeAll(PasswordLoginLive, PasswordRegistrationLive, PasswordManagementLive).pipe(Layer.provideMerge(Pbkdf2PasswordHasherLive(options)));
|
|
641
|
+
//# sourceMappingURL=Password.js.map
|