from datetime import datetime, timedelta, timezone from typing import Optional from jose import JWTError, jwt import bcrypt from fastapi import Depends, HTTPException, Query, Request, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.orm import Session from app.config import get_settings from app.database import get_db from app.models.admin_user import AdminUser ALGORITHM = "HS256" bearer_scheme = HTTPBearer(auto_error=False) def hash_password(password: str) -> str: pw = password.encode("utf-8")[:72] return bcrypt.hashpw(pw, bcrypt.gensalt()).decode("utf-8") def verify_password(plain: str, hashed: str) -> bool: pw = plain.encode("utf-8")[:72] return bcrypt.checkpw(pw, hashed.encode("utf-8")) def create_access_token(email: str, expires_hours: Optional[int] = None) -> str: settings = get_settings() hours = expires_hours or settings.jwt_expire_hours expire = datetime.now(timezone.utc) + timedelta(hours=hours) data = {"sub": email, "exp": expire} return jwt.encode(data, settings.jwt_secret, algorithm=ALGORITHM) def decode_token(token: str) -> Optional[str]: settings = get_settings() try: payload = jwt.decode(token, settings.jwt_secret, algorithms=[ALGORITHM]) return payload.get("sub") except JWTError: return None def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme), db: Session = Depends(get_db), ) -> AdminUser: token = credentials.credentials if credentials else None if not token: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated") email = decode_token(token) if not email: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") user = db.query(AdminUser).filter(AdminUser.email == email).first() if not user: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found") return user def get_user_from_token_param( token: Optional[str] = Query(default=None), credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme), db: Session = Depends(get_db), ) -> AdminUser: """Accept JWT from ?token= query param (for iframe src) OR Authorization header.""" raw = token or (credentials.credentials if credentials else None) if not raw: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated") email = decode_token(raw) if not email: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") user = db.query(AdminUser).filter(AdminUser.email == email).first() if not user: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found") return user