nixos/hosts/web-arm/modules/supabase/FUELTIDE_AUTH_SETUP.md

7.9 KiB
Raw Blame History

Supabase auth setup: Google OAuth, Apple native sign-in (iOS), fueltide.io email

This doc lists the user-side steps required to make the code changes in this branch functional. Nothing here is performed by Nix — these are manual actions on external services, LDAP, SOPS, and DNS.

The Nix changes in this branch cover:

  • hosts/web-arm/modules/supabase/default.nix — GoTrue env for Google OAuth (web code-exchange flow) and Apple native sign-in (iOS id_token flow, GOTRUE_EXTERNAL_APPLE_CLIENT_ID=io.fueltide.workout), SMTP pointed at mail.cloonar.com:587, MAILER_AUTOCONFIRM=false, SITE_URL + URI_ALLOW_LIST for fueltide.io.
  • hosts/web-arm/modules/supabase/env-generate.sh — new auth.env block that pulls SMTP + Google creds from SOPS.
  • hosts/mail/modules/dkim-fueltide.nix — installs a per-domain DKIM key for fueltide.io into rspamd so outbound mail from noreply@fueltide.io is signed.

Apple sign-in is scoped to the native iOS flow only: the app uses AuthenticationServices to obtain an Apple id_token, then calls supabase.auth.signInWithIdToken({ provider: 'apple', token, nonce }). GoTrue verifies the id_token against Apple's JWKS and checks that aud matches io.fueltide.workout. No server-side client secret, .p8 key, or Services ID is needed. Android uses native Google sign-in (handled separately) and no Apple browser flow is supported.

Complete the six steps below before merging to master. Merging without them will deploy a broken GoTrue (missing Google/SMTP creds → auth emails fail, Google OAuth flows 500).


1. LDAP service account + fueltide alias on mail.cloonar.com

Mirrors the gitea@cloonar.com / authelia@cloonar.com pattern. The alias on noreply@fueltide.io is what smtpd_sender_login_maps uses to let the supabase SASL user send as that address without tripping reject_authenticated_sender_login_mismatch.

# on mail.cloonar.com
SMTP_PASS=$(openssl rand -base64 30 | tr -d '/+=' | head -c 32)
echo "SMTP_PASS (store this in SOPS, step 3): $SMTP_PASS"
CRYPT=$(mkpasswd -m sha-512 "$SMTP_PASS")

cat > /tmp/supabase.ldif <<EOF
dn: uid=supabase,ou=users,dc=cloonar,dc=com
objectClass: mailAccount
objectClass: inetOrgPerson
uid: supabase
cn: Supabase Auth
sn: Auth
mail: supabase@cloonar.com
mailSendOnly: TRUE
userPassword: {CRYPT}$CRYPT
description: SASL account for Supabase GoTrue outbound mail

dn: mail=noreply@fueltide.io,ou=aliases,dc=cloonar,dc=com
objectClass: mailAlias
mail: noreply@fueltide.io
maildrop: supabase@cloonar.com
EOF

ldapadd -x -D "cn=admin,dc=cloonar,dc=com" -W -f /tmp/supabase.ldif
rm /tmp/supabase.ldif

2. Generate the fueltide.io DKIM key

Selector is default to match the glob that rspamd's dkim_signing block (hosts/mail/modules/rspamd.nix:15-19) watches for.

mkdir -p /tmp/dkim-gen && cd /tmp/dkim-gen
nix-shell -p rspamd --run \
  "rspamadm dkim_keygen -s default -d fueltide.io -k fueltide.io.default.key"

# private key: fueltide.io.default.key   -> goes into SOPS (step 3)
# public key:  printed to stdout         -> goes into DNS (step 4)

Wipe the temp dir once both are copied out.

3. SOPS edits (two files)

hosts/mail/secrets.yaml

nix-shell -p sops --run 'sops hosts/mail/secrets.yaml'

Add:

rspamd-dkim-fueltide-io-key: |
  -----BEGIN PRIVATE KEY-----
  <paste contents of /tmp/dkim-gen/fueltide.io.default.key>
  -----END PRIVATE KEY-----

hosts/web-arm/secrets.yaml

nix-shell -p sops --run 'sops hosts/web-arm/secrets.yaml'

Inside the existing supabase-env multiline value, append four new lines (these are sourced as shell variables by env-generate.sh):

