M1+M2+M10 (CC2 R3): JWT auth + admin users + GDPR backend

- auth/auth_v2.py: JWT login/refresh/logout/me + bcrypt + tenant_id/role/tier claims
- auth/admin_users.py: /api/admin/users CRUD + invite/role/suspend + bulk CSV
- auth/gdpr.py: cookie consent + Art.20 export + Art.17 erasure + admin queue
- auth/seed_demo.py: 3 demo tenants + 4 users (damir@pgz.hr / PGZ2026!)
- Removed legacy /api/auth/login + /api/auth/me from pgz_sport_api.py
- Wired auth/admin/gdpr routers into FastAPI

5/5 live curl tests pass: damir@pgz.hr login → JWT with tenant_id=1, role=pgz_admin, tier=0
This commit is contained in:
Damir Radulić
2026-05-05 00:09:09 +02:00
parent c12a8e9698
commit 492c8fdd87
23 changed files with 21518 additions and 49 deletions
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
# seed_demo.py — Demo tenants & users for Round 3 (M1+M2+M10)
# v1.0 dradulic@outlook.com / damir@rinet.one — 2026-05-04
"""
Seeds:
- 3 tenants: PGŽ (existing), Atletski savez PGŽ, AK Kvarner Rijeka
- Demo users:
damir@pgz.hr / PGZ2026! (pgz_admin) ← KEY DEMO
pero@atletika.pgz.hr/ PGZ2026! (savez_admin)
ana@akkvarner.hr / PGZ2026! (klub_admin)
sportas@akkvarner.hr/ PGZ2026! (klub_clan)
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from auth.auth_v2 import db_query, db_one, db_exec, hash_password
def get_or_create_tenant(slug, display_name, ttype, oib=None):
row = db_one("SELECT id FROM pgz_sport.tenants WHERE slug=%s", (slug,))
if row: return row["id"]
return db_one("""INSERT INTO pgz_sport.tenants (slug, display_name, type, oib, status)
VALUES (%s,%s,%s,%s,'active') RETURNING id""",
(slug, display_name, ttype, oib))["id"]
def get_or_create_savez(naziv, sport, oib=None):
row = db_one("SELECT id FROM pgz_sport.savezi WHERE naziv=%s LIMIT 1", (naziv,))
if row: return row["id"]
return db_one("""INSERT INTO pgz_sport.savezi (naziv, sport, oib, aktivan)
VALUES (%s,%s,%s,true) RETURNING id""", (naziv, sport, oib))["id"]
def get_or_create_klub(naziv, sport, grad, savez_id, oib=None, tenant_id=None):
row = db_one("SELECT id FROM pgz_sport.klubovi WHERE naziv=%s LIMIT 1", (naziv,))
if row: return row["id"]
return db_one("""INSERT INTO pgz_sport.klubovi
(naziv, sport, grad, savez_id, oib, tenant_id, aktivan)
VALUES (%s,%s,%s,%s,%s,%s,true) RETURNING id""",
(naziv, sport, grad, savez_id, oib, tenant_id))["id"]
def upsert_user(email, password, full_name, ime, prezime, user_type,
klub_id=None, savez_id=None):
pw_hash = hash_password(password)
row = db_one("SELECT id FROM pgz_sport.users WHERE LOWER(email)=%s",
(email.lower(),))
if row:
db_exec("""UPDATE pgz_sport.users SET
password_hash=%s, full_name=%s, ime=%s, prezime=%s,
user_type=%s, klub_id=%s, savez_id=%s,
aktivan=true, status='active', must_change_pwd=false,
failed_login_count=0, locked_until=NULL,
updated_at=now() WHERE id=%s""",
(pw_hash, full_name, ime, prezime, user_type,
klub_id, savez_id, row["id"]))
return row["id"], "updated"
new_id = db_one("""INSERT INTO pgz_sport.users
(email, password_hash, full_name, ime, prezime, user_type, klub_id, savez_id,
aktivan, status, must_change_pwd, auth_provider, email_verified)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,true,'active',false,'local',true)
RETURNING id""",
(email.lower(), pw_hash, full_name, ime, prezime,
user_type, klub_id, savez_id))["id"]
return new_id, "created"
def main():
print("== Tenants ==")
pgz_id = get_or_create_tenant("pgz", "Primorsko-goranska županija", "county")
atletski_id = get_or_create_tenant("atletski_savez_pgz", "Atletski savez PGŽ", "federation")
ak_kvarner_t = get_or_create_tenant("ak_kvarner_rijeka", "AK Kvarner Rijeka", "club")
print(f" pgz tenant: {pgz_id}")
print(f" atletski_savez_pgz tenant: {atletski_id}")
print(f" ak_kvarner_rijeka tenant: {ak_kvarner_t}")
print("== Savezi ==")
atletski_savez = get_or_create_savez("Atletski savez Primorsko-goranske županije", "Atletika")
print(f" atletski_savez id: {atletski_savez}")
print("== Klub ==")
ak_klub = get_or_create_klub("Atletski klub Kvarner Rijeka", "Atletika",
"Rijeka", atletski_savez, tenant_id=ak_kvarner_t)
print(f" AK Kvarner: {ak_klub}")
print("== Users ==")
users = [
("damir@pgz.hr", "PGZ2026!", "Damir Radulić", "Damir", "Radulić", "pgz_admin", None, None),
("pero@atletika.pgz.hr", "PGZ2026!", "Pero Perić", "Pero", "Perić", "savez_admin", None, atletski_savez),
("ana@akkvarner.hr", "PGZ2026!", "Ana Anić", "Ana", "Anić", "klub_admin", ak_klub, atletski_savez),
("sportas@akkvarner.hr", "PGZ2026!", "Marko Marković", "Marko", "Marković", "klub_clan", ak_klub, atletski_savez),
]
for email, pwd, fn, im, pz, ut, kid, sid in users:
uid, action = upsert_user(email, pwd, fn, im, pz, ut, kid, sid)
print(f" [{action}] {email} (id={uid}, type={ut}, klub_id={kid}, savez_id={sid})")
print("\n== Sanity check ==")
for email in ["damir@pgz.hr","pero@atletika.pgz.hr","ana@akkvarner.hr","sportas@akkvarner.hr"]:
u = db_one("SELECT id, email, user_type, klub_id, savez_id, aktivan FROM pgz_sport.users WHERE LOWER(email)=%s", (email,))
print(f" {email}: {u}")
if __name__ == "__main__":
main()