diff --git a/web/src/repository/account.ts b/web/src/repository/account.ts index 1226213..b6b6a21 100644 --- a/web/src/repository/account.ts +++ b/web/src/repository/account.ts @@ -22,29 +22,59 @@ export interface CreateAccountRequest { export const useAccountRepo = () => { const { db } = useDbContext(); - const encryptToken = useCallback(async (token: string) => { - const encoder = new TextEncoder(); - - const iv = crypto.getRandomValues(new Uint8Array(12)); - + const getDeviceKey = useCallback(async () => { const deviceId = await getDeviceId(); const deviceKey = await deriveDeviceKey(deviceId); - const cipherText = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - deviceKey, - encoder.encode(token) - ); - - return { - data: Array.from(new Uint8Array(cipherText)), - iv: Array.from(iv), - }; + return deviceKey; }, []); + const encryptToken = useCallback( + async (token: string) => { + const encoder = new TextEncoder(); + + const iv = crypto.getRandomValues(new Uint8Array(12)); + + const deviceKey = await getDeviceKey(); + + const cipherText = await crypto.subtle.encrypt( + { name: "AES-GCM", iv }, + deviceKey, + encoder.encode(token) + ); + + return { + data: Array.from(new Uint8Array(cipherText)), + iv: Array.from(iv), + }; + }, + [getDeviceKey] + ); + + const decryptToken = useCallback( + async (encrypted: { data: number[]; iv: number[] }) => { + const decoder = new TextDecoder(); + + const deviceKey = await getDeviceKey(); + + const decrypted = await crypto.subtle.decrypt( + { + name: "AES-GCM", + iv: new Uint8Array(encrypted.iv), + }, + deviceKey, + new Uint8Array(encrypted.data) + ); + + return decoder.decode(decrypted); + }, + [getDeviceKey] + ); + const save = useCallback( async (req: CreateAccountRequest) => { - console.log({ db }); + if (!db) throw new Error("No database connection"); + const access = await encryptToken(req.access); const refresh = await encryptToken(req.refresh); @@ -60,5 +90,34 @@ export const useAccountRepo = () => { [db, encryptToken] ); - return { save }; + const loadAll = useCallback(async () => { + if (!db) throw new Error("No database connection"); + + const tx = db.transaction("accounts", "readonly"); + const store = tx.objectStore("accounts"); + + const accounts: LocalAccount[] = await store.getAll(); + + const results = []; + for (const account of accounts) { + try { + const accessToken = await decryptToken(account.access); + const refreshToken = await decryptToken(account.refresh); + results.push({ + accountId: account.accountId, + label: account.label, + email: account.email, + access: accessToken, + refresh: refreshToken, + updatedAt: account.updatedAt, + }); + } catch (err) { + console.warn(`Failed to decrypt account ${account.label}:`, err); + } + } + + return results; + }, [db, decryptToken]); + + return { save, loadAll }; };