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:
Daniel Bedeleanu
2026-04-11 13:42:09 +03:00
parent e9ada00497
commit e6ca33f2f0
6 changed files with 181 additions and 55 deletions

View File

@@ -3,6 +3,7 @@
import { useState, useEffect, useRef, memo } from 'react';
import { User, Shield, X, Lock, ChevronRight } from 'lucide-react';
import { inventoryApi } from '@/lib/api';
import { saveToken } from '@/lib/auth';
import { toast, Toaster } from 'react-hot-toast';
import { useRouter } from 'next/navigation';
@@ -20,9 +21,9 @@ export default function LoginPage() {
useEffect(() => {
setMounted(true);
inventoryApi.getUsers().then(setUsers).catch(() => {});
// If already logged in, go home
if (localStorage.getItem('inventory_user')) {
if (localStorage.getItem('inventory_token')) {
router.push('/');
}
}, [router]);
@@ -45,33 +46,29 @@ export default function LoginPage() {
}
try {
const user = await inventoryApi.login({
// [C-01] Login vrânceanu token JWT
const tokenResponse = await inventoryApi.login({
username,
password
});
localStorage.setItem('inventory_user', JSON.stringify(user));
toast.success(`Welcome back, ${user.username}`);
// Save JWT token și user info
saveToken(tokenResponse);
toast.success(`Welcome back, ${tokenResponse.username}`);
// Delay slightly to show toast then redirect
setTimeout(() => {
router.push('/');
}, 500);
} catch (error) {
toast.error(isEnterprise ? "Login failed. Check credentials or group membership." : "Invalid password");
}
};
const handleSelectUser = async (user: any) => {
if (user.username === 'Admin' || user.id > 1) {
setSelectedUserForLogin(user);
return;
}
// Auto-login for passwordless users (if any exist beyond Admin)
localStorage.setItem('inventory_user', JSON.stringify(user));
router.push('/');
// [C-01] Toți userii necesită parolă pentru JWT
setSelectedUserForLogin(user);
};
if (!mounted) return null;