feat(supabase): add Google/Apple OAuth and fueltide.io-branded email flows

Enables the auth providers and transactional email flows the self-hosted
Supabase was missing compared to the cloud instance:

- GoTrue now accepts Google and Apple OAuth (web flow); Apple client-secret
  JWT is signed fresh on every activation from the SOPS-stored .p8 so
  there's no 6-month rotation ritual.
- SMTP points at mail.cloonar.com:587 with SASL auth via a new `supabase`
  LDAP account; a `noreply@fueltide.io` mailAlias lets that account send
  as the fueltide.io address.
- rspamd on mail.cloonar.com gets a per-domain DKIM key for fueltide.io
  (selector `default`) so outbound mail is signed.
- MAILER_AUTOCONFIRM is off so signup confirmation + password reset
  actually go through email.
- SITE_URL + URI_ALLOW_LIST point at app.fueltide.io / stage so links in
  emails and OAuth redirects land in the right app.

FUELTIDE_AUTH_SETUP.md documents the manual steps (LDAP entries, SOPS
additions, DNS records, Google/Apple console setup) that must be completed
before merging.
This commit is contained in:
Dominik Polakovics Polakovics 2026-04-22 22:08:29 +02:00
parent 1f5e5b9a37
commit 67e81d39f3
5 changed files with 366 additions and 12 deletions

View file

@ -22,9 +22,49 @@ LOGFLARE_PRIVATE_ACCESS_TOKEN=$LOGFLARE_PRIVATE_ACCESS_TOKEN
POSTGRES_BACKEND_URL=postgresql://supabase_admin:$PG_PASS_ENCODED@db:5432/_supabase
EOF
# Apple client-secret is a short-lived JWT signed with the .p8 key downloaded
# from Apple Developer. Re-sign on every activation (lifetime 180 days, Apple's
# cap) so there is no manual rotation ritual. The SOPS-sourced APPLE_PRIVATE_KEY
# is stored as a single line with literal \n separators; python un-escapes it.
APPLE_SECRET=""
if [ -n "${APPLE_TEAM_ID:-}" ] && [ -n "${APPLE_KEY_ID:-}" ] \
&& [ -n "${APPLE_SERVICES_ID:-}" ] && [ -n "${APPLE_PRIVATE_KEY:-}" ]; then
APPLE_SECRET=$(
APPLE_TEAM_ID="$APPLE_TEAM_ID" \
APPLE_KEY_ID="$APPLE_KEY_ID" \
APPLE_SERVICES_ID="$APPLE_SERVICES_ID" \
APPLE_PRIVATE_KEY="$APPLE_PRIVATE_KEY" \
python3 - <<'PY'
import base64, json, os, time
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
now = int(time.time())
header = {"alg": "ES256", "kid": os.environ["APPLE_KEY_ID"], "typ": "JWT"}
payload = {"iss": os.environ["APPLE_TEAM_ID"], "iat": now, "exp": now + 86400 * 180,
"aud": "https://appleid.apple.com", "sub": os.environ["APPLE_SERVICES_ID"]}
parts = (b64u(json.dumps(header, separators=(",", ":")).encode())
+ "." + b64u(json.dumps(payload, separators=(",", ":")).encode())).encode()
pem = os.environ["APPLE_PRIVATE_KEY"].encode().decode("unicode_escape").encode()
key = serialization.load_pem_private_key(pem, password=None)
der = key.sign(parts, ec.ECDSA(hashes.SHA256()))
r, s = decode_dss_signature(der)
raw = r.to_bytes(32, "big") + s.to_bytes(32, "big")
print(parts.decode() + "." + b64u(raw))
PY
)
fi
cat > /run/supabase/auth.env <<EOF
GOTRUE_JWT_SECRET=$JWT_SECRET
GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:$PG_PASS_ENCODED@db:5432/postgres
GOTRUE_SMTP_USER=${SMTP_USER:-}
GOTRUE_SMTP_PASS=${SMTP_PASS:-}
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
GOTRUE_EXTERNAL_GOOGLE_SECRET=${GOOGLE_SECRET:-}
GOTRUE_EXTERNAL_APPLE_CLIENT_ID=${APPLE_SERVICES_ID:-}
GOTRUE_EXTERNAL_APPLE_SECRET=$APPLE_SECRET
EOF
cat > /run/supabase/rest.env <<EOF