# Admin-requested customer password recovery

The customer directory/profile in `planck-admin` contains **ارسال بازیابی رمز عبور**. The owner confirms the destination before requesting delivery. The owner never sees or chooses a customer's password, recovery link, access token, or refresh token.

## Deployment

Local project activation on 2026-09-30: migration `202609300008_admin_customer_recovery.sql` was applied successfully to `wlsxzyeernwtsetzyltq`. The redirect `http://localhost:3000/auth/admin-recovery**` is configured. Supabase still uses its default email templates; custom SMTP is not configured. No real recovery email was sent and no password changed during verification. The live admin UI review requires a renewed owner/MFA login.

1. Apply `supabase/migrations/202609300008_admin_customer_recovery.sql` after the activity/search and account-retention migrations. It adds a private recovery-attempt ledger and three narrow RPCs; it does not change any account or password.
2. Set the admin server's existing `MARKETPLACE_URL` to the customer site's HTTPS origin (no path, credentials, query, or fragment). Development defaults to `http://localhost:3000`; production has no fallback.
3. In Supabase Auth redirect URLs, allow the customer callback including its request query: `https://CUSTOMER-HOST/auth/admin-recovery**`. For this local workspace use `http://localhost:3000/auth/admin-recovery**`. Do not broadly allow unrelated domains.
4. The Auth **Reset password** email template must use the standard `{{ .ConfirmationURL }}` link. Its verified redirect includes `type=recovery` in the fragment. This admin-requested flow intentionally has no PKCE verifier in the admin browser because the recipient uses their own browser. Customer-initiated PKCE recovery remains unchanged.
5. Configure SMTP in Supabase Auth for delivery to real customer addresses. Supabase's default email service has restrictive recipient/rate limits. An HTTP 200 only means Auth accepted the request; the UI and activity log do not claim confirmed delivery. Provider restrictions, rate limits, and uncertain network outcomes have separate messages and history.

## Authorization and retained history

- Only a current owner session with AAL2 can reserve or complete a send attempt. Same-origin JSON is required by the admin endpoint.
- The destination is the current confirmed `auth.users.email`, read on the server. Client-supplied email, phone, redirect and password fields are rejected. Unverified `contact_email` metadata never authorizes recovery.
- Sending has a durable 60-second cooldown per customer, a maximum of 3 attempts per customer in 24 hours and 20 per administrator in 24 hours. Failed and unknown attempts also count.
- A reservation is committed before requesting delivery. The separate result is `accepted`, `failed`, or `unknown`, never `delivered`. The reservation remains even if the connection fails before the result can be recorded.
- Every request/result is append-only customer activity with a searchable Persian title. The private request ledger blocks delete/truncate and references Auth accounts with deletion restricted. It contains no passwords, recovery links or bearer tokens.

## Recipient flow

The callback removes all URL fragments before network requests, never stores tokens in local/session storage, and sends only the access token and request UUID to its same-origin server. Auth validates the token; the database also requires the exact recipient and verified email, an OTP/recovery AMR issued after the request and within 10 minutes, and one bound Auth session ID. Password-login tokens, another user's proof, old proof, failed requests and changed email addresses cannot redeem the request.

Only a 10-minute HttpOnly recovery cookie scoped to `/api/auth/recovery` is issued. No normal application login session is created. The recipient sees the target email before entering a new password. Reload restores an unexpired form from the scoped recovery cookie. Successful reset uses the existing password-update flow, clears recovery access and requires a fresh login. Account suspensions continue to be enforced.

## Validation

`planck-admin/tests/customer-password-reset.test.mjs` covers authorization, server-owned destinations, durable reservation/results, upstream failures and safe redirects. Marketplace tests `admin-recovery.test.mjs` and `admin-customer-recovery-db.test.mjs` cover callback/reload isolation, claim binding, owner/AAL2 permissions, rate limits, retention and Persian activity search. Tests use isolated fixtures and mocked email delivery; no real customer email or password change is performed.

Official references: [Supabase password recovery](https://supabase.com/docs/guides/auth/passwords), [email templates](https://supabase.com/docs/guides/auth/auth-email-templates), [SMTP configuration](https://supabase.com/docs/guides/auth/auth-smtp).
