Files
konstruct/packages/gateway/gateway/verify.py
Adolfo Delorenzo 6f30705e1a feat(01-03): Channel Gateway (Slack adapter) and Message Router
- gateway/normalize.py: normalize_slack_event -> KonstructMessage (strips bot mention)
- gateway/channels/slack.py: register_slack_handlers for app_mention + DM events
  - rate limit check -> ephemeral rejection on exceeded
  - idempotency dedup (Slack retry protection)
  - placeholder 'Thinking...' message posted in-thread before Celery dispatch
  - auto-follow engaged threads with 30-minute TTL
  - HTTP 200 returned immediately; all LLM work dispatched to Celery
- gateway/main.py: FastAPI on port 8001, /slack/events + /health
- router/tenant.py: resolve_tenant workspace_id -> tenant_id (RLS-bypass query)
- router/ratelimit.py: check_rate_limit Redis token bucket, RateLimitExceeded exception
- router/idempotency.py: is_duplicate + mark_processed (SET NX, 24h TTL)
- router/context.py: load_agent_for_tenant with RLS ContextVar setup
- orchestrator/tasks.py: handle_message now extracts placeholder_ts/channel_id,
  calls _update_slack_placeholder via chat.update after LLM response
- docker-compose.yml: gateway service on port 8001
- pyproject.toml: added redis, konstruct-router, konstruct-orchestrator deps
2026-03-23 10:27:59 -06:00

65 lines
1.9 KiB
Python

"""
Slack request signature verification.
slack-bolt's AsyncApp handles signature verification automatically when
initialized with a signing_secret. This module provides a standalone
helper for contexts that require manual verification (e.g., testing,
custom middleware layers).
In production, prefer slack-bolt's built-in verification — do NOT disable
it or bypass it.
"""
from __future__ import annotations
import hashlib
import hmac
import time
def verify_slack_signature(
body: bytes,
timestamp: str,
signature: str,
signing_secret: str,
max_age_seconds: int = 300,
) -> bool:
"""
Verify a Slack webhook request signature.
Implements Slack's signing secret verification algorithm:
https://api.slack.com/authentication/verifying-requests-from-slack
Args:
body: Raw request body bytes.
timestamp: Value of the ``X-Slack-Request-Timestamp`` header.
signature: Value of the ``X-Slack-Signature`` header.
signing_secret: App's signing secret from Slack dashboard.
max_age_seconds: Reject requests older than this (replay protection).
Returns:
True if signature is valid and request is fresh, False otherwise.
"""
# Replay attack prevention — reject stale requests
try:
request_age = abs(int(time.time()) - int(timestamp))
except (ValueError, TypeError):
return False
if request_age > max_age_seconds:
return False
# Compute expected signature
sig_basestring = f"v0:{timestamp}:{body.decode('utf-8', errors='replace')}"
computed = (
"v0="
+ hmac.new(
signing_secret.encode("utf-8"),
sig_basestring.encode("utf-8"),
hashlib.sha256,
).hexdigest()
)
# Constant-time comparison to prevent timing attacks
return hmac.compare_digest(computed, signature)