fix: stay signed in after changing the password in settings

After a password change the session kept authenticating with the old
password: the JMAP client's Basic header, the server-side context the
Stalwart management calls use, and the remembered login. The next
request was refused and the user was sent back to the login page.

A successful password change now switches all three to the new
password for Basic-auth sessions. OAuth sessions are unaffected by this
change.
This commit is contained in:
Linus Rath
2026-09-22 21:20:41 +02:00
parent b217dba461
commit fa33351f8d
3 changed files with 28 additions and 0 deletions
@@ -9,9 +9,12 @@ vi.mock('@/lib/stalwart/jmap-passthrough', () => ({
},
}));
const updateBasicPassword = vi.fn(async (_password: string) => {});
vi.mock('@/stores/auth-store', () => ({
useAuthStore: {
getState: () => ({
updateBasicPassword,
client: {
getAccountId: () => 'acc-primary',
hasAccountCapability: (cap: string) => cap === 'urn:stalwart:jmap',
@@ -270,6 +273,7 @@ describe('account-security-store', () => {
]);
await useAccountSecurityStore.getState().changePassword('old', 'new', ' 123456 ');
expect(updateBasicPassword).toHaveBeenCalledWith('new');
const args = mockedJmap.mock.calls[0][0][0][1];
expect(args.update.singleton).toEqual({ currentSecret: 'old', secret: 'new', 'otpAuth/otpCode': '123456' });
+7
View File
@@ -579,6 +579,13 @@ export const useAccountSecurityStore = create<AccountSecurityState>()((set, get)
],
]);
requireAccountPasswordUpdate(responses, 'Failed to change password');
// The session still authenticates with the old password; switch it over
// so the next request does not bounce the user to the login page.
try {
await useAuthStore.getState().updateBasicPassword(newPassword);
} catch (error) {
debug.warn('auth', 'Password changed, but the session could not switch to it:', error);
}
set({ isSaving: false });
} catch (error) {
set({
+17
View File
@@ -80,6 +80,12 @@ interface AuthState {
checkAuth: () => Promise<void>;
clearError: () => void;
syncIdentities: () => void;
/**
* Keep a Basic-auth session working after the user changed the password:
* the client, the server-side management context and the remembered
* session still hold the old one, which the server now refuses.
*/
updateBasicPassword: (newPassword: string) => Promise<void>;
refreshIdentities: () => Promise<void>;
getClientForAccount: (accountId: string) => JMAPClient | undefined;
getAllConnectedClients: () => Map<string, JMAPClient>;
@@ -2378,6 +2384,17 @@ export const useAuthStore = create<AuthState>()(
clearError: () => set({ error: null }),
updateBasicPassword: async (newPassword) => {
const { client, activeAccountId } = get();
const account = activeAccountId ? useAccountStore.getState().getAccountById(activeAccountId) : undefined;
if (!client || !account || account.authMode !== 'basic') return;
client.updateBasicAuth(newPassword);
await syncStalwartAuthContext(account.serverUrl, account.username, client.getAuthHeader(), account.cookieSlot);
if (account.rememberMe || IS_LITE) {
await persistBasicSession(account.cookieSlot, account.serverUrl, account.username, newPassword);
}
},
syncIdentities: () => {
const identityState = useIdentityStore.getState();
const identities = identityState.identities;