# Authentication handoff — 2026-08-11

## Goal

Authentication is being refactored inside the `Authentication` module, gradually and without changing unrelated admin or representative flows. The current focus is secure customer OTP/password authentication. A representative is **not** a `users` table record; its authentication design remains a separate future concern.

## Current state

The worktree contains uncommitted Authentication-related changes. Do **not** use `git reset`, `git checkout`, or broad cleanup commands: these changes are intentional.

The following is already implemented and manually verified:

- OTP is stored in Redis cache, not in the database.
- The Redis key is an HMAC of the mobile number, so raw mobile numbers are not exposed in cache keys.
- The OTP itself is stored as a Laravel hash, never in plaintext.
- A short Redis lock prevents two simultaneous OTP verification requests from consuming the same code.
- OTP sending and OTP verification have per-mobile and per-IP rate limits.
- Password login has its own rate limit.
- On local environments with SMS disabled, the OTP is included in the response for development. In production, the OTP is sent through SMS and is not included in the response.
- Authentication exceptions and rate-limit errors use Persian messages.
- OTP request and verification endpoints are covered by feature tests.
- `users.type` was added with `customer` and `admin` enum values. Authentication responses return the user `type`.

## Important files

| Purpose | File |
| --- | --- |
| Main authentication orchestrator | `Modules/Authentication/Services/AuthenticationService.php` |
| OTP cache, hashing, locking and delivery flow | `Modules/Authentication/Services/OtpService.php` |
| OTP service contract | `Modules/Authentication/Services/Contracts/OtpServiceContract.php` |
| Auth routes and throttle middleware | `Modules/Authentication/routes/api.php` |
| Named rate limit definitions | `Modules/Authentication/app/Providers/RouteServiceProvider.php` |
| OTP validation | `Modules/Authentication/app/Http/Requests/OtpLoginRequest.php` |
| Authentication bindings | `Modules/Authentication/app/Providers/AuthenticationServiceProvider.php` |
| Feature tests | `Modules/Authentication/tests/Feature/OtpAuthenticationTest.php` |
| Auth config | `Modules/Authentication/config/config.php` |
| Persian messages | `lang/fa/auth.php` |
| Global exception rendering | `bootstrap/app.php` |

## Current API contract

```text
POST /api/v1/otp
POST /api/v1/verify-otp
POST /api/v1/verify-password
POST /api/v1/logout     (Sanctum authenticated)
```

`verify-otp` receives a **four-character string** OTP. The frontend must send a code with leading zeroes unchanged:

```json
{
  "mobile": "+989121234567",
  "otp": "0123"
}
```

Do not send OTP as a JSON number, because `0123` would become `123`.

## Local Redis setup

Local development is Windows with Redis running in WSL Ubuntu. If Redis is not running:

```bash
# Run inside Ubuntu / WSL
sudo service redis-server start
redis-cli ping
```

Expected result:

```text
PONG
```

The local `.env` uses these relevant values (do not commit `.env`):

```env
APP_LOCALE=fa
APP_FALLBACK_LOCALE=fa

AUTH_OTP_SMS_ENABLED=false
AUTH_OTP_EXPIRATION_MINUTES=2
AUTH_OTP_CACHE_STORE=redis

REDIS_CLIENT=predis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
CACHE_LIMITER=redis
```

`predis/predis` has already been added to Composer dependencies. The production server must provide Redis and use its own production `.env` values.

## Tests completed

File: `Modules/Authentication/tests/Feature/OtpAuthenticationTest.php`

The test suite currently passes five cases:

1. OTP request returns `remainingTime` and does not expose OTP in the test environment.
2. Invalid mobile number returns validation errors.
3. A repeated OTP request for the same mobile is rate limited.
4. Invalid OTP returns the Authentication error response.
5. Repeated invalid OTP verification attempts are rate limited.

Run it from the project root:

```powershell
php artisan test Modules/Authentication/tests/Feature/OtpAuthenticationTest.php
```

The last successful result was:

```text
Tests: 5 passed (26 assertions)
Duration: 1.01s
```

`phpunit.xml` has test-only settings for in-memory cache/rate limiting and Persian locale. Tests must not require real Redis or SMS.

## Exact continuation point

Two new files were created but are not yet connected to the application:

```text
Modules/Authentication/Services/Contracts/OtpCodeGeneratorContract.php
Modules/Authentication/Services/RandomOtpCodeGenerator.php
```

Their purpose is deterministic testing: production generates a cryptographically secure four-digit OTP with `random_int`, while tests will replace the generator with a fixed code. This lets us test successful OTP login, token response, user creation, and returned `type` without ever exposing a production-style OTP in test responses.

Before committing, complete these exact changes:

1. In `AuthenticationServiceProvider`, bind `OtpCodeGeneratorContract` to `RandomOtpCodeGenerator`.
2. Inject `OtpCodeGeneratorContract` into `OtpService`.
3. Replace the current `UtilityHelper::generateOtp()` call with `$this->otpCodeGenerator->generate()`.
4. Add a fake generator in tests and test a successful OTP verification.
5. Run the OTP test file again.
6. Review the diff, commit only intentional changes, then push.

At the time of writing, the Provider binding was **not yet present**, and `OtpService` still uses `UtilityHelper::generateOtp()`. Do not commit the new generator files until this connection is finished, otherwise they are dead code.

## Known follow-up work after the generator step

These are not implemented yet; do them one at a time after the successful OTP flow test:

1. Add comprehensive password-login feature tests, including its rate limit.
2. Make the default `ResponseMaker::success()` message translatable to Persian instead of hard-coded `Success`.
3. Add an SMS delivery abstraction with queue/retry/error handling appropriate to the provider.
4. Normalize mobile numbers at the Auth boundary so every equivalent format produces one canonical identity/cache key.
5. Add security audit logging without OTPs, passwords, tokens, or raw sensitive values.
6. Review token abilities and authorization separately when entering the Admin module. Do not add Admin middleware while the current task is Auth-only.

## Migration note

The type migration was created and run individually:

```text
Modules/Authentication/database/migrations/2026_08_09_120000_add_type_to_users_table.php
```

A full `php artisan migrate --force` previously stopped at an unrelated existing `discounts` table migration after a separate SEO migration succeeded. Investigate that duplicate migration before relying on a full migration run; do not modify it as part of this Auth handoff without a separate decision.

## Safe restart checklist on another device

1. Clone/pull the branch containing the eventual commit.
2. Run `composer install`.
3. Create `.env` from `.env.example` and set application, database, Redis and SMS values.
4. Ensure Redis is reachable.
5. Run the focused test command above.
6. Continue from **Exact continuation point**.
