Commit fdfd0a0
authored
* feat: user management — authentication, permissions, login dialog
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>
* fix: security — address critical and high audit findings
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>
* fix: medium audit fixes + camelCase throughout server code
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>
* fix: UI state after logout/login without page refresh
- 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>
* splash screen with theme-aware SVG background, login dialog redesign
- 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>
* New UserGroupEditor component
Initial version to edit users + groups.
Signed-off-by: Mike Lischke <mike@lischke-online.de>
* Portal-based stacking, SCSS restructuring, user-group colors
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>
* Split big App.scss into smaller files
Signed-off-by: Mike Lischke <mike@lischke-online.de>
* User management dialog improvements
- 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>
* Group shared login and user/group management overhaul
- 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>
* New unit and e2e Tests for the user & group editor
Signed-off-by: Mike Lischke <mike@lischke-online.de>
* Add permission visualization and initial setup flow
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>
* Temporary commit
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>
* Group Access popup — entry-driven permissions, tree integration, tests
- 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>
* A fix bug fixes.
Signed-off-by: Mike Lischke <mike@lischke-online.de>
* security: fix auth vulnerabilities, add login audit trail
- 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>
---------
Signed-off-by: Mike Lischke <mike@lischke-online.de>
1 parent d9393b3 commit fdfd0a0
95 files changed
Lines changed: 19004 additions & 3958 deletions
File tree
- public
- src
- components
- ui
- Arrangement
- Print
- composites
- framework
- Menu
- styles
- core
- player
- server
- supplement
- ui
- tests
- core
- e2e
- server
- ui
- __snapshots__
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
| |||
68 | 69 | | |
69 | 70 | | |
70 | 71 | | |
| 72 | + | |
71 | 73 | | |
72 | 74 | | |
73 | 75 | | |
| |||
80 | 82 | | |
81 | 83 | | |
82 | 84 | | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
83 | 88 | | |
| 89 | + | |
84 | 90 | | |
85 | 91 | | |
86 | 92 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
307 | 307 | | |
308 | 308 | | |
309 | 309 | | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
310 | 314 | | |
311 | 315 | | |
312 | 316 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| 35 | + | |
34 | 36 | | |
35 | 37 | | |
36 | 38 | | |
| |||
47 | 49 | | |
48 | 50 | | |
49 | 51 | | |
| 52 | + | |
50 | 53 | | |
51 | 54 | | |
52 | 55 | | |
| |||
Loading
0 commit comments