docs: document admin groups, OAuth redirect, and API renames

This commit is contained in:
RhenCloud 2026-08-03 00:33:18 +08:00
parent 8c8cfbf211
commit 8c9720b1b3
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
10 changed files with 324 additions and 142 deletions

View file

@ -35,13 +35,19 @@ WebHooker ships with a built-in config console at `/admin` for managing routes i
### Endpoints
| Endpoint | Description |
| ----------------------- | --------------------------- |
| `GET /admin` | Config console UI |
| `GET /admin/login` | Start GitHub OAuth sign-in |
| `GET /admin/logout` | Destroy session |
| `GET /admin/api/routes` | List routes (admin only) |
| `PUT /admin/api/routes` | Replace routes (admin only) |
| Endpoint | Description |
| ---------------------------------- | --------------------------------------- |
| `GET /admin` | Config console UI |
| `GET /admin/login` | Start GitHub OAuth sign-in |
| `GET /admin/logout` | Destroy session |
| `GET /admin/api/me` | Current session, scope, and groups |
| `GET /admin/api/routes` | List routes (admin only) |
| `PUT /admin/api/routes` | Replace routes (admin only) |
| `GET /admin/api/groups` | List groups (scoped to access) |
| `PUT /admin/api/groups` | Replace groups (super admin only) |
| `GET /admin/api/groups/:id/routes` | List a group's routes |
| `PUT /admin/api/groups/:id/routes` | Replace a group's routes |
| `GET /admin/api/logs` | Send logs (scoped to accessible routes) |
The console lets you add, edit, delete, and toggle routes. Saved routes are written to KV `config:routes` immediately and the config cache is invalidated so the webhook pipeline picks them up on the next run.
@ -58,6 +64,8 @@ There are **no default routes** — each route must define its own target. If no
"id": "unique-route-id",
"name": "Human-readable name",
"enabled": true,
"groupId": "my-group",
"fallback": false,
"filters": [
{ "type": "event", "match": "push" },
{ "type": "repo", "match": "org/repo", "exclude": false }
@ -71,6 +79,14 @@ There are **no default routes** — each route must define its own target. If no
`target.channelId` is required and used as-is; there is no fallback to a default channel.
Other route fields:
| Field | Type | Required | Description |
| ---------- | ------- | -------- | ----------------------------------------------------------------------------------------------- |
| `groupId` | string | Yes | Id of the [group](#groups) this route belongs to |
| `fallback` | boolean | No | When `true`, fires only if no non-fallback route matched the event; its own filters are ignored |
| `lang` | string | No | Message language override for this route (e.g. `en`, `zh`); defaults to the global setting |
### Custom Route Example
```json
@ -79,6 +95,7 @@ There are **no default routes** — each route must define its own target. If no
"id": "backend-prs",
"name": "Backend PRs",
"enabled": true,
"groupId": "backend-team",
"filters": [
{ "type": "repo", "match": "myorg/backend" },
{ "type": "event", "match": "pull_request" },
@ -92,6 +109,35 @@ There are **no default routes** — each route must define its own target. If no
]
```
## Groups
Routes belong to groups. Groups scope admin access and can restrict which events flow into them. They are stored in Cloudflare KV under the key `config:groups` as a JSON array.
### Group Schema
```json
{
"id": "backend-team",
"name": "Backend Team",
"adminIds": ["rhencloud"],
"owners": ["myorg"]
}
```
| Field | Type | Required | Description |
| ---------- | -------- | -------- | ---------------------------------------------------------------------- |
| `id` | string | Yes | Lowercase id (`a-z0-9`, `-`); referenced by each route's `groupId` |
| `name` | string | Yes | Human-readable group name |
| `adminIds` | string[] | Yes | GitHub user IDs or logins who may manage this group's routes |
| `owners` | string[] | No | Org/user logins whose events are accepted into this group; empty = all |
### Access Model
- **Super admins** (`ADMIN_USER_IDS`) see and edit every group and all routes.
- **Group admins** (`adminIds`) only see and edit the groups they manage; submitting a route outside their groups returns `403`.
- Group admin endpoints operate on a single group at a time via `/admin/api/groups/:id/routes`; `groupId` is forced from the path parameter.
- The `owners` list restricts which event actors (sender logins) the group's routes will dispatch at all.
## Filter Types
| Type | Matches | Example |
@ -108,7 +154,7 @@ There are **no default routes** — each route must define its own target. If no
- All filters in a route must match for the route to trigger (AND logic)
- Set `"exclude": true` on any filter to invert it (NOT logic)
- `keyword` filter supports regex patterns — falls back to substring match if regex is invalid
- `branch` filter works for push, pull_request, create/delete, workflow_run, and code_scanning_alert events
- `branch` filter works for push, pull_request, pull_request_review, pull_request_review_comment, create/delete, workflow_run, and code_scanning_alert events
### Match Values
@ -121,9 +167,14 @@ Filters accept either a single string or an array of strings:
## KV Storage Layout
| Key Pattern | Value | TTL |
| ---------------- | --------------------------------- | ------------ |
| `config:routes` | JSON array of routes | Permanent |
| `session:{id}` | Admin session `{ userId, login }` | 7 days |
| `token:{userId}` | `{ accessToken, expiresAt }` | Until expiry |
| `state:{hex}` | `{ userId, createdAt }` | 600 seconds |
| Key Pattern | Value | TTL |
| ------------------------ | --------------------------------------------------- | ------------------ |
| `config:routes` | JSON array of routes | Permanent |
| `config:groups` | JSON array of groups | Permanent |
| `session:{id}` | Admin session `{ userId, login }` | 7 days |
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × token expiry |
| `token-reverse:{sha256}` | User id for reverse lookup by token | 0.9 × token expiry |
| `discord-link:{userId}` | GitHub user id linked to a Discord user | Permanent |
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId? }` | 600 seconds |
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds |
| `logs:send:{ts}-{hex}` | Send record | 1 hour |