import jwt from datetime import datetime, timedelta, timezone from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.orm import Session from database import get_db from config import settings from models.user import User bearer = HTTPBearer() # In-memory token blacklist (cleared on restart — acceptable for local use) _blacklisted_tokens: set[str] = set() TOKEN_EXPIRY_HOURS = 8 MASTER_USER_ID = 0 # sentinel — never a real DB row def _make_ghost_superadmin(): """Synthetic in-memory superadmin — plain namespace, never touches SQLAlchemy.""" from types import SimpleNamespace return SimpleNamespace( id=MASTER_USER_ID, username=settings.MASTER_USERNAME, full_name="Xenia Support", role="superadmin", is_active=True, pin_hash=None, password_hash=None, email=None, nickname=None, mobile_phone=None, note=None, avatar_url=None, waiter_settings=None, hourly_rate=None, created_at=datetime(2024, 1, 1, tzinfo=timezone.utc), zone_assignments=[], perm_access_dashboard=True, perm_access_waiter_app=True, perm_access_kds=True, perm_cancel_orders=True, perm_apply_discounts=True, perm_modify_prices=True, perm_open_orders=True, perm_close_orders=True, perm_view_reports=True, perm_manage_staff=True, perm_manage_tables=True, perm_manage_menu=True, perm_manage_settings=True, ) def make_token(user: User) -> str: payload = { "sub": str(user.id), "username": user.username, "role": user.role, "exp": datetime.now(timezone.utc) + timedelta(hours=TOKEN_EXPIRY_HOURS), } return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256") def decode_token(token: str) -> dict: if token in _blacklisted_tokens: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token revoked") try: return jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) except jwt.ExpiredSignatureError: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token expired") except jwt.InvalidTokenError: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") def blacklist_token(token: str): _blacklisted_tokens.add(token) def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(bearer), db: Session = Depends(get_db), ) -> User: payload = decode_token(credentials.credentials) if int(payload["sub"]) == MASTER_USER_ID and payload.get("role") == "superadmin": return _make_ghost_superadmin() user = db.query(User).filter(User.id == int(payload["sub"]), User.is_active == True).first() if not user: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found") return user def _has_perm(user: User, perm: str) -> bool: """Superadmins always have every permission.""" if user.role == "superadmin": return True return bool(getattr(user, perm, False)) def require_manager(user: User = Depends(get_current_user)) -> User: """Allow anyone with dashboard access (formerly role-based manager check).""" if not _has_perm(user, "perm_access_dashboard"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Dashboard access required") return user def require_staff_manager(user: User = Depends(get_current_user)) -> User: if not _has_perm(user, "perm_manage_staff"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Staff management permission required") return user def require_reports(user: User = Depends(get_current_user)) -> User: if not _has_perm(user, "perm_view_reports"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Reports permission required") return user def require_menu_manager(user: User = Depends(get_current_user)) -> User: if not _has_perm(user, "perm_manage_menu"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Menu management permission required") return user def require_settings_manager(user: User = Depends(get_current_user)) -> User: if not _has_perm(user, "perm_manage_settings"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Settings management permission required") return user def require_kds(user: User = Depends(get_current_user)) -> User: if not _has_perm(user, "perm_access_kds"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Δεν έχετε πρόσβαση στο KDS") return user def require_tables_manager(user: User = Depends(get_current_user)) -> User: if not _has_perm(user, "perm_manage_tables"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Tables management permission required") return user def require_superadmin(user: User = Depends(get_current_user)) -> User: if user.role != "superadmin": raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Superadmin access required") return user # Kept for any callers that still use the old name require_sysadmin = require_superadmin