Phase 1: Add role, mfa_enforce_pending, must_change_password to users table. Create system_config (singleton) and audit_log tables. Migration 026. Phase 2: Add user_id FK to all 8 data tables (todos, reminders, projects, calendars, people, locations, event_templates, ntfy_sent) with 4-step nullable→backfill→FK→NOT NULL pattern. Migrations 027-034. Phase 3: Harden auth schemas (extra="forbid" on RegisterRequest), add MFA enforcement token serializer with distinct salt, rewrite auth router with require_role() factory and registration endpoint. Phase 4: Scope all 12 routers by user_id, fix dependency type bugs, bound weather cache (SEC-15), multi-user ntfy dispatch. Phase 5: Create admin router (14 endpoints), admin schemas, audit service, rate limiting in nginx. SEC-08 CSRF via X-Requested-With. Phase 6: Update frontend types, useAuth hook (role/isAdmin/register), App.tsx (AdminRoute guard), Sidebar (admin link), api.ts (XHR header). Security findings addressed: SEC-01, SEC-02, SEC-03, SEC-04, SEC-05, SEC-06, SEC-07, SEC-08, SEC-12, SEC-13, SEC-15. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
92 lines
2.9 KiB
TypeScript
92 lines
2.9 KiB
TypeScript
import { lazy, Suspense } from 'react';
|
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
import { useAuth } from '@/hooks/useAuth';
|
|
import LockScreen from '@/components/auth/LockScreen';
|
|
import AppLayout from '@/components/layout/AppLayout';
|
|
import DashboardPage from '@/components/dashboard/DashboardPage';
|
|
import TodosPage from '@/components/todos/TodosPage';
|
|
import CalendarPage from '@/components/calendar/CalendarPage';
|
|
import RemindersPage from '@/components/reminders/RemindersPage';
|
|
import ProjectsPage from '@/components/projects/ProjectsPage';
|
|
import ProjectDetail from '@/components/projects/ProjectDetail';
|
|
import PeoplePage from '@/components/people/PeoplePage';
|
|
import LocationsPage from '@/components/locations/LocationsPage';
|
|
import SettingsPage from '@/components/settings/SettingsPage';
|
|
|
|
const AdminPortal = lazy(() => import('@/components/admin/AdminPortal'));
|
|
|
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
const { authStatus, isLoading } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-screen items-center justify-center">
|
|
<div className="text-muted-foreground">Loading...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!authStatus?.authenticated) {
|
|
return <Navigate to="/login" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function AdminRoute({ children }: { children: React.ReactNode }) {
|
|
const { authStatus, isLoading } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-screen items-center justify-center">
|
|
<div className="text-muted-foreground">Loading...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!authStatus?.authenticated || authStatus?.role !== 'admin') {
|
|
return <Navigate to="/dashboard" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function App() {
|
|
return (
|
|
<Routes>
|
|
<Route path="/login" element={<LockScreen />} />
|
|
<Route
|
|
path="/"
|
|
element={
|
|
<ProtectedRoute>
|
|
<AppLayout />
|
|
</ProtectedRoute>
|
|
}
|
|
>
|
|
<Route index element={<Navigate to="/dashboard" replace />} />
|
|
<Route path="dashboard" element={<DashboardPage />} />
|
|
<Route path="todos" element={<TodosPage />} />
|
|
<Route path="calendar" element={<CalendarPage />} />
|
|
<Route path="reminders" element={<RemindersPage />} />
|
|
<Route path="projects" element={<ProjectsPage />} />
|
|
<Route path="projects/:id" element={<ProjectDetail />} />
|
|
<Route path="people" element={<PeoplePage />} />
|
|
<Route path="locations" element={<LocationsPage />} />
|
|
<Route path="settings" element={<SettingsPage />} />
|
|
<Route
|
|
path="admin/*"
|
|
element={
|
|
<AdminRoute>
|
|
<Suspense fallback={<div className="flex h-full items-center justify-center text-muted-foreground">Loading...</div>}>
|
|
<AdminPortal />
|
|
</Suspense>
|
|
</AdminRoute>
|
|
}
|
|
/>
|
|
</Route>
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
export default App;
|