Files
webmail/lib/app-lock/policy.ts
T
Linus Rath e64c6c7bc3 fix: keep data on a mistyped offline policy and use the shortest lock time for an unreadable one
Forced OfflineEnabled values that make no sense read as forced off,
which wipes kept mail. They now stop new mail being kept and delete
nothing. An unreadable AppLockTimeoutMinutes now means the shortest
time, so the existing out-of-range test changes from no value to 1:
no value let the user's own choice win.

- recommended AutoUpdate, AppLockRequired, TelemetryAllowed and
  CrashReportsAllowed are gone: nothing read them, or one acted as forced
- auth lists and certificate rules can't name a whole top-level domain
- ALL_WIRED is gone, as every key has a consumer
2026-11-04 03:08:30 +01:00

71 lines
2.3 KiB
TypeScript

/**
* The desktop app's OS policies for the lock, read through the bridge. In a browser
* there's no bridge and nothing is required. The bridge's `policy` member comes from
* another package, so it is feature detected, as lib/offline/scope.ts does.
*/
import { getDesktopBridge } from '@/lib/desktop/bridge';
import { POLICY_SCHEMA } from '@/lib/desktop/policy-schema';
import { DEFAULT_TIMEOUT_MINUTES } from './state';
const timeoutDefinition = POLICY_SCHEMA.AppLockTimeoutMinutes;
const SHORTEST_MINUTES = timeoutDefinition.type === 'integer' ? timeoutDefinition.min : 1;
interface PolicyEntry<T> {
value: T;
locked: boolean;
source: string;
}
interface LockPolicies {
AppLockRequired?: PolicyEntry<boolean>;
AppLockTimeoutMinutes?: PolicyEntry<number | null>;
}
interface PolicyBridge {
get?: () => LockPolicies | null | undefined;
onChange?: (listener: (snapshot: LockPolicies) => void) => () => void;
}
function policyBridge(): PolicyBridge | undefined {
return (getDesktopBridge() as { policy?: PolicyBridge } | null)?.policy;
}
function read(): LockPolicies {
try {
return policyBridge()?.get?.() ?? {};
} catch {
return {};
}
}
/** Only a forced value requires it: a recommended one can't choose a PIN for anyone. */
export function appLockRequired(): boolean {
const entry = read().AppLockRequired;
return entry?.locked === true && entry.value === true;
}
export interface EffectiveTimeout {
minutes: number;
/** Set by the organisation: the user's choice doesn't apply. */
locked: boolean;
}
/** Forced, then the user's choice, then recommended, then the default. */
export function effectiveTimeout(choice: number | undefined): EffectiveTimeout {
const entry = read().AppLockTimeoutMinutes;
const value = typeof entry?.value === 'number' && entry.value > 0 ? entry.value : null;
// Forced to something nobody can read, by a shell that hands that on as nothing: the
// tightest there is, since the user's choice was never meant to apply.
if (entry?.locked) return { minutes: value ?? SHORTEST_MINUTES, locked: true };
return { minutes: choice ?? value ?? DEFAULT_TIMEOUT_MINUTES, locked: false };
}
export function onLockPolicyChange(listener: () => void): () => void {
try {
return policyBridge()?.onChange?.(() => listener()) ?? (() => {});
} catch {
return () => {};
}
}