Initial commit
This commit is contained in:
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
0
app/const.py
Normal file
0
app/const.py
Normal file
25
app/database.py
Normal file
25
app/database.py
Normal file
@ -0,0 +1,25 @@
|
||||
from pymongo import AsyncMongoClient
|
||||
from beanie import init_beanie
|
||||
|
||||
from app.models import User
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
async def init_db():
|
||||
"""Initialize database connection and Beanie ODM"""
|
||||
try:
|
||||
client = AsyncMongoClient(settings.MONGODB_URL)
|
||||
await client.admin.command('ping')
|
||||
|
||||
await init_beanie(
|
||||
database=client[settings.MONGODB_DATABASE],
|
||||
document_models=[User]
|
||||
)
|
||||
except Exception as e:
|
||||
raise
|
||||
|
||||
|
||||
async def close_db():
|
||||
"""Close database connection"""
|
||||
client = AsyncMongoClient(settings.MONGODB_URL)
|
||||
await client.close()
|
||||
22
app/models.py
Normal file
22
app/models.py
Normal file
@ -0,0 +1,22 @@
|
||||
import uuid
|
||||
from datetime import datetime, UTC
|
||||
|
||||
from beanie import Document, Indexed
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class User(Document):
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4)
|
||||
username: str = Indexed(unique=True)
|
||||
hashed_password: str
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||
|
||||
class Settings:
|
||||
name = "users"
|
||||
|
||||
class Config:
|
||||
json_encoders = {
|
||||
datetime: lambda v: v.isoformat(),
|
||||
uuid.UUID: lambda v: str(v),
|
||||
}
|
||||
0
app/repositories/__init__.py
Normal file
0
app/repositories/__init__.py
Normal file
6
app/repositories/auth.py
Normal file
6
app/repositories/auth.py
Normal file
@ -0,0 +1,6 @@
|
||||
from fastapi import HTTPException, status
|
||||
from beanie import PydanticObjectId
|
||||
|
||||
from app import schemas
|
||||
from app.models import User
|
||||
from app.security import create_jwt_token, get_password_hash, verify_password
|
||||
0
app/routers/__init__.py
Normal file
0
app/routers/__init__.py
Normal file
8
app/routers/auth.py
Normal file
8
app/routers/auth.py
Normal file
@ -0,0 +1,8 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from app import schemas
|
||||
from app.repositories import auth
|
||||
from app.security import create_jwt_token
|
||||
from app.settings import settings
|
||||
|
||||
router = APIRouter(prefix="", tags=["auth"])
|
||||
14
app/schemas.py
Normal file
14
app/schemas.py
Normal file
@ -0,0 +1,14 @@
|
||||
import datetime
|
||||
import enum
|
||||
import uuid
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, EmailStr
|
||||
|
||||
|
||||
class BaseResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: uuid.UUID
|
||||
65
app/security.py
Normal file
65
app/security.py
Normal file
@ -0,0 +1,65 @@
|
||||
import os
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import jwt
|
||||
from bcrypt import checkpw, gensalt, hashpw
|
||||
from fastapi import HTTPException, Security
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from app import schemas
|
||||
from app.models import User
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
return hashpw(password.encode("utf-8"), gensalt()).decode("utf-8")
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
return checkpw(
|
||||
plain_password.encode("utf-8"), hashed_password.encode("utf-8")
|
||||
)
|
||||
|
||||
|
||||
def create_jwt_token(data: dict) -> str:
|
||||
_ed = timedelta(minutes=settings.JWT_EXPIRE_MINUTES)
|
||||
iat = datetime.now(UTC)
|
||||
exp = datetime.now(UTC) + _ed
|
||||
token_payload = data
|
||||
token_payload.update({"iat": iat, "exp": exp})
|
||||
|
||||
token = jwt.encode(
|
||||
token_payload, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM
|
||||
)
|
||||
|
||||
return token
|
||||
|
||||
|
||||
def get_user_from_token(token: str) -> schemas.User:
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM]
|
||||
)
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise HTTPException(status_code=401, detail="Token has expired")
|
||||
except jwt.JWTError:
|
||||
raise HTTPException(
|
||||
status_code=401, detail="Invalid authentication credentials"
|
||||
)
|
||||
|
||||
user_id = payload.get("user_id")
|
||||
|
||||
# Return user from database
|
||||
...
|
||||
|
||||
|
||||
def get_user(
|
||||
authorization: HTTPAuthorizationCredentials = Security(HTTPBearer()),
|
||||
) -> schemas.User:
|
||||
if authorization.scheme.lower() != "bearer":
|
||||
raise HTTPException(
|
||||
status_code=401, detail="Invalid authentication scheme"
|
||||
)
|
||||
|
||||
token = authorization.credentials
|
||||
return get_user_from_token(token)
|
||||
18
app/settings.py
Normal file
18
app/settings.py
Normal file
@ -0,0 +1,18 @@
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
JWT_SECRET: str
|
||||
JWT_ALGORITHM: str
|
||||
JWT_EXPIRE_MINUTES: int
|
||||
|
||||
MONGODB_URL: str
|
||||
MONGODB_DATABASE: str
|
||||
|
||||
|
||||
settings = Settings()
|
||||
0
app/utils.py
Normal file
0
app/utils.py
Normal file
Reference in New Issue
Block a user