Files
clinicpro/docs/api/auth.md
T

629 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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": "با موفقیت خارج شدید"
}
}
```
---
## POST `/api/v1/pre-registration`
Submit a pre-registration request (doctor or clinic). Public endpoint — no auth required.
**Permission:** Public
### Request Body
```json
{
"type": "independent_doctor",
"name": "دکتر علی احمدی",
"mobile": "09121234567",
"info": "متخصص داخلی، ۱۰ سال سابقه"
}
```
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `type` | string | ✅ | `independent_doctor` \| `doctor_with_clinic` \| `clinic_manager` |
| `name` | string | ✅ | min 2 chars |
| `mobile` | string | ✅ | 1015 chars |
| `info` | string | ❌ | specialty, address, etc. |
**Type meanings:**
| Value | Roles granted on approval | Entities created |
|-------|--------------------------|-----------------|
| `independent_doctor` | `ROLE_DOCTOR` | Doctor |
| `doctor_with_clinic` | `ROLE_DOCTOR` + `ROLE_CLINIC` | Doctor + Clinic |
| `clinic_manager` | `ROLE_CLINIC` | Clinic |
### Response `200`
```json
{
"success": true,
"data": { "uuid": "...", "status": "pending" }
}
```
### Error Codes
| Code | HTTP | Meaning |
|------|------|---------|
| `VALIDATION_ERROR` | 422 | Invalid type / mobile / name |
| `DUPLICATE_REQUEST` | 409 | Pending request already exists for this mobile |
---
## POST `/api/v1/user/otp-login`
ورود با کد OTP تأییدشده — **فقط کاربران موجود**، کاربر جدید ایجاد نمی‌شود.
**Permission:** `PUBLIC`
### Request Body
```json
{ "uuid": "550e8400-e29b-41d4-a716-446655440000" }
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `uuid` | string | ✅ | UUID از `verify-code` (باید قبلاً verify شده باشد) |
### Response `200`
```json
{
"access_token": "eyJ...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token_expires_in": 2592000
}
```
### Error Codes
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_002` | 422 | uuid ارسال نشده |
| `ERR_AUTH_002` | 400 | uuid نامعتبر یا تأیید نشده |
| `ERR_AUTH_003` | 400 | OTP منقضی شده |
| `ERR_AUTH_005` | 401 | کاربری با این شماره یافت نشد |
---
## POST `/api/v1/user/reset-password`
تغییر رمز عبور با تأیید هویت از طریق OTP.
**Permission:** `PUBLIC`
### Request Body
```json
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"new_password": "newpass123"
}
```
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `uuid` | string | ✅ | UUID از `verify-code` (باید قبلاً verify شده باشد) |
| `new_password` | string | ✅ | حداقل ۶ کاراکتر |
### Response `200`
```json
{
"success": true,
"data": { "message": "رمز عبور با موفقیت تغییر یافت" }
}
```
### Error Codes
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_001` | 422 | uuid یا new_password نادرست/ناقص |
| `ERR_AUTH_002` | 400 | uuid نامعتبر یا تأیید نشده |
| `ERR_NOT_FOUND_001` | 404 | کاربری با این شماره یافت نشد |