● Zion Boggan
repos/Sightline/backend/app/api/deps.py
zionboggan.com ↗
64 lines · python
History for this file →
1
from __future__ import annotations
2
 
3
from collections.abc import Generator
4
 
5
from fastapi import Depends, Header, HTTPException, status
6
from fastapi.security import OAuth2PasswordBearer
7
from jose import JWTError
8
from sqlalchemy.orm import Session
9
 
10
from datetime import datetime, timezone
11
 
12
from sqlalchemy import select
13
 
14
from app.core.permissions import Perm, role_has
15
from app.core.security import decode_token, hash_api_key
16
from app.db.session import get_db
17
from app.models import ApiKey, User
18
 
19
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login", auto_error=False)
20
 
21
 
22
def get_current_user(
23
    token: str | None = Depends(oauth2_scheme),
24
    x_api_key: str | None = Header(default=None),
25
    db: Session = Depends(get_db),
26
) -> User:
27
    if x_api_key:
28
        key = db.scalar(select(ApiKey).where(ApiKey.key_hash == hash_api_key(x_api_key)))
29
        if key is None or key.revoked_at is not None:
30
            raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid or revoked api key")
31
        key.last_used_at = datetime.now(timezone.utc)
32
        db.commit()
33
        user = db.get(User, key.user_id)
34
        if user is None or not user.is_active:
35
            raise HTTPException(status.HTTP_401_UNAUTHORIZED, "api key owner inactive")
36
        return user
37
 
38
    if not token:
39
        from app.core.config import settings
40
        if settings.open_mode:
41
            user = db.scalar(select(User).where(User.role == "admin").order_by(User.created_at)) \
42
                or db.scalar(select(User).order_by(User.created_at))
43
            if user:
44
                return user
45
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, "missing credentials")
46
    try:
47
        payload = decode_token(token)
48
    except JWTError:
49
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid token")
50
    user = db.get(User, payload.get("sub"))
51
    if user is None or not user.is_active:
52
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, "user not found or inactive")
53
    return user
54
 
55
 
56
def require(perm: Perm):
57
    def _dep(user: User = Depends(get_current_user)) -> User:
58
        if not role_has(user.role, perm):
59
            raise HTTPException(status.HTTP_403_FORBIDDEN, f"requires permission: {perm.value}")
60
        return user
61
    return _dep
62
 
63
 
64
__all__ = ["get_db", "get_current_user", "require", "Perm", "Session", "Generator"]