mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-21 05:10:41 +00:00
feat: add auto-lock feature with customizable timeout settings and update UI for security preferences
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = '待上传附件';
|
||||
|
||||
Reference in New Issue
Block a user