diff --git a/app/api/api_v1/api.py b/app/api/api_v1/api.py index 0962699..47bc5c5 100644 --- a/app/api/api_v1/api.py +++ b/app/api/api_v1/api.py @@ -1,8 +1,9 @@ from fastapi import APIRouter -from app.api.api_v1.endpoints import lesson +from app.api.api_v1.endpoints import lesson, auth api_router = APIRouter() api_router.include_router(lesson.router, prefix="/lesson", tags=["lessons"]) +api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) diff --git a/app/api/api_v1/endpoints/auth.py b/app/api/api_v1/endpoints/auth.py new file mode 100644 index 0000000..1d53ec1 --- /dev/null +++ b/app/api/api_v1/endpoints/auth.py @@ -0,0 +1,65 @@ +from typing import Any + +from fastapi import APIRouter, Depends, HTTPException +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.orm.session import Session + +from app import crud +from app import schemas +from app.api import deps +from app.core.auth import ( + authenticate, + create_access_token, +) +from app.models.user import User + +router = APIRouter() + + +@router.post("/login") +def login( + db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends() +) -> Any: + """ + Get the JWT for a user with data from OAuth2 request form body. + """ + + user = authenticate(email=form_data.username, password=form_data.password, db=db) + if not user: + raise HTTPException(status_code=400, detail="Incorrect username or password") + + return { + "access_token": create_access_token(sub=user.id), + "token_type": "bearer", + } + + +@router.get("/me", response_model=schemas.User) +def read_users_me(current_user: User = Depends(deps.get_current_user)): + """ + Fetch the current logged in user. + """ + + user = current_user + return user + + +@router.post("/signup", response_model=schemas.User, status_code=201) +def create_user_signup( + *, + db: Session = Depends(deps.get_db), + user_in: schemas.user.UserCreate, +) -> Any: + """ + Create new user without the need to be logged in. + """ + + user = db.query(User).filter(User.email == user_in.email).first() + if user: + raise HTTPException( + status_code=400, + detail="The user with this email already exists in the system", + ) + user = crud.user.create(db=db, obj_in=user_in) + + return user diff --git a/app/api/deps.py b/app/api/deps.py index 8ca9515..cb8c1d5 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -1,6 +1,18 @@ -from typing import Generator +from typing import Generator, Optional +from fastapi import Depends, HTTPException, status +from jose import jwt, JWTError +from pydantic import BaseModel +from sqlalchemy.orm.session import Session + +from app.core.auth import oauth2_scheme +from app.core.config import settings from app.db.session import SessionLocal +from app.models.user import User + + +class TokenData(BaseModel): + username: Optional[str] = None def get_db() -> Generator: @@ -10,3 +22,31 @@ def get_db() -> Generator: yield db finally: db.close() + + +async def get_current_user( + db: Session = Depends(get_db), token: str = Depends(oauth2_scheme) +) -> User: + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode( + token, + settings.JWT_SECRET, + algorithms=[settings.ALGORITHM], + options={"verify_aud": False}, + ) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + token_data = TokenData(username=username) + except JWTError: + raise credentials_exception + + user = db.query(User).filter(User.id == token_data.username).first() + if user is None: + raise credentials_exception + return user