-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Updated requirements.txt. * Delete unnecessary directory. * Created User model. * Cleanup unnecessary code. * Fixed /register. Refactoring. * Cleanup. * Refactor /login. * Use bcrypt for password hashing. * Cleanup. * Updated README.md.
- Loading branch information
Showing
15 changed files
with
207 additions
and
186 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,30 @@ | ||
# apm-service | ||
Back-end service for APM. | ||
|
||
## Brief Description | ||
## Description | ||
This is a brief template that provides the basic setup to create the backend portion using FastAPI. The documentation of the API is done using SwaggerUI, and Pydantic is used for data validation and setting management. | ||
|
||
(To be updated) | ||
|
||
## Dependencies | ||
- Python 3.12.3 | ||
|
||
## 🏃 Running Locally | ||
Preparation: | ||
1. Setup a virtual environment (conda, venv, etc). | ||
2. Run `pip install -r requirements.txt` to install necessary packages. | ||
3. Run service with `uvicorn app.main:app --reload`. | ||
4. Run tests with `to be configured`. | ||
5. Check code style with `flake8`. | ||
6. Check for static typing with `mypy .`. | ||
3. Create an _env file_ named `.env` in the project root path and add the following environment variables: | ||
``` | ||
ACCESS_TOKEN_SECRET_KEY | ||
ACCESS_TOKEN_VALIDITY_MINUTES | ||
DB_NAME | ||
DB_HOST | ||
DB_PORT | ||
DB_USERNAME | ||
DB_PASSWORD | ||
``` | ||
|
||
Running: | ||
1. Run service with `uvicorn app.main:app --reload`. | ||
2. Run tests with `to be configured`. | ||
3. Check code style with `flake8`. | ||
4. Check for static typing with `mypy .`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
from fastapi import Depends, HTTPException, APIRouter | ||
from fastapi.responses import JSONResponse | ||
from sqlalchemy.orm import Session | ||
|
||
from app.db.database import get_db | ||
from app.schemas.auth import LoginUserSchema, RegisterUserSchema | ||
from app.services.auth_service import login_user, register_user | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.post("/login") | ||
def login(returning_user: LoginUserSchema, db: Session = Depends(get_db)) -> JSONResponse: | ||
try: | ||
# Todo: Refresh token. | ||
access_token = login_user(returning_user, db) | ||
|
||
response_payload = { | ||
"access_token": access_token, | ||
"token_type": "bearer" | ||
} | ||
|
||
return JSONResponse(response_payload) | ||
|
||
except HTTPException: | ||
raise | ||
|
||
except Exception: | ||
# Todo: Handle exceptions. | ||
raise HTTPException(status_code=500, detail="Internal Server Error") | ||
|
||
|
||
@router.post("/register") | ||
def register(new_user: RegisterUserSchema, db: Session = Depends(get_db)) -> JSONResponse: | ||
try: | ||
register_user(new_user, db) | ||
|
||
# Todo: Return token. | ||
return JSONResponse({}) | ||
|
||
except HTTPException: | ||
raise | ||
|
||
except Exception: | ||
# Todo: Handle exceptions. | ||
raise HTTPException(status_code=500, detail="Internal Server Error") | ||
|
||
|
||
# async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_db)) -> User: | ||
# credentials_exception = HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, | ||
# detail="Could not validate credentials", | ||
# headers={"WWW-Authenticate": "Bearer"},) | ||
# try: | ||
# payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) | ||
# username: str = payload.get("sub") | ||
# if username is None: | ||
# raise credentials_exception | ||
# token_data = TokenData(username=username) | ||
# except InvalidTokenError: | ||
# raise credentials_exception | ||
# user = db.query(User).filter(User.username == token_data.username).first() # type: ignore | ||
# if user is None: | ||
# raise credentials_exception | ||
# return user | ||
# | ||
# | ||
# async def get_current_active_user(current_user: Annotated[User, Depends(get_current_user)],) -> User: | ||
# if current_user.disabled: | ||
# raise HTTPException(status_code=400, detail="Inactive user.") | ||
# return current_user | ||
# | ||
# | ||
# @router.get("/users/me/") | ||
# async def read_users_me(current_user: Annotated[User, Depends(get_current_active_user)]) -> User: | ||
# return current_user |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import uuid | ||
from sqlalchemy.orm import Mapped, mapped_column # type: ignore[attr-defined] | ||
from sqlalchemy.dialects.postgresql import UUID | ||
from app.db.database import Base | ||
|
||
|
||
class User(Base): # type: ignore[misc] | ||
__tablename__ = "user" | ||
|
||
id: Mapped[str] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4()) | ||
username: Mapped[str] = mapped_column(unique=True, nullable=True) | ||
email: Mapped[str] = mapped_column(unique=True, nullable=False) | ||
password: Mapped[str] = mapped_column(nullable=False) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from sqlalchemy.orm import Session | ||
|
||
from app.db.models.user import User | ||
|
||
|
||
class UserRepository: | ||
def __init__(self, db: Session): | ||
self.db = db | ||
|
||
def get_user_by_email(self, email: str) -> User | None: | ||
return self.db.query(User).filter(User.email == email).first() | ||
|
||
def insert_user(self, user: User) -> None: | ||
self.db.add(user) | ||
self.db.commit() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,11 @@ | ||
from dotenv import load_dotenv | ||
from fastapi import FastAPI | ||
|
||
from app.api.v1.router import api_router | ||
from app.api import authentication | ||
from app.api import auth_router | ||
|
||
load_dotenv() | ||
|
||
app = FastAPI() | ||
app.include_router(api_router) | ||
app.include_router(authentication.router) | ||
app.include_router(auth_router.router) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from pydantic import BaseModel, EmailStr | ||
|
||
|
||
class LoginUserSchema(BaseModel): # type: ignore[misc] | ||
email: EmailStr | ||
password: str | ||
|
||
|
||
class RegisterUserSchema(BaseModel): # type: ignore[misc] | ||
email: EmailStr | ||
username: str | ||
password: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import bcrypt | ||
import jwt | ||
import os | ||
import time | ||
from fastapi import HTTPException | ||
from sqlalchemy.orm import Session | ||
|
||
from app.db.models.user import User | ||
from app.db.repositories.user_repository import UserRepository | ||
from app.schemas.auth import LoginUserSchema, RegisterUserSchema | ||
|
||
|
||
def verify_password(plain_password: str, hashed_password: str) -> bool: | ||
return bcrypt.checkpw(plain_password.encode(), hashed_password.encode()) | ||
|
||
|
||
def hash_password(password: str) -> str: | ||
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() | ||
|
||
|
||
def generate_jwt(user: User) -> str: | ||
time_now = int(time.time()) | ||
claims = { | ||
"sub": str(user.id), | ||
"iss": "apm-service", | ||
"iat": time_now, | ||
"exp": time_now + (int(os.getenv("ACCESS_TOKEN_VALIDITY_MINUTES", 15)) * 60) | ||
} | ||
|
||
# Todo: Consolidate env vars. Ensure not None. | ||
token = jwt.encode(payload=claims, key=os.getenv("ACCESS_TOKEN_SECRET_KEY"), algorithm="HS256") | ||
|
||
return token | ||
|
||
|
||
def login_user(returning_user: LoginUserSchema, db: Session) -> str: | ||
user_repository = UserRepository(db) | ||
|
||
existing_user = user_repository.get_user_by_email(returning_user.email) | ||
if not existing_user or not verify_password(returning_user.password, existing_user.password): | ||
raise HTTPException(status_code=401, detail="Incorrect email or password.") | ||
|
||
access_token = generate_jwt(existing_user) | ||
|
||
return access_token | ||
|
||
|
||
def register_user(new_user: RegisterUserSchema, db: Session) -> None: | ||
user_repository = UserRepository(db) | ||
|
||
existing_user = user_repository.get_user_by_email(new_user.email) | ||
if existing_user: | ||
raise HTTPException(status_code=409, detail="Email already in use.") | ||
|
||
user = User( | ||
username=new_user.username, | ||
email=new_user.email, | ||
password=hash_password(new_user.password) | ||
) | ||
user_repository.insert_user(user) |
Oops, something went wrong.