@poolzin/pool-bot 2026.2.21 → 2026.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
|
@@ -1,55 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Embed, serializePayload, } from "@buape/carbon";
|
|
2
2
|
import { PollLayoutType } from "discord-api-types/payloads/v10";
|
|
3
3
|
import { Routes } from "discord-api-types/v10";
|
|
4
4
|
import { loadConfig } from "../config/config.js";
|
|
5
|
-
import { createDiscordRetryRunner } from "../infra/retry-policy.js";
|
|
6
5
|
import { normalizePollDurationHours, normalizePollInput } from "../polls.js";
|
|
7
6
|
import { loadWebMedia } from "../web/media.js";
|
|
8
7
|
import { resolveDiscordAccount } from "./accounts.js";
|
|
9
8
|
import { chunkDiscordTextWithMode } from "./chunk.js";
|
|
9
|
+
import { createDiscordClient, resolveDiscordRest } from "./client.js";
|
|
10
10
|
import { fetchChannelPermissionsDiscord, isThreadChannelType } from "./send.permissions.js";
|
|
11
11
|
import { DiscordSendError } from "./send.types.js";
|
|
12
12
|
import { parseDiscordTarget, resolveDiscordTarget } from "./targets.js";
|
|
13
|
-
import { normalizeDiscordToken } from "./token.js";
|
|
14
13
|
const DISCORD_TEXT_LIMIT = 2000;
|
|
15
14
|
const DISCORD_MAX_STICKERS = 3;
|
|
16
15
|
const DISCORD_POLL_MAX_ANSWERS = 10;
|
|
17
16
|
const DISCORD_POLL_MAX_DURATION_HOURS = 32 * 24;
|
|
18
17
|
const DISCORD_MISSING_PERMISSIONS = 50013;
|
|
19
18
|
const DISCORD_CANNOT_DM = 50007;
|
|
20
|
-
export const SUPPRESS_NOTIFICATIONS_FLAG = 1 << 12;
|
|
21
|
-
function resolveToken(params) {
|
|
22
|
-
const explicit = normalizeDiscordToken(params.explicit);
|
|
23
|
-
if (explicit) {
|
|
24
|
-
return explicit;
|
|
25
|
-
}
|
|
26
|
-
const fallback = normalizeDiscordToken(params.fallbackToken);
|
|
27
|
-
if (!fallback) {
|
|
28
|
-
throw new Error(`Discord bot token missing for account "${params.accountId}" (set discord.accounts.${params.accountId}.token or DISCORD_BOT_TOKEN for default).`);
|
|
29
|
-
}
|
|
30
|
-
return fallback;
|
|
31
|
-
}
|
|
32
|
-
function resolveRest(token, rest) {
|
|
33
|
-
return rest ?? new RequestClient(token);
|
|
34
|
-
}
|
|
35
|
-
function createDiscordClient(opts, cfg = loadConfig()) {
|
|
36
|
-
const account = resolveDiscordAccount({ cfg, accountId: opts.accountId });
|
|
37
|
-
const token = resolveToken({
|
|
38
|
-
explicit: opts.token,
|
|
39
|
-
accountId: account.accountId,
|
|
40
|
-
fallbackToken: account.token,
|
|
41
|
-
});
|
|
42
|
-
const rest = resolveRest(token, opts.rest);
|
|
43
|
-
const request = createDiscordRetryRunner({
|
|
44
|
-
retry: opts.retry,
|
|
45
|
-
configRetry: account.config.retry,
|
|
46
|
-
verbose: opts.verbose,
|
|
47
|
-
});
|
|
48
|
-
return { token, rest, request };
|
|
49
|
-
}
|
|
50
|
-
function resolveDiscordRest(opts) {
|
|
51
|
-
return createDiscordClient(opts).rest;
|
|
52
|
-
}
|
|
53
19
|
function normalizeReactionEmoji(raw) {
|
|
54
20
|
const trimmed = raw.trim();
|
|
55
21
|
if (!trimmed) {
|
|
@@ -202,6 +168,8 @@ async function resolveChannelId(rest, recipient, request) {
|
|
|
202
168
|
}
|
|
203
169
|
return { channelId: dmChannel.id, dm: true };
|
|
204
170
|
}
|
|
171
|
+
// Discord message flag for silent/suppress notifications
|
|
172
|
+
export const SUPPRESS_NOTIFICATIONS_FLAG = 1 << 12;
|
|
205
173
|
export function buildDiscordTextChunks(text, opts = {}) {
|
|
206
174
|
if (!text) {
|
|
207
175
|
return [];
|
|
@@ -216,62 +184,137 @@ export function buildDiscordTextChunks(text, opts = {}) {
|
|
|
216
184
|
}
|
|
217
185
|
return chunks;
|
|
218
186
|
}
|
|
219
|
-
|
|
187
|
+
function hasV2Components(components) {
|
|
188
|
+
return Boolean(components?.some((component) => "isV2" in component && component.isV2));
|
|
189
|
+
}
|
|
190
|
+
export function resolveDiscordSendComponents(params) {
|
|
191
|
+
if (!params.components || !params.isFirst) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
return typeof params.components === "function"
|
|
195
|
+
? params.components(params.text)
|
|
196
|
+
: params.components;
|
|
197
|
+
}
|
|
198
|
+
function normalizeDiscordEmbeds(embeds) {
|
|
199
|
+
if (!embeds?.length) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
return embeds.map((embed) => (embed instanceof Embed ? embed : new Embed(embed)));
|
|
203
|
+
}
|
|
204
|
+
export function resolveDiscordSendEmbeds(params) {
|
|
205
|
+
if (!params.embeds || !params.isFirst) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
return normalizeDiscordEmbeds(params.embeds);
|
|
209
|
+
}
|
|
210
|
+
export function buildDiscordMessagePayload(params) {
|
|
211
|
+
const payload = {};
|
|
212
|
+
const hasV2 = hasV2Components(params.components);
|
|
213
|
+
const trimmed = params.text.trim();
|
|
214
|
+
if (!hasV2 && trimmed) {
|
|
215
|
+
payload.content = params.text;
|
|
216
|
+
}
|
|
217
|
+
if (params.components?.length) {
|
|
218
|
+
payload.components = params.components;
|
|
219
|
+
}
|
|
220
|
+
if (!hasV2 && params.embeds?.length) {
|
|
221
|
+
payload.embeds = params.embeds;
|
|
222
|
+
}
|
|
223
|
+
if (params.flags !== undefined) {
|
|
224
|
+
payload.flags = params.flags;
|
|
225
|
+
}
|
|
226
|
+
if (params.files?.length) {
|
|
227
|
+
payload.files = params.files;
|
|
228
|
+
}
|
|
229
|
+
return payload;
|
|
230
|
+
}
|
|
231
|
+
export function stripUndefinedFields(value) {
|
|
232
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
233
|
+
}
|
|
234
|
+
async function sendDiscordText(rest, channelId, text, replyTo, request, maxLinesPerMessage, components, embeds, chunkMode, silent) {
|
|
220
235
|
if (!text.trim()) {
|
|
221
236
|
throw new Error("Message must be non-empty for Discord sends");
|
|
222
237
|
}
|
|
223
238
|
const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined;
|
|
239
|
+
const flags = silent ? SUPPRESS_NOTIFICATIONS_FLAG : undefined;
|
|
224
240
|
const chunks = buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode });
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
241
|
+
const sendChunk = async (chunk, isFirst) => {
|
|
242
|
+
const chunkComponents = resolveDiscordSendComponents({
|
|
243
|
+
components,
|
|
244
|
+
text: chunk,
|
|
245
|
+
isFirst,
|
|
246
|
+
});
|
|
247
|
+
const chunkEmbeds = resolveDiscordSendEmbeds({ embeds, isFirst });
|
|
248
|
+
const payload = buildDiscordMessagePayload({
|
|
249
|
+
text: chunk,
|
|
250
|
+
components: chunkComponents,
|
|
251
|
+
embeds: chunkEmbeds,
|
|
252
|
+
flags,
|
|
253
|
+
});
|
|
254
|
+
const body = stripUndefinedFields({
|
|
255
|
+
...serializePayload(payload),
|
|
256
|
+
...(messageReference ? { message_reference: messageReference } : {}),
|
|
257
|
+
});
|
|
258
|
+
return (await request(() => rest.post(Routes.channelMessages(channelId), {
|
|
259
|
+
body,
|
|
232
260
|
}), "text"));
|
|
233
|
-
|
|
261
|
+
};
|
|
262
|
+
if (chunks.length === 1) {
|
|
263
|
+
return await sendChunk(chunks[0], true);
|
|
234
264
|
}
|
|
235
265
|
let last = null;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
last = (await request(() => rest.post(Routes.channelMessages(channelId), {
|
|
239
|
-
body: {
|
|
240
|
-
content: chunk,
|
|
241
|
-
message_reference: isFirst ? messageReference : undefined,
|
|
242
|
-
...(isFirst && embeds?.length ? { embeds } : {}),
|
|
243
|
-
},
|
|
244
|
-
}), "text"));
|
|
245
|
-
isFirst = false;
|
|
266
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
267
|
+
last = await sendChunk(chunk, index === 0);
|
|
246
268
|
}
|
|
247
269
|
if (!last) {
|
|
248
270
|
throw new Error("Discord send failed (empty chunk result)");
|
|
249
271
|
}
|
|
250
272
|
return last;
|
|
251
273
|
}
|
|
252
|
-
async function sendDiscordMedia(rest, channelId, text, mediaUrl, replyTo, request, maxLinesPerMessage, embeds, chunkMode) {
|
|
253
|
-
const media = await loadWebMedia(mediaUrl);
|
|
274
|
+
async function sendDiscordMedia(rest, channelId, text, mediaUrl, mediaLocalRoots, replyTo, request, maxLinesPerMessage, components, embeds, chunkMode, silent) {
|
|
275
|
+
const media = await loadWebMedia(mediaUrl, { localRoots: mediaLocalRoots });
|
|
254
276
|
const chunks = text ? buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode }) : [];
|
|
255
277
|
const caption = chunks[0] ?? "";
|
|
256
278
|
const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined;
|
|
279
|
+
const flags = silent ? SUPPRESS_NOTIFICATIONS_FLAG : undefined;
|
|
280
|
+
let fileData;
|
|
281
|
+
if (media.buffer instanceof Blob) {
|
|
282
|
+
fileData = media.buffer;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const arrayBuffer = new ArrayBuffer(media.buffer.byteLength);
|
|
286
|
+
new Uint8Array(arrayBuffer).set(media.buffer);
|
|
287
|
+
fileData = new Blob([arrayBuffer]);
|
|
288
|
+
}
|
|
289
|
+
const captionComponents = resolveDiscordSendComponents({
|
|
290
|
+
components,
|
|
291
|
+
text: caption,
|
|
292
|
+
isFirst: true,
|
|
293
|
+
});
|
|
294
|
+
const captionEmbeds = resolveDiscordSendEmbeds({ embeds, isFirst: true });
|
|
295
|
+
const payload = buildDiscordMessagePayload({
|
|
296
|
+
text: caption,
|
|
297
|
+
components: captionComponents,
|
|
298
|
+
embeds: captionEmbeds,
|
|
299
|
+
flags,
|
|
300
|
+
files: [
|
|
301
|
+
{
|
|
302
|
+
data: fileData,
|
|
303
|
+
name: media.fileName ?? "upload",
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
});
|
|
257
307
|
const res = (await request(() => rest.post(Routes.channelMessages(channelId), {
|
|
258
|
-
body: {
|
|
259
|
-
|
|
260
|
-
message_reference: messageReference,
|
|
261
|
-
|
|
262
|
-
files: [
|
|
263
|
-
{
|
|
264
|
-
data: media.buffer,
|
|
265
|
-
name: media.fileName ?? "upload",
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
},
|
|
308
|
+
body: stripUndefinedFields({
|
|
309
|
+
...serializePayload(payload),
|
|
310
|
+
...(messageReference ? { message_reference: messageReference } : {}),
|
|
311
|
+
}),
|
|
269
312
|
}), "media"));
|
|
270
313
|
for (const chunk of chunks.slice(1)) {
|
|
271
314
|
if (!chunk.trim()) {
|
|
272
315
|
continue;
|
|
273
316
|
}
|
|
274
|
-
await sendDiscordText(rest, channelId, chunk,
|
|
317
|
+
await sendDiscordText(rest, channelId, chunk, replyTo, request, maxLinesPerMessage, undefined, undefined, chunkMode, silent);
|
|
275
318
|
}
|
|
276
319
|
return res;
|
|
277
320
|
}
|
|
@@ -284,7 +327,4 @@ function buildReactionIdentifier(emoji) {
|
|
|
284
327
|
function formatReactionEmoji(emoji) {
|
|
285
328
|
return buildReactionIdentifier(emoji);
|
|
286
329
|
}
|
|
287
|
-
export function stripUndefinedFields(obj) {
|
|
288
|
-
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
289
|
-
}
|
|
290
330
|
export { buildDiscordSendError, buildReactionIdentifier, createDiscordClient, formatReactionEmoji, normalizeDiscordPollInput, normalizeEmojiName, normalizeReactionEmoji, normalizeStickerIds, parseRecipient, resolveChannelId, resolveDiscordRest, sendDiscordMedia, sendDiscordText, };
|
|
@@ -14,7 +14,7 @@ import crypto from "node:crypto";
|
|
|
14
14
|
import fs from "node:fs/promises";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { promisify } from "node:util";
|
|
17
|
-
import {
|
|
17
|
+
import { resolvePreferredPoolbotTmpDir } from "../infra/tmp-poolbot-dir.js";
|
|
18
18
|
const execFileAsync = promisify(execFile);
|
|
19
19
|
const DISCORD_VOICE_MESSAGE_FLAG = 1 << 13;
|
|
20
20
|
const SUPPRESS_NOTIFICATIONS_FLAG = 1 << 12;
|
|
@@ -62,7 +62,7 @@ export async function generateWaveform(filePath) {
|
|
|
62
62
|
* Generate waveform by extracting raw PCM data and sampling amplitudes
|
|
63
63
|
*/
|
|
64
64
|
async function generateWaveformFromPcm(filePath) {
|
|
65
|
-
const tempDir =
|
|
65
|
+
const tempDir = resolvePreferredPoolbotTmpDir();
|
|
66
66
|
const tempPcm = path.join(tempDir, `waveform-${crypto.randomUUID()}.raw`);
|
|
67
67
|
try {
|
|
68
68
|
// Convert to raw 16-bit signed PCM, mono, 8kHz
|
|
@@ -161,7 +161,7 @@ export async function ensureOggOpus(filePath) {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
// Convert to OGG/Opus
|
|
164
|
-
const tempDir =
|
|
164
|
+
const tempDir = resolvePreferredPoolbotTmpDir();
|
|
165
165
|
const outputPath = path.join(tempDir, `voice-${crypto.randomUUID()}.ogg`);
|
|
166
166
|
await execFileAsync("ffmpeg", [
|
|
167
167
|
"-y",
|
package/dist/gateway/auth.js
CHANGED
|
@@ -1,46 +1,24 @@
|
|
|
1
|
-
import { safeEqualSecret } from "../security/secret-equal.js";
|
|
2
1
|
import { readTailscaleWhoisIdentity } from "../infra/tailscale.js";
|
|
3
|
-
import {
|
|
2
|
+
import { safeEqualSecret } from "../security/secret-equal.js";
|
|
3
|
+
import { AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET, } from "./auth-rate-limit.js";
|
|
4
|
+
import { isLoopbackAddress, isTrustedProxyAddress, resolveHostName, parseForwardedForClientIp, resolveGatewayClientIp, } from "./net.js";
|
|
4
5
|
function normalizeLogin(login) {
|
|
5
6
|
return login.trim().toLowerCase();
|
|
6
7
|
}
|
|
7
|
-
function isLoopbackAddress(ip) {
|
|
8
|
-
if (!ip)
|
|
9
|
-
return false;
|
|
10
|
-
if (ip === "127.0.0.1")
|
|
11
|
-
return true;
|
|
12
|
-
if (ip.startsWith("127."))
|
|
13
|
-
return true;
|
|
14
|
-
if (ip === "::1")
|
|
15
|
-
return true;
|
|
16
|
-
if (ip.startsWith("::ffff:127."))
|
|
17
|
-
return true;
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
function getHostName(hostHeader) {
|
|
21
|
-
const host = (hostHeader ?? "").trim().toLowerCase();
|
|
22
|
-
if (!host)
|
|
23
|
-
return "";
|
|
24
|
-
if (host.startsWith("[")) {
|
|
25
|
-
const end = host.indexOf("]");
|
|
26
|
-
if (end !== -1)
|
|
27
|
-
return host.slice(1, end);
|
|
28
|
-
}
|
|
29
|
-
const [name] = host.split(":");
|
|
30
|
-
return name ?? "";
|
|
31
|
-
}
|
|
32
8
|
function headerValue(value) {
|
|
33
9
|
return Array.isArray(value) ? value[0] : value;
|
|
34
10
|
}
|
|
35
11
|
function resolveTailscaleClientIp(req) {
|
|
36
|
-
if (!req)
|
|
12
|
+
if (!req) {
|
|
37
13
|
return undefined;
|
|
14
|
+
}
|
|
38
15
|
const forwardedFor = headerValue(req.headers?.["x-forwarded-for"]);
|
|
39
16
|
return forwardedFor ? parseForwardedForClientIp(forwardedFor) : undefined;
|
|
40
17
|
}
|
|
41
18
|
function resolveRequestClientIp(req, trustedProxies) {
|
|
42
|
-
if (!req)
|
|
19
|
+
if (!req) {
|
|
43
20
|
return undefined;
|
|
21
|
+
}
|
|
44
22
|
return resolveGatewayClientIp({
|
|
45
23
|
remoteAddr: req.socket?.remoteAddress ?? "",
|
|
46
24
|
forwardedFor: headerValue(req.headers?.["x-forwarded-for"]),
|
|
@@ -49,12 +27,14 @@ function resolveRequestClientIp(req, trustedProxies) {
|
|
|
49
27
|
});
|
|
50
28
|
}
|
|
51
29
|
export function isLocalDirectRequest(req, trustedProxies) {
|
|
52
|
-
if (!req)
|
|
30
|
+
if (!req) {
|
|
53
31
|
return false;
|
|
32
|
+
}
|
|
54
33
|
const clientIp = resolveRequestClientIp(req, trustedProxies) ?? "";
|
|
55
|
-
if (!isLoopbackAddress(clientIp))
|
|
34
|
+
if (!isLoopbackAddress(clientIp)) {
|
|
56
35
|
return false;
|
|
57
|
-
|
|
36
|
+
}
|
|
37
|
+
const host = resolveHostName(req.headers?.host);
|
|
58
38
|
const hostIsLocal = host === "localhost" || host === "127.0.0.1" || host === "::1";
|
|
59
39
|
const hostIsTailscaleServe = host.endsWith(".ts.net");
|
|
60
40
|
const hasForwarded = Boolean(req.headers?.["x-forwarded-for"] ||
|
|
@@ -64,11 +44,13 @@ export function isLocalDirectRequest(req, trustedProxies) {
|
|
|
64
44
|
return (hostIsLocal || hostIsTailscaleServe) && (!hasForwarded || remoteIsTrustedProxy);
|
|
65
45
|
}
|
|
66
46
|
function getTailscaleUser(req) {
|
|
67
|
-
if (!req)
|
|
47
|
+
if (!req) {
|
|
68
48
|
return null;
|
|
49
|
+
}
|
|
69
50
|
const login = req.headers["tailscale-user-login"];
|
|
70
|
-
if (typeof login !== "string" || !login.trim())
|
|
51
|
+
if (typeof login !== "string" || !login.trim()) {
|
|
71
52
|
return null;
|
|
53
|
+
}
|
|
72
54
|
const nameRaw = req.headers["tailscale-user-name"];
|
|
73
55
|
const profilePic = req.headers["tailscale-user-profile-pic"];
|
|
74
56
|
const name = typeof nameRaw === "string" && nameRaw.trim() ? nameRaw.trim() : login.trim();
|
|
@@ -79,15 +61,17 @@ function getTailscaleUser(req) {
|
|
|
79
61
|
};
|
|
80
62
|
}
|
|
81
63
|
function hasTailscaleProxyHeaders(req) {
|
|
82
|
-
if (!req)
|
|
64
|
+
if (!req) {
|
|
83
65
|
return false;
|
|
66
|
+
}
|
|
84
67
|
return Boolean(req.headers["x-forwarded-for"] &&
|
|
85
68
|
req.headers["x-forwarded-proto"] &&
|
|
86
69
|
req.headers["x-forwarded-host"]);
|
|
87
70
|
}
|
|
88
71
|
function isTailscaleProxyRequest(req) {
|
|
89
|
-
if (!req)
|
|
72
|
+
if (!req) {
|
|
90
73
|
return false;
|
|
74
|
+
}
|
|
91
75
|
return isLoopbackAddress(req.socket?.remoteAddress) && hasTailscaleProxyHeaders(req);
|
|
92
76
|
}
|
|
93
77
|
async function resolveVerifiedTailscaleUser(params) {
|
|
@@ -122,40 +106,124 @@ async function resolveVerifiedTailscaleUser(params) {
|
|
|
122
106
|
export function resolveGatewayAuth(params) {
|
|
123
107
|
const authConfig = params.authConfig ?? {};
|
|
124
108
|
const env = params.env ?? process.env;
|
|
125
|
-
const token = authConfig.token ?? env.POOLBOT_GATEWAY_TOKEN ??
|
|
126
|
-
const password = authConfig.password ??
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
109
|
+
const token = authConfig.token ?? env.POOLBOT_GATEWAY_TOKEN ?? undefined;
|
|
110
|
+
const password = authConfig.password ?? env.POOLBOT_GATEWAY_PASSWORD ?? undefined;
|
|
111
|
+
const trustedProxy = authConfig.trustedProxy;
|
|
112
|
+
let mode;
|
|
113
|
+
if (authConfig.mode) {
|
|
114
|
+
mode = authConfig.mode;
|
|
115
|
+
}
|
|
116
|
+
else if (password) {
|
|
117
|
+
mode = "password";
|
|
118
|
+
}
|
|
119
|
+
else if (token) {
|
|
120
|
+
mode = "token";
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
mode = "none";
|
|
124
|
+
}
|
|
125
|
+
const allowTailscale = authConfig.allowTailscale ??
|
|
126
|
+
(params.tailscaleMode === "serve" && mode !== "password" && mode !== "trusted-proxy");
|
|
132
127
|
return {
|
|
133
128
|
mode,
|
|
134
129
|
token,
|
|
135
130
|
password,
|
|
136
131
|
allowTailscale,
|
|
132
|
+
trustedProxy,
|
|
137
133
|
};
|
|
138
134
|
}
|
|
139
135
|
export function assertGatewayAuthConfigured(auth) {
|
|
140
136
|
if (auth.mode === "token" && !auth.token) {
|
|
141
|
-
if (auth.allowTailscale)
|
|
137
|
+
if (auth.allowTailscale) {
|
|
142
138
|
return;
|
|
139
|
+
}
|
|
143
140
|
throw new Error("gateway auth mode is token, but no token was configured (set gateway.auth.token or POOLBOT_GATEWAY_TOKEN)");
|
|
144
141
|
}
|
|
145
142
|
if (auth.mode === "password" && !auth.password) {
|
|
146
143
|
throw new Error("gateway auth mode is password, but no password was configured");
|
|
147
144
|
}
|
|
145
|
+
if (auth.mode === "trusted-proxy") {
|
|
146
|
+
if (!auth.trustedProxy) {
|
|
147
|
+
throw new Error("gateway auth mode is trusted-proxy, but no trustedProxy config was provided (set gateway.auth.trustedProxy)");
|
|
148
|
+
}
|
|
149
|
+
if (!auth.trustedProxy.userHeader || auth.trustedProxy.userHeader.trim() === "") {
|
|
150
|
+
throw new Error("gateway auth mode is trusted-proxy, but trustedProxy.userHeader is empty (set gateway.auth.trustedProxy.userHeader)");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check if the request came from a trusted proxy and extract user identity.
|
|
156
|
+
* Returns the user identity if valid, or null with a reason if not.
|
|
157
|
+
*/
|
|
158
|
+
function authorizeTrustedProxy(params) {
|
|
159
|
+
const { req, trustedProxies, trustedProxyConfig } = params;
|
|
160
|
+
if (!req) {
|
|
161
|
+
return { reason: "trusted_proxy_no_request" };
|
|
162
|
+
}
|
|
163
|
+
const remoteAddr = req.socket?.remoteAddress;
|
|
164
|
+
if (!remoteAddr || !isTrustedProxyAddress(remoteAddr, trustedProxies)) {
|
|
165
|
+
return { reason: "trusted_proxy_untrusted_source" };
|
|
166
|
+
}
|
|
167
|
+
const requiredHeaders = trustedProxyConfig.requiredHeaders ?? [];
|
|
168
|
+
for (const header of requiredHeaders) {
|
|
169
|
+
const value = headerValue(req.headers[header.toLowerCase()]);
|
|
170
|
+
if (!value || value.trim() === "") {
|
|
171
|
+
return { reason: `trusted_proxy_missing_header_${header}` };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const userHeaderValue = headerValue(req.headers[trustedProxyConfig.userHeader.toLowerCase()]);
|
|
175
|
+
if (!userHeaderValue || userHeaderValue.trim() === "") {
|
|
176
|
+
return { reason: "trusted_proxy_user_missing" };
|
|
177
|
+
}
|
|
178
|
+
const user = userHeaderValue.trim();
|
|
179
|
+
const allowUsers = trustedProxyConfig.allowUsers ?? [];
|
|
180
|
+
if (allowUsers.length > 0 && !allowUsers.includes(user)) {
|
|
181
|
+
return { reason: "trusted_proxy_user_not_allowed" };
|
|
182
|
+
}
|
|
183
|
+
return { user };
|
|
148
184
|
}
|
|
149
185
|
export async function authorizeGatewayConnect(params) {
|
|
150
186
|
const { auth, connectAuth, req, trustedProxies } = params;
|
|
151
187
|
const tailscaleWhois = params.tailscaleWhois ?? readTailscaleWhoisIdentity;
|
|
152
188
|
const localDirect = isLocalDirectRequest(req, trustedProxies);
|
|
189
|
+
if (auth.mode === "trusted-proxy") {
|
|
190
|
+
if (!auth.trustedProxy) {
|
|
191
|
+
return { ok: false, reason: "trusted_proxy_config_missing" };
|
|
192
|
+
}
|
|
193
|
+
if (!trustedProxies || trustedProxies.length === 0) {
|
|
194
|
+
return { ok: false, reason: "trusted_proxy_no_proxies_configured" };
|
|
195
|
+
}
|
|
196
|
+
const result = authorizeTrustedProxy({
|
|
197
|
+
req,
|
|
198
|
+
trustedProxies,
|
|
199
|
+
trustedProxyConfig: auth.trustedProxy,
|
|
200
|
+
});
|
|
201
|
+
if ("user" in result) {
|
|
202
|
+
return { ok: true, method: "trusted-proxy", user: result.user };
|
|
203
|
+
}
|
|
204
|
+
return { ok: false, reason: result.reason };
|
|
205
|
+
}
|
|
206
|
+
const limiter = params.rateLimiter;
|
|
207
|
+
const ip = params.clientIp ?? resolveRequestClientIp(req, trustedProxies) ?? req?.socket?.remoteAddress;
|
|
208
|
+
const rateLimitScope = params.rateLimitScope ?? AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET;
|
|
209
|
+
if (limiter) {
|
|
210
|
+
const rlCheck = limiter.check(ip, rateLimitScope);
|
|
211
|
+
if (!rlCheck.allowed) {
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
reason: "rate_limited",
|
|
215
|
+
rateLimited: true,
|
|
216
|
+
retryAfterMs: rlCheck.retryAfterMs,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
153
220
|
if (auth.allowTailscale && !localDirect) {
|
|
154
221
|
const tailscaleCheck = await resolveVerifiedTailscaleUser({
|
|
155
222
|
req,
|
|
156
223
|
tailscaleWhois,
|
|
157
224
|
});
|
|
158
225
|
if (tailscaleCheck.ok) {
|
|
226
|
+
limiter?.reset(ip, rateLimitScope);
|
|
159
227
|
return {
|
|
160
228
|
ok: true,
|
|
161
229
|
method: "tailscale",
|
|
@@ -168,11 +236,14 @@ export async function authorizeGatewayConnect(params) {
|
|
|
168
236
|
return { ok: false, reason: "token_missing_config" };
|
|
169
237
|
}
|
|
170
238
|
if (!connectAuth?.token) {
|
|
239
|
+
limiter?.recordFailure(ip, rateLimitScope);
|
|
171
240
|
return { ok: false, reason: "token_missing" };
|
|
172
241
|
}
|
|
173
242
|
if (!safeEqualSecret(connectAuth.token, auth.token)) {
|
|
243
|
+
limiter?.recordFailure(ip, rateLimitScope);
|
|
174
244
|
return { ok: false, reason: "token_mismatch" };
|
|
175
245
|
}
|
|
246
|
+
limiter?.reset(ip, rateLimitScope);
|
|
176
247
|
return { ok: true, method: "token" };
|
|
177
248
|
}
|
|
178
249
|
if (auth.mode === "password") {
|
|
@@ -181,12 +252,16 @@ export async function authorizeGatewayConnect(params) {
|
|
|
181
252
|
return { ok: false, reason: "password_missing_config" };
|
|
182
253
|
}
|
|
183
254
|
if (!password) {
|
|
255
|
+
limiter?.recordFailure(ip, rateLimitScope);
|
|
184
256
|
return { ok: false, reason: "password_missing" };
|
|
185
257
|
}
|
|
186
258
|
if (!safeEqualSecret(password, auth.password)) {
|
|
259
|
+
limiter?.recordFailure(ip, rateLimitScope);
|
|
187
260
|
return { ok: false, reason: "password_mismatch" };
|
|
188
261
|
}
|
|
262
|
+
limiter?.reset(ip, rateLimitScope);
|
|
189
263
|
return { ok: true, method: "password" };
|
|
190
264
|
}
|
|
265
|
+
limiter?.recordFailure(ip, rateLimitScope);
|
|
191
266
|
return { ok: false, reason: "unauthorized" };
|
|
192
267
|
}
|