Files
channel-exit/app/config.py
T
nirpa e4d6230dd1 The estate's one channel exit
Services that need to reach a person hand the message here instead of each
carrying a SendGrid key. Email goes to SendGrid, SMS to Twilio; whatsapp and
push answer 501 rather than pretending.

It binds loopback and the docker bridge only. An authenticated relay on the
public internet is an open spam relay the moment the token leaks, and that
token is copied into every consumer's environment.

The log names a channel, a redacted address and the provider's answer. Never
the subject, never the body.
2026-08-18 15:41:28 -04:00

52 lines
1.9 KiB
Python

"""What the relay needs to know, and what it refuses to start without.
The bearer token is the only thing standing between this service and an open
spam relay, so an unset token is a startup failure rather than a service that
accepts everything. Provider credentials are the opposite: a missing SendGrid
key must not stop SMS working, so each channel is asked at send time whether it
is configured and answers for itself.
"""
from __future__ import annotations
import os
from dataclasses import dataclass, field
def _env(name: str, default: str = "") -> str:
return (os.environ.get(name, default) or "").strip()
@dataclass(frozen=True)
class Settings:
# The shared secret every caller must present. No default: a relay that
# falls back to a well-known token is worse than one that will not boot.
token: str = field(default_factory=lambda: _env("CHANNEL_EXIT_TOKEN"))
sendgrid_key: str = field(default_factory=lambda: _env("SENDGRID_API_KEY"))
# SendGrid rejects a From that is not a verified sender, so this is an
# operator setting and never anything a caller can influence.
email_from: str = field(default_factory=lambda: _env("EMAIL_FROM"))
twilio_sid: str = field(default_factory=lambda: _env("TWILIO_ACCOUNT_SID"))
twilio_token: str = field(default_factory=lambda: _env("TWILIO_AUTH_TOKEN"))
twilio_from: str = field(default_factory=lambda: _env("TWILIO_FROM"))
timeout: float = field(default_factory=lambda: float(_env("PROVIDER_TIMEOUT", "15") or 15))
@property
def email_ready(self) -> bool:
return bool(self.sendgrid_key and self.email_from)
@property
def sms_ready(self) -> bool:
return bool(self.twilio_sid and self.twilio_token and self.twilio_from)
def require_token(self) -> None:
if not self.token:
raise RuntimeError("CHANNEL_EXIT_TOKEN is unset; refusing to run an unauthenticated relay")
def get_settings() -> Settings:
return Settings()