mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-01 00:59:33 +00:00
Add Telegram webhook support with credential injection
Enable instant message delivery for Telegram via webhooks instead of polling.
Key changes:
- Add tunnel URL configuration for local development (ngrok, cloudflare)
- Auto-register webhook with Telegram API on startup using setWebhook
- Implement webhook secret validation via X-Telegram-Bot-Api-Secret-Token header
- Add credential injection for bot token via URL placeholder substitution
- Fix metadata preservation in respond() to route replies correctly
- Fix serde flatten with Option<T> issue in capabilities schema parsing
The credential injection pattern replaces {TELEGRAM_BOT_TOKEN} placeholders
in URLs with the actual token from the secrets store, keeping credentials
out of WASM module memory until the HTTP request is made.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
4ab20ff939
commit
7955c9742e
+103
@@ -13,6 +13,7 @@ pub struct Config {
|
||||
pub database: DatabaseConfig,
|
||||
pub llm: LlmConfig,
|
||||
pub embeddings: EmbeddingsConfig,
|
||||
pub tunnel: TunnelConfig,
|
||||
pub channels: ChannelsConfig,
|
||||
pub agent: AgentConfig,
|
||||
pub safety: SafetyConfig,
|
||||
@@ -32,6 +33,7 @@ impl Config {
|
||||
database: DatabaseConfig::from_env()?,
|
||||
llm: LlmConfig::from_env()?,
|
||||
embeddings: EmbeddingsConfig::from_env()?,
|
||||
tunnel: TunnelConfig::from_env()?,
|
||||
channels: ChannelsConfig::from_env()?,
|
||||
agent: AgentConfig::from_env()?,
|
||||
safety: SafetyConfig::from_env()?,
|
||||
@@ -43,6 +45,81 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tunnel configuration for exposing the agent to the internet.
|
||||
///
|
||||
/// Used by channels and tools that need public webhook endpoints.
|
||||
/// The tunnel URL is shared across all channels (Telegram, Slack, etc.).
|
||||
///
|
||||
/// # Security Notes
|
||||
///
|
||||
/// **Webhook endpoints** (e.g., `/webhook/telegram`) should NOT use tunnel-level
|
||||
/// authentication because webhook providers (Telegram, Slack, GitHub) need
|
||||
/// unauthenticated access to POST updates. Security for webhooks comes from:
|
||||
/// - Webhook signature verification (provider-specific secrets)
|
||||
/// - IP allowlisting (if supported by provider)
|
||||
///
|
||||
/// **Non-webhook endpoints** (admin APIs, health checks) CAN be protected using
|
||||
/// tunnel provider features:
|
||||
/// - ngrok: Basic Auth, OAuth, IP restrictions
|
||||
/// - Cloudflare: Access policies, mTLS
|
||||
///
|
||||
/// These protections are configured in the tunnel provider, not here.
|
||||
///
|
||||
/// # Supported Providers
|
||||
///
|
||||
/// - **ngrok**: `ngrok http 8080` -> `https://abc123.ngrok.io`
|
||||
/// - **Cloudflare Tunnel**: `cloudflared tunnel --url http://localhost:8080`
|
||||
/// - **localtunnel**: `lt --port 8080`
|
||||
/// - Any service that provides a public HTTPS URL to localhost
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TunnelConfig {
|
||||
/// Public URL from tunnel provider (e.g., "https://abc123.ngrok.io").
|
||||
///
|
||||
/// When set, channels that support webhooks will register their endpoints
|
||||
/// with this base URL instead of using polling.
|
||||
pub public_url: Option<String>,
|
||||
}
|
||||
|
||||
impl TunnelConfig {
|
||||
fn from_env() -> Result<Self, ConfigError> {
|
||||
// Priority: env var > settings file
|
||||
let public_url = optional_env("TUNNEL_URL")?.or_else(|| {
|
||||
crate::settings::Settings::load()
|
||||
.tunnel
|
||||
.public_url
|
||||
.filter(|s| !s.is_empty())
|
||||
});
|
||||
|
||||
// Validate URL format if provided
|
||||
if let Some(ref url) = public_url {
|
||||
if !url.starts_with("https://") {
|
||||
return Err(ConfigError::InvalidValue {
|
||||
key: "TUNNEL_URL".to_string(),
|
||||
message: "must start with https:// (webhooks require HTTPS)".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { public_url })
|
||||
}
|
||||
|
||||
/// Check if a tunnel is configured.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.public_url.is_some()
|
||||
}
|
||||
|
||||
/// Get the webhook URL for a given path.
|
||||
///
|
||||
/// Returns `None` if no tunnel is configured.
|
||||
pub fn webhook_url(&self, path: &str) -> Option<String> {
|
||||
self.public_url.as_ref().map(|base| {
|
||||
let base = base.trim_end_matches('/');
|
||||
let path = path.trim_start_matches('/');
|
||||
format!("{}/{}", base, path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Database configuration.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DatabaseConfig {
|
||||
@@ -231,12 +308,37 @@ fn default_session_path() -> PathBuf {
|
||||
pub struct ChannelsConfig {
|
||||
pub cli: CliConfig,
|
||||
pub http: Option<HttpConfig>,
|
||||
pub telegram: TelegramChannelConfig,
|
||||
/// Directory containing WASM channel modules (default: ~/.near-agent/channels/).
|
||||
pub wasm_channels_dir: std::path::PathBuf,
|
||||
/// Whether WASM channels are enabled.
|
||||
pub wasm_channels_enabled: bool,
|
||||
}
|
||||
|
||||
/// Telegram channel configuration.
|
||||
///
|
||||
/// The tunnel URL for webhook mode comes from the global `TunnelConfig`.
|
||||
/// This config only contains Telegram-specific settings.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TelegramChannelConfig {
|
||||
/// Secret token for webhook validation (optional but recommended).
|
||||
///
|
||||
/// When set, Telegram will include this value in the
|
||||
/// `X-Telegram-Bot-Api-Secret-Token` header of webhook requests.
|
||||
/// The agent validates this header to ensure requests come from Telegram.
|
||||
///
|
||||
/// Generate a secure random token (32+ characters recommended).
|
||||
pub webhook_secret: Option<String>,
|
||||
}
|
||||
|
||||
impl TelegramChannelConfig {
|
||||
fn from_env() -> Result<Self, ConfigError> {
|
||||
Ok(Self {
|
||||
webhook_secret: optional_env("TELEGRAM_WEBHOOK_SECRET")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CliConfig {
|
||||
pub enabled: bool,
|
||||
@@ -277,6 +379,7 @@ impl ChannelsConfig {
|
||||
enabled: cli_enabled,
|
||||
},
|
||||
http,
|
||||
telegram: TelegramChannelConfig::from_env()?,
|
||||
wasm_channels_dir: optional_env("WASM_CHANNELS_DIR")?
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(default_channels_dir),
|
||||
|
||||
Reference in New Issue
Block a user