Skip to content

iOS: account sign-in, and local + public server addresses - #7

Merged
techmore merged 1 commit into
mainfrom
fix/ios-login-local-public-servers
Nov 2, 2026
Merged

techmore merged 1 commit into
mainfrom
fix/ios-login-local-public-servers

Conversation

@techmore

@techmore techmore commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Why the iPhone app stopped working

The app reaches https://sonder.stoverparc.org fine (TLS is OK and the server answers), but the library never loads. Since account auth landed on the server (643cdc1, 2026-09-24), every API route answers 401 unless the request carries an account session. The iOS client could only send a legacy pairing token and had no sign-in screen, so it showed "No Titles" and "This server is restricted. Add a token…".

Reproduced in the iOS 27.2 simulator against the live public URL before the change.

What changed

Sign-in

  • A sign-in sheet appears when the server answers 401. It shows that the server answered, and on which address.
  • It calls POST /api/auth/login, takes the sonder_session value from Set-Cookie, and stores it in the Keychain per server. Requests send it as Authorization: Bearer, which the server already accepts. One sign-in works on both the local and the public address.
  • Requests no longer send shared cookies. sessionUsername reads a cookie before the Authorization header, so a stale cookie could hide a valid token.
  • Sign Out is in Settings. Wrong password, throttling (429 Retry-After) and "no account yet" (409) each get their own message. A 403 (LAN access off) no longer asks you to sign in.
  • No server changes.

Local + public addresses

  • Settings and onboarding have a public address field and an optional local address field.
  • On launch, on network change (NWPathMonitor) and from a "check again" button, the app probes <local>/api/auth/session with a 1.5 s timeout and requires X-Sonder-Server: tm-sonder, so a captive portal doesn't count. If the probe fails, it uses public. A network error on the local route mid-sync falls back to public at once.
  • Settings shows the active address (Local network / Public internet).
  • Offline copies, queued progress, caches and the token stay keyed by the server's identity (its public address). Switching routes doesn't hide downloads. The old single sonder.serverBaseURL setting migrates to the public address.
  • ATS: no exception added. Plain http:// to a LAN IP loaded under the default ATS policy in the simulator, so none was needed.

Testing

  • swift test --package-path IOS_Client_Xcode/TM_Sonder_Client: 29 tests pass, 20 of them new (address parsing, local/public selection, probe identity check, sign-in response handling, Go timestamp parsing).
  • Simulator, live server: public route → server answered → sign-in screen. The owner's password was not entered.
  • Simulator, local stub on the LAN with a fake account: local route chosen → sign-in → library loads (3 stub titles). With the stub stopped, "check again" fell back to the public address. Wrong-password path checked too.

Note for the local address on the current network

On ser8's LAN, Caddy answers only for the hostname sonder.stoverparc.org on 10.20.0.117:443, because the certificate needs that name. :8096 and :8098 aren't reachable from the LAN (8098 is host loopback). So http://10.20.0.117:… won't work as a local address yet. Two ways to get one, neither done here:

  1. A LAN DNS override sonder.stoverparc.org → 10.20.0.117. The public address then goes direct at home, and no local address is needed.
  2. A Caddy site bound to the LAN address, for example http://10.20.0.117:8096 → 127.0.0.1:8098, entered as the local address.

🤖 Generated with Claude Code

The app could reach https://sonder.stoverparc.org but never load the
library: since account auth landed on the server (643cdc1, 2026-09-24)
every API route answers 401 unless the request carries an account
session, and the iOS client only knew how to send a legacy pairing
token. There was no way to sign in from the phone.

Sign-in
- New sign-in sheet, shown when the server answers 401. It calls
  POST /api/auth/login, takes the sonder_session cookie value from the
  response and stores it per server in the Keychain, then sends it as a
  Bearer token. The same token works on both addresses.
- Requests no longer send shared cookies: the server reads a cookie
  before the Authorization header, so a stale cookie could hide a valid
  token.
- Sign out in Settings; wrong password, throttling and "no account yet"
  get their own messages. 403 (LAN access off) no longer asks to sign in.

Local + public addresses
- Settings and onboarding take a public address and an optional local
  one. On launch, on network change (NWPathMonitor) and on demand the
  app probes the local address's /api/auth/session with a 1.5 s timeout
  and requires the X-Sonder-Server header; otherwise it uses public.
  A transport error on the local route falls back to public at once.
- Settings shows which address is active.
- Downloads, queued progress, caches and the token stay keyed by the
  server's identity (its public address), so switching routes does not
  hide offline copies. Older single-URL settings migrate to the public
  address.
- No ATS exception is needed: plain http to a LAN IP loads under the
  default ATS policy (checked in the iOS 27.2 simulator).

Tests: 20 new unit tests (address parsing, route selection, probe,
sign-in responses, date parsing); swift test passes (29 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@techmore
techmore merged commit 09c6267 into main Nov 2, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant