Files
webmail/lib/devices/types.ts
T
Linus Rath 8523289a4b feat: let an admin wipe a lost device, and clear an account the server refuses for good
Until now a lost laptop kept its offline mail for as long as anyone could
open it, and an account whose login the server had ended kept its copy too.

- `lib/devices`, `app/api/devices`: browsers with offline on and desktop
  apps register, in a durable collection with no expiry. A check-in goes
  by a per-device secret, so a revoked device still hears its wipe.
- `app/api/admin/devices` and the org devices page: wipe one device or all
  of a user's, with audit entries for each request and each confirmation.
- `lib/offline/device-wipe.ts`: a start finishes any wipe cut short, then
  waits up to a second for the instance before showing the local copy. A
  wipe takes the store, keys, plugin databases and handoffs, signs out,
  and reports back, again at the next contact if the report failed.
- `lib/jmap/client.ts`, `stores/auth-store.ts`: only `invalid_grant`, the
  session limit, or a 401 lasting past a re-auth count as refused. A 500,
  a proxy page or an outage never do.
- Settings: "Remove everything from this device", with a confirmation.

`lib/shared-store/__tests__/import-data.test.ts` changes its assertion
because it lists every collection the import copies, and `devices` is new.
2026-11-03 13:19:44 +01:00

31 lines
1.1 KiB
TypeScript

/**
* The device registry as both sides see it: what the instance lists for its
* admin and what a device says about its own wipe. No secrets, no store.
*/
/** Who asked for a wipe. `rejected`: the server refused the stored login for good. */
export const WIPE_REASONS = ['admin', 'user', 'rejected'] as const;
export type WipeReason = (typeof WIPE_REASONS)[number];
export interface DeviceView {
/** `<settingsId(user, server)>/<deviceId>`. */
id: string;
/** The first part of `id`: every device of this user on this server. */
userKey: string;
user: string;
server: string;
platform: string;
appVersion: string;
firstSeen: string;
lastSeen: string;
/** Asked for and not confirmed yet. Stays until the device answers or the admin takes it back. */
wipe: { requestedAt: string; by: string } | null;
wipedAt: string | null;
wipeReason: WipeReason | null;
}
export const AUDIT_DEVICE_WIPE_REQUEST = 'device.wipe.request';
export const AUDIT_DEVICE_WIPE_CANCEL = 'device.wipe.cancel';
export const AUDIT_DEVICE_WIPE_CONFIRM = 'device.wipe.confirm';
export const AUDIT_DEVICE_FORGET = 'device.forget';