Damir Radulić
0046b8d695
CC2 R5: defense-in-depth JWT + invite/reset token flows + audit
...
#1 JWT middleware:
- pgz_sport_api.py: starlette middleware require_jwt_on_admin runs before
every /api/admin/* route. Even routes that lack Depends(require_user)
cannot be reached without a valid Bearer token (verifies signature,
exp, typ='access', revocation via user_sessions). OPTIONS passes for CORS.
#2 Invitation flow:
- pgz_sport.user_action_tokens table (token_hash, user_id, kind, expires_at,
used_at, created_by, ip, meta). Single-use, raw token never persisted.
- POST /api/admin/users/{id}/invite — issues 'invite' token (TTL 7d),
marks must_change_pwd, revokes existing sessions, returns invite_link.
- GET /api/auth/setup-password?token=X — preflight (no consume).
- POST /api/auth/setup-password — consumes token, sets password, sets
email_verified=true.
#3 Password reset flow:
- POST /api/auth/forgot-password — generic 'ako račun postoji' response;
issues 'reset' token (TTL 2h) only for active users. Token returned in
response only on localhost or if PGZ_REVEAL_RESET_TOKEN=1.
- GET /api/auth/reset-password?token=X — preflight.
- POST /api/auth/reset-password — consumes token, sets new password,
revokes all active sessions.
#4 Audit coverage (auth events):
- login.ok, login.fail (with reason), login.locked, login.2fa_required,
login.2fa_fail, logout, auth.refresh, password.change, password.reset.ok,
password.reset.fail, password.forgot.issue, password.forgot.miss,
invite.consume.ok, invite.consume.fail, user.invite, user.create,
user.update, user.delete, user.role.change, user.suspend, user.unsuspend,
user.password.reset, 2fa.verify.ok, 2fa.verify.fail, 2fa.disable.
#5 Live tests: 41/41 across 6 demo users (incl. fresh invited+deleted user).
Phase 2 verifies 14 endpoints reject no-auth and accept valid Bearer.
2026-05-05 01:28:29 +02:00
Damir Radulić
bd3773434e
CC2 R4 #6 : real TOTP 2FA (setup + verify + disable + login flow)
...
- auth/auth_v2.py:
- pyotp-based TOTP (RFC 6238, base32 secret, ±30s window)
- new pgz_sport.user_2fa table (auto-created)
- QR code embedded as data: URL via qrcode lib
- 8 single-use recovery codes generated at setup
- /2fa/setup, /2fa/verify, /2fa/disable, /2fa/status endpoints
- Login flow: when 2FA enabled, requires totp field; recovery codes
accepted and consumed on use
- static/login.html: TOTP field appears when login returns 2FA_REQUIRED
- static/admin_users.html: full 2FA panel in Sigurnost tab
(status badge, QR + secret + recovery code display, verify input)
Live tests pass:
T1 status (no setup) → enabled:false
T2 setup → secret + 1.5KB QR PNG + 8 recovery codes
T3 verify wrong code → 401
T4 verify real TOTP → enabled:true
T5 login w/o TOTP after enable → 401 detail=2FA_REQUIRED
T6 login w/ TOTP → 200
2026-05-05 00:50:28 +02:00
Damir Radulić
cb3faee731
CC3 R3 M4+: avatar upload, PUT /api/auth/me, /uploads mount
...
Backend (auth/auth_v2.py + pgz_sport_api.py):
- POST /api/auth/me/avatar (multipart, jpeg/png/webp ≤5 MB) -> /uploads/avatars/{userid}_{ts}.ext
- DELETE /api/auth/me/avatar (uklanja datoteku + briše users.avatar_url)
- PUT /api/auth/me (UpdateMeReq: ime/prezime/full_name/telefon/phone/preferred_language/oib)
- GET /api/auth/me proširen s avatar_url, two_factor_enabled, gdpr_consent_at, google_picture
- StaticFiles mount /uploads -> /opt/pgz-sport/uploads
- DB: ALTER TABLE pgz_sport.users ADD COLUMN avatar_url TEXT
- Audit: profile.update, profile.avatar_upload, profile.avatar_delete
Backups: _backups/auth_v2.py.cc3_pre_avatar.*, pgz_sport_api.py.cc3_pre_avatar.*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-05 00:44:14 +02:00
Damir Radulić
8fe2478b84
CC2 R3 frontend: login.html + admin_users.html (M1+M2+M10 UI)
...
- static/login.html: dark Palantir-style login with PGŽ branding,
Prijava se / Zaboravljena lozinka, demo account quick-fills,
GDPR cookie banner, autostore tokens (local/session)
- static/admin_users.html: full user-management admin panel:
- Collapsible left sidebar (Pregled, Korisnici, Tenanti, Audit log,
Sigurnost, GDPR, links to ERP/CRM)
- Users table with filters (q, tenant, role, status, limit)
- + Dodaj korisnika modal (CRUD via /api/admin/users/*)
- Suspend / unsuspend / reset-password / delete actions
- Audit log viewer + Security KPIs + GDPR queue
- Self-service: change pwd, export data (Art. 20), erasure request (Art. 17)
- pgz_sport_api.py: /login and /admin/users URL routes
- auth/seed_demo.py: added tajnik@atletski.pgz.hr/Atl2026!,
admin@ak-kvarner.hr/Kvarner2026! demo users
5/5 live tests pass: login JWT, /me, /admin/users, /gdpr/consent, /gdpr/export
Note: existing admin.html (CC4 ERP/OCR work) preserved intact;
admin_users.html is dedicated user-mgmt page linked from sidebar.
2026-05-05 00:20:03 +02:00