Repository navigation
fix(mobile): couper la session d'un compte supprimé et la boucle /me - #480
Merged
Merged
Conversation
useMe gave a new refetch at each render, and ProfileScreen passes it to useFocusEffect: each render ran the effect again, hence a request, hence a render. Signed in, it asked /me about ten times a second; signed out, refetch ignored `enabled` and sent /me without token, refused in a loop. refetch is now stable and does nothing without account. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After DELETE /me, or on a 401 « Account deleted », session.endSession makes the app anonymous at once, its token no longer sent, then asks Firebase to sign out: a failure no longer leaves the app signed in, and goes to Sentry. Called again while anonymous, it does nothing, so the sign-out runs once however many queries fail. requireAccount treats a deleted account as no account and opens the sheet, and a sign-in that goes through sets the session user without waiting for Firebase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closed by the veil or a swipe, the sheet told the flow, whose closed state dismissed the sheet a second time: gorhom then no longer presented it, and every action that asks for an account sent auth_prompted with no sheet, on « Programmer » as on « Créer un compte ou se connecter ». Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Plan d'implémentation et décisions techniques
|
Open
This was referenced Oct 15, 2026
Closed
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.
Résultat et problème
Après « Supprimer mon compte », l'app envoyait
GET /v1/meen boucle (environ 8 par seconde, refusés en 401), et la feuille de connexion ne s'ouvrait plus. Trois causes, toutes reproduites en local sur le code demain, et un commit pour chacune :GET /me(7c05942).useMerendait unrefetchneuf à chaque rendu, et l'onglet Profil le passe àuseFocusEffect. Chaque rendu relançait donc l'effet, qui lançait une requête, qui provoquait un rendu. Connecté, cela faisait déjà 42GET /meen 5 s. Une fois anonyme,refetchignoraitenabled: uid !== nullet partait sans jeton, d'où les 401 sans fin. Désormaisrefetchest stable et ne fait rien sans compte.9a48a2b). La session ne suivait queonUserChangedde Firebase. La nouvelle fonctionsession.endSession()passe l'app anonyme tout de suite : le jeton n'est plus envoyé et la querymeest désactivée. Elle demande ensuite la déconnexion Firebase, une seule fois, même si plusieurs requêtes échouent en même temps. Un échec de Firebase part dans Sentry sans laisser l'app connectée. Elle est appelée aprèsDELETE /meet sur un 401 « Account deleted » (signOutOnDeletedAccount).requireAccounttraite un compte supprimé comme une absence de compte, ouvre la feuille et ne lance plus l'action. Une connexion réussie pose l'utilisateur dans la session sans attendre Firebase, qui peut ne rien signaler pour le compte qu'il garde (Aligner la suppression de compte sur D-025 et permettre la réinscription #128).74b3ff3). Quand on la fermait par le voile ou par un glissement, gorhom prévenait le flux, puis l'état fermé du flux la refermait une seconde fois. gorhom ne la présentait plus ensuite :auth_promptedpartait, sans feuille. C'est le symptôme vu sur le Note 9.Limite restante : le scénario « déconnexion Firebase muette » n'a été recetté qu'avec un faux Firebase. Sur un vrai build, le cas nominal reste à constater (voir plus bas).
Fixes #475
Écrans : E-09, E-10
Dépendance : aucune
Comment vérifier
Tests unitaires (un test par cause, chacun échoue sur le code de
main) :src/profile/meRequests.test.tsx: l'onglet Profil sur une API simulée qui compte ses requêtes (src/test/fakeApi.ts). On vérifie au plus deuxGET /meà l'ouverture et au plus un après la suppression, y compris quand la déconnexion Firebase échoue. Sur un 401 « Account deleted » avec un Firebase muet, il y a exactement unGET /meet une seule déconnexion, puis la reconnexion donne un compte neuf. Surmain: « The focus effect of the Profil tab runs in a loop ».session.test.ts:endSessionpasse anonyme tout de suite et une seule fois, même quand Firebase échoue, et une nouvelle connexion est reprise.flow.test.ts: un compte supprimé ouvre la feuille. Hors ligne, l'action part sans couper la session.SignInSheet.test.tsx: la feuille se rouvre après une fermeture sur le voile, sans seconddismiss.Recette locale, le 2026-10-15, par l'agent CODE. Dev builds sur l'émulateur Android
Medium_Phone(API 34) et le simulateur iPhone 17 Pro (iOS 26.4). API locale sur une base de recette dédiée, migrée jusqu'à 0014 puis semée. Faux Firebase par patch local jamais commité, dans deux variantes : déconnexion muette (Firebase garde l'utilisateur, le pire cas) et déconnexion réelle (le cas de staging). Comptages tirés du journal de l'API :mainGET /me200DELETE /me, Firebase muet, 8 sGET /me401, app restée connectéeGET /me, app anonymeDELETE /me, Firebase réel, 8 sGET /me401, profil anonyme affichéGET /medeleted_at), étape « Bienvenue »Android : les deux variantes. iOS : la variante muette. Texte système à 150 % (Android) : la feuille de connexion reste lisible et complète.
Avant (
main) : boucle sur un profil anonyme, puis feuille absente après le voile.Après :
pnpm lint && pnpm typecheck && pnpm testverts en local, et lint, typecheck et tests mobile verts commit par commit (git rebase --exec)Formatage mécanique : non
Risque
Niveau : 2 — on change la gestion de la session et l'envoi du jeton côté mobile. L'effet est réversible et circonscrit : aucune migration, aucune donnée persistée, rien côté API.
Taille :
7c05942+183/−1, dont 172 de test (meRequests.test.tsx,fakeApi.ts) ·9a48a2b+170/−21, dont 112 de test ·74b3ff3+25/−0. Chaque commit est sous 400 lignes.Revue OWASP Top 10 (2025), ciblée :
getTokenrendnulldès que la session est anonyme, sans attendre Firebase (testsession.test.ts). La déconnexion Firebase est toujours demandée. Si elle échoue, l'app reste anonyme et l'échec part dans Sentry. Une session neuve n'est ouverte que par une connexion qui a abouti (signedIn(result.user)). Côté API, le contrôle est inchangé : l'ancien jeton reste refusé (« Account deleted », D-027).requireAccountlance l'action sans couper la session (testflow.test.ts).Retour arrière
Un revert de la PR suffit : aucune migration, aucune donnée créée. Après le revert, la boucle de
GET /merevient.🤖 Generated with Claude Code