Admin page (admin.euzos.eu)
FAQ › Profile server › Admin page · Nederlands
What is it?
The admin page is the web administration of the profile server, next to the CLI. It lives on a domain of its own: https://admin.euzos.eu for production (the sandbox has its own admin domain); / redirects to /beheer. Only accounts with the role beheerder (admin) can log in, with e-mail address and password, confirmed in the EUZOS app on a phone paired with that admin account (no TOTP or external authenticator app). It is own code (server/profile/src/admin_page/), works without JavaScript, and is only reachable from the IP allow list.
How do I…?
How do I make someone an admin?
On the VPS: euzos-profielserver admin-create <email> (an existing, active account). admins lists the admins and how many phones each has paired; admin-delete <email> removes the role. The admin pairs a phone in the EUZOS app like any user.
How do I log in?
- Open
https://admin.euzos.eu/(from an address on the allow list), enter e-mail address and password (POST /beheer/inloggen). - The page shows a QR code with
https://admin.euzos.eu/b/<id>and a 6-digit pairing code, and refreshes every 2 s. - Scan it with the EUZOS app on your paired phone; the app shows "Log in to admin from <IP>" ("Inloggen op beheer vanaf <IP>") with the same code. Confirm with Face ID / fingerprint.
- The next refresh of the same browser logs you in.
Without a paired phone you cannot log in (the page says so after the correct password). Cancel on the page (POST /beheer/annuleer) or Decline in the app aborts.
How do I log out?
The logout button (POST /beheer/uit). A session also ends after 15 minutes without a click, after 4 hours in any case, after a server restart, or when the account is no longer an active admin.
What can I do on the admin page?
Page (path under /beheer/) | What | CLI counterpart |
|---|---|---|
Dashboard (/beheer) | counts: accounts per status, admins, phones, PC sessions, apps, admin log in 24 h | users, devices, pc-sessions, apps |
Accounts (accounts, accounts?q=) | search, create (accounts/nieuw) | users, user-create |
Account (accounts/<id>) | data; block/unblock, password (optionally revoke phones), change address, set/remove the admin policy beheer=, revoke phones, log out PCs, vault info and vault log, delete (GDPR, two steps) | user-block, user-unblock, password, user-email, admin-policy, vault, user-delete |
PC sessions (pcsessies) | all logged-in PCs, log out (pcsessies/afmelden) | pc-sessions |
Phones (telefoons) | all paired phones, revoke (telefoons/intrek) | devices, device-revoke |
App catalog (apps) | list, set (apps/zet), delete (apps/wis) | apps, app-set, app-delete |
Download tokens (downloadtokens) | record a revocation request (downloadtokens/intrek); tokens and revocations from the admin log | download-token |
Admin log (log) | the last 500 lines, with who did it | admin-log |
Server status (status) | environment, domain, version, schema, signing key, vault, mail, flags, sessions | key |
Why can't I block or delete my own account?
On their own account an admin cannot block, unblock, change password or address, or delete. That way nobody locks themselves out by accident; use the CLI or another admin.
How do I delete an account (GDPR) on the page?
On the account page: type the address again, then on a separate page "Yes, delete permanently" ("Ja, definitief wissen"; a one-time token valid for 5 minutes). Exactly what user-delete does then happens.
How do I revoke a download token from the page?
The page records the request in the admin log (downloadtoken-intrek …) and shows the command for the package server: sudo euzos-download revoke <jti> [comment]. The blocklist lives on the package server, not on the profile server. (A direct revoke endpoint on the package server is a proposal for later, planned.)
Why do I get 404 on the admin domain?
You are not on the allow list (Caddy answers 404 for the whole admin domain from outside the list, except the two app-link files), or admin_page = no (the default in production until switched on), or admin_domain is not set.
Details
Login rules: one error message for everything (no enumeration); throttling 10 attempts per network (IPv6 per /64 and /48) and 5 per account per quarter of an hour; Argon2 through the same gate as the rest of the server. The waiting request is valid at most 2 minutes, single use, bound to the browser (cookie __Host-euzos_beheer_wacht, only its hash on the server) and the IP address. Only phones of that same account count; another phone gets 410. The signed message is EUZOS-beheer-v1, so a PC-login signature never counts for admin and vice versa.
Session: 256-bit token in the cookie __Host-euzos_beheer (Secure, HttpOnly, SameSite=Strict), hash in memory only, bound to the IP; at most 64 sessions at once.
CSRF: every POST under /beheer/ needs Sec-Fetch-Site: same-origin or an Origin of the admin domain, and the session's CSRF token in the form. The login form uses a double-submit cookie (__Host-euzos_beheer_inlog). Redirects only go to paths under /beheer. Strict CSP (script-src 'none').
Domains: on the admin domain only /beheer…, /b/<id> and the two /.well-known/ files answer; on the profile domain GET /beheer… gives a 308 to the admin domain (until 26-10-2027, then 404). The app API for confirming (/api/app/v2/admin/…) stays on the profile domain, because the app only talks to its own pinned server.
Admin log: every action goes into the table admin_log with the same (Dutch) action names as the CLI (gebruiker-blokkeer, wachtwoord, app-zet, …), plus beheer-inloggen, beheer-inloggen mislukt, beheer-uitloggen, pc-afmelden <ref>, apparaat-intrek <id>, downloadtoken-intrek …. The column admin_id is the admin's account id (NULL = the CLI).
Planned (plan B): a separate admin VPS for admin.euzos.eu that calls a shielded admin API on the profile server (docs/ADMIN-PAGE.md, "Plan B").
Reference
CLI commands
| Command | Arguments | What it does |
|---|---|---|
admin-create | <email> | give an active account the role beheerder |
admin-delete | <email> | remove the role |
admins | — | accounts with the role, and their number of paired phones |
admin-log | — | what the admin commands and the admin page changed |
Legacy names: beheerder-maak, beheerder-wis, beheerders, beheerlog.
Functions and modules
| Function / module | Role |
|---|---|
euzos_profileserver::admin_page::router, gate | routes and the domain gate (admin_domain) |
euzos_profileserver::admin_page::login (login, cancel, link_page) | password step, waiting request, /b/<id> page |
euzos_profileserver::admin_page::pages (show, action) | the GET pages and POST actions |
euzos_profileserver::admin_page::app (request, confirm, reject) | the app API for confirming |
euzos_profileserver::admin_page::db (role, set_role, admin_log_by, admin_log_last, search_accounts, counts, schema; ADMIN_ROLE) | database helpers |
libs/qr | the QR code (own encoder, as SVG) |
Config files
| Key (server configuration) | Default | Meaning |
|---|---|---|
admin_page (legacy beheerpagina) | on in local and sandbox, off in production | switch the admin page on or off |
admin_domain (legacy beheer_domein) | — | the admin domain; without it no admin page anywhere |
apple_app_id, android_package | — | needed to serve the app-link files with /b/* |
On the VPS: the allow list for the admin domain is generated by euzos-whitelist into a Caddy snippet; see Environments and deploy.
On-disk config keys and typed confirmation words are English since 27-10-2026 (for example
WIS-…→WIPE-…,omgeving=→environment=; docs/ENGLISH.md); the Dutch names stay accepted. The profile server's wire formats (pc line protocol, signed profile, signed phone messages, settings keys) stay Dutch.
API endpoints
| Method | Path | Legacy Dutch path | Purpose |
|---|---|---|---|
| GET | /beheer, /beheer/<page> | — (user-visible paths) | the pages |
| POST | /beheer/inloggen, /beheer/annuleer, /beheer/uit, /beheer/<action> | — | login, cancel, logout, actions |
| GET | /b/<id> | — | the link target of the QR code (explanation without the app) |
| GET | /api/app/v2/admin/<id> | /api/app/v1/beheer/<id> | the app fetches the request |
| POST | /api/app/v2/admin/<id>/confirm | /api/app/v1/beheer/<id>/bevestig | confirm ({time, signature}) |
| POST | /api/app/v2/admin/<id>/reject | /api/app/v1/beheer/<id>/weiger | reject |
Protocol messages and formats
GET /api/app/v2/admin/<id> → {kind: "beheer", session, environment, server, user, admin_ip, phone_ip, same_network, text, code, created, expires, challenge, device}; errors 401 niet_aangemeld, 410 verlopen, 429, 404 (admin page off).
Signed with the chip key (ECDSA P-256, SHA-256, DER, base64); keys stay Dutch:
EUZOS-beheer-v1
omgeving=<environment>
server=<server>
sessie=<id>
beheerip=<admin_ip>
gebruiker=<user>
tijd=<Unix time of the phone>
uitdaging=<challenge>
apparaat=<device>
Confirm → 200 {ok: true, admin_ip}; 401 (signature), 409 (fetch first), 400 (clock more than 2 minutes off), 410, 429.
See also
- docs/ADMIN-PAGE.md
- docs/PROFILE-PROTOCOL.md, "Administration"
- Phone app · CLI · Environments and deploy
EUZOS