Compare commits

..

No commits in common. "main" and "feat/controlled_swagger_access" have entirely different histories.

3 changed files with 176 additions and 346 deletions

224
api.py
View file

@ -283,15 +283,13 @@ def get_auth_schemes_for_user(swagger_user: dict) -> dict:
"type": "http", "type": "http",
"scheme": "bearer", "scheme": "bearer",
"bearerFormat": "JWT", "bearerFormat": "JWT",
"description": "Authentification JWT pour utilisateurs (POST /auth/login). " "description": "Authentification JWT pour utilisateurs (POST /auth/login)",
"Utilisez SOIT JWT SOIT API Key, pas les deux.",
}, },
"ApiKeyAuth": { "ApiKeyAuth": {
"type": "apiKey", "type": "apiKey",
"in": "header", "in": "header",
"name": "X-API-Key", "name": "X-API-Key",
"description": " Clé API pour intégrations externes (format: sdk_live_xxx). " "description": "Clé API pour intégrations externes (format: sdk_live_xxx)",
"Utilisez SOIT JWT SOIT API Key, pas les deux.",
}, },
} }
@ -302,17 +300,24 @@ def get_auth_schemes_for_user(swagger_user: dict) -> dict:
"type": "http", "type": "http",
"scheme": "bearer", "scheme": "bearer",
"bearerFormat": "JWT", "bearerFormat": "JWT",
"description": "Authentification JWT pour utilisateurs (POST /auth/login). " "description": "Authentification JWT pour utilisateurs (POST /auth/login)",
"Utilisez SOIT JWT SOIT API Key, pas les deux.",
} }
schemes["ApiKeyAuth"] = { if "API Keys Management" in allowed_tags or len(allowed_tags) > 3:
"type": "apiKey", schemes["ApiKeyAuth"] = {
"in": "header", "type": "apiKey",
"name": "X-API-Key", "in": "header",
"description": " Clé API pour intégrations externes (format: sdk_live_xxx). " "name": "X-API-Key",
"Utilisez SOIT JWT SOIT API Key, pas les deux.", "description": "Clé API pour intégrations externes (format: sdk_live_xxx)",
} }
if not schemes:
schemes["HTTPBearer"] = {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Authentification requise",
}
return schemes return schemes
@ -347,10 +352,16 @@ def generate_filtered_openapi_schema(
base_schema["components"]["securitySchemes"] = auth_schemes base_schema["components"]["securitySchemes"] = auth_schemes
base_schema["security"] = [] security_requirements = []
if "HTTPBearer" in auth_schemes:
security_requirements.append({"HTTPBearer": []})
if "ApiKeyAuth" in auth_schemes:
security_requirements.append({"ApiKeyAuth": []})
base_schema["security"] = security_requirements if security_requirements else []
if not allowed_tags: if not allowed_tags:
logger.info("⚙️ Schéma OpenAPI complet (admin)") logger.info(" Schéma OpenAPI complet (admin)")
return base_schema return base_schema
filtered_paths = {} filtered_paths = {}
@ -373,13 +384,6 @@ def generate_filtered_openapi_schema(
operation_tags = operation.get("tags", []) operation_tags = operation.get("tags", [])
if any(tag in allowed_tags for tag in operation_tags): if any(tag in allowed_tags for tag in operation_tags):
operation_security = []
if "HTTPBearer" in auth_schemes:
operation_security.append({"HTTPBearer": []})
if "ApiKeyAuth" in auth_schemes:
operation_security.append({"ApiKeyAuth": []})
operation["security"] = operation_security
filtered_operations[method] = operation filtered_operations[method] = operation
if filtered_operations: if filtered_operations:
@ -396,47 +400,16 @@ def generate_filtered_openapi_schema(
if "components" in base_schema and "schemas" in base_schema["components"]: if "components" in base_schema and "schemas" in base_schema["components"]:
all_schemas = base_schema["components"]["schemas"] all_schemas = base_schema["components"]["schemas"]
filtered_schemas = get_schemas_for_tags(allowed_tags, all_schemas)
referenced_schemas = set()
def extract_schema_refs(obj):
if isinstance(obj, dict):
for key, value in obj.items():
if key == "$ref" and isinstance(value, str):
schema_name = value.split("/")[-1]
referenced_schemas.add(schema_name)
else:
extract_schema_refs(value)
elif isinstance(obj, list):
for item in obj:
extract_schema_refs(item)
extract_schema_refs(filtered_paths)
def add_dependencies(schema_name):
if schema_name not in all_schemas:
return
schema_def = all_schemas[schema_name]
extract_schema_refs(schema_def)
initial_schemas = referenced_schemas.copy()
for schema_name in initial_schemas:
add_dependencies(schema_name)
filtered_schemas = {}
for schema_name in referenced_schemas:
if schema_name in all_schemas:
filtered_schemas[schema_name] = all_schemas[schema_name]
base_schema["components"]["schemas"] = filtered_schemas base_schema["components"]["schemas"] = filtered_schemas
logger.info( logger.info(
f"🔍 Schéma filtré: {len(filtered_paths)} paths, " f" Schéma filtré: {len(filtered_paths)} paths, "
f"{len(filtered_schemas)} schémas, tags: {allowed_tags}" f"{len(filtered_schemas)} schémas, tags: {allowed_tags}"
) )
else: else:
logger.info( logger.info(
f"🔍 Schéma filtré: {len(filtered_paths)} paths, tags: {allowed_tags}" f" Schéma filtré: {len(filtered_paths)} paths, tags: {allowed_tags}"
) )
return base_schema return base_schema
@ -456,23 +429,10 @@ async def custom_openapi_endpoint(request: Request):
username = swagger_user.get("username", "unknown") username = swagger_user.get("username", "unknown")
allowed_tags = swagger_user.get("allowed_tags") allowed_tags = swagger_user.get("allowed_tags")
logger.info(f"📖 OpenAPI demandé par: {username}, tags: {allowed_tags or 'ALL'}") logger.info(f" OpenAPI demandé par: {username}, tags: {allowed_tags or 'ALL'}")
schema = generate_filtered_openapi_schema(app, allowed_tags, swagger_user) schema = generate_filtered_openapi_schema(app, allowed_tags, swagger_user)
scheme = request.url.scheme
forwarded_proto = request.headers.get("X-Forwarded-Proto")
if forwarded_proto:
scheme = forwarded_proto
base_url = str(request.base_url).rstrip("/")
if scheme == "https" and base_url.startswith("http://"):
base_url = base_url.replace("http://", "https://", 1)
schema["servers"] = [{"url": base_url}]
return JSONResponse(content=schema) return JSONResponse(content=schema)
@ -487,31 +447,17 @@ async def custom_swagger_ui(request: Request):
headers={"WWW-Authenticate": 'Basic realm="Swagger UI"'}, headers={"WWW-Authenticate": 'Basic realm="Swagger UI"'},
) )
allowed_tags = swagger_user.get("allowed_tags")
is_restricted = allowed_tags is not None and len(allowed_tags) > 0
swagger_params = {
"persistAuthorization": True,
"displayRequestDuration": True,
"filter": True,
"tryItOutEnabled": True,
"docExpansion": "list",
}
if is_restricted:
swagger_params["preAuthorizeApiKey"] = {
"ApiKeyAuth": {
"name": "X-API-Key",
"schema": {"type": "apiKey", "in": "header", "name": "X-API-Key"},
"value": "",
}
}
return get_swagger_ui_html( return get_swagger_ui_html(
openapi_url="/openapi.json", openapi_url="/openapi.json",
title=f"{app.title} - Documentation", title=f"{app.title} - Documentation",
swagger_favicon_url="https://fastapi.tiangolo.com/img/favicon.png", swagger_favicon_url="https://fastapi.tiangolo.com/img/favicon.png",
swagger_ui_parameters=swagger_params, swagger_ui_parameters={
"persistAuthorization": True,
"displayRequestDuration": True,
"filter": True,
"tryItOutEnabled": True,
"docExpansion": "list", # Meilleure UX
},
) )
@ -550,6 +496,7 @@ app.include_router(entreprises_router)
@app.get("/clients", response_model=List[ClientDetails], tags=["Clients"]) @app.get("/clients", response_model=List[ClientDetails], tags=["Clients"])
async def obtenir_clients( async def obtenir_clients(
query: Optional[str] = Query(None), query: Optional[str] = Query(None),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -563,6 +510,7 @@ async def obtenir_clients(
@app.get("/clients/{code}", response_model=ClientDetails, tags=["Clients"]) @app.get("/clients/{code}", response_model=ClientDetails, tags=["Clients"])
async def lire_client_detail( async def lire_client_detail(
code: str, code: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -585,6 +533,7 @@ async def modifier_client(
code: str, code: str,
client_update: ClientUpdate, client_update: ClientUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -610,6 +559,7 @@ async def modifier_client(
async def ajouter_client( async def ajouter_client(
client: ClientCreate, client: ClientCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -634,6 +584,7 @@ async def ajouter_client(
@app.get("/articles", response_model=List[Article], tags=["Articles"]) @app.get("/articles", response_model=List[Article], tags=["Articles"])
async def rechercher_articles( async def rechercher_articles(
query: Optional[str] = Query(None), query: Optional[str] = Query(None),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -652,6 +603,7 @@ async def rechercher_articles(
) )
async def creer_article( async def creer_article(
article: ArticleCreate, article: ArticleCreate,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -692,6 +644,7 @@ async def creer_article(
async def modifier_article( async def modifier_article(
reference: str = Path(..., description="Référence de l'article à modifier"), reference: str = Path(..., description="Référence de l'article à modifier"),
article: ArticleUpdate = Body(...), article: ArticleUpdate = Body(...),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -735,6 +688,7 @@ async def modifier_article(
@app.get("/articles/{reference}", response_model=Article, tags=["Articles"]) @app.get("/articles/{reference}", response_model=Article, tags=["Articles"])
async def lire_article( async def lire_article(
reference: str = Path(..., description="Référence de l'article"), reference: str = Path(..., description="Référence de l'article"),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -764,6 +718,7 @@ async def lire_article(
@app.post("/devis", response_model=Devis, status_code=201, tags=["Devis"]) @app.post("/devis", response_model=Devis, status_code=201, tags=["Devis"])
async def creer_devis( async def creer_devis(
devis: DevisRequest, devis: DevisRequest,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -803,6 +758,7 @@ async def modifier_devis(
id: str, id: str,
devis_update: DevisUpdate, devis_update: DevisUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -848,6 +804,7 @@ async def modifier_devis(
async def creer_commande( async def creer_commande(
commande: CommandeCreate, commande: CommandeCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -897,6 +854,7 @@ async def modifier_commande(
id: str, id: str,
commande_update: CommandeUpdate, commande_update: CommandeUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -945,6 +903,7 @@ async def lister_devis(
inclure_lignes: bool = Query( inclure_lignes: bool = Query(
True, description="Inclure les lignes de chaque devis" True, description="Inclure les lignes de chaque devis"
), ),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -961,6 +920,7 @@ async def lister_devis(
@app.get("/devis/{id}", tags=["Devis"]) @app.get("/devis/{id}", tags=["Devis"])
async def lire_devis( async def lire_devis(
id: str, id: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -981,6 +941,7 @@ async def lire_devis(
@app.get("/devis/{id}/pdf", tags=["Devis"]) @app.get("/devis/{id}/pdf", tags=["Devis"])
async def telecharger_devis_pdf( async def telecharger_devis_pdf(
id: str, id: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1003,6 +964,7 @@ async def telecharger_document_pdf(
description="Type de document (0=Devis, 10=Commande, 30=Livraison, 60=Facture, 50=Avoir)", description="Type de document (0=Devis, 10=Commande, 30=Livraison, 60=Facture, 50=Avoir)",
), ),
numero: str = Path(..., description="Numéro du document"), numero: str = Path(..., description="Numéro du document"),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1059,6 +1021,7 @@ async def envoyer_devis_email(
id: str, id: str,
request: EmailEnvoi, request: EmailEnvoi,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1114,6 +1077,7 @@ async def changer_statut_document(
nouveau_statut: int = Query( nouveau_statut: int = Query(
..., ge=0, le=6, description="0=Saisi, 1=Confirmé, 2=Accepté" ..., ge=0, le=6, description="0=Saisi, 1=Confirmé, 2=Accepté"
), ),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
document_type_sql = None document_type_sql = None
@ -1230,6 +1194,7 @@ async def changer_statut_document(
@app.get("/commandes/{id}", tags=["Commandes"]) @app.get("/commandes/{id}", tags=["Commandes"])
async def lire_commande( async def lire_commande(
id: str, id: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1248,6 +1213,7 @@ async def lire_commande(
async def lister_commandes( async def lister_commandes(
limit: int = Query(100, le=1000), limit: int = Query(100, le=1000),
statut: Optional[int] = Query(None), statut: Optional[int] = Query(None),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1263,6 +1229,7 @@ async def lister_commandes(
async def devis_vers_commande( async def devis_vers_commande(
id: str, id: str,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1307,6 +1274,7 @@ async def devis_vers_commande(
async def commande_vers_facture( async def commande_vers_facture(
id: str, id: str,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1408,6 +1376,7 @@ async def envoyer_emails_lot(
async def valider_remise( async def valider_remise(
client_id: str = Query(..., min_length=1), client_id: str = Query(..., min_length=1),
remise_pourcentage: float = Query(0.0, ge=0, le=100), remise_pourcentage: float = Query(0.0, ge=0, le=100),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1441,6 +1410,7 @@ async def relancer_devis_signature(
id: str, id: str,
relance: RelanceDevis, relance: RelanceDevis,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1507,6 +1477,7 @@ class ContactClientResponse(BaseModel):
@app.get("/devis/{id}/contact", response_model=ContactClientResponse, tags=["Devis"]) @app.get("/devis/{id}/contact", response_model=ContactClientResponse, tags=["Devis"])
async def recuperer_contact_devis( async def recuperer_contact_devis(
id: str, id: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1534,6 +1505,7 @@ async def recuperer_contact_devis(
async def lister_factures( async def lister_factures(
limit: int = Query(100, le=1000), limit: int = Query(100, le=1000),
statut: Optional[int] = Query(None), statut: Optional[int] = Query(None),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1548,6 +1520,7 @@ async def lister_factures(
@app.get("/factures/{numero}", tags=["Factures"]) @app.get("/factures/{numero}", tags=["Factures"])
async def lire_facture_detail( async def lire_facture_detail(
numero: str, numero: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1574,6 +1547,7 @@ class RelanceFacture(BaseModel):
async def creer_facture( async def creer_facture(
facture: FactureCreate, facture: FactureCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1623,6 +1597,7 @@ async def modifier_facture(
id: str, id: str,
facture_update: FactureUpdate, facture_update: FactureUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1692,6 +1667,7 @@ async def relancer_facture(
id: str, id: str,
relance: RelanceFacture, relance: RelanceFacture,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1762,6 +1738,7 @@ async def journal_emails(
destinataire: Optional[str] = Query(None), destinataire: Optional[str] = Query(None),
limit: int = Query(100, le=1000), limit: int = Query(100, le=1000),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
query = select(EmailLog) query = select(EmailLog)
@ -1797,6 +1774,7 @@ async def journal_emails(
async def exporter_logs_csv( async def exporter_logs_csv(
statut: Optional[StatutEmail] = Query(None), statut: Optional[StatutEmail] = Query(None),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
query = select(EmailLog) query = select(EmailLog)
@ -1866,7 +1844,9 @@ class TemplatePreview(BaseModel):
@app.get("/templates/emails", response_model=List[TemplateEmail], tags=["Emails"]) @app.get("/templates/emails", response_model=List[TemplateEmail], tags=["Emails"])
async def lister_templates(): async def lister_templates(
user: User = Depends(get_current_user),
):
return [TemplateEmail(**template) for template in templates_email_db.values()] return [TemplateEmail(**template) for template in templates_email_db.values()]
@ -1875,6 +1855,7 @@ async def lister_templates():
) )
async def lire_template( async def lire_template(
template_id: str, template_id: str,
user: User = Depends(get_current_user),
): ):
if template_id not in templates_email_db: if template_id not in templates_email_db:
raise HTTPException(404, f"Template {template_id} introuvable") raise HTTPException(404, f"Template {template_id} introuvable")
@ -1885,6 +1866,7 @@ async def lire_template(
@app.post("/templates/emails", response_model=TemplateEmail, tags=["Emails"]) @app.post("/templates/emails", response_model=TemplateEmail, tags=["Emails"])
async def creer_template( async def creer_template(
template: TemplateEmail, template: TemplateEmail,
user: User = Depends(get_current_user),
): ):
template_id = str(uuid.uuid4()) template_id = str(uuid.uuid4())
@ -1907,6 +1889,7 @@ async def creer_template(
async def modifier_template( async def modifier_template(
template_id: str, template_id: str,
template: TemplateEmail, template: TemplateEmail,
user: User = Depends(get_current_user),
): ):
if template_id not in templates_email_db: if template_id not in templates_email_db:
raise HTTPException(404, f"Template {template_id} introuvable") raise HTTPException(404, f"Template {template_id} introuvable")
@ -1930,6 +1913,7 @@ async def modifier_template(
@app.delete("/templates/emails/{template_id}", tags=["Emails"]) @app.delete("/templates/emails/{template_id}", tags=["Emails"])
async def supprimer_template( async def supprimer_template(
template_id: str, template_id: str,
user: User = Depends(get_current_user),
): ):
if template_id not in templates_email_db: if template_id not in templates_email_db:
raise HTTPException(404, f"Template {template_id} introuvable") raise HTTPException(404, f"Template {template_id} introuvable")
@ -1947,6 +1931,7 @@ async def supprimer_template(
@app.post("/templates/emails/preview", tags=["Emails"]) @app.post("/templates/emails/preview", tags=["Emails"])
async def previsualiser_email( async def previsualiser_email(
preview: TemplatePreview, preview: TemplatePreview,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
if preview.template_id not in templates_email_db: if preview.template_id not in templates_email_db:
@ -1985,6 +1970,7 @@ async def previsualiser_email(
@app.get("/prospects", tags=["Prospects"]) @app.get("/prospects", tags=["Prospects"])
async def rechercher_prospects( async def rechercher_prospects(
query: Optional[str] = Query(None), query: Optional[str] = Query(None),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -1998,6 +1984,7 @@ async def rechercher_prospects(
@app.get("/prospects/{code}", tags=["Prospects"]) @app.get("/prospects/{code}", tags=["Prospects"])
async def lire_prospect( async def lire_prospect(
code: str, code: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2017,6 +2004,7 @@ async def lire_prospect(
) )
async def rechercher_fournisseurs( async def rechercher_fournisseurs(
query: Optional[str] = Query(None), query: Optional[str] = Query(None),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2038,6 +2026,7 @@ async def rechercher_fournisseurs(
async def ajouter_fournisseur( async def ajouter_fournisseur(
fournisseur: FournisseurCreate, fournisseur: FournisseurCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2067,6 +2056,7 @@ async def modifier_fournisseur(
code: str, code: str,
fournisseur_update: FournisseurUpdate, fournisseur_update: FournisseurUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2089,6 +2079,7 @@ async def modifier_fournisseur(
@app.get("/fournisseurs/{code}", tags=["Fournisseurs"]) @app.get("/fournisseurs/{code}", tags=["Fournisseurs"])
async def lire_fournisseur( async def lire_fournisseur(
code: str, code: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2107,6 +2098,7 @@ async def lire_fournisseur(
async def lister_avoirs( async def lister_avoirs(
limit: int = Query(100, le=1000), limit: int = Query(100, le=1000),
statut: Optional[int] = Query(None), statut: Optional[int] = Query(None),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2120,6 +2112,7 @@ async def lister_avoirs(
@app.get("/avoirs/{numero}", tags=["Avoirs"]) @app.get("/avoirs/{numero}", tags=["Avoirs"])
async def lire_avoir( async def lire_avoir(
numero: str, numero: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2138,6 +2131,7 @@ async def lire_avoir(
async def creer_avoir( async def creer_avoir(
avoir: AvoirCreate, avoir: AvoirCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2185,6 +2179,7 @@ async def modifier_avoir(
id: str, id: str,
avoir_update: AvoirUpdate, avoir_update: AvoirUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2230,6 +2225,7 @@ async def modifier_avoir(
async def lister_livraisons( async def lister_livraisons(
limit: int = Query(100, le=1000), limit: int = Query(100, le=1000),
statut: Optional[int] = Query(None), statut: Optional[int] = Query(None),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2243,6 +2239,7 @@ async def lister_livraisons(
@app.get("/livraisons/{numero}", tags=["Livraisons"]) @app.get("/livraisons/{numero}", tags=["Livraisons"])
async def lire_livraison( async def lire_livraison(
numero: str, numero: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2261,6 +2258,7 @@ async def lire_livraison(
async def creer_livraison( async def creer_livraison(
livraison: LivraisonCreate, livraison: LivraisonCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2314,6 +2312,7 @@ async def modifier_livraison(
id: str, id: str,
livraison_update: LivraisonUpdate, livraison_update: LivraisonUpdate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2359,6 +2358,7 @@ async def modifier_livraison(
async def livraison_vers_facture( async def livraison_vers_facture(
id: str, id: str,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2402,6 +2402,7 @@ async def livraison_vers_facture(
async def devis_vers_facture_direct( async def devis_vers_facture_direct(
id: str, id: str,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2462,6 +2463,7 @@ async def devis_vers_facture_direct(
async def commande_vers_livraison( async def commande_vers_livraison(
id: str, id: str,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2533,6 +2535,7 @@ async def commande_vers_livraison(
) )
async def lister_familles( async def lister_familles(
filtre: Optional[str] = Query(None, description="Filtre sur code ou intitulé"), filtre: Optional[str] = Query(None, description="Filtre sur code ou intitulé"),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2558,6 +2561,7 @@ async def lister_familles(
) )
async def lire_famille( async def lire_famille(
code: str = Path(..., description="Code de la famille (ex: ZDIVERS)"), code: str = Path(..., description="Code de la famille (ex: ZDIVERS)"),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2593,6 +2597,7 @@ async def lire_famille(
) )
async def creer_famille( async def creer_famille(
famille: FamilleCreate, famille: FamilleCreate,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2636,6 +2641,7 @@ async def creer_famille(
) )
async def creer_entree_stock( async def creer_entree_stock(
entree: EntreeStock, entree: EntreeStock,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2672,6 +2678,7 @@ async def creer_entree_stock(
) )
async def creer_sortie_stock( async def creer_sortie_stock(
sortie: SortieStock, sortie: SortieStock,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2707,6 +2714,7 @@ async def creer_sortie_stock(
) )
async def lire_mouvement_stock( async def lire_mouvement_stock(
numero: str = Path(..., description="Numéro du mouvement (ex: ME00123 ou MS00124)"), numero: str = Path(..., description="Numéro du mouvement (ex: ME00123 ou MS00124)"),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2739,6 +2747,7 @@ async def lire_mouvement_stock(
summary="Statistiques sur les familles", summary="Statistiques sur les familles",
) )
async def statistiques_familles( async def statistiques_familles(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2760,6 +2769,7 @@ async def lister_utilisateurs_debug(
limit: int = Query(100, le=1000), limit: int = Query(100, le=1000),
role: Optional[str] = Query(None), role: Optional[str] = Query(None),
verified_only: bool = Query(False), verified_only: bool = Query(False),
user: User = Depends(get_current_user),
): ):
from database import User from database import User
from sqlalchemy import select from sqlalchemy import select
@ -2846,6 +2856,7 @@ async def statistiques_utilisateurs(session: AsyncSession = Depends(get_session)
async def creer_contact( async def creer_contact(
numero: str, numero: str,
contact: ContactCreate, contact: ContactCreate,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2878,6 +2889,7 @@ async def creer_contact(
@app.get("/tiers/{numero}/contacts", response_model=List[Contact], tags=["Contacts"]) @app.get("/tiers/{numero}/contacts", response_model=List[Contact], tags=["Contacts"])
async def lister_contacts( async def lister_contacts(
numero: str, numero: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2896,6 +2908,7 @@ async def lister_contacts(
async def obtenir_contact( async def obtenir_contact(
numero: str, numero: str,
contact_numero: int, contact_numero: int,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2921,6 +2934,7 @@ async def modifier_contact(
numero: str, numero: str,
contact_numero: int, contact_numero: int,
contact: ContactUpdate, contact: ContactUpdate,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2952,6 +2966,7 @@ async def modifier_contact(
async def supprimer_contact( async def supprimer_contact(
numero: str, numero: str,
contact_numero: int, contact_numero: int,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2966,6 +2981,7 @@ async def supprimer_contact(
async def definir_contact_defaut( async def definir_contact_defaut(
numero: str, numero: str,
contact_numero: int, contact_numero: int,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -2987,6 +3003,7 @@ async def obtenir_tiers(
description="Filtre par type: 0/client, 1/fournisseur, 2/prospect, 3/all ou strings", description="Filtre par type: 0/client, 1/fournisseur, 2/prospect, 3/all ou strings",
), ),
query: Optional[str] = Query(None, description="Recherche sur code ou intitulé"), query: Optional[str] = Query(None, description="Recherche sur code ou intitulé"),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3001,6 +3018,7 @@ async def obtenir_tiers(
@app.get("/tiers/{code}", response_model=TiersDetails, tags=["Tiers"]) @app.get("/tiers/{code}", response_model=TiersDetails, tags=["Tiers"])
async def lire_tiers_detail( async def lire_tiers_detail(
code: str, code: str,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3038,6 +3056,7 @@ async def lister_collaborateurs(
actifs_seulement: bool = Query( actifs_seulement: bool = Query(
True, description="Exclure les collaborateurs en sommeil" True, description="Exclure les collaborateurs en sommeil"
), ),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Liste tous les collaborateurs""" """Liste tous les collaborateurs"""
@ -3056,6 +3075,7 @@ async def lister_collaborateurs(
) )
async def lire_collaborateur_detail( async def lire_collaborateur_detail(
numero: int, numero: int,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Lit un collaborateur par son numéro""" """Lit un collaborateur par son numéro"""
@ -3082,6 +3102,7 @@ async def lire_collaborateur_detail(
) )
async def creer_collaborateur( async def creer_collaborateur(
collaborateur: CollaborateurCreate, collaborateur: CollaborateurCreate,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Crée un nouveau collaborateur""" """Crée un nouveau collaborateur"""
@ -3108,6 +3129,7 @@ async def creer_collaborateur(
async def modifier_collaborateur( async def modifier_collaborateur(
numero: int, numero: int,
collaborateur: CollaborateurUpdate, collaborateur: CollaborateurUpdate,
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Modifie un collaborateur existant""" """Modifie un collaborateur existant"""
@ -3130,6 +3152,7 @@ async def modifier_collaborateur(
@app.get("/societe/info", response_model=SocieteInfo, tags=["Société"]) @app.get("/societe/info", response_model=SocieteInfo, tags=["Société"])
async def obtenir_informations_societe( async def obtenir_informations_societe(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3149,6 +3172,7 @@ async def obtenir_informations_societe(
@app.get("/societe/logo", tags=["Société"]) @app.get("/societe/logo", tags=["Société"])
async def obtenir_logo_societe( async def obtenir_logo_societe(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Retourne le logo en tant qu'image directe""" """Retourne le logo en tant qu'image directe"""
@ -3173,6 +3197,7 @@ async def obtenir_logo_societe(
@app.get("/societe/preview", response_class=HTMLResponse, tags=["Société"]) @app.get("/societe/preview", response_class=HTMLResponse, tags=["Société"])
async def preview_societe( async def preview_societe(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Page HTML pour visualiser les infos société avec logo""" """Page HTML pour visualiser les infos société avec logo"""
@ -3246,6 +3271,7 @@ async def preview_societe(
async def valider_facture( async def valider_facture(
numero_facture: str, numero_facture: str,
_: AsyncSession = Depends(get_session), _: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3269,6 +3295,7 @@ async def valider_facture(
async def devalider_facture( async def devalider_facture(
numero_facture: str, numero_facture: str,
_: AsyncSession = Depends(get_session), _: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3292,6 +3319,7 @@ async def devalider_facture(
async def get_statut_validation_facture( async def get_statut_validation_facture(
numero_facture: str, numero_facture: str,
_: AsyncSession = Depends(get_session), _: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3312,6 +3340,7 @@ async def regler_facture(
numero_facture: str, numero_facture: str,
reglement: ReglementFactureCreate, reglement: ReglementFactureCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3355,6 +3384,7 @@ async def regler_facture(
async def regler_factures_multiple( async def regler_factures_multiple(
reglement: ReglementMultipleCreate, reglement: ReglementMultipleCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3393,6 +3423,7 @@ async def regler_factures_multiple(
async def get_reglements_facture( async def get_reglements_facture(
numero_facture: str, numero_facture: str,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3417,6 +3448,7 @@ async def get_reglements_client(
date_fin: Optional[datetime] = Query(None, description="Date fin"), date_fin: Optional[datetime] = Query(None, description="Date fin"),
inclure_soldees: bool = Query(True, description="Inclure les factures soldées"), inclure_soldees: bool = Query(True, description="Inclure les factures soldées"),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3441,6 +3473,7 @@ async def get_reglements_client(
@app.get("/journaux/banque", tags=["Règlements"]) @app.get("/journaux/banque", tags=["Règlements"])
async def get_journaux_banque( async def get_journaux_banque(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
try: try:
@ -3453,6 +3486,7 @@ async def get_journaux_banque(
@app.get("/reglements/modes", tags=["Référentiels"]) @app.get("/reglements/modes", tags=["Référentiels"])
async def get_modes_reglement( async def get_modes_reglement(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Liste des modes de règlement disponibles dans Sage""" """Liste des modes de règlement disponibles dans Sage"""
@ -3466,6 +3500,7 @@ async def get_modes_reglement(
@app.get("/devises", tags=["Référentiels"]) @app.get("/devises", tags=["Référentiels"])
async def get_devises( async def get_devises(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Liste des devises disponibles dans Sage""" """Liste des devises disponibles dans Sage"""
@ -3479,6 +3514,7 @@ async def get_devises(
@app.get("/journaux/tresorerie", tags=["Référentiels"]) @app.get("/journaux/tresorerie", tags=["Référentiels"])
async def get_journaux_tresorerie( async def get_journaux_tresorerie(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Liste des journaux de trésorerie (banque + caisse)""" """Liste des journaux de trésorerie (banque + caisse)"""
@ -3497,6 +3533,7 @@ async def get_comptes_generaux(
None, None,
description="client | fournisseur | banque | caisse | tva | produit | charge", description="client | fournisseur | banque | caisse | tva | produit | charge",
), ),
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Liste des comptes généraux""" """Liste des comptes généraux"""
@ -3510,6 +3547,7 @@ async def get_comptes_generaux(
@app.get("/tva/taux", tags=["Référentiels"]) @app.get("/tva/taux", tags=["Référentiels"])
async def get_tva_taux( async def get_tva_taux(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Liste des taux de TVA""" """Liste des taux de TVA"""
@ -3523,6 +3561,7 @@ async def get_tva_taux(
@app.get("/parametres/encaissement", tags=["Référentiels"]) @app.get("/parametres/encaissement", tags=["Référentiels"])
async def get_parametres_encaissement( async def get_parametres_encaissement(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
"""Paramètres TVA sur encaissement""" """Paramètres TVA sur encaissement"""
@ -3569,6 +3608,7 @@ async def get_reglement_detail(rg_no):
""" @app.get("/health", tags=["System"]) """ @app.get("/health", tags=["System"])
async def health_check( async def health_check(
user: User = Depends(get_current_user),
sage: SageGatewayClient = Depends(get_sage_client_for_user), sage: SageGatewayClient = Depends(get_sage_client_for_user),
): ):
gateway_health = sage.health() gateway_health = sage.health()

View file

@ -158,33 +158,28 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware):
auth_header = request.headers.get("Authorization") auth_header = request.headers.get("Authorization")
api_key_header = request.headers.get("X-API-Key") api_key_header = request.headers.get("X-API-Key")
if api_key_header:
api_key_header = api_key_header.strip()
if not api_key_header or api_key_header == "":
api_key_header = None
if auth_header and auth_header.startswith("Bearer "):
token = auth_header.split(" ", 1)[1].strip()
if token.startswith("sdk_live_"):
logger.warning(
" API Key envoyée dans Authorization au lieu de X-API-Key"
)
return await self._handle_api_key_auth(
request, token, path, method, call_next
)
logger.debug(f"JWT détecté pour {method} {path} → délégation à FastAPI")
request.state.authenticated_via = "jwt"
return await call_next(request)
if api_key_header: if api_key_header:
logger.debug(f" API Key détectée pour {method} {path}") logger.debug(f" API Key détectée pour {method} {path}")
return await self._handle_api_key_auth( return await self._handle_api_key_auth(
request, api_key_header, path, method, call_next request, api_key_header, path, method, call_next
) )
logger.debug(f" Aucune auth pour {method} {path} → délégation à FastAPI") if auth_header and auth_header.startswith("Bearer "):
token = auth_header.split(" ")[1]
if token.startswith("sdk_live_"):
logger.warning(
"⚠️ API Key envoyée dans Authorization au lieu de X-API-Key"
)
return await self._handle_api_key_auth(
request, token, path, method, call_next
)
logger.debug(f"🎫 JWT détecté pour {method} {path} → délégation à FastAPI")
request.state.authenticated_via = "jwt"
return await call_next(request)
logger.debug(f"❓ Aucune auth pour {method} {path} → délégation à FastAPI")
return await call_next(request) return await call_next(request)
async def _handle_api_key_auth( async def _handle_api_key_auth(
@ -205,7 +200,7 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware):
api_key_obj = await service.verify_api_key(api_key) api_key_obj = await service.verify_api_key(api_key)
if not api_key_obj: if not api_key_obj:
logger.warning(f"🔒 Clé API invalide: {method} {path}") logger.warning(f" Clé API invalide: {method} {path}")
return JSONResponse( return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
content={ content={

View file

@ -1,3 +1,9 @@
#!/usr/bin/env python3
"""
Script de gestion avancée des utilisateurs Swagger et API Keys
avec configuration des schémas d'authentification
"""
import sys import sys
import os import os
from pathlib import Path from pathlib import Path
@ -13,35 +19,12 @@ _current_file = Path(__file__).resolve()
_script_dir = _current_file.parent _script_dir = _current_file.parent
_app_dir = _script_dir.parent _app_dir = _script_dir.parent
print(f"DEBUG: Script path: {_current_file}")
print(f"DEBUG: App dir: {_app_dir}")
print(f"DEBUG: Current working dir: {os.getcwd()}")
if str(_app_dir) in sys.path: if str(_app_dir) in sys.path:
sys.path.remove(str(_app_dir)) sys.path.remove(str(_app_dir))
sys.path.insert(0, str(_app_dir)) sys.path.insert(0, str(_app_dir))
os.chdir(str(_app_dir)) os.chdir(str(_app_dir))
print(f"DEBUG: sys.path[0]: {sys.path[0]}")
print(f"DEBUG: New working dir: {os.getcwd()}")
_test_imports = [
"database",
"database.db_config",
"database.models",
"services",
"security",
]
print("\nDEBUG: Vérification des imports...")
for module in _test_imports:
try:
__import__(module)
print(f"{module}")
except ImportError as e:
print(f"{module}: {e}")
try: try:
from database.db_config import async_session_factory from database.db_config import async_session_factory
from database.models.api_key import SwaggerUser, ApiKey from database.models.api_key import SwaggerUser, ApiKey
@ -50,7 +33,6 @@ try:
except ImportError as e: except ImportError as e:
print(f"\n ERREUR D'IMPORT: {e}") print(f"\n ERREUR D'IMPORT: {e}")
print(" Vérifiez que vous êtes dans /app") print(" Vérifiez que vous êtes dans /app")
print(" Commande correcte: cd /app && python scripts/manage_security.py ...")
sys.exit(1) sys.exit(1)
logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
@ -58,8 +40,8 @@ logger = logging.getLogger(__name__)
AVAILABLE_TAGS = { AVAILABLE_TAGS = {
"Authentication": " Authentification et gestion des comptes", "Authentication": "🔐 Authentification et gestion des comptes",
"API Keys Management": "🔑 Gestion des clés API", "API Keys Management": " Gestion des clés API",
"Clients": "👥 Gestion des clients", "Clients": "👥 Gestion des clients",
"Fournisseurs": "🏭 Gestion des fournisseurs", "Fournisseurs": "🏭 Gestion des fournisseurs",
"Prospects": "🎯 Gestion des prospects", "Prospects": "🎯 Gestion des prospects",
@ -74,13 +56,13 @@ AVAILABLE_TAGS = {
"Factures": "💰 Factures", "Factures": "💰 Factures",
"Avoirs": "↩️ Avoirs", "Avoirs": "↩️ Avoirs",
"Règlements": "💳 Règlements et encaissements", "Règlements": "💳 Règlements et encaissements",
"Workflows": " Transformations de documents", "Workflows": "🔄 Transformations de documents",
"Documents": "📑 Gestion documents (PDF)", "Documents": "📑 Gestion documents (PDF)",
"Emails": "📧 Envoi d'emails", "Emails": "📧 Envoi d'emails",
"Validation": " Validations métier", "Validation": " Validations métier",
"Collaborateurs": "👔 Collaborateurs internes", "Collaborateurs": "👔 Collaborateurs internes",
"Société": "🏢 Informations société", "Société": "🏢 Informations société",
"Référentiels": "📚 Données de référence", "Référentiels": " Données de référence",
"System": "⚙️ Système et santé", "System": "⚙️ Système et santé",
"Admin": "🛠️ Administration", "Admin": "🛠️ Administration",
"Debug": "🐛 Debug et diagnostics", "Debug": "🐛 Debug et diagnostics",
@ -217,16 +199,16 @@ async def list_swagger_users():
auth_schemes.append("JWT (Bearer)") auth_schemes.append("JWT (Bearer)")
logger.info( logger.info(
f" Authentification autorisée: {', '.join(auth_schemes)}" f"🔐 Authentification autorisée: {', '.join(auth_schemes)}"
) )
else: else:
logger.info("👑 Tags autorisés: ADMIN COMPLET (tous)") logger.info("👑 Tags autorisés: ADMIN COMPLET (tous)")
logger.info(" Authentification: JWT + X-API-Key (tout)") logger.info("🔐 Authentification: JWT + X-API-Key (tout)")
except json.JSONDecodeError: except json.JSONDecodeError:
logger.info(" Tags: Erreur format") logger.info("⚠️ Tags: Erreur format")
else: else:
logger.info("👑 Tags autorisés: ADMIN COMPLET (tous)") logger.info("👑 Tags autorisés: ADMIN COMPLET (tous)")
logger.info(" Authentification: JWT + X-API-Key (tout)") logger.info("🔐 Authentification: JWT + X-API-Key (tout)")
logger.info("\n" + "=" * 80) logger.info("\n" + "=" * 80)
@ -262,7 +244,7 @@ async def update_swagger_user(
elif set_tags is not None: elif set_tags is not None:
user.allowed_tags = json.dumps(set_tags) if set_tags else None user.allowed_tags = json.dumps(set_tags) if set_tags else None
logger.info(f" Tags remplacés: {len(set_tags) if set_tags else 0}") logger.info(f"🔄 Tags remplacés: {len(set_tags) if set_tags else 0}")
modified = True modified = True
elif add_tags or remove_tags: elif add_tags or remove_tags:
@ -291,36 +273,19 @@ async def update_swagger_user(
if active is not None: if active is not None:
user.is_active = active user.is_active = active
logger.info(f" Statut: {'ACTIF' if active else 'INACTIF'}") logger.info(f"🔄 Statut: {'ACTIF' if active else 'INACTIF'}")
modified = True modified = True
if modified: if modified:
await session.commit() await session.commit()
logger.info(f" Utilisateur '{username}' mis à jour") logger.info(f" Utilisateur '{username}' mis à jour")
else: else:
logger.info(" Aucune modification effectuée") logger.info(" Aucune modification effectuée")
async def delete_swagger_user(username: str):
"""Supprimer un utilisateur Swagger"""
async with async_session_factory() as session:
result = await session.execute(
select(SwaggerUser).where(SwaggerUser.username == username)
)
user = result.scalar_one_or_none()
if not user:
logger.error(f" Utilisateur '{username}' introuvable")
return
await session.delete(user)
await session.commit()
logger.info(f"🗑️ Utilisateur Swagger supprimé: {username}")
async def list_available_tags(): async def list_available_tags():
"""Liste tous les tags disponibles avec description""" """Liste tous les tags disponibles avec description"""
logger.info("\n🏷️ TAGS DISPONIBLES:\n") logger.info("\n TAGS DISPONIBLES:\n")
logger.info("=" * 80) logger.info("=" * 80)
for tag, desc in AVAILABLE_TAGS.items(): for tag, desc in AVAILABLE_TAGS.items():
@ -337,135 +302,20 @@ async def list_available_tags():
logger.info("=" * 80) logger.info("=" * 80)
async def create_api_key( async def delete_swagger_user(username: str):
name: str,
description: str = None,
expires_in_days: int = 365,
rate_limit: int = 60,
endpoints: list = None,
):
"""Créer une clé API"""
async with async_session_factory() as session: async with async_session_factory() as session:
service = ApiKeyService(session) result = await session.execute(
select(SwaggerUser).where(SwaggerUser.username == username)
api_key_obj, api_key_plain = await service.create_api_key(
name=name,
description=description,
created_by="cli",
expires_in_days=expires_in_days,
rate_limit_per_minute=rate_limit,
allowed_endpoints=endpoints,
) )
user = result.scalar_one_or_none()
logger.info("=" * 70) if not user:
logger.info("🔑 Clé API créée avec succès") logger.error(f" Utilisateur '{username}' introuvable")
logger.info("=" * 70)
logger.info(f" ID: {api_key_obj.id}")
logger.info(f" Nom: {api_key_obj.name}")
logger.info(f" Clé: {api_key_plain}")
logger.info(f" Préfixe: {api_key_obj.key_prefix}")
logger.info(f" Rate limit: {api_key_obj.rate_limit_per_minute} req/min")
logger.info(f" Expire le: {api_key_obj.expires_at}")
if api_key_obj.allowed_endpoints:
try:
endpoints_list = json.loads(api_key_obj.allowed_endpoints)
logger.info(f" Endpoints: {', '.join(endpoints_list)}")
except Exception:
logger.info(f" Endpoints: {api_key_obj.allowed_endpoints}")
else:
logger.info(" Endpoints: Tous (aucune restriction)")
logger.info("=" * 70)
logger.info(" SAUVEGARDEZ CETTE CLÉ - Elle ne sera plus affichée !")
logger.info("=" * 70)
async def list_api_keys():
"""Lister toutes les clés API"""
async with async_session_factory() as session:
service = ApiKeyService(session)
keys = await service.list_api_keys()
if not keys:
logger.info("🔭 Aucune clé API")
return return
logger.info(f"🔑 {len(keys)} clé(s) API:\n") await session.delete(user)
for key in keys:
is_valid = key.is_active and (
not key.expires_at or key.expires_at > datetime.now()
)
status = "" if is_valid else ""
logger.info(f" {status} {key.name:<30} ({key.key_prefix}...)")
logger.info(f" ID: {key.id}")
logger.info(f" Rate limit: {key.rate_limit_per_minute} req/min")
logger.info(f" Requêtes: {key.total_requests}")
logger.info(f" Expire: {key.expires_at or 'Jamais'}")
logger.info(f" Dernière utilisation: {key.last_used_at or 'Jamais'}")
if key.allowed_endpoints:
try:
endpoints = json.loads(key.allowed_endpoints)
display = ", ".join(endpoints[:4])
if len(endpoints) > 4:
display += f"... (+{len(endpoints) - 4})"
logger.info(f" Endpoints: {display}")
except Exception:
pass
else:
logger.info(" Endpoints: Tous")
logger.info("")
async def revoke_api_key(key_id: str):
"""Révoquer une clé API"""
async with async_session_factory() as session:
result = await session.execute(select(ApiKey).where(ApiKey.id == key_id))
key = result.scalar_one_or_none()
if not key:
logger.error(f" Clé API '{key_id}' introuvable")
return
key.is_active = False
key.revoked_at = datetime.now()
await session.commit() await session.commit()
logger.info(f"🗑️ Utilisateur Swagger supprimé: {username}")
logger.info(f"🗑️ Clé API révoquée: {key.name}")
logger.info(f" ID: {key.id}")
async def verify_api_key(api_key: str):
"""Vérifier une clé API"""
async with async_session_factory() as session:
service = ApiKeyService(session)
key = await service.verify_api_key(api_key)
if not key:
logger.error(" Clé API invalide ou expirée")
return
logger.info("=" * 60)
logger.info(" Clé API valide")
logger.info("=" * 60)
logger.info(f" Nom: {key.name}")
logger.info(f" ID: {key.id}")
logger.info(f" Rate limit: {key.rate_limit_per_minute} req/min")
logger.info(f" Requêtes totales: {key.total_requests}")
logger.info(f" Expire: {key.expires_at or 'Jamais'}")
if key.allowed_endpoints:
try:
endpoints = json.loads(key.allowed_endpoints)
logger.info(f" Endpoints autorisés: {endpoints}")
except Exception:
pass
else:
logger.info(" Endpoints autorisés: Tous")
logger.info("=" * 60)
async def main(): async def main():
@ -475,8 +325,6 @@ async def main():
epilog=""" epilog="""
EXEMPLES D'UTILISATION: EXEMPLES D'UTILISATION:
=== UTILISATEURS SWAGGER ===
1. Créer un utilisateur avec preset: 1. Créer un utilisateur avec preset:
python scripts/manage_security.py swagger add commercial Pass123! --preset commercial python scripts/manage_security.py swagger add commercial Pass123! --preset commercial
@ -500,23 +348,6 @@ python scripts/manage_security.py swagger tags
8. Désactiver temporairement: 8. Désactiver temporairement:
python scripts/manage_security.py swagger update client --inactive python scripts/manage_security.py swagger update client --inactive
=== CLÉS API ===
9. Créer une clé API:
python scripts/manage_security.py apikey create "Mon App" --days 365 --rate-limit 100
10. Créer avec endpoints restreints:
python scripts/manage_security.py apikey create "SDK-ReadOnly" --endpoints "/clients" "/clients/*" "/devis" "/devis/*"
11. Lister les clés:
python scripts/manage_security.py apikey list
12. Vérifier une clé:
python scripts/manage_security.py apikey verify sdk_live_xxxxx
13. Révoquer une clé:
python scripts/manage_security.py apikey revoke <key_id>
""", """,
) )
@ -561,24 +392,6 @@ python scripts/manage_security.py apikey create "Mon App" --days 365 --rate-limi
swagger_sub.add_parser("tags", help="Lister les tags disponibles") swagger_sub.add_parser("tags", help="Lister les tags disponibles")
apikey_parser = subparsers.add_parser("apikey", help="Gestion clés API")
apikey_sub = apikey_parser.add_subparsers(dest="apikey_command")
create_p = apikey_sub.add_parser("create", help="Créer clé API")
create_p.add_argument("name", help="Nom de la clé")
create_p.add_argument("--description", help="Description")
create_p.add_argument("--days", type=int, default=365, help="Expiration (jours)")
create_p.add_argument("--rate-limit", type=int, default=60, help="Req/min")
create_p.add_argument("--endpoints", nargs="+", help="Endpoints autorisés")
apikey_sub.add_parser("list", help="Lister clés")
rev_p = apikey_sub.add_parser("revoke", help="Révoquer clé")
rev_p.add_argument("key_id", help="ID de la clé")
ver_p = apikey_sub.add_parser("verify", help="Vérifier clé")
ver_p.add_argument("api_key", help="Clé API complète")
args = parser.parse_args() args = parser.parse_args()
if not args.command: if not args.command:
@ -618,30 +431,12 @@ python scripts/manage_security.py apikey create "Mon App" --days 365 --rate-limi
else: else:
swagger_parser.print_help() swagger_parser.print_help()
elif args.command == "apikey":
if args.apikey_command == "create":
await create_api_key(
name=args.name,
description=args.description,
expires_in_days=args.days,
rate_limit=args.rate_limit,
endpoints=args.endpoints,
)
elif args.apikey_command == "list":
await list_api_keys()
elif args.apikey_command == "revoke":
await revoke_api_key(args.key_id)
elif args.apikey_command == "verify":
await verify_api_key(args.api_key)
else:
apikey_parser.print_help()
if __name__ == "__main__": if __name__ == "__main__":
try: try:
asyncio.run(main()) asyncio.run(main())
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n Interrupted") print("\n Interrupted")
sys.exit(0) sys.exit(0)
except Exception as e: except Exception as e:
logger.error(f" Erreur: {e}") logger.error(f" Erreur: {e}")