Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ The API service is configured entirely via environment variables. Variables are
| `NEWSLETTER_TOKEN_TTL_SECS` | `86400` | Confirmation token expiry |
| `OTLP_ENDPOINT` | *(none)* | OpenTelemetry collector endpoint |
| `TRACE_SAMPLE_RATE` | `0.1` | Fraction of requests traced (0–1) |
| `SENDGRID_WEBHOOK_SECRET` | *(none)* | SendGrid webhook signature secret |
| `SENDGRID_WEBHOOK_SECRET` | *(none)* | SendGrid webhook ECDSA public key (base64 DER) |
| `ADMIN_WHITELIST_IPS` | *(none — admin routes unrestricted)* | Comma-separated CIDR allowlist |

### ECS task secrets and environment mapping
Expand Down
4 changes: 2 additions & 2 deletions docs/webhook-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The `/webhooks/sendgrid` endpoint receives and processes email delivery status e
SendGrid signs every webhook request with an HMAC-SHA256 signature. The signature is verified by the `sendgrid_webhook_middleware` before the request reaches the handler.

**Configuration:**
- `SENDGRID_WEBHOOK_SECRET`: The shared secret used to verify signatures. Must be set in production.
- `SENDGRID_WEBHOOK_SECRET`: SendGrid's Signed Event Webhook **public key** (base64 DER, ECDSA P-256). Must be set in production. HMAC-SHA256 with a shared secret is accepted only outside production, for local testing.
- Signature is in the `X-Twilio-Email-Event-Webhook-Signature` header
- Verification happens in `security::sendgrid_webhook_middleware` before body parsing

Expand Down Expand Up @@ -89,7 +89,7 @@ Provides fallback deduplication if:
| Variable | Default | Purpose |
|---|---|---|
| `WEBHOOK_REPLAY_WINDOW_SECS` | `300` | Duration (seconds) that Redis nonces are retained |
| `SENDGRID_WEBHOOK_SECRET` | (required) | Shared HMAC key for signature verification |
| `SENDGRID_WEBHOOK_SECRET` | (required) | SendGrid ECDSA public key (base64 DER) for signature verification |

**Production Checklist:**
- [ ] `SENDGRID_WEBHOOK_SECRET` is set and matches the SendGrid dashboard
Expand Down
1 change: 1 addition & 0 deletions services/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ hmac = "0.12"
rand = { version = "0.8", features = ["getrandom"] }
hex = "0.4"
base64 = "0.22"
p256 = { version = "0.13", features = ["ecdsa", "pem"] }
subtle = "2.5"
secrecy = { version = "0.8", features = ["serde"] }
ipnet = "2"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Store newsletter confirmation tokens as SHA-256 hex digests (matches unsubscribe tokens).
UPDATE newsletter_subscribers
SET confirmation_token = encode(sha256(convert_to(confirmation_token, 'UTF8')), 'hex')
WHERE confirmation_token IS NOT NULL
AND confirmation_token !~ '^[0-9a-f]{64}$';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Durable store for chain events (Redis is only a cache).
CREATE TABLE IF NOT EXISTS chain_events (
event_id TEXT PRIMARY KEY,
ledger BIGINT NOT NULL,
topic TEXT NOT NULL,
tx_hash TEXT,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS chain_events_ledger_idx ON chain_events (ledger);

-- Markets: on-chain identity + resolved-outcome consistency.
ALTER TABLE markets ADD COLUMN IF NOT EXISTS on_chain_id BIGINT;
CREATE UNIQUE INDEX IF NOT EXISTS markets_on_chain_id_key ON markets (on_chain_id);
ALTER TABLE markets DROP CONSTRAINT IF EXISTS markets_resolved_requires_outcome;
ALTER TABLE markets ADD CONSTRAINT markets_resolved_requires_outcome
CHECK (status <> 'resolved' OR outcome_index IS NOT NULL) NOT VALID;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE markets DROP CONSTRAINT IF EXISTS markets_resolved_requires_outcome;
DROP INDEX IF EXISTS markets_on_chain_id_key;
ALTER TABLE markets DROP COLUMN IF EXISTS on_chain_id;
DROP TABLE IF EXISTS chain_events;
Loading
Loading