- sms/auth.py: verify_api_key dependency (Bearer/X-API-Key, SHA-256 hash compare via secrets.compare_digest, fail-closed 503 if no hash). All routes gated via a protected router; /health stays open for probes. - config.py: new sms_api_key_hash setting (VOIPMS_SMS_API_KEY_HASH). - Dockerfile + .dockerignore + docker-compose.yml: lean python:3.13-slim image; secrets injected via env_file, never baked in; host-localhost-only port mapping (SMS_PORT override); /health healthcheck. - .env.example: committed template (placeholders only, no secrets). - sms/docs/sms-facade.dokuwiki.txt: abstraction API reference (published to the apidoc wiki at voipms:sms-facade). - README: Authentication section, Docker section, 401/503 error rows. Co-Authored-By: Claude <noreply@anthropic.com>
56 lines
1.7 KiB
Python
56 lines
1.7 KiB
Python
"""Configuration for the voip.ms SMS client, loaded from environment.
|
|
|
|
Set credentials before running:
|
|
export VOIPMS_API_USERNAME=...
|
|
export VOIPMS_API_PASSWORD=...
|
|
|
|
On the voip.ms side you must also whitelist this machine's public IP under
|
|
Main Menu -> SOAP / REST API -> API Security, and the sending DID must have
|
|
SMS enabled.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Literal
|
|
|
|
from pydantic import Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_prefix="voipms_",
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
)
|
|
|
|
api_username: str = Field(..., description="voip.ms API username")
|
|
api_password: str = Field(..., description="voip.ms API password (set in API Security)")
|
|
|
|
base_url: str = Field(
|
|
"https://voip.ms/api/v1/rest.php",
|
|
description="voip.ms REST endpoint. Avoid www.voip.ms (302 drops POST bodies).",
|
|
)
|
|
|
|
dialing_mode: Literal["nanpa", "e164"] = Field(
|
|
"nanpa",
|
|
description="Number format. nanpa=10 digits; e164=+1 then 10 digits.",
|
|
)
|
|
|
|
daily_limit: int = Field(
|
|
100,
|
|
description="Per-UTC-day cap on outbound sends (matches the upstream API limit).",
|
|
ge=0,
|
|
)
|
|
|
|
sms_api_key_hash: str = Field(
|
|
"",
|
|
description=(
|
|
"Comma-separated SHA-256 hex hashes of accepted client API keys. "
|
|
"Generate a key, hash it (sha256), and store only the hash here — "
|
|
"the raw key is never persisted. Empty = fail-closed (503)."
|
|
),
|
|
)
|
|
|
|
timeout: float = Field(30.0, description="HTTP timeout in seconds.", ge=1.0) |