From 4da97774f21e2198b9fc15ed40794208fe435661 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 27 Aug 2026 17:33:09 -0700 Subject: [PATCH] Fix password reminder after settings unlock --- CHANGELOG.md | 2 + .../reducers/passwordReminderReducer.test.ts | 39 +++++++++++++++++++ src/components/scenes/SettingsScene.tsx | 5 +-- src/reducers/PasswordReminderReducer.ts | 12 ++---- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68120eca1f7..02e4fe84c29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased (develop) +- fixed: Password reminder no longer stops reappearing after unlocking Account Settings + ## 4.51.0 (staging) - added: Push info-server attestation tokens into edge-core-js via `setAttestationToken` so the login server can skip CAPTCHA for attested devices, and allow `LOGIN_SERVER` / `INFO_SERVER` env overrides for local E2E stacks. diff --git a/src/__tests__/reducers/passwordReminderReducer.test.ts b/src/__tests__/reducers/passwordReminderReducer.test.ts index e5e1a65109e..d45cbdbe290 100644 --- a/src/__tests__/reducers/passwordReminderReducer.test.ts +++ b/src/__tests__/reducers/passwordReminderReducer.test.ts @@ -4,9 +4,13 @@ import { initialState, MAX_NON_PASSWORD_DAYS_LIMIT, MAX_NON_PASSWORD_LOGINS_LIMIT, + NON_PASSWORD_DAYS_GROWTH_RATE, + NON_PASSWORD_LOGINS_GROWTH_RATE, + passwordReminder, type PasswordReminderReducerAction, untranslatedReducer as uut } from '../../reducers/PasswordReminderReducer' +import type { Action } from '../../types/reduxTypes' import { daysBetween, MILLISECONDS_PER_DAY } from '../../util/utils' describe('PasswordReminder', () => { @@ -263,4 +267,39 @@ describe('PasswordReminder', () => { }) }) }) + describe('Account Settings unlock', () => { + // Unlocking Account Settings runs the password through `validatePassword`, + // which dispatches `PASSWORD_USED`, and then dispatches + // `UI/SETTINGS/SET_SETTINGS_LOCK` to record the unlocked state. Only the + // first of those may count as a password use: counting both squares the + // reminder thresholds and the reminder stops reappearing. + test('Counts one password use per unlock', () => { + const unlockActions: Action[] = [ + { type: 'PASSWORD_USED' }, + { type: 'UI/SETTINGS/SET_SETTINGS_LOCK', data: false } + ] + + const actual = unlockActions.reduce( + (state, action) => passwordReminder(state, action), + initialState + ) + + expect(actual.passwordUseCount).toEqual(1) + expect(actual.nonPasswordLoginsLimit).toEqual( + NON_PASSWORD_LOGINS_GROWTH_RATE + ) + expect(actual.nonPasswordDaysLimit).toEqual(NON_PASSWORD_DAYS_GROWTH_RATE) + }) + + test('Lock state changes alone are not password uses', () => { + const action: Action = { + type: 'UI/SETTINGS/SET_SETTINGS_LOCK', + data: false + } + + const actual = passwordReminder(initialState, action) + + expect(actual).toEqual(initialState) + }) + }) }) diff --git a/src/components/scenes/SettingsScene.tsx b/src/components/scenes/SettingsScene.tsx index 7606ac6da46..1406fb76b66 100644 --- a/src/components/scenes/SettingsScene.tsx +++ b/src/components/scenes/SettingsScene.tsx @@ -204,11 +204,8 @@ export const SettingsScene: React.FC = props => { } ) if (password == null) return true + // `showUnlockSettingsModal` already unlocked the settings. setValidatedPassword(password) - dispatch({ - type: 'UI/SETTINGS/SET_SETTINGS_LOCK', - data: false - }) } return false } diff --git a/src/reducers/PasswordReminderReducer.ts b/src/reducers/PasswordReminderReducer.ts index 88b592269f1..ad5df6ffb42 100644 --- a/src/reducers/PasswordReminderReducer.ts +++ b/src/reducers/PasswordReminderReducer.ts @@ -237,6 +237,10 @@ function translateAction(action: Action): PasswordReminderReducerAction { } } + // `validatePassword` dispatches this for every successful password entry, + // including the Account Settings unlock. It is the only signal counted here: + // each password entry must raise `passwordUseCount` exactly once, because the + // reminder thresholds grow as `GROWTH_RATE ** passwordUseCount`. if (action.type === 'PASSWORD_USED') { return { type: 'PASSWORD_USED', @@ -245,14 +249,6 @@ function translateAction(action: Action): PasswordReminderReducerAction { } } } - if (action.type === 'UI/SETTINGS/SET_SETTINGS_LOCK' && !action.data) { - return { - type: 'PASSWORD_USED', - data: { - lastPasswordUseDate: Date.now() - } - } - } if (action.type === 'PASSWORD_REMINDER_MODAL/CHECK_PASSWORD_SUCCESS') { return { type: 'PASSWORD_USED',