Files
clinicpro/docs/api/auth.md
T
hamed e7b90a6399 feat(api): add dashboard endpoints for clinic, doctor, and secretary roles
- Implemented GET /api/v1/dashboard/clinic to return clinic stats and today's schedule for clinic owners.
- Implemented GET /api/v1/dashboard/doctor to return doctor's stats and today's schedule for doctors.
- Implemented GET /api/v1/dashboard/secretary to return stats and conditional appointments for secretaries.

feat(migrations): create user_active_context and mobile_verification_otp tables

- Added migration to create user_active_context table for tracking active user sessions.
- Added migration to create mobile_verification_otp table for handling mobile number verification.

feat(migrations): create site_config table for application settings

- Added migration to create site_config table to store various site configuration settings.

feat(appointments): create MyAppointmentsController for user-specific appointments

- Added MyAppointmentsController to handle fetching user-specific appointments with pagination and filtering.

feat(auth): implement NotificationMobileController for mobile number verification

- Added NotificationMobileController to handle OTP requests and verification for mobile number changes.

feat(auth): create MobileVerificationOtp entity for OTP management

- Created MobileVerificationOtp entity to manage OTP records for mobile verification.

feat(auth): create UserActiveContext entity for user session management

- Created UserActiveContext entity to manage user active sessions.

feat(config): implement SiteConfigController for managing site settings

- Added SiteConfigController to handle fetching and updating site configuration settings.

feat(config): create SiteConfig entity and repository for configuration management

- Created SiteConfig entity and repository to manage site configuration data.
2026-06-11 12:20:12 +03:30

510 lines
11 KiB
Markdown

