feat(admin): scope groups to specific GitHub orgs/users

Add an optional owners[] field to groups (super-admin only). When set,
only webhook events whose repository owner or organization login matches
enter that group's routes; empty owners means no restriction, keeping
existing routes backward compatible.

- types: Group.owners?, groups.ts groupAcceptsOwners()
- webhook.ts eventOwners() extracts repo owner + org login
- discord.ts dispatch skips routes whose group rejects the event owner
- admin-routes.ts validateGroups() validates owners list
This commit is contained in:
RhenCloud 2026-08-02 08:15:49 +08:00
parent 667b8038af
commit 537f4cbb84
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
5 changed files with 46 additions and 1 deletions

View file

@ -1,10 +1,11 @@
import type { Config, FormattedMessage, WebhookEvent, Env } from "./types";
import { formatEvent } from "./formatter";
import { matchRoute } from "./webhook";
import { matchRoute, eventOwners } from "./webhook";
import { log } from "./log";
import { loadTranslations, type Translations } from "./i18n";
import { sendMessage } from "./discord-rest";
import { recordSend } from "./send-log";
import { loadGroups, groupAcceptsOwners } from "./groups";
export function isGatewayEnabled(env: Env): boolean {
return env.DISCORD_GATEWAY_ENABLED === "true";
@ -37,9 +38,18 @@ export async function dispatchEvent(config: Config, event: WebhookEvent, env: En
}),
);
const groups = await loadGroups(env.KV);
const groupById = new Map(groups.map((g) => [g.id, g]));
const owners = eventOwners(event);
for (const route of config.routes) {
if (!matchRoute(route, event)) continue;
if (route.groupId) {
const group = groupById.get(route.groupId);
if (group && !groupAcceptsOwners(group, owners)) continue;
}
const target = route.target.threadId
? `${route.target.channelId}/${route.target.threadId}`
: route.target.channelId;