Compare commits

...

2 Commits

Author SHA1 Message Date
68e2ece877 feat: load all accounts req 2025-05-24 12:16:11 +02:00
04bd27607f feat: load all saved accounts 2025-05-24 12:15:51 +02:00
2 changed files with 82 additions and 39 deletions

View File

@ -1,30 +1,14 @@
import { deriveDeviceKey, getDeviceId } from "@/util/deviceId"; import { useAccountRepo } from "@/repository/account";
import { useEffect, useState, type FC } from "react"; import { useEffect, type FC } from "react";
const IndexPage: FC = () => { const IndexPage: FC = () => {
const [deviceId, setDeviceId] = useState(""); const repo = useAccountRepo();
const [deviceKey, setDeviceKey] = useState("");
useEffect(() => { useEffect(() => {
getDeviceId().then((id) => { repo.loadAll().then((res) => console.log({ res }));
setDeviceId(id); }, [repo]);
deriveDeviceKey(id).then((key) => {
crypto.subtle.exportKey("raw", key).then((key) => {
const enc = new TextDecoder();
setDeviceKey(enc.decode(key));
});
});
});
}, []);
return ( return <div></div>;
<div>
<p>Id:</p>
<p>{deviceId}</p>
<p>Key:</p>
<p>{deviceKey}</p>
</div>
);
}; };
export default IndexPage; export default IndexPage;

View File

@ -22,13 +22,20 @@ export interface CreateAccountRequest {
export const useAccountRepo = () => { export const useAccountRepo = () => {
const { db } = useDbContext(); const { db } = useDbContext();
const encryptToken = useCallback(async (token: string) => { const getDeviceKey = useCallback(async () => {
const deviceId = await getDeviceId();
const deviceKey = await deriveDeviceKey(deviceId);
return deviceKey;
}, []);
const encryptToken = useCallback(
async (token: string) => {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const iv = crypto.getRandomValues(new Uint8Array(12)); const iv = crypto.getRandomValues(new Uint8Array(12));
const deviceId = await getDeviceId(); const deviceKey = await getDeviceKey();
const deviceKey = await deriveDeviceKey(deviceId);
const cipherText = await crypto.subtle.encrypt( const cipherText = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, { name: "AES-GCM", iv },
@ -40,11 +47,34 @@ export const useAccountRepo = () => {
data: Array.from(new Uint8Array(cipherText)), data: Array.from(new Uint8Array(cipherText)),
iv: Array.from(iv), 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( const save = useCallback(
async (req: CreateAccountRequest) => { async (req: CreateAccountRequest) => {
console.log({ db }); if (!db) throw new Error("No database connection");
const access = await encryptToken(req.access); const access = await encryptToken(req.access);
const refresh = await encryptToken(req.refresh); const refresh = await encryptToken(req.refresh);
@ -60,5 +90,34 @@ export const useAccountRepo = () => {
[db, encryptToken] [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 };
}; };