Implement FastAPI user authentication service with MongoDB
- Set up FastAPI application with MongoDB Motor driver - Implemented user registration, login, and logout with HTTP-only cookies - Added JWT token authentication and password hashing - Created user management endpoints for username updates and password changes - Structured application with proper separation of concerns (models, schemas, services, routes) - Added CORS configuration and health endpoints - Documented API endpoints and environment variables in README
This commit is contained in:
parent
ea5ed1570b
commit
50b3fc513b
93
README.md
93
README.md
@ -1,3 +1,92 @@
|
|||||||
# FastAPI Application
|
# User Authentication Service
|
||||||
|
|
||||||
This is a FastAPI application bootstrapped by BackendIM, the AI-powered backend generation platform.
|
A FastAPI application with MongoDB using Motor for user authentication with HTTP-only cookies and CRUD operations.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- User registration and login with email/password
|
||||||
|
- HTTP-only cookie authentication
|
||||||
|
- Username updates
|
||||||
|
- Password changes
|
||||||
|
- MongoDB with Motor async driver
|
||||||
|
- JWT token-based session management
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Create a `.env` file in the root directory with the following variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MONGODB_URL=mongodb://localhost:27017
|
||||||
|
SECRET_KEY=your-secret-key-here-change-in-production
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start MongoDB (make sure MongoDB is running on your system)
|
||||||
|
|
||||||
|
3. Copy environment variables:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Update the `.env` file with your actual values
|
||||||
|
|
||||||
|
## Running the Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uvicorn main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
The application will be available at:
|
||||||
|
- API: http://localhost:8000
|
||||||
|
- Documentation: http://localhost:8000/docs
|
||||||
|
- Health check: http://localhost:8000/health
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
- `POST /api/auth/register` - Register a new user
|
||||||
|
- `POST /api/auth/login` - Login user (sets HTTP-only cookie)
|
||||||
|
- `POST /api/auth/logout` - Logout user (clears cookie)
|
||||||
|
- `GET /api/auth/me` - Get current user info
|
||||||
|
|
||||||
|
### User Management
|
||||||
|
- `PUT /api/users/username` - Update username
|
||||||
|
- `PUT /api/users/password` - Change password
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── app/
|
||||||
|
│ ├── db/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── connection.py
|
||||||
|
│ ├── models/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── user.py
|
||||||
|
│ ├── routes/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── auth.py
|
||||||
|
│ │ └── users.py
|
||||||
|
│ ├── schemas/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── user.py
|
||||||
|
│ ├── services/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── user_service.py
|
||||||
|
│ ├── utils/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── dependencies.py
|
||||||
|
│ │ └── security.py
|
||||||
|
│ └── __init__.py
|
||||||
|
├── main.py
|
||||||
|
├── requirements.txt
|
||||||
|
├── .env.example
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
0
app/db/__init__.py
Normal file
0
app/db/__init__.py
Normal file
19
app/db/connection.py
Normal file
19
app/db/connection.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import os
|
||||||
|
from motor.motor_asyncio import AsyncIOMotorClient
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
client: Optional[AsyncIOMotorClient] = None
|
||||||
|
|
||||||
|
db = Database()
|
||||||
|
|
||||||
|
async def get_database():
|
||||||
|
return db.client.user_auth_db
|
||||||
|
|
||||||
|
async def connect_to_mongo():
|
||||||
|
mongodb_url = os.getenv("MONGODB_URL", "mongodb://localhost:27017")
|
||||||
|
db.client = AsyncIOMotorClient(mongodb_url)
|
||||||
|
|
||||||
|
async def close_mongo_connection():
|
||||||
|
if db.client:
|
||||||
|
db.client.close()
|
0
app/models/__init__.py
Normal file
0
app/models/__init__.py
Normal file
36
app/models/user.py
Normal file
36
app/models/user.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from pydantic import BaseModel, Field, EmailStr
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
|
class PyObjectId(ObjectId):
|
||||||
|
@classmethod
|
||||||
|
def __get_validators__(cls):
|
||||||
|
yield cls.validate
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, v):
|
||||||
|
if not ObjectId.is_valid(v):
|
||||||
|
raise ValueError("Invalid objectid")
|
||||||
|
return ObjectId(v)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __modify_schema__(cls, field_schema):
|
||||||
|
field_schema.update(type="string")
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
|
||||||
|
email: EmailStr
|
||||||
|
username: str
|
||||||
|
hashed_password: str
|
||||||
|
is_active: bool = True
|
||||||
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
allow_population_by_field_name = True
|
||||||
|
arbitrary_types_allowed = True
|
||||||
|
json_encoders = {ObjectId: str}
|
||||||
|
|
||||||
|
class UserInDB(User):
|
||||||
|
pass
|
0
app/routes/__init__.py
Normal file
0
app/routes/__init__.py
Normal file
70
app/routes/auth.py
Normal file
70
app/routes/auth.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, status, Response, Depends
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from app.schemas.user import UserCreate, UserLogin, UserResponse, Message
|
||||||
|
from app.services.user_service import user_service
|
||||||
|
from app.utils.security import create_access_token, ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
|
from app.utils.dependencies import get_current_active_user
|
||||||
|
from app.models.user import UserInDB
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||||||
|
async def register(user_data: UserCreate):
|
||||||
|
user = await user_service.create_user(user_data)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Email or username already registered"
|
||||||
|
)
|
||||||
|
|
||||||
|
return UserResponse(
|
||||||
|
id=str(user.id),
|
||||||
|
email=user.email,
|
||||||
|
username=user.username,
|
||||||
|
is_active=user.is_active,
|
||||||
|
created_at=user.created_at,
|
||||||
|
updated_at=user.updated_at
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.post("/login", response_model=Message)
|
||||||
|
async def login(user_credentials: UserLogin, response: Response):
|
||||||
|
user = await user_service.authenticate_user(user_credentials.email, user_credentials.password)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Incorrect email or password"
|
||||||
|
)
|
||||||
|
|
||||||
|
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
access_token = create_access_token(
|
||||||
|
data={"sub": user.email}, expires_delta=access_token_expires
|
||||||
|
)
|
||||||
|
|
||||||
|
response.set_cookie(
|
||||||
|
key="access_token",
|
||||||
|
value=access_token,
|
||||||
|
max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
||||||
|
httponly=True,
|
||||||
|
secure=False, # Set to True in production with HTTPS
|
||||||
|
samesite="lax"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"message": "Login successful"}
|
||||||
|
|
||||||
|
@router.post("/logout", response_model=Message)
|
||||||
|
async def logout(response: Response, current_user: UserInDB = Depends(get_current_active_user)):
|
||||||
|
response.delete_cookie(key="access_token")
|
||||||
|
return {"message": "Logout successful"}
|
||||||
|
|
||||||
|
@router.get("/me", response_model=UserResponse)
|
||||||
|
async def get_current_user_info(current_user: UserInDB = Depends(get_current_active_user)):
|
||||||
|
return UserResponse(
|
||||||
|
id=str(current_user.id),
|
||||||
|
email=current_user.email,
|
||||||
|
username=current_user.username,
|
||||||
|
is_active=current_user.is_active,
|
||||||
|
created_at=current_user.created_at,
|
||||||
|
updated_at=current_user.updated_at
|
||||||
|
)
|
54
app/routes/users.py
Normal file
54
app/routes/users.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, status, Depends
|
||||||
|
|
||||||
|
from app.schemas.user import UserUpdate, PasswordChange, UserResponse, Message
|
||||||
|
from app.services.user_service import user_service
|
||||||
|
from app.utils.dependencies import get_current_active_user
|
||||||
|
from app.models.user import UserInDB
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.put("/username", response_model=UserResponse)
|
||||||
|
async def update_username(
|
||||||
|
user_update: UserUpdate,
|
||||||
|
current_user: UserInDB = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
if not user_update.username:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Username is required"
|
||||||
|
)
|
||||||
|
|
||||||
|
updated_user = await user_service.update_username(str(current_user.id), user_update.username)
|
||||||
|
if not updated_user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Username already taken"
|
||||||
|
)
|
||||||
|
|
||||||
|
return UserResponse(
|
||||||
|
id=str(updated_user.id),
|
||||||
|
email=updated_user.email,
|
||||||
|
username=updated_user.username,
|
||||||
|
is_active=updated_user.is_active,
|
||||||
|
created_at=updated_user.created_at,
|
||||||
|
updated_at=updated_user.updated_at
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.put("/password", response_model=Message)
|
||||||
|
async def change_password(
|
||||||
|
password_change: PasswordChange,
|
||||||
|
current_user: UserInDB = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
success = await user_service.change_password(
|
||||||
|
str(current_user.id),
|
||||||
|
password_change.current_password,
|
||||||
|
password_change.new_password
|
||||||
|
)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Current password is incorrect"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"message": "Password updated successfully"}
|
0
app/schemas/__init__.py
Normal file
0
app/schemas/__init__.py
Normal file
30
app/schemas/user.py
Normal file
30
app/schemas/user.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from pydantic import BaseModel, EmailStr, Field
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class UserCreate(BaseModel):
|
||||||
|
email: EmailStr
|
||||||
|
username: str
|
||||||
|
password: str = Field(..., min_length=8)
|
||||||
|
|
||||||
|
class UserLogin(BaseModel):
|
||||||
|
email: EmailStr
|
||||||
|
password: str
|
||||||
|
|
||||||
|
class UserResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
email: EmailStr
|
||||||
|
username: str
|
||||||
|
is_active: bool
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
class UserUpdate(BaseModel):
|
||||||
|
username: Optional[str] = None
|
||||||
|
|
||||||
|
class PasswordChange(BaseModel):
|
||||||
|
current_password: str
|
||||||
|
new_password: str = Field(..., min_length=8)
|
||||||
|
|
||||||
|
class Message(BaseModel):
|
||||||
|
message: str
|
0
app/services/__init__.py
Normal file
0
app/services/__init__.py
Normal file
104
app/services/user_service.py
Normal file
104
app/services/user_service.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from bson import ObjectId
|
||||||
|
from pymongo.errors import DuplicateKeyError
|
||||||
|
|
||||||
|
from app.db.connection import get_database
|
||||||
|
from app.models.user import User, UserInDB
|
||||||
|
from app.schemas.user import UserCreate, UserUpdate
|
||||||
|
from app.utils.security import get_password_hash, verify_password
|
||||||
|
|
||||||
|
class UserService:
|
||||||
|
def __init__(self):
|
||||||
|
self.collection_name = "users"
|
||||||
|
|
||||||
|
async def get_collection(self):
|
||||||
|
db = await get_database()
|
||||||
|
return db[self.collection_name]
|
||||||
|
|
||||||
|
async def create_user(self, user_data: UserCreate) -> Optional[UserInDB]:
|
||||||
|
try:
|
||||||
|
collection = await self.get_collection()
|
||||||
|
|
||||||
|
existing_user = await collection.find_one({"email": user_data.email})
|
||||||
|
if existing_user:
|
||||||
|
return None
|
||||||
|
|
||||||
|
existing_username = await collection.find_one({"username": user_data.username})
|
||||||
|
if existing_username:
|
||||||
|
return None
|
||||||
|
|
||||||
|
hashed_password = get_password_hash(user_data.password)
|
||||||
|
user_dict = {
|
||||||
|
"email": user_data.email,
|
||||||
|
"username": user_data.username,
|
||||||
|
"hashed_password": hashed_password,
|
||||||
|
"is_active": True,
|
||||||
|
"created_at": datetime.utcnow(),
|
||||||
|
"updated_at": datetime.utcnow()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await collection.insert_one(user_dict)
|
||||||
|
user_dict["_id"] = result.inserted_id
|
||||||
|
|
||||||
|
return UserInDB(**user_dict)
|
||||||
|
except DuplicateKeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_user_by_email(self, email: str) -> Optional[UserInDB]:
|
||||||
|
collection = await self.get_collection()
|
||||||
|
user_data = await collection.find_one({"email": email})
|
||||||
|
if user_data:
|
||||||
|
return UserInDB(**user_data)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_user_by_id(self, user_id: str) -> Optional[UserInDB]:
|
||||||
|
collection = await self.get_collection()
|
||||||
|
user_data = await collection.find_one({"_id": ObjectId(user_id)})
|
||||||
|
if user_data:
|
||||||
|
return UserInDB(**user_data)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def authenticate_user(self, email: str, password: str) -> Optional[UserInDB]:
|
||||||
|
user = await self.get_user_by_email(email)
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
if not verify_password(password, user.hashed_password):
|
||||||
|
return None
|
||||||
|
return user
|
||||||
|
|
||||||
|
async def update_username(self, user_id: str, new_username: str) -> Optional[UserInDB]:
|
||||||
|
collection = await self.get_collection()
|
||||||
|
|
||||||
|
existing_username = await collection.find_one({"username": new_username, "_id": {"$ne": ObjectId(user_id)}})
|
||||||
|
if existing_username:
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = await collection.update_one(
|
||||||
|
{"_id": ObjectId(user_id)},
|
||||||
|
{"$set": {"username": new_username, "updated_at": datetime.utcnow()}}
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.modified_count:
|
||||||
|
return await self.get_user_by_id(user_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def change_password(self, user_id: str, current_password: str, new_password: str) -> bool:
|
||||||
|
user = await self.get_user_by_id(user_id)
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not verify_password(current_password, user.hashed_password):
|
||||||
|
return False
|
||||||
|
|
||||||
|
collection = await self.get_collection()
|
||||||
|
new_hashed_password = get_password_hash(new_password)
|
||||||
|
|
||||||
|
result = await collection.update_one(
|
||||||
|
{"_id": ObjectId(user_id)},
|
||||||
|
{"$set": {"hashed_password": new_hashed_password, "updated_at": datetime.utcnow()}}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.modified_count > 0
|
||||||
|
|
||||||
|
user_service = UserService()
|
0
app/utils/__init__.py
Normal file
0
app/utils/__init__.py
Normal file
39
app/utils/dependencies.py
Normal file
39
app/utils/dependencies.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from fastapi import Depends, HTTPException, status, Request
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from app.services.user_service import user_service
|
||||||
|
from app.utils.security import verify_token
|
||||||
|
from app.models.user import UserInDB
|
||||||
|
|
||||||
|
async def get_current_user(request: Request) -> UserInDB:
|
||||||
|
token = request.cookies.get("access_token")
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Not authenticated"
|
||||||
|
)
|
||||||
|
|
||||||
|
email = verify_token(token)
|
||||||
|
if email is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Invalid token"
|
||||||
|
)
|
||||||
|
|
||||||
|
user = await user_service.get_user_by_email(email)
|
||||||
|
if user is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="User not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
async def get_current_active_user(current_user: UserInDB = Depends(get_current_user)) -> UserInDB:
|
||||||
|
if not current_user.is_active:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Inactive user"
|
||||||
|
)
|
||||||
|
return current_user
|
38
app/utils/security.py
Normal file
38
app/utils/security.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Optional
|
||||||
|
from jose import JWTError, jwt
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
from fastapi import HTTPException, status
|
||||||
|
|
||||||
|
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-here-change-in-production")
|
||||||
|
ALGORITHM = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||||
|
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
|
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 create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||||
|
to_encode = data.copy()
|
||||||
|
if expires_delta:
|
||||||
|
expire = datetime.utcnow() + expires_delta
|
||||||
|
else:
|
||||||
|
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
to_encode.update({"exp": expire})
|
||||||
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
|
def verify_token(token: str) -> Optional[str]:
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
|
email: str = payload.get("sub")
|
||||||
|
if email is None:
|
||||||
|
return None
|
||||||
|
return email
|
||||||
|
except JWTError:
|
||||||
|
return None
|
47
main.py
Normal file
47
main.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from app.routes import auth, users
|
||||||
|
from app.db.connection import connect_to_mongo, close_mongo_connection
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="User Authentication Service",
|
||||||
|
description="A simple CRUD app with user authentication using HTTP-only cookies",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
app.include_router(auth.router, prefix="/api/auth", tags=["authentication"])
|
||||||
|
app.include_router(users.router, prefix="/api/users", tags=["users"])
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup_db_client():
|
||||||
|
await connect_to_mongo()
|
||||||
|
|
||||||
|
@app.on_event("shutdown")
|
||||||
|
async def shutdown_db_client():
|
||||||
|
await close_mongo_connection()
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {
|
||||||
|
"title": "User Authentication Service",
|
||||||
|
"documentation": "/docs",
|
||||||
|
"health": "/health"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "healthy", "service": "User Authentication Service"}
|
9
requirements.txt
Normal file
9
requirements.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
fastapi==0.104.1
|
||||||
|
uvicorn==0.24.0
|
||||||
|
motor==3.3.2
|
||||||
|
python-jose[cryptography]==3.3.0
|
||||||
|
passlib[bcrypt]==1.7.4
|
||||||
|
python-multipart==0.0.6
|
||||||
|
email-validator==2.1.0
|
||||||
|
pydantic[email]==2.5.0
|
||||||
|
python-dotenv==1.0.0
|
Loading…
x
Reference in New Issue
Block a user