mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-23 15:42:09 -05:00
feat(haveibeenpwned): add enable option (#8728)
Co-authored-by: Taesu <bytaesu@gmail.com>
This commit is contained in:
@@ -32,3 +32,4 @@ Timestampz
|
||||
Vercel
|
||||
rgba
|
||||
idtoken
|
||||
HIBP
|
||||
|
||||
@@ -26,19 +26,46 @@ When a user attempts to create an account or update their password with a compro
|
||||
```json
|
||||
{
|
||||
"code": "PASSWORD_COMPROMISED",
|
||||
"message": "Password is compromised"
|
||||
"message": "The password you entered has been compromised. Please choose a different password."
|
||||
}
|
||||
```
|
||||
|
||||
## Config
|
||||
## Options
|
||||
|
||||
You can customize the error message:
|
||||
### `enabled`
|
||||
|
||||
```ts
|
||||
haveIBeenPwned({
|
||||
customPasswordCompromisedMessage: "Please choose a more secure password."
|
||||
Enable or disable password checks against the HIBP database. Useful for skipping checks in development or testing without removing the plugin. Defaults to `true`.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { haveIBeenPwned } from "better-auth/plugins"
|
||||
|
||||
const auth = betterAuth({
|
||||
plugins: [
|
||||
haveIBeenPwned({
|
||||
enabled: process.env.NODE_ENV === 'production' // [!code highlight]
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### `customPasswordCompromisedMessage`
|
||||
|
||||
Customize the error message shown when a compromised password is detected.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { haveIBeenPwned } from "better-auth/plugins"
|
||||
|
||||
const auth = betterAuth({
|
||||
plugins: [
|
||||
haveIBeenPwned({
|
||||
customPasswordCompromisedMessage: "Please choose a more secure password." // [!code highlight]
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Only the first 5 characters of the password hash are sent to the API
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("have-i-been-pwned", async () => {
|
||||
},
|
||||
);
|
||||
const ctx = await auth.$context;
|
||||
|
||||
it("should prevent account creation with compromised password", async () => {
|
||||
const uniqueEmail = `test-${Date.now()}@example.com`;
|
||||
const compromisedPassword = "123456789";
|
||||
@@ -63,4 +64,28 @@ describe("have-i-been-pwned", async () => {
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.error?.status).toBe(400);
|
||||
});
|
||||
|
||||
describe("when enabled is false", async () => {
|
||||
const { client: disabledClient } = await getTestInstance(
|
||||
{
|
||||
plugins: [haveIBeenPwned({ enabled: false })],
|
||||
},
|
||||
{
|
||||
disableTestUser: true,
|
||||
},
|
||||
);
|
||||
|
||||
it("should allow account creation with compromised password", async () => {
|
||||
const uniqueEmail = `test-${Date.now()}@example.com`;
|
||||
|
||||
const result = await disabledClient.signUp.email({
|
||||
email: uniqueEmail,
|
||||
password: "123456789",
|
||||
name: "Test User",
|
||||
});
|
||||
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.data?.user).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,6 +66,9 @@ async function checkPasswordCompromise(
|
||||
}
|
||||
|
||||
export interface HaveIBeenPwnedOptions {
|
||||
/**
|
||||
* Custom error message shown when a compromised password is detected.
|
||||
*/
|
||||
customPasswordCompromisedMessage?: string | undefined;
|
||||
/**
|
||||
* Paths to check for password
|
||||
@@ -73,6 +76,12 @@ export interface HaveIBeenPwnedOptions {
|
||||
* @default ["/sign-up/email", "/change-password", "/reset-password"]
|
||||
*/
|
||||
paths?: string[];
|
||||
/**
|
||||
* Enable or disable password checks against the HIBP database.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enabled?: boolean | undefined;
|
||||
}
|
||||
|
||||
export const haveIBeenPwned = (options?: HaveIBeenPwnedOptions | undefined) => {
|
||||
@@ -91,6 +100,8 @@ export const haveIBeenPwned = (options?: HaveIBeenPwnedOptions | undefined) => {
|
||||
password: {
|
||||
...ctx.password,
|
||||
async hash(password) {
|
||||
if (options?.enabled === false) return originalHash(password);
|
||||
|
||||
const c = await getCurrentAuthContext();
|
||||
if (!c.path || !paths.includes(c.path)) {
|
||||
return originalHash(password);
|
||||
|
||||
Reference in New Issue
Block a user