feat: implement short-lived grant system for OTP verification and enhance rate limiting across authentication endpoints
This commit is contained in:
+47
-35
@@ -65,19 +65,22 @@ Verify OTP code. Returns whether this is a new or existing user.
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "کد تأیید شد",
|
||||
"is_new_user": false,
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000"
|
||||
"message": "کد با موفقیت تایید شد.",
|
||||
"grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039",
|
||||
"is_new_user": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `grant` یک توکنِ **یکبارمصرف و کوتاهعمر (۱۲۰ ثانیه)** است که در مرحلهی بعد (`register` / `oauth/token` / `otp-login` / `reset-password`) مصرف میشود. خودِ OTP (`uuid`) پس از تأیید موفق حذف میشود. هر grant فقط یکبار قابلاستفاده است؛ هر action که grant میخواهد یک grant جداگانه لازم دارد (یعنی برای new-user که هم `register` و هم login لازم است، باید دوبار verify انجام شود یا مستقیماً `oauth/token` صدا زده شود که خودش کاربر را میسازد).
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_002` | 401 | Invalid OTP code |
|
||||
| `ERR_AUTH_003` | 401 | OTP expired |
|
||||
| `ERR_AUTH_002` | 400 | Invalid OTP code |
|
||||
| `ERR_AUTH_003` | 400 | OTP expired |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing required field |
|
||||
| `ERR_RATE_LIMIT_001` | 429 | بیش از ۱۰ تلاش در ۱۵ دقیقه (per-IP) |
|
||||
|
||||
---
|
||||
|
||||
@@ -90,14 +93,14 @@ Complete registration for new users (called only when `is_new_user: true`).
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039",
|
||||
"real_name": "علی احمدی"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `uuid` | string | ✅ | Verified UUID from `verify-code` |
|
||||
| `grant` | string | ✅ | grant یکبارمصرف از `verify-code` |
|
||||
| `real_name` | string | ❌ | User's full name |
|
||||
|
||||
### Response `201`
|
||||
@@ -114,8 +117,8 @@ Complete registration for new users (called only when `is_new_user: true`).
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_VALIDATION_002` | 422 | Missing uuid |
|
||||
| `ERR_CONFLICT_001` | 409 | User already registered |
|
||||
| `ERR_VALIDATION_002` | 422 | Missing grant |
|
||||
| `ERR_AUTH_002` | 400 | grant نامعتبر یا منقضی/مصرفشده |
|
||||
|
||||
---
|
||||
|
||||
@@ -141,26 +144,28 @@ Login with mobile number and password (for users who set a password).
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
||||
"refresh_token": "def50200..."
|
||||
}
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
||||
"refresh_token": "def50200...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 900,
|
||||
"refresh_token_expires_in": 2592000
|
||||
}
|
||||
```
|
||||
|
||||
> فقط برای کاربران staff (`ROLE_ADMIN/DOCTOR/CLINIC/SECRETARY`). Access token TTL: **۹۰۰ ثانیه (۱۵ دقیقه)**.
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_005` | 401 | Wrong credentials |
|
||||
| `ERR_AUTH_006` | 403 | Account suspended |
|
||||
| `ERR_AUTH_004` | 429 | Too many attempts |
|
||||
| `ERR_AUTH_006` | 403 | کاربر staff نیست |
|
||||
| `429` | 429 | Too many attempts (۱۰ در دقیقه per-IP) |
|
||||
|
||||
---
|
||||
|
||||
## POST `/oauth/token`
|
||||
|
||||
Exchange verified UUID for JWT access token.
|
||||
Exchange a one-time `grant` (from `verify-code`) for a JWT access token. اگر کاربری با آن موبایل وجود نداشته باشد، ساخته میشود.
|
||||
|
||||
**Permission:** `PUBLIC`
|
||||
|
||||
@@ -168,30 +173,36 @@ Exchange verified UUID for JWT access token.
|
||||
```json
|
||||
{
|
||||
"grant_type": "mobile",
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000"
|
||||
"grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `grant_type` | string | ✅ | Must be `"mobile"` |
|
||||
| `uuid` | string | ✅ | UUID from verified OTP flow |
|
||||
| `grant` | string | ✅ | grant یکبارمصرف از `verify-code` (عمر ۱۲۰ ثانیه) |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
||||
"refresh_token": "def50200..."
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
||||
"refresh_token": "def50200...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 900,
|
||||
"refresh_token_expires_in": 2592000
|
||||
}
|
||||
```
|
||||
|
||||
> JWT payload: `{ username: mobile_number, roles: [...], iat, exp }`
|
||||
> Access token TTL: **1 hour** | Refresh token TTL: **30 days** (stored in Redis)
|
||||
> Access token TTL: **۹۰۰ ثانیه (۱۵ دقیقه)** | Refresh token TTL: **۳۰ روز** (در cache، هششده و rotate-on-use)
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_AUTH_002` | 400 | Invalid or expired UUID |
|
||||
| `ERR_VALIDATION_001` | 400 | `grant_type` نامعتبر |
|
||||
| `ERR_VALIDATION_002` | 422 | `grant` ارسال نشده |
|
||||
| `ERR_AUTH_002` | 400 | grant نامعتبر، منقضی یا قبلاً مصرفشده |
|
||||
| `ERR_RATE_LIMIT_001` | 429 | بیش از ۱۰ درخواست در ۵ دقیقه (per-IP) |
|
||||
|
||||
---
|
||||
|
||||
@@ -578,12 +589,12 @@ Submit a pre-registration request (doctor or clinic). Public endpoint — no aut
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{ "uuid": "550e8400-e29b-41d4-a716-446655440000" }
|
||||
{ "grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039" }
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `uuid` | string | ✅ | UUID از `verify-code` (باید قبلاً verify شده باشد) |
|
||||
| `grant` | string | ✅ | grant یکبارمصرف از `verify-code` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
@@ -591,7 +602,7 @@ Submit a pre-registration request (doctor or clinic). Public endpoint — no aut
|
||||
"access_token": "eyJ...",
|
||||
"refresh_token": "...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"expires_in": 900,
|
||||
"refresh_token_expires_in": 2592000
|
||||
}
|
||||
```
|
||||
@@ -599,10 +610,10 @@ Submit a pre-registration request (doctor or clinic). Public endpoint — no aut
|
||||
### Error Codes
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_VALIDATION_002` | 422 | uuid ارسال نشده |
|
||||
| `ERR_AUTH_002` | 400 | uuid نامعتبر یا تأیید نشده |
|
||||
| `ERR_AUTH_003` | 400 | OTP منقضی شده |
|
||||
| `ERR_VALIDATION_002` | 422 | grant ارسال نشده |
|
||||
| `ERR_AUTH_002` | 400 | grant نامعتبر، منقضی یا مصرفشده |
|
||||
| `ERR_AUTH_005` | 401 | کاربری با این شماره یافت نشد |
|
||||
| `ERR_RATE_LIMIT_001` | 429 | بیش از ۱۰ درخواست در ۵ دقیقه (per-IP) |
|
||||
|
||||
---
|
||||
|
||||
@@ -615,15 +626,15 @@ Submit a pre-registration request (doctor or clinic). Public endpoint — no aut
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"new_password": "newpass123"
|
||||
"grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039",
|
||||
"new_password": "newpass1234"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Validation |
|
||||
|-------|------|----------|------------|
|
||||
| `uuid` | string | ✅ | UUID از `verify-code` (باید قبلاً verify شده باشد) |
|
||||
| `new_password` | string | ✅ | حداقل ۶ کاراکتر |
|
||||
| `grant` | string | ✅ | grant یکبارمصرف از `verify-code` |
|
||||
| `new_password` | string | ✅ | حداقل ۸ کاراکتر |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
@@ -636,6 +647,7 @@ Submit a pre-registration request (doctor or clinic). Public endpoint — no aut
|
||||
### Error Codes
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `ERR_VALIDATION_001` | 422 | uuid یا new_password نادرست/ناقص |
|
||||
| `ERR_AUTH_002` | 400 | uuid نامعتبر یا تأیید نشده |
|
||||
| `ERR_VALIDATION_001` | 422 | grant ارسال نشده یا رمز کمتر از ۸ کاراکتر |
|
||||
| `ERR_AUTH_002` | 400 | grant نامعتبر، منقضی یا مصرفشده |
|
||||
| `ERR_NOT_FOUND_001` | 404 | کاربری با این شماره یافت نشد |
|
||||
| `ERR_RATE_LIMIT_001` | 429 | بیش از ۵ درخواست در ۶۰ دقیقه (per-IP) |
|
||||
|
||||
Reference in New Issue
Block a user