- NOTIFICATIONS.md: document owner/private fields, channel visibility rules, and user-created channels; add troubleshooting note for private channel visibility - HTTP_API.md: add notification channel API endpoints table and full endpoint reference (GET types, GET/POST/PUT/DELETE channels) - USERS.md: add missing PUT /api/0/users/me endpoint documentation with all three update modes (identity, channels, password) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.2 KiB
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
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] # channels bob has selected
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
Client-declared host ownership
A host can declare its own owner directly in the hbc or hbc_mini client configuration. This is useful for hosts that are not listed in the server config, or during initial setup before a server-side config entry has been created.
~/.hbc.yaml (hbc):
owner: andreas
~/.hbc.json (hbc_mini):
{ "owner": "andreas" }
When set, the value is included in the os_info plugin data sent to the server. The server applies it as host.owner the first time os_info arrives, provided no owner has been configured server-side for that host. Server-configured ownership always takes precedence.
Assigning roles to hosts
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
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:
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
# 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.
# 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:
{ "username": "andreas", "password": "mysecret" }
Response:
{ "token": "<opaque-hex-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 <token> or cookie
Response: { "success": true }
Users
GET /api/0/users
List all users. Admin only.
Response:
[
{ "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:
{ "username": "carol", "full_name": "Carol Jones", "avatar": "", "admin": false, "notification_channels": [] }
PUT /api/0/users/me
Update the current user's profile. All fields are optional — send only what you want to change.
Update display name and avatar:
{ "full_name": "Carol Jones", "avatar": "/avatars/carol.png" }
Change notification channel selection:
{ "notification_channels": ["pushover_ops", "email_ops"] }
Only channels visible to the user (public + own private) are accepted; others are silently dropped.
Change password:
{ "password": { "current": "oldpass", "new": "newpass" } }
Requires the correct current password. New password is hashed before storage.
Response: {"ok": true}
Status codes: 200 OK, 400 (missing/invalid field), 401 (unauthenticated), 403 (wrong current password)
Host Access
GET /api/0/hosts/{hostname}/access
Return owner/managers/monitors for a host. Requires at least monitor role.
Response:
{
"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):
{
"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 return404.
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
HttpOnlyandSameSite=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
- Notifications
- Configuration example:
hbd/config_example.yaml