Files
heartbeat/docs/USERS.md
T

6.8 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]

  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

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": [] }

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 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