Repository navigation
fix(ios): MM-70605 give each recreated session its own delegate in addSessionHeaders - #175
Merged
Merged
Conversation
…dSessionHeaders addSessionHeaders invalidated the old Alamofire Session and created the new one with the same SessionDelegate. Session.init points the delegate's stateProvider at the new Session, so the old URLSession's didBecomeInvalidWithError reached the new Session from the old root queue and failed the dispatchPrecondition in cancelRequestsForSessionInvalidation (SIGTRAP). ApiClientSessionDelegate holds no state, so a fresh instance behaves the same.
fmartingr
marked this pull request as ready for review
October 17, 2026 16:14
fmartingr
added a commit
to mattermost/mattermost-mobile
that referenced
this pull request
Oct 17, 2026
Temporary pin to mattermost/react-native-network-client#175 to check CI. Replace with the released version before merge.
fmartingr
added a commit
to mattermost/mattermost-mobile
that referenced
this pull request
Oct 23, 2026
* [MM-70605][MM-70607] Scope pre-auth secret storage per server The pre-auth secret was written with react-native-keychain's generic-password API using the `server` option, which those calls do not read. Every server therefore resolved to one shared entry, and a falsy secret argument deleted it, so ordinary operations such as building a client, refreshing the token header or removing another server discarded the stored value. Key the entry per server instead. On Android the alias namespace is shared with internet credentials, so the service is prefixed to keep it away from the session token; iOS uses the bare server URL, which is what the native reader in Gekidou already queries. The account value is renamed to `preauth_secret` on both sides. Writing the token no longer touches the secret: removal is explicit, via removePreauthSecret. A one-time migration adopts the previously shared entry when a single server makes ownership unambiguous, and discards it otherwise. It only deletes the old entry once the new one is confirmed stored, and defers when no server is active. Also forward the secret through the login screen to SSO, and fix loginOptions to count only enabled providers so the single-provider redirect works. Relands the `.enabled` filter from #9585 by @pltnm239. Edit Server now updates the live client's headers rather than invalidating the session, and validates with the candidate secret so a value the server rejects cannot be saved. * [MM-70605] Harden preauth secret flows and fix Edit Server Detox on iOS Address review feedback around migration markers, credential errors, and keeping re-login secrets out of nav params. Focus the empty Authentication secret field via Display Name return so iOS Detox can type into it. Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Fix lines-around-comment in Edit Server Detox helper Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Roll back preauth keychain when Edit Server client sync fails After the secret is stored, header apply or createClient failures left the new value in secure storage while UI state still held the old baseline. Restore the previous secret (and live header) before surfacing the error. Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Preserve exact preauth secret on Edit Server rollback Avoid trimming during keychain restore so boundary whitespace from the previously stored value is not altered. Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Harden preauth migration ownership and secret handoff Adopt the legacy secret only when a single server exists in the app DB, keep add-server candidates in EphemeralStore instead of nav params, and resync the live client to stored credentials when keychain rollback fails. Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Discard the legacy preauth secret when no server can own it * [MM-70605] Shorten the preauth migration docblock * [MM-70605] Skip the Authorization header when there is no bearer token * [MM-70605] Surface preauth secret rejections with a specific error message * [MM-70605] Mock clearPendingPreauthSecret in session action tests * [MM-70605] Remove the unreachable catch from restorePreviousPreauthSecret setPreauthSecret and removePreauthSecret never reject, and the returned promise was not awaited, so the catch could not run. * [MM-70605] Use network-client fix for the iOS addHeaders crash Temporary pin to mattermost/react-native-network-client#175 to check CI. Replace with the released version before merge. * [MM-70605] Bump react-native-network-client to 1.11.5 1.11.5 gives each recreated iOS session its own delegate in addHeaders, which fixes the SIGTRAP on pre-auth secret changes. --------- Co-authored-by: Cursor <cursoragent@cursor.com>
ux-builder
pushed a commit
to okrbest/okrbest-mobile
that referenced
this pull request
Oct 27, 2026
* [MM-70605][MM-70607] Scope pre-auth secret storage per server The pre-auth secret was written with react-native-keychain's generic-password API using the `server` option, which those calls do not read. Every server therefore resolved to one shared entry, and a falsy secret argument deleted it, so ordinary operations such as building a client, refreshing the token header or removing another server discarded the stored value. Key the entry per server instead. On Android the alias namespace is shared with internet credentials, so the service is prefixed to keep it away from the session token; iOS uses the bare server URL, which is what the native reader in Gekidou already queries. The account value is renamed to `preauth_secret` on both sides. Writing the token no longer touches the secret: removal is explicit, via removePreauthSecret. A one-time migration adopts the previously shared entry when a single server makes ownership unambiguous, and discards it otherwise. It only deletes the old entry once the new one is confirmed stored, and defers when no server is active. Also forward the secret through the login screen to SSO, and fix loginOptions to count only enabled providers so the single-provider redirect works. Relands the `.enabled` filter from #9585 by @pltnm239. Edit Server now updates the live client's headers rather than invalidating the session, and validates with the candidate secret so a value the server rejects cannot be saved. * [MM-70605] Harden preauth secret flows and fix Edit Server Detox on iOS Address review feedback around migration markers, credential errors, and keeping re-login secrets out of nav params. Focus the empty Authentication secret field via Display Name return so iOS Detox can type into it. Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Fix lines-around-comment in Edit Server Detox helper Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Roll back preauth keychain when Edit Server client sync fails After the secret is stored, header apply or createClient failures left the new value in secure storage while UI state still held the old baseline. Restore the previous secret (and live header) before surfacing the error. Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Preserve exact preauth secret on Edit Server rollback Avoid trimming during keychain restore so boundary whitespace from the previously stored value is not altered. Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Harden preauth migration ownership and secret handoff Adopt the legacy secret only when a single server exists in the app DB, keep add-server candidates in EphemeralStore instead of nav params, and resync the live client to stored credentials when keychain rollback fails. Co-authored-by: Cursor <cursoragent@cursor.com> * [MM-70605] Discard the legacy preauth secret when no server can own it * [MM-70605] Shorten the preauth migration docblock * [MM-70605] Skip the Authorization header when there is no bearer token * [MM-70605] Surface preauth secret rejections with a specific error message * [MM-70605] Mock clearPendingPreauthSecret in session action tests * [MM-70605] Remove the unreachable catch from restorePreviousPreauthSecret setPreauthSecret and removePreauthSecret never reject, and the returned promise was not awaited, so the catch could not run. * [MM-70605] Use network-client fix for the iOS addHeaders crash Temporary pin to mattermost/react-native-network-client#175 to check CI. Replace with the released version before merge. * [MM-70605] Bump react-native-network-client to 1.11.5 1.11.5 gives each recreated iOS session its own delegate in addHeaders, which fixes the SIGTRAP on pre-auth secret changes. --------- Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 9ccfa0e) Upstream: mattermost/mattermost-mobile@9ccfa0e
amyblais
added a commit
to mattermost/mattermost-mobile
that referenced
this pull request
Oct 27, 2026
#10278) * [MM-70605][MM-70607] Scope pre-auth secret storage per server The pre-auth secret was written with react-native-keychain's generic-password API using the `server` option, which those calls do not read. Every server therefore resolved to one shared entry, and a falsy secret argument deleted it, so ordinary operations such as building a client, refreshing the token header or removing another server discarded the stored value. Key the entry per server instead. On Android the alias namespace is shared with internet credentials, so the service is prefixed to keep it away from the session token; iOS uses the bare server URL, which is what the native reader in Gekidou already queries. The account value is renamed to `preauth_secret` on both sides. Writing the token no longer touches the secret: removal is explicit, via removePreauthSecret. A one-time migration adopts the previously shared entry when a single server makes ownership unambiguous, and discards it otherwise. It only deletes the old entry once the new one is confirmed stored, and defers when no server is active. Also forward the secret through the login screen to SSO, and fix loginOptions to count only enabled providers so the single-provider redirect works. Relands the `.enabled` filter from #9585 by @pltnm239. Edit Server now updates the live client's headers rather than invalidating the session, and validates with the candidate secret so a value the server rejects cannot be saved. * [MM-70605] Harden preauth secret flows and fix Edit Server Detox on iOS Address review feedback around migration markers, credential errors, and keeping re-login secrets out of nav params. Focus the empty Authentication secret field via Display Name return so iOS Detox can type into it. * [MM-70605] Fix lines-around-comment in Edit Server Detox helper * [MM-70605] Roll back preauth keychain when Edit Server client sync fails After the secret is stored, header apply or createClient failures left the new value in secure storage while UI state still held the old baseline. Restore the previous secret (and live header) before surfacing the error. * [MM-70605] Preserve exact preauth secret on Edit Server rollback Avoid trimming during keychain restore so boundary whitespace from the previously stored value is not altered. * [MM-70605] Harden preauth migration ownership and secret handoff Adopt the legacy secret only when a single server exists in the app DB, keep add-server candidates in EphemeralStore instead of nav params, and resync the live client to stored credentials when keychain rollback fails. * [MM-70605] Discard the legacy preauth secret when no server can own it * [MM-70605] Shorten the preauth migration docblock * [MM-70605] Skip the Authorization header when there is no bearer token * [MM-70605] Surface preauth secret rejections with a specific error message * [MM-70605] Mock clearPendingPreauthSecret in session action tests * [MM-70605] Remove the unreachable catch from restorePreviousPreauthSecret setPreauthSecret and removePreauthSecret never reject, and the returned promise was not awaited, so the catch could not run. * [MM-70605] Use network-client fix for the iOS addHeaders crash Temporary pin to mattermost/react-native-network-client#175 to check CI. Replace with the released version before merge. * [MM-70605] Bump react-native-network-client to 1.11.5 1.11.5 gives each recreated iOS session its own delegate in addHeaders, which fixes the SIGTRAP on pre-auth secret changes. --------- (cherry picked from commit 9ccfa0e) Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SessionManager.addSessionHeaderscrashes the iOS app withSIGTRAPwhen the old session's invalidation callback arrives after the new session exists.addSessionHeadersinvalidates the old AlamofireSessionand creates a new one with the sameSessionDelegate.Session.initsetsdelegate.stateProviderto the newSession, and the newSessiongets its own serial root queue that targets the old one.URLSessionthen callsdidBecomeInvalidWithErroron the old root queue. The shared delegate forwards it to the newSession, andcancelRequestsForSessionInvalidationfailsdispatchPrecondition(condition: .onQueue(rootQueue)).The fix gives the recreated session a new
ApiClientSessionDelegate. That class holds no state (it looks sessions up throughSessionManager.default), so a new instance behaves the same. The old delegate keeps a weak reference to the oldSessiononly.Stack (Detox iOS E2E in mattermost-mobile#10149, and a local reproduction):
Testing
iOS 26.5 simulator, Release build of mattermost-mobile with this change applied via
patch-package. A local server requires theX-Mattermost-Preauth-Secretheader. Edit Server → change the pre-auth secret callsaddHeaders.invalidateSessionWith the fix, the server log shows every request switch to the new header value after each save.
Android is not affected: OkHttp adds headers without recreating the session.
Ticket Link
https://mattermost.atlassian.net/browse/MM-70605
Change Impact: 🟡 Medium
Changes Breakdown:
+3 -1 Production Changes (100%)
+0 -0 Test Changes (0%)
Regression Risk: The change is limited to session recreation in
SessionManager.addSessionHeaders, but it affects iOS session management. Test coverage for this path is not reported.Generated by CodeRabbitAI