Implement ALTCHA captcha service with challenge generation and solution verification
- Added AltchaService class for managing ALTCHA captcha challenges and solutions. - Created CaptchaController to handle API requests for generating challenges. - Introduced CaptchaGuard for validating captcha solutions on public endpoints. - Developed unit tests for AltchaService to ensure challenge creation and solution verification functionality. - Implemented integration tests for the Captcha API endpoint and captcha bypass behavior when disabled. - Added documentation for the Captcha API in the corresponding markdown file.
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
> **Prefix:** `/api/v1/user` and `/oauth`
|
||||
> **Permission:** All endpoints in this module are **PUBLIC** (no JWT required) except `userinfo` and `logout`
|
||||
|
||||
> **🛡️ ALTCHA captcha:** وقتی `ALTCHA_ENABLED=true` است (در prod)، endpointهای `send-code`، `register`، `otp-login` و `reset-password` علاوه بر بدنهی خود، فیلد `altcha` (payload حلشدهی widget) را الزامی میکنند؛ در غیر این صورت `422` با کد `ERR_CAPTCHA_001` برمیگردد. جزئیات و مسیر challenge در [captcha.md](captcha.md).
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/user/send-code`
|
||||
@@ -548,6 +550,8 @@ Submit a pre-registration request (doctor or clinic). Public endpoint — no aut
|
||||
|
||||
**Permission:** Public
|
||||
|
||||
> **🛡️ ALTCHA:** وقتی `ALTCHA_ENABLED=true` است، فیلد `altcha` (payload حلشدهی widget) الزامی است؛ در غیر این صورت `422` با `ERR_CAPTCHA_001`. رجوع به [captcha.md](captcha.md).
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# Captcha API (ALTCHA)
|
||||
|
||||
کپچای خودمیزبان مبتنی بر ALTCHA (Proof-of-Work، بدون تصویر، بدون سرویس خارجی). برای محافظت از endpointهای عمومی و بدون احراز هویت در برابر اسپم/بات.
|
||||
|
||||
- فعال/غیرفعال با `ALTCHA_ENABLED` (پیشفرض `false` در dev/test، در prod باید `true` شود).
|
||||
- وقتی غیرفعال است، همهی endpointها بدون فیلد `altcha` کار میکنند.
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/altcha/challenge`
|
||||
|
||||
صدور یک challenge امضاشده برای حل proof-of-work سمت مرورگر. **عمومی** (بدون توکن).
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"algorithm": "SHA-256",
|
||||
"challenge": "417af7b4d4b661c437b99c7e4d5d55747d36f01a8c28d77013af95683b71e642",
|
||||
"maxnumber": 100000,
|
||||
"salt": "1d5e0b983af8930ed6d4081f?expires=1783665554&",
|
||||
"signature": "5d797fdbe9d9cd322fdb02c95c4a673ea92fae09bb63122091713895d0897a11"
|
||||
}
|
||||
```
|
||||
|
||||
- `signature` — HMAC-SHA256 روی challenge با کلید سرور (`ALTCHA_HMAC_KEY`). قابل جعل نیست.
|
||||
- `salt` شامل `expires` است؛ پس از انقضا challenge نامعتبر میشود.
|
||||
- خروجی مستقیماً به `<altcha-widget>` داده میشود (پاسخ خام است، نه envelope استاندارد).
|
||||
|
||||
---
|
||||
|
||||
## اعمال کپچا روی endpointهای محافظتشده
|
||||
|
||||
کلاینت مقدار حلشدهی widget (base64) را با کلید `altcha` در بدنهی همان درخواست میفرستد:
|
||||
|
||||
```json
|
||||
{ "mobile": "09120000000", "altcha": "eyJhbGdvcml0aG0iOiJTSEEt..." }
|
||||
```
|
||||
|
||||
endpointهایی که وقتی `ALTCHA_ENABLED=true` است فیلد `altcha` را الزامی میکنند:
|
||||
|
||||
| Endpoint | Method |
|
||||
|---|---|
|
||||
| `/api/v1/user/send-code` | POST |
|
||||
| `/api/v1/user/register` | POST |
|
||||
| `/api/v1/user/otp-login` | POST |
|
||||
| `/api/v1/user/reset-password` | POST |
|
||||
| `/api/v1/pre-registration` | POST |
|
||||
|
||||
> endpointهای امتیاز/نظر (`POST /api/v1/rate`، `POST /api/v1/comment`) پشت JWT هستند (کاربر لاگینشده)، بنابراین کپچا نمیگیرند — بات برای رسیدن به آنها باید توکن معتبر داشته باشد که خودش از مسیر OTP (کپچادار) عبور میکند.
|
||||
|
||||
### خطای اعتبارسنجی کپچا `422`
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"data": null,
|
||||
"errors": [{ "code": "ERR_CAPTCHA_001", "field": "altcha", "message": "تأیید امنیتی ناموفق بود. لطفاً صفحه را رفرش کنید و دوباره تلاش کنید" }]
|
||||
}
|
||||
```
|
||||
|
||||
این خطا زمانی برمیگردد که `altcha` غایب، نامعتبر، منقضی، یا **قبلاً استفاده شده** (replay) باشد. هر challenge فقط **یکبار** معتبر است (امضایش در Redis سوزانده میشود).
|
||||
|
||||
---
|
||||
|
||||
## امنیت
|
||||
|
||||
- Challenge با HMAC-SHA256 و کلید سرور امضا میشود؛ کلید هرگز به client نمیرود.
|
||||
- انقضا (`ALTCHA_EXPIRE_SECONDS`) داخل salt امضاشده است.
|
||||
- استفادهی یکباره: signature هر challenge در pool اختصاصی Redis (`altcha.pool`) تا زمان انقضا نگهداری میشود → ضدِ replay.
|
||||
- کپچا مکملِ rate-limit موجود است، نه جایگزین آن.
|
||||
Reference in New Issue
Block a user