added encryption
This commit is contained in:
@@ -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>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -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('')
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user