rriviere commited on
Commit
a9477b4
·
1 Parent(s): e59e621

feat: secure endpoints with API key authentication

Browse files
Files changed (3) hide show
  1. .env.example +1 -0
  2. app/main.py +16 -0
  3. app/routes/predict.py +11 -3
.env.example ADDED
@@ -0,0 +1 @@
 
 
1
+ API_KEY=your-secret-api-key-here
app/main.py CHANGED
@@ -1,12 +1,28 @@
1
  from contextlib import asynccontextmanager
2
  from fastapi import FastAPI
 
 
3
  from app.routes import predict
4
  from ml_model.loader import load_pipeline
5
 
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  @asynccontextmanager
8
  async def lifespan(app: FastAPI):
9
  app.state.pipeline, app.state.threshold = load_pipeline()
 
10
  yield
11
 
12
  app = FastAPI(
 
1
  from contextlib import asynccontextmanager
2
  from fastapi import FastAPI
3
+ from pydantic import field_validator
4
+ from pydantic_settings import BaseSettings
5
  from app.routes import predict
6
  from ml_model.loader import load_pipeline
7
 
8
 
9
+ class Settings(BaseSettings):
10
+ api_key: str = ""
11
+
12
+ model_config = {"env_file": ".env"}
13
+
14
+ @field_validator("api_key")
15
+ @classmethod
16
+ def api_key_must_not_be_empty(cls, v: str) -> str:
17
+ if not v:
18
+ raise ValueError("API_KEY doit être défini dans le fichier .env")
19
+ return v
20
+
21
+
22
  @asynccontextmanager
23
  async def lifespan(app: FastAPI):
24
  app.state.pipeline, app.state.threshold = load_pipeline()
25
+ app.state.settings = Settings()
26
  yield
27
 
28
  app = FastAPI(
app/routes/predict.py CHANGED
@@ -1,10 +1,18 @@
1
  import numpy as np
2
  import pandas as pd
3
- from fastapi import APIRouter, Request
 
4
  from app.schemas.prediction import PredictionInput, PredictionOutput
5
 
6
  router = APIRouter()
7
 
 
 
 
 
 
 
 
8
  SAT_COLS = [
9
  "satisfaction_employee_environnement",
10
  "satisfaction_employee_nature_travail",
@@ -36,7 +44,7 @@ def health_check():
36
  return {"status": "ok", "message": "API opérationnelle"}
37
 
38
 
39
- @router.get("/model/info", summary="Informations sur le modèle", description="Retourne les métadonnées du modèle déployé")
40
  def model_info(request: Request):
41
  return {
42
  "algorithme": "Régression Logistique",
@@ -45,7 +53,7 @@ def model_info(request: Request):
45
  }
46
 
47
 
48
- @router.post("/predict", response_model=PredictionOutput, summary="Prédiction du risque de départ", description="Envoie les features RH d'un employé et reçoit une prédiction")
49
  def predict_churn(data: PredictionInput, request: Request):
50
  pipeline = request.app.state.pipeline
51
  threshold = request.app.state.threshold
 
1
  import numpy as np
2
  import pandas as pd
3
+ from fastapi import APIRouter, Depends, HTTPException, Request, Security, status
4
+ from fastapi.security import APIKeyHeader
5
  from app.schemas.prediction import PredictionInput, PredictionOutput
6
 
7
  router = APIRouter()
8
 
9
+ api_key_header = APIKeyHeader(name="X-API-Key")
10
+
11
+
12
+ async def verify_api_key(request: Request, api_key: str = Security(api_key_header)):
13
+ if api_key != request.app.state.settings.api_key:
14
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Clé API invalide")
15
+
16
  SAT_COLS = [
17
  "satisfaction_employee_environnement",
18
  "satisfaction_employee_nature_travail",
 
44
  return {"status": "ok", "message": "API opérationnelle"}
45
 
46
 
47
+ @router.get("/model/info", summary="Informations sur le modèle", description="Retourne les métadonnées du modèle déployé", dependencies=[Depends(verify_api_key)])
48
  def model_info(request: Request):
49
  return {
50
  "algorithme": "Régression Logistique",
 
53
  }
54
 
55
 
56
+ @router.post("/predict", response_model=PredictionOutput, summary="Prédiction du risque de départ", description="Envoie les features RH d'un employé et reçoit une prédiction", dependencies=[Depends(verify_api_key)])
57
  def predict_churn(data: PredictionInput, request: Request):
58
  pipeline = request.app.state.pipeline
59
  threshold = request.app.state.threshold