feat(auth): two-role permission system (admin/member)

Adds an admin/member role to WebUI auth. /api/users and /api/audit
are now gated by a requireAdmin middleware; all other authenticated
endpoints accept both roles. Schema migration defaults all existing
users to admin to preserve access. POST /api/users defaults new users
to member; first-run setup always creates an admin. Adds PATCH
/api/users/:id/role with last-admin demotion and deletion guards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.7 committed 2026-05-27 15:35:15 +08:00
1 parent b0b61f8fce
commit a73f797bcb
18 files changed
+292 -60

No files matched your search

+3 -3
View File
@@ -60,7 +60,7 @@ export function createSessionRouter(
res.status(401).json({ error: "unauthenticated" });
return;
}
req.user = { id: result.userId, username: result.username };
req.user = { id: result.userId, username: result.username, role: result.role };
const token = extractSessionToken(req.headers.cookie);
if (token) setSessionCookie(res, token);
next();
@@ -98,7 +98,7 @@ export function createSessionRouter(
logger.warn({ err: auditErr, action: "admin.first_created" }, "audit insert failed");
}
logger.info({ userId: user.id, username }, "First admin created");
res.json({ id: user.id, username: user.username });
res.json({ id: user.id, username: user.username, role: user.role });
} catch (err) {
logger.error({ err }, "setup failed");
res.status(500).json({ error: "internal" });
@@ -120,7 +120,7 @@ export function createSessionRouter(
}
const { token } = sessions.createSession(user.id);
setSessionCookie(res, token);
res.json({ id: user.id, username: user.username });
res.json({ id: user.id, username: user.username, role: user.role });
});
router.post("/logout", (req, res) => {