diff --git a/server/__pycache__/config.cpython-311.pyc b/server/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000..976bee0 Binary files /dev/null and b/server/__pycache__/config.cpython-311.pyc differ diff --git a/server/requirements.txt b/server/requirements.txt index a0ccd53..a578301 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -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 diff --git a/server/.env.example b/server/src/app/.env.example similarity index 83% rename from server/.env.example rename to server/src/app/.env.example index e3ca509..8107c5e 100644 --- a/server/.env.example +++ b/server/src/app/.env.example @@ -2,5 +2,4 @@ DATABASE_URI="sqlite:///database.db" 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 \ No newline at end of file +SECRET_KEY="xxxx" #generate secure random secret key: openssl rand -hex 32 \ No newline at end of file diff --git a/server/src/app/__pycache__/config.cpython-311.pyc b/server/src/app/__pycache__/config.cpython-311.pyc index b0182d1..eed296b 100644 Binary files a/server/src/app/__pycache__/config.cpython-311.pyc and b/server/src/app/__pycache__/config.cpython-311.pyc differ diff --git a/server/src/app/__pycache__/database.cpython-311.pyc b/server/src/app/__pycache__/database.cpython-311.pyc index 67b67ad..bf9ba73 100644 Binary files a/server/src/app/__pycache__/database.cpython-311.pyc and b/server/src/app/__pycache__/database.cpython-311.pyc differ diff --git a/server/src/app/__pycache__/main.cpython-311.pyc b/server/src/app/__pycache__/main.cpython-311.pyc index 590e08e..1671d87 100644 Binary files a/server/src/app/__pycache__/main.cpython-311.pyc and b/server/src/app/__pycache__/main.cpython-311.pyc differ diff --git a/server/src/app/api/v1/__init__.py b/server/src/app/api/v1/__init__.py index d01ee60..37b1ddf 100644 --- a/server/src/app/api/v1/__init__.py +++ b/server/src/app/api/v1/__init__.py @@ -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) \ No newline at end of file +router.include_router(auth_router) \ No newline at end of file diff --git a/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc b/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc index b0c6a39..e682e78 100644 Binary files a/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc and b/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc differ diff --git a/server/src/app/api/v1/__pycache__/auth.cpython-311.pyc b/server/src/app/api/v1/__pycache__/auth.cpython-311.pyc new file mode 100644 index 0000000..b8dfa3e Binary files /dev/null and b/server/src/app/api/v1/__pycache__/auth.cpython-311.pyc differ diff --git a/server/src/app/api/v1/__pycache__/users.cpython-311.pyc b/server/src/app/api/v1/__pycache__/users.cpython-311.pyc index 59bbe23..16ce17d 100644 Binary files a/server/src/app/api/v1/__pycache__/users.cpython-311.pyc and b/server/src/app/api/v1/__pycache__/users.cpython-311.pyc differ diff --git a/server/src/app/api/v1/users.py b/server/src/app/api/v1/auth.py similarity index 85% rename from server/src/app/api/v1/users.py rename to server/src/app/api/v1/auth.py index eb64c49..f111dc8 100644 --- a/server/src/app/api/v1/users.py +++ b/server/src/app/api/v1/auth.py @@ -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"]) diff --git a/server/src/app/auth/__pycache__/auth.cpython-311.pyc b/server/src/app/auth/__pycache__/auth.cpython-311.pyc new file mode 100644 index 0000000..1204d95 Binary files /dev/null and b/server/src/app/auth/__pycache__/auth.cpython-311.pyc differ diff --git a/server/src/app/auth/__pycache__/dependancies.cpython-311.pyc b/server/src/app/auth/__pycache__/dependancies.cpython-311.pyc new file mode 100644 index 0000000..369a640 Binary files /dev/null and b/server/src/app/auth/__pycache__/dependancies.cpython-311.pyc differ diff --git a/server/src/app/auth/__pycache__/schemas.cpython-311.pyc b/server/src/app/auth/__pycache__/schemas.cpython-311.pyc new file mode 100644 index 0000000..4aa928a Binary files /dev/null and b/server/src/app/auth/__pycache__/schemas.cpython-311.pyc differ diff --git a/server/src/app/auth/__pycache__/security.cpython-311.pyc b/server/src/app/auth/__pycache__/security.cpython-311.pyc new file mode 100644 index 0000000..9e77eeb Binary files /dev/null and b/server/src/app/auth/__pycache__/security.cpython-311.pyc differ diff --git a/server/src/app/services/auth.py b/server/src/app/auth/dependancies.py similarity index 57% rename from server/src/app/services/auth.py rename to server/src/app/auth/dependancies.py index 71e8a8e..7affdb4 100644 --- a/server/src/app/services/auth.py +++ b/server/src/app/auth/dependancies.py @@ -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 diff --git a/server/src/app/auth/schemas.py b/server/src/app/auth/schemas.py new file mode 100644 index 0000000..1728652 --- /dev/null +++ b/server/src/app/auth/schemas.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + +class Token(BaseModel): + access_token: str + token_type: str + +class TokenData(BaseModel): + username: str | None = None \ No newline at end of file diff --git a/server/src/app/auth/security.py b/server/src/app/auth/security.py new file mode 100644 index 0000000..f180f94 --- /dev/null +++ b/server/src/app/auth/security.py @@ -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]: \ No newline at end of file diff --git a/server/src/app/config.py b/server/src/app/config.py index 130233b..254a45a 100644 --- a/server/src/app/config.py +++ b/server/src/app/config.py @@ -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" diff --git a/server/src/app/data/__init__.py b/server/src/app/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/src/app/data/__pycache__/__init__.cpython-311.pyc b/server/src/app/data/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..346e8e6 Binary files /dev/null and b/server/src/app/data/__pycache__/__init__.cpython-311.pyc differ diff --git a/server/src/app/main.py b/server/src/app/main.py index 8f99323..a571bc7 100644 --- a/server/src/app/main.py +++ b/server/src/app/main.py @@ -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") ? diff --git a/server/src/app/models/__init__.py b/server/src/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/src/app/models/__pycache__/__init__.cpython-311.pyc b/server/src/app/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..da2a82f Binary files /dev/null and b/server/src/app/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/server/src/app/models/__pycache__/user.cpython-311.pyc b/server/src/app/models/__pycache__/user.cpython-311.pyc index 4e0f82c..3fe7359 100644 Binary files a/server/src/app/models/__pycache__/user.cpython-311.pyc and b/server/src/app/models/__pycache__/user.cpython-311.pyc differ diff --git a/server/src/app/models/user.py b/server/src/app/models/user.py index 355d511..48a4191 100644 --- a/server/src/app/models/user.py +++ b/server/src/app/models/user.py @@ -3,4 +3,5 @@ from sqlmodel import Field, SQLModel, Relationship class User(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) username: str - hashed_password: str \ No newline at end of file + hashed_password: str + #is_active: bool \ No newline at end of file diff --git a/server/src/app/services/__pycache__/language_generation.cpython-311.pyc b/server/src/app/services/__pycache__/language_generation.cpython-311.pyc index c72f822..905c0d0 100644 Binary files a/server/src/app/services/__pycache__/language_generation.cpython-311.pyc and b/server/src/app/services/__pycache__/language_generation.cpython-311.pyc differ