From 83389e0c100620508800722dc0cad7ce9553c1ee Mon Sep 17 00:00:00 2001 From: osbm Date: Mon, 5 May 2025 21:25:00 +0300 Subject: [PATCH] Revert "Merge pull request 'add updates to show bedir' (#14) from another into main" This reverts commit 99f611b3d0b224dd65980c1b2460bf3573795f33, reversing changes made to d5588dd055e06cc812af3dc02d2e2d190bce97b4. --- auth/models.py | 146 +++++++++++++++++++++++++++++++++++++++++------- auth/router.py | 86 ++++++++++++++-------------- auth/schemas.py | 23 -------- config.py | 35 ++++++------ items/models.py | 25 ++++----- items/router.py | 13 +++-- 6 files changed, 210 insertions(+), 118 deletions(-) delete mode 100644 auth/schemas.py diff --git a/auth/models.py b/auth/models.py index 41cb69f..7a3dd73 100644 --- a/auth/models.py +++ b/auth/models.py @@ -1,27 +1,135 @@ -from sqlalchemy import Column, Integer, String, Enum, DateTime -import enum, datetime +from enum import Enum +from backend.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES +from backend.config import pwd_context, get_session_db +from datetime import datetime, timedelta, timezone +from pydantic import BaseModel +from fastapi import Depends, HTTPException +from typing import Annotated, Optional +from fastapi.security import OAuth2PasswordBearer +from passlib.context import CryptContext +import jwt +from sqlmodel import SQLModel, Field, Session, select +from pydantic.networks import EmailStr -from ..config import Base +class Token(BaseModel): + access_token: str + token_type: str -class Role(str, enum.Enum): - admin = "admin" + +### ENUMS ### +class Role(str, Enum): user = "user" + admin = "admin" + guest = "guest" mod = "mod" -class Status(str, enum.Enum): - banned = "banned" +class Status(str, Enum): active = "active" + banned = "banned" suspended = "suspended" -class User(Base): - __tablename__ = "users" - user_id = Column(Integer, primary_key=True) - username = Column(String, unique=True) - name = Column(String) - surname = Column(String) - hashedPassword = Column(String) - email = Column(String, unique=True) - role = Column(Enum(Role), default=Role.user) - status = Column(Enum(Status), default=Status.active) - bio = Column(String(144)) - created_date = Column(DateTime, default=datetime.datetime.utcnow) +### KULLANICI MODELLERİ ### +class UserBase(SQLModel): + username: Optional[str] = None + user_id: Optional[int] = None + role: Optional[Role] = None + status: Optional[Status] = None + +class UserInDb(UserBase): + hashed_password: str | None = None + +class UserPublic(UserBase): + pass + +class UserCreate(BaseModel): + username: Optional[str] = None + role: Optional[Role] = None + email : EmailStr | None = None + status: Optional[Status] = None + password : str | None = None + +### VERİTABANI MODELİ ### +class DBUser(SQLModel, table=True): + __tablename__ = "users" # opsiyonel, sqlmodel bunu otomatik de atar + user_id: Optional[int] = Field(default=None, primary_key=True) + username: str = Field(index=True, nullable=False) + hashed_password: str = Field(nullable=False) + role: Role = Field(default=Role.user) + status: Status = Field(default=Status.active) + + +### AUTH ### +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login") + + +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) + +def authenticate_user( + session: Annotated[Session, Depends(get_session_db)], + username: str, + password: str + ) -> UserInDb | None: + + statement = select(DBUser).where(DBUser.username == username) + result = session.exec(statement).first() + if not result or not verify_password(password, result.hashed_password): + return None + return result + + +def create_access_token( + data: dict, + expires_delta: Optional[timedelta] = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), +) -> str: + to_encode = data.copy() + expire = datetime.now(timezone.utc) + expires_delta + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm="HS256") + return encoded_jwt + + +async def get_user( + session: Annotated[Session, Depends(get_session_db)], + username: str + ) -> UserInDb | None: + + statement = select(DBUser).where(DBUser.username == username) + result = session.exec(statement).first() + return result + + +async def get_current_user( + token: Annotated[str, Depends(oauth2_scheme)], + session: Annotated[Session, Depends(get_session_db)] +) -> UserPublic: + + credentials_exception = HTTPException( + status_code=401, + detail="Invalid credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) + username: Optional[str] = payload.get("sub") + if username is None: + raise credentials_exception + except jwt.PyJWTError: + raise credentials_exception + + user = await get_user(session, username) + if user is None: + raise credentials_exception + return user + + +async def get_current_active_user( + current_user: Annotated[UserInDb, Depends(get_current_user)] +) -> UserPublic: + + if current_user.status == Status.banned: + raise HTTPException(status_code=400, detail="Inactive user") + return current_user diff --git a/auth/router.py b/auth/router.py index 92affa8..0b8517e 100644 --- a/auth/router.py +++ b/auth/router.py @@ -1,14 +1,15 @@ -import os - -from fastapi import APIRouter, HTTPException -import bcrypt -import jwt - +from fastapi import APIRouter, Depends, HTTPException, status +from .models import Token, UserPublic +from .models import authenticate_user, create_access_token +from datetime import timedelta +from ..auth.models import get_password_hash, verify_password +from typing import Annotated +from sqlmodel import Session +from ..config import get_session_db from fastapi import Depends -from sqlalchemy.orm import Session -from .models import User -from .schemas import UserCreate, UserOut, UserLogin -from ..config import get_db +from fastapi.security import OAuth2PasswordRequestForm +from .models import UserCreate, DBUser + router = APIRouter( prefix="/auth", @@ -17,40 +18,41 @@ router = APIRouter( dependencies=[], ) -def create_token(user: User): - return jwt.encode({"sub": user.username}, os.getenv("SECRET_KEY"), algorithm=os.getenv("ALGORITHM")) +@router.post('/login') +async def login_for_access_token( + form_data : Annotated[OAuth2PasswordRequestForm, Depends()], + session : Annotated[Session, Depends(get_session_db)], +) -> Token: -def verify_token(token: str): - try: - data = jwt.decode(token, os.getenv("SECRET_KEY"), algorithms=[os.getenv("ALGORITHM")]) - return data.get("sub") - except jwt.ExpiredSignatureError: - raise HTTPException(401, "Token expired") - except jwt.InvalidTokenError: - raise HTTPException(401, "Invalid token") + user = authenticate_user(session, form_data.username, form_data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + access_token_expires = timedelta(minutes=30) + access_token = create_access_token( + data={"sub": user.username, "role": user.role, 'status': user.status}, expires_delta=access_token_expires + ) + return Token(access_token=access_token, token_type="bearer") -@router.post("/register") -def register(user: UserCreate, db: Session = Depends(get_db)): - if db.query(User).filter_by(username=user.username).first(): - raise HTTPException(400, "Username taken") - hashed = bcrypt.hashpw(user.password.encode(), bcrypt.gensalt()).decode() - db_user = User(**user.model_dump(exclude={"password"}), hashedPassword=hashed) - db.add(db_user) - db.commit() - return {"msg": "User created"} +@router.post('/register', response_model=UserPublic) +async def create_user( + session : Annotated[Session, Depends(get_session_db)], + user : Annotated[UserCreate, Depends()] +): + user_dict = user.dict() + print(user.password) + user_dict['hashed_password'] = get_password_hash(user.password) + print (user_dict['hashed_password']) -@router.post("/login") -def login(user: UserLogin, db: Session = Depends(get_db)): - db_user = db.query(User).filter_by(username=user.username).first() - if not db_user or not bcrypt.checkpw(user.password.encode(), db_user.hashedPassword.encode()): - raise HTTPException(401, "Invalid creds") - return {"token": create_token(db_user)} + if not verify_password(user.password, user_dict['hashed_password']): + raise HTTPException(status_code=400, detail="Password hashing failed") -@router.get("/me", response_model=UserOut) -def get_me(token: str, db: Session = Depends(get_db)): - username = verify_token(token) - if not username: - raise HTTPException(401, "Invalid token") - user = db.query(User).filter_by(username=username).first() - return user \ No newline at end of file + db_user = DBUser.model_validate(user_dict) + session.add(db_user) + session.commit() + session.refresh(db_user) + return db_user \ No newline at end of file diff --git a/auth/schemas.py b/auth/schemas.py deleted file mode 100644 index b9f3a16..0000000 --- a/auth/schemas.py +++ /dev/null @@ -1,23 +0,0 @@ -from pydantic import BaseModel, EmailStr -from .models import Role, Status - -class UserCreate(BaseModel): - username: str - name: str - surname: str - password: str - email: EmailStr - bio: str = "" - -class UserOut(BaseModel): - username: str - name: str - surname: str - email: EmailStr - role: Role - status: Status - bio: str - -class UserLogin(BaseModel): - username: str - password: str \ No newline at end of file diff --git a/config.py b/config.py index e4641c4..07d6e59 100644 --- a/config.py +++ b/config.py @@ -1,13 +1,17 @@ from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker, declarative_base +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from passlib.context import CryptContext +from sqlmodel import SQLModel, Field, Session from dotenv import load_dotenv import os load_dotenv() +# Veritabanı URL'sini oluştur DATABASE_URL = ( f"postgresql://{os.getenv('USERNAME_DB')}:" f"{os.getenv('PASSWORD_DB')}@" @@ -16,24 +20,16 @@ DATABASE_URL = ( f"{os.getenv('NAME_DB')}" ) +engine = create_engine(DATABASE_URL, echo=False) +def init_db(): + SQLModel.metadata.create_all(engine) -engine = create_engine(DATABASE_URL) -SessionLocal = sessionmaker(bind=engine) -Base = declarative_base() - -from .auth.models import * -from .items.models import * - -Base.metadata.create_all(bind=engine) +def get_session_db(): + with Session(engine) as session: + yield session -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - +### SECRET KEY ### origins = [ "http://localhost", "http://localhost:8080", @@ -42,6 +38,10 @@ origins = [ ] app = FastAPI() +@app.on_event("startup") +def on_startup(): + init_db() + app.add_middleware( CORSMiddleware, allow_origins=origins, @@ -49,3 +49,6 @@ app.add_middleware( allow_methods=["*"], allow_headers=["*"], ) + + + diff --git a/items/models.py b/items/models.py index 75215e3..8d7cb69 100644 --- a/items/models.py +++ b/items/models.py @@ -1,15 +1,12 @@ -from datetime import datetime -from ..config import Base -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey -from sqlalchemy.orm import relationship, Mapped, mapped_column +from datetime import datetime, timedelta, timezone +from ..auth.models import UserBase + +class UserProfile(UserBase): + bio : str | None = None + created_date : datetime | None = None + collections : list[str] | None = None + items :list[str] | None = None + + + -class Item(Base): - __tablename__ = "items" - item_id = Column(Integer, primary_key=True) - name = Column(String(100), nullable=False) - description = Column(String(500), nullable=True) - price = Column(Integer, nullable=False) - created_date = Column(DateTime, default=datetime.utcnow) - updated_date = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) - user: Mapped["User"] = relationship(back_populates="items") \ No newline at end of file diff --git a/items/router.py b/items/router.py index 2961d4f..7fd3c4b 100644 --- a/items/router.py +++ b/items/router.py @@ -1,4 +1,7 @@ +from .models import UserProfile from fastapi import APIRouter, Depends +from typing import Annotated +from ..auth.models import get_current_active_user router = APIRouter( prefix="/items", @@ -7,7 +10,9 @@ router = APIRouter( dependencies=[], ) - -@router.get("/") -async def get_items(): - return {"message": "List of items"} \ No newline at end of file +@router.get('/profile', response_model=UserProfile) +async def get_user_profile( + current_user: Annotated[UserProfile, Depends(get_current_active_user)] +) -> UserProfile: + + return current_user \ No newline at end of file