fix: surface match metadata in audits and slack logs
Co-authored-by: thewilloftheshadow <thewilloftheshadow@users.noreply.github.com>
This commit is contained in:
@@ -24,6 +24,8 @@ type DiscordPermissionsAuditSummary = {
|
|||||||
ok?: boolean;
|
ok?: boolean;
|
||||||
missing?: string[];
|
missing?: string[];
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
|
matchKey?: string;
|
||||||
|
matchSource?: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,11 +74,15 @@ function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsA
|
|||||||
? entry.missing.map((v) => asString(v)).filter(Boolean)
|
? entry.missing.map((v) => asString(v)).filter(Boolean)
|
||||||
: undefined;
|
: undefined;
|
||||||
const error = asString(entry.error) ?? null;
|
const error = asString(entry.error) ?? null;
|
||||||
|
const matchKey = asString(entry.matchKey) ?? undefined;
|
||||||
|
const matchSource = asString(entry.matchSource) ?? undefined;
|
||||||
return {
|
return {
|
||||||
channelId,
|
channelId,
|
||||||
ok,
|
ok,
|
||||||
missing: missing?.length ? missing : undefined,
|
missing: missing?.length ? missing : undefined,
|
||||||
error,
|
error,
|
||||||
|
matchKey,
|
||||||
|
matchSource,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(Boolean) as DiscordPermissionsAuditSummary["channels"])
|
.filter(Boolean) as DiscordPermissionsAuditSummary["channels"])
|
||||||
@@ -122,11 +128,15 @@ export function collectDiscordStatusIssues(
|
|||||||
if (channel.ok === true) continue;
|
if (channel.ok === true) continue;
|
||||||
const missing = channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : "";
|
const missing = channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : "";
|
||||||
const error = channel.error ? `: ${channel.error}` : "";
|
const error = channel.error ? `: ${channel.error}` : "";
|
||||||
|
const matchMeta =
|
||||||
|
channel.matchKey || channel.matchSource
|
||||||
|
? ` (matchKey=${channel.matchKey ?? "none"} matchSource=${channel.matchSource ?? "none"})`
|
||||||
|
: "";
|
||||||
issues.push({
|
issues.push({
|
||||||
channel: "discord",
|
channel: "discord",
|
||||||
accountId,
|
accountId,
|
||||||
kind: "permissions",
|
kind: "permissions",
|
||||||
message: `Channel ${channel.channelId} permission check failed.${missing}${error}`,
|
message: `Channel ${channel.channelId} permission check failed.${missing}${error}${matchMeta}`,
|
||||||
fix: "Ensure the bot role can view + send in this channel (and that channel overrides don't deny it).",
|
fix: "Ensure the bot role can view + send in this channel (and that channel overrides don't deny it).",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ type TelegramGroupMembershipAuditSummary = {
|
|||||||
ok?: boolean;
|
ok?: boolean;
|
||||||
status?: string | null;
|
status?: string | null;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
|
matchKey?: string;
|
||||||
|
matchSource?: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,7 +55,9 @@ function readTelegramGroupMembershipAuditSummary(
|
|||||||
const ok = typeof entry.ok === "boolean" ? entry.ok : undefined;
|
const ok = typeof entry.ok === "boolean" ? entry.ok : undefined;
|
||||||
const status = asString(entry.status) ?? null;
|
const status = asString(entry.status) ?? null;
|
||||||
const error = asString(entry.error) ?? null;
|
const error = asString(entry.error) ?? null;
|
||||||
return { chatId, ok, status, error };
|
const matchKey = asString(entry.matchKey) ?? undefined;
|
||||||
|
const matchSource = asString(entry.matchSource) ?? undefined;
|
||||||
|
return { chatId, ok, status, error, matchKey, matchSource };
|
||||||
})
|
})
|
||||||
.filter(Boolean) as TelegramGroupMembershipAuditSummary["groups"])
|
.filter(Boolean) as TelegramGroupMembershipAuditSummary["groups"])
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -107,11 +111,15 @@ export function collectTelegramStatusIssues(
|
|||||||
if (group.ok === true) continue;
|
if (group.ok === true) continue;
|
||||||
const status = group.status ? ` status=${group.status}` : "";
|
const status = group.status ? ` status=${group.status}` : "";
|
||||||
const err = group.error ? `: ${group.error}` : "";
|
const err = group.error ? `: ${group.error}` : "";
|
||||||
|
const matchMeta =
|
||||||
|
group.matchKey || group.matchSource
|
||||||
|
? ` (matchKey=${group.matchKey ?? "none"} matchSource=${group.matchSource ?? "none"})`
|
||||||
|
: "";
|
||||||
issues.push({
|
issues.push({
|
||||||
channel: "telegram",
|
channel: "telegram",
|
||||||
accountId,
|
accountId,
|
||||||
kind: "runtime",
|
kind: "runtime",
|
||||||
message: `Group ${group.chatId} not reachable by bot.${status}${err}`,
|
message: `Group ${group.chatId} not reachable by bot.${status}${err}${matchMeta}`,
|
||||||
fix: "Invite the bot to the group, then DM the bot once (/start) and restart the gateway.",
|
fix: "Invite the bot to the group, then DM the bot once (/start) and restart the gateway.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export type DiscordChannelPermissionsAuditEntry = {
|
|||||||
ok: boolean;
|
ok: boolean;
|
||||||
missing?: string[];
|
missing?: string[];
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
|
matchKey?: string;
|
||||||
|
matchSource?: "id";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiscordChannelPermissionsAudit = {
|
export type DiscordChannelPermissionsAudit = {
|
||||||
@@ -97,12 +99,16 @@ export async function auditDiscordChannelPermissions(params: {
|
|||||||
ok: missing.length === 0,
|
ok: missing.length === 0,
|
||||||
missing: missing.length ? missing : undefined,
|
missing: missing.length ? missing : undefined,
|
||||||
error: null,
|
error: null,
|
||||||
|
matchKey: channelId,
|
||||||
|
matchSource: "id",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
channels.push({
|
channels.push({
|
||||||
channelId,
|
channelId,
|
||||||
ok: false,
|
ok: false,
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: err instanceof Error ? err.message : String(err),
|
||||||
|
matchKey: channelId,
|
||||||
|
matchSource: "id",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-
|
|||||||
import type { ResolvedSlackAccount } from "../accounts.js";
|
import type { ResolvedSlackAccount } from "../accounts.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
allowListMatches,
|
|
||||||
normalizeAllowList,
|
normalizeAllowList,
|
||||||
normalizeAllowListLower,
|
normalizeAllowListLower,
|
||||||
|
resolveSlackAllowListMatch,
|
||||||
resolveSlackUserAllowed,
|
resolveSlackUserAllowed,
|
||||||
} from "./allow-list.js";
|
} from "./allow-list.js";
|
||||||
import { resolveSlackChannelConfig, type SlackChannelConfigResolved } from "./channel-config.js";
|
import { resolveSlackChannelConfig, type SlackChannelConfigResolved } from "./channel-config.js";
|
||||||
@@ -201,12 +201,15 @@ export function registerSlackMonitorSlashCommands(params: {
|
|||||||
if (ctx.dmPolicy !== "open") {
|
if (ctx.dmPolicy !== "open") {
|
||||||
const sender = await ctx.resolveUserName(command.user_id);
|
const sender = await ctx.resolveUserName(command.user_id);
|
||||||
const senderName = sender?.name ?? undefined;
|
const senderName = sender?.name ?? undefined;
|
||||||
const permitted = allowListMatches({
|
const allowMatch = resolveSlackAllowListMatch({
|
||||||
allowList: effectiveAllowFromLower,
|
allowList: effectiveAllowFromLower,
|
||||||
id: command.user_id,
|
id: command.user_id,
|
||||||
name: senderName,
|
name: senderName,
|
||||||
});
|
});
|
||||||
if (!permitted) {
|
const allowMatchMeta = `matchKey=${allowMatch.matchKey ?? "none"} matchSource=${
|
||||||
|
allowMatch.matchSource ?? "none"
|
||||||
|
}`;
|
||||||
|
if (!allowMatch.allowed) {
|
||||||
if (ctx.dmPolicy === "pairing") {
|
if (ctx.dmPolicy === "pairing") {
|
||||||
const { code, created } = await upsertChannelPairingRequest({
|
const { code, created } = await upsertChannelPairingRequest({
|
||||||
channel: "slack",
|
channel: "slack",
|
||||||
@@ -214,6 +217,11 @@ export function registerSlackMonitorSlashCommands(params: {
|
|||||||
meta: { name: senderName },
|
meta: { name: senderName },
|
||||||
});
|
});
|
||||||
if (created) {
|
if (created) {
|
||||||
|
logVerbose(
|
||||||
|
`slack pairing request sender=${command.user_id} name=${
|
||||||
|
senderName ?? "unknown"
|
||||||
|
} (${allowMatchMeta})`,
|
||||||
|
);
|
||||||
await respond({
|
await respond({
|
||||||
text: buildPairingReply({
|
text: buildPairingReply({
|
||||||
channel: "slack",
|
channel: "slack",
|
||||||
@@ -224,6 +232,9 @@ export function registerSlackMonitorSlashCommands(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
logVerbose(
|
||||||
|
`slack: blocked slash sender ${command.user_id} (dmPolicy=${ctx.dmPolicy}, ${allowMatchMeta})`,
|
||||||
|
);
|
||||||
await respond({
|
await respond({
|
||||||
text: "You are not authorized to use this command.",
|
text: "You are not authorized to use this command.",
|
||||||
response_type: "ephemeral",
|
response_type: "ephemeral",
|
||||||
@@ -289,11 +300,11 @@ export function registerSlackMonitorSlashCommands(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ownerAllowed = allowListMatches({
|
const ownerAllowed = resolveSlackAllowListMatch({
|
||||||
allowList: effectiveAllowFromLower,
|
allowList: effectiveAllowFromLower,
|
||||||
id: command.user_id,
|
id: command.user_id,
|
||||||
name: senderName,
|
name: senderName,
|
||||||
});
|
}).allowed;
|
||||||
if (isRoomish) {
|
if (isRoomish) {
|
||||||
commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
|
commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
|
||||||
useAccessGroups: ctx.useAccessGroups,
|
useAccessGroups: ctx.useAccessGroups,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export type TelegramGroupMembershipAuditEntry = {
|
|||||||
ok: boolean;
|
ok: boolean;
|
||||||
status?: string | null;
|
status?: string | null;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
|
matchKey?: string;
|
||||||
|
matchSource?: "id";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TelegramGroupMembershipAudit = {
|
export type TelegramGroupMembershipAudit = {
|
||||||
@@ -105,9 +107,16 @@ export async function auditTelegramGroupMembership(params: {
|
|||||||
isRecord(json) && json.ok === false && typeof json.description === "string"
|
isRecord(json) && json.ok === false && typeof json.description === "string"
|
||||||
? json.description
|
? json.description
|
||||||
: `getChatMember failed (${res.status})`;
|
: `getChatMember failed (${res.status})`;
|
||||||
groups.push({ chatId, ok: false, status: null, error: desc });
|
groups.push({
|
||||||
continue;
|
chatId,
|
||||||
}
|
ok: false,
|
||||||
|
status: null,
|
||||||
|
error: desc,
|
||||||
|
matchKey: chatId,
|
||||||
|
matchSource: "id",
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const status = isRecord((json as TelegramApiOk<unknown>).result)
|
const status = isRecord((json as TelegramApiOk<unknown>).result)
|
||||||
? ((json as TelegramApiOk<{ status?: string }>).result.status ?? null)
|
? ((json as TelegramApiOk<{ status?: string }>).result.status ?? null)
|
||||||
: null;
|
: null;
|
||||||
@@ -117,6 +126,8 @@ export async function auditTelegramGroupMembership(params: {
|
|||||||
ok,
|
ok,
|
||||||
status,
|
status,
|
||||||
error: ok ? null : "bot not in group",
|
error: ok ? null : "bot not in group",
|
||||||
|
matchKey: chatId,
|
||||||
|
matchSource: "id",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
groups.push({
|
groups.push({
|
||||||
@@ -124,6 +135,8 @@ export async function auditTelegramGroupMembership(params: {
|
|||||||
ok: false,
|
ok: false,
|
||||||
status: null,
|
status: null,
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: err instanceof Error ? err.message : String(err),
|
||||||
|
matchKey: chatId,
|
||||||
|
matchSource: "id",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user