# Google Authenticator (TOTP)

**3-series** (`3xxxxxxxxx`, prefix configurable via `INTERNAL_PHONE_SERIES_PREFIX`) use **TOTP only** — not SMS and not fixed `123456`. **Real numbers** use fixed `123456` while `ENABLE_REAL_NUMBER_DEMO_OTP=true` (default).

## One Authenticator for all 3-series numbers

Each phone used to get its **own** random secret, so a code from `3123456789` did **not** work for `3214567890`.

**Shared secret (all environments):** all 3-series users use the same secret, so **one** Google Authenticator entry works for **any** `3xxxxxxxxx` login (same 6-digit code at that moment). Default key: `JBSWY3DPEHPK3PXP`. Override with `INTERNAL_SHARED_TOTP_SECRET` if needed.

Add **one** manual entry with that secret (not one scan per phone). `request-otp` still returns `totpSetup` per phone for QR on first login; codes are identical.

## 3-series internal flow

1. `POST /request-otp` `{ "phone": "3123456789" }`
2. Response includes **`totpSetup`**:

```json
{
  "totpSetup": {
    "provisioningUri": "otpauth://totp/GrowKit:3123456789?secret=...&issuer=GrowKit",
    "secretBase32": "...",
    "issuer": "GrowKit"
  }
}
```

3. Scan **`provisioningUri`** in Google Authenticator (or enter `secretBase32` manually).
4. `POST /verify-otp` `{ "phone": "3123456789", "otp": "<6 digits from app>" }`
5. Next logins: `"totpRequired": true` on request-otp — use authenticator code only (no SMS, no fixed PIN).

## Real numbers (e.g. 8688259303)

- **Random SMS-style OTP** stored server-side (or `otp` in JSON if `OTP_INCLUDE_IN_RESPONSE=true`).
- Optional: after login, enable TOTP:
  - `POST /auth/totp/setup` with Bearer token
  - Scan QR / URI
  - `POST /auth/totp/enable` `{ "otp": "<6 digits>" }`
- Then login uses authenticator instead of SMS/interim OTP.

## Troubleshooting

**`ArgumentError: wrong number of arguments (given 2, expected 0..1)`** in `request-otp` — **`rotp` 6.x** changed the API. Use `totp.provisioning_uri(label)` only (issuer is set on `ROTP::TOTP.new(..., issuer:, name:)`). Restart the API after pulling: `docker compose restart api`.

**`uninitialized constant UserTotp::ROTP`** — the **`rotp` gem is not installed in the Render Docker image** (stale build). Fix:

1. Commit and push **`Gemfile`**, **`Gemfile.lock`**, **`Dockerfile`**, and **`config/application.rb`** (must list `rotp`).
2. Render → **Manual Deploy** → **Clear build cache** (required once so `bundle install` runs again).
3. Build must pass the Docker step that runs `require 'rotp'`. If build fails there, the lockfile was not copied into the image.
4. After deploy, `/health` must be 200. If you still see the 503 TOTP message, the service is running an **old image** — redeploy with cache cleared.

Also run **`rails db:migrate`** on Aiven if OTP columns are missing (see `docs/render-deploy-checklist.md`).

## Flutter

- Show QR from `provisioningUri` (use a QR widget) or deep-link instructions.
- Verify screen: same 6-digit field; codes rotate every **30 seconds**.
