mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
A refused login used to delete the account's data, and with it everyone's S/MIME and OpenPGP keys, also with offline off. The usual cause is a password changed on another device, which Stalwart 0.16 and 1.0 both answer with `invalid_grant`, so that lost unsent mail for little protection. - A refused login signs out at once and hides the data. A new password brings it back, unsent mail included. Otherwise it goes after 30 days. The sign-in page gives a count and a date. The 24-hour timer is gone. - `organisation.devices.wipeOnRefusedLogin`, off by default, asks for deletion at once, of that account's own data only. - The session limit no longer counts as a refusal on any route. It hides the data and says nothing. - A wipe runs under a Web Lock, tells other windows when it starts and ends, and queues its report before deleting anything. It reports only once the deletion worked and no plugin database is left. - More goes with a wipe: Pro tabs, search history, Files and WebDAV state, templates, pop-out drafts, shown notifications, push state, and Web Push with the last account. - The unsent count is per account, and unknown rather than 0 when the store can't say. A new row is checked against the account that asked, and a sign-out drops its row. Existing tests changed because the rule did: `auth-store-keep-local.test.ts` expected a wipe on `invalid_grant` and at the session limit, and `device-wipe.test.ts` expected shared keys to go on a refused login.
24 lines
1.1 KiB
TypeScript
24 lines
1.1 KiB
TypeScript
/**
|
|
* Plugin secrets of an account the app dropped, in the desktop app, where they sit
|
|
* on this device's own server. The account has no cookie left to ask with, so the
|
|
* route takes who it was, from what was written down at the time. On an instance
|
|
* they aren't the device's to remove, and nothing is written down.
|
|
*/
|
|
|
|
import { apiFetch } from '@/lib/browser-navigation';
|
|
import { IS_DESKTOP_BUILD } from '@/lib/desktop/flag';
|
|
import { dropEvictedOwner, readEvictedOwners } from './state';
|
|
|
|
const BUDGET_MS = 3_000;
|
|
|
|
/** `account` null: everyone's. */
|
|
export async function forgetEvictedSecrets(account: string | null): Promise<void> {
|
|
if (!IS_DESKTOP_BUILD) return;
|
|
const owners = Object.entries(readEvictedOwners()).filter(([id]) => account === null || id === account);
|
|
await Promise.all(owners.map(async ([id, owner]) => {
|
|
const query = new URLSearchParams({ all: 'true', username: owner.username, server: owner.serverUrl });
|
|
const response = await apiFetch(`/api/plugin-secrets?${query}`, { method: 'DELETE', signal: AbortSignal.timeout(BUDGET_MS) }).catch(() => null);
|
|
if (response?.ok) dropEvictedOwner(id);
|
|
}));
|
|
}
|