# strong-password-refiner

```bash
npx zod-refiners add strong-password-refiner
```

Validates a configurable password-strength policy and reports the
**first** rule that fails — "too short" instead of one generic
"password is invalid" message. Every rule's wording is overridable
through `options.messages`.

## Installs

* `strong-password-refiner.ts` — the factory
* `types.ts` — the shared `RefineTuple` type (dependency)

## Signature

```ts
function createStrongPasswordRefiner<T extends Record<string, unknown>>(
  field: keyof T & string,
  options?: StrongPasswordOptions,
): RefineTuple<T>;
```

## Options

```ts
{
  minLength: 8,
  maxLength: 128,
  requireUppercase: true,
  requireLowercase: true,
  requireDigit: true,
  requireSpecialChar: true,
  specialChars: "!@#$%^&*()_+-=[]{};':\"\\|,.<>/?",
  forbidWhitespace: true,
  forbidRepeatingChars: false,
  messages: {}, // per-rule overrides: tooShort, tooLong, missingUppercase,
                // missingLowercase, missingDigit, missingSpecialChar,
                // containsWhitespace, repeatingChars, invalidType,
                // generic (fallback before any rule has failed)
}
```

## Usage

```ts
import { z } from "zod";
import { createStrongPasswordRefiner } from "@/lib/refiners/strong-password-refiner";

type SignupForm = { password: string };

const signupSchema = z.object({ password: z.string() }).refine(
  ...createStrongPasswordRefiner<SignupForm>("password", {
    minLength: 10,
    messages: { tooShort: "Use at least 10 characters" },
  }),
);
```

## Behavior

| Case                    | Result                                                              |
| ----------------------- | ------------------------------------------------------------------- |
| All rules pass          | Parses successfully                                                 |
| A rule fails            | Issue at `path: ["password"]` with the first failing rule's message |
| Non-string value        | Issue at `path: ["password"]` with the `invalidType` message        |
| `minLength > maxLength` | Throws at construction time (config error)                          |

The tuple's second element is a plain `{ message, path }` object, as
`RefineTuple` requires. The predicate writes the first failing rule's
message into it before returning `false`, and Zod reads it back when
building the issue.
