Password and Email Risk
Reject breached passwords and unwanted email domains through explicit policy.
Password and email risk run at credential/identity mutation points, not only HTTP routes, so alternate transports cannot bypass them.
Breached password policy
PasswordRiskPolicy receives a redacted password and operation: sign-up, reset, set, or change. The HIBP adapter implements BreachedPasswordProvider.
import * as HibpPwnedPasswords from "@effect-auth/core/HibpPwnedPasswords";
import {
BreachedPasswordProvider,
PasswordRiskPolicy,
} from "@effect-auth/core/PasswordRisk";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
const HibpLive = HibpPwnedPasswords.layer({
applicationId: "acme-auth-worker",
cacheFailure: "fail",
timeoutMs: 2_000,
staleOnError: true,
});
export const PasswordRiskLive = Layer.effect(
PasswordRiskPolicy,
Effect.flatMap(BreachedPasswordProvider, (provider) =>
PasswordRiskPolicy.make(provider, {
occurrenceThreshold: 1,
providerFailure: "fail-closed",
})
)
).pipe(Layer.provide(HibpLive));The adapter computes SHA-1 locally for the HIBP range protocol and sends only the first five hexadecimal characters with response padding. SHA-1 is not used to store passwords. HIBP still observes the auth Worker's network endpoint, request timing, and prefix.
An optional HibpRangeCache is keyed only by prefixes and stores the returned range entries required for local suffix matching. The bundled memory cache is bounded; same-prefix requests are coalesced. For distributed Workers, provide an app-owned bounded cache and never persist the queried password, full hash, or its specific suffix separately.
Email acceptance policy
EmailAcceptancePolicy covers password signup and identity add/replace. The disposable-domain adapter uses an application-pinned dataset and performs no request-time download.
import { layer as disposableEmailDomainsLayer } from "@effect-auth/core/DisposableEmailDomains";
import { layerNoDeps as emailAcceptancePolicyLayerNoDeps } from "@effect-auth/core/EmailRisk";
import { Layer } from "effect";
const ReputationLayer = disposableEmailDomainsLayer({
domains: pinnedDisposableDomains,
allowlist: ["approved.example"],
denylist: ["blocked.example"],
});
export const EmailRiskLayer = emailAcceptancePolicyLayerNoDeps({
disposable: "deny",
blocked: "deny",
suspicious: "deny",
outage: { mode: "fail" },
operations: ["password-sign-up", "identity-add", "identity-replace"],
}).pipe(Layer.provide(ReputationLayer));Allowlist wins over denylist/dataset. Exact parent matches cover subdomains. Record dataset source, license, revision, digest, and review process. Existing login is never blocked by a changed email policy.
Wire feature Layers
import {
PasswordDefaultLive,
PasswordResetDefaultLive,
} from "@effect-auth/core/Password";
export const AppPasswordLive = PasswordDefaultLive(
undefined,
PasswordRiskLive,
EmailRiskLayer
);
export const AppPasswordResetLive = PasswordResetDefaultLive(
passwordResetOptions,
PasswordRiskLive
);Use IdentityManagementWithEmailAcceptanceLive for identity email mutations. Bare IdentityManagementLive does not apply email acceptance.
| Operation | Maintained order |
|---|---|
| Signup | Size, normalize, email policy, password policy, hash/write |
| Reset | Inspect challenge/account/credential, password policy, hash, atomic consume/update |
| Initial set | Size, ensure absent credential, policy, hash/write |
| Change | Size, verify current credential, replacement policy, hash/write |
| Identity add/replace | Normalize, email policy for email only, mutation |
Passwords over 1,024 UTF-8 bytes fail before provider lookup. A reset password rejected by policy leaves a valid unexpired challenge unconsumed, so the user may retry with another password. Password change verifies the current credential before calling the risk provider.
| Explicit policy | Behavior |
|---|---|
PasswordRiskPolicy.noopLayer | Accepts passwords without a provider |
EmailAcceptancePolicy.noopLayer | Accepts email without a provider |
emailAcceptancePolicyLayerNoDeps(...) | Uses the required EmailReputation |
Construct explicit production policy when these defaults do not match requirements. Test all operation kinds, provider failures, HIBP prefix-only transport/cache bounds, domain parent/IDNA matches, allowlist precedence, reset retry, and absence of sensitive provider data from telemetry.