mirror of
https://github.com/TencentCloud/Octop.git
synced 2026-10-20 06:19:17 +08:00
* feat(auth): add optional login captcha (slider, turnstile/hcaptcha/recaptcha, tencent)
Password login can now be guarded by a captcha. Default slider stays
client-only; strong providers are verified server-side with the vendor
(Cloudflare Turnstile, hCaptcha, reCAPTCHA v2/v3, Tencent Cloud Captcha).
- infra/auth/captcha: env snapshot + settings blob resolution, provider
registry with per-vendor verify calls, siteverify with 10s timeout
- GET /api/auth/captcha (public widget config); POST /api/auth/login
accepts captcha_token; tencent token carries ticket:randstr and is
checked via GET with the client IP
- admin GET/PUT /api/settings/captcha behind the new captcha permission;
OCTOP_CAPTCHA_SECRET redacted in envs API; boot validation for strong
env providers
- dashboard: CaptchaField with slider/checkbox/invisible/popup modes,
login page wiring, advanced-settings captcha panel
- docs + i18n (en/zh) + unit/integration/frontend tests
* fix(captcha): verify Tencent tickets via DescribeCaptchaResult (TC3)
The legacy ssl.captcha.qq.com/ticket/verify endpoint rejects current
tickets with response=15 "decrypt fail" even for correct
CaptchaAppId/AppSecretKey pairs. Switch to the official ticket-check API:
- POST captcha.tencentcloudapi.com DescribeCaptchaResult (2019-07-22),
TC3-HMAC-SHA256 signed with CAM API keys; AppSecretKey stays in the body
- captcha settings gain cam_secret_id/cam_secret (encrypted at rest,
env fallback OCTOP_CAPTCHA_CAM_SECRET_ID/KEY); settings view exposes
cam_secret_id + has_cam_secret; dashboard shows the two fields for the
tencent provider only
- interpret maps CaptchaCode (1 OK; 7/8/9/15/16/21/100 fail) and
EvilLevel=100 (malicious) instead of the legacy response=="1"
- TC3 signer moved voice/tencent_sign.py -> utils/tencent_sign.py (shared)
- drop the redundant Host header from signed requests: httpx sets Host
from the URL (same value the signature covers), and an explicit Host
let host-routing system proxies intercept localhost mock calls
- secrets no longer appear in httpx URL logs (payload is in the body)
- move the Login Captcha settings tab next to HTTPS
* polish(dashboard): show reCAPTCHA v3 min score only when that provider is active
* fix(dashboard): captcha widgets follow the site locale, not the browser
- tencent popup: userLanguage from i18n.language (was navigator.language)
- turnstile: pass language render option
- hcaptcha/recaptcha/recaptcha-v3: hl script param from UI locale
- slider already renders site-provided labels
* feat(captcha): unlist reCAPTCHA v2 from the settings catalog
No verified deployment key for v2; keep the provider registered so
existing configs still verify, but stop offering it in the dropdown
(list_providers now filters on a per-provider listed flag).
* feat(cli): octop captcha reset — offline lockout escape hatch
Clears the stored captcha settings blob so login falls back to the
built-in slider when a misconfigured provider blocks all logins
(settings UI requires login, so it cannot fix itself). Boot-env
captcha is untouched; a running server needs a restart.
CHANGELOG: cover reset command, TC3 switch, widget language, v2 unlist.
* feat(cli): register octop captcha reset command and add changelog
Without the registry entry the command module from c5a7649d was
unreachable; CHANGELOG covers the login captcha feature, the TC3
ticket-check switch, widget locale, v2 unlisting, and captcha reset.
* fix(captcha): close pre-merge gaps + document the provider architecture
- envs API: redact OCTOP_CAPTCHA_CAM_SECRET_KEY like OCTOP_CAPTCHA_SECRET
(was exposed in plaintext by GET /api/envs)
- validate_boot: env-only tencent provider now requires the CAM keys,
failing fast at boot instead of at first login
- docs/configuration.md: document the two CAM env vars; note recaptcha
v2 is unlisted but resolvable
- providers.py: architecture docstring (four layers + settled design
decisions + adding-a-vendor recipe); rename _SuccessProvider to
_FormPostProvider; drop the register() cast, which exposed a real
structural bug: frozen-dataclass providers never satisfied the
Protocol's mutable metadata members — declare them read-only
properties so the structural check is real
- verify.py: docstring naming it the execution layer
51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
"""Tests for `octop captcha reset` (lockout escape hatch)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from octop.cli.main import cli
|
|
from octop.cli.support.db import open_cli_services
|
|
from octop.infra.auth.captcha.config import snapshot_env
|
|
from octop.infra.auth.captcha.store import SETTINGS_KEY, load_effective
|
|
|
|
|
|
@pytest.fixture
|
|
def octop_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
monkeypatch.setenv("OCTOP_HOME", str(tmp_path))
|
|
return tmp_path
|
|
|
|
|
|
def _seed_blob(active: str = "recaptcha-v3") -> None:
|
|
with open_cli_services() as services:
|
|
services.settings_repo.set(
|
|
SETTINGS_KEY,
|
|
json.dumps(
|
|
{
|
|
"active": active,
|
|
"providers": {active: {"site_key": "k", "secret_enc": ""}},
|
|
}
|
|
),
|
|
)
|
|
|
|
|
|
def test_reset_clears_stored_settings_and_falls_back_to_slider(octop_home: Path) -> None:
|
|
_seed_blob()
|
|
r = CliRunner().invoke(cli, ["captcha", "reset"])
|
|
assert r.exit_code == 0, r.output
|
|
with open_cli_services() as services:
|
|
assert services.settings_repo.get(SETTINGS_KEY) is None
|
|
effective = load_effective(services.settings_repo, services.secret_repo, snapshot_env({}))
|
|
assert effective.slug == "slider"
|
|
assert "slider" in r.output
|
|
|
|
|
|
def test_reset_is_idempotent(octop_home: Path) -> None:
|
|
r = CliRunner().invoke(cli, ["captcha", "reset"])
|
|
assert r.exit_code == 0, r.output
|
|
assert "already at default" in r.output
|