Feat: RBAC, Keys Extension, Invites

feat: org members page — invite users, cancel invites, change roles
feat: show pending invitations banner on profile page
feat: invite accept flow for existing users (no password needed)
feat: departments page updates
feat: SSH keys page — dept cert policy UI (expiry + extensions)
feat: wire up auth pages to real API (register, verify, reset, OIDC)
feat: CLI auth bridge — login page handles CLI token flow
feat: admin users — suspend/unsuspend, role badges, role filter
feat: add admin OAuth providers management page
feat: activity page — org-wide audit log view for admins
feat: add my memberships page
chore: add isOrgAdmin/isOrgMember to AuthContext, restrict sidebar
chore: update app routing and shared layout
This commit is contained in:
2026-03-01 16:50:19 +05:45
parent 62f767474b
commit 4c01fd0107
22 changed files with 2457 additions and 496 deletions
+42 -14
View File
@@ -38,8 +38,10 @@ import OIDCClientsPage from "@/pages/org/OIDCClientsPage";
import CAsPage from "@/pages/org/CAsPage";
import DepartmentsPage from "@/pages/org/DepartmentsPage";
import PrincipalsPage from "@/pages/org/PrincipalsPage";
import MyMembershipsPage from "@/pages/org/MyMembershipsPage";
import SystemAuditPage from "@/pages/admin/SystemAuditPage";
import AdminUsersPage from "@/pages/admin/AdminUsersPage";
import OAuthProvidersPage from "@/pages/admin/OAuthProvidersPage";
import NotFound from "@/pages/NotFound";
import ApiDevTools from "@/components/dev/ApiDevTools";
@@ -78,8 +80,30 @@ import { Navigate } from "react-router-dom";
/** Redirects already-authenticated users away from guest-only pages (e.g. /login). */
function GuestRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
// Allow authenticated users through to /login when it's a CLI auth request —
// LoginPage will immediately forward the existing token to the CLI callback.
const params = new URLSearchParams(window.location.search);
const isCli = params.has('cli_token') || params.has('cli_redirect');
if (isLoading) return null; // wait for auth state to resolve
if (isAuthenticated) return <Navigate to="/profile" replace />;
if (isAuthenticated && !isCli) return <Navigate to="/profile" replace />;
return <>{children}</>;
}
/** Blocks access to /admin/* for non-admin users. */
function RequireAdmin({ children }: { children: React.ReactNode }) {
const { isOrgAdmin, isLoading, isAuthenticated } = useAuth();
if (isLoading) return null;
if (!isAuthenticated) return <Navigate to="/login" replace />;
if (!isOrgAdmin) return <Navigate to="/profile" replace />;
return <>{children}</>;
}
/** Blocks access to /org/* for users who don't belong to any organisation. */
function RequireOrgMember({ children }: { children: React.ReactNode }) {
const { isOrgMember, isLoading, isAuthenticated } = useAuth();
if (isLoading) return null;
if (!isAuthenticated) return <Navigate to="/login" replace />;
if (!isOrgMember) return <Navigate to="/profile" replace />;
return <>{children}</>;
}
@@ -113,20 +137,24 @@ function AppRoutes() {
<Route path="/activity" element={<ActivityPage />} />
<Route path="/ssh-keys" element={<SSHKeysPage />} />
{/* Organization routes */}
<Route path="/org" element={<OrgOverviewPage />} />
<Route path="/org/members" element={<MembersPage />} />
<Route path="/org/departments" element={<DepartmentsPage />} />
<Route path="/org/principals" element={<PrincipalsPage />} />
<Route path="/org/policies" element={<PoliciesPage />} />
<Route path="/org/policies/compliance" element={<CompliancePage />} />
<Route path="/org/audit" element={<OrgAuditPage />} />
<Route path="/org/clients" element={<OIDCClientsPage />} />
<Route path="/org/cas" element={<CAsPage />} />
{/* Organization routes — org members: overview + own memberships only */}
<Route path="/org" element={<RequireOrgMember><OrgOverviewPage /></RequireOrgMember>} />
<Route path="/org/my-memberships" element={<RequireOrgMember><MyMembershipsPage /></RequireOrgMember>} />
{/* Admin routes */}
<Route path="/admin/audit" element={<SystemAuditPage />} />
<Route path="/admin/users" element={<AdminUsersPage />} />
{/* Organization management routes — org admins/owners only */}
<Route path="/org/members" element={<RequireAdmin><MembersPage /></RequireAdmin>} />
<Route path="/org/departments" element={<RequireAdmin><DepartmentsPage /></RequireAdmin>} />
<Route path="/org/principals" element={<RequireAdmin><PrincipalsPage /></RequireAdmin>} />
<Route path="/org/policies" element={<RequireAdmin><PoliciesPage /></RequireAdmin>} />
<Route path="/org/policies/compliance" element={<RequireAdmin><CompliancePage /></RequireAdmin>} />
<Route path="/org/audit" element={<RequireAdmin><OrgAuditPage /></RequireAdmin>} />
<Route path="/org/clients" element={<RequireAdmin><OIDCClientsPage /></RequireAdmin>} />
<Route path="/org/cas" element={<RequireAdmin><CAsPage /></RequireAdmin>} />
{/* Admin routes — org admin/owner only */}
<Route path="/admin/audit" element={<RequireAdmin><SystemAuditPage /></RequireAdmin>} />
<Route path="/admin/users" element={<RequireAdmin><AdminUsersPage /></RequireAdmin>} />
<Route path="/admin/oauth" element={<RequireAdmin><OAuthProvidersPage /></RequireAdmin>} />
</Route>
{/* Catch-all */}