fooder-api/fooder/auth.py

150 lines
4.1 KiB
Python
Raw Normal View History

2023-04-01 16:19:12 +02:00
from passlib.context import CryptContext
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.asyncio import async_sessionmaker
from jose import JWTError, jwt
from fastapi.security import OAuth2PasswordBearer
from fastapi import Depends, HTTPException
2023-04-01 16:19:12 +02:00
from fastapi_users.password import PasswordHelper
2024-05-21 14:40:31 +02:00
from typing import Annotated
2023-04-01 16:19:12 +02:00
from datetime import datetime, timedelta
from .settings import Settings
from .domain.user import User
2023-04-02 14:38:22 +02:00
from .domain.token import RefreshToken
2023-04-01 16:19:12 +02:00
from .db import get_session
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
2023-04-01 20:23:04 +02:00
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/token")
2023-04-01 16:19:12 +02:00
settings = Settings()
2024-05-21 14:40:31 +02:00
password_helper = PasswordHelper(pwd_context) # type: ignore
2023-04-01 16:19:12 +02:00
AsyncSessionDependency = Annotated[async_sessionmaker, Depends(get_session)]
TokenDependency = Annotated[str, Depends(oauth2_scheme)]
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
async def authenticate_user(
session: AsyncSession, username: str, password: str
2024-05-21 14:40:31 +02:00
) -> User | None:
2023-04-01 16:19:12 +02:00
user = await User.get_by_username(session, username)
2024-05-21 14:40:31 +02:00
if user is None:
2023-04-01 16:19:12 +02:00
return None
2024-05-21 14:40:31 +02:00
assert user is not None
2023-04-01 16:19:12 +02:00
if not verify_password(password, user.hashed_password):
return None
2024-05-21 14:40:31 +02:00
2023-04-01 16:19:12 +02:00
return user
2023-04-02 14:38:22 +02:00
async def verify_refresh_token(
session: AsyncSession, token: str
2024-05-21 14:40:31 +02:00
) -> RefreshToken | None:
2023-04-02 14:38:22 +02:00
try:
payload = jwt.decode(
2023-04-02 14:51:08 +02:00
token, settings.REFRESH_SECRET_KEY, algorithms=[settings.ALGORITHM]
2023-04-02 14:38:22 +02:00
)
2024-05-21 14:40:31 +02:00
sub = payload.get("sub")
if sub is None:
return None
if not isinstance(sub, str):
return None
username: str = str(sub)
2023-04-02 14:38:22 +02:00
if username is None:
2024-05-21 14:40:31 +02:00
return None
2023-04-02 14:38:22 +02:00
except JWTError:
2024-05-21 14:40:31 +02:00
return None
2023-04-02 14:38:22 +02:00
user = await User.get_by_username(session, username)
2024-05-21 14:40:31 +02:00
2023-04-02 15:20:53 +02:00
if user is None:
2024-05-21 14:40:31 +02:00
return None
assert user is not None
2023-04-02 15:20:53 +02:00
current_token = await RefreshToken.get_token(session, user.id, token)
2024-05-21 14:40:31 +02:00
2023-04-02 15:20:53 +02:00
if current_token is not None:
return current_token
2023-04-02 14:38:22 +02:00
2024-05-21 14:40:31 +02:00
return None
2023-04-02 14:38:22 +02:00
def create_access_token(user: User) -> str:
2023-04-01 16:19:12 +02:00
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode = {
"sub": user.username,
"exp": expire,
}
encoded_jwt = jwt.encode(
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
)
return encoded_jwt
2023-04-02 14:38:22 +02:00
async def create_refresh_token(session: AsyncSession, user: User) -> RefreshToken:
expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
to_encode = {
"sub": user.username,
"exp": expire,
}
encoded_jwt = jwt.encode(
to_encode, settings.REFRESH_SECRET_KEY, algorithm=settings.ALGORITHM
)
return await RefreshToken.create(session, token=encoded_jwt, user_id=user.id)
2023-04-01 16:19:12 +02:00
async def get_current_user(
session: AsyncSessionDependency, token: TokenDependency
) -> User:
async with session() as session:
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
)
2024-05-21 14:40:31 +02:00
sub = payload.get("sub")
if sub is None:
raise HTTPException(status_code=401, detail="Unathorized")
if not isinstance(sub, str):
raise HTTPException(status_code=401, detail="Unathorized")
username: str = str(sub)
2023-04-01 16:19:12 +02:00
if username is None:
raise HTTPException(status_code=401, detail="Unathorized")
2024-05-21 14:40:31 +02:00
2023-04-01 16:19:12 +02:00
except JWTError:
raise HTTPException(status_code=401, detail="Unathorized")
2024-05-21 14:40:31 +02:00
user = await User.get_by_username(session, username)
if user is None:
raise HTTPException(status_code=401, detail="Unathorized")
assert user is not None
return user
async def authorize_api_key(
session: AsyncSessionDependency, token: TokenDependency
) -> None:
if token == settings.API_KEY:
return None
raise HTTPException(status_code=401, detail="Unathorized")