12e8812070
- 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>
287 lines
8.2 KiB
Markdown
287 lines
8.2 KiB
Markdown
# 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] # 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):
|
|
```yaml
|
|
owner: andreas
|
|
```
|
|
|
|
**`~/.hbc.json`** (hbc_mini):
|
|
```json
|
|
{ "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
|
|
|
|
```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": "<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:**
|
|
```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": [] }
|
|
```
|
|
|
|
---
|
|
|
|
#### 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:**
|
|
```json
|
|
{ "full_name": "Carol Jones", "avatar": "/avatars/carol.png" }
|
|
```
|
|
|
|
**Change notification channel selection:**
|
|
```json
|
|
{ "notification_channels": ["pushover_ops", "email_ops"] }
|
|
```
|
|
Only channels visible to the user (public + own private) are accepted; others are silently dropped.
|
|
|
|
**Change password:**
|
|
```json
|
|
{ "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:**
|
|
```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`
|