Repository navigation
feat(ui): machine binding status, toggle and reset per token on the Users page - #264
Merged
Merged
Conversation
added 3 commits
October 6, 2026 09:01
…sers page
The per-token device binding UI (switch, status, reset) only existed in the
old theme and disappeared with "Aurora is the only theme". Admins could not
see whether a token was bound, could not use the "individual" mode at all and
could not release a binding when someone got a new PC.
Users page (edit user → API tokens), for every client token:
- "Gerätebindung" area: bound to device ab12cd34… since <date> / not bound
yet (binds on the next connection) / not active, with a stored device
shown when the binding is off
- effective state incl. the global mode ("durch globale Einstellung aktiv");
the per-token switch is only enabled in the "individual" mode, otherwise
disabled with the reason (global, off with a link to the settings)
- "Bindung zurücksetzen…" with a confirmation dialog, only with a stored
fingerprint
- without the licence: Pro chip, lock note, controls dimmed and disabled
- one line explaining that this is not the token→peer binding
API:
- token lists (GET /users/:id, /users/unassigned-tokens, /tokens) return
machine_fingerprint shortened to 8 hex chars, machine_binding_enabled,
machine_bound_at, machine_binding_mode, machine_binding_active, plus a
top-level machine_binding { licensed, mode }
- PUT /tokens/:id/binding answers 409 (code binding_mode) outside the
"individual" mode instead of storing a flag that does nothing; both
binding routes return the updated token and log with proper details
- one shared rule for "is binding active" (tokens.isMachineBindingActive)
used by the client routes, enrollment and the lists
Migration 88 adds api_tokens.machine_bound_at: set on the first binding
(client request or setup code), cleared by the reset. The first binding logs
machine_binding_bound (once per binding, race-safe UPDATE ... IS NULL).
Settings → Gerätebindung explains device vs. peer binding, links the Users
page for status and reset, and names the clients that send the fingerprint
(Windows client, Android app from 1.16).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
… fix) New advisories that also affect master: proxy-addr <= 2.0.7 (critical, GHSA-jqcg-44mw-7w3h, IP spoofing via an IPv4-mapped IPv6 trust subnet; an express dependency) and fast-copy 4.0.0-4.0.4 (moderate, GHSA-jggr-w7fw-pc2j). Both are patch releases within the existing ranges, applied with `npm audit fix --package-lock-only`; package.json is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
The settings note claimed Android only sends it from app version 1.16; older Android apps send it as well. Name scripts as the clients without one. 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.
Why
Machine binding (licence feature
machine_binding) binds an API token to the hardware fingerprint of the client device that first connects with it. The per-token UI for it (switch, status, reset) only existed in the old theme and went away in 66a4418 ("Aurora is the only theme"). Since then the admin could not see whether a token is bound, theindividualmode had no way to switch binding on for a token, and a binding could not be released when someone got a new PC. On the Users page "binding" also only meant token→peer binding, which is a different thing.What
Users page (edit user → API tokens)
Each client token (scopes
client,client:*orfull-access, or any token with a stored fingerprint) gets a Gerätebindung area:Gebunden an Gerät ab12cd34… seit 14.09.2026/Noch nicht gebunden – wird beim nächsten Verbinden gebunden/Nicht aktiv(plus the stored device when the binding is off but a fingerprint is kept)für dieses Token aktiv, ordurch globale Einstellung aktivin global modeindividualmode. Inglobalandoffit is disabled and the reason is shown, with a link to Settings → Gerätebindung foroff.Bindung zurücksetzen…with a gc-dialog confirmation, shown only when a fingerprint is storedPage-local i18n island (
#mb-users-i18n), same pattern as the 2FA strings on this page; CSS prefixmb-; the switch reuses.st-switch.API
GET /api/v1/users/:id,GET /api/v1/users/unassigned-tokens,GET /api/v1/tokens: per tokenmachine_fingerprint(shortened to 8 hex chars, the full hash no longer leaves the server),machine_binding_enabled,machine_bound_at,machine_binding_mode,machine_binding_active; top levelmachine_binding: { licensed, mode }PUT /api/v1/tokens/:id/binding: returns 409{ code: 'binding_mode', mode }outsideindividualmode, so it no longer stores a flag that has no effect. Returns the updated token.DELETE /api/v1/tokens/:id/binding: clears the fingerprint andmachine_bound_at, and returns the updated tokendetails/source/severitytoactivity.logcorrectly. Before this they used a 4-argument call, so the details were dropped./tokensrouter is admin-session only, with no ownership check, so an admin can already switch or reset tokens owned by other users. This is now covered by a test, and the users router needed no change.tokens.isMachineBindingActive()/machineBindingState()is now used by the client routes (verifyMachineBinding), the enrollment redeem and the lists.Data
add_machine_bound_at:api_tokens.machine_bound_at. It is set bybindMachineFingerprint(client request or setup code) and cleared by the reset. Tokens bound before this migration keepNULL, so the page shows no "seit".machine_binding_boundonce per binding. The UPDATE has aWHERE machine_fingerprint IS NULLguard, so concurrent first requests cannot double-log.machine_binding_resetnow carries the short fingerprint. Both events fall into the existingmachine_binding_security category.Settings → Gerätebindung
The description and hints are rewritten:
New keys are added in both
de.jsonanden.json.Dependency fix (not related to the feature)
Dependency Audit and Trivy failed because of new advisories that also affect master:
proxy-addr<= 2.0.7, critical, GHSA-jqcg-44mw-7w3h (pulled in by express)fast-copy4.0.0–4.0.4, moderate, GHSA-jggr-w7fw-pc2jnpm audit fix --package-lock-onlybumps them to 2.0.8 and 4.1.2. These are patch releases inside the existing ranges, and package.json is unchanged. This is a separate commit.Tests
tests/machine_binding_ui.test.jscovers:bound_atand themachine_binding_boundevent, logged onceverifyMachineBindingglobalandoffbound_atand logs/usersand/settingsrender without raw keys (withoutScripts), and every island string the JS uses is translatedThere is no users scenario under
tests/e2e, so no browser scenario was extended.🤖 Generated with Claude Code
https://claude.ai/code/session_016xX1efcZF1f6G9rhmaJNLD
Generated by Claude Code