corrected admin view

This commit is contained in:
kaspa 2026-05-03 20:40:48 -05:00
parent c59ad6cb70
commit 25c768d013
4 changed files with 19 additions and 11 deletions

View File

@ -19,6 +19,7 @@
- **authentik client auth method**: Admin OIDC settings now include an advanced `client_secret_basic` / `client_secret_post` token endpoint authentication method selector. The default remains `client_secret_basic`, matching the previous `openid-client` behavior. - **authentik client auth method**: Admin OIDC settings now include an advanced `client_secret_basic` / `client_secret_post` token endpoint authentication method selector. The default remains `client_secret_basic`, matching the previous `openid-client` behavior.
- **Admin user role management**: Admin Users table now lets an admin promote another user to `admin` or demote an admin back to `user`, with protections against changing your own role or removing the last admin account. - **Admin user role management**: Admin Users table now lets an admin promote another user to `admin` or demote an admin back to `user`, with protections against changing your own role or removing the last admin account.
- **Single-user mode recovery**: User Settings now shows a Login Mode section while single-user mode is active, allowing the default user to restore multi-user login without needing access to Admin routes. - **Single-user mode recovery**: User Settings now shows a Login Mode section while single-user mode is active, allowing the default user to restore multi-user login without needing access to Admin routes.
- **Admin navigation parity**: Admin users now keep the normal app navigation and get an Admin link after Status; `/admin` uses the same top nav so admins can return to Tracker/Bills/Categories/Profile/Settings/Status without typing a URL. Backend `/admin` protection remains unchanged.
- **Admin-controlled auth method toggles** in Admin panel (Authentication Methods card): - **Admin-controlled auth method toggles** in Admin panel (Authentication Methods card):
- `local_login_enabled` — enable/disable local username/password login (default: enabled) - `local_login_enabled` — enable/disable local username/password login (default: enabled)
- `oidc_login_enabled` — enable/disable OIDC/authentik login (default: disabled) - `oidc_login_enabled` — enable/disable OIDC/authentik login (default: disabled)

View File

@ -35,8 +35,10 @@ function RequireAuth({ children, role }) {
return <Navigate to="/login" state={{ from: location }} replace />; return <Navigate to="/login" state={{ from: location }} replace />;
} }
const roleAllowed = !role || user.role === role || (role === 'user' && user.role === 'admin');
// Role mismatch // Role mismatch
if (role && user.role !== role) { if (!roleAllowed) {
return <Navigate to={user.role === 'admin' ? '/admin' : '/'} replace />; return <Navigate to={user.role === 'admin' ? '/admin' : '/'} replace />;
} }

View File

@ -98,14 +98,16 @@ function UserMenu({ adminMode = false }) {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52"> <DropdownMenuContent align="end" className="w-52">
<DropdownMenuLabel className="truncate">{name}</DropdownMenuLabel> <DropdownMenuLabel className="truncate">{name}</DropdownMenuLabel>
{!adminMode && ( <DropdownMenuSeparator />
<> <DropdownMenuItem onSelect={() => navigate('/profile')}>
<DropdownMenuSeparator /> <User className="h-4 w-4" />
<DropdownMenuItem onSelect={() => navigate('/profile')}> Profile
<User className="h-4 w-4" /> </DropdownMenuItem>
Profile {user?.role === 'admin' && !adminMode && (
</DropdownMenuItem> <DropdownMenuItem onSelect={() => navigate('/admin')}>
</> <ShieldCheck className="h-4 w-4" />
Admin
</DropdownMenuItem>
)} )}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem destructive onSelect={handleLogout}> <DropdownMenuItem destructive onSelect={handleLogout}>
@ -119,7 +121,10 @@ function UserMenu({ adminMode = false }) {
export default function Sidebar({ adminMode = false }) { export default function Sidebar({ adminMode = false }) {
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
const items = adminMode ? adminNavItems : userNavItems; const { user } = useAuth();
const items = user?.role === 'admin'
? [...userNavItems, ...adminNavItems]
: userNavItems;
return ( return (
<header className="sticky top-0 z-40 border-b border-border/70 bg-background/85 shadow-sm shadow-foreground/5 backdrop-blur-xl supports-[backdrop-filter]:bg-background/70"> <header className="sticky top-0 z-40 border-b border-border/70 bg-background/85 shadow-sm shadow-foreground/5 backdrop-blur-xl supports-[backdrop-filter]:bg-background/70">

View File

@ -27,7 +27,7 @@ function requireAuth(req, res, next) {
} }
function requireUser(req, res, next) { function requireUser(req, res, next) {
if (req.user?.role !== 'user') { if (!['user', 'admin'].includes(req.user?.role)) {
return res.status(403).json({ error: 'Access denied: user account required' }); return res.status(403).json({ error: 'Access denied: user account required' });
} }
next(); next();