user & group management with permissions, audit trail, and security hardening, implements #23 - #24
Merged
Merged
Conversation
Owner
Author
|
Quite a few UI enhancements are included in this patch, as they are needed to fully provide feedback to the user for certain events. |
Backend — Authentication & Authorisation ──────────────────────────────────────── - New src/server/auth.ts: scrypt password hashing, JWT sign/verify, Linux-style rwx permission system with owner/group/world bits, inheritance (scores inherit from parent folders), admin bypass - New API endpoints (17): login, refresh, logout, whoami, listUsers, createUser, updateUser, deleteUser, listGroups, createGroup, updateGroup, deleteGroup, addUserToGroup, removeUserFromGroup, listGroupMembers, getPermissions, setPermissions - Permission checks on all existing mutation endpoints (addScoreFolder, addScore, renameEntry, updateScore, delete, move) - New DB tables: users, groups, user_groups, permissions (MySQL + PostgreSQL adapters, backend-db.sql) - Admin seed: auto-creates admin/admin on first start when no users exist - Dependency: jsonwebtoken (scrypt uses Node.js built-in crypto) Frontend — Auth State & Login Dialog ──────────────────────────────────── - ScoreBookDataModel: access/refresh token management, login/logout/ restoreSession, auto-401-refresh in fetchApi, capabilities getter replacing the old canWriteScores boolean field - Requisitions: new authChanged event for UI updates on auth changes - LoginDialog: uses ValueDialog with password fields, Enter-to-submit (username→focus password, password→trigger accept), error re-display - App.tsx: calls restoreSession() on startup, shows LoginDialog when no valid session exists, Continue Anonymously support Dialog Architecture Refactoring ─────────────────────────────── - ValueDialog extended: password fields, custom button labels, errorMessage display, hideActions option, isDefault button flag, Spinner entry type, dismiss()/triggerAccept() public methods - New StatusDialog: composes ValueDialog for status-only modals (no user input), simplified API: show()/update()/dismiss() - BackendDisconnectedDialog: rewritten to use StatusDialog, configurable dialogId prop Tests ───── - New tests/server/auth.spec.ts: 10 tests (JWT round-trip, refresh/access token separation, permission bit math) - New tests/core/ScoreBookDataModel-auth.spec.ts: 8 tests (auth state, login/logout, capabilities, Authorization header) - New tests/e2e/login-flow.spec.ts: 4 tests (dialog appears, anonymous continue, successful login, failed login error) - tests/e2e/helpers.ts: setupAuthenticatedSession() and setupAnonymousSession() helpers; routeApi defaults to authenticated - tests/e2e/backend-disconnect.spec.ts: adapted to login dialog and StatusDialog id changes Signed-off-by: Mike Lischke <mike@lischke-online.de>
Auth enforcement on unauthenticated endpoints: - handleSetup: require admin if users already exist (prevents DB reconfiguration) - handleClearAll: admin-only (was unauthenticated, trivial GET wipe) - handleUploadInstrumentImage: admin-only Permission fixes: - Root-level writes (parentId === -1) now require authenticated user - buildCapabilities: sync, simple admin check; removed broken per-feature permission queries and the ?? operator precedence bug - verifyPassword: JSON.parse of stored hash inside try/catch Token and credential hardening: - JWT_SECRET: mandatory env variable, throws on startup if absent (no hardcoded fallback secret) - Admin seed: log without plaintext password - Refresh cookie: added Secure flag Tests: - tests/setup.ts: JWT_SECRET set for test environment Signed-off-by: Mike Lischke <mike@lischke-online.de>
Raw error messages replaced with generic responses (audit #13): - console.error with convertErrorToString, sendError with generic text Refresh token rotation (audit #11): - Replaced JWT refresh tokens with random token + SHA-256 hash - DB: refresh_token_hash column in users table (MySQL + PostgreSQL, migration for existing databases) - createRefreshToken(): 32 random bytes, returns raw + hash - verifyAndRotateRefreshToken(): compares hash, rotates on success, clears all hashes on mismatch (stolen token detection) - login stores hash, refresh returns new raw token in cookie Read-permission filtering in listScoreFolderContent (audit #9): - Folders and scores filtered post-query via checkPermission(R) camelCase throughout server code: - All SELECT queries use AS to map snake_case columns to camelCase - Removed unused IUserRow, IGroupRow; IPermissionRow fields camelCase - Upload JSON response keys changed to camelCase Signed-off-by: Mike Lischke <mike@lischke-online.de>
- Add AppPhase enum for explicit UI phases (Checking, Setup, AdminSetup, Login, Running) - Add AdminSetupDialog for first-time install with no admin user - Add user dropdown menu with sign-out in the toolbar - Clear stale StatusBar items (score stats, notification icon) on logout - Use fresh array for scoreLib on re-initialize so TreeGrid detects the change - Backend: add whoami endpoint, support JWT_SECRET env var for local dev Signed-off-by: Mike Lischke <mike@lischke-online.de>
- Replace ValueDialog-based LoginDialog with Dialog-based layout styled like SettingsDialog - Add splash screen overlay with percussion-background.svg mask, auto-adapts to theme - Show splash during Setup, AdminSetup, Login phases; fade out on Running - Add logo.svg watermark (top-right, desaturated, wobble animation every 5s) - Center ProgressIndicator in a rounded card during Checking phase - Button component passes type attribute to native <button> element - Fix Sign In from Running: transition through Login phase for proper splash display - Fix continue-anonymous from Running: return directly without reinitializing - Fix stale StatusBar items and score lib after logout/login without page refresh - Tests updated Signed-off-by: Mike Lischke <mike@lischke-online.de>
Initial version to edit users + groups. Signed-off-by: Mike Lischke <mike@lischke-online.de>
Replace native <dialog> with a Portal-based Dialog and introduce a reusable Portal component. Portals render into managed host divs in document.body with automatic z-index stacking — fixing all overlay/backdrop ordering issues. New/refactored components: - Portal (src/components/ui/framework/Portal.tsx) Static portalStack, incrementing z-index, topmost-Escape-only, configurable background opacity, click-outside, mouse-event blocking. - Dialog rewritten to build on Portal instead of native <dialog>. Restores legacy action-button value→onClose forwarding. - Popup builds on Portal with auto-flip positioning via existing computeContentPosition() — replaces naive manual positioning. - SCSS split from monolithic component-styles.scss into 25 partials under styles/, loaded via a single @use index. Features: - Group colors: random color on creation, editable via inline <input type=color> in TagInput badges with auto light/dark text. - UserGroupEditor: inline form moved to Popup anchored to the clicked button; pending groups held until Save; ConfirmDialog replaces native confirm() for deletes. - ConvertErrorToString import cleanup in backend.ts. Fixes: - All 285 unit tests and 73 e2e tests green. - TagInput: .badge→.du-badge, add stopPropagation on remove btn. - SettingsDialog tests: query document.body (Portal renders there). - LoginDialog: add onClick for anonymous button (lost in Dialog rewrite). - print-dialog/backend-setup e2e: remove native dialog element selectors. - Coding-preference: remove all _-prefixed params and this.props/state direct access from authored files. Signed-off-by: Mike Lischke <mike@lischke-online.de>
Signed-off-by: Mike Lischke <mike@lischke-online.de>
- Simpler handling for canceling a dialog/popup. - Remove is_admin. That right is determined by the group the user is in (here Admin group). - Anonymous login and admin group always exist and cannot be changed. Signed-off-by: Mike Lischke <mike@lischke-online.de>
- Add group shared password login (DB, backend, data model, UI) - New columns: groups.password_hash, groups.admin_id, users/groups.last_login - Group login endpoint authenticates as anonymous with group permissions - LoginDialog tab switcher for User/Group login with group dropdown - Group info shown in user menu, "Sign Out" returns to login screen - Redesign UserGroupEditor with settings-card/settings-row pattern - Users and Groups cards with headings and compact add buttons - Inline popup forms for create/edit user, group password, create group - Delete group with member count warning and password notice - Self-delete logs out and returns to splash/login screen - Form validation errors shown inside popups, not main dialog - Anonymous user filtered from list - Popup arrow: rotated square with border matching popup, slight rounding - Input component: showPasswordToggle prop (eye icon for reveal) - Dropdown: keyboard navigation (↑↓/Enter), skip non-interactive items, disabled support, auto-focus first item on open - Dialog: closeOnBackdropClick prop, ConfirmDialog support - Fix arrangement player crash when arrangement is undefined during init - Prevent Admins group from getting a shared password - Name collision checks between users and groups - Various lint fixes, JSDoc cleanup Signed-off-by: Mike Lischke <mike@lischke-online.de>
Signed-off-by: Mike Lischke <mike@lischke-online.de>
Backend: - Add getPermissionSummary() returning isOwner/isGroup/isWorld/permBits - Include perm data in listScoreFolderContent response - Create default group + assign permissions on first admin creation - Support custom group name from admin setup dialog - Preserve group-login context across page reloads via sessionStorage - Sync users/groups table definitions with backend-db.sql in both adapters Frontend: - Add PermMatrix component (3×2 dot grid: Owner/Group/World × Read/Write) - Render permission matrix in Score Library tree (right of kebab menu) - Hide matrix for anonymous/world-only access - Add "Show permission matrix" toggle in Settings dialog (default on) - Live tree update on settings change via requisitions - Rewrite AdminSetupDialog to use standard Dialog pattern - Two blocks: Admin User + Initial Group - "Finish Installation" title and button - Group name field with validation (whitespace-only rejected) - Enter key on last field triggers submit - Fix ValueDialog: suppress empty decline button, wire Enter on last field - Fix default button primary-color styling - Remove dead PermissionGlyph component Tests: - auth.spec.ts: IPermissionSummary permBits tests - PermMatrix.spec.tsx: 6 tests for 3×2 dot rendering - settings-perm-matrix.spec.ts: e2e test for toggle flow Signed-off-by: Mike Lischke <mike@lischke-online.de>
This commit exists only to avoid having to push a big commit later, after the coming rework. Signed-off-by: Mike Lischke <mike@lischke-online.de>
- PermissionEditor now receives the data model entry directly (not entityType/ID). saveChanges mutates entry.perm as single source of truth; no server round-trip needed. Removed originalReadIds — diffs computed against entry.perm.groupIds. - Auto-select score tree entry when Group Access menu item is clicked. - Single-row tree update via handlePermChanged(entry) → row.reformat(). - World group blocked from Write zone (init, drop, save). Blocked cursor feedback via dragGroupId tracking in handleDragOverWrite. - Admins group excluded from group pool. - canWrite no longer overwritten in saveChanges — remains user-relative from backend (PermIndicator now shows current user's write capability, not group). - Double-click handling: cellDblClick on column definition instead of manual event.detail check in onRowClick. Removed dead handleScoreTreeRowClick. - CSS: .perm-drop-zone and .perm-group-pool use display:flex (parent flex, not child inline-flex). PermIndicator moved left of kebab button. user-select:none on score tree entries. - Added ResizeObserver mock to test setup. - Unit tests (5) and e2e tests (6) for PermissionEditor. - Fixed pre-existing e2e selector rot: .settings-card→.form-card, .permMatrix→.permIndicator, .settings-row→.form-row, popup input specifier. Signed-off-by: Mike Lischke <mike@lischke-online.de>
Signed-off-by: Mike Lischke <mike@lischke-online.de>
- handleRefresh: store auth_type/group_id server-side in users table instead of trusting client-controlled x-auth-type/x-group-id headers. Prevents privilege escalation via forged group membership. - handleTestConnection: added admin auth check (was unauthenticated SSRF oracle). - handleListUsers: restricted to admins only (was any authenticated user). - handleUpdateGroup: only full admins may reassign group adminId. - body size limits: readJsonBody 10MB, readRawBody 50MB. Oversized requests destroy the socket. - login_audit table: tracks login, group_login, refresh, logout events with user_id, group_id, ip_address, timestamp. Replaces users.last_login. - recordLoginAudit() helper + LoginAuditEvent enum in auth.ts. - getClientIp() respects x-forwarded-for header. - Tests: 2 new auth spec tests for LoginAuditEvent enum. Signed-off-by: Mike Lischke <mike@lischke-online.de>
mike-lischke
force-pushed
the
features/user-management
branch
from
June 30, 2026 14:55
6e8fd73 to
491cd62
Compare
Owner
Author
|
My fault, I should have done the CSS refactoring separately. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
"My Groups" mode, group login flow, admin badges with per-user colors.
World write-blocked, Admins filtered. PermIndicator in score tree.
Auto-select tree entry when Group Access menu opens.
admin-only testConnection/listUsers, adminId reassignment restricted,
body size limits (10MB JSON, 50MB upload).
login audit enum tests. Fixed stale selectors in print/settings/users-groups e2e.