Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/app/components/NavigationHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DefaultAvatar, getDisplayName, type UserForAvatar } from './DefaultAvat
import { MobileMenu } from './NavigationComponents';
import { useTranslation } from '../stores/i18nStore';
import LanguageSelector from './LanguageSelector';
import { clearAllUserData } from '../utils/security';

interface NavigationHeaderProps {
/** Background variant - determines logo color */
Expand Down Expand Up @@ -103,12 +104,8 @@ export function NavigationHeader({
return () => window.removeEventListener('storage', checkAuth);
}, []);

const handleLogout = () => {
localStorage.removeItem('vdid_user');
localStorage.removeItem('auth_method');
localStorage.removeItem('vspec_subscription');
localStorage.removeItem('vspec_user_profile');
localStorage.removeItem('vspec_notifications');
const handleLogout = async () => {
await clearAllUserData();
setIsLoggedIn(false);
setUser(null);
setShowUserMenu(false);
Expand Down
14 changes: 3 additions & 11 deletions src/app/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect, lazy, Suspense, memo, useMemo, useCallback } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useDocumentStore } from '../stores/documentStore';
import { supabase } from '../../lib/supabase';
import { clearAllUserData } from '../utils/security';
import {
FileText,
FolderOpen,
Expand Down Expand Up @@ -419,18 +419,10 @@ export default function Dashboard() {
).slice(0, 8);
}, [searchQuery, documents]);

// Handle logout with Supabase signOut
// Handle logout with comprehensive cleanup
const handleLogout = useCallback(async () => {
setShowUserMenu(false);
try {
await supabase.auth.signOut();
} catch (error) {
logger.error('Supabase signOut error:', error);
}
localStorage.removeItem('auth_method');
localStorage.removeItem('vdid_user');
localStorage.removeItem('vspec_user');
sessionStorage.clear();
await clearAllUserData();
navigate('/');
}, [navigate]);

Expand Down
9 changes: 3 additions & 6 deletions src/app/pages/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { supabase } from '../../lib/supabase';
import { getOrCreateUserId } from '../../lib/user-id';
import { getStoredVDIDUser } from '../../lib/vdid';
import { logger } from '../../lib/logger';
import { clearAllUserData } from '../utils/security';

interface UserProfile {
username: string;
Expand Down Expand Up @@ -411,12 +412,8 @@ export default function SettingsPage() {
}
};

const handleLogout = () => {
localStorage.removeItem('vdid_user');
localStorage.removeItem('auth_method');
localStorage.removeItem('vspec_subscription');
localStorage.removeItem('vspec_user_profile');
localStorage.removeItem('vspec_notifications');
const handleLogout = async () => {
await clearAllUserData();
navigate('/login');
};

Expand Down
57 changes: 57 additions & 0 deletions src/app/utils/security.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { supabase } from '../../lib/supabase';

/**
* 🔐 安全登出工具函数
* 强制清理所有与项目相关的敏感数据,防止隐私残留
*/
export const clearAllUserData = async (): Promise<number> => {
// 1. 定义需要清理的敏感 Key 前缀 (覆盖两种命名风格,以及常用的 Zustand store)
const SENSITIVE_PREFIXES = [
'auth_', 'auth-',
'vdid_', 'vdid-',
'vspec_', 'vspec-',
'user_', 'user-',
'workspace-',
'document-',
'ai-',
'template-',
'sb-', // Supabase 默认有时候会用 sb- 开头
];

// 2. 清理 localStorage
const localKeysToRemove: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && SENSITIVE_PREFIXES.some(prefix => key.startsWith(prefix))) {
localKeysToRemove.push(key);
}
}
localKeysToRemove.forEach(key => localStorage.removeItem(key));

// 3. 清理 sessionStorage (敏感的临时数据常驻留在此)
const sessionKeysToRemove: string[] = [];
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (key && SENSITIVE_PREFIXES.some(prefix => key.startsWith(prefix))) {
sessionKeysToRemove.push(key);
}
}
sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));

// 4. 清理 Supabase session (服务端/Edge状态)
try {
await supabase.auth.signOut();
} catch (error) {
// 静默失败,通常是因为 session 已经过期或网络问题,不影响本地清理
if (import.meta.env.DEV) console.warn('[Security] Supabase signOut warning:', error);
}

const totalCleared = localKeysToRemove.length + sessionKeysToRemove.length;

// 仅在开发环境打印日志
if (import.meta.env.DEV) {
console.log(`[Security] Cleared ${totalCleared} sensitive keys (Local + Session).`);
}

return totalCleared;
};