added encryption
This commit is contained in:
@@ -15,11 +15,25 @@ import {
|
||||
} from 'firebase/auth'
|
||||
import { auth, googleProvider } from '../lib/firebase'
|
||||
import { registerUser, getUserByEmail } from '../lib/api'
|
||||
import {
|
||||
deriveSecretKey,
|
||||
generateDeviceKey,
|
||||
generateSalt,
|
||||
getSalt,
|
||||
saveSalt,
|
||||
getDeviceKey,
|
||||
saveDeviceKey,
|
||||
encryptSecretKey,
|
||||
decryptSecretKey,
|
||||
saveEncryptedSecretKey,
|
||||
getEncryptedSecretKey,
|
||||
} from '../lib/crypto'
|
||||
|
||||
type AuthContextValue = {
|
||||
user: User | null
|
||||
userId: string | null
|
||||
loading: boolean
|
||||
secretKey: Uint8Array | null
|
||||
signInWithGoogle: () => Promise<void>
|
||||
signOut: () => Promise<void>
|
||||
}
|
||||
@@ -29,17 +43,78 @@ const AuthContext = createContext<AuthContextValue | null>(null)
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
const [userId, setUserId] = useState<string | null>(null)
|
||||
const [secretKey, setSecretKey] = useState<Uint8Array | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
// Initialize encryption keys on login
|
||||
async function initializeEncryption(authUser: User, token: string) {
|
||||
try {
|
||||
const firebaseUID = authUser.uid
|
||||
const firebaseIDToken = token
|
||||
|
||||
// Get or create salt
|
||||
let salt = getSalt()
|
||||
if (!salt) {
|
||||
salt = generateSalt()
|
||||
saveSalt(salt)
|
||||
}
|
||||
|
||||
// Derive master key from Firebase credentials
|
||||
const derivedKey = await deriveSecretKey(firebaseUID, firebaseIDToken, salt)
|
||||
|
||||
// Check if device key exists
|
||||
let deviceKey = await getDeviceKey()
|
||||
if (!deviceKey) {
|
||||
// First login on this device: generate device key
|
||||
deviceKey = await generateDeviceKey()
|
||||
await saveDeviceKey(deviceKey)
|
||||
}
|
||||
|
||||
// Check if encrypted key exists in IndexedDB
|
||||
const cachedEncrypted = await getEncryptedSecretKey()
|
||||
if (!cachedEncrypted) {
|
||||
// First login (or IndexedDB cleared): encrypt and cache the key
|
||||
const encrypted = await encryptSecretKey(derivedKey, deviceKey)
|
||||
await saveEncryptedSecretKey(encrypted.ciphertext, encrypted.nonce)
|
||||
} else {
|
||||
// Subsequent login on same device: verify we can decrypt
|
||||
// (This ensures device key is correct)
|
||||
try {
|
||||
await decryptSecretKey(
|
||||
cachedEncrypted.ciphertext,
|
||||
cachedEncrypted.nonce,
|
||||
deviceKey
|
||||
)
|
||||
} catch (error) {
|
||||
console.warn('Device key mismatch, regenerating...', error)
|
||||
// Device key doesn't match - regenerate
|
||||
deviceKey = await generateDeviceKey()
|
||||
await saveDeviceKey(deviceKey)
|
||||
const encrypted = await encryptSecretKey(derivedKey, deviceKey)
|
||||
await saveEncryptedSecretKey(encrypted.ciphertext, encrypted.nonce)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep secret key in memory for session
|
||||
setSecretKey(derivedKey)
|
||||
} catch (error) {
|
||||
console.error('Error initializing encryption:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Register or fetch user from MongoDB
|
||||
async function syncUserWithDatabase(authUser: User) {
|
||||
try {
|
||||
const token = await authUser.getIdToken()
|
||||
const email = authUser.email!
|
||||
|
||||
// Initialize encryption before syncing user
|
||||
await initializeEncryption(authUser, token)
|
||||
|
||||
// Try to get existing user
|
||||
try {
|
||||
const existingUser = await getUserByEmail(email, token)
|
||||
const existingUser = await getUserByEmail(email, token) as { id: string }
|
||||
setUserId(existingUser.id)
|
||||
} catch (error) {
|
||||
// User doesn't exist, register them
|
||||
@@ -50,11 +125,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
photoURL: authUser.photoURL || undefined,
|
||||
},
|
||||
token
|
||||
)
|
||||
) as { id: string }
|
||||
setUserId(newUser.id)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error syncing user with database:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +138,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const unsubscribe = onAuthStateChanged(auth, async (u) => {
|
||||
setUser(u)
|
||||
if (u) {
|
||||
await syncUserWithDatabase(u)
|
||||
try {
|
||||
await syncUserWithDatabase(u)
|
||||
} catch (error) {
|
||||
console.error('Auth sync failed:', error)
|
||||
}
|
||||
} else {
|
||||
setUserId(null)
|
||||
setSecretKey(null)
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
@@ -77,6 +158,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
// Clear secret key from memory
|
||||
setSecretKey(null)
|
||||
// Keep device key and encrypted key for next login
|
||||
// Do NOT clear localStorage or IndexedDB
|
||||
await firebaseSignOut(auth)
|
||||
setUserId(null)
|
||||
}
|
||||
@@ -84,6 +169,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const value: AuthContextValue = {
|
||||
user,
|
||||
userId,
|
||||
secretKey,
|
||||
loading,
|
||||
signInWithGoogle,
|
||||
signOut,
|
||||
|
||||
Reference in New Issue
Block a user