feat: frontend JWT handling, rate limiting [H-02], CORS config
Frontend: - Creiez frontend/lib/auth.ts cu saveToken, getToken, getAuthHeader, clearAuth - Modific api.ts: axiosInstance cu interceptor Bearer token + 401 → /login redirect - Modific login page: salveaza JWT token din response Backend: - [H-02] Integrez slowapi rate limiting: 10 req/minute pe /items/extract-label - [M-01] CORS: ALLOWED_ORIGINS din env (dev fallback: localhost:3000, localhost:3002) - [C-01] JWT_SECRET_KEY din env (dev fallback: ephemeral key) docker-compose.yml: - Adaug ALLOWED_ORIGINS env var (dev: localhost) - Adaug JWT_SECRET_KEY env var cu fallback warning Status: - ✅ JWT backend: complet - ✅ JWT frontend: token save + attach + 401 handling - ✅ Rate limiting: 10/min pe extract-label - ✅ CORS: configurable via env Gata pentru dev + testing local. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import os
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
from . import models
|
||||
from .database import engine
|
||||
from .routers import items, operations, users, categories
|
||||
@@ -13,17 +15,26 @@ log.info("Database tables verified.")
|
||||
app = FastAPI(title="TFM aInventory API", version="1.1.0")
|
||||
log.info("TFM aInventory API process started.")
|
||||
|
||||
# [H-02] Rate limiting pe API
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
app.state.limiter = limiter
|
||||
app.add_exception_handler = limiter.add_exception_handler
|
||||
|
||||
# [SECURITY FIX M-01] CORS: allow_origins=["*"] + allow_credentials=True este invalid per spec.
|
||||
# Originile permise se configurează via variabila de mediu ALLOWED_ORIGINS (comma-separated).
|
||||
# Fallback sigur: doar localhost pentru development.
|
||||
_raw_origins = os.environ.get("ALLOWED_ORIGINS", "http://localhost:3000,http://localhost:3002")
|
||||
_raw_origins = os.environ.get(
|
||||
"ALLOWED_ORIGINS",
|
||||
"http://localhost:3000,http://localhost:3002"
|
||||
)
|
||||
ALLOWED_ORIGINS = [o.strip() for o in _raw_origins.split(",") if o.strip()]
|
||||
log.info(f"CORS allowed origins: {ALLOWED_ORIGINS}")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Request
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from typing import List
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
from .. import models, schemas, auth
|
||||
from ..database import get_db
|
||||
|
||||
# [H-02] Rate limiter pentru extract-label endpoint
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/items",
|
||||
tags=["Items"]
|
||||
@@ -55,12 +60,13 @@ def read_item(
|
||||
_ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
|
||||
_MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
@limiter.limit("10/minute")
|
||||
@router.post("/extract-label")
|
||||
async def extract_label(
|
||||
file: UploadFile = File(...),
|
||||
current_user: auth.TokenData = Depends(auth.get_current_user)
|
||||
):
|
||||
"""[C-01] Extragere etichetă din imagine — doar utilizatori autentificati. [H-02] Rate limit pe endpoint."""
|
||||
"""[C-01] Extragere etichetă din imagine — doar utilizatori autentificati. [H-02] Rate limit: 10 req/min per IP."""
|
||||
from ..ai_vision import extract_label_info
|
||||
|
||||
# [SECURITY FIX H-03] Validare tip MIME și dimensiune maximă
|
||||
|
||||
Reference in New Issue
Block a user