first commit
This commit is contained in:
0
app/core/__init__.py
Normal file
0
app/core/__init__.py
Normal file
21
app/core/database.py
Normal file
21
app/core/database.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
from app.config.settings import settings
|
||||
|
||||
engine = create_async_engine(settings.database_url, echo=settings.debug)
|
||||
async_session = async_sessionmaker(engine, expire_on_commit=False)
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
async def get_db() -> AsyncSession:
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
|
||||
|
||||
async def init_db():
|
||||
import app.models # noqa: F401 — ensure all models are registered
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
25
app/core/dependencies.py
Normal file
25
app/core/dependencies.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from fastapi import Depends
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db # noqa: F401
|
||||
from app.core.security import decode_token
|
||||
from app.core.exceptions import UnauthorizedError
|
||||
from app.models import User
|
||||
|
||||
security_scheme = HTTPBearer()
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security_scheme),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> User:
|
||||
payload = decode_token(credentials.credentials)
|
||||
if not payload or payload.get("type") != "access":
|
||||
raise UnauthorizedError("Invalid or expired token")
|
||||
|
||||
user_id = payload.get("sub")
|
||||
user = await db.get(User, user_id)
|
||||
if not user or user.status != "active":
|
||||
raise UnauthorizedError("User not found or disabled")
|
||||
return user
|
||||
21
app/core/exceptions.py
Normal file
21
app/core/exceptions.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
||||
class NotFoundError(HTTPException):
|
||||
def __init__(self, detail: str = "Resource not found"):
|
||||
super().__init__(status_code=404, detail=detail)
|
||||
|
||||
|
||||
class BadRequestError(HTTPException):
|
||||
def __init__(self, detail: str = "Bad request"):
|
||||
super().__init__(status_code=400, detail=detail)
|
||||
|
||||
|
||||
class UnauthorizedError(HTTPException):
|
||||
def __init__(self, detail: str = "Not authenticated"):
|
||||
super().__init__(status_code=401, detail=detail, headers={"WWW-Authenticate": "Bearer"})
|
||||
|
||||
|
||||
class ForbiddenError(HTTPException):
|
||||
def __init__(self, detail: str = "Forbidden"):
|
||||
super().__init__(status_code=403, detail=detail)
|
||||
40
app/core/security.py
Normal file
40
app/core/security.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
import jwt
|
||||
import bcrypt
|
||||
|
||||
from app.config.settings import settings
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
||||
|
||||
|
||||
def verify_password(password: str, hashed: str) -> bool:
|
||||
return bcrypt.checkpw(password.encode(), hashed.encode())
|
||||
|
||||
|
||||
def create_access_token(user_id: str) -> str:
|
||||
expire = datetime.utcnow() + timedelta(minutes=settings.jwt_access_expire_minutes)
|
||||
payload = {"sub": user_id, "exp": expire, "type": "access"}
|
||||
return jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm)
|
||||
|
||||
|
||||
def create_refresh_token(user_id: str) -> str:
|
||||
expire = datetime.utcnow() + timedelta(days=settings.jwt_refresh_expire_days)
|
||||
payload = {"sub": user_id, "exp": expire, "type": "refresh"}
|
||||
return jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm)
|
||||
|
||||
|
||||
def create_reset_token(user_id: str) -> str:
|
||||
expire = datetime.utcnow() + timedelta(hours=1)
|
||||
payload = {"sub": user_id, "exp": expire, "type": "reset"}
|
||||
return jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm)
|
||||
|
||||
|
||||
def decode_token(token: str) -> Optional[dict]:
|
||||
try:
|
||||
return jwt.decode(token, settings.jwt_secret, algorithms=[settings.jwt_algorithm])
|
||||
except jwt.PyJWTError:
|
||||
return None
|
||||
Reference in New Issue
Block a user