Add encrypted key support for chat and nodes
Introduces encrypted private key storage for nodes and users, updates chat message schema to support sender-side encryption, and adds supporting libraries and tests for cryptographic signing and identity unlock flows. Includes new database migrations, API route updates, and React components for identity unlock prompts.
This commit is contained in:
@@ -7,16 +7,18 @@ import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useAuth } from '@/lib/contexts/AuthContext';
|
||||
import { HomeIcon, SearchIcon, BellIcon, UserIcon, ShieldIcon, SettingsIcon, BotIcon } from './Icons';
|
||||
import { formatFullHandle } from '@/lib/utils/handle';
|
||||
import { LogOut, Settings2 } from 'lucide-react';
|
||||
import { LogOut, Settings2, Lock, Unlock } from 'lucide-react';
|
||||
import { IdentityUnlockPrompt } from './IdentityUnlockPrompt';
|
||||
|
||||
export function Sidebar() {
|
||||
const { user, isAdmin } = useAuth();
|
||||
const { user, isAdmin, logout, isIdentityUnlocked } = useAuth();
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [customLogoUrl, setCustomLogoUrl] = useState<string | null | undefined>(undefined);
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
const [unreadChatCount, setUnreadChatCount] = useState(0);
|
||||
const [loggingOut, setLoggingOut] = useState(false);
|
||||
const [showUnlockPrompt, setShowUnlockPrompt] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/node')
|
||||
@@ -75,7 +77,7 @@ export function Sidebar() {
|
||||
|
||||
setLoggingOut(true);
|
||||
try {
|
||||
await fetch('/api/auth/logout', { method: 'POST' });
|
||||
await logout();
|
||||
window.location.href = '/explore';
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
@@ -188,6 +190,72 @@ export function Sidebar() {
|
||||
<div style={{ color: 'var(--foreground-tertiary)', fontSize: '13px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{formatFullHandle(user.handle)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Identity Status Indicator */}
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!isIdentityUnlocked) {
|
||||
setShowUnlockPrompt(true);
|
||||
}
|
||||
}}
|
||||
title={isIdentityUnlocked ? 'Identity unlocked - you can perform actions' : 'Identity locked - click to unlock'}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
padding: '10px 12px',
|
||||
marginBottom: '12px',
|
||||
borderRadius: '8px',
|
||||
background: isIdentityUnlocked
|
||||
? 'rgba(34, 197, 94, 0.1)'
|
||||
: 'rgba(251, 191, 36, 0.1)',
|
||||
border: isIdentityUnlocked
|
||||
? '1px solid rgba(34, 197, 94, 0.2)'
|
||||
: '1px solid rgba(251, 191, 36, 0.2)',
|
||||
cursor: isIdentityUnlocked ? 'default' : 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isIdentityUnlocked) {
|
||||
e.currentTarget.style.background = 'rgba(251, 191, 36, 0.15)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isIdentityUnlocked) {
|
||||
e.currentTarget.style.background = 'rgba(251, 191, 36, 0.1)';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isIdentityUnlocked ? (
|
||||
<Unlock size={16} style={{ color: 'rgb(34, 197, 94)', flexShrink: 0 }} />
|
||||
) : (
|
||||
<Lock size={16} style={{ color: 'rgb(251, 191, 36)', flexShrink: 0 }} />
|
||||
)}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
color: isIdentityUnlocked ? 'rgb(34, 197, 94)' : 'rgb(251, 191, 36)',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}}>
|
||||
{isIdentityUnlocked ? 'Identity Unlocked' : 'Identity Locked'}
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: '11px',
|
||||
color: 'var(--foreground-tertiary)',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}}>
|
||||
{isIdentityUnlocked
|
||||
? 'You can perform actions'
|
||||
: 'Click to unlock'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
disabled={loggingOut}
|
||||
@@ -205,6 +273,14 @@ export function Sidebar() {
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Identity Unlock Prompt Modal */}
|
||||
{showUnlockPrompt && (
|
||||
<IdentityUnlockPrompt
|
||||
onUnlock={() => setShowUnlockPrompt(false)}
|
||||
onCancel={() => setShowUnlockPrompt(false)}
|
||||
/>
|
||||
)}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user