# Forgot Password

Native, email-based password reset. A user requests a link, opens it, and sets a new password — with single-use tokens, anti-enumeration, and invalidation of earlier refresh tokens.

## Overview

The flow has two halves. First the user **requests a reset** (`POST /v1/auth/forgot-password`) and, if an eligible account exists, receives an email containing a single-use link. Then they open that link and **complete the reset** (`POST /v1/auth/reset-password`), which sets the new password, invalidates the refresh tokens issued before the change, and signs them straight back in.

The reset link opens the branded Aurea reset page at `https://api.aureahub.com/reset-password?token=…` on any device. It opens the mobile app directly only once Universal Links / App Links are configured for the app, which they currently are not. The completion endpoints are **token-authenticated** — the single-use token is the capability — so they work from a plain browser without a tenant signature.

> ℹ️ Password reset is **enabled per tenant** and is off by default. When it is disabled, `forgot-password` returns `404`. Enable it from the Admin Dashboard (tenant → *Password Reset*) along with the token validity window, sender name, and reset link base URL.

## Request a reset

Send the user's email. The response is **always identical** whether or not the account exists, so the endpoint never reveals which emails are registered. OAuth-only accounts (Google/Apple) receive a "use social sign-in" email instead of a reset link — again, the API response is unchanged.

### `POST /v1/auth/forgot-password`

Authentication: none (public endpoint).

Emails a reset link if an eligible account exists. Always returns the same response (anti-enumeration). Sent in the tenant context (tenant API key).

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string | yes | Email address of the account to reset |
| `locale` | string | no | Email language: 'en' (default) or 'it' |

**Responses**

`200` OK

```json
{
  "ok": true,
  "message": "If an account exists for that email, a password-reset link is on its way."
}
```

`404` Not Enabled

```json
{ "error": "Password reset is not enabled for this tenant" }
```

`429` Too Many Requests

```json
{ "error": "Too many requests. Please try again later." }
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/auth/forgot-password \
  -H "Content-Type: application/json" \
  -H "x-tenant-api-key: <your-tenant-api-key>" \
  -H "x-timestamp: <unix-seconds>" \
  -H "x-signature: <hmac-sha256>" \
  -d '{"email": "alice@example.com", "locale": "en"}'
```

> ℹ️ This endpoint is signed with your tenant API key (the same HMAC headers used for other tenant-scoped calls). The official SDKs add these headers automatically.

## Validate a token

Optionally check a token before showing the reset form — useful to display an "expired link" message early. This does **not** consume the token and needs no tenant signature.

### `GET /v1/auth/reset-password/validate`

Authentication: none (public endpoint).

Returns whether a reset token is still valid, without consuming it. Token-authenticated — no tenant signature required.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `token` | string | yes | The reset token from the email link |

**Responses**

`200` OK

```json
{ "valid": true }
```

`429` Too Many Requests

```json
{ "error": "Too many requests. Please try again later." }
```

**Example request**

```bash
curl "https://api.aureahub.com/v1/auth/reset-password/validate?token=<token>"
```

## Complete the reset

Submit the token and the new password. On success the user's password is rotated, refresh tokens issued before the change stop working, a security-notice email is sent, and a **fresh token pair is returned** so the user is immediately signed in.

### `POST /v1/auth/reset-password`

Authentication: none (public endpoint).

Sets the new password, invalidates earlier refresh tokens, and returns fresh tokens (auto-login). Token-authenticated.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `token` | string | yes | The reset token from the email link |
| `newPassword` | string | yes | New password — min 8 chars, with an uppercase letter, a lowercase letter, and a digit |
| `locale` | string | no | Confirmation email language: 'en' (default) or 'it' |

**Responses**

`200` OK

```json
{
  "ok": true,
  "accessToken": "eyJhbGc...",
  "refreshToken": "eyJhbGc...",
  "expiresIn": 900,
  "userId": "uuid"
}
```

`400` Invalid Token

```json
{ "error": "This reset link is invalid or has expired. Request a new one." }
```

`400` Weak Password

```json
{ "error": "That password appears in a known data breach. Please choose a different one." }
```

`429` Too Many Requests

```json
{ "error": "Too many requests. Please try again later." }
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{"token": "<token>", "newPassword": "NewSecurePass123"}'
```

## Operator reset (support)

Support staff can trigger a reset email for a specific user from a trusted backend, using an operator bearer token (role `admin` or `tenantadmin`).

### `POST /v1/auth/admin/send-reset`

Authentication: bearer token required.

Operator-triggered reset email for a specific user. Requires an admin or tenantadmin bearer token.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `userId` | string | yes | ID of the user to send a reset link to |
| `tenantId` | string | no | Target tenant — required for platform admins; tenantadmins are scoped to their own tenant |

**Responses**

`200` OK

```json
{ "ok": true, "sent": true }
```

`403` Forbidden

```json
{ "error": "Forbidden" }
```

`404` Not Enabled

```json
{ "error": "Password reset is not enabled for this tenant" }
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/auth/admin/send-reset \
  -H "Authorization: Bearer <operator-token>" \
  -H "Content-Type: application/json" \
  -d '{"userId": "<user-uuid>"}'
```

## Implementation

The two completion endpoints are what your reset page calls. A minimal browser flow — validate the token on load, then submit the new password:

```javascript
// 1) On the reset page, read the token from the URL and validate it
const token = new URLSearchParams(location.search).get('token');

const { valid } = await fetch(
  `https://api.aureahub.com/v1/auth/reset-password/validate?token=${encodeURIComponent(token)}`
).then(r => r.json());

if (!valid) {
  // show "this link is invalid or has expired — request a new one"
  return;
}

// 2) Submit the new password
const res = await fetch('https://api.aureahub.com/v1/auth/reset-password', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ token, newPassword })
});

if (!res.ok) {
  const err = await res.json();
  throw new Error(err.error || 'Reset failed');
}

// 3) Success — the response signs the user straight back in
const { accessToken, refreshToken, userId } = await res.json();
sessionStorage.setItem('access_token', accessToken);
localStorage.setItem('refresh_token', refreshToken);
```

To kick the flow off from your app's login screen, call `forgot-password` with the user's email and show a generic "check your email" confirmation regardless of the response:

```javascript
async function requestPasswordReset(email) {
  await fetch('https://api.aureahub.com/v1/auth/forgot-password', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' /* + tenant HMAC headers (SDK adds these) */ },
    body: JSON.stringify({ email, locale: 'en' })
  });
  // Always show the same message — never reveal whether the account exists.
  return 'If an account exists for that email, a reset link is on its way.';
}
```

## Security

- **Single-use, hashed tokens.** Only a SHA-256 hash of the token is stored; the raw token lives only in the email link. Each token works once and is consumed on a successful reset.
- **Expiry.** Links expire after the tenant's configured window (60 minutes by default).
- **Anti-enumeration.** `forgot-password` returns an identical response and timing whether or not the email exists; OAuth-only accounts get a "use social sign-in" email instead of a link.
- **Breach check.** New passwords are checked against the Have I Been Pwned k-anonymity range API and rejected if they appear in a known breach.
- **Rate limiting.** Requests are throttled per email and per IP.
- **Session invalidation.** Completing a reset rejects refresh tokens issued before the change, so a stolen session can no longer be renewed. Access tokens already issued stay valid until they expire (15 minutes by default).

> ℹ️ On a correct device, an existing non-custodial or MPC wallet keeps working transparently after a password reset — the login password never gates local wallet key material, so no seed or backup phrase is required to keep using the wallet.

---

Web version: https://docs.aureahub.com/#auth-forgot-password