# Authentication API
> **Prefix:** `/api/v1/user` and `/oauth`
> **Permission:** All endpoints in this module are **PUBLIC** (no JWT required) except `userinfo` and `logout`
---
## POST `/api/v1/user/send-code`
Send OTP code to mobile number.
**Permission:** `PUBLIC`
### Request Body
```json
{
"mobile": "09123456789"
}
```
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `mobile` | string | ✅ | Format: `09XXXXXXXXX` (11 digits) |
### Response `200`
```json
{
"success": true,
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"message": "کد تأیید ارسال شد"
}
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_001` | 422 | Invalid mobile format |
| `ERR_AUTH_004` | 429 | OTP rate limit exceeded |
---
## POST `/api/v1/user/verify-code`
Verify OTP code. Returns whether this is a new or existing user.
**Permission:** `PUBLIC`
### Request Body
```json
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"code": "123456"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `uuid` | string | ✅ | UUID returned from `send-code` |
| `code` | string | ✅ | 6-digit OTP |
### Response `200`
```json
{
"success": true,
"data": {
"message": "کد تأیید شد",
"is_new_user": false,
"uuid": "550e8400-e29b-41d4-a716-446655440000"
}
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_002` | 401 | Invalid OTP code |
| `ERR_AUTH_003` | 401 | OTP expired |
| `ERR_VALIDATION_002` | 422 | Missing required field |
---
## POST `/api/v1/user/register`
Complete registration for new users (called only when `is_new_user: true`).
**Permission:** `PUBLIC`
### Request Body
```json
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"real_name": "علی احمدی"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `uuid` | string | ✅ | Verified UUID from `verify-code` |
| `real_name` | string | ❌ | User's full name |
### Response `201`
```json
{
"success": true,
"data": {
"message": "ثبت‌نام با موفقیت انجام شد",
"uuid": "550e8400-e29b-41d4-a716-446655440000"
}
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_002` | 422 | Missing uuid |
| `ERR_CONFLICT_001` | 409 | User already registered |
---
## POST `/api/v1/user/login`
Login with mobile number and password (for users who set a password).
**Permission:** `PUBLIC`
### Request Body
```json
{
"mobile_number": "09123456789",
"password": "mypassword"
}
```
| Field | Type | Required |
|-------|------|----------|
| `mobile_number` | string | ✅ |
| `password` | string | ✅ |
### Response `200`
```json
{
"success": true,
"data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200..."
}
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_005` | 401 | Wrong credentials |
| `ERR_AUTH_006` | 403 | Account suspended |
| `ERR_AUTH_004` | 429 | Too many attempts |
---
## POST `/oauth/token`
Exchange verified UUID for JWT access token.
**Permission:** `PUBLIC`
### Request Body
```json
{
"grant_type": "mobile",
"uuid": "550e8400-e29b-41d4-a716-446655440000"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `grant_type` | string | ✅ | Must be `"mobile"` |
| `uuid` | string | ✅ | UUID from verified OTP flow |
### Response `200`
```json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200..."
}
```
> JWT payload: `{ username: mobile_number, roles: [...], iat, exp }`
> Access token TTL: **1 hour** | Refresh token TTL: **30 days** (stored in Redis)
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_002` | 400 | Invalid or expired UUID |
---
## POST `/oauth/token/refresh`
Refresh expired JWT using refresh token.
**Permission:** `PUBLIC`
### Request Body
```json
{
"refresh_token": "def50200..."
}
```
### Response `200`
```json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200..."
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Invalid or expired refresh token |
---
## GET `/oauth/userinfo`
Get authenticated user info — extended with multi-context support.
**Permission:** `AUTH` — requires valid JWT
### Headers
```
Authorization: Bearer <token>
```
### Response `200`
```json
{
"success": true,
"data": {
"id": 4766,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"mobile_number": "09123456789",
"realName": "دکتر وحید درویشی",
"status": 1,
"roles": ["ROLE_USER", "ROLE_DOCTOR"],
"primary_role": "doctor",
"db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
"db_key": "hmac-sha256-hash...",
"context": {
"type": "doctor",
"db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
"name": "مطب شخصی دکتر وحید درویشی",
"role": "doctor"
},
"available_contexts": [
{
"type": "doctor",
"db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
"name": "مطب شخصی دکتر وحید درویشی",
"role": "doctor"
},
{
"type": "clinic",
"db_uuid": "clinic-uuid-...",
"name": "کلینیک سلامت",
"role": "doctor"
}
]
}
}
```
| فیلد | نوع | توضیح |
|------|-----|-------|
| `primary_role` | string | نقش اصلی: `admin` \| `clinic` \| `doctor` \| `secretary` \| `user` |
| `db_uuid` | string\|null | UUID موجودیت فعال (null = هنوز context انتخاب نشده) |
| `db_key` | string\|null | `HMAC-SHA256(db_uuid, APP_SECRET)` برای اعتبارسنجی |
| `context` | object\|null | context فعال انتخاب‌شده |
| `available_contexts` | array | همه محیط‌های کاری قابل انتخاب |
**قانون `primary_role`** (اولویت‌بندی):
- `ROLE_ADMIN``"admin"`
- `ROLE_CLINIC``"clinic"`
- `ROLE_DOCTOR``"doctor"`
- `ROLE_SECRETARY``"secretary"`
- بقیه → `"user"`
**قانون `db_uuid`**:
- اگر یک context وجود دارد: خودکار فعال می‌شود
- اگر چند context وجود دارد و کاربر هنوز انتخاب نکرده: `null` — frontend باید صفحه انتخاب نشان دهد
- پس از `POST /api/v1/auth/switch-context`: برابر context انتخاب‌شده
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Invalid or missing token |
---
## POST `/api/v1/auth/switch-context`
تغییر محیط کاری فعال — باید از لیست `available_contexts` انتخاب شود.
**Permission:** `AUTH`
### Request Body
```json
{
"db_uuid": "clinic-uuid-..."
}
```
| فیلد | نوع | Required | توضیح |
|------|-----|----------|-------|
| `db_uuid` | string (UUID) | ✅ | UUID محیط کاری از لیست `available_contexts` |
### Response `200`
```json
{
"success": true,
"data": {
"db_uuid": "clinic-uuid-...",
"db_key": "new-hmac-hash...",
"context": {
"type": "clinic",
"db_uuid": "clinic-uuid-...",
"name": "کلینیک سلامت",
"role": "doctor"
}
}
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing or invalid token |
| `ERR_AUTH_006` | 403 | `db_uuid` در لیست context های این کاربر نیست |
| `ERR_VALIDATION_001` | 422 | `db_uuid` ارسال نشده |
---
## Notification Mobile (OTP)
Endpoints for setting/verifying a separate SMS notification number for doctors and clinics. This number receives appointment SMS notifications instead of the account login mobile.
> **Permission:** All 4 endpoints require `IS_AUTHENTICATED_FULLY` (JWT).
---
## POST `/api/v1/notification-mobile/request-otp`
Request an OTP code to verify a new notification mobile number.
**Permission:** `AUTH`
### Request Body
```json
{
"target": "doctor",
"new_mobile": "09123456789"
}
```
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `target` | string | ✅ | `doctor` or `clinic` |
| `new_mobile` | string | ✅ | Format: `09XXXXXXXXX` |
### Response `200`
```json
{
"success": true,
"data": {
"message": "کد تأیید ارسال شد",
"expires_in": 300
}
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_001` | 422 | Invalid `target` or mobile format |
| `ERR_NOT_FOUND_001` | 404 | No doctor/clinic profile found for this user |
### Notes
- Previous unused OTPs for the entity are deleted before creating a new one
- OTP is 6-digit, expires in 5 minutes
- Sends via SMS asynchronously
---
## POST `/api/v1/notification-mobile/verify`
Verify the OTP and save the notification mobile number.
**Permission:** `AUTH`
### Request Body
```json
{
"target": "doctor",
"otp_code": "123456"
}
```
| Field | Type | Required |
|-------|------|----------|
| `target` | string | ✅ `doctor` or `clinic` |
| `otp_code` | string | ✅ 6-digit code |
### Response `200`
```json
{
"success": true,
"data": {
"notification_mobile": "09123456789",
"message": "شماره اعلان با موفقیت ذخیره شد"
}
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_001` | 422 | Invalid target / OTP expired / wrong code |
| `ERR_NOT_FOUND_001` | 404 | No OTP request found or profile not found |
---
## GET `/api/v1/notification-mobile/{target}`
Get the current notification mobile for the authenticated user.
**Permission:** `AUTH`
**Path param:** `target``doctor` or `clinic`
### Response `200`
```json
{
"success": true,
"data": {
"notification_mobile": "09123456789"
}
}
```
- Returns `null` for `notification_mobile` if not set
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | No profile found for this user |
---
## DELETE `/api/v1/notification-mobile/{target}`
Remove the notification mobile number.
**Permission:** `AUTH`
**Path param:** `target``doctor` or `clinic`
### Response `200`
```json
{
"success": true,
"data": {
"message": "شماره اعلان حذف شد"
}
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | No profile found for this user |
---
## POST `/oauth/logout`
Invalidate the refresh token (stored in Redis).
**Permission:** `AUTH`
### Request Body
```json
{
"refresh_token": "def50200..."
}
```
| Field | Type | Required |
|-------|------|----------|
| `refresh_token` | string | ❌ |
### Response `200`
```json
{
"success": true,
"data": {
"message": "با موفقیت خارج شدید"
}
}
```