# Supabase auth setup: Google + Apple OAuth, 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 + Apple OAuth, SMTP pointed at `mail.cloonar.com:587`, `MAILER_AUTOCONFIRM=false`, `SITE_URL` + `URI_ALLOW_LIST` for fueltide.io, python+cryptography in the env-generate path (for Apple JWT signing). - `hosts/web-arm/modules/supabase/env-generate.sh` — new `auth.env` block that pulls SMTP + OAuth creds from SOPS and signs the Apple client-secret JWT fresh on every activation. - `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. Complete the seven steps below **before** merging to master. Merging without them will deploy a broken GoTrue (missing OAuth/SMTP creds → auth emails fail, 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`. ```bash # 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 < 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` ```bash nix-shell -p sops --run 'sops hosts/mail/secrets.yaml' ``` Add: ```yaml rspamd-dkim-fueltide-io-key: | -----BEGIN PRIVATE KEY----- -----END PRIVATE KEY----- ``` ### `hosts/web-arm/secrets.yaml` ```bash nix-shell -p sops --run 'sops hosts/web-arm/secrets.yaml' ``` Inside the existing `supabase-env` multiline value, append eight new lines (these are sourced as shell variables by `env-generate.sh`): ``` SMTP_USER=supabase@cloonar.com SMTP_PASS= GOOGLE_CLIENT_ID=<from step 5> GOOGLE_SECRET=<from step 5> APPLE_TEAM_ID=XWJ4DC7TBH APPLE_KEY_ID=<from step 6> APPLE_SERVICES_ID=com.cloonar.supabase.fueltide APPLE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\n<.p8 body>\n-----END PRIVATE KEY----- ``` Note on `APPLE_PRIVATE_KEY`: it must be **one line** with literal backslash-n separating the PEM lines (no real newlines inside the value). The python signer in `env-generate.sh` un-escapes those via `decode("unicode_escape")` before loading the PEM. To format an existing `AuthKey_XXX.p8` as that single line: ```bash awk '{printf "%s\\n", $0}' AuthKey_XXXXXXXXXX.p8 ``` ## 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 Sign in with Apple (≈ 15 min, paid account required) 1. developer.apple.com → **Certificates, IDs & Profiles → Identifiers → + → Services IDs**. Description `Fueltide Supabase Auth`. Identifier `com.cloonar.supabase.fueltide`. Check **Sign in with Apple → Configure**. 2. Primary App ID: existing `io.fueltide.workout` (Team `XWJ4DC7TBH`, see `hosts/web-arm/sites/fueltide.io.nix`). Domains and Subdomains: `supabase.cloonar.com`. Return URLs: `https://supabase.cloonar.com/auth/v1/callback`. Save. 3. **Keys → +** → name `Fueltide Supabase Auth` → check **Sign in with Apple → Configure** → primary App ID `io.fueltide.workout`. Register. 4. **Download the `.p8` file now** — Apple only offers it once. 5. Note the Key ID (10 chars) displayed on the key page. 6. Team ID is `XWJ4DC7TBH` (already known). 7. Into SOPS on web-arm: - `APPLE_TEAM_ID=XWJ4DC7TBH` - `APPLE_KEY_ID=<from step 5>` - `APPLE_SERVICES_ID=com.cloonar.supabase.fueltide` - `APPLE_PRIVATE_KEY=<single-line .p8 as described in step 3>` ### iOS native flow (optional) If the fueltide iOS app will use `supabase.auth.signInWithIdToken({ provider: 'apple', token: identityToken })` (native `AuthenticationServices` SDK, no web browser), the iOS bundle ID must also appear in `GOTRUE_EXTERNAL_APPLE_CLIENT_ID`. Change the line in `env-generate.sh` that currently reads: ```sh GOTRUE_EXTERNAL_APPLE_CLIENT_ID=${APPLE_SERVICES_ID:-} ``` to something like: ```sh GOTRUE_EXTERNAL_APPLE_CLIENT_ID=${APPLE_SERVICES_ID:-},io.fueltide.workout ``` (GoTrue accepts a comma-separated audiences list here and validates incoming id_tokens against any of them.) ## 7. Merge and deploy Once steps 1–6 are done: ```bash ./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`: ```bash sudo systemctl restart supabase-env-generate sudo cat /run/supabase/auth.env # expect 8 new 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_EXTERNAL_APPLE_SECRET=<long-JWT>`. - [ ] Second `systemctl restart supabase-env-generate` produces a different Apple JWT (freshness — signed with new `iat`). - [ ] `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'`. - [ ] Same with `?provider=apple` from a page Apple's Return URL accepts. - [ ] Send a signup to [mail-tester.com](https://www.mail-tester.com/) — target ≥ 9/10 spam score. ## Rotation notes - **Apple client-secret JWT**: auto-regenerated on every activation (`supabase-env-generate.service`). No manual rotation. - **Apple `.p8` key**: no expiry, but revoking it in the Apple console immediately breaks auth. If ever rotated, update `APPLE_KEY_ID` and `APPLE_PRIVATE_KEY` in SOPS together. - **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`.