feat: add auto-lock feature with customizable timeout settings and update UI for security preferences

This commit is contained in:
shuaiplus
2026-04-24 15:27:46 +08:00
parent d40b0514fd
commit acd59a7387
6 changed files with 233 additions and 49 deletions
+19 -3
View File
@@ -122,9 +122,11 @@ export function loadProfileSnapshot(email?: string | null): Profile | null {
const raw = localStorage.getItem(PROFILE_SNAPSHOT_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw) as Profile;
if (!parsed?.email || !parsed?.key) return null;
if (!parsed?.email) return null;
if (email && parsed.email !== email) return null;
return parsed;
const snapshot = stripProfileSecrets(parsed);
localStorage.setItem(PROFILE_SNAPSHOT_KEY, JSON.stringify(snapshot));
return snapshot;
} catch {
return null;
}
@@ -132,13 +134,27 @@ export function loadProfileSnapshot(email?: string | null): Profile | null {
export function saveProfileSnapshot(profile: Profile | null): void {
if (!profile) return;
localStorage.setItem(PROFILE_SNAPSHOT_KEY, JSON.stringify(profile));
localStorage.setItem(PROFILE_SNAPSHOT_KEY, JSON.stringify(stripProfileSecrets(profile)));
}
export function clearProfileSnapshot(): void {
localStorage.removeItem(PROFILE_SNAPSHOT_KEY);
}
export function stripProfileSecrets(profile: Profile | null): Profile | null {
if (!profile) return null;
return {
id: String(profile.id || ''),
email: String(profile.email || ''),
name: String(profile.name || ''),
role: profile.role === 'admin' ? 'admin' : 'user',
masterPasswordHint: profile.masterPasswordHint ?? null,
publicKey: profile.publicKey ?? null,
key: '',
privateKey: null,
};
}
export function getCurrentDeviceIdentifier(): string {
return (localStorage.getItem(DEVICE_IDENTIFIER_KEY) || '').trim();
}
+28 -8
View File
@@ -372,16 +372,36 @@ export async function performRegistration(args: {
export async function performUnlock(
session: SessionState,
profile: Profile,
profile: Profile | null,
password: string,
fallbackIterations: number
): Promise<SessionState> {
const derived = await deriveLoginHashLocally(profile.email || session.email, password, fallbackIterations);
const keys = await unlockVaultKey(profile.key, derived.masterKey);
const refreshedSession = await maybeRefreshSession(session);
if (!refreshedSession) {
throw new Error('Session expired');
): Promise<PasswordLoginResult> {
const normalizedEmail = (profile?.email || session.email).trim().toLowerCase();
const derived = await deriveLoginHashLocally(normalizedEmail, password, fallbackIterations);
const token = await loginWithPassword(normalizedEmail, derived.hash, { useRememberToken: true });
if ('access_token' in token && token.access_token) {
return {
kind: 'success',
login: await completeLogin(token, normalizedEmail, derived.masterKey),
};
}
return { ...refreshedSession, ...keys };
const tokenError = token as { TwoFactorProviders?: unknown; error_description?: string; error?: string };
if (tokenError.TwoFactorProviders) {
return {
kind: 'totp',
pendingTotp: {
email: normalizedEmail,
passwordHash: derived.hash,
masterKey: derived.masterKey,
},
};
}
return {
kind: 'error',
message: tokenError.error_description || tokenError.error || 'Unlock failed',
};
}
+18
View File
@@ -1485,6 +1485,24 @@ zhCNOverrides.txt_lock = '锁定';
zhCNOverrides.txt_menu = '菜单';
zhCNOverrides.txt_settings = '设置';
zhCNOverrides.txt_back = '返回';
messages.en.txt_auto_lock = 'Auto-lock';
messages.en.txt_auto_lock_description = 'Locks after inactivity. Closing and reopening the page always starts locked.';
messages.en.txt_auto_lock_updated = 'Auto-lock updated';
messages.en.txt_security_preferences = 'Security Preferences';
messages.en.txt_lock_after_1_minute = 'After 1 minute';
messages.en.txt_lock_after_5_minutes = 'After 5 minutes';
messages.en.txt_lock_after_15_minutes = 'After 15 minutes';
messages.en.txt_lock_after_30_minutes = 'After 30 minutes';
messages.en.txt_lock_after_never = 'Never for inactivity';
zhCNOverrides.txt_auto_lock = '自动锁定';
zhCNOverrides.txt_auto_lock_description = '页面闲置后锁定;关闭页面或浏览器后再次打开始终进入锁定页。';
zhCNOverrides.txt_auto_lock_updated = '自动锁定时间已更新';
zhCNOverrides.txt_security_preferences = '安全偏好';
zhCNOverrides.txt_lock_after_1_minute = '闲置 1 分钟后';
zhCNOverrides.txt_lock_after_5_minutes = '闲置 5 分钟后';
zhCNOverrides.txt_lock_after_15_minutes = '闲置 15 分钟后';
zhCNOverrides.txt_lock_after_30_minutes = '闲置 30 分钟后';
zhCNOverrides.txt_lock_after_never = '不因闲置锁定';
zhCNOverrides.txt_attachments = '附件';
zhCNOverrides.txt_upload_attachments = '上传附件';
zhCNOverrides.txt_new_attachments = '待上传附件';