Accounts and registration
FAQ › Profile server › Accounts and registration · Nederlands
What is it?
Every EUZOS user has an account on the profile server of an environment (SBX or PROD). The user name is always the e-mail address, and that address must be confirmed (link or 6-digit code from a mail) before the account may do anything. Each account has two profiles: Work or school (internal value zakelijk) and Private (prive). Accounts are created by the admin (CLI or admin page) or, where the environment allows it, by the user: in the phone app, on the PC's login screen ("Create account") or on the web page /registreer. Accounts from a Microsoft/Entra login are created just in time (see Organisations).
How do I…?
How do I create an account as admin?
On the VPS: euzos-profielserver user-create <email> [--first-name F] [--last-name L] [--city C] [--display-name "Name"] [--language nl|en] (password asked interactively, or --password-stdin). The account starts as niet_geverifieerd and the address immediately gets a mail with a link and a code. The same is possible on the admin page (Accounts › new).
How do I let users register themselves?
Set registration_open = yes in the server configuration (default no; open in the sandbox, closed in production according to docs/PROFILE-PROTOCOL.md). Then three entry points work with exactly the same steps and limits (app::register_core):
- the app:
POST /api/app/v2/register; - the PC's login screen, button Create account:
POST /api/pc/register, thenPOST /api/pc/verifywith the code; - the web page
https://<domain>/registreer(the QR code on the PC's registration screen points there).
With registration closed the PC gets 403 reden=gesloten and says "ask the admin".
How do I confirm my e-mail address?
With the link https://<domain>/v/<token> from the mail (the page only shows a form; you type your password and press the button, so a link preview confirms nothing), with the code in the app (POST /api/app/v2/verify), or with the code on the PC ("Type the 6-digit code from your mail"). Confirming always asks for the account's password as well. Link and code are valid 24 hours, single use; the code may be wrong at most 5 times.
How do I get a new confirmation mail?
App: POST /api/app/v2/verify/resend; PC: Resend code (POST /api/pc/resend, the button counts down 60 s). Limits: 1 per minute and 5 per day per address; a new mail invalidates the previous link and code.
How do I change an account's e-mail address?
User: in the app (POST /api/app/v2/email {new} with a password sign-in). Admin: user-email <address> <new address>. The new address gets a mail; the old one stays valid until the new one is confirmed and gets a notice.
How do I block or unblock an account?
user-block <email> sets the status geblokkeerd and, in the same transaction, logs out all PCs and revokes all paired phones. user-unblock <email> makes it actief again; the user then signs in and pairs the phone again.
How do I reset a password?
password <email> [--password-stdin] [--revoke-devices]: logs out all PCs; with --revoke-devices also revokes all phones. There is no self-service password recovery yet (docs/PROFILE-PROTOCOL.md: "can come later").
How do I delete an account (GDPR, right to erasure)?
user-delete <email> asks you to type the address again (--yes <email> for scripts; it must be the same address). It deletes the reports, logs out all PCs, revokes all phones, deletes the vault (including the storage key, which makes the encrypted profile folders unreadable), the settings, verifications, mail counters, the profiles and the account. The admin log keeps only a hash of the address. The admin page does the same in two steps ("Yes, delete permanently").
Can a user export their data?
There is no export command in the code (only erasure, user-delete, and the partial erasures vault-delete and reports-delete). A right-of-access export is not implemented (to verify whether it is planned).
Why do I get the same error for a wrong password and an unknown address?
On purpose: no account enumeration. A wrong password, an unknown address and an unconfirmed account with a wrong password all give 401 onjuist after the same amount of work (a fixed dummy hash). Only after the correct password does the server say the account is not active (403 with the status code).
Details
The address is normalised (trimmed, lower case, ASCII only; IDN domains as punycode), has exactly one @, at most 254 characters, and belongs to at most one account (crate::email::normalize).
Status per account (values on the wire and in the database, Dutch):
| Status | Meaning | Can log in |
|---|---|---|
niet_geverifieerd | new, address not yet confirmed | no (email_niet_geverifieerd) |
actief | address confirmed | yes |
geblokkeerd | blocked by the admin (or by Entra offboarding) | no (account_geblokkeerd) |
email_ontbreekt | safety net: an account from schema 1 without @ | no (email_ontbreekt) |
Password: at least 10 characters (keys::MIN_PASSWORD), at most 200 bytes (keys::MAX_PASSWORD), stored as Argon2id (19 MiB, 2 passes, 1 thread, own 16-byte salt). At most 3 password checks run at once in the whole server.
Self-registration always answers 201 {user, status: "niet_geverifieerd"}, also for an existing address (that address gets a "you already have an account" notice instead). A self-registered account that is never confirmed is removed after 7 days. First name, last name and city (each at most 64 characters) are stored with the account; the display name is first name + last name.
The verification mail (crate::mail) is plain text in Dutch or English, without HTML or tracking. Outside production the subject starts with (sbx) (locally (lokaal) ). Sending is done with Microsoft Graph (mail = graph), SMTP (mail = smtp) or, locally, into a folder (mail = map).
Admin log: user-create, user-email, user-verify, user-block, user-unblock, password, user-delete are recorded in the table admin_log (readable with admin-log); the stored action texts are Dutch (gebruiker-wis, …).
Reference
CLI commands
All as euzos-profielserver [--config <file>] <command>; full list in CLI.
| Command | Arguments | What it does |
|---|---|---|
user-create | <email> [--first-name F] [--last-name L] [--city C] [--display-name "Name"] [--language nl|en] [--password-stdin] | create an account; mail with link and code |
user-email | <address> <new address> | change the address (the new one must be confirmed) |
user-verify | <email> | emergency: confirm without mail (logged) |
user-block | <email> | block; logs out all PCs, revokes all phones |
user-unblock | <email> | status back to actief |
user-delete | <email> [--yes <email>] | GDPR erasure of the whole account |
password | <email> [--password-stdin] [--revoke-devices] | new password; logs out all PCs |
users | — | all accounts with their status |
reports-delete | <email> | delete all reports of this account (GDPR) |
vault-delete | <email> | delete the account's whole vault (GDPR) |
Legacy Dutch names still accepted (hidden): gebruiker-maak, gebruiker-email, gebruiker-verifieer, gebruiker-blokkeer, gebruiker-deblokkeer, gebruiker-wis, wachtwoord, gebruikers, meldingen-wis, kluis-wis; flags --voornaam, --achternaam, --woonplaats, --weergavenaam, --taal, --wachtwoord-stdin, --ja, --trek-apparaten-in.
Functions and modules
| Function / module | Role |
|---|---|
euzos_profileserver::email::normalize | normalise and validate an address |
euzos_profileserver::app::register_core | the one registration routine for app, PC and web page |
euzos_profileserver::app::register, verify, verify_resend, email_change | the app endpoints |
euzos_profileserver::pc_registration::register, verify, resend | the PC endpoints (line format) |
euzos_profileserver::web::register_page, register, verification_page, verify | the web pages /registreer and /v/<token> |
euzos_profileserver::verification::create_and_send, with_code, with_token, resend, change_email, limit | link/code creation, checking and mail limits |
euzos_profileserver::mail | the text of the mails |
euzos_profileserver::gdpr::user_delete | GDPR erasure (user-delete) |
euzos_profileserver::keys | Argon2id, the password gate, MIN_PASSWORD/MAX_PASSWORD |
Config files
In the server configuration (/etc/euzos-profiel/profiel.conf on the VPS; key = value; environment variables EUZOS_PROFILE_<KEY> override).
| Key | Default | Meaning |
|---|---|---|
registration_open (legacy registratie_open) | no | self-registration in app, PC and web page |
mail | local: map; sandbox/production must choose | graph, smtp or map |
mail_dir (legacy mail_map) | mail | folder for mail = map |
smtp_host, smtp_port (smtp_poort, default 587), smtp_user (smtp_gebruiker), smtp_password_file (smtp_wachtwoord_bestand) | — | SMTP sending |
graph_tenant, graph_client_id, graph_secret_file (graph_geheim_bestand) or graph_certificate_file + graph_key_file | — | Graph sending |
sender (legacy afzender) | EUZOS <no-reply@euzos.eu> | sender address |
reply_to (legacy antwoordadres) | — | reply address |
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 |
|---|---|---|---|
| POST | /api/app/v2/register | /api/app/v1/registreer | register from the app |
| POST | /api/app/v2/verify | /api/app/v1/verifieer | confirm with code or token + password |
| POST | /api/app/v2/verify/resend | /api/app/v1/verifieer/opnieuw | new confirmation mail |
| POST | /api/app/v2/email | /api/app/v1/email | change the address |
| POST | /api/pc/register | /api/pc/registreer | register on the PC |
| POST | /api/pc/verify | /api/pc/verifieer | confirm the code on the PC |
| POST | /api/pc/resend | /api/pc/opnieuw | resend the code from the PC |
| GET, POST | /registreer | — (user-visible path, unchanged) | the registration web page |
| GET, POST | /v/<token> | — | the confirmation web page |
Full request/response fields: API.
Protocol messages and formats
PC registration (text/plain, lines key=value, at most 2 KiB):
POST /api/pc/register
email=… wachtwoord=… voornaam=… achternaam=… woonplaats=… taal=nl|en
→ 201 ok + email=<address> | 400 reden=ongeldig|email_ongeldig|wachtwoord_kort
| 403 reden=gesloten | 413 | 429 te_vaak | 503 druk
POST /api/pc/verify
email=… code=<6 digits> wachtwoord=…
→ 200 ok | 400 reden=code_onjuist | 401 wachtwoord_onjuist | 409 email_in_gebruik | 429 te_vaak
POST /api/pc/resend
email=…
→ always 200 ok (no enumeration) | 400 ongeldig | 429 reden=te_vaak
Stored hash of a verification code: SHA-256("EUZOS-verificatiecode-v1\0" || address || "\0" || code); of a link token: SHA-256(token).
App error codes (JSON field code, values unchanged): onjuist, email_niet_geverifieerd, account_geblokkeerd, email_ontbreekt, te_vaak, email_ongeldig, wachtwoord_kort, code_onjuist, wachtwoord_onjuist, link_ongeldig, email_in_gebruik, niet_aangemeld, druk, intern.
EUZOS