diff --git a/.env.example b/.env.example index e03719d..35aed92 100644 --- a/.env.example +++ b/.env.example @@ -1,18 +1,41 @@ -# Local Compose defaults work without this file. Replace every value before a public deployment. +# Copy this file to .env. Compose intentionally refuses to start without the +# required values. Replace every credential before any public deployment. HTTP_PORT=4010 MAILPIT_PORT=8027 PHX_HOST=localhost +PHX_SCHEME=http +PHX_URL_PORT=4010 # Android debug builds compile this origin into BuildConfig. The Docker # emulator uses adb reverse to expose the local Compose proxy on loopback. WNH_DEBUG_BASE_URL=http://localhost:4010 +# Release builds require a public HTTPS origin. +WNH_BASE_URL=https://whoneedhelp.imalto.site # Public raster tile template used by MapLibre. Use a provider whose policy and # capacity match the deployment before a public launch. MAP_TILE_URL=https://tile.openstreetmap.org/{z}/{x}/{y}.png -# Compose derives PHX_URL_PORT from HTTP_PORT and uses HTTP for local development. + +POSTGRES_DB=who_need_help +POSTGRES_USER=postgres +POSTGRES_PASSWORD=replace-with-a-local-or-deployment-secret +DATABASE_URL=ecto://postgres:replace-with-url-encoded-password@db/who_need_help + POOL_SIZE=10 SECRET_KEY_BASE=generate-with-mix-phx-gen-secret HANDOVER_SECRET=generate-an-independent-random-secret RELEASE_COOKIE=generate-an-independent-beam-cluster-cookie + +# Mailpit settings for local development. Replace these with the selected SMTP +# provider for public registration and magic links. +SMTP_RELAY=mailpit +SMTP_PORT=1025 +SMTP_USERNAME= +SMTP_PASSWORD= +SMTP_AUTH=never +SMTP_TLS=never +SMTP_SSL=false +EMAIL_FROM_NAME=Who Need Help +EMAIL_FROM_ADDRESS=contact@example.com + CODEX_SESSION_ID=copy-the-main-local-codex-session-id # Optional shared PostgreSQL-backed policies. Keep {} until product thresholds are approved. # Shape: {"action_name":{"limit":POSITIVE_INTEGER,"window_seconds":POSITIVE_INTEGER}} diff --git a/compose.yaml b/compose.yaml index 884da77..c3e9731 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,18 +2,25 @@ name: who_need_help x-app-environment: &app-environment APP_ROLE: web - DATABASE_URL: ecto://postgres:postgres@db/who_need_help - SECRET_KEY_BASE: ${SECRET_KEY_BASE:-h0rJQH8xgH9vV1cS7gVw6M2oI0Kp3Lx9uA5fE4bD8nR7qT2yW6zC1sP9mN3kF5jH} - HANDOVER_SECRET: ${HANDOVER_SECRET:-local-compose-handover-secret-change-before-public-use} - RELEASE_COOKIE: ${RELEASE_COOKIE:-local-compose-beam-cookie-change-before-public-use} + DATABASE_URL: ${DATABASE_URL:?Set DATABASE_URL in .env} + SECRET_KEY_BASE: ${SECRET_KEY_BASE:?Set SECRET_KEY_BASE in .env} + HANDOVER_SECRET: ${HANDOVER_SECRET:?Set HANDOVER_SECRET in .env} + RELEASE_COOKIE: ${RELEASE_COOKIE:?Set RELEASE_COOKIE in .env} DNS_CLUSTER_QUERY: web - PHX_HOST: ${PHX_HOST:-localhost} - PHX_SCHEME: http - PHX_URL_PORT: ${HTTP_PORT:-4010} + PHX_HOST: ${PHX_HOST:?Set PHX_HOST in .env} + PHX_SCHEME: ${PHX_SCHEME:?Set PHX_SCHEME in .env} + PHX_URL_PORT: ${PHX_URL_PORT:?Set PHX_URL_PORT in .env} PORT: "4000" - POOL_SIZE: ${POOL_SIZE:-10} - SMTP_RELAY: mailpit - SMTP_PORT: "1025" + POOL_SIZE: ${POOL_SIZE:?Set POOL_SIZE in .env after measuring the target profile} + SMTP_RELAY: ${SMTP_RELAY:?Set SMTP_RELAY in .env} + SMTP_PORT: ${SMTP_PORT:?Set SMTP_PORT in .env} + SMTP_USERNAME: ${SMTP_USERNAME:-} + SMTP_PASSWORD: ${SMTP_PASSWORD:-} + SMTP_AUTH: ${SMTP_AUTH:?Set SMTP_AUTH in .env} + SMTP_TLS: ${SMTP_TLS:?Set SMTP_TLS in .env} + SMTP_SSL: ${SMTP_SSL:?Set SMTP_SSL in .env} + EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:?Set EMAIL_FROM_NAME in .env} + EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:?Set EMAIL_FROM_ADDRESS in .env} CODEX_SESSION_ID: ${CODEX_SESSION_ID:-not-configured} RATE_LIMIT_POLICIES_JSON: ${RATE_LIMIT_POLICIES_JSON:-{}} MAP_TILE_URL: ${MAP_TILE_URL:-https://tile.openstreetmap.org/{z}/{x}/{y}.png} @@ -36,11 +43,11 @@ services: db: image: postgis/postgis:18-3.6-alpine environment: - POSTGRES_DB: who_need_help - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres + POSTGRES_DB: ${POSTGRES_DB:?Set POSTGRES_DB in .env} + POSTGRES_USER: ${POSTGRES_USER:?Set POSTGRES_USER in .env} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d who_need_help"] + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 3s timeout: 3s retries: 20 diff --git a/config/config.exs b/config/config.exs index 4edffb4..c582777 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,6 +7,10 @@ # General application configuration import Config +config :who_need_help, :mailer_from, + name: "Who Need Help", + address: "contact@example.com" + config :who_need_help, :scopes, user: [ default: true, diff --git a/config/runtime.exs b/config/runtime.exs index a33a585..ef1dafd 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -114,15 +114,58 @@ if config_env() == :prod do default_url_port = if scheme == "https", do: "443", else: "80" url_port = String.to_integer(System.get_env("PHX_URL_PORT", default_url_port)) + smtp_auth = + case System.get_env("SMTP_AUTH", "never") do + "always" -> :always + "never" -> :never + "if_available" -> :if_available + other -> raise "SMTP_AUTH must be always, never, or if_available; got #{inspect(other)}" + end + + smtp_tls = + case System.get_env("SMTP_TLS", "never") do + "always" -> :always + "never" -> :never + "if_available" -> :if_available + other -> raise "SMTP_TLS must be always, never, or if_available; got #{inspect(other)}" + end + + smtp_ssl = + case System.get_env("SMTP_SSL", "false") do + value when value in ["true", "1"] -> true + value when value in ["false", "0"] -> false + other -> raise "SMTP_SSL must be true, false, 1, or 0; got #{inspect(other)}" + end + + smtp_config = + [ + adapter: Swoosh.Adapters.SMTP, + relay: System.get_env("SMTP_RELAY", "mailpit"), + port: String.to_integer(System.get_env("SMTP_PORT", "1025")), + auth: smtp_auth, + tls: smtp_tls, + ssl: smtp_ssl + ] + |> then(fn config -> + case System.get_env("SMTP_USERNAME") do + value when is_binary(value) and value != "" -> Keyword.put(config, :username, value) + _ -> config + end + end) + |> then(fn config -> + case System.get_env("SMTP_PASSWORD") do + value when is_binary(value) and value != "" -> Keyword.put(config, :password, value) + _ -> config + end + end) + config :who_need_help, :handover_secret, handover_secret - config :who_need_help, WhoNeedHelp.Mailer, - adapter: Swoosh.Adapters.SMTP, - relay: System.get_env("SMTP_RELAY", "mailpit"), - port: String.to_integer(System.get_env("SMTP_PORT", "1025")), - auth: :never, - tls: :never, - ssl: false + config :who_need_help, :mailer_from, + name: System.get_env("EMAIL_FROM_NAME", "Who Need Help"), + address: System.get_env("EMAIL_FROM_ADDRESS", "contact@example.com") + + config :who_need_help, WhoNeedHelp.Mailer, smtp_config config :who_need_help, WhoNeedHelpWeb.Endpoint, url: [host: host, port: url_port, scheme: scheme], diff --git a/lib/who_need_help/accounts/user_notifier.ex b/lib/who_need_help/accounts/user_notifier.ex index 3c83fcb..871edba 100644 --- a/lib/who_need_help/accounts/user_notifier.ex +++ b/lib/who_need_help/accounts/user_notifier.ex @@ -6,10 +6,12 @@ defmodule WhoNeedHelp.Accounts.UserNotifier do # Delivers the email using the application mailer. defp deliver(recipient, subject, body) do + from = Application.fetch_env!(:who_need_help, :mailer_from) + email = new() |> to(recipient) - |> from({"WhoNeedHelp", "contact@example.com"}) + |> from({from[:name], from[:address]}) |> subject(subject) |> text_body(body)