fix(auth+users): production-discovered bugs from live deploy on gk2 (ref #120)

usersctl: parents[3] indexing crashed when run from /usr/sbin/usersctl
(production has only 2 parents). Wrapped path resolution in try/except
so the script still works in-tree AND when installed.

main.py: nginx strips /api/v1/auth/ prefix before forwarding to the auth
socket, so the canonical external URL /api/v1/auth/login was being
routed to FastAPI's /login — but _login_router was mounted under /auth,
producing 404. Now mounted under BOTH '' (canonical) and /auth (legacy
doubled-URL compat). Tests still pass under the /auth prefix.

Live verified on https://admin.gk2.secubox.in:
- empty password -> setup_token (200)
- wrong password -> 401 "Aucun mot de passe local"
- unknown user -> 401 "Identifiants incorrects" (same body, no enum)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-05-13 10:14:32 +02:00
parent e3a4efd4e6
commit 10d3272ddd
2 changed files with 17 additions and 2 deletions

View File

@ -333,6 +333,10 @@ async def _set_password(req: _SetPasswordIn, request: _Request):
# Mount v2 router FIRST so its /login overrides the legacy auth_router /login.
# FastAPI uses the first matching route, so _login_router must come before auth_router.
# Mounted under both prefixes: "" for the canonical URL (/api/v1/auth/login after nginx
# strips the /api/v1/auth/ prefix) and "/auth" for the legacy doubled URL
# (/api/v1/auth/auth/login), so existing frontend code keeps working during the cutover.
app.include_router(_login_router, prefix="")
app.include_router(_login_router, prefix="/auth")
app.include_router(auth_router, prefix="/auth")
router = APIRouter()

View File

@ -24,9 +24,20 @@ import sys
from pathlib import Path
# Ensure the engine import resolves whether we're installed (/usr/lib/...) or in-tree.
# In production, /usr/sbin/usersctl has only 2 parents; in-tree, parents[3] is the repo root.
sys.path.insert(0, "/usr/lib/secubox/users")
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "common"))
_here = Path(__file__).resolve()
try:
sys.path.insert(0, str(_here.parents[1]))
except IndexError:
pass
try:
_common = _here.parents[3] / "common"
if _common.exists():
sys.path.insert(0, str(_common))
except IndexError:
pass
# Production: secubox_core installed as a site-package, importable directly.
from api import engine, password_policy, totp, migrate_v1_to_v2 # noqa: E402