fix(auth): reconcile production-shipped stale-identity fix into the live lineage - #8
Merged
Aalv3 merged 4 commits intoSep 3, 2026
Conversation
A non-ephemeral ASWebAuthenticationSession shares the system Safari data store. Deleting the app clears the container, AsyncStorage, the client ID, the RSA keys and the stored User API Key, but not that shared cookie jar, so a reinstalled app presented an already-authenticated Discourse session and bound the new User API Key to the previous account without prompting. Physical TestFlight Build 8 production testing confirmed the auth session reused qa_test instead of allowing cert_probe_01 to sign in (server-side evidence commit 35d9d27). The native bridge already accepted an ephemeral argument and applied it as prefersEphemeralWebBrowserSession. The defect was entirely in JavaScript: site_manager passed a hardcoded false and requestIOSAuth defaulted to false. EPHEMERAL_AUTH_SESSION is now a module constant rather than a caller-supplied parameter, so no call site can reintroduce a persistent session. The adjusternetwork://auth_redirect callback contract, the one-shot nonce/client-ID binding, the canonical-origin admission check, and the fail-closed channel-derived environment resolution are unchanged. OTA-shippable on runtime an-ios-android-1.0.0-native-2: the shipped Build 8 executable already exports requestAuth:callbackURLScheme:ephemeral:resolver: rejecter: and references setPrefersEphemeralWebBrowserSession:, so the JS/native interface is unchanged and no new binary is required. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fk48MTrNBBSZeLvcJmc8SR
POST-SUBMISSION-HOLD.md still names group 39eb1e9b-8b72-480b-99f1-f52ad6d351fc as production. A read-only channel check on 2026-09-02 shows production now serves group 70eebadf-5736-4cd6-a7db-2980a69f0494 at source fdb8314, published 2026-09-01. That document is the stated rollback target during the Apple-review freeze, so the stale value is worth correcting. The hold document is untracked in this lineage, so the correction is recorded in the fix lane's backlog rather than applied to it. Documentation only; it does not affect the staged OTA, which was published from fdd5963. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fk48MTrNBBSZeLvcJmc8SR
The ephemeral browser-auth session alone did not resolve the stale-identity failure on a physical device, and it cannot: it is not the only carrier of a previous member identity, and the app cannot observe whether iOS honoured it. Three carriers survive an app delete and reinstall: - the Keychain User API Key and RSA material, which outlive the app container and are restored without any staleness check; - the shared Safari cookie jar, which app deletion never clears; - js/admissionHandoff.js, which opens the real Safari app through Linking.openURL, where prefersEphemeralWebBrowserSession has no effect. SiteManager.resetAuthorizationIdentity retires all of the client-side carriers together - browser cookies, stored User API Key, RSA material, the recorded authorization profile and the client ID - and the sign-in screen exposes it as "Use a different account". This does not depend on iOS honouring an ephemeral session and it never revokes a server-side credential. A staging-gated diagnostics block on the sign-in screen reports the active OTA git SHA, update ID, channel and embedded-versus-remote source, plus whether a Keychain credential survived reinstall, so bundle activation is provable in the product instead of over USB. It also opens the canonical session endpoint inside the same browser-auth context, which shows directly whether that context is anonymous. The gate is the trusted OTA channel, so the block cannot render on production. The ephemeral session is retained: it is correct, and it is the cheaper of the two mechanisms when iOS honours it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fk48MTrNBBSZeLvcJmc8SR
…iagnosis Physical testing found no avatar controls in the native Edit Profile screen. The native editor is in fact already built: NativeProfileScreen renders the current avatar, a Change photo action and a Remove photo action, backed by uploadProfilePhoto/removeProfilePhoto against /native/v1/profile/photo, which delegates to Discourse's own avatar system rather than a parallel one, with type validation, permission and error states, and immediate cross-surface refresh through avatarAuthority. The block is gated on the server capability card.photo.enabled, and POST-SUBMISSION-HOLD.md deliberately keeps structured_profile_photo_enabled false during Apple review. The controls were therefore absent because the capability is off, not because the UI is missing. Remaining work is the deferred server/privacy activation lane, one genuine native gap (camera capture, library picking only today), and a web parity audit that cannot be performed from this repository. Recorded only. No product code changed during auth certification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fk48MTrNBBSZeLvcJmc8SR
Aalv3
merged commit Sep 3, 2026
6846b9e
into
fix/auth-failure-classification-20260830
4 checks passed
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.
Reconciles the code currently serving production into the live source lineage, so production is not running behavior that exists only in an OTA artifact and a feature branch.
What is already in production
cb5cb74b-32a4-4aa9-ba22-6be1967f4dea5b7d754fc94d450f7ce49e7f958bb0b7a95013d1an-ios-android-1.0.0-native-270eebadf-5736-4cd6-a7db-2980a69f0494(fdb83141879f)Promotion was a republish of the physically certified staging group
6796074f-42ba-4793-9a69-528ec20ec62f, not a rebundle: launch-asset hashes are identical on both platforms.Behavior
No behavior change from what shipped. This branch is a clean fast-forward on
fdb83141879f(4 ahead, 0 behind). Two commits are product code, two are lane documentation.Physically certified on an iPhone 15 Pro running the staging Ad Hoc binary, with the active bundle confirmed from the device's expo-updates database (
updateId 01a064f2ce14746c89664de78912f441, the iOS update of the certified group). Signing out and starting Member sign in opened a clean credential screen and did not reuse the prior authenticated identity.Root cause
The ephemeral browser-auth session was necessary but not sufficient. Three carriers of a previous identity survive an app delete and reinstall:
js/admissionHandoff.jsopens the real Safari app viaLinking.openURL, whereprefersEphemeralWebBrowserSessionhas no effect.SiteManager.resetAuthorizationIdentity()retires all client-side carriers together, surfaced as Use a different account. It does not depend on iOS honouring an ephemeral session and never revokes a server-side credential. The ephemeral session is retained as the cheaper mechanism when honoured.Preserved contracts
Governed
AUTH_REDIRECTcallback and its allowlist; one-shot nonce/client-ID binding including the server-verified/native/v1/authorization-profileexact match; canonical-origin admission check; fail-closed channel-derived environment resolution. No native source, dependency, entitlement, permission, capability or runtime change.The staging diagnostics block is gated on
Updates.channel === 'staging'and cannot render on a production binary — the productionExpo.plistcarriesexpo-channel-name: production.Validation
validate:systemon0c04f005: format/lint clean, 90 suites / 623 tests,verify:ota17/17,verify:ios-auth12/12, native Release BUILD SUCCEEDED.Notes for the reviewer
mainis the upstream Discourse fork baseline (July, PR UX: hide keyboard accessory bar on iPhone discourse/DiscourseMobile#294) and is 150 commits behind this lineage.fix/auth-failure-classification-20260830is the de facto Adjuster Network trunk and the branch production ships from, which is why it is the base here.testing/native-auth-stale-identity/BACKLOG.mdrecords follow-ups including the now-stalePOST-SUBMISSION-HOLD.mdproduction-group reference. No backlog item is implemented in this PR.