SMTP_USER=supabase@cloonar.com
SMTP_PASS=<plaintext from step 1>
GOOGLE_CLIENT_ID=<from step 5>
GOOGLE_SECRET=<from step 5>

4. DNS records for fueltide.io

Add on whichever DNS provider hosts fueltide.io:

TXT  @                    v=spf1 mx a:mail.cloonar.com ~all
TXT  default._domainkey   v=DKIM1; k=rsa; p=<public key from step 2>
TXT  _dmarc               v=DMARC1; p=quarantine; rua=mailto:postmaster@cloonar.com; fo=1

PTR for mail.cloonar.com is already set (it's been sending for cloonar.com). If fueltide.io has no MX record, outbound is fine but bounces from remote MTAs won't route — acceptable for one-way transactional mail. Add an MX pointing at mail.cloonar.com. if you want bounces to be received.

5. Google Cloud OAuth client (≈ 5 min)

  1. console.cloud.google.com → APIs & Services → OAuth consent screen. External user type. App name Fueltide, user support email, developer contact. Scopes: openid, email, profile. Submit (or keep in testing if only internal users).
  2. Credentials → Create Credentials → OAuth client ID → Web application. Name Supabase. Authorised redirect URI: https://supabase.cloonar.com/auth/v1/callback.
  3. Copy Client ID + Client Secret → into SOPS as GOOGLE_CLIENT_ID and GOOGLE_SECRET.

6. Apple Developer — enable Sign in with Apple on the iOS App ID

Only one action, no keys or Services IDs:

  1. developer.apple.com → Certificates, IDs & Profiles → Identifiers → App IDs. Select io.fueltide.workout (Team XWJ4DC7TBH, see hosts/web-arm/sites/fueltide.io.nix). Check Sign in with Apple. Save.

That's it on the Apple side. No Services ID, no Keys, no .p8 download. The iOS app obtains the id_token on-device via AuthenticationServices and posts it to supabase.auth.signInWithIdToken; GoTrue validates it against Apple's JWKS with aud=io.fueltide.workout.

7. Merge and deploy

Once steps 16 are done:

./scripts/test-configuration web-arm
./scripts/test-configuration mail
git checkout master
git merge --no-ff <this-branch>
git push

Bento rolls out both hosts. On web-arm.cloonar.com:

sudo systemctl restart supabase-env-generate
sudo cat /run/supabase/auth.env           # expect SMTP + Google vars populated
sudo podman exec supabase-auth nc -vz mail.cloonar.com 587
sudo podman restart supabase-auth

Verification checklist

  • /run/supabase/auth.env contains GOTRUE_SMTP_USER, GOTRUE_SMTP_PASS, GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID, GOTRUE_EXTERNAL_GOOGLE_SECRET.
  • podman inspect supabase-auth shows GOTRUE_EXTERNAL_APPLE_ENABLED=true and GOTRUE_EXTERNAL_APPLE_CLIENT_ID=io.fueltide.workout in the env.
  • curl -X POST -H 'apikey: <anon>' -H 'Content-Type: application/json' \ https://supabase.cloonar.com/auth/v1/signup \ -d '{"email":"<real inbox>","password":"correct horse battery staple"}' delivers a mail with From: noreply@fueltide.io within ~30 s.
  • Mail headers show dkim=pass, spf=pass, dmarc=pass (Authentication-Results header).
  • POST /auth/v1/recover triggers a reset mail.
  • Browser visit to https://supabase.cloonar.com/auth/v1/authorize?provider=google completes and lands on /auth/v1/callback. Row in auth.identities with provider='google'.
  • From the iOS app: Sign in with Apple → supabase.auth.signInWithIdToken({ provider: 'apple', token, nonce }) succeeds. Row in auth.identities with provider='apple' and identity_data.sub matching the Apple user id. (Apple sign-in has no browser flow here — it is tested from the app only.)
  • Send a signup to mail-tester.com — target ≥ 9/10 spam score.

Rotation notes

  • Google client secret: no expiry; rotate via Google Cloud console if leaked and update GOOGLE_SECRET in SOPS.
  • DKIM key: no expiry, but best practice is to rotate yearly. Rotation = regenerate keypair (step 2), replace the SOPS value (step 3), update DNS (step 4), deploy. Keep both old+new DNS records live for 24h during cutover.
  • SMTP LDAP password: no expiry. To rotate, run mkpasswd again and update both the LDAP userPassword attribute and SOPS SMTP_PASS.