Skip to content

[Bug]: iOS sign-in saves the guessed mail.<domain> host instead of the server the redirects lead to #95

Closed

Description

@alyyousuf7

Description

When you sign in with an email address, the app checks /.well-known/jmap on https://<domain>, https://mail.<domain> and https://webmail.<domain>. It saves the name it tried for whichever one answered (src/lib/server-discovery.ts L194 at 0.1.86). When mail.<domain> only redirects to the real mail server, as it often does for a second domain on the same server, the account is saved with that redirect name.

Steps to Reproduce

  1. Host example.org as an extra domain on a Stalwart server at mail.example.com, with https://mail.example.org redirecting (302) to https://mail.example.com.
  2. In the iOS app, sign in as user@example.org.
  3. Look at the server URL saved for the account.

Expected Behavior

The account is saved with https://mail.example.com, the server that actually answered. _jmap._tcp.example.org SRV, ua-auto-config and the session document's apiUrl all name that host.

Actual Behavior

The account is saved with https://mail.example.org. Every request then takes the redirect, which may drop the Authorization header when iOS follows it to another domain, and the operator has to keep the redirect name and its TLS certificate alive for as long as the account exists.

Bulwark Edition

Bulwark

Bulwark Version

0.1.86 (build 32, TestFlight)

Stalwart Mail Server Version

1.0.3

Browser

No response

Operating System

iOS

Screenshots / Screen Recording

No response

Relevant Logs or Error Output

GET https://mail.example.org/.well-known/jmap
  → 302 https://mail.example.com/.well-known/jmap
  → 307 /jmap/session
  → 200 (session document; apiUrl https://mail.example.com/jmap/)

Additional Context

This is the iOS app (bulwarkmail/native, TestFlight 0.1.86), not the webmail. The same code is still on main (L217 at f5875f8).

Possible fixes, any one would do:

  1. In probeJmapServer, return the origin of response.url (where the redirects ended up) instead of the name that was tried.
  2. Take the origin from the session document's apiUrl.
  3. Check _jmap._tcp.<domain> SRV (RFC 8620 §2.2) and/or ua-auto-config before guessing host names.

Activity

  1. bulwark-warden commented on Nov 8, 2026

    @bulwark-warden

    The current main (19720d6) behaves the same way. probeJmapServer returns only a boolean, and discoverServerForEmail keeps the candidate name it tried (server-discovery.ts L210-L217). The login screen saves that value as the server URL (LoginScreen.tsx L378-L384).

    This also explains why every request goes through the redirect. rewriteSessionUrls moves the session's apiUrl onto the origin of the saved server URL (jmap-client.ts L884-L896, rewriteSessionUrl). So the session document's apiUrl on mail.example.com gets replaced with the mail.example.org origin.

    Some details that matter when choosing among your proposed fixes:

    • Option 2 (apiUrl) does not work when the server answers 401. The probe counts a 401 as a hit without reading a body (L161), so there is no session document to read.
    • Option 1 (response.url) only covers the plain fetch path. With a client certificate set, secureFetch follows a redirect only to the same host or from http to https of that host. Any other 3xx comes back unfollowed (client-cert.ts L219-L247, mayFollowRedirect). The probe then sees a non-200 status and returns false (L162). For those users, mail.example.org isn't discovered at all.
    • No SRV lookup exists yet. The parity notes say "neither side does _jmap._tcp SRV" (docs/parity/01-auth-accounts.md L175).

    Until this is fixed, you can avoid the guessed host by entering the real server yourself through "I know my server address" (EmailStep.tsx L93-L95).

    Bulwark Warden is an AI assistant and can be wrong. It answers from the docs, the code and earlier issues, and links its sources.

  2. rathlinus commented on Nov 9, 2026

    @rathlinus
    Member

    9b28c00 on main changes this. When the probe of a guessed name is answered on another host, the app now offers and saves that host: for your example the confirm step shows https://mail.example.com and the account is stored with it. A base path in front of /jmap/session is kept. A redirect that ends on plain http is not taken over.

    With a client certificate set, the request does not follow a redirect to another host, so the probe now takes that one step itself. It carries no credentials.

    Not in this change: the SRV lookup and ua-auto-config. An account that was already saved with the redirect name keeps it until you sign in again.

    This is covered by unit tests only. I could not run it on iOS against a setup like yours, so I am leaving the issue open. Could you confirm with the next release?

  3. wmcparla commented on Nov 14, 2026

    @wmcparla
    Contributor

    I ran this on iOS against a setup like the one in the report. The build was the branch of #142, which is main at b3b4230 (0.1.94 plus the commits since, 9b28c00 among them) with one change to the tab bar; the sign-in code is the same as on main.

    Setup: an iPhone 17 Pro simulator on iOS 26.5, a Release build. mail.<second domain> answered every request with a 302 to the same path on the real mail server, a Stalwart 1.1.0 on another domain; the bare second domain answered 404. The redirecting host logged each request it received.

    • Signing in with "Use my email address" and user@<second domain>: the confirm step showed the real mail server, not mail.<second domain>.
    • After a password sign-in, Settings > Account showed the real server as the JMAP server.
    • The redirecting host saw two requests in all, the discovery probes of /.well-known/jmap on the bare domain and on mail., neither with an Authorization header. Nothing came through it after sign-in.

    Not covered: OAuth sign-in, a client certificate (the path where the probe takes the redirect step itself), and a real device.

    🤖 Generated with Claude Code

  4. rathlinus commented on Nov 15, 2026

    @rathlinus
    Member

    This shipped in 0.1.93 (9b28c00), and wmcparla confirmed it on iOS against a redirect setup like yours: the confirm step and the saved account show the real server, and nothing goes through the redirect host after sign-in. An account saved with the redirect name before the update keeps it until you sign in again. Closing this; if you still see the guessed host on 0.1.93 or later, reopen it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions