Spaces:
Sleeping
Sleeping
fix: allowlist role authoritative on every authed request
Browse files
auth/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (153 Bytes). View file
|
|
|
auth/__pycache__/crypto.cpython-314.pyc
ADDED
|
Binary file (2.31 kB). View file
|
|
|
auth/__pycache__/dependencies.cpython-314.pyc
ADDED
|
Binary file (2 kB). View file
|
|
|
auth/__pycache__/roles.cpython-314.pyc
ADDED
|
Binary file (2.77 kB). View file
|
|
|
auth/__pycache__/utils.cpython-314.pyc
ADDED
|
Binary file (3.64 kB). View file
|
|
|
auth/dependencies.py
CHANGED
|
@@ -4,6 +4,7 @@ FastAPI dependency for authenticated routes.
|
|
| 4 |
from fastapi import Depends, Header, HTTPException, status
|
| 5 |
from sqlalchemy.orm import Session
|
| 6 |
|
|
|
|
| 7 |
from auth.utils import decode_access_token
|
| 8 |
from db.database import get_db
|
| 9 |
from db.models import User
|
|
@@ -23,4 +24,12 @@ def get_current_user(
|
|
| 23 |
user = db.query(User).filter(User.id == user_id).first()
|
| 24 |
if not user:
|
| 25 |
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
return user
|
|
|
|
| 4 |
from fastapi import Depends, Header, HTTPException, status
|
| 5 |
from sqlalchemy.orm import Session
|
| 6 |
|
| 7 |
+
from auth.roles import ensure_role
|
| 8 |
from auth.utils import decode_access_token
|
| 9 |
from db.database import get_db
|
| 10 |
from db.models import User
|
|
|
|
| 24 |
user = db.query(User).filter(User.id == user_id).first()
|
| 25 |
if not user:
|
| 26 |
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
|
| 27 |
+
# The email allowlist is authoritative on EVERY authenticated request — not
|
| 28 |
+
# just login — so downstream gates (e.g. premium generation) never see a
|
| 29 |
+
# stale role='user' for an allowlisted SUPER_ADMIN. Idempotent after the
|
| 30 |
+
# first upgrade. Non-fatal if the write fails (still returns the user).
|
| 31 |
+
try:
|
| 32 |
+
ensure_role(user, db)
|
| 33 |
+
except Exception:
|
| 34 |
+
db.rollback()
|
| 35 |
return user
|
auth/roles.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Role resolution — the SUPER_ADMIN allowlist is the single source of truth.
|
| 3 |
+
|
| 4 |
+
Lives in the `auth` layer (not `api`) so BOTH the auth endpoints and the
|
| 5 |
+
`get_current_user` dependency can apply it. Previously the allowlist upgrade
|
| 6 |
+
only ran during login/`/me`, so a request authenticated with a token whose DB
|
| 7 |
+
row still said `role='user'` (e.g. a SUPER_ADMIN email that logged in before the
|
| 8 |
+
upgrade persisted) was treated as a normal user by downstream gates like the
|
| 9 |
+
premium generation flow. Applying it in `get_current_user` makes every
|
| 10 |
+
authenticated request authoritative.
|
| 11 |
+
"""
|
| 12 |
+
import os
|
| 13 |
+
|
| 14 |
+
from sqlalchemy.orm import Session
|
| 15 |
+
|
| 16 |
+
from db.models import User
|
| 17 |
+
|
| 18 |
+
# ponytail: small allowlist drives the SUPER_ADMIN role (env-overridable, comma-separated).
|
| 19 |
+
SUPER_ADMIN_EMAILS: set[str] = {
|
| 20 |
+
e.strip().lower()
|
| 21 |
+
for e in os.environ.get("SUPER_ADMIN_EMAILS", "proff.pratiksingh@gmail.com").split(",")
|
| 22 |
+
if e.strip()
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def resolve_role(email: str) -> str:
|
| 27 |
+
"""Role is fully determined by the email allowlist (case-insensitive)."""
|
| 28 |
+
return "SUPER_ADMIN" if (email or "").lower() in SUPER_ADMIN_EMAILS else "user"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def ensure_role(user: User, db: Session) -> str:
|
| 32 |
+
"""Set the user's role from the allowlist, persisting an upgrade to
|
| 33 |
+
SUPER_ADMIN once. Idempotent: after the first upgrade it only reads."""
|
| 34 |
+
role = resolve_role(user.email)
|
| 35 |
+
if getattr(user, "role", None) != role and role == "SUPER_ADMIN":
|
| 36 |
+
user.role = role
|
| 37 |
+
db.commit()
|
| 38 |
+
return role
|