signalwire-sdk 3.0.2.dev33__tar.gz → 3.0.2.dev35__tar.gz
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.
- {signalwire_sdk-3.0.2.dev33/signalwire/signalwire_sdk.egg-info → signalwire_sdk-3.0.2.dev35}/PKG-INFO +1 -1
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/pyproject.toml +1 -1
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/__init__.py +1 -1
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/agent_server.py +1 -1
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent_base.py +33 -3
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/web_mixin.py +46 -20
- signalwire_sdk-3.0.2.dev35/signalwire/signalwire/core/security/__init__.py +26 -0
- signalwire_sdk-3.0.2.dev35/signalwire/signalwire/core/security/webhook_middleware.py +164 -0
- signalwire_sdk-3.0.2.dev35/signalwire/signalwire/core/security/webhook_validator.py +327 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35/signalwire/signalwire_sdk.egg-info}/PKG-INFO +1 -1
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/SOURCES.txt +2 -0
- signalwire_sdk-3.0.2.dev33/signalwire/signalwire/core/security/__init__.py +0 -9
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/LICENSE +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/man/sw-agent-init.1 +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/man/sw-search.1 +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/man/swaig-test.1 +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/setup.cfg +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/setup.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/agents/bedrock.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/build_search.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/config.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/agent_loader.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/argparse_helpers.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/dynamic_config.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/service_loader.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/dokku.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/execution/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/execution/datamap_exec.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/execution/webhook_exec.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/init_project.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/output/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/output/output_formatter.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/output/swml_dump.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/simulation/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/simulation/data_generation.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/simulation/data_overrides.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/simulation/mock_env.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/swaig_test_wrapper.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/test_swaig.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/types.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/config/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/deployment/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/deployment/handlers/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/prompt/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/prompt/manager.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/routing/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/security/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/swml/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/tools/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/tools/decorator.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/tools/registry.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/tools/type_inference.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/auth_handler.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/config_loader.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/contexts.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/data_map.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/function_result.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/logging_config.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/ai_config_mixin.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/auth_mixin.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/mcp_server_mixin.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/prompt_mixin.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/serverless_mixin.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/skill_mixin.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/state_mixin.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/tool_mixin.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/pom_builder.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/security/session_manager.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/security_config.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/skill_base.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/skill_manager.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swaig_function.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swml_builder.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swml_handler.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swml_renderer.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swml_service.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/livewire/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/livewire/plugins.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/mcp_gateway/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/mcp_gateway/gateway_service.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/mcp_gateway/mcp_manager.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/mcp_gateway/session_manager.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/pom/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/pom/pom.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/pom/pom_tool.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/concierge.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/faq_bot.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/info_gatherer.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/receptionist.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/survey.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/call.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/client.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/constants.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/event.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/message.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/_base.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/_pagination.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/call_handler.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/client.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/addresses.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/calling.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/chat.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/compat.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/datasphere.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/fabric.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/imported_numbers.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/logs.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/lookup.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/mfa.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/number_groups.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/phone_numbers.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/project.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/pubsub.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/queues.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/recordings.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/registry.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/short_codes.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/sip_profile.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/verified_callers.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/video.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/schema.json +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/document_processor.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/index_builder.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/migration.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/models.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/pgvector_backend.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/query_processor.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/search_engine.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/search_service.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/api_ninjas_trivia/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/api_ninjas_trivia/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/api_ninjas_trivia/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/claude_skills/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/claude_skills/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/claude_skills/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere_serverless/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere_serverless/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere_serverless/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datetime/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datetime/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datetime/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/google_maps/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/google_maps/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/info_gatherer/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/info_gatherer/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/info_gatherer/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/joke/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/joke/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/joke/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/math/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/math/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/math/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/mcp_gateway/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/mcp_gateway/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/mcp_gateway/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/native_vector_search/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/native_vector_search/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/native_vector_search/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/play_background_file/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/play_background_file/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/play_background_file/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/registry.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/spider/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/spider/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/spider/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/swml_transfer/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/swml_transfer/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/swml_transfer/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/weather_api/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/weather_api/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/weather_api/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/skill_improved.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/skill_original.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/wikipedia_search/README.md +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/wikipedia_search/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/wikipedia_search/skill.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/pom_utils.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/schema_utils.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/token_generators.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/url_validator.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/validators.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/web/__init__.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/web/web_service.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/dependency_links.txt +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/entry_points.txt +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/requires.txt +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/top_level.txt +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/tests/test_examples.py +0 -0
- {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/tests/test_mcp_integration.py +0 -0
|
@@ -18,7 +18,7 @@ A package for building AI agents using SignalWire's AI and SWML capabilities.
|
|
|
18
18
|
from .core.logging_config import configure_logging
|
|
19
19
|
configure_logging()
|
|
20
20
|
|
|
21
|
-
__version__ = "3.0.2.
|
|
21
|
+
__version__ = "3.0.2.dev35"
|
|
22
22
|
|
|
23
23
|
# Import core classes for easier access
|
|
24
24
|
from .core.agent_base import AgentBase
|
{signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent_base.py
RENAMED
|
@@ -126,11 +126,13 @@ class AgentBase(
|
|
|
126
126
|
enable_post_prompt_override: bool = False,
|
|
127
127
|
check_for_input_override: bool = False,
|
|
128
128
|
config_file: Optional[str] = None,
|
|
129
|
-
schema_validation: bool = True
|
|
129
|
+
schema_validation: bool = True,
|
|
130
|
+
signing_key: Optional[str] = None,
|
|
131
|
+
trust_proxy_for_signature: bool = False
|
|
130
132
|
):
|
|
131
133
|
"""
|
|
132
134
|
Initialize a new agent
|
|
133
|
-
|
|
135
|
+
|
|
134
136
|
Args:
|
|
135
137
|
name: Agent name/identifier
|
|
136
138
|
route: HTTP route path for this agent
|
|
@@ -153,6 +155,17 @@ class AgentBase(
|
|
|
153
155
|
config_file: Optional path to configuration file
|
|
154
156
|
schema_validation: Enable SWML schema validation. Default True. Can also
|
|
155
157
|
be disabled via SWML_SKIP_SCHEMA_VALIDATION=1 env var.
|
|
158
|
+
signing_key: Optional SignalWire Signing Key (from Dashboard → API
|
|
159
|
+
Credentials). When set, webhook signature validation is
|
|
160
|
+
enforced on POST /, /swaig, /post_prompt — unsigned or
|
|
161
|
+
invalidly-signed requests get a 403. Falls back to the
|
|
162
|
+
SIGNALWIRE_SIGNING_KEY env var if not passed.
|
|
163
|
+
See porting-sdk/webhooks.md for the contract.
|
|
164
|
+
trust_proxy_for_signature: If True, honor X-Forwarded-Proto /
|
|
165
|
+
X-Forwarded-Host when reconstructing the URL during
|
|
166
|
+
signature validation. Default False — proxy headers
|
|
167
|
+
are spoofable, so opt in only when you control the
|
|
168
|
+
proxy chain.
|
|
156
169
|
"""
|
|
157
170
|
# Import SWMLService here to avoid circular imports
|
|
158
171
|
from signalwire.core.swml_service import SWMLService
|
|
@@ -214,7 +227,24 @@ class AgentBase(
|
|
|
214
227
|
|
|
215
228
|
# Initialize session manager
|
|
216
229
|
self._session_manager = SessionManager(token_expiry_secs=token_expiry_secs)
|
|
217
|
-
|
|
230
|
+
|
|
231
|
+
# Webhook signature validation (porting-sdk/webhooks.md).
|
|
232
|
+
# Resolution order: explicit constructor arg → SIGNALWIRE_SIGNING_KEY env.
|
|
233
|
+
# When unset, web_mixin._register_routes does NOT mount the validator
|
|
234
|
+
# and the SDK logs a one-time WARNING so users notice in production.
|
|
235
|
+
self.signing_key = signing_key or os.environ.get("SIGNALWIRE_SIGNING_KEY")
|
|
236
|
+
self._trust_proxy_for_signature = trust_proxy_for_signature
|
|
237
|
+
if self.signing_key:
|
|
238
|
+
self.log.info("webhook_signature_validation_enabled")
|
|
239
|
+
else:
|
|
240
|
+
self.log.warning(
|
|
241
|
+
"webhook_signature_validation_disabled",
|
|
242
|
+
message=(
|
|
243
|
+
"[signalwire] webhook signature validation is disabled — "
|
|
244
|
+
"set signing_key or SIGNALWIRE_SIGNING_KEY to enable"
|
|
245
|
+
),
|
|
246
|
+
)
|
|
247
|
+
|
|
218
248
|
# URL override variables
|
|
219
249
|
self._web_hook_url_override = None
|
|
220
250
|
self._post_prompt_url_override = None
|
|
@@ -17,11 +17,14 @@ import contextvars
|
|
|
17
17
|
from typing import Optional, Dict, Any, Callable
|
|
18
18
|
from urllib.parse import urlparse, urlunparse
|
|
19
19
|
|
|
20
|
-
from fastapi import FastAPI, APIRouter, Request, Response
|
|
20
|
+
from fastapi import Depends, FastAPI, APIRouter, Request, Response
|
|
21
21
|
from fastapi.middleware.cors import CORSMiddleware
|
|
22
22
|
|
|
23
23
|
from signalwire.core.logging_config import get_execution_mode
|
|
24
24
|
from signalwire.core.function_result import FunctionResult
|
|
25
|
+
from signalwire.core.security.webhook_middleware import (
|
|
26
|
+
make_webhook_validation_dependency,
|
|
27
|
+
)
|
|
25
28
|
|
|
26
29
|
# Per-request proxy URL to avoid race conditions in concurrent async contexts
|
|
27
30
|
_request_proxy_url = contextvars.ContextVar('_request_proxy_url', default=None)
|
|
@@ -343,22 +346,37 @@ class WebMixin:
|
|
|
343
346
|
def _register_routes(self, router):
|
|
344
347
|
"""
|
|
345
348
|
Register routes for this agent
|
|
346
|
-
|
|
347
|
-
This method ensures proper route registration by handling the routes
|
|
349
|
+
|
|
350
|
+
This method ensures proper route registration by handling the routes
|
|
348
351
|
directly in AgentBase rather than inheriting from SWMLService.
|
|
349
|
-
|
|
352
|
+
|
|
350
353
|
Args:
|
|
351
354
|
router: FastAPI router to register routes with
|
|
352
355
|
"""
|
|
353
356
|
# Health check endpoints are now registered directly on the main app
|
|
354
|
-
|
|
357
|
+
|
|
358
|
+
# Build webhook signature validation dependency once if signing_key is set.
|
|
359
|
+
# See porting-sdk/webhooks.md and signalwire.core.security.webhook_middleware.
|
|
360
|
+
# When unset, signed_post_deps stays empty and routes register without it.
|
|
361
|
+
signed_post_deps = []
|
|
362
|
+
if getattr(self, "signing_key", None):
|
|
363
|
+
sig_dep = make_webhook_validation_dependency(
|
|
364
|
+
self.signing_key,
|
|
365
|
+
trust_proxy=getattr(self, "_trust_proxy_for_signature", False),
|
|
366
|
+
)
|
|
367
|
+
signed_post_deps = [Depends(sig_dep)]
|
|
368
|
+
|
|
355
369
|
# Root endpoint (handles both with and without trailing slash)
|
|
356
370
|
@router.get("/")
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
"""Handle GET/POST requests to the root endpoint"""
|
|
371
|
+
async def handle_root_get(request: Request, response: Response):
|
|
372
|
+
"""Handle GET requests to the root endpoint"""
|
|
360
373
|
return await self._handle_root_request(request)
|
|
361
|
-
|
|
374
|
+
|
|
375
|
+
@router.post("/", dependencies=signed_post_deps)
|
|
376
|
+
async def handle_root_post(request: Request, response: Response):
|
|
377
|
+
"""Handle POST requests to the root endpoint (signature-validated when signing_key is set)"""
|
|
378
|
+
return await self._handle_root_request(request)
|
|
379
|
+
|
|
362
380
|
# Debug endpoint - Both versions
|
|
363
381
|
@router.get("/debug")
|
|
364
382
|
@router.get("/debug/")
|
|
@@ -367,23 +385,31 @@ class WebMixin:
|
|
|
367
385
|
async def handle_debug(request: Request):
|
|
368
386
|
"""Handle GET/POST requests to the debug endpoint"""
|
|
369
387
|
return await self._handle_debug_request(request)
|
|
370
|
-
|
|
371
|
-
# SWAIG endpoint - Both versions
|
|
388
|
+
|
|
389
|
+
# SWAIG endpoint - Both versions
|
|
372
390
|
@router.get("/swaig")
|
|
373
391
|
@router.get("/swaig/")
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
async def handle_swaig(request: Request, response: Response):
|
|
377
|
-
"""Handle GET/POST requests to the SWAIG endpoint"""
|
|
392
|
+
async def handle_swaig_get(request: Request, response: Response):
|
|
393
|
+
"""Handle GET requests to the SWAIG endpoint"""
|
|
378
394
|
return await self._handle_swaig_request(request, response)
|
|
379
|
-
|
|
395
|
+
|
|
396
|
+
@router.post("/swaig", dependencies=signed_post_deps)
|
|
397
|
+
@router.post("/swaig/", dependencies=signed_post_deps)
|
|
398
|
+
async def handle_swaig_post(request: Request, response: Response):
|
|
399
|
+
"""Handle POST requests to the SWAIG endpoint (signature-validated when signing_key is set)"""
|
|
400
|
+
return await self._handle_swaig_request(request, response)
|
|
401
|
+
|
|
380
402
|
# Post prompt endpoint - Both versions
|
|
381
403
|
@router.get("/post_prompt")
|
|
382
404
|
@router.get("/post_prompt/")
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
405
|
+
async def handle_post_prompt_get(request: Request):
|
|
406
|
+
"""Handle GET requests to the post_prompt endpoint"""
|
|
407
|
+
return await self._handle_post_prompt_request(request)
|
|
408
|
+
|
|
409
|
+
@router.post("/post_prompt", dependencies=signed_post_deps)
|
|
410
|
+
@router.post("/post_prompt/", dependencies=signed_post_deps)
|
|
411
|
+
async def handle_post_prompt_post(request: Request):
|
|
412
|
+
"""Handle POST requests to the post_prompt endpoint (signature-validated when signing_key is set)"""
|
|
387
413
|
return await self._handle_post_prompt_request(request)
|
|
388
414
|
|
|
389
415
|
# Check for input endpoint - Both versions
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2025 SignalWire
|
|
3
|
+
|
|
4
|
+
This file is part of the SignalWire SDK.
|
|
5
|
+
|
|
6
|
+
Licensed under the MIT License.
|
|
7
|
+
See LICENSE file in the project root for full license information.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from signalwire.core.security.webhook_validator import (
|
|
11
|
+
validate_request,
|
|
12
|
+
validate_webhook_signature,
|
|
13
|
+
)
|
|
14
|
+
from signalwire.core.security.webhook_middleware import (
|
|
15
|
+
SIGNALWIRE_SIGNATURE_HEADER,
|
|
16
|
+
TWILIO_COMPAT_SIGNATURE_HEADER,
|
|
17
|
+
make_webhook_validation_dependency,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"validate_request",
|
|
22
|
+
"validate_webhook_signature",
|
|
23
|
+
"make_webhook_validation_dependency",
|
|
24
|
+
"SIGNALWIRE_SIGNATURE_HEADER",
|
|
25
|
+
"TWILIO_COMPAT_SIGNATURE_HEADER",
|
|
26
|
+
]
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI middleware / dependency for SignalWire webhook signature validation.
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025 SignalWire. Licensed under the MIT License.
|
|
5
|
+
See LICENSE file in the project root for full license information.
|
|
6
|
+
|
|
7
|
+
This module ships a small, framework-aware adapter around
|
|
8
|
+
:func:`signalwire.core.security.webhook_validator.validate_webhook_signature`.
|
|
9
|
+
|
|
10
|
+
Why a custom dependency rather than a vanilla ``Depends`` on ``request.body()``?
|
|
11
|
+
|
|
12
|
+
- We MUST capture the raw bytes BEFORE FastAPI's JSON parser consumes the
|
|
13
|
+
stream — re-serialization changes whitespace and key order, which breaks
|
|
14
|
+
the Scheme A digest. The dependency stashes the raw body on
|
|
15
|
+
``request.state.raw_body`` so the downstream handler can re-parse without
|
|
16
|
+
re-reading the stream.
|
|
17
|
+
- Reverse-proxy / ngrok deployments need the URL the platform POSTed to,
|
|
18
|
+
which differs from the URL the SDK sees. The dependency honors
|
|
19
|
+
``X-Forwarded-Proto`` / ``X-Forwarded-Host`` when ``trust_proxy=True``,
|
|
20
|
+
plus the ``SWML_PROXY_URL_BASE`` env var, with ``request.url`` as last
|
|
21
|
+
resort.
|
|
22
|
+
- The legacy cXML/Compatibility scheme used the ``X-Twilio-Signature``
|
|
23
|
+
header. We accept it as an alias of ``X-SignalWire-Signature`` so users
|
|
24
|
+
migrating from the legacy SDK can keep their callers unchanged.
|
|
25
|
+
|
|
26
|
+
Usage::
|
|
27
|
+
|
|
28
|
+
from signalwire.core.security.webhook_middleware import (
|
|
29
|
+
make_webhook_validation_dependency,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
dep = make_webhook_validation_dependency(signing_key="PSK...")
|
|
33
|
+
|
|
34
|
+
@app.post("/webhook", dependencies=[Depends(dep)])
|
|
35
|
+
async def webhook(request: Request):
|
|
36
|
+
body = request.state.raw_body # bytes; re-parse if you need JSON
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import os
|
|
42
|
+
from typing import Awaitable, Callable, Optional
|
|
43
|
+
|
|
44
|
+
from fastapi import HTTPException, Request, Response, status
|
|
45
|
+
|
|
46
|
+
from signalwire.core.security.webhook_validator import validate_webhook_signature
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
SIGNALWIRE_SIGNATURE_HEADER = "x-signalwire-signature"
|
|
50
|
+
TWILIO_COMPAT_SIGNATURE_HEADER = "x-twilio-signature"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _reconstruct_url(request: Request, *, trust_proxy: bool) -> str:
|
|
54
|
+
"""Rebuild the public URL SignalWire POSTed to.
|
|
55
|
+
|
|
56
|
+
Resolution order (highest priority first):
|
|
57
|
+
|
|
58
|
+
1. ``SWML_PROXY_URL_BASE`` env var (joined with the request path + query).
|
|
59
|
+
2. ``X-Forwarded-Proto`` / ``X-Forwarded-Host`` headers, if
|
|
60
|
+
``trust_proxy=True`` and both headers are present.
|
|
61
|
+
3. ``request.url`` (FastAPI's view of the URL).
|
|
62
|
+
"""
|
|
63
|
+
path_and_query = request.url.path
|
|
64
|
+
if request.url.query:
|
|
65
|
+
path_and_query = f"{path_and_query}?{request.url.query}"
|
|
66
|
+
|
|
67
|
+
proxy_base = os.environ.get("SWML_PROXY_URL_BASE")
|
|
68
|
+
if proxy_base:
|
|
69
|
+
return f"{proxy_base.rstrip('/')}{path_and_query}"
|
|
70
|
+
|
|
71
|
+
if trust_proxy:
|
|
72
|
+
fwd_host = request.headers.get("x-forwarded-host")
|
|
73
|
+
fwd_proto = request.headers.get("x-forwarded-proto", "https")
|
|
74
|
+
if fwd_host:
|
|
75
|
+
return f"{fwd_proto}://{fwd_host}{path_and_query}"
|
|
76
|
+
|
|
77
|
+
return str(request.url)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _extract_signature_header(request: Request) -> Optional[str]:
|
|
81
|
+
"""Return the SignalWire signature header (or ``X-Twilio-Signature`` alias)."""
|
|
82
|
+
sig = request.headers.get(SIGNALWIRE_SIGNATURE_HEADER)
|
|
83
|
+
if sig is None:
|
|
84
|
+
sig = request.headers.get(TWILIO_COMPAT_SIGNATURE_HEADER)
|
|
85
|
+
return sig
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def make_webhook_validation_dependency(
|
|
89
|
+
signing_key: str,
|
|
90
|
+
*,
|
|
91
|
+
trust_proxy: bool = False,
|
|
92
|
+
) -> Callable[[Request, Response], Awaitable[Optional[Response]]]:
|
|
93
|
+
"""Build a FastAPI dependency that enforces signature validation.
|
|
94
|
+
|
|
95
|
+
The returned coroutine:
|
|
96
|
+
|
|
97
|
+
1. Reads ``await request.body()`` and stashes the bytes on
|
|
98
|
+
``request.state.raw_body``.
|
|
99
|
+
2. Pulls the ``X-SignalWire-Signature`` header (or the Twilio alias).
|
|
100
|
+
3. Reconstructs the public URL (proxy headers / env / fallback).
|
|
101
|
+
4. Calls :func:`validate_webhook_signature`.
|
|
102
|
+
5. On invalid signature: raises ``HTTPException(403)`` to short-circuit
|
|
103
|
+
the handler. FastAPI's ``dependencies=[Depends(...)]`` only honors
|
|
104
|
+
short-circuiting via raised exceptions — returning a Response from a
|
|
105
|
+
dependency does not stop the endpoint.
|
|
106
|
+
6. On valid: returns ``None`` so the handler runs as normal.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
signing_key: The customer's Signing Key. Required, non-empty.
|
|
110
|
+
trust_proxy: If True, honor ``X-Forwarded-Proto`` / ``X-Forwarded-Host``
|
|
111
|
+
when reconstructing the URL. Default False — proxy headers are
|
|
112
|
+
spoofable, so opt in only when you control the proxy.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Async callable suitable for ``Depends(...)``.
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
ValueError: at construction time if ``signing_key`` is empty.
|
|
119
|
+
"""
|
|
120
|
+
if not signing_key:
|
|
121
|
+
raise ValueError("signing_key is required")
|
|
122
|
+
|
|
123
|
+
def _forbidden() -> None:
|
|
124
|
+
# Single canonical 403 short-circuit. No body detail (would leak
|
|
125
|
+
# which branch failed); validators MUST NOT log scheme details.
|
|
126
|
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
|
127
|
+
|
|
128
|
+
async def dependency(request: Request, response: Response) -> None:
|
|
129
|
+
# Capture raw body BEFORE any other consumer reads the stream.
|
|
130
|
+
# request.body() caches internally so subsequent calls are safe.
|
|
131
|
+
raw_bytes = await request.body()
|
|
132
|
+
request.state.raw_body = raw_bytes
|
|
133
|
+
try:
|
|
134
|
+
raw_body_str = raw_bytes.decode("utf-8")
|
|
135
|
+
except UnicodeDecodeError:
|
|
136
|
+
# Non-UTF-8 body cannot match an HMAC over UTF-8 input.
|
|
137
|
+
_forbidden()
|
|
138
|
+
|
|
139
|
+
signature = _extract_signature_header(request)
|
|
140
|
+
if not signature:
|
|
141
|
+
_forbidden()
|
|
142
|
+
|
|
143
|
+
url = _reconstruct_url(request, trust_proxy=trust_proxy)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
ok = validate_webhook_signature(signing_key, signature, url, raw_body_str)
|
|
147
|
+
except (TypeError, ValueError):
|
|
148
|
+
# Programming errors or non-string body — treat as invalid for the
|
|
149
|
+
# request without leaking which branch tripped.
|
|
150
|
+
_forbidden()
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
if not ok:
|
|
154
|
+
_forbidden()
|
|
155
|
+
# Valid — fall through and let the handler run.
|
|
156
|
+
|
|
157
|
+
return dependency
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
__all__ = [
|
|
161
|
+
"make_webhook_validation_dependency",
|
|
162
|
+
"SIGNALWIRE_SIGNATURE_HEADER",
|
|
163
|
+
"TWILIO_COMPAT_SIGNATURE_HEADER",
|
|
164
|
+
]
|