added encryption

This commit is contained in:
2026-03-09 10:54:07 +05:30
parent 6e184dc590
commit 6720e28d08
27 changed files with 2093 additions and 709 deletions

View File

@@ -1,14 +1,21 @@
import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { getUserEntries, type JournalEntry } from '../lib/api'
import { formatIST, formatISTDateOnly, getISTDateComponents } from '../lib/timezone'
import { decryptEntry } from '../lib/crypto'
import { formatIST, getISTDateComponents } from '../lib/timezone'
import BottomNav from '../components/BottomNav'
interface DecryptedEntry extends JournalEntry {
decryptedTitle?: string
decryptedContent?: string
decryptError?: string
}
export default function HistoryPage() {
const { user, userId, loading } = useAuth()
const { user, userId, secretKey, loading } = useAuth()
const [currentMonth, setCurrentMonth] = useState(new Date())
const [selectedDate, setSelectedDate] = useState(new Date())
const [entries, setEntries] = useState<JournalEntry[]>([])
const [entries, setEntries] = useState<DecryptedEntry[]>([])
const [loadingEntries, setLoadingEntries] = useState(false)
// Fetch entries on mount and when userId changes
@@ -20,7 +27,57 @@ export default function HistoryPage() {
try {
const token = await user.getIdToken()
const response = await getUserEntries(userId, token, 100, 0)
setEntries(response.entries)
// Decrypt entries if they are encrypted
const decryptedEntries: DecryptedEntry[] = await Promise.all(
response.entries.map(async (entry) => {
if (entry.encryption?.encrypted && entry.encryption?.ciphertext && entry.encryption?.nonce) {
// Entry is encrypted, try to decrypt
if (!secretKey) {
return {
...entry,
decryptError: 'Encryption key not available',
decryptedTitle: '[Encrypted]',
}
}
try {
const decrypted = await decryptEntry(
entry.encryption.ciphertext,
entry.encryption.nonce,
secretKey
)
// Split decrypted content: first line is title, rest is content
const lines = decrypted.split('\n\n')
const decryptedTitle = lines[0]
const decryptedContent = lines.slice(1).join('\n\n')
return {
...entry,
decryptedTitle,
decryptedContent,
}
} catch (error) {
console.error(`Failed to decrypt entry ${entry.id}:`, error)
return {
...entry,
decryptError: 'Failed to decrypt entry',
decryptedTitle: '[Decryption Failed]',
}
}
} else {
// Entry is not encrypted, use plaintext
return {
...entry,
decryptedTitle: entry.title || '[Untitled]',
decryptedContent: entry.content || '',
}
}
})
)
setEntries(decryptedEntries)
} catch (error) {
console.error('Error fetching entries:', error)
} finally {
@@ -29,7 +86,7 @@ export default function HistoryPage() {
}
fetchEntries()
}, [user, userId])
}, [user, userId, secretKey])
const getDaysInMonth = (date: Date) => {
const year = date.getFullYear()
@@ -208,7 +265,7 @@ export default function HistoryPage() {
<span className="entry-date">{formatDate(entry.createdAt)}</span>
<span className="entry-time">{formatTime(entry.createdAt)}</span>
</div>
<h4 className="entry-title">{entry.title}</h4>
<h4 className="entry-title">{entry.decryptedTitle || entry.title || '[Untitled]'}</h4>
</button>
))
)}

View File

@@ -2,10 +2,11 @@ import { useAuth } from '../contexts/AuthContext'
import { Link } from 'react-router-dom'
import { useState } from 'react'
import { createEntry } from '../lib/api'
import { encryptEntry } from '../lib/crypto'
import BottomNav from '../components/BottomNav'
export default function HomePage() {
const { user, userId, loading, signOut } = useAuth()
const { user, userId, secretKey, loading } = useAuth()
const [entry, setEntry] = useState('')
const [title, setTitle] = useState('')
const [saving, setSaving] = useState(false)
@@ -41,22 +42,45 @@ export default function HomePage() {
return
}
if (!secretKey) {
setMessage({ type: 'error', text: 'Encryption key not available. Please log in again.' })
return
}
setSaving(true)
setMessage(null)
try {
const token = await user.getIdToken()
// Combine title and content for encryption
const contentToEncrypt = `${title.trim()}\n\n${entry.trim()}`
// Encrypt the entry with master key
const { ciphertext, nonce } = await encryptEntry(
contentToEncrypt,
secretKey
)
// Send encrypted data to backend
// Note: title and content are null for encrypted entries
await createEntry(
userId,
{
title: title.trim(),
content: entry.trim(),
title: undefined,
content: undefined,
isPublic: false,
encryption: {
encrypted: true,
ciphertext,
nonce,
algorithm: 'XSalsa20-Poly1305',
},
},
token
)
setMessage({ type: 'success', text: 'Entry saved successfully!' })
setMessage({ type: 'success', text: 'Entry saved securely!' })
setTitle('')
setEntry('')

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'
import { useState } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { updateUserProfile } from '../lib/api'
import BottomNav from '../components/BottomNav'
@@ -213,7 +213,7 @@ export default function SettingsPage() {
)}
{/* Clear Data */}
<button type="button" className="settings-clear-btn" onClick={handleClearData}>
<button type="button" className="settings-clear-btn" onClick={handleClearData} disabled>
<span>Clear Local Data</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>