mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
docs: document roles, invites, audit log and self-signup
- configuration.md (en/zh): ALLOW_SELF_SIGNUP, AUDIT_RETENTION_DAYS, member schema, role table, invite/self-signup sections, invite KV key, audit_logs D1 table, new admin endpoints - deployment.md (en/zh): migration list through 0005 - README (en/zh) and AGENTS.md: access model, invites, audit log - config.example.yaml: members example; .env.example: new variables - .gitignore: ignore local saas-roadmap.md
This commit is contained in:
parent
11b74aa82a
commit
ce9f6147f1
10 changed files with 184 additions and 76 deletions
|
|
@ -20,6 +20,8 @@ TELEGRAM_WEBHOOK_SECRET=your-webhook-secret
|
||||||
|
|
||||||
# Admin
|
# Admin
|
||||||
ADMIN_USER_IDS=your-github-id,your-github-login
|
ADMIN_USER_IDS=your-github-id,your-github-login
|
||||||
|
# ALLOW_SELF_SIGNUP=1 # non-admin GitHub users get a personal group on first login (default off)
|
||||||
|
# AUDIT_RETENTION_DAYS=90 # audit log retention for the scheduled cleanup (default 90)
|
||||||
|
|
||||||
# Server / public URL (OAuth callbacks + Telegram webhook sync)
|
# Server / public URL (OAuth callbacks + Telegram webhook sync)
|
||||||
BASE_URL=http://localhost:8787
|
BASE_URL=http://localhost:8787
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -14,3 +14,4 @@ data/
|
||||||
config.yaml
|
config.yaml
|
||||||
.direnv/
|
.direnv/
|
||||||
result
|
result
|
||||||
|
saas-roadmap.md
|
||||||
|
|
|
||||||
24
AGENTS.md
24
AGENTS.md
|
|
@ -16,6 +16,9 @@ Core pipeline: GitHub Webhook → Worker (verify + filter + format) → Discord
|
||||||
- Webhook providers: pluggable forge adapters under `src/providers/` (github, gitea) — each verifies its own signature format and normalizes its payload to a GitHub-shaped `WebhookEvent`; GitLab etc. can be added later
|
- Webhook providers: pluggable forge adapters under `src/providers/` (github, gitea) — each verifies its own signature format and normalizes its payload to a GitHub-shaped `WebhookEvent`; GitLab etc. can be added later
|
||||||
- GitHub OAuth: octokit (token is stored hashed for reverse lookup)
|
- GitHub OAuth: octokit (token is stored hashed for reverse lookup)
|
||||||
- Admin WebUI: `/admin` config console, OAuth-session protected via `ADMIN_USER_IDS` whitelist
|
- Admin WebUI: `/admin` config console, OAuth-session protected via `ADMIN_USER_IDS` whitelist
|
||||||
|
- Access control: every group has role-based members (`owner` / `admin` / `viewer`); super admins bypass; legacy `adminIds` are read as owners (backward compatible); owners manage members + invites; `owners` field stays super-only
|
||||||
|
- Invites: single-use 7-day links (`invite:{token}`) for joining a group as admin/viewer; `ALLOW_SELF_SIGNUP=1` gives access-less users a personal group on first login (self-service SaaS entry)
|
||||||
|
- Audit log: every admin operation (logins, group/route/member/invite changes) recorded in D1 `audit_logs`; pruned by the scheduled trigger after `AUDIT_RETENTION_DAYS` (default 90)
|
||||||
- Local dev: wrangler + Miniflare
|
- Local dev: wrangler + Miniflare
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
@ -66,21 +69,24 @@ src/
|
||||||
│ ├── oauth.ts # OAuth URL, callback token exchange, getUserOctokit, comment/getComment/editComment/deleteComment/merge/close actions
|
│ ├── oauth.ts # OAuth URL, callback token exchange, getUserOctokit, comment/getComment/editComment/deleteComment/merge/close actions
|
||||||
│ └── store.ts # KV token CRUD + D1 discord-link/telegram-link mapping (was token-store.ts)
|
│ └── store.ts # KV token CRUD + D1 discord-link/telegram-link mapping (was token-store.ts)
|
||||||
├── web/ # HTTP UI/API routes
|
├── web/ # HTTP UI/API routes
|
||||||
│ ├── oauth-routes.ts # GET /auth/github, callback (admin session / discord-link / telegram-link), DELETE /token/:userId
|
│ ├── oauth-routes.ts # GET /auth/github, callback (admin session / invite accept / self-signup / discord-link / telegram-link), DELETE /token/:userId
|
||||||
│ ├── action-routes.ts # POST /api/comment|merge|close|react (Bearer token auth via KV lookup)
|
│ ├── action-routes.ts # POST /api/comment|merge|close|react (Bearer token auth via shared middleware)
|
||||||
│ ├── admin-routes.ts # /admin UI + GET/PUT /admin/api/routes|groups|me|logs (session + scope auth, validation)
|
│ ├── admin-routes.ts # /admin UI + GET/PUT /admin/api/routes|groups|me|logs|invites|audit (auth middleware + role guards, validation)
|
||||||
|
│ ├── auth.ts # Shared auth middleware + guards: sessionMiddleware, requireAnyAccess, requireGroup(Role), bearerAuthMiddleware, clientIp
|
||||||
|
│ ├── invites.ts # Invite CRUD (KV invite:{token}, 7d TTL) + acceptInvite (join group as admin/viewer)
|
||||||
│ ├── session.ts # Session CRUD (KV session:{id}), isAdminUser, cookie helpers
|
│ ├── session.ts # Session CRUD (KV session:{id}), isAdminUser, cookie helpers
|
||||||
│ ├── groups.ts # Group CRUD (config:groups), resolveScope, hasAnyAccess, groupAcceptsOwners
|
│ ├── groups.ts # Group CRUD (config:groups), member roles (normalizeGroupMembers/memberRole), resolveScope + role helpers (roleAt/canEditRoutes/canEditGroup)
|
||||||
│ ├── home-routes.ts # landing page (zh/en)
|
│ ├── home-routes.ts # landing page (zh/en)
|
||||||
│ ├── legal-routes.ts # /terms + /privacy pages (zh/en)
|
│ ├── legal-routes.ts # /terms + /privacy pages (zh/en)
|
||||||
│ └── richheader-routes.ts # GET /api/richheader: Open Graph page for Telegram avatar link-preview card
|
│ └── richheader-routes.ts # GET /api/richheader: Open Graph page for Telegram avatar link-preview card
|
||||||
└── lib/ # shared infra
|
└── lib/ # shared infra
|
||||||
├── i18n.ts # loadTranslations (KV i18n:{lang} overrides), t() with param interpolation
|
├── i18n.ts # loadTranslations (KV i18n:{lang} overrides), t() with param interpolation
|
||||||
├── send-log.ts # SendRecord, recordSend/getSendLog/getSendLogById (D1 send_logs)
|
├── send-log.ts # SendRecord, recordSend/getSendLog/getSendLogById (D1 send_logs)
|
||||||
|
├── audit.ts # recordAudit/getAuditLog/pruneAuditLogs (D1 audit_logs, best-effort writes)
|
||||||
├── log.ts # JSON console logger (info/warn/error/fatal)
|
├── log.ts # JSON console logger (info/warn/error/fatal)
|
||||||
└── locales/ # en.ts, zh.ts translation dictionaries
|
└── locales/ # en.ts, zh.ts translation dictionaries
|
||||||
|
|
||||||
src/__tests__/ # bun test unit tests (webhook, formatter, discord, telegram, admin, send-log, token-store)
|
src/__tests__/ # bun test unit tests (webhook, formatter, discord, telegram, admin, groups, invites, audit, send-log, token-store)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
|
|
@ -92,6 +98,9 @@ src/__tests__/ # bun test unit tests (webhook, formatter, discord, te
|
||||||
- Verify Telegram webhook calls (X-Telegram-Bot-Api-Secret-Token when configured)
|
- Verify Telegram webhook calls (X-Telegram-Bot-Api-Secret-Token when configured)
|
||||||
- Filter events by: event type, repo name, actor, action, branch, keyword (regex supported)
|
- Filter events by: event type, repo name, actor, action, branch, keyword (regex supported)
|
||||||
- Filter routes by group owner restriction (`Group.owners`), group source-platform restriction (`Group.providers`: github/gitea), and skip fallback routes whenever a regular route matched; stop evaluating further routes when a matched route has `stop: true`
|
- Filter routes by group owner restriction (`Group.owners`), group source-platform restriction (`Group.providers`: github/gitea), and skip fallback routes whenever a regular route matched; stop evaluating further routes when a matched route has `stop: true`
|
||||||
|
- Enforce role-based access on every admin API: super admins bypass, `owner` manages the group (routes/members/invites/settings), `admin` edits routes, `viewer` is read-only; legacy `adminIds` groups resolve to `owner` members
|
||||||
|
- Issue single-use 7-day group invite links (`invite:{token}`); accepting joins as admin/viewer (never owner); `ALLOW_SELF_SIGNUP=1` creates a deterministic personal group (`u-{userId}`) on first login
|
||||||
|
- Record every admin operation (login/logout, group/route/member/invite changes) to D1 `audit_logs`; the scheduled trigger prunes entries past `AUDIT_RETENTION_DAYS`
|
||||||
- Mention Discord roles on route trigger: route-level `discordRoleIds` are rendered as `<@&id>` into the Discord message `content` (Telegram targets ignore the field)
|
- Mention Discord roles on route trigger: route-level `discordRoleIds` are rendered as `<@&id>` into the Discord message `content` (Telegram targets ignore the field)
|
||||||
- Format 28 event types as platform-neutral messages (Discord embeds + Telegram HTML)
|
- Format 28 event types as platform-neutral messages (Discord embeds + Telegram HTML)
|
||||||
- Route messages to Discord channels/threads and Telegram chats/topics via REST
|
- Route messages to Discord channels/threads and Telegram chats/topics via REST
|
||||||
|
|
@ -143,7 +152,8 @@ Rule: no functional change ships without its documentation; docs and code must n
|
||||||
- **Production**: `wrangler secret put <NAME>` for each secret
|
- **Production**: `wrangler secret put <NAME>` for each secret
|
||||||
- **Routes**: KV key `config:routes` (JSON array, empty until configured)
|
- **Routes**: KV key `config:routes` (JSON array, empty until configured)
|
||||||
- **KV namespace**: Required binding for token/state/config/session storage
|
- **KV namespace**: Required binding for token/state/config/session storage
|
||||||
- **D1 database**: Binding `DB` (database `webhooker`, id `214a0104-3235-47c0-b7bf-ddda95f3c8ac`) for `send_logs` + `discord_links` + `telegram_links` tables
|
- **D1 database**: Binding `DB` (database `webhooker`, id `214a0104-3235-47c0-b7bf-ddda95f3c8ac`) for `send_logs` + `audit_logs` + `discord_links` + `telegram_links` tables
|
||||||
|
- **Access control**: `ADMIN_USER_IDS` (super admins), `ALLOW_SELF_SIGNUP` (optional personal group on first login), `AUDIT_RETENTION_DAYS` (default 90) — all plain env vars, not secrets
|
||||||
- **Discord**: `DISCORD_PUBLIC_KEY` (Interactions Endpoint signature verification, from Discord Developer Portal) and `DISCORD_APPLICATION_ID` (optional, auto-resolved via `GET /oauth2/applications/@me` when omitted) are required for interactions
|
- **Discord**: `DISCORD_PUBLIC_KEY` (Interactions Endpoint signature verification, from Discord Developer Portal) and `DISCORD_APPLICATION_ID` (optional, auto-resolved via `GET /oauth2/applications/@me` when omitted) are required for interactions
|
||||||
- **Telegram**: `TELEGRAM_TOKEN` (Bot API token from BotFather) required for Telegram routes; `TELEGRAM_WEBHOOK_SECRET` (optional secret token for `POST /telegram/webhook` verification); avatars are sent as a link-preview card via the built-in `GET /api/richheader` (overridable with `TELEGRAM_RICH_HEADER_HOST`)
|
- **Telegram**: `TELEGRAM_TOKEN` (Bot API token from BotFather) required for Telegram routes; `TELEGRAM_WEBHOOK_SECRET` (optional secret token for `POST /telegram/webhook` verification); avatars are sent as a link-preview card via the built-in `GET /api/richheader` (overridable with `TELEGRAM_RICH_HEADER_HOST`)
|
||||||
- **Webhook providers**: `GITEA_WEBHOOK_SECRET` (required to receive Gitea webhooks; Gitea signs `X-Gitea-Signature` with the hex HMAC-SHA256 of the body)
|
- **Webhook providers**: `GITEA_WEBHOOK_SECRET` (required to receive Gitea webhooks; Gitea signs `X-Gitea-Signature` with the hex HMAC-SHA256 of the body)
|
||||||
|
|
@ -158,7 +168,7 @@ npx wrangler kv namespace create KV
|
||||||
# Update wrangler.jsonc with KV ID
|
# Update wrangler.jsonc with KV ID
|
||||||
npx wrangler d1 create webhooker
|
npx wrangler d1 create webhooker
|
||||||
# Update wrangler.jsonc d1_databases with the database ID
|
# Update wrangler.jsonc d1_databases with the database ID
|
||||||
npm run db:migrate:prod # wrangler d1 migrations apply webhooker --remote (migrations/0001..0003)
|
npm run db:migrate:prod # wrangler d1 migrations apply webhooker --remote (migrations/0001..0005)
|
||||||
npx wrangler deploy
|
npx wrangler deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
21
README.md
21
README.md
|
|
@ -65,6 +65,8 @@ npx wrangler dev # Start local dev server
|
||||||
| `TELEGRAM_RICH_HEADER_HOST` | Optional base URL overriding the built-in `GET /api/richheader` for Telegram avatar cards |
|
| `TELEGRAM_RICH_HEADER_HOST` | Optional base URL overriding the built-in `GET /api/richheader` for Telegram avatar cards |
|
||||||
| `BASE_URL` | Public URL for OAuth callbacks and the Telegram webhook sync |
|
| `BASE_URL` | Public URL for OAuth callbacks and the Telegram webhook sync |
|
||||||
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access `/admin` |
|
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access `/admin` |
|
||||||
|
| `ALLOW_SELF_SIGNUP` | `1` to give access-less GitHub users a personal group on first login (default off) |
|
||||||
|
| `AUDIT_RETENTION_DAYS` | Audit-log retention in days for the scheduled cleanup (default 90) |
|
||||||
| `DOCS_URL` | Optional docs site URL used by the landing page |
|
| `DOCS_URL` | Optional docs site URL used by the landing page |
|
||||||
| `GITHUB_REPO_URL` | Optional GitHub repo URL used by the landing page |
|
| `GITHUB_REPO_URL` | Optional GitHub repo URL used by the landing page |
|
||||||
| `LEGAL_CONTACT` | Optional contact shown on `/terms` and `/privacy` |
|
| `LEGAL_CONTACT` | Optional contact shown on `/terms` and `/privacy` |
|
||||||
|
|
@ -110,14 +112,16 @@ Routes belong to **groups** (KV `config:groups`) that scope admin access and can
|
||||||
|
|
||||||
### Web UI (`/admin`)
|
### Web UI (`/admin`)
|
||||||
|
|
||||||
The built-in config console lets you manage routes and groups in the browser (add / edit / delete / toggle / reorder), and inspect send logs — no KV access needed:
|
The built-in config console lets you manage routes and groups in the browser (add / edit / delete / toggle / reorder), inspect send logs, manage group members and invite links, and read the audit log — no KV access needed:
|
||||||
|
|
||||||
1. Set `ADMIN_USER_IDS` to the GitHub user IDs (or logins) allowed to manage the console, e.g. `ADMIN_USER_IDS=12345,RhenCloud`.
|
1. Set `ADMIN_USER_IDS` to the GitHub user IDs (or logins) allowed to manage the console, e.g. `ADMIN_USER_IDS=12345,RhenCloud`.
|
||||||
2. Visit `/admin` and sign in with GitHub. Only users in the whitelist get access.
|
2. Visit `/admin` and sign in with GitHub. Users with no access get `403` — unless `ALLOW_SELF_SIGNUP=1` (they receive a personal group) or they follow a group invite link.
|
||||||
3. Changes are written to KV immediately and picked up by the webhook pipeline.
|
3. Changes are written to KV immediately and picked up by the webhook pipeline.
|
||||||
|
|
||||||
Sign out at `/admin/logout`.
|
Sign out at `/admin/logout`.
|
||||||
|
|
||||||
|
**Access model.** Every group has `members` with a role: `owner` (manage group, members, invites; edit routes), `admin` (edit routes, view logs), or `viewer` (read-only). Super admins (`ADMIN_USER_IDS`) bypass everything. Legacy `adminIds` are read as owners. Owners generate single-use, 7-day invite links from the group page; group admins and viewers can browse, admins edit, owners administer. All admin operations (logins, group/route/member/invite changes) are recorded in the D1 `audit_logs` table, pruned after `AUDIT_RETENTION_DAYS` (default 90).
|
||||||
|
|
||||||
See `config.example.yaml` for full syntax examples.
|
See `config.example.yaml` for full syntax examples.
|
||||||
|
|
||||||
### Filter Types
|
### Filter Types
|
||||||
|
|
@ -157,13 +161,18 @@ Set `exclude: true` to invert any filter.
|
||||||
- `GET /admin` — Config console UI
|
- `GET /admin` — Config console UI
|
||||||
- `GET /admin/login` — Start admin sign-in (GitHub OAuth)
|
- `GET /admin/login` — Start admin sign-in (GitHub OAuth)
|
||||||
- `GET /admin/logout` — Sign out
|
- `GET /admin/logout` — Sign out
|
||||||
|
- `GET /admin/invite?token=…` — Accept a group invite (browser page)
|
||||||
- `GET /admin/api/routes` — List routes
|
- `GET /admin/api/routes` — List routes
|
||||||
- `PUT /admin/api/routes` — Replace routes
|
- `PUT /admin/api/routes` — Replace routes (owner/admin per group)
|
||||||
- `GET /admin/api/groups` — List groups (scoped)
|
- `GET /admin/api/groups` — List groups + your role in each
|
||||||
- `PUT /admin/api/groups` — Replace groups (super admin only)
|
- `PUT /admin/api/groups` — Replace groups (super: all; owner: own groups only)
|
||||||
- `GET /admin/api/groups/:groupId/routes` — List a group's routes
|
- `GET /admin/api/groups/:groupId/routes` — List a group's routes
|
||||||
- `PUT /admin/api/groups/:groupId/routes` — Replace a group's routes
|
- `PUT /admin/api/groups/:groupId/routes` — Replace a group's routes
|
||||||
- `GET /admin/api/me` — Current session / scope
|
- `POST /admin/api/groups/:groupId/invites` — Create invite link (owner)
|
||||||
|
- `GET /admin/api/groups/:groupId/invites` — List pending invites (owner)
|
||||||
|
- `DELETE /admin/api/invites/:token` — Revoke an invite (owner)
|
||||||
|
- `GET /admin/api/audit` — Audit log (scoped)
|
||||||
|
- `GET /admin/api/me` — Current session / scope / roles
|
||||||
- `GET /admin/api/logs` — Send logs (scoped)
|
- `GET /admin/api/logs` — Send logs (scoped)
|
||||||
- `GET /admin/api/logs/:id` — Single send-log entry
|
- `GET /admin/api/logs/:id` — Single send-log entry
|
||||||
|
|
||||||
|
|
|
||||||
21
README.zh.md
21
README.zh.md
|
|
@ -65,6 +65,8 @@ npx wrangler dev # 启动本地开发服务器
|
||||||
| `TELEGRAM_RICH_HEADER_HOST` | 可选;覆盖内置 `GET /api/richheader` 的 Telegram 头像卡片地址 |
|
| `TELEGRAM_RICH_HEADER_HOST` | 可选;覆盖内置 `GET /api/richheader` 的 Telegram 头像卡片地址 |
|
||||||
| `BASE_URL` | 公网地址(用于 OAuth 回调与 Telegram webhook 同步) |
|
| `BASE_URL` | 公网地址(用于 OAuth 回调与 Telegram webhook 同步) |
|
||||||
| `ADMIN_USER_IDS` | 允许访问 `/admin` 的 GitHub 用户 ID(或登录名),逗号分隔 |
|
| `ADMIN_USER_IDS` | 允许访问 `/admin` 的 GitHub 用户 ID(或登录名),逗号分隔 |
|
||||||
|
| `ALLOW_SELF_SIGNUP` | 设为 `1` 时,无权限的 GitHub 用户首次登录自动获得个人分组(默认关闭) |
|
||||||
|
| `AUDIT_RETENTION_DAYS` | 定时清理时审计日志的保留天数(默认 90) |
|
||||||
| `DOCS_URL` | 可选;落地页使用的文档站点 URL |
|
| `DOCS_URL` | 可选;落地页使用的文档站点 URL |
|
||||||
| `GITHUB_REPO_URL` | 可选;落地页使用的 GitHub 仓库 URL |
|
| `GITHUB_REPO_URL` | 可选;落地页使用的 GitHub 仓库 URL |
|
||||||
| `LEGAL_CONTACT` | 可选;`/terms` 与 `/privacy` 页面展示的联系方式 |
|
| `LEGAL_CONTACT` | 可选;`/terms` 与 `/privacy` 页面展示的联系方式 |
|
||||||
|
|
@ -110,14 +112,16 @@ npx wrangler dev # 启动本地开发服务器
|
||||||
|
|
||||||
### Web 控制台(`/admin`)
|
### Web 控制台(`/admin`)
|
||||||
|
|
||||||
内置的配置控制台让你在浏览器中管理路由与分组(新增 / 编辑 / 删除 / 开关 / 排序),并查看发送日志——无需操作 KV:
|
内置的配置控制台让你在浏览器中管理路由与分组(新增 / 编辑 / 删除 / 开关 / 排序)、查看发送日志、管理组成员与邀请链接、阅读审计日志——无需操作 KV:
|
||||||
|
|
||||||
1. 设置 `ADMIN_USER_IDS` 为允许管理控制台的 GitHub 用户 ID(或登录名),例如 `ADMIN_USER_IDS=12345,RhenCloud`。
|
1. 设置 `ADMIN_USER_IDS` 为允许管理控制台的 GitHub 用户 ID(或登录名),例如 `ADMIN_USER_IDS=12345,RhenCloud`。
|
||||||
2. 访问 `/admin` 并用 GitHub 登录,仅白名单内用户可进入。
|
2. 访问 `/admin` 并用 GitHub 登录。无任何权限的用户收到 `403`——除非开启 `ALLOW_SELF_SIGNUP=1`(自动获得个人分组)或通过分组邀请链接加入。
|
||||||
3. 修改会立即写入 KV,webhook 管线随即生效。
|
3. 修改会立即写入 KV,webhook 管线随即生效。
|
||||||
|
|
||||||
在 `/admin/logout` 退出登录。
|
在 `/admin/logout` 退出登录。
|
||||||
|
|
||||||
|
**权限模型。** 每个分组都有带角色的 `members`:`owner`(管理分组、成员、邀请;可编辑路由)、`admin`(编辑路由、查看日志)、`viewer`(只读)。超级管理员(`ADMIN_USER_IDS`)绕过所有角色限制。旧的 `adminIds` 字段按 owner 读取。owner 可从分组页面生成一次性、7 天有效的邀请链接;所有管理操作(登录、分组/路由/成员/邀请变更)都会写入 D1 `audit_logs` 表,并按 `AUDIT_RETENTION_DAYS`(默认 90 天)自动清理。
|
||||||
|
|
||||||
完整语法示例见 `config.example.yaml`。
|
完整语法示例见 `config.example.yaml`。
|
||||||
|
|
||||||
### 过滤器类型
|
### 过滤器类型
|
||||||
|
|
@ -157,13 +161,18 @@ npx wrangler dev # 启动本地开发服务器
|
||||||
- `GET /admin` — 配置控制台页面
|
- `GET /admin` — 配置控制台页面
|
||||||
- `GET /admin/login` — 开始管理员登录(GitHub OAuth)
|
- `GET /admin/login` — 开始管理员登录(GitHub OAuth)
|
||||||
- `GET /admin/logout` — 退出登录
|
- `GET /admin/logout` — 退出登录
|
||||||
|
- `GET /admin/invite?token=…` — 接受分组邀请(浏览器页面)
|
||||||
- `GET /admin/api/routes` — 列出路由
|
- `GET /admin/api/routes` — 列出路由
|
||||||
- `PUT /admin/api/routes` — 替换路由
|
- `PUT /admin/api/routes` — 替换路由(按分组 owner/admin 权限)
|
||||||
- `GET /admin/api/groups` — 列出分组(按权限过滤)
|
- `GET /admin/api/groups` — 列出分组 + 你的角色
|
||||||
- `PUT /admin/api/groups` — 替换分组(仅超级管理员)
|
- `PUT /admin/api/groups` — 替换分组(超管全量;owner 仅自己的组)
|
||||||
- `GET /admin/api/groups/:groupId/routes` — 列出某分组的路由
|
- `GET /admin/api/groups/:groupId/routes` — 列出某分组的路由
|
||||||
- `PUT /admin/api/groups/:groupId/routes` — 替换某分组的路由
|
- `PUT /admin/api/groups/:groupId/routes` — 替换某分组的路由
|
||||||
- `GET /admin/api/me` — 当前会话 / 权限范围
|
- `POST /admin/api/groups/:groupId/invites` — 创建邀请链接(owner)
|
||||||
|
- `GET /admin/api/groups/:groupId/invites` — 列出待接受邀请(owner)
|
||||||
|
- `DELETE /admin/api/invites/:token` — 撤销邀请(owner)
|
||||||
|
- `GET /admin/api/audit` — 审计日志(按权限过滤)
|
||||||
|
- `GET /admin/api/me` — 当前会话 / 权限范围 / 角色
|
||||||
- `GET /admin/api/logs` — 发送日志(按权限过滤)
|
- `GET /admin/api/logs` — 发送日志(按权限过滤)
|
||||||
- `GET /admin/api/logs/:id` — 单条发送日志
|
- `GET /admin/api/logs/:id` — 单条发送日志
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@
|
||||||
groups:
|
groups:
|
||||||
- id: default
|
- id: default
|
||||||
name: "Default"
|
name: "Default"
|
||||||
adminIds: ["your-github-id"]
|
# Members with roles (owner / admin / viewer); legacy adminIds still work and are treated as owners.
|
||||||
|
members:
|
||||||
|
- login: "your-github-id"
|
||||||
|
role: owner
|
||||||
# owners: ["myorg"] # restrict events to this org/user (optional)
|
# owners: ["myorg"] # restrict events to this org/user (optional)
|
||||||
# providers: ["github", "gitea"] # restrict to source platforms (optional; empty = all)
|
# providers: ["github", "gitea"] # restrict to source platforms (optional; empty = all)
|
||||||
# emoji: true # include emoji in messages (default true)
|
# emoji: true # include emoji in messages (default true)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ WebHooker requires several secrets to function. For local development, store the
|
||||||
| `TELEGRAM_RICH_HEADER_HOST` | Base URL of an external rich-header service; when unset, the built-in `GET /api/richheader` serves the Telegram avatar card | Built-in `/api/richheader` |
|
| `TELEGRAM_RICH_HEADER_HOST` | Base URL of an external rich-header service; when unset, the built-in `GET /api/richheader` serves the Telegram avatar card | Built-in `/api/richheader` |
|
||||||
| `BASE_URL` | Public URL for OAuth callbacks | `http://localhost:8787` |
|
| `BASE_URL` | Public URL for OAuth callbacks | `http://localhost:8787` |
|
||||||
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access the Web UI | Disabled |
|
| `ADMIN_USER_IDS` | Comma-separated GitHub user IDs (or logins) allowed to access the Web UI | Disabled |
|
||||||
|
| `ALLOW_SELF_SIGNUP` | When enabled (`1`/`true`), GitHub users without any group access get a personal group on first login instead of `403` | Disabled |
|
||||||
|
| `AUDIT_RETENTION_DAYS` | Audit-log retention in days for the scheduled cleanup | `90` |
|
||||||
|
|
||||||
## Webhook Providers
|
## Webhook Providers
|
||||||
|
|
||||||
|
|
@ -48,26 +50,31 @@ WebHooker ships with a built-in config console at `/admin` for managing routes i
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
1. Configure `ADMIN_USER_IDS` with the GitHub user IDs allowed to manage routes. Logins are also accepted, e.g. `ADMIN_USER_IDS=12345,RhenCloud`. If unset, the console is disabled.
|
1. Configure `ADMIN_USER_IDS` with the GitHub user IDs allowed to manage everything. Logins are also accepted, e.g. `ADMIN_USER_IDS=12345,RhenCloud`. If unset, the console is disabled (unless `ALLOW_SELF_SIGNUP` is enabled).
|
||||||
2. Open `/admin` and sign in with GitHub.
|
2. Open `/admin` and sign in with GitHub.
|
||||||
3. Only users in the whitelist receive a session cookie; everyone else gets `403`.
|
3. Users without any access get `403`, except when `ALLOW_SELF_SIGNUP=1` (they receive a personal group) or when they follow a group [invite link](#invites).
|
||||||
|
|
||||||
### Endpoints
|
### Endpoints
|
||||||
|
|
||||||
| Endpoint | Description |
|
| Endpoint | Description |
|
||||||
| ---------------------------------- | --------------------------------------- |
|
| ----------------------------------------- | -------------------------------------------- |
|
||||||
| `GET /admin` | Config console UI |
|
| `GET /admin` | Config console UI |
|
||||||
| `GET /admin/login` | Start GitHub OAuth sign-in |
|
| `GET /admin/login` | Start GitHub OAuth sign-in |
|
||||||
| `GET /admin/logout` | Destroy session |
|
| `GET /admin/logout` | Destroy session |
|
||||||
| `GET /admin/api/me` | Current session, scope, and groups |
|
| `GET /admin/invite?token=…` | Accept a group invite (browser page) |
|
||||||
| `GET /admin/api/routes` | List routes (admin only) |
|
| `GET /admin/api/me` | Current session, scope, groups, and roles |
|
||||||
| `PUT /admin/api/routes` | Replace routes (admin only) |
|
| `GET /admin/api/routes` | List routes (scoped to access) |
|
||||||
| `GET /admin/api/groups` | List groups (scoped to access) |
|
| `PUT /admin/api/routes` | Replace routes (owner/admin per group) |
|
||||||
| `PUT /admin/api/groups` | Replace groups (super admin only) |
|
| `GET /admin/api/groups` | List groups + the signed-in user's role each |
|
||||||
| `GET /admin/api/groups/:id/routes` | List a group's routes |
|
| `PUT /admin/api/groups` | Replace groups (super: all; owner: own only) |
|
||||||
| `PUT /admin/api/groups/:id/routes` | Replace a group's routes |
|
| `GET /admin/api/groups/:id/routes` | List a group's routes |
|
||||||
| `GET /admin/api/logs` | Send logs (scoped to accessible routes) |
|
| `PUT /admin/api/groups/:id/routes` | Replace a group's routes (owner/admin) |
|
||||||
| `GET /admin/api/logs/:id` | Single send-log entry (scoped) |
|
| `GET /admin/api/logs` | Send logs (scoped to accessible routes) |
|
||||||
|
| `GET /admin/api/logs/:id` | Single send-log entry (scoped) |
|
||||||
|
| `POST /admin/api/groups/:id/invites` | Create an invite link (owner) |
|
||||||
|
| `GET /admin/api/groups/:id/invites` | List pending invites (owner) |
|
||||||
|
| `DELETE /admin/api/invites/:token` | Revoke an invite (owner) |
|
||||||
|
| `GET /admin/api/audit` | Audit log (scoped to accessible groups) |
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|
@ -167,29 +174,53 @@ Routes belong to groups. Groups scope admin access and can restrict which events
|
||||||
{
|
{
|
||||||
"id": "backend-team",
|
"id": "backend-team",
|
||||||
"name": "Backend Team",
|
"name": "Backend Team",
|
||||||
"adminIds": ["rhencloud"],
|
"members": [
|
||||||
|
{ "login": "rhencloud", "role": "owner" },
|
||||||
|
{ "login": "octobot", "role": "admin" },
|
||||||
|
{ "login": "reader", "role": "viewer" }
|
||||||
|
],
|
||||||
"owners": ["myorg"],
|
"owners": ["myorg"],
|
||||||
"providers": ["github", "gitea"]
|
"providers": ["github", "gitea"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Field | Type | Required | Description |
|
| Field | Type | Required | Description |
|
||||||
| ----------- | -------- | -------- | ------------------------------------------------------------------------- |
|
| ----------- | -------- | -------- | ---------------------------------------------------------------------------- |
|
||||||
| `id` | string | Yes | Lowercase id (`a-z0-9`, `-`); referenced by each route's `groupId` |
|
| `id` | string | Yes | Lowercase id (`a-z0-9`, `-`); referenced by each route's `groupId` |
|
||||||
| `name` | string | Yes | Human-readable group name |
|
| `name` | string | Yes | Human-readable group name |
|
||||||
| `adminIds` | string[] | Yes | GitHub user IDs or logins who may manage this group's routes |
|
| `members` | object[] | No | `{ login, role }` entries; role is `owner`, `admin`, or `viewer` |
|
||||||
| `owners` | string[] | No | Org/user logins whose events are accepted into this group; empty = all |
|
| `adminIds` | string[] | No | Deprecated legacy field; treated as `members` with role `owner` when present |
|
||||||
| `providers` | string[] | No | Source platforms allowed into this group (`github`, `gitea`); empty = all |
|
| `owners` | string[] | No | Org/user logins whose events are accepted into this group; empty = all |
|
||||||
| `emoji` | boolean | No | Whether to include emoji in this group's messages (default `true`) |
|
| `providers` | string[] | No | Source platforms allowed into this group (`github`, `gitea`); empty = all |
|
||||||
|
| `emoji` | boolean | No | Whether to include emoji in this group's messages (default `true`) |
|
||||||
|
|
||||||
|
### Roles
|
||||||
|
|
||||||
|
Every group member has one of three roles. Super admins (`ADMIN_USER_IDS`) always bypass them.
|
||||||
|
|
||||||
|
| Role | View routes/logs | Edit routes | Manage members & invites | Edit group settings |
|
||||||
|
| -------- | ---------------- | ----------- | ------------------------ | ------------------- |
|
||||||
|
| `owner` | ✓ | ✓ | ✓ | ✓ (except `owners`) |
|
||||||
|
| `admin` | ✓ | ✓ | ✗ | ✗ |
|
||||||
|
| `viewer` | ✓ (read-only) | ✗ | ✗ | ✗ |
|
||||||
|
|
||||||
### Access Model
|
### Access Model
|
||||||
|
|
||||||
- **Super admins** (`ADMIN_USER_IDS`) see and edit every group and all routes.
|
- **Super admins** (`ADMIN_USER_IDS`) see and edit every group and all routes; only they can edit a group's `owners` list.
|
||||||
- **Group admins** (`adminIds`) only see and edit the groups they manage; submitting a route outside their groups returns `403`.
|
- **Owners** manage their group's routes, members, invites, name, `emoji`, and `providers`. They cannot remove the last owner or demote themselves when no other owner remains.
|
||||||
|
- **Admins** edit routes inside their groups and view logs; **viewers** get a read-only console.
|
||||||
- Group admin endpoints operate on a single group at a time via `/admin/api/groups/:id/routes`; `groupId` is forced from the path parameter.
|
- 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.
|
- The `owners` list restricts which event actors (sender logins) the group's routes will dispatch at all.
|
||||||
- The `providers` list restricts which forge's events (`github`, `gitea`) the group's routes will dispatch. This lets you keep GitHub and Gitea groups separate even when org/user names collide.
|
- The `providers` list restricts which forge's events (`github`, `gitea`) the group's routes will dispatch. This lets you keep GitHub and Gitea groups separate even when org/user names collide.
|
||||||
|
|
||||||
|
### Invites
|
||||||
|
|
||||||
|
Owners (and super admins) can create single-use invite links valid for 7 days from the group's _Members_ panel. Accepting an invite adds the user with the invited role (`admin` or `viewer` — never `owner`); an existing `viewer` is upgraded to `admin`. Invites are stored in KV as `invite:{token}`.
|
||||||
|
|
||||||
|
### Self Sign-up
|
||||||
|
|
||||||
|
With `ALLOW_SELF_SIGNUP=1`, a GitHub user who has no group access gets a personal group (`u-{userId}`, owned by them) on first login instead of a `403`. This is the entry point for a fully self-service SaaS install; disable it to keep the console invite-only.
|
||||||
|
|
||||||
## Filter Types
|
## Filter Types
|
||||||
|
|
||||||
See the [Filter Tutorial](./filters) for a hands-on guide with worked examples.
|
See the [Filter Tutorial](./filters) for a hands-on guide with worked examples.
|
||||||
|
|
@ -230,6 +261,7 @@ Filters accept either a single string or an array of strings:
|
||||||
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × token expiry |
|
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × token expiry |
|
||||||
| `token-reverse:{sha256}` | User id for reverse lookup by token | 0.9 × token expiry |
|
| `token-reverse:{sha256}` | User id for reverse lookup by token | 0.9 × token expiry |
|
||||||
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 seconds |
|
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 seconds |
|
||||||
|
| `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 days |
|
||||||
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds |
|
| `delivery:{id}` | Webhook delivery id (dedup marker) | 300 seconds |
|
||||||
| `msg:{routeId}:{key}:{target}` | Message id tracking for in-place updates (e.g. `workflow_run`) | 7 days |
|
| `msg:{routeId}:{key}:{target}` | Message id tracking for in-place updates (e.g. `workflow_run`) | 7 days |
|
||||||
| `cmd:guild:{id}` | Guild id whose commands were registered (dedup) | Permanent |
|
| `cmd:guild:{id}` | Guild id whose commands were registered (dedup) | Permanent |
|
||||||
|
|
@ -239,10 +271,13 @@ Filters accept either a single string or an array of strings:
|
||||||
|
|
||||||
## D1 Storage Layout
|
## D1 Storage Layout
|
||||||
|
|
||||||
The D1 database (`DB` binding, database `webhooker`) holds three tables:
|
The D1 database (`DB` binding, database `webhooker`) holds four tables:
|
||||||
|
|
||||||
| Table | Purpose |
|
| Table | Purpose |
|
||||||
| ---------------- | ---------------------------------------------------------------------------------------------- |
|
| ---------------- | ---------------------------------------------------------------------------------------------- |
|
||||||
| `send_logs` | One row per dispatch attempt (route id, event, target, ok/error, duration, error code, detail) |
|
| `send_logs` | One row per dispatch attempt (route id, event, target, ok/error, duration, error code, detail) |
|
||||||
|
| `audit_logs` | One row per admin operation (login/logout, group/route/member/invite changes) |
|
||||||
| `discord_links` | Maps `discord_user_id` → `github_user_id` for `/gh` Discord commands |
|
| `discord_links` | Maps `discord_user_id` → `github_user_id` for `/gh` Discord commands |
|
||||||
| `telegram_links` | Maps `telegram_user_id` → `github_user_id` for `/gh` Telegram commands |
|
| `telegram_links` | Maps `telegram_user_id` → `github_user_id` for `/gh` Telegram commands |
|
||||||
|
|
||||||
|
`audit_logs` is pruned automatically by the scheduled trigger after `AUDIT_RETENTION_DAYS` (default 90).
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,8 @@ If the database already has these tables/columns (e.g. previously migrated with
|
||||||
npx wrangler d1 execute webhooker --remote --file ./migrations/0001_init.sql
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0001_init.sql
|
||||||
npx wrangler d1 execute webhooker --remote --file ./migrations/0002_log_detail.sql
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0002_log_detail.sql
|
||||||
npx wrangler d1 execute webhooker --remote --file ./migrations/0003_telegram_links.sql
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0003_telegram_links.sql
|
||||||
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0004_add_group_id.sql
|
||||||
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0005_audit_logs.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ WebHooker 需要多个密钥才能运行。本地开发时存储在 `.dev.vars`
|
||||||
| --------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------- |
|
| --------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------- |
|
||||||
| `BASE_URL` | OAuth 回调的公开 URL | `http://localhost:8787` |
|
| `BASE_URL` | OAuth 回调的公开 URL | `http://localhost:8787` |
|
||||||
| `ADMIN_USER_IDS` | 允许访问 WebUI 的 GitHub 用户 ID(或登录名),逗号分隔 | 未设置时 WebUI 关闭 |
|
| `ADMIN_USER_IDS` | 允许访问 WebUI 的 GitHub 用户 ID(或登录名),逗号分隔 | 未设置时 WebUI 关闭 |
|
||||||
|
| `ALLOW_SELF_SIGNUP` | 开启(`1`/`true`)后,没有任何分组权限的 GitHub 用户首次登录会自动获得个人分组而非 403 | 关闭 |
|
||||||
|
| `AUDIT_RETENTION_DAYS` | 定时清理时审计日志的保留天数 | `90` |
|
||||||
| `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取),交互功能必需 | 未设置时交互返回 401 |
|
| `DISCORD_PUBLIC_KEY` | Discord 应用的公钥(开发者门户获取),交互功能必需 | 未设置时交互返回 401 |
|
||||||
| `DISCORD_APPLICATION_ID` | Discord 应用 ID;省略时自动获取 | 自动获取 |
|
| `DISCORD_APPLICATION_ID` | Discord 应用 ID;省略时自动获取 | 自动获取 |
|
||||||
| `TELEGRAM_WEBHOOK_SECRET` | `POST /telegram/webhook` 验签密钥(X-Telegram-Bot-Api-Secret-Token) | 未设置时不校验 |
|
| `TELEGRAM_WEBHOOK_SECRET` | `POST /telegram/webhook` 验签密钥(X-Telegram-Bot-Api-Secret-Token) | 未设置时不校验 |
|
||||||
|
|
@ -48,26 +50,31 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
|
|
||||||
### 设置
|
### 设置
|
||||||
|
|
||||||
1. 配置 `ADMIN_USER_IDS`,填写允许管理路由的 GitHub 用户 ID,也支持登录名,例如 `ADMIN_USER_IDS=12345,RhenCloud`。未设置时控制台禁用。
|
1. 配置 `ADMIN_USER_IDS`,填写允许管理路由的 GitHub 用户 ID,也支持登录名,例如 `ADMIN_USER_IDS=12345,RhenCloud`。未设置时控制台禁用(除非开启 `ALLOW_SELF_SIGNUP`)。
|
||||||
2. 打开 `/admin` 并使用 GitHub 登录。
|
2. 打开 `/admin` 并使用 GitHub 登录。
|
||||||
3. 只有白名单中的用户会获得会话 Cookie;其他人收到 `403`。
|
3. 没有任何权限的用户收到 `403`,除非开启 `ALLOW_SELF_SIGNUP=1`(自动获得个人分组)或通过分组[邀请链接](#邀请)加入。
|
||||||
|
|
||||||
### 端点
|
### 端点
|
||||||
|
|
||||||
| 端点 | 说明 |
|
| 端点 | 说明 |
|
||||||
| ---------------------------------- | ---------------------------- |
|
| ------------------------------------- | ------------------------------------ |
|
||||||
| `GET /admin` | 配置控制台页面 |
|
| `GET /admin` | 配置控制台页面 |
|
||||||
| `GET /admin/login` | 开始 GitHub OAuth 登录 |
|
| `GET /admin/login` | 开始 GitHub OAuth 登录 |
|
||||||
| `GET /admin/logout` | 销毁会话 |
|
| `GET /admin/logout` | 销毁会话 |
|
||||||
| `GET /admin/api/me` | 当前会话、权限范围和分组 |
|
| `GET /admin/invite?token=…` | 接受分组邀请(浏览器页面) |
|
||||||
| `GET /admin/api/routes` | 列出路由(仅管理员) |
|
| `GET /admin/api/me` | 当前会话、权限范围、分组和角色 |
|
||||||
| `PUT /admin/api/routes` | 替换路由(仅管理员) |
|
| `GET /admin/api/routes` | 列出路由(按权限过滤) |
|
||||||
| `GET /admin/api/groups` | 列出分组(按权限过滤) |
|
| `PUT /admin/api/routes` | 替换路由(按分组 owner/admin 权限) |
|
||||||
| `PUT /admin/api/groups` | 替换分组(仅超级管理员) |
|
| `GET /admin/api/groups` | 列出分组 + 当前用户在各组的角色 |
|
||||||
| `GET /admin/api/groups/:id/routes` | 列出某分组的路由 |
|
| `PUT /admin/api/groups` | 替换分组(超管全量;owner 仅自己的组) |
|
||||||
| `PUT /admin/api/groups/:id/routes` | 替换某分组的路由 |
|
| `GET /admin/api/groups/:id/routes` | 列出某分组的路由 |
|
||||||
| `GET /admin/api/logs` | 发送日志(按可访问路由过滤) |
|
| `PUT /admin/api/groups/:id/routes` | 替换某分组的路由(owner/admin) |
|
||||||
| `GET /admin/api/logs/:id` | 单条发送日志(按权限过滤) |
|
| `GET /admin/api/logs` | 发送日志(按可访问路由过滤) |
|
||||||
|
| `GET /admin/api/logs/:id` | 单条发送日志(按权限过滤) |
|
||||||
|
| `POST /admin/api/groups/:id/invites` | 创建邀请链接(owner) |
|
||||||
|
| `GET /admin/api/groups/:id/invites` | 列出待接受邀请(owner) |
|
||||||
|
| `DELETE /admin/api/invites/:token` | 撤销邀请(owner) |
|
||||||
|
| `GET /admin/api/audit` | 审计日志(按可访问分组过滤) |
|
||||||
|
|
||||||
控制台支持新增、编辑、删除和开关路由。保存后立即写入 KV `config:routes` 并使配置缓存失效,下一次 webhook 处理即会生效。
|
控制台支持新增、编辑、删除和开关路由。保存后立即写入 KV `config:routes` 并使配置缓存失效,下一次 webhook 处理即会生效。
|
||||||
|
|
||||||
|
|
@ -167,29 +174,53 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
{
|
{
|
||||||
"id": "backend-team",
|
"id": "backend-team",
|
||||||
"name": "后端团队",
|
"name": "后端团队",
|
||||||
"adminIds": ["rhencloud"],
|
"members": [
|
||||||
|
{ "login": "rhencloud", "role": "owner" },
|
||||||
|
{ "login": "octobot", "role": "admin" },
|
||||||
|
{ "login": "reader", "role": "viewer" }
|
||||||
|
],
|
||||||
"owners": ["myorg"],
|
"owners": ["myorg"],
|
||||||
"providers": ["github", "gitea"]
|
"providers": ["github", "gitea"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| 字段 | 类型 | 必需 | 说明 |
|
| 字段 | 类型 | 必需 | 说明 |
|
||||||
| ----------- | -------- | ---- | ----------------------------------------------------------- |
|
| ----------- | -------- | ---- | ------------------------------------------------------------------ |
|
||||||
| `id` | string | 是 | 小写 id(`a-z0-9`、`-`),由每条路由的 `groupId` 引用 |
|
| `id` | string | 是 | 小写 id(`a-z0-9`、`-`),由每条路由的 `groupId` 引用 |
|
||||||
| `name` | string | 是 | 可读的分组名称 |
|
| `name` | string | 是 | 可读的分组名称 |
|
||||||
| `adminIds` | string[] | 是 | 可管理该分组路由的 GitHub 用户 ID 或登录名 |
|
| `members` | object[] | 否 | `{ login, role }` 列表;角色为 `owner`、`admin` 或 `viewer` |
|
||||||
| `owners` | string[] | 否 | 允许事件进入该分组的组织/用户登录名;为空表示不限制 |
|
| `adminIds` | string[] | 否 | 已废弃的旧字段;存在时按 role 为 `owner` 的成员处理 |
|
||||||
| `providers` | string[] | 否 | 允许进入该分组的来源平台(`github`、`gitea`);为空表示全部 |
|
| `owners` | string[] | 否 | 允许事件进入该分组的组织/用户登录名;为空表示不限制 |
|
||||||
| `emoji` | boolean | 否 | 是否在该分组消息中显示 emoji(默认 `true`) |
|
| `providers` | string[] | 否 | 允许进入该分组的来源平台(`github`、`gitea`);为空表示全部 |
|
||||||
|
| `emoji` | boolean | 否 | 是否在该分组消息中显示 emoji(默认 `true`) |
|
||||||
|
|
||||||
|
### 角色
|
||||||
|
|
||||||
|
每个分组成员拥有三种角色之一。超级管理员(`ADMIN_USER_IDS`)始终绕过角色限制。
|
||||||
|
|
||||||
|
| 角色 | 查看路由/日志 | 编辑路由 | 管理成员与邀请 | 编辑分组设置 |
|
||||||
|
| -------- | ------------- | -------- | -------------- | ------------ |
|
||||||
|
| `owner` | ✓ | ✓ | ✓ | ✓(`owners` 除外) |
|
||||||
|
| `admin` | ✓ | ✓ | ✗ | ✗ |
|
||||||
|
| `viewer` | ✓(只读) | ✗ | ✗ | ✗ |
|
||||||
|
|
||||||
### 权限模型
|
### 权限模型
|
||||||
|
|
||||||
- **超级管理员**(`ADMIN_USER_IDS`)可查看和编辑所有分组及全部路由。
|
- **超级管理员**(`ADMIN_USER_IDS`)可查看和编辑所有分组及全部路由;只有他们能修改分组的 `owners` 列表。
|
||||||
- **分组管理员**(`adminIds`)只能查看和编辑其管理的分组;提交其分组之外的路由返回 `403`。
|
- **owner** 管理本组的路由、成员、邀请、名称、`emoji` 与 `providers`;不能移除最后一位 owner,也没有其他 owner 时不能把自己降级。
|
||||||
|
- **admin** 可编辑本组路由并查看日志;**viewer** 只读控制台。
|
||||||
- 分组管理端点通过 `/admin/api/groups/:id/routes` 一次只操作一个分组;`groupId` 由路径参数强制指定。
|
- 分组管理端点通过 `/admin/api/groups/:id/routes` 一次只操作一个分组;`groupId` 由路径参数强制指定。
|
||||||
- `owners` 列表限定哪些事件参与者(发送者登录名)的事件会被该分组的路由投递。
|
- `owners` 列表限定哪些事件参与者(发送者登录名)的事件会被该分组的路由投递。
|
||||||
- `providers` 列表限定哪个 forge(`github`、`gitea`)的事件会被该分组的路由投递。即使组织/用户同名,也可以借此将 GitHub 与 Gitea 分组区分开。
|
- `providers` 列表限定哪个 forge(`github`、`gitea`)的事件会被该分组的路由投递。即使组织/用户同名,也可以借此将 GitHub 与 Gitea 分组区分开。
|
||||||
|
|
||||||
|
### 邀请
|
||||||
|
|
||||||
|
owner(及超级管理员)可在分组的「成员」面板创建一次性邀请链接,7 天内有效。接受邀请后用户以邀请角色(`admin` 或 `viewer`,绝不授予 `owner`)加入;已有的 `viewer` 会被升级为 `admin`。邀请存储在 KV `invite:{token}`。
|
||||||
|
|
||||||
|
### 自助注册
|
||||||
|
|
||||||
|
开启 `ALLOW_SELF_SIGNUP=1` 后,没有分组权限的 GitHub 用户首次登录会获得一个由自己担任 owner 的个人分组(`u-{userId}`),而不是 `403`。这是全自助 SaaS 部署的入口;关闭它则控制台保持仅邀请制。
|
||||||
|
|
||||||
## 过滤器类型
|
## 过滤器类型
|
||||||
|
|
||||||
实操指南见[过滤器教程](./filters),包含完整示例。
|
实操指南见[过滤器教程](./filters),包含完整示例。
|
||||||
|
|
@ -230,6 +261,7 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × Token 有效期 |
|
| `token:{userId}` | `{ userId, accessToken, expiresAt, refreshToken? }` | 0.9 × Token 有效期 |
|
||||||
| `token-reverse:{sha256}` | 用于按 Token 反查的用户 id | 0.9 × Token 有效期 |
|
| `token-reverse:{sha256}` | 用于按 Token 反查的用户 id | 0.9 × Token 有效期 |
|
||||||
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 秒 |
|
| `state:{hex}` | `{ redirectTo, expiresAt, discordUserId?, telegramUserId?, telegramChatId? }` | 600 秒 |
|
||||||
|
| `invite:{token}` | `{ groupId, role, expiresAt, createdBy, note? }` | 7 天 |
|
||||||
| `delivery:{id}` | Webhook 投递 id(去重标记) | 300 秒 |
|
| `delivery:{id}` | Webhook 投递 id(去重标记) | 300 秒 |
|
||||||
| `msg:{routeId}:{key}:{target}` | 原地更新用消息 id 追踪(如 `workflow_run`) | 7 天 |
|
| `msg:{routeId}:{key}:{target}` | 原地更新用消息 id 追踪(如 `workflow_run`) | 7 天 |
|
||||||
| `cmd:guild:{id}` | 已注册命令的服务器 id(去重标记) | 永久 |
|
| `cmd:guild:{id}` | 已注册命令的服务器 id(去重标记) | 永久 |
|
||||||
|
|
@ -239,10 +271,13 @@ WebHooker 内置了位于 `/admin` 的配置控制台,可在浏览器中管理
|
||||||
|
|
||||||
## D1 存储布局
|
## D1 存储布局
|
||||||
|
|
||||||
D1 数据库(`DB` 绑定,数据库 `webhooker`)包含三张表:
|
D1 数据库(`DB` 绑定,数据库 `webhooker`)包含四张表:
|
||||||
|
|
||||||
| 表 | 用途 |
|
| 表 | 用途 |
|
||||||
| ---------------- | ---------------------------------------------------------------------- |
|
| ---------------- | ---------------------------------------------------------------------- |
|
||||||
| `send_logs` | 每次分发尝试一行(路由 id、事件、目标、成功/失败、耗时、错误码、详情) |
|
| `send_logs` | 每次分发尝试一行(路由 id、事件、目标、成功/失败、耗时、错误码、详情) |
|
||||||
|
| `audit_logs` | 每次管理操作一行(登录/登出、分组/路由/成员/邀请变更) |
|
||||||
| `discord_links` | 映射 `discord_user_id` → `github_user_id`,用于 Discord `/gh` 命令 |
|
| `discord_links` | 映射 `discord_user_id` → `github_user_id`,用于 Discord `/gh` 命令 |
|
||||||
| `telegram_links` | 映射 `telegram_user_id` → `github_user_id`,用于 Telegram `/gh` 命令 |
|
| `telegram_links` | 映射 `telegram_user_id` → `github_user_id`,用于 Telegram `/gh` 命令 |
|
||||||
|
|
||||||
|
`audit_logs` 由定时触发器按 `AUDIT_RETENTION_DAYS`(默认 90)自动清理。
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ npm run db:migrate # 将迁移应用到本地(Miniflare)数据库
|
||||||
npx wrangler d1 execute webhooker --remote --file ./migrations/0001_init.sql
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0001_init.sql
|
||||||
npx wrangler d1 execute webhooker --remote --file ./migrations/0002_log_detail.sql
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0002_log_detail.sql
|
||||||
npx wrangler d1 execute webhooker --remote --file ./migrations/0003_telegram_links.sql
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0003_telegram_links.sql
|
||||||
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0004_add_group_id.sql
|
||||||
|
npx wrangler d1 execute webhooker --remote --file ./migrations/0005_audit_logs.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue