172 lines
6.1 KiB
Markdown
172 lines
6.1 KiB
Markdown
# Authentication & Authorization
|
|
|
|
Negareh API supports two actor types — **users** (customers) and **admins** (back-office). Both authenticate via **phone OTP** and receive **JWT access tokens** with **refresh tokens**.
|
|
|
|
## Overview
|
|
|
|
```
|
|
┌─────────────┐ OTP request ┌─────────────┐
|
|
│ Client │ ──────────────────► │ Auth API │
|
|
│ │ ◄────────────────── │ (SMS/Redis)│
|
|
└─────────────┘ OTP code └─────────────┘
|
|
│ │
|
|
│ OTP verify │
|
|
└──────────────────────────────────► │
|
|
▼
|
|
┌─────────────────────────┐
|
|
│ Access JWT + Refresh │
|
|
│ token (hashed in DB) │
|
|
└─────────────────────────┘
|
|
```
|
|
|
|
## OTP flow
|
|
|
|
### User (public)
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/public/auth/otp/request` | POST | Send OTP to phone (creates user on verify) |
|
|
| `/public/auth/otp/verify` | POST | Verify OTP → returns tokens + user profile |
|
|
| `/public/auth/refresh` | POST | Exchange refresh token for new token pair |
|
|
|
|
### Admin
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/admin/auth/otp/request` | POST | Send OTP (phone must exist in `admins` table) |
|
|
| `/admin/auth/otp/verify` | POST | Verify OTP → returns tokens + admin profile |
|
|
| `/admin/auth/refresh` | POST | Exchange refresh token for new token pair |
|
|
|
|
### OTP details
|
|
|
|
- Phone numbers are normalized to local format via `normalizePhoneToLocal()` (`09xxxxxxxxx`).
|
|
- OTP codes are 5-digit numeric values stored in **Redis** with TTL (`OTP_EXPIRATION_TIME`, default 240 seconds).
|
|
- OTP request endpoints are rate-limited (`@Throttle`: 3 requests per 180 seconds).
|
|
- SMS delivery uses SMS.ir patterns (configured via `SMS_*` env vars).
|
|
|
|
### Request / verify DTOs
|
|
|
|
```typescript
|
|
// Request OTP
|
|
{ "phone": "09362532122" }
|
|
|
|
// Verify OTP
|
|
{ "phone": "09362532122", "otp": "12345" }
|
|
```
|
|
|
|
## JWT tokens
|
|
|
|
### Access token
|
|
|
|
- Signed with `JWT_SECRET`.
|
|
- Expiration: `JWT_EXPIRATION_TIME` (seconds).
|
|
- Sent as `Authorization: Bearer <token>`.
|
|
|
|
**User payload:**
|
|
|
|
```typescript
|
|
{ userId: string }
|
|
```
|
|
|
|
**Admin payload:**
|
|
|
|
```typescript
|
|
{ adminId: string }
|
|
```
|
|
|
|
### Refresh token
|
|
|
|
- Opaque 64-character hex string (not a JWT).
|
|
- Stored **hashed** (SHA-256) in the `refresh_tokens` table.
|
|
- Expiration: `REFRESH_TOKEN_EXPIRE` (days).
|
|
- Rotated on each refresh — old token is deleted in the same transaction as the new pair.
|
|
- Type field distinguishes `user` vs `admin` tokens.
|
|
|
|
### Refresh response shape
|
|
|
|
```json
|
|
{
|
|
"accessToken": { "token": "...", "expire": 1710000000000 },
|
|
"refreshToken": { "token": "...", "expire": 1711000000000 }
|
|
}
|
|
```
|
|
|
|
`expire` values are Unix timestamps in milliseconds.
|
|
|
|
## Guards
|
|
|
|
### `AuthGuard` (users)
|
|
|
|
- Applied with `@UseGuards(AuthGuard)` on public routes that require a logged-in user.
|
|
- Verifies JWT from `Authorization` header.
|
|
- Sets `request.userId` from the token payload.
|
|
- Extract identity in controllers with `@UserId()`.
|
|
|
|
### `AdminAuthGuard` (admins)
|
|
|
|
- Applied with `@UseGuards(AdminAuthGuard)` on admin routes.
|
|
- Verifies JWT and sets `request.adminId`.
|
|
- Extract identity with `@AdminId()`.
|
|
- Reads `@Permissions(...)` metadata from the handler/class for RBAC checks.
|
|
|
|
## Role-based access control (RBAC)
|
|
|
|
Admins have a **role** with attached **permissions**. Permission names are defined in `PermissionEnum` (e.g. `view_orders`, `create_invoice`, `manage_admins`).
|
|
|
|
### Declaring required permissions
|
|
|
|
```typescript
|
|
@Get('admin/orders')
|
|
@UseGuards(AdminAuthGuard)
|
|
@Permissions(PermissionEnum.VIEW_ORDERS)
|
|
findAllAsAdmin(@Query() dto: FindOrdersDto) {
|
|
return this.orderService.findOrdersAsAdmin(dto);
|
|
}
|
|
```
|
|
|
|
The `@Permissions()` decorator stores required permission keys in metadata. `AdminAuthGuard` reads them via `Reflector`.
|
|
|
|
> **Note:** Permission enforcement against the database/cache is partially implemented. The guard structure and decorators are in place; ensure permission checks are active before relying on them in production.
|
|
|
|
## Route prefixes
|
|
|
|
Routes are namespaced by audience in the controller path (not a global prefix):
|
|
|
|
| Prefix | Audience | Guard |
|
|
|--------|----------|-------|
|
|
| `public/` | End users | `AuthGuard` (when protected) |
|
|
| `admin/` | Back-office | `AdminAuthGuard` |
|
|
|
|
Unauthenticated routes use `public/auth/...` or `admin/auth/...` without guards.
|
|
|
|
## WebSocket auth
|
|
|
|
Admin WebSocket connections use `WsAdminAuthGuard` in the notifications module. Follow the same JWT verification pattern as HTTP admin auth.
|
|
|
|
## Environment variables
|
|
|
|
| Variable | Purpose |
|
|
|----------|---------|
|
|
| `JWT_SECRET` | Signing key for access tokens |
|
|
| `JWT_EXPIRATION_TIME` | Access token TTL (seconds) |
|
|
| `REFRESH_TOKEN_EXPIRE` | Refresh token TTL (days) |
|
|
| `OTP_EXPIRATION_TIME` | OTP cache TTL (seconds) |
|
|
| `REDIS_URI` / `REDIS_HOST` | OTP and cache storage |
|
|
| `SMS_BASE_URL`, `SMS_API_KEY`, `SMS_PATTERN_OTP` | SMS delivery |
|
|
|
|
## Security practices
|
|
|
|
- Refresh tokens are hashed at rest; raw tokens are only returned once to the client.
|
|
- Token refresh runs inside a database transaction to prevent race conditions.
|
|
- Rate limiting on OTP and refresh endpoints reduces brute-force risk.
|
|
- Never log tokens, OTP codes, or secrets.
|
|
- Use HTTPS in production; CORS is enabled with `credentials: true` for cookie support if needed.
|
|
|
|
## Client integration checklist
|
|
|
|
1. Request OTP with normalized Iranian mobile number.
|
|
2. Verify OTP and store both `accessToken.token` and `refreshToken.token`.
|
|
3. Send `Authorization: Bearer <accessToken>` on protected requests.
|
|
4. On 401, call the refresh endpoint with the refresh token.
|
|
5. Replace stored tokens with the new pair from the refresh response.
|