feat(auth): login, roles, user mgmt, setup wizard, and session guard

- New skald-setup crate: interactive first-run wizard that creates the
  admin user, prompts for encryption choice and password
- Auth system: session-based login/logout with cookie, guard middleware
- Roles API: CRUD for data-driven roles, seeded on first boot
- Users management API: create, list, edit, delete users
- Setup state API: check if first admin has been created
- Frontend: login-page, setup-page, users-page, roles-page, profile-page
  components with corresponding CSS
- Topbar: avatar dropdown with profile link and logout
- Sidebar: nav entries for Users and Roles (admin only)
- Page shell CSS: layout support for the new pages
- build.sh: builds both skald and skald-setup binaries
- run.sh: runs skald-setup before the server loop
- CLAUDE.md: updated workspace layout and build/run docs
This commit is contained in:
2026-07-10 19:19:25 +01:00
parent 178a38357e
commit 7dd77d4ef4
36 changed files with 2660 additions and 27 deletions
+26
View File
@@ -327,6 +327,32 @@ pub async fn set_active(pool: &SqlitePool, id: &str, active: bool) -> Result<()>
Ok(())
}
/// Changes the role assignment and optionally the display name in one statement.
pub async fn update_profile(
pool: &SqlitePool,
id: &str,
username: &str,
display_name: Option<&str>,
role_id: &str,
) -> Result<()> {
let n = sqlx::query(
"UPDATE users
SET username = ?2, display_name = ?3, role_id = ?4, updated_at = datetime('now')
WHERE id = ?1",
)
.bind(id)
.bind(username)
.bind(display_name)
.bind(role_id)
.execute(pool)
.await?
.rows_affected();
if n == 0 {
bail!("no such user: {id}");
}
Ok(())
}
pub async fn rename(pool: &SqlitePool, id: &str, username: &str, display_name: Option<&str>) -> Result<()> {
let n = sqlx::query(
"UPDATE users SET username = ?2, display_name = ?3, updated_at = datetime('now')