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

215 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`.
```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 <<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.
```bash
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`
```bash
nix-shell -p sops --run 'sops hosts/mail/secrets.yaml'
```
Add:
```yaml
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`
```bash
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:
```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 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](https://www.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`.