Skip to content

Commit d7b641c

Browse files
authored
Add API docs, improve metrics and configurable OIDC unmatched-user policy (#312)
* Add API documentation * Refine README description * Refine metrics response colors Closes #309 * Improve CrowdSec runtime metrics page * Add OIDC unmatched user policy Closes #311
1 parent b14a03d commit d7b641c

24 files changed

Lines changed: 1140 additions & 162 deletions

API.md

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# CrowdSec Web UI API
2+
3+
All API routes return JSON unless a route explicitly redirects for OIDC login/callback. Routes are served below `/api`, or below `${BASE_PATH}/api` when `BASE_PATH` is configured. `GET /api/health` is always registered at the root path; when `BASE_PATH` is set, `${BASE_PATH}/api/health` is registered too.
4+
5+
The browser UI authenticates with the HTTP-only `crowdsec_web_ui_session` cookie. There is no bearer-token API in this codebase. Authentication is enabled for new installs by default; migrated databases from older unauthenticated versions stay disabled until `AUTH_ENABLED=true` is set. When auth is enabled, protected API routes return `401` without a valid session.
6+
7+
Protected application routes also ensure the backend can authenticate to CrowdSec LAPI. If LAPI login fails, they return `502`.
8+
9+
`PERMISSION_READ_ONLY=true` or a read-only user role blocks enforcement and management writes. Blocked requests return:
10+
11+
```json
12+
{ "error": "Read-only mode is enabled", "code": "READ_ONLY" }
13+
```
14+
15+
Read-only mode still allows user preferences and notification read-state writes.
16+
17+
## Common Behavior
18+
19+
### Pagination
20+
21+
List endpoints that support pagination use `page` and `page_size`. Pagination is enabled only when the `page` query parameter is present.
22+
23+
- `page` defaults to `1` and is clamped to at least `1`.
24+
- `page_size` defaults to `50` and is clamped from `10` to `200`.
25+
- Paginated responses use:
26+
27+
```json
28+
{
29+
"data": [],
30+
"pagination": {
31+
"page": 1,
32+
"page_size": 50,
33+
"total": 0,
34+
"total_pages": 0,
35+
"unfiltered_total": 0
36+
},
37+
"selectable_ids": []
38+
}
39+
```
40+
41+
### Search and Filters
42+
43+
`GET /api/alerts` and `GET /api/decisions` support a structured `q` search when paginated. Search supports free text, quoted phrases, `AND`, `OR`, `NOT`, `-`, grouping, field matching with `:`, exact matching with `=`, inequality with `<>`, and date comparisons with `<`, `<=`, `>`, `>=`.
44+
45+
Alert search fields: `id`, `scenario`, `message`, `ip`/`source`, `country`, `as`, `target`, `date`/`created`/`created_at`/`time`, `sim`/`simulation`, `machine`, `origin`.
46+
47+
Decision search fields: `id`, `alert`/`alert_id`, `scenario`/`reason`, `ip`/`value`, `country`, `as`, `target`, `date`/`created`/`created_at`/`time`, `action`, `type`, `status`, `duplicate`, `sim`/`simulation`, `machine`, `origin`.
48+
49+
Date range filters use `dateStart` and `dateEnd`. Use `YYYY-MM-DD` for day buckets or values containing `T` for hour-level comparisons. `tz_offset` is an offset in minutes used for local bucket comparisons when the server has no fixed `TZ` configured.
50+
51+
## Health
52+
53+
| Method | Endpoint | Description |
54+
| --- | --- | --- |
55+
| GET | `/api/health` | Public health check returning `{ "status": "ok" }`. |
56+
57+
## Auth
58+
59+
These routes are mounted below `/api/auth`. Auth setup/login routes are available without a session so onboarding and login can work.
60+
61+
| Method | Endpoint | Description |
62+
| --- | --- | --- |
63+
| GET | `/api/auth/status` | Auth state, setup status, current user/session state, OIDC availability, passkey availability, and password-login state. |
64+
| POST | `/api/auth/setup` | Create the first admin user and start a session. Requires `username` and `password`; only works before any auth user exists. |
65+
| POST | `/api/auth/login` | Password login with `username` and `password`. |
66+
| POST | `/api/auth/logout` | Clear the session cookie. |
67+
| GET | `/api/auth/me` | Current authenticated session user. |
68+
| GET | `/api/auth/settings` | Auth settings visible to the current user, including OIDC settings metadata and password/passkey state. |
69+
| PUT | `/api/auth/settings` | Admin-only auth settings update. Can disable password login and configure OIDC issuer, client ID, client secret, groups claim, admin groups, read-only groups, and unmatched-user policy. |
70+
| POST | `/api/auth/change-password` | Change the current user's password. The user must be logged in with password auth. |
71+
| GET | `/api/auth/passkeys` | List passkeys for the current user. |
72+
| PATCH | `/api/auth/passkeys/:id` | Rename a passkey with `{ "name": "..." }`. |
73+
| DELETE | `/api/auth/passkeys/:id` | Delete one of the current user's passkeys. |
74+
| POST | `/api/auth/webauthn/register/options` | Start passkey registration for the current user. |
75+
| POST | `/api/auth/webauthn/register/verify` | Complete passkey registration. Optional `name` is stored as the passkey label. |
76+
| POST | `/api/auth/webauthn/login/options` | Start passkey login. Optional `username` narrows allowed credentials. |
77+
| POST | `/api/auth/webauthn/login/verify` | Complete passkey login and start a session. |
78+
| GET | `/api/auth/oidc/login` | Redirect to the configured OIDC provider. |
79+
| GET | `/api/auth/oidc/callback` | OIDC callback handler; creates or updates the OIDC user session and redirects to the UI. |
80+
81+
## Configuration
82+
83+
| Method | Endpoint | Description |
84+
| --- | --- | --- |
85+
| GET | `/api/config` | Runtime UI config, LAPI status, sync status, simulation setting, table column preferences, time settings, metrics availability, metrics sidebar preference, and permissions. |
86+
| PUT | `/api/config/metrics-sidebar` | Save the metrics sidebar preference. Body: `{ "visible": true }`. |
87+
| PUT | `/api/config/table-columns` | Save visible columns for one table and viewport. Body: `{ "table": "alerts" \| "decisions", "viewport": "desktop" \| "mobile", "visible_columns": [...] }`. |
88+
| PUT | `/api/config/refresh-interval` | Update the refresh interval. Body: `{ "interval": "manual" \| "0" \| "5s" \| "30s" \| "1m" \| "5m" }`. Blocked in read-only mode. |
89+
| PUT | `/api/config/language` | Save language preference. Body: `{ "language": "browser" }` or a supported locale code. |
90+
91+
Alert columns: `id`, `time`, `scenario`, `country`, `as`, `source`, `machine`, `origin`, `decisions`.
92+
93+
Decision columns: `id`, `time`, `scenario`, `country`, `as`, `source`, `action`, `expiration`, `machine`, `origin`, `alert`.
94+
95+
## Alerts
96+
97+
| Method | Endpoint | Description |
98+
| --- | --- | --- |
99+
| GET | `/api/alerts` | List synced alerts. Without `page`, returns an array. With `page`, returns a paginated response. |
100+
| GET | `/api/alerts/:id` | Fetch alert details from CrowdSec LAPI, hydrate with decisions, and apply simulation visibility. `:id` must be numeric. |
101+
| POST | `/api/alerts/bulk-delete` | Delete multiple alerts by numeric ID. Body: `{ "ids": [1, "2"] }`. Also deletes cached linked decisions. Blocked in read-only mode. |
102+
| DELETE | `/api/alerts/:id` | Delete one alert from CrowdSec LAPI and local cache. `:id` must be numeric. Blocked in read-only mode. |
103+
104+
Supported paginated alert filters: `q`, `ip`, `country`, `scenario`, `as`, `date`, `dateStart`, `dateEnd`, `target`, `simulation`, `tz_offset`.
105+
106+
`simulation` accepts `all`, `live`, or `simulated`; unknown values behave like `all`.
107+
108+
## Decisions
109+
110+
| Method | Endpoint | Description |
111+
| --- | --- | --- |
112+
| GET | `/api/decisions` | List decisions. Without `page`, returns an array. With `page`, returns a paginated response. Active decisions are returned by default. |
113+
| POST | `/api/decisions` | Add a manual CrowdSec decision through LAPI. Body: `{ "ip": "1.2.3.4", "duration": "4h", "reason": "manual", "type": "ban" }`. `type` defaults to `ban` and accepts `ban` or `captcha`. Blocked in read-only mode. |
114+
| POST | `/api/decisions/bulk-delete` | Delete multiple decisions by numeric ID. Body: `{ "ids": [10, "11"] }`. Blocked in read-only mode. |
115+
| DELETE | `/api/decisions/:id` | Delete one decision from CrowdSec LAPI and local cache. `:id` must be numeric. Blocked in read-only mode. |
116+
117+
Supported decision query parameters: `include_expired`, `page`, `page_size`, `q`, `alert_id`, `country`, `scenario`, `as`, `ip`, `target`, `dateStart`, `dateEnd`, `simulation`, `hide_duplicates`, `tz_offset`.
118+
119+
- `include_expired=true` includes expired decisions within the configured lookback window.
120+
- Duplicate decisions are hidden by default in paginated results. Set `hide_duplicates=false` or filter by `alert_id` to show them.
121+
- `simulation` accepts `all`, `live`, or `simulated`; unknown values behave like `all`.
122+
123+
## Cleanup and Cache
124+
125+
| Method | Endpoint | Description |
126+
| --- | --- | --- |
127+
| POST | `/api/cleanup/by-ip` | Delete cached/LAPI alerts and decisions for one IP address or range. Body: `{ "ip": "1.2.3.4" }`. Blocked in read-only mode. |
128+
| POST | `/api/cache/clear` | Clear synced alert/decision data and run a bootstrap sync. Blocked in read-only mode. |
129+
130+
Bulk delete and cleanup responses use:
131+
132+
```json
133+
{
134+
"requested_alerts": 0,
135+
"requested_decisions": 0,
136+
"deleted_alerts": 0,
137+
"deleted_decisions": 0,
138+
"failed": [],
139+
"ip": "1.2.3.4"
140+
}
141+
```
142+
143+
`ip` is only included for cleanup-by-IP responses.
144+
145+
## Stats and Dashboard
146+
147+
| Method | Endpoint | Description |
148+
| --- | --- | --- |
149+
| GET | `/api/stats/alerts` | Alert records shaped for chart/stat consumers within the configured lookback window. |
150+
| GET | `/api/stats/decisions` | Decision records shaped for chart/stat consumers within the configured lookback window. |
151+
| GET | `/api/dashboard/stats` | Aggregated dashboard totals, filtered totals, top targets/countries/scenarios/AS, world-map country data, and history series. |
152+
153+
Supported dashboard filters: `country`, `scenario`, `as`, `ip`, `target`, `dateStart`, `dateEnd`, `simulation`, `granularity`, `tz_offset`.
154+
155+
- `simulation` accepts `all`, `live`, or `simulated`.
156+
- `granularity=hour` returns hourly buckets; any other value uses daily buckets.
157+
158+
## CrowdSec Metrics
159+
160+
| Method | Endpoint | Description |
161+
| --- | --- | --- |
162+
| GET | `/api/metrics/crowdsec` | Fetch and normalize CrowdSec Prometheus metrics. Returns `404` when `CROWDSEC_PROMETHEUS_URL` is not configured and `502` when the metrics fetch fails. |
163+
164+
The response includes `fetched_at`, `totals`, `bouncers`, `machines`, `parserSources`, `parserNodes`, `whitelists`, and `parserTimings`. It can also include runtime-only observability sections: `lapiRoutes` and `appsecEngines`.
165+
166+
Parser, LAPI latency, AppSec, bouncer, and machine values are derived from the current CrowdSec Prometheus scrape. The endpoint does not query Prometheus history or calculate Grafana-style `rate()`/`increase()` windows, so metrics that require time-window tracking are intentionally omitted.
167+
168+
## Notifications
169+
170+
Notification inbox routes operate on generated notification items.
171+
172+
| Method | Endpoint | Description |
173+
| --- | --- | --- |
174+
| GET | `/api/notifications` | List notification items. Supports `page` and `page_size`; defaults to page `1`, size `50` when omitted. |
175+
| POST | `/api/notifications/:id/read` | Mark one notification as read. Allowed in read-only mode. |
176+
| POST | `/api/notifications/bulk-read` | Mark multiple notifications as read. Body: `{ "ids": ["id-1", "id-2"] }`. Allowed in read-only mode. |
177+
| POST | `/api/notifications/bulk-delete` | Delete multiple notifications. Body: `{ "ids": ["id-1", "id-2"] }`. Blocked in read-only mode. |
178+
| POST | `/api/notifications/delete-read` | Delete all read notifications. Blocked in read-only mode. |
179+
| DELETE | `/api/notifications/:id` | Delete one notification. Blocked in read-only mode. |
180+
| GET | `/api/notifications/settings` | List notification channels and rules. |
181+
182+
Notification configuration routes manage destinations and rule definitions.
183+
184+
| Method | Endpoint | Description |
185+
| --- | --- | --- |
186+
| POST | `/api/notification-channels` | Create a notification channel. Blocked in read-only mode. |
187+
| PUT | `/api/notification-channels/:id` | Update a notification channel. Blocked in read-only mode. |
188+
| DELETE | `/api/notification-channels/:id` | Delete a notification channel. Blocked in read-only mode. |
189+
| POST | `/api/notification-channels/:id/test` | Send a test notification through a saved channel. Blocked in read-only mode. |
190+
| POST | `/api/notification-rules` | Create a notification rule. Blocked in read-only mode. |
191+
| PUT | `/api/notification-rules/:id` | Update a notification rule. Blocked in read-only mode. |
192+
| DELETE | `/api/notification-rules/:id` | Delete a notification rule. Blocked in read-only mode. |
193+
194+
Channel create/update body:
195+
196+
```json
197+
{
198+
"name": "Security alerts",
199+
"type": "ntfy",
200+
"enabled": true,
201+
"config": {}
202+
}
203+
```
204+
205+
Supported channel types: `ntfy`, `gotify`, `email`, `mqtt`, `webhook`.
206+
207+
Rule create/update body:
208+
209+
```json
210+
{
211+
"name": "IP bans",
212+
"type": "ip-ban",
213+
"enabled": true,
214+
"severity": "warning",
215+
"channel_ids": ["channel-id"],
216+
"config": {}
217+
}
218+
```
219+
220+
Supported rule types: `alert-spike`, `alert-threshold`, `new-alert-decision`, `new-cve`, `ip-ban`, `application-update`, `lapi-availability`.
221+
222+
Supported severities: `info`, `warning`, `critical`.
223+
224+
## Update Check
225+
226+
| Method | Endpoint | Description |
227+
| --- | --- | --- |
228+
| GET | `/api/update-check` | Check whether a newer application release is available. Response is not cached. |
229+
230+
Optional query parameters override the runtime version metadata used for the check: `version`, `branch`, `commit_hash`.
231+
232+
## Example Requests
233+
234+
List the first page of active decisions:
235+
236+
```bash
237+
curl -b cookie.txt 'http://localhost:3000/api/decisions?page=1&page_size=50'
238+
```
239+
240+
Search alerts with structured syntax:
241+
242+
```bash
243+
curl -b cookie.txt 'http://localhost:3000/api/alerts?page=1&page_size=50&q=origin:(manual%20OR%20CAPI)%20AND%20-country:us'
244+
```
245+
246+
Add a manual ban:
247+
248+
```bash
249+
curl -b cookie.txt \
250+
-H 'Content-Type: application/json' \
251+
-d '{"ip":"1.2.3.4","duration":"4h","type":"ban","reason":"manual"}' \
252+
http://localhost:3000/api/decisions
253+
```

0 commit comments

Comments
 (0)