Files
Budget_Tool_Martinhal/budget-app/backend/app/security.py
T

55 lines
1.8 KiB
Python

from datetime import datetime, timedelta, timezone
import bcrypt
import jwt
from fastapi import Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from .config import settings
from .db import get_db
from .models import User
COOKIE = "budget_session"
# Ordered by what each role may change. "management" (Upper Management) sits below editor: it only
# views, downloads and emails budgets and reports, in every department.
ROLES = ("viewer", "management", "editor", "approver", "admin")
def hash_password(password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
def verify_password(password: str, hashed: str) -> bool:
try:
return bcrypt.checkpw(password.encode(), hashed.encode())
except ValueError:
return False
def make_token(user: User) -> str:
exp = datetime.now(timezone.utc) + timedelta(hours=settings.session_hours)
return jwt.encode({"sub": str(user.id), "exp": exp}, settings.secret_key, algorithm="HS256")
def current_user(request: Request, db: Session = Depends(get_db)) -> User:
token = request.cookies.get(COOKIE)
if not token:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "not_authenticated")
try:
payload = jwt.decode(token, settings.secret_key, algorithms=["HS256"])
except jwt.PyJWTError:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "not_authenticated")
user = db.get(User, int(payload["sub"]))
if not user or not user.active:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "not_authenticated")
return user
def require(min_role: str):
def dep(user: User = Depends(current_user)) -> User:
if ROLES.index(user.role) < ROLES.index(min_role):
raise HTTPException(status.HTTP_403_FORBIDDEN, "forbidden")
return user
return dep