X Enterprises
fastify-xauth-local

local.password-reset

Built-in POST/PUT routes for requesting and completing a password reset flow.

local.password-reset

Two built-in routes registered at {loginPath}/password-reset when local.enabled is true:

  • POST {loginPath}/password-reset — initiate a reset (send email, generate token, etc.) via the passwordReset(email) callback.
  • PUT {loginPath}/password-reset — complete the reset by supplying a reset token and new password via the passwordReset(null, token, hashedPassword) callback.

Both routes are automatically excluded from JWT verification.

Default paths: {prefix}/local/password-reset (e.g. /api/local/password-reset)

Signatures

POST — Request a password reset

POST {loginPath}/password-reset
Content-Type: application/json

{
  "email": string   // required, valid email format
}

PUT — Complete a password reset

PUT {loginPath}/password-reset
Content-Type: application/json

{
  "token": string,    // required, the reset token from the email
  "password": string  // required, new password (min 8 chars); hashed before callback
}

Params

POST params:

NameTypeRequiredDescription
emailstringYesThe account email to reset

PUT params:

NameTypeRequiredDescription
tokenstringYesThe reset token (from email link or other delivery)
passwordstringYesNew plaintext password (min 8 chars)

Returns

POST 200:

{ "message": "If an account with that email exists, a password reset link has been sent" }

Always returns 200 regardless of whether the email exists (prevents email enumeration).

PUT 200:

{ "message": "Password has been reset successfully" }

Throws

StatusMessageReason
400Invalid or expired reset tokenpasswordReset threw an error on PUT

Examples

Basic: request and complete a password reset

# Step 1: request a reset link
curl -X POST http://localhost:3000/api/local/password-reset \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com"}'

# Step 2: complete the reset with the token from the email
curl -X PUT http://localhost:3000/api/local/password-reset \
  -H "Content-Type: application/json" \
  -d '{"token":"abc123","password":"NewSecurePass1"}'

Advanced: implementing the passwordReset callback

The plugin hashes the new password before calling your passwordReset function on PUT. Your callback decides how to validate the token and store the new hash.

await fastify.register(xAuthLocal, {
  configs: [
    {
      name: "api",
      prefix: "/api",
      secret: process.env.JWT_SECRET,
      local: {
        enabled: true,
        userLookup: async (email) => db.users.findByEmail(email),
        /**
         * passwordReset is called in two ways:
         *   POST: passwordReset(email)           — initiate, send link
         *   PUT:  passwordReset(null, token, hash) — complete, validate + store
         */
        passwordReset: async (email, token, hashedPassword) => {
          if (email) {
            // Initiate: generate a token and send an email
            const resetToken = crypto.randomUUID();
            await db.resetTokens.create({
              email,
              token: resetToken,
              expiresAt: new Date(Date.now() + 15 * 60 * 1000),
            });
            await mailer.send({
              to: email,
              subject: "Reset your password",
              body: `Click here: https://app.example.com/reset?token=${resetToken}`,
            });
          } else {
            // Complete: validate token and update password
            const record = await db.resetTokens.findByToken(token);
            if (!record || record.expiresAt < new Date()) {
              throw new Error("Invalid or expired reset token");
            }
            await db.users.updatePassword(record.email, hashedPassword);
            await db.resetTokens.delete(token);
          }
        },
      },
    },
  ],
});

See Also

AI Context

package: "@xenterprises/fastify-xauth-local"
routes: POST {loginPath}/password-reset (request reset) | PUT {loginPath}/password-reset (complete reset)
use-when: Built-in password reset flow — POST requests a reset token, PUT completes with the new password
requires: local.enabled: true, local.passwordReset function
Copyright © 2026