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
+16 -5
View File
@@ -13,6 +13,7 @@ pub mod llm_requests;
pub mod mcp_events;
pub mod mcp_servers;
pub mod plugins;
pub mod roles;
pub mod scheduled_jobs;
pub mod scratchpad;
pub mod session_mcp_grants;
@@ -349,16 +350,26 @@ async fn create_registry_tables(pool: &SqlitePool) -> Result<()> {
// the registry — which means it must never hold anything that derives a
// user's key: `database_password` is the DEK sealed under a key derived
// from the password, useless without it.
//
// `role_id` has no `REFERENCES roles(id)` yet: sqlx turns on
// `PRAGMA foreign_keys`, so pointing at a table that does not exist would
// make every INSERT fail. The constraint lands with the `roles` table.
sqlx::query(
"CREATE TABLE IF NOT EXISTS roles (
id TEXT PRIMARY KEY,
label TEXT NOT NULL,
permission_group TEXT NOT NULL,
attrs TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)",
)
.execute(pool)
.await?;
crate::db::roles::seed_admin(pool).await?;
sqlx::query(
"CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
display_name TEXT,
role_id TEXT NOT NULL,
role_id TEXT NOT NULL REFERENCES roles(id),
encrypted INTEGER NOT NULL,
kdf_params TEXT,
kdf_salt BLOB,