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:
hamed
2026-07-10 10:31:59 +03:30
parent 11efed4100
commit 10b0743d9a
43 changed files with 5586 additions and 1554 deletions
+47
View File
@@ -812,3 +812,50 @@ clinic-pro-symfony/
> CSS فرانت‌اند از کلاس‌های template اختصاصی استفاده می‌کند (نه Tailwind).
> بعد از هر تغییر backend: `cache:clear`
> بعد از هر تغییر entity: `migrations:diff` سپس `migrations:migrate`
---
## 🛡️ ALTCHA — کپچای خودمیزبان (ضدِ اسپم/بات)
کپچای Proof-of-Work مبتنی بر [ALTCHA](https://altcha.org) — بدون تصویر، بدون تایپ، بدون سرویس خارجی (مناسب کاربران داخل ایران). فقط روی endpointهای **عمومی و بدون احراز هویت** اعمال می‌شود.
### نصب (انجام‌شده)
- بک‌اند: `composer require altcha-org/altcha` (پکیج رسمی PHP، از V1 API استفاده می‌شود).
- فرانت‌اند: `yarn add altcha` (web-component محلی، بدون CDN؛ Encore آن را bundle می‌کند).
### تنظیمات (`.env` / برای prod در `.env.local`)
| متغیر | پیش‌فرض | توضیح |
|---|---|---|
| `ALTCHA_ENABLED` | `false` | در prod روی `true`؛ در dev/test غیرفعال بماند |
| `ALTCHA_HMAC_KEY` | `change-me-in-env-local` | کلید امضای سرور — **حتماً در prod عوض شود** و مخفی بماند |
| `ALTCHA_MAX_NUMBER` | `100000` | سقف اعداد PoW = **سختی**؛ بالاتر = سنگین‌تر برای مرورگر |
| `ALTCHA_EXPIRE_SECONDS` | `300` | عمر هر challenge (ثانیه) |
### جریان
1. کلاینت `GET /api/v1/altcha/challenge` را می‌گیرد (challenge امضاشده).
2. `<altcha-widget>` در پس‌زمینه PoW را حل می‌کند.
3. مقدار حل‌شده (base64) با کلید `altcha` در بدنه‌ی درخواستِ endpoint عمومی ارسال می‌شود.
4. سرور با `CaptchaGuard::assertValid($request)` اعتبارسنجی می‌کند (امضا + انقضا + یک‌بارمصرف بودن).
### endpointهای محافظت‌شده
`send-code`، `register`، `otp-login`، `reset-password`، `pre-registration`.
> `rate`/`comment` پشت JWT هستند و کپچا نمی‌گیرند (بی‌فایده است).
### افزودن کپچا به endpoint عمومی جدید
`CaptchaGuard` را inject کن و اولین خط handler:
```php
$this->captcha->assertValid($request); // پرتاب ERR_CAPTCHA_001 (422) در صورت شکست
```
سپس مسیر را در فرانت به بدنه‌ی درخواست `altcha` وصل کن.
### تغییر سختی
`ALTCHA_MAX_NUMBER` را بالا/پایین ببر (مثلاً `1000000` برای سخت‌تر).
### غیرفعال‌سازی
`ALTCHA_ENABLED=false` → guard کاملاً no-op می‌شود (dev/test همیشه این‌طور است).
### Troubleshooting
- **همیشه ERR_CAPTCHA_001:** کلید `ALTCHA_HMAC_KEY` بین challenge و verify باید یکسان باشد؛ اگر بعد از صدور challenge کلید عوض شود، امضا نامعتبر می‌شود.
- **challenge منقضی:** ساعت سرور را چک کن؛ `ALTCHA_EXPIRE_SECONDS` خیلی کوتاه نباشد.
- **replay:** هر challenge یک‌بار مصرف است؛ برای هر submit یک challenge تازه بگیر.
- **خطای Redis:** pool اختصاصی `altcha.pool` روی `REDIS_URL` است؛ در دسترس بودن Redis لازم است.