# 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 نامعتبر می‌شود. - خروجی مستقیماً به `` داده می‌شود (پاسخ خام است، نه 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 موجود است، نه جایگزین آن.