File size: 6,195 Bytes
5c2a829 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | // Allow-from helpers parse and match plugin channel allowlist entries.
import { normalizeOptionalLowercaseString } from "../../packages/normalization-core/src/string-coerce.js";
import {
normalizeStringEntries,
uniqueStrings,
} from "../../packages/normalization-core/src/string-normalization.js";
export { isAllowedParsedChatSender } from "../channels/plugins/chat-target-prefixes.js";
export type {
AllowlistMatch,
AllowlistMatchSource,
CompiledAllowlist,
} from "../channels/allowlist-match.js";
export type { AllowlistUserResolutionLike } from "../channels/allowlists/resolve-utils.js";
export {
compileAllowlist,
formatAllowlistMatchMeta,
resolveAllowlistCandidates,
resolveAllowlistMatchByCandidates,
resolveAllowlistMatchSimple,
resolveCompiledAllowlistMatch,
} from "../channels/allowlist-match.js";
export {
firstDefined,
isSenderIdAllowed,
mergeDmAllowFromSources,
resolveGroupAllowFromSources,
} from "../channels/allow-from.js";
export {
addAllowlistUserEntriesFromConfigEntry,
buildAllowlistResolutionSummary,
canonicalizeAllowlistWithResolvedIds,
mergeAllowlist,
patchAllowlistUsersInConfigEntries,
summarizeMapping,
} from "../channels/allowlists/resolve-utils.js";
/** Lowercase and optionally strip prefixes from allowlist entries before sender comparisons. */
export function formatAllowFromLowercase(params: {
/** Raw allowlist entries from config or channel-specific overrides. */
allowFrom: Array<string | number>;
/** Optional prefix remover for channel aliases such as `tg:` or `zalo:`. */
stripPrefixRe?: RegExp;
}): string[] {
return normalizeStringEntries(params.allowFrom)
.map((entry) => (params.stripPrefixRe ? entry.replace(params.stripPrefixRe, "") : entry))
.map((entry) => normalizeOptionalLowercaseString(entry))
.filter((entry): entry is string => Boolean(entry));
}
/** Normalize allowlist entries through a channel-provided parser or canonicalizer. */
export function formatNormalizedAllowFromEntries(params: {
/** Raw allowlist entries from config or channel-specific overrides. */
allowFrom: Array<string | number>;
/** Channel-specific canonicalizer; empty results are omitted. */
normalizeEntry: (entry: string) => string | undefined | null;
}): string[] {
return normalizeStringEntries(params.allowFrom)
.map((entry) => params.normalizeEntry(entry))
.filter((entry): entry is string => Boolean(entry));
}
type ParsedAllowFromEntry = { value: string } | { error: string };
/** Parse, validate, and deduplicate setup allow-from entries with wildcard support. */
export function parseAllowFromEntries(
raw: string,
parseEntry: (entry: string) => ParsedAllowFromEntry,
): { entries: string[]; error?: string } {
const entries: string[] = [];
for (const entry of normalizeStringEntries(raw.split(/[\n,;]+/g))) {
if (entry === "*") {
entries.push(entry);
continue;
}
const parsed = parseEntry(entry);
if ("error" in parsed) {
return { entries: [], error: parsed.error };
}
entries.push(parsed.value);
}
return { entries: uniqueStrings(normalizeStringEntries(entries)) };
}
/** Resolve basic setup allow-from entries when a channel token is available. */
export async function resolveBasicAllowFromEntries(params: {
token?: string | null;
entries: string[];
resolveEntries: (params: {
token: string;
entries: string[];
}) => Promise<Array<{ input: string; resolved: boolean; id?: string | null }>>;
}): Promise<Array<{ input: string; resolved: boolean; id: string | null }>> {
const token = params.token?.trim();
if (!token) {
return params.entries.map((input) => ({ input, resolved: false, id: null }));
}
return (await params.resolveEntries({ token, entries: params.entries })).map((entry) => ({
input: entry.input,
resolved: entry.resolved,
id: entry.id ?? null,
}));
}
/** Check whether a sender id matches a simple normalized allowlist with wildcard support. */
export function isNormalizedSenderAllowed(params: {
/** Sender id or handle to compare after string coercion and lowercase normalization. */
senderId: string | number;
/** Raw allowlist entries; `*` allows every sender. */
allowFrom: Array<string | number>;
/** Optional prefix remover applied to allowlist entries before comparison. */
stripPrefixRe?: RegExp;
}): boolean {
const normalizedAllow = formatAllowFromLowercase({
allowFrom: params.allowFrom,
stripPrefixRe: params.stripPrefixRe,
});
if (normalizedAllow.length === 0) {
// Empty allowlists deny by default; callers must opt into wildcard access explicitly.
return false;
}
if (normalizedAllow.includes("*")) {
return true;
}
const sender = normalizeOptionalLowercaseString(String(params.senderId));
return sender ? normalizedAllow.includes(sender) : false;
}
/** Serializable allowlist resolution record used by setup/status UI surfaces. */
export type BasicAllowlistResolutionEntry = {
/** Original allowlist input. */
input: string;
/** Whether resolution found a concrete account/user id. */
resolved: boolean;
/** Resolved id when available. */
id?: string;
/** Resolved display name when available. */
name?: string;
/** Optional resolver note for UI or docs output. */
note?: string;
};
/** Clone allowlist resolution entries into a plain serializable shape for UI and docs output. */
export function mapBasicAllowlistResolutionEntries(
entries: BasicAllowlistResolutionEntry[],
): BasicAllowlistResolutionEntry[] {
return entries.map((entry) => ({
input: entry.input,
resolved: entry.resolved,
id: entry.id,
name: entry.name,
note: entry.note,
}));
}
/** Map allowlist inputs sequentially so resolver side effects stay ordered and predictable. */
export async function mapAllowlistResolutionInputs<T>(params: {
/** Ordered allowlist inputs to resolve. */
inputs: string[];
/** Resolver callback invoked once per input in order. */
mapInput: (input: string) => Promise<T> | T;
}): Promise<T[]> {
const results: T[] = [];
for (const input of params.inputs) {
results.push(await params.mapInput(input));
}
return results;
}
|