# User Management Heartbeat supports optional user accounts with role-based access control per host. When no users are configured the server runs in **unauthenticated mode** — all existing behaviour is unchanged. --- ## Overview Users are defined in the server config file. Each host can have an **owner**, zero or more **managers**, and zero or more **monitors**. A **default owner** catches any host that does not name an explicit owner. ### Roles | Role | Inherits | Permissions | |------|----------|-------------| | **monitor** | — | View host status, plugin data, alerts; acknowledge alerts they were notified for | | **manager** | monitor | + Queue commands (`/c`), trigger DNS re-registration (`/n`), queue upgrades (`/u`); add/remove monitors | | **owner** | manager | + Drop host (`/d`); add/remove managers; transfer ownership; update host access | | **admin** *(flag)* | owner on all hosts | Full access to every host and the user list | `admin` is a flag on the user, not a per-host role. An admin user has owner-level access on every host without being listed as owner/manager/monitor. --- ## Configuration ### Defining users ```yaml users: andreas: full_name: Andreas Wrede avatar: /path/to/avatar.png # file path, URL, or base64 data URI (optional) password: pbkdf2:sha256:... # generated with: hbd passwd andreas admin: true # optional — grants server-wide owner access bob: full_name: Bob Smith password: pbkdf2:sha256:... notification_channels: [pushover_standard] carol: full_name: Carol Jones password: pbkdf2:sha256:... default_owner: andreas # owns hosts with no explicit owner # falls back to the first admin user if omitted ``` ### Assigning roles to hosts ```yaml hosts: webserver01: owner: andreas managers: [bob] monitors: [carol] threshold_config: default watch: true notification_channels: [pushover_standard] unattended-host: # no owner → owned by default_owner threshold_config: default watch: true ``` ### Generating a password hash ```bash hbd passwd andreas ``` Enter and confirm the password when prompted. Paste the printed hash into the config file under the user's `password` key. You can also generate a hash non-interactively from Python: ```python from hbd.server.users import hash_password print(hash_password("mysecret")) ``` Passwords are stored as PBKDF2-HMAC-SHA256 hashes (260 000 iterations). No third-party libraries are required — only Python's standard `hashlib`. --- ## Authentication When at least one user is defined, every request must be authenticated. Unauthenticated requests to HTML pages are redirected to `/login`; unauthenticated API requests receive `401 Unauthorized`. ### Browser login Navigate to any page — you will be redirected to `/login` automatically. After submitting valid credentials the server sets an `hbd_session` cookie (HttpOnly, SameSite=Lax, 24 h lifetime). All subsequent requests, including JavaScript `fetch()` calls on the dashboards, carry the cookie automatically. To log out, visit `/logout`. ### API / programmatic login ```bash # Log in and capture the token TOKEN=$(curl -s -X POST http://localhost:50004/api/0/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"andreas","password":"mysecret"}' | jq -r .token) # Use the token in subsequent requests curl -H "Authorization: Bearer $TOKEN" http://localhost:50004/api/0/hosts ``` The token is identical to the session cookie value — both mechanisms work simultaneously. ```bash # Log out curl -s -X POST http://localhost:50004/api/0/auth/logout \ -H "Authorization: Bearer $TOKEN" ``` --- ## API Endpoints ### Authentication #### POST /api/0/auth/login Obtain a session token. **Request body:** ```json { "username": "andreas", "password": "mysecret" } ``` **Response:** ```json { "token": "", "username": "andreas" } ``` Also sets the `hbd_session` cookie for browser clients. **Status codes:** `200 OK`, `401 Unauthorized`, `404` (auth not configured) --- #### POST /api/0/auth/logout Invalidate the current session. **Headers:** `Authorization: Bearer ` or cookie **Response:** `{ "success": true }` --- ### Users #### GET /api/0/users List all users. **Admin only.** **Response:** ```json [ { "username": "andreas", "full_name": "Andreas Wrede", "avatar": "", "admin": true, "notification_channels": [] }, { "username": "bob", "full_name": "Bob Smith", "avatar": "", "admin": false, "notification_channels": ["pushover_standard"] } ] ``` --- #### GET /api/0/users/me Return the currently authenticated user's profile. **Response:** ```json { "username": "carol", "full_name": "Carol Jones", "avatar": "", "admin": false, "notification_channels": [] } ``` --- ### Host Access #### GET /api/0/hosts/{hostname}/access Return owner/managers/monitors for a host. Requires at least **monitor** role. **Response:** ```json { "owner": "andreas", "managers": ["bob"], "monitors": ["carol"] } ``` --- #### PUT /api/0/hosts/{hostname}/access Update owner/managers/monitors. Requires **owner** role or admin. **Request body** (all fields optional): ```json { "owner": "bob", "managers": ["carol"], "monitors": [] } ``` Changes take effect immediately in memory. They are not written back to the config file — reload (`SIGHUP`) will re-apply config values. To make changes permanent, update the config file. --- ## Host visibility When users are configured, `GET /api/0/hosts` only returns hosts the authenticated user has at least monitor access to. Admins see all hosts. --- ## Config reload On `SIGHUP`, the server reloads the config file, re-loads the user registry, and re-applies `owner`/`managers`/`monitors` from config to all known hosts. Existing sessions remain valid after a reload. --- ## No-auth mode If `users:` is absent or empty, the server starts in **unauthenticated mode**: - No login required — all pages and API endpoints are accessible without credentials. - All permission checks pass unconditionally. - `/login`, `/logout`, and the auth/user API endpoints return `404`. This preserves full backwards compatibility with existing deployments. --- ## Security notes - Session tokens are 64-character cryptographically random hex strings (`secrets.token_hex(32)`). - Sessions expire after 24 hours (configurable via `users_mod.SESSION_TTL`). - Cookies are `HttpOnly` and `SameSite=Lax` — they are not accessible to JavaScript and are not sent on cross-site requests. - The HTTP API does not yet enforce TLS. For production use, place hbd behind a TLS-terminating reverse proxy (nginx, Caddy, etc.) or enable WSS. --- ## See Also - [HTTP API Documentation](HTTP_API.md) - [Notifications](NOTIFICATIONS.md) - Configuration example: `hbd/config_example.yaml`