CC2 R4 #2+#5: remove legacy unauth /api/admin/users — close 401 gap

The bare @app.get/post('/api/admin/users') decorators in pgz_sport_api.py
were registered before app.include_router(admin_users_router) and shadowed
the JWT-protected M2 routes, leaking user list to anyone.

Removed all three: GET /api/admin/users, POST /api/admin/users,
POST /api/admin/users/{uid}/toggle. The auth.admin_users router now owns
this prefix exclusively and gates every method with require_user.

Verified: no-auth → 401, invalid token → 401, valid Bearer → 200.
This commit is contained in:
Damir Radulić
2026-05-05 00:44:50 +02:00
parent cb3faee731
commit f5c6570d47
20 changed files with 11746 additions and 110 deletions
+10 -57
View File
@@ -991,63 +991,9 @@ def admin_stats():
return {"users_total": ut, "users_active": ua, "permissions_total": pt,
"audit_today": at, "by_type": by_type}
@app.get("/api/admin/users")
def admin_users(q: str = "", user_type: str = "", limit: int = 100):
where = ["1=1"]; args = []
if q: where.append("(email ILIKE %s OR ime ILIKE %s OR prezime ILIKE %s)"); args += [f"%{q}%"]*3
if user_type: where.append("user_type = %s"); args.append(user_type)
args.append(limit)
with db() as conn:
cur = conn.cursor()
cur.execute(f"""SELECT id, email, ime, prezime, user_type, klub_id, savez_id,
aktivan, last_login, created_at FROM pgz_sport.users
WHERE {' AND '.join(where)} ORDER BY id LIMIT %s""", args)
rows = cur.fetchall()
cols = [d[0] for d in cur.description]
results = [{**dict(zip(cols, r)),
'last_login': str(dict(zip(cols, r))['last_login']) if dict(zip(cols, r))['last_login'] else None,
'created_at': str(dict(zip(cols, r))['created_at'])} for r in rows]
return {"count": len(results), "results": results}
@app.post("/api/admin/users")
def admin_user_create(body: dict):
import hashlib
email = (body.get("email") or "").strip().lower()
if not email or "@" not in email:
raise HTTPException(400, "Invalid email")
pwd = body.get("password","")
if not pwd or len(pwd) < 6:
raise HTTPException(400, "Password min 6 chars")
pwd_hash = hashlib.sha256(pwd.encode()).hexdigest()
with db() as conn:
cur = conn.cursor()
try:
cur.execute("""INSERT INTO pgz_sport.users
(email, password_hash, ime, prezime, user_type, klub_id, savez_id, aktivan)
VALUES (%s,%s,%s,%s,%s,%s,%s,true) RETURNING id""",
(email, pwd_hash, body.get("ime"), body.get("prezime"),
body.get("user_type","klub_user"), body.get("klub_id"), body.get("savez_id")))
new_id = cur.fetchone()[0]
cur.execute("""INSERT INTO pgz_sport.sys_audit (action, target_type, target_id, target_text, payload)
VALUES ('user.create','sys_users',%s,%s,%s::jsonb)""",
(new_id, email, json.dumps({"user_type": body.get("user_type")})))
conn.commit()
return {"id": new_id, "email": email}
except psycopg2.IntegrityError as e:
conn.rollback()
raise HTTPException(400, f"Email već postoji: {email}")
@app.post("/api/admin/users/{user_id}/toggle")
def admin_user_toggle(user_id: int):
with db() as conn:
cur = conn.cursor()
cur.execute("UPDATE pgz_sport.users SET aktivan = NOT aktivan WHERE id=%s RETURNING aktivan", (user_id,))
r = cur.fetchone()
if not r: raise HTTPException(404, "User not found")
cur.execute("""INSERT INTO pgz_sport.sys_audit (action, target_type, target_id, payload)
VALUES ('user.toggle','sys_users',%s,%s::jsonb)""", (user_id, json.dumps({"aktivan": r[0]})))
conn.commit()
return {"id": user_id, "aktivan": r[0]}
# Legacy unauthenticated /api/admin/users CRUD removed (R4 #5).
# All /api/admin/users* endpoints are now served by auth.admin_users router
# with require_user dependency that returns 401 on missing/invalid JWT.
# ──────── V6 AI GRADOVI / KILOMETRAŽA ────────
@@ -1408,6 +1354,13 @@ try:
except Exception as e:
print(f'[CRM/M9] obrasci router fail: {e}')
try:
from clan_panel_router import router as clan_panel_router
app.include_router(clan_panel_router)
print('[CRM/PANEL] clan_panel router loaded (/api/crm/clanovi/{id}/full|avatar)')
except Exception as e:
print(f'[CRM/PANEL] clan_panel router fail: {e}')
# === Round 3 / CC2 — M1 Auth + M2 Admin Users + M10 GDPR ===
try:
from auth.auth_v2 import router as auth_v2_router