Repository navigation
[HOLD][Bulk Sign Out] Sign out on all devices from Device management - #106679
roryabraham wants to merge 6 commits into
Conversation
Require online confirmation and confirmed server success before clearing local credentials. Keep failures retryable in the same confirmation.
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index 2695ec73..4733321e 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -2676,7 +2676,7 @@ const translations: TranslationDeepObject<typeof en> = {
revoke: 'Widerrufen',
unknownDevice: 'Unbekanntes Gerät',
signOutAllDevices: 'Auf allen Geräten abmelden',
- signOutAllDevicesConfirmation: 'Sie werden auf allen Geräten abgemeldet, einschließlich diesem. Sie müssen sich erneut anmelden, um Expensify zu nutzen.',
+ signOutAllDevicesConfirmation: 'Sie werden auf allen Geräten, einschließlich diesem, abgemeldet. Sie müssen sich erneut anmelden, um Expensify zu nutzen.',
signOutAllDevicesError: 'Wir konnten Sie nicht auf allen Geräten abmelden. Bitte versuchen Sie es erneut.',
signOutAllDevicesSuccess: 'Sie wurden auf allen Geräten abgemeldet.',
},
diff --git a/src/languages/el.ts b/src/languages/el.ts
index 4d820075..22874c9c 100644
--- a/src/languages/el.ts
+++ b/src/languages/el.ts
@@ -2732,9 +2732,9 @@ const translations: TranslationDeepObject<typeof en> = {
revoke: 'Ανακαλέστε',
unknownDevice: 'Άγνωστη συσκευή',
signOutAllDevices: 'Αποσύνδεση από όλες τις συσκευές',
- signOutAllDevicesConfirmation: 'Θα αποσυνδεθείτε από όλες τις συσκευές, συμπεριλαμβανομένης αυτής. Θα χρειαστεί να συνδεθείτε ξανά για να χρησιμοποιήσετε το Expensify.',
- signOutAllDevicesError: 'Δεν μπορέσαμε να σας αποσυνδέσουμε από όλες τις συσκευές. Δοκιμάστε ξανά.',
- signOutAllDevicesSuccess: 'Αποσυνδεθήκατε από όλες τις συσκευές.',
+ signOutAllDevicesConfirmation: 'Θα αποσυνδεθείτε από όλες τις συσκευές, συμπεριλαμβανομένης και αυτής. Θα χρειαστεί να συνδεθείτε ξανά για να χρησιμοποιήσετε το Expensify.',
+ signOutAllDevicesError: 'Δεν μπορέσαμε να σας αποσυνδέσουμε από όλες τις συσκευές. Παρακαλούμε δοκιμάστε ξανά.',
+ signOutAllDevicesSuccess: 'Έχετε αποσυνδεθεί από όλες τις συσκευές.',
},
twoFactorAuth: {
headerTitle: 'Έλεγχος ταυτότητας δύο παραγόντων',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 579c2ff4..352a6c8c 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2656,9 +2656,9 @@ const translations: TranslationDeepObject<typeof en> = {
revoke: 'Revocar',
unknownDevice: 'Dispositivo Desconocido',
signOutAllDevices: 'Cerrar sesión en todos los dispositivos',
- signOutAllDevicesConfirmation: 'Se cerrará tu sesión en todos los dispositivos, incluido este. Tendrás que volver a iniciar sesión para usar Expensify.',
- signOutAllDevicesError: 'No pudimos cerrar tu sesión en todos los dispositivos. Inténtalo de nuevo.',
- signOutAllDevicesSuccess: 'Se ha cerrado tu sesión en todos los dispositivos.',
+ signOutAllDevicesConfirmation: 'Se cerrará tu sesión en todos los dispositivos, incluido este. Tendrás que iniciar sesión de nuevo para usar Expensify.',
+ signOutAllDevicesError: 'No hemos podido cerrar tu sesión en todos los dispositivos. Por favor, inténtalo de nuevo.',
+ signOutAllDevicesSuccess: 'Has cerrado sesión en todos los dispositivos.',
},
twoFactorAuth: {
headerTitle: 'Autenticación de dos factores',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 0d988be2..c4afef65 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -2666,10 +2666,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Gestisci tutti i dispositivi su cui hai effettuato l\'accesso con il tuo account Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Scopri di più</a>.',
revoke: 'Revoca',
unknownDevice: 'Dispositivo Sconosciuto',
- signOutAllDevices: 'Esci da tutti i dispositivi',
- signOutAllDevicesConfirmation: 'Uscirai da tutti i dispositivi, incluso questo. Dovrai accedere di nuovo per usare Expensify.',
- signOutAllDevicesError: 'Non è stato possibile disconnetterti da tutti i dispositivi. Riprova.',
- signOutAllDevicesSuccess: 'Hai effettuato la disconnessione da tutti i dispositivi.',
+ signOutAllDevices: 'Disconnetti da tutti i dispositivi',
+ signOutAllDevicesConfirmation: 'Verrai disconnesso da tutti i dispositivi, incluso questo. Dovrai effettuare di nuovo l’accesso per usare Expensify.',
+ signOutAllDevicesError: 'Non siamo riusciti a disconnetterti da tutti i dispositivi. Riprova.',
+ signOutAllDevicesSuccess: 'Sei stato disconnesso da tutti i dispositivi.',
},
twoFactorAuth: {
headerTitle: 'Autenticazione a due fattori',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index c86cbdbe..e45a45a0 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -2648,10 +2648,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Expensify アカウントでログインしているすべてのデバイスを管理できます。<a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">詳しくはこちら</a>.',
revoke: '取り消す',
unknownDevice: '不明なデバイス',
- signOutAllDevices: 'すべてのデバイスからログアウト',
- signOutAllDevicesConfirmation: 'このデバイスを含むすべてのデバイスからログアウトします。Expensifyを使用するには、再度ログインする必要があります。',
- signOutAllDevicesError: 'すべてのデバイスからログアウトできませんでした。もう一度お試しください。',
- signOutAllDevicesSuccess: 'すべてのデバイスからログアウトしました。',
+ signOutAllDevices: 'すべての端末からサインアウト',
+ signOutAllDevicesConfirmation: 'この端末を含むすべてのデバイスからサインアウトされます。Expensify を利用するには、再度サインインする必要があります。',
+ signOutAllDevicesError: 'すべての端末からサインアウトできませんでした。もう一度お試しください。',
+ signOutAllDevicesSuccess: 'すべてのデバイスからサインアウトされました。',
},
twoFactorAuth: {
headerTitle: '2要素認証',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 2f854a92..a30b50fa 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -2664,10 +2664,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Beheer alle apparaten waarop je bent ingelogd met je Expensify-account. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Meer informatie</a>.',
revoke: 'Intrekken',
unknownDevice: 'Onbekend Apparaat',
- signOutAllDevices: 'Uitloggen op alle apparaten',
- signOutAllDevicesConfirmation: 'Je wordt uitgelogd op alle apparaten, inclusief dit apparaat. Je moet opnieuw inloggen om Expensify te gebruiken.',
- signOutAllDevicesError: 'We konden je niet op alle apparaten uitloggen. Probeer het opnieuw.',
- signOutAllDevicesSuccess: 'Je bent uitgelogd op alle apparaten.',
+ signOutAllDevices: 'Afmelden op alle apparaten',
+ signOutAllDevicesConfirmation: 'Je wordt op alle apparaten afgemeld, ook op dit apparaat. Je moet je opnieuw aanmelden om Expensify te gebruiken.',
+ signOutAllDevicesError: 'We konden je niet op alle apparaten afmelden. Probeer het opnieuw.',
+ signOutAllDevicesSuccess: 'Je bent op alle apparaten uitgelogd.',
},
twoFactorAuth: {
headerTitle: 'Tweeledige verificatie',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 87c829a9..d14b6880 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -2711,10 +2711,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Zarządzaj wszystkimi urządzeniami, na które zalogowałeś(-aś) się za pomocą konta Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Dowiedz się więcej</a>.',
revoke: 'Cofnąć',
unknownDevice: 'Nieznane Urządzenie',
- signOutAllDevices: 'Wyloguj się ze wszystkich urządzeń',
- signOutAllDevicesConfirmation: 'Wylogujesz się ze wszystkich urządzeń, w tym z tego. Aby korzystać z Expensify, trzeba będzie zalogować się ponownie.',
- signOutAllDevicesError: 'Nie udało się wylogować Cię ze wszystkich urządzeń. Spróbuj ponownie.',
- signOutAllDevicesSuccess: 'Wylogowano Cię ze wszystkich urządzeń.',
+ signOutAllDevices: 'Wyloguj się na wszystkich urządzeniach',
+ signOutAllDevicesConfirmation: 'Zostaniesz wylogowany ze wszystkich urządzeń, w tym z tego. Żeby dalej korzystać z Expensify, musisz zalogować się ponownie.',
+ signOutAllDevicesError: 'Nie udało się wylogować cię ze wszystkich urządzeń. Spróbuj ponownie.',
+ signOutAllDevicesSuccess: 'Zostałeś wylogowany ze wszystkich urządzeń.',
},
twoFactorAuth: {
headerTitle: 'Uwierzytelnianie dwuskładnikowe',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 19e24668..c9d36873 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -2669,10 +2669,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Gerencie todos os dispositivos nos quais você fez login com sua conta Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Saiba mais</a>.',
revoke: 'Revogar',
unknownDevice: 'Dispositivo Desconhecido',
- signOutAllDevices: 'Sair em todos os dispositivos',
- signOutAllDevicesConfirmation: 'Você sairá da sua conta em todos os dispositivos, incluindo este. Será necessário entrar novamente para usar o Expensify.',
- signOutAllDevicesError: 'Não foi possível sair da sua conta em todos os dispositivos. Tente novamente.',
- signOutAllDevicesSuccess: 'Você saiu da sua conta em todos os dispositivos.',
+ signOutAllDevices: 'Sair de todos os dispositivos',
+ signOutAllDevicesConfirmation: 'Você será desconectado de todos os dispositivos, incluindo este. Será preciso entrar novamente para usar o Expensify.',
+ signOutAllDevicesError: 'Não foi possível desconectá-lo de todos os dispositivos. Tente novamente.',
+ signOutAllDevicesSuccess: 'Você foi desconectado de todos os dispositivos.',
},
twoFactorAuth: {
headerTitle: 'Autenticação de dois fatores',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 775eef7e..a1364ee6 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -2569,10 +2569,10 @@ const translations: TranslationDeepObject<typeof en> = {
description: '管理所有已使用你的 Expensify 账户登录的设备。<a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">了解详情</a>.',
revoke: '撤销',
unknownDevice: '未知设备',
- signOutAllDevices: '退出所有设备上的登录',
- signOutAllDevicesConfirmation: '你将退出所有设备上的登录,包括此设备。你需要重新登录才能使用 Expensify。',
- signOutAllDevicesError: '我们无法退出你在所有设备上的登录。请重试。',
- signOutAllDevicesSuccess: '你已退出所有设备上的登录。',
+ signOutAllDevices: '在所有设备上退出',
+ signOutAllDevicesConfirmation: '您将在所有设备上退出登录(包括此设备)。您需要重新登录才能使用 Expensify。',
+ signOutAllDevicesError: '我们无法让你在所有设备上退出登录。请重试。',
+ signOutAllDevicesSuccess: '你已在所有设备上登出。',
},
twoFactorAuth: {
headerTitle: '双重身份验证',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
Honor explicit bottom placement on native with a full-screen containing block and touch pass-through. Reset cleanup to the root registered by both public and authenticated navigation.
|
@codex review |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
🚧 roryabraham has triggered a test Expensify/App build. You can view the workflow run here. |
|
This will be on HOLD for the backend pieces to go out, but I'm opening it up for review. |
|
@MonilBhavsar Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
Explanation of Change
Lets people sign out on all devices from Account > Security > Device management. A confirmation explains that the current device is included, supports retry after an error, and returns the user to sign-in with a success message when the request succeeds. Deploy after the API endpoint and its published Auth dependency.
Fixed Issues
$ https://github.com/Expensify/Expensify/issues/702815
Tests
Open the app and sign in to a test account: Account A, Session 1.
Sign in to the same account in a second, separate browser, incognito tab, or device: Account A, Session 2.
Sign in to a different account in a third profile or device: Account B, Session 1.
In Account A, Session 1, open
Settings>Security>Device management.Verify that "Sign out on all devices" appears at the bottom and existing controls for individual devices are still available.
Select
Sign out on all devicesand verify that the confirmation includes this device and offersSign out on all devicesandCancel.Select
Canceland verify that the confirmation closes, the account stays signed in, and no success message appears.Reload Account A, Session 2 and verify that it is still signed in.
For the web error test, use Google Chrome for Account A, Session 1. Keep Chrome DevTools > Network set to
Online, with request blocking and response overrides disabled. While on Device management, open Chrome DevTools > Console and paste the following snippet. It replacesfetchfor this page only and holds matching calls before any HTTP request or session revocation occurs. All other requests continue normally. Do not reload this page until the interception is restored.In Account A, Session 1, open the confirmation again and confirm. Verify that the button shows a pending state. Attempt repeated confirmation clicks, then run
({count: __signOutTest.count, pending: __signOutTest.pending})in the Console and verify that it returns{count: 1, pending: 1}. These intercepted calls do not appear in the Network panel because no HTTP request is sent; use these counters to check duplicate submissions.In the same Console, run
__signOutTest.fail()to release the held call with HTTP 200 and JSON{"jsonCode":400}, without contacting the server. In Account A, Session 1, verify that the confirmation stays open withWe couldn’t sign you out on all devices. Please try again., allows retry orCancel, and shows no success message. Verify that({count: __signOutTest.count, pending: __signOutTest.pending})now returns{count: 1, pending: 0}.Reload Account A, Session 2 and verify that it remains signed in. Back in Account A, Session 1's Console, run
__signOutTest.restore()before retrying. This restores normal requests; the next confirmation will really revoke the account's sessions. If stopping this test early, runwindow.__signOutTest?.restore()to release any held calls as failures and restore normal requests without forwarding those held calls.In Account A, Session 1, retry the confirmation and verify that it returns to sign-in, displays the success message at the bottom of the page, and shows no JavaScript error popup.
Reload or relaunch Account A, Session 2 and verify that it requires fresh sign-in.
Reload Account B, Session 1 and verify that it remains signed in and can open Device management.
Sign in again to Account A, Session 1, reload or relaunch it, and verify that the new session remains usable.
Repeat the cancellation and successful sign-out steps with iOS and Android as the initiating device.
Verify that the flow introduces no JavaScript console errors.
Sign in again to Account A, Session 1, open My settings, select the ordinary Sign out action, and verify that sign-in opens without a navigation reset error.
Offline tests
Sign out on all devicesand verify that the offline dialog appears with Got it and no success message.QA Steps
Same as tests and offline tests.
Additional provider coverage for QA; Google and Apple sign-in have not been verified locally:
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Verified cancellation, actual device disconnection, successful sign-out with the visible success message, a second session rejected with
407, an unrelated session still authorized with200, and fresh sign-in surviving relaunch. This used three sessions and authenticated backend reads; the held-request failure scenario was verified on web.Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Playwright verification against the dev backend covered cancellation, offline handling, a held request, failure and retry, and successful revocation. Fresh server requests returned
407for the second session and200for the unrelated account; fresh sign-in also returned200.