structure auth

This commit is contained in:
Robin COuret
2026-03-05 22:38:21 +01:00
parent 01f9e9f05e
commit a243149bf1
27 changed files with 68 additions and 52 deletions

Binary file not shown.

View File

@@ -1,5 +1,6 @@
fastapi==0.128.6
sqlmodel==0.0.32
python-dotenv==1.2.1
openai==2.21.0
spacy==3.8.11
PyJWT>=2.11.0

View File

@@ -3,4 +3,3 @@ LANGUAGE_MODEL_API="http://localhost:8080/v1"
LANGUAGE_MODEL_NAME="SmolLM3-Q4_K_M.gguf"
ORIGIN="http://localhost:5173"
SECRET_KEY="xxxx" #generate secure random secret key: openssl rand -hex 32
ACCESS_TOKEN_EXPIRE_MINUTES=10080

View File

@@ -2,9 +2,9 @@ from fastapi import APIRouter
from .knowledges import router as knowledge_router
from .metrics import router as metric_router
from .users import router as user_router
from .auth import router as auth_router
router = APIRouter(prefix="/v1")
router.include_router(knowledge_router)
router.include_router(metric_router)
router.include_router(user_router)
router.include_router(auth_router)

Binary file not shown.

View File

@@ -2,11 +2,13 @@ from typing import Annotated
from fastapi import Depends, APIRouter, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
from datetime import timedelta
from src.app.models.user import User
from src.app.data.user import create_user
from src.app.services.auth import get_current_user, authenticate_user, create_access_token, hash_password, Token
from src.app.auth.dependancies import get_current_user, authenticate_user
from src.app.auth.security import hash_password, create_access_token
from src.app.auth.schemas import Token
router = APIRouter(tags=["users"])

Binary file not shown.

View File

@@ -1,35 +1,21 @@
from src.app.config import settings
from datetime import timedelta, datetime, timezone
from typing import Annotated
from pydantic import BaseModel
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from argon2 import PasswordHasher
from argon2 import PasswordHasher
import jwt
from jwt.exceptions import InvalidTokenError
from argon2 import PasswordHasher
from argon2.exceptions import (
VerifyMismatchError,
VerificationError,
InvalidHashError,
)
from src.app.models.user import User
from src.app.data.user import get_user
from .schemas import TokenData
from .security import verify_password
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/token")
password_hasher = PasswordHasher()
algorithm = "HS256"
access_token_expire_minutes = 10080
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None
def authenticate_user(username: str, password: str):
user: User = get_user(username)
if not user:
@@ -40,23 +26,6 @@ def authenticate_user(username: str, password: str):
return False
return user
def verify_password(plain_password: str, hashed_password: str) -> bool:
isValidated: bool = False
try:
return password_hasher.verify(hashed_password, plain_password)
except (VerifyMismatchError, VerificationError, InvalidHashError):
return False
def create_access_token(data: dict):
expire = datetime.now(timezone.utc) + timedelta(minutes=access_token_expire_minutes)
to_encode = data.copy()
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=algorithm)
return encoded_jwt
async def hash_password(password: str) -> str:
return password_hasher.hash(password)
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> User:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -64,7 +33,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> Use
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithm=algorithm)
payload = jwt.decode(token, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
username = payload.get("sub")
if username is None:
raise credentials_exception

View File

@@ -0,0 +1,8 @@
from pydantic import BaseModel
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None

View File

@@ -0,0 +1,33 @@
from src.app.config import settings
from datetime import timedelta, datetime, timezone
from argon2 import PasswordHasher
from argon2.exceptions import (
VerifyMismatchError,
VerificationError,
InvalidHashError,
)
import jwt
from jwt.exceptions import InvalidTokenError
password_hasher = PasswordHasher()
def verify_password(plain_password: str, hashed_password: str) -> bool:
try:
return password_hasher.verify(hashed_password, plain_password)
except (VerifyMismatchError, VerificationError, InvalidHashError):
return False
def hash_password(password: str) -> str:
return password_hasher.hash(password)
def create_access_token(data: dict):
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode = data.copy()
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt
#def create_refresh_token(data: dict) -> str:
#def verify_token(token: str, token_type: str = "access") -> Optional[dict]:

View File

@@ -1,17 +1,19 @@
from pydantic_settings import BaseSettings
from pydantic import Field
class Settings(BaseSettings):
# Database
DATABASE_URI: str
DATABASE_URI: str = Field("sqlite:///database.db", env='DATABASE_URI')
# Language model
LANGUAGE_MODEL_API: str = "http://localhost:8080/v1"
LANGUAGE_MODEL_NAME: str = "SmolLM3-Q4_K_M.gguf"
LANGUAGE_MODEL_API: str = Field("http://localhost:8080/v1", env='LANGUAGE_MODEL_API')
LANGUAGE_MODEL_NAME: str = Field("SmolLM3-Q4_K_M.gguf", env='LANGUAGE_MODEL_NAME')
# Security
ORIGIN: str
SECRET_KEY : str
ACCESS_TOKEN_EXPIRE_MINUTES: int
ORIGIN: str = Field('http://localhost:5173', env='ORIGIN')
SECRET_KEY : str = Field('random_string', env='SECRET_KEY')
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
ALGORITHM: str = "HS256"
class Config:
env_file = ".env"

View File

View File

@@ -6,7 +6,8 @@ from fastapi.middleware.cors import CORSMiddleware
from src.app.database import create_db_and_tables
#TODO : best practice to manage models import
# Import models in app
# TODO : best practice to manage models import
from src.app.models.question import Question
from src.app.models.knowledge import Knowledge
from src.app.models.metric import Metric
@@ -14,7 +15,7 @@ from src.app.models.user import User
from .api import router
#Test
#Fake data
from src.app.faker_seed import faker
#TODO : alternative @app.on_event("startup") ?

View File

View File

@@ -4,3 +4,4 @@ class User(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
username: str
hashed_password: str
#is_active: bool