Repository navigation
feat(ui): redesigned users page with role clarity, member self-service area and per-device Pi-hole - #266
Merged
Conversation
…e area and per-device Pi-hole Users page: list with search/filters and German relative times, detail panel (Übersicht, Geräte & Zugänge, "Was sieht …?", Anmeldung & Sicherheit, Aktivität) deep-linkable via ?user=&tab=, role comparison, owner-less accesses, create user, a four-step access wizard (device code that creates and assigns the peer, or script code / key), edit access (PATCH /tokens/:id, rights capped by the owner's role), new password, change role (promotion needs a password), delete with computed consequences and type-to-confirm. Dirty dialogs ask before Escape or the backdrop discard input. Members: role-aware navigation, every admin page redirects to /me, opt-in "Mein Bereich" via one-time invitation links (hashed, 72 h, single use), /api/v1/me/* scoped to the session's own account, must-change-password step at login, Pi-hole statistics as a per-device right (read only) inside the member role cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
- users.js / me.js: the DOM helper no longer sets arbitrary attribute names; a fixed list of literal attributes is allowed and href only takes same-origin paths or https URLs, so no text can become a handler, a script URL or markup. - Login redirect: the target is the constant '/portal' prefix plus the remainder of the validated portal path, otherwise the account's home. - "Change password at login": the pending state is stored on a fresh session id, and the pre-login session is abandoned before the account is signed in. - Rate limits on GET / and GET /login/change-password. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
added 2 commits
October 6, 2026 16:10
- GET /login/change-password only reads the pending session state; the session is regenerated on the successful POST before the account is signed in, and the login step that sets pendingPwChange regenerates too. - GET / is back to the plain redirect to /dashboard; /dashboard sends members on to /me, so no user-derived target reaches res.redirect. - users.js / me.js: error messages are always rendered through textContent, and the el() helper appends children via ParentNode.append, which inserts strings as Text nodes only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
…trators Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
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.
Summary
Implements the approved Users page redesign, the member self-service area "Mein Bereich" and per-device Pi-hole statistics for members.
Users page (admin)
PATCH /tokens/:id, so the new owner's role caps the rights) and revoke.last_login_atand token use,Intl.RelativeTimeFormatin the UI language). Card list below 768 px.?user=<id>&tab=…) with five tabs: Übersicht (tiles, role card, display name/e-mail, danger zone with explanations for own account / last admin), Geräte & Zugänge (devices vs. API accesses, rights in words, binding, expiry, last use), "Was sieht ?" (newGET /api/v1/users/:id/visibility), Anmeldung & Sicherheit (password, 2FA reset, passkeys, sessions; for members the "Mein Bereich" invitation and the per-member "own device setup" switch), Aktivität (GET /api/v1/users/:id/activity).client-enrollment.jsstays for the Peers page), edit access, new password, change role, delete (consequences computed byGET /api/v1/users/:id/delete-impact), invitation. Escape, × and the backdrop ask before discarding a changed form.Fixed along the way
Admin password reset (the old modal's
passwordwas ignored), promotion leaving the'!'sentinel, English relative times, disabled users looking enabled, enable without confirmation, hidden delete side effects, errors only after clicking, wizard issues (presets not filtered by role, deadbackupscope, no "no owner", disabled owners offered, no review step, unnamed steps, ae/ue spellings, English fallbacks), tokens not editable, MFA column ignoring passkeys, display name maxlength 200 vs 100, severalerror.users.*keys that were used but missing (raw keys), legacyactivity.logcalls intokens.create/revokewhose options were silently ignored.Role-aware navigation
Member sessions see only Mein Bereich, Konto & Sicherheit and the portal link; no bottom nav, FAB, topbar "Einstellungen", admin palette sources or admin SSE feed. Every admin page redirects a member to
/me(generalises the old dashboard/settings →/profileredirect). APIs keep answering 403."Mein Bereich" (
/me)users.self_service_enabled. The admin creates a one-time link (/invite/:token, 32 random bytes, only SHA-256 stored, 72 h, single use, consumed atomically); mailed when SMTP and an address exist. The page applies the password policy. Switching it off, disabling, demoting or deleting the member removes password, 2FA, passkeys, invitations and sessions.GET /api/v1/me,GET /me/devices,DELETE /me/devices/:tokenId,POST /me/enrollment,GET /me/services— session only, scoped toreq.session.userId, ids from the client ignored, mutating endpoints rate-limited and logged.Pi-hole per device
pihole(read) is in the member role cap;pihole:controlnever. New member devices default to off; the wizard/edit dialog offer "Pi-hole-Statistik". Enrollment defaults:DEFAULT_SCOPES(app scopes) for members,ADMIN_DEFAULT_SCOPES(+ pihole) for admins — both inside the cap. Member tokens only see their own device in/pihole/top-clients. Licencepihole_integrationstill gates it.New endpoints
GET /users/:id/visibility,GET /users/:id/delete-impact,GET /users/:id/activity,POST /users/:id/password,POST /users/:id/role,GET|DELETE /users/:id/sessions[/:ref],DELETE /users/:id/passkeys/:pid,POST /users/:id/invite,DELETE /users/:id/self-service,PUT /users/:id/self-enroll,PATCH /tokens/:id,/api/v1/me/*,GET|POST /invite/:token,GET|POST /login/change-password.Migration 89
users_self_serviceusers.self_service_enabled,self_enroll_enabled,must_change_password,password_changed_at; tableuser_invites. Existing members with a real password (e.g. demoted admins) keep their login (self_service_enabled = 1).Security decisions
ref, always filtered by the account.requireAuth.must_change_passwordkeeps the session anonymous until the new password is set.Deviations from the mockups
routes.user_idsalso applies to admin devices, so the matrix/role texts say "„für alle“ + freigegebene" for admins instead of "alle", and the promote dialog says "Zugänge … nicht mehr auf App-Rechte begrenzt" instead of "sehen dann alle Dienste". The web UI is "alles" for admins as designed.gatewayscope is not offered in the wizard (gateway tokens are managed on the Peers page).Tests
tests/users_redesign.test.js(services, admin API, invite flow end to end,/meisolation/escalation, Pi-hole cap, nav guards, templates without raw keys in de/en), updated existing tests, new browser scenariotests/e2e/scenarios/07-users.js(added to the CI set). Full suite green locally.🤖 Generated with Claude Code
https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
Generated by Claude Code