Files
tset-antiplagiat/backend/app/api/auth.py
jze9 2a14350ee3
Some checks failed
Deploy / deploy (push) Has been cancelled
test build
2026-05-18 01:14:40 +05:00

116 lines
3.8 KiB
Python

from datetime import datetime, timedelta
from typing import Optional
import uuid
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from app.config import settings
from app.models.db import get_db, User
router = APIRouter(prefix="/api/auth", tags=["auth"])
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False)
# ── Schemas ────────────────────────────────────────────────────────────────
class RegisterRequest(BaseModel):
email: EmailStr
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
class UserOut(BaseModel):
id: str
email: str
created_at: datetime
class Config:
from_attributes = True
# ── Helpers ────────────────────────────────────────────────────────────────
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def create_token(user_id) -> str:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
return jwt.encode(
{"sub": str(user_id), "exp": expire},
settings.SECRET_KEY,
algorithm=settings.ALGORITHM,
)
def get_current_user(
token: Optional[str] = Depends(oauth2_scheme),
db: Session = Depends(get_db),
) -> Optional[User]:
if not token:
return None
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
user_id_str: str = payload.get("sub")
if not user_id_str:
return None
import uuid as _uuid
user_id = _uuid.UUID(user_id_str)
except (JWTError, ValueError):
return None
return db.query(User).filter(User.id == user_id).first()
def require_user(current_user: Optional[User] = Depends(get_current_user)) -> User:
if not current_user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
return current_user
# ── Endpoints ──────────────────────────────────────────────────────────────
@router.post("/register", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def register(body: RegisterRequest, db: Session = Depends(get_db)):
if db.query(User).filter(User.email == body.email).first():
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered")
user = User(
email=body.email,
hashed_password=hash_password(body.password),
)
db.add(user)
db.commit()
db.refresh(user)
return user
@router.post("/login", response_model=TokenResponse)
def login(form: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
user = db.query(User).filter(User.email == form.username).first()
if not user or not verify_password(form.password, user.hashed_password):
raise HTTPException(status_code=400, detail="Incorrect email or password")
return {"access_token": create_token(user.id)}
@router.get("/me", response_model=UserOut)
def me(current_user: User = Depends(require_user)):
return current_user