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:
+40
@@ -9,6 +9,9 @@ import { ModelsImageSection } from './components/models-image.js';
|
||||
import { ModelsTtsSection } from './components/models-tts.js';
|
||||
import { TasksPage } from './components/tasks/index.js';
|
||||
import { AgentsPage } from './components/agents.js';
|
||||
import { UsersPage } from './components/users-page.js';
|
||||
import { RolesPage } from './components/roles-page.js';
|
||||
import { ProfilePage } from './components/profile-page.js';
|
||||
import { ApprovalGroupsPage } from './components/approval-groups.js';
|
||||
import { ApprovalRulesPage } from './components/approval-rules.js';
|
||||
import { ConfigPage } from './components/config-page.js';
|
||||
@@ -20,6 +23,8 @@ import { SessionDetailPage } from './components/session-detail.js';
|
||||
import { TicSessionsPage } from './components/tic-sessions.js';
|
||||
import { ProjectsPage } from './components/projects/index.js';
|
||||
import { FileViewerPage } from './components/file-viewer-page.js';
|
||||
import { SetupPage } from './components/setup-page.js';
|
||||
import { LoginPage } from './components/login-page.js';
|
||||
|
||||
// Register the global `openFile(path)` helper (window.openFile → location.hash).
|
||||
import './lib/open-file.js';
|
||||
@@ -35,6 +40,9 @@ customElements.define('models-image-section', ModelsImageSection);
|
||||
customElements.define('models-tts-section', ModelsTtsSection);
|
||||
customElements.define('tasks-page', TasksPage);
|
||||
customElements.define('agents-page', AgentsPage);
|
||||
customElements.define('users-page', UsersPage);
|
||||
customElements.define('roles-page', RolesPage);
|
||||
customElements.define('profile-page', ProfilePage);
|
||||
customElements.define('approval-groups-page', ApprovalGroupsPage);
|
||||
customElements.define('approval-rules-page', ApprovalRulesPage);
|
||||
customElements.define('config-page', ConfigPage);
|
||||
@@ -46,9 +54,41 @@ customElements.define('session-detail-page', SessionDetailPage);
|
||||
customElements.define('tic-sessions-page', TicSessionsPage);
|
||||
customElements.define('projects-page', ProjectsPage);
|
||||
customElements.define('file-viewer-page', FileViewerPage);
|
||||
customElements.define('setup-page', SetupPage);
|
||||
customElements.define('login-page', LoginPage);
|
||||
|
||||
// Toggle the workspace placeholder when an LLM page opens/closes.
|
||||
const workspace = document.getElementById('app-workspace');
|
||||
window.addEventListener('llm-page-change', (e) => {
|
||||
workspace.style.display = e.detail.page ? 'none' : 'flex';
|
||||
});
|
||||
|
||||
// ── First-run check ─────────────────────────────────────────────────────────
|
||||
// If no user exists yet, show the setup screen. Otherwise, check if we have
|
||||
// a valid session: if not, show the login screen. If logged in, show the app.
|
||||
(async () => {
|
||||
const app = document.getElementById('app');
|
||||
const setup = document.querySelector('setup-page');
|
||||
const login = document.querySelector('login-page');
|
||||
|
||||
try {
|
||||
const setupRes = await fetch('/api/setup/status');
|
||||
if (setupRes.ok) {
|
||||
const { needs_setup } = await setupRes.json();
|
||||
if (needs_setup) {
|
||||
if (app) app.style.display = 'none';
|
||||
if (setup) setup.style.display = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch { /* fall through to auth check */ }
|
||||
|
||||
try {
|
||||
const meRes = await fetch('/api/auth/me');
|
||||
if (meRes.status === 401) {
|
||||
if (app) app.style.display = 'none';
|
||||
if (login) login.style.display = '';
|
||||
return;
|
||||
}
|
||||
} catch { /* show app by default */ }
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user