fefeat: supabase add secrets and change to just ios native auth

This commit is contained in:
Dominik Polakovics Polakovics 2026-04-24 18:57:09 +02:00
parent 67e81d39f3
commit 5c6b4f18eb
5 changed files with 134 additions and 205 deletions

View file

@ -1,4 +1,4 @@
# Supabase auth setup: Google + Apple OAuth, fueltide.io email
# 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
@ -6,20 +6,28 @@ 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
- `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.
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).
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).
---
@ -97,7 +105,7 @@ rspamd-dkim-fueltide-io-key: |
nix-shell -p sops --run 'sops hosts/web-arm/secrets.yaml'
```
Inside the existing `supabase-env` multiline value, append eight new lines
Inside the existing `supabase-env` multiline value, append four new lines
(these are sourced as shell variables by `env-generate.sh`):
```
@ -105,20 +113,6 @@ SMTP_USER=supabase@cloonar.com
SMTP_PASS=<plaintext from step 1>
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`
@ -148,45 +142,19 @@ won't route — acceptable for one-way transactional mail. Add an MX pointing at
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)
## 6. Apple Developer — enable Sign in with Apple on the iOS App ID
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>`
Only one action, no keys or Services IDs:
### iOS native flow (optional)
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.
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.)
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
@ -204,16 +172,18 @@ 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 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_EXTERNAL_APPLE_SECRET=<long-JWT>`.
- [ ] Second `systemctl restart supabase-env-generate` produces a different
Apple JWT (freshness — signed with new `iat`).
- [ ] `/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"}'`
@ -225,17 +195,16 @@ sudo podman restart supabase-auth
`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.
- [ ] 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
- **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