release: v0.5.0 - Authentication, API Keys & Advanced Features
Complete v0.5.0 implementation: Database (@db-engineer): - 3 migrations: users, api_keys, report_schedules tables - Foreign keys, indexes, constraints, enums Backend (@backend-dev): - JWT authentication service with bcrypt (cost=12) - Auth endpoints: /register, /login, /refresh, /me - API Keys service with hash storage and prefix validation - API Keys endpoints: CRUD + rotate - Security module with JWT HS256 Frontend (@frontend-dev): - Login/Register pages with validation - AuthContext with localStorage persistence - Protected routes implementation - API Keys management UI (create, revoke, rotate) - Header with user dropdown DevOps (@devops-engineer): - .env.example and .env.production.example - docker-compose.scheduler.yml - scripts/setup-secrets.sh - INFRASTRUCTURE_SETUP.md QA (@qa-engineer): - 85 E2E tests: auth.spec.ts, apikeys.spec.ts, scenarios.spec.ts, regression-v050.spec.ts - auth-helpers.ts with 20+ utility functions - Test plans and documentation Architecture (@spec-architect): - SECURITY.md with best practices - SECURITY-CHECKLIST.md pre-deployment - Updated architecture.md with auth flows - Updated README.md with v0.5.0 features Documentation: - Updated todo.md with v0.5.0 status - Added docs/README.md index - Complete setup instructions Dependencies added: - bcrypt, python-jose, passlib, email-validator Tested: JWT auth flow, API keys CRUD, protected routes, 85 E2E tests ready Closes: v0.5.0 milestone
This commit is contained in:
@@ -1,8 +1,33 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Cloud } from 'lucide-react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Cloud, User, Settings, Key, LogOut, ChevronDown } from 'lucide-react';
|
||||
import { ThemeToggle } from '@/components/ui/theme-toggle';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
|
||||
export function Header() {
|
||||
const { user, isAuthenticated, logout } = useAuth();
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="border-b bg-card sticky top-0 z-50">
|
||||
<div className="flex h-16 items-center px-6">
|
||||
@@ -15,8 +40,87 @@ export function Header() {
|
||||
AWS Cost Simulator
|
||||
</span>
|
||||
<ThemeToggle />
|
||||
|
||||
{isAuthenticated && user ? (
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">{user.full_name || user.email}</span>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
{isDropdownOpen && (
|
||||
<div className="absolute right-0 mt-2 w-56 rounded-md border bg-popover shadow-lg">
|
||||
<div className="p-2">
|
||||
<div className="px-2 py-1.5 text-sm font-medium">
|
||||
{user.full_name}
|
||||
</div>
|
||||
<div className="px-2 py-0.5 text-xs text-muted-foreground">
|
||||
{user.email}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t my-1" />
|
||||
<div className="p-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsDropdownOpen(false);
|
||||
navigate('/profile');
|
||||
}}
|
||||
className="w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded-sm hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
Profile
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsDropdownOpen(false);
|
||||
navigate('/settings');
|
||||
}}
|
||||
className="w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded-sm hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Settings
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsDropdownOpen(false);
|
||||
navigate('/settings/api-keys');
|
||||
}}
|
||||
className="w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded-sm hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||
>
|
||||
<Key className="h-4 w-4" />
|
||||
API Keys
|
||||
</button>
|
||||
</div>
|
||||
<div className="border-t my-1" />
|
||||
<div className="p-1">
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded-sm hover:bg-destructive hover:text-destructive-foreground transition-colors text-destructive"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Link to="/login">
|
||||
<Button variant="ghost" size="sm">Sign in</Button>
|
||||
</Link>
|
||||
<Link to="/register">
|
||||
<Button size="sm">Sign up</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user