diff --git a/src/channels/allow-from.test.ts b/src/channels/allow-from.test.ts new file mode 100644 index 0000000000..a802349a1a --- /dev/null +++ b/src/channels/allow-from.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from "vitest"; +import { firstDefined, isSenderIdAllowed, mergeAllowFromSources } from "./allow-from.js"; + +describe("mergeAllowFromSources", () => { + it("merges, trims, and filters empty values", () => { + expect( + mergeAllowFromSources({ + allowFrom: [" line:user:abc ", "", 123], + storeAllowFrom: [" ", "telegram:456"], + }), + ).toEqual(["line:user:abc", "123", "telegram:456"]); + }); +}); + +describe("firstDefined", () => { + it("returns the first non-undefined value", () => { + expect(firstDefined(undefined, undefined, "x", "y")).toBe("x"); + expect(firstDefined(undefined, 0, 1)).toBe(0); + }); +}); + +describe("isSenderIdAllowed", () => { + it("supports per-channel empty-list defaults and wildcard/id matches", () => { + expect( + isSenderIdAllowed( + { + entries: [], + hasEntries: false, + hasWildcard: false, + }, + "123", + true, + ), + ).toBe(true); + expect( + isSenderIdAllowed( + { + entries: [], + hasEntries: false, + hasWildcard: false, + }, + "123", + false, + ), + ).toBe(false); + expect( + isSenderIdAllowed( + { + entries: ["111", "222"], + hasEntries: true, + hasWildcard: true, + }, + undefined, + false, + ), + ).toBe(true); + expect( + isSenderIdAllowed( + { + entries: ["111", "222"], + hasEntries: true, + hasWildcard: false, + }, + "222", + false, + ), + ).toBe(true); + }); +}); diff --git a/src/channels/allow-from.ts b/src/channels/allow-from.ts new file mode 100644 index 0000000000..8ab2f65c11 --- /dev/null +++ b/src/channels/allow-from.ts @@ -0,0 +1,34 @@ +export function mergeAllowFromSources(params: { + allowFrom?: Array; + storeAllowFrom?: string[]; +}): string[] { + return [...(params.allowFrom ?? []), ...(params.storeAllowFrom ?? [])] + .map((value) => String(value).trim()) + .filter(Boolean); +} + +export function firstDefined(...values: Array) { + for (const value of values) { + if (typeof value !== "undefined") { + return value; + } + } + return undefined; +} + +export function isSenderIdAllowed( + allow: { entries: string[]; hasWildcard: boolean; hasEntries: boolean }, + senderId: string | undefined, + allowWhenEmpty: boolean, +): boolean { + if (!allow.hasEntries) { + return allowWhenEmpty; + } + if (allow.hasWildcard) { + return true; + } + if (!senderId) { + return false; + } + return allow.entries.includes(senderId); +} diff --git a/src/line/bot-access.ts b/src/line/bot-access.ts index 4498826611..2c4094406f 100644 --- a/src/line/bot-access.ts +++ b/src/line/bot-access.ts @@ -1,3 +1,5 @@ +import { firstDefined, isSenderIdAllowed, mergeAllowFromSources } from "../channels/allow-from.js"; + export type NormalizedAllowFrom = { entries: string[]; hasWildcard: boolean; @@ -28,33 +30,14 @@ export const normalizeAllowFrom = (list?: Array): NormalizedAll export const normalizeAllowFromWithStore = (params: { allowFrom?: Array; storeAllowFrom?: string[]; -}): NormalizedAllowFrom => { - const combined = [...(params.allowFrom ?? []), ...(params.storeAllowFrom ?? [])]; - return normalizeAllowFrom(combined); -}; - -export const firstDefined = (...values: Array) => { - for (const value of values) { - if (typeof value !== "undefined") { - return value; - } - } - return undefined; -}; +}): NormalizedAllowFrom => normalizeAllowFrom(mergeAllowFromSources(params)); export const isSenderAllowed = (params: { allow: NormalizedAllowFrom; senderId?: string; }): boolean => { const { allow, senderId } = params; - if (!allow.hasEntries) { - return false; - } - if (allow.hasWildcard) { - return true; - } - if (!senderId) { - return false; - } - return allow.entries.includes(senderId); + return isSenderIdAllowed(allow, senderId, false); }; + +export { firstDefined }; diff --git a/src/telegram/bot-access.ts b/src/telegram/bot-access.ts index 05a2034c7d..338788a245 100644 --- a/src/telegram/bot-access.ts +++ b/src/telegram/bot-access.ts @@ -1,3 +1,4 @@ +import { firstDefined, isSenderIdAllowed, mergeAllowFromSources } from "../channels/allow-from.js"; import type { AllowlistMatch } from "../channels/allowlist-match.js"; export type NormalizedAllowFrom = { @@ -53,21 +54,7 @@ export const normalizeAllowFrom = (list?: Array): NormalizedAll export const normalizeAllowFromWithStore = (params: { allowFrom?: Array; storeAllowFrom?: string[]; -}): NormalizedAllowFrom => { - const combined = [...(params.allowFrom ?? []), ...(params.storeAllowFrom ?? [])] - .map((value) => String(value).trim()) - .filter(Boolean); - return normalizeAllowFrom(combined); -}; - -export const firstDefined = (...values: Array) => { - for (const value of values) { - if (typeof value !== "undefined") { - return value; - } - } - return undefined; -}; +}): NormalizedAllowFrom => normalizeAllowFrom(mergeAllowFromSources(params)); export const isSenderAllowed = (params: { allow: NormalizedAllowFrom; @@ -75,18 +62,11 @@ export const isSenderAllowed = (params: { senderUsername?: string; }) => { const { allow, senderId } = params; - if (!allow.hasEntries) { - return true; - } - if (allow.hasWildcard) { - return true; - } - if (senderId && allow.entries.includes(senderId)) { - return true; - } - return false; + return isSenderIdAllowed(allow, senderId, true); }; +export { firstDefined }; + export const resolveSenderAllowMatch = (params: { allow: NormalizedAllowFrom; senderId?: string;