mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
feat: enhance zero-knowledge authentication by deriving and sending auth hashes to the server
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { act, renderHook } from "@testing-library/react";
|
import { act, renderHook } from "@testing-library/react";
|
||||||
import { HttpResponse, http } from "msw";
|
import { HttpResponse, http } from "msw";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { mockMasterKey } from "../../test/fixtures/auth.fixture";
|
||||||
import { mockUser } from "../../test/fixtures/user.fixture";
|
import { mockUser } from "../../test/fixtures/user.fixture";
|
||||||
import { server } from "../../test/mocks/server";
|
import { server } from "../../test/mocks/server";
|
||||||
import { useAuthStore } from "../store/useAuthStore";
|
import { useAuthStore } from "../store/useAuthStore";
|
||||||
@@ -13,26 +14,24 @@ import {
|
|||||||
} from "../utils/keystore";
|
} from "../utils/keystore";
|
||||||
import { useAuth } from "./useAuth";
|
import { useAuth } from "./useAuth";
|
||||||
|
|
||||||
|
vi.mock("../utils/crypto");
|
||||||
|
vi.mock("../utils/keystore");
|
||||||
|
|
||||||
const API_URL = "http://piku-server";
|
const API_URL = "http://piku-server";
|
||||||
|
|
||||||
vi.mock("../utils/crypto", () => ({
|
|
||||||
CryptoUtils: {
|
|
||||||
deriveMasterKey: vi
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValue({ type: "secret" } as unknown as CryptoKey),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../utils/keystore", () => ({
|
|
||||||
saveMasterKey: vi.fn().mockResolvedValue(undefined),
|
|
||||||
loadMasterKey: vi
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValue({ type: "secret" } as unknown as CryptoKey),
|
|
||||||
clearMasterKey: vi.fn().mockResolvedValue(undefined),
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
// hack to set up mock implementations using fixtures
|
||||||
|
vi.mocked(CryptoUtils.deriveKeyBundle).mockResolvedValue({
|
||||||
|
masterKey: mockMasterKey,
|
||||||
|
authHash: "mock-auth-hash",
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(loadMasterKey).mockResolvedValue(mockMasterKey);
|
||||||
|
vi.mocked(saveMasterKey).mockResolvedValue(undefined);
|
||||||
|
vi.mocked(clearMasterKey).mockResolvedValue(undefined);
|
||||||
|
|
||||||
useAuthStore.setState({
|
useAuthStore.setState({
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
user: null,
|
user: null,
|
||||||
@@ -61,34 +60,21 @@ describe("isAuthenticated", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("login", () => {
|
describe("login", () => {
|
||||||
it("should derive the master key using the provided credentials", async () => {
|
it("should persist the provided master key to IndexedDB", async () => {
|
||||||
const { result } = renderHook(() => useAuth());
|
const { result } = renderHook(() => useAuth());
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await result.current.login("access-token", mockUser, "test-password");
|
await result.current.login("access-token", mockUser, mockMasterKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(CryptoUtils.deriveMasterKey).toHaveBeenCalledWith(
|
expect(saveMasterKey).toHaveBeenCalledWith(mockMasterKey);
|
||||||
"test-password",
|
|
||||||
mockUser.email,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should persist the derived master key to IndexedDB", async () => {
|
|
||||||
const { result } = renderHook(() => useAuth());
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await result.current.login("access-token", mockUser, "my-password");
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(saveMasterKey).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update the store with the access token and user profile", async () => {
|
it("should update the store with the access token and user profile", async () => {
|
||||||
const { result } = renderHook(() => useAuth());
|
const { result } = renderHook(() => useAuth());
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await result.current.login("my-access-token", mockUser, "my-password");
|
await result.current.login("my-access-token", mockUser, mockMasterKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(useAuthStore.getState().accessToken).toBe("my-access-token");
|
expect(useAuthStore.getState().accessToken).toBe("my-access-token");
|
||||||
@@ -99,7 +85,7 @@ describe("login", () => {
|
|||||||
const { result } = renderHook(() => useAuth());
|
const { result } = renderHook(() => useAuth());
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await result.current.login("token", mockUser, "my-password");
|
await result.current.login("token", mockUser, mockMasterKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(useKeyStore.getState().masterKey).not.toBeNull();
|
expect(useKeyStore.getState().masterKey).not.toBeNull();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { endpoints } from "../config/endpoints";
|
|||||||
import type { UserProfile } from "../store/useAuthStore";
|
import type { UserProfile } from "../store/useAuthStore";
|
||||||
import { useAuthStore } from "../store/useAuthStore";
|
import { useAuthStore } from "../store/useAuthStore";
|
||||||
import { useKeyStore } from "../store/useKeyStore";
|
import { useKeyStore } from "../store/useKeyStore";
|
||||||
import { CryptoUtils } from "../utils/crypto";
|
|
||||||
import {
|
import {
|
||||||
clearMasterKey,
|
clearMasterKey,
|
||||||
loadMasterKey,
|
loadMasterKey,
|
||||||
@@ -18,16 +17,12 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
const isAuthenticated = !!accessToken;
|
const isAuthenticated = !!accessToken;
|
||||||
|
|
||||||
// called after successful login — derive & save master key
|
// called after successful login — save master key
|
||||||
const login = async (
|
const setAuthStore = async (
|
||||||
access: string,
|
access: string,
|
||||||
profile: UserProfile,
|
profile: UserProfile,
|
||||||
password: string,
|
masterKey: CryptoKey,
|
||||||
) => {
|
) => {
|
||||||
const masterKey = await CryptoUtils.deriveMasterKey(
|
|
||||||
password,
|
|
||||||
profile.email,
|
|
||||||
);
|
|
||||||
await saveMasterKey(masterKey);
|
await saveMasterKey(masterKey);
|
||||||
setMasterKey(masterKey);
|
setMasterKey(masterKey);
|
||||||
setAuth(access, profile);
|
setAuth(access, profile);
|
||||||
@@ -76,7 +71,7 @@ export const useAuth = () => {
|
|||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
user,
|
user,
|
||||||
isInitializing,
|
isInitializing,
|
||||||
login,
|
setAuthStore,
|
||||||
logout,
|
logout,
|
||||||
initialize,
|
initialize,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import FormField from "../components/ui/FormField";
|
|||||||
import { endpoints } from "../config/endpoints";
|
import { endpoints } from "../config/endpoints";
|
||||||
import { ROUTES } from "../config/routes";
|
import { ROUTES } from "../config/routes";
|
||||||
import { useAuth } from "../hooks/useAuth";
|
import { useAuth } from "../hooks/useAuth";
|
||||||
|
import { CryptoUtils } from "../utils/crypto";
|
||||||
|
|
||||||
const loginSchema = z.object({
|
const loginSchema = z.object({
|
||||||
email: z.string().email("Please enter a valid email"),
|
email: z.string().email("Please enter a valid email"),
|
||||||
@@ -22,7 +23,7 @@ export default function Login() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [apiError, setApiError] = useState<string | null>(null);
|
const [apiError, setApiError] = useState<string | null>(null);
|
||||||
const { login } = useAuth();
|
const { setAuthStore } = useAuth();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -36,13 +37,24 @@ export default function Login() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setApiError(null);
|
setApiError(null);
|
||||||
try {
|
try {
|
||||||
const { data: authData } = await publicApi.post(endpoints.LOGIN, data);
|
// client side key derivation for 0 knowledge
|
||||||
|
const { masterKey, authHash } = await CryptoUtils.deriveKeyBundle(
|
||||||
|
data.password,
|
||||||
|
data.email,
|
||||||
|
);
|
||||||
|
|
||||||
|
// send just the authHash as the password to the server
|
||||||
|
const { data: authData } = await publicApi.post(endpoints.LOGIN, {
|
||||||
|
email: data.email,
|
||||||
|
password: authHash,
|
||||||
|
});
|
||||||
|
|
||||||
const { data: userData } = await api.get(endpoints.ME, {
|
const { data: userData } = await api.get(endpoints.ME, {
|
||||||
headers: { Authorization: `Bearer ${authData.access}` },
|
headers: { Authorization: `Bearer ${authData.access}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
login(authData.access, userData, data.password);
|
// store the auth related data
|
||||||
|
setAuthStore(authData.access, userData, masterKey);
|
||||||
|
|
||||||
navigate(ROUTES.DRAWER);
|
navigate(ROUTES.DRAWER);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Logo from "../components/Logo";
|
|||||||
import FormField from "../components/ui/FormField";
|
import FormField from "../components/ui/FormField";
|
||||||
import { endpoints } from "../config/endpoints";
|
import { endpoints } from "../config/endpoints";
|
||||||
import { ROUTES } from "../config/routes";
|
import { ROUTES } from "../config/routes";
|
||||||
|
import { CryptoUtils } from "../utils/crypto";
|
||||||
|
|
||||||
// validation logic
|
// validation logic
|
||||||
const registerSchema = z
|
const registerSchema = z
|
||||||
@@ -43,10 +44,16 @@ export default function Register() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setApiError(null);
|
setApiError(null);
|
||||||
try {
|
try {
|
||||||
|
// We generate the key bundle here to get the authHash (password) for the server.
|
||||||
|
const { authHash } = await CryptoUtils.deriveKeyBundle(
|
||||||
|
data.password,
|
||||||
|
data.email,
|
||||||
|
);
|
||||||
|
|
||||||
await publicApi.post(endpoints.REGISTER, {
|
await publicApi.post(endpoints.REGISTER, {
|
||||||
full_name: data.full_name,
|
full_name: data.full_name,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
password: data.password,
|
password: authHash,
|
||||||
});
|
});
|
||||||
navigate(ROUTES.VERIFY_EMAIL);
|
navigate(ROUTES.VERIFY_EMAIL);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -3,43 +3,65 @@ import { CryptoUtils } from "./crypto";
|
|||||||
|
|
||||||
let utils: CryptoUtils;
|
let utils: CryptoUtils;
|
||||||
|
|
||||||
describe("deriveMasterKey", () => {
|
describe("deriveKeyBundle", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
utils = new CryptoUtils();
|
utils = new CryptoUtils();
|
||||||
await utils.initialize();
|
await utils.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should generate a valid CryptoKey instance", async () => {
|
it("should generate a valid CryptoKey and a 64-char hex authHash", async () => {
|
||||||
const key = await CryptoUtils.deriveMasterKey("password", "test@test.com");
|
const { masterKey, authHash } = await CryptoUtils.deriveKeyBundle(
|
||||||
|
"password",
|
||||||
|
"test@test.com",
|
||||||
|
);
|
||||||
|
|
||||||
expect(key.type).toBe("secret");
|
expect(masterKey.type).toBe("secret");
|
||||||
expect(key).toBeInstanceOf(CryptoKey);
|
expect(masterKey).toBeInstanceOf(CryptoKey);
|
||||||
|
expect(authHash).toHaveLength(64); // SHA-256 hex
|
||||||
|
expect(typeof authHash).toBe("string");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should produce identical keys for identical credentials (deterministic)", async () => {
|
it("should produce identical bundles for identical credentials (deterministic)", async () => {
|
||||||
const keyA = await CryptoUtils.deriveMasterKey("password", "user@me.com");
|
const bundleA = await CryptoUtils.deriveKeyBundle(
|
||||||
const keyB = await CryptoUtils.deriveMasterKey("password", "USER@me.com");
|
"password",
|
||||||
const secret = "shared-secret";
|
"user@me.com",
|
||||||
|
);
|
||||||
|
const bundleB = await CryptoUtils.deriveKeyBundle(
|
||||||
|
"password",
|
||||||
|
"user@me.com",
|
||||||
|
);
|
||||||
|
|
||||||
const encryptedContent = await utils.encryptLetter(secret, keyA);
|
expect(bundleA.authHash).toBe(bundleB.authHash);
|
||||||
const decryptedContent = await utils.decryptLetter(encryptedContent, keyB);
|
|
||||||
|
const secret = "shared-secret";
|
||||||
|
const encryptedContent = await utils.encryptLetter(
|
||||||
|
secret,
|
||||||
|
bundleA.masterKey,
|
||||||
|
);
|
||||||
|
const decryptedContent = await utils.decryptLetter(
|
||||||
|
encryptedContent,
|
||||||
|
bundleB.masterKey,
|
||||||
|
);
|
||||||
|
|
||||||
expect(decryptedContent).toBe(secret);
|
expect(decryptedContent).toBe(secret);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should produce different keys for different users", async () => {
|
it("should produce different keys and hashes for different users", async () => {
|
||||||
const keyA = await CryptoUtils.deriveMasterKey(
|
const bundleA = await CryptoUtils.deriveKeyBundle(
|
||||||
"password",
|
"password",
|
||||||
"test1@gmail.com",
|
"test1@gmail.com",
|
||||||
);
|
);
|
||||||
const keyB = await CryptoUtils.deriveMasterKey(
|
const bundleB = await CryptoUtils.deriveKeyBundle(
|
||||||
"password",
|
"password",
|
||||||
"test2@gmail.com",
|
"test2@gmail.com",
|
||||||
);
|
);
|
||||||
|
|
||||||
const encrypted = await utils.encryptLetter("secret", keyA);
|
expect(bundleA.authHash).not.toBe(bundleB.authHash);
|
||||||
|
|
||||||
await expect(utils.decryptLetter(encrypted, keyB)).rejects.toThrow();
|
const encrypted = await utils.encryptLetter("secret", bundleA.masterKey);
|
||||||
|
await expect(
|
||||||
|
utils.decryptLetter(encrypted, bundleB.masterKey),
|
||||||
|
).rejects.toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,7 +69,11 @@ describe("encryptLetter / decryptLetter", () => {
|
|||||||
let masterKey: CryptoKey;
|
let masterKey: CryptoKey;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
masterKey = await CryptoUtils.deriveMasterKey("password", "test@test.com");
|
const bundle = await CryptoUtils.deriveKeyBundle(
|
||||||
|
"password",
|
||||||
|
"test@test.com",
|
||||||
|
);
|
||||||
|
masterKey = bundle.masterKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should restore the original plaintext after a roundtrip", async () => {
|
it("should restore the original plaintext after a roundtrip", async () => {
|
||||||
@@ -80,7 +106,11 @@ describe("encryptLetter / decryptLetter", () => {
|
|||||||
describe("encryptMetadata / decryptMetadata", () => {
|
describe("encryptMetadata / decryptMetadata", () => {
|
||||||
let masterKey: CryptoKey;
|
let masterKey: CryptoKey;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
masterKey = await CryptoUtils.deriveMasterKey("password", "test@test.com");
|
const bundle = await CryptoUtils.deriveKeyBundle(
|
||||||
|
"password",
|
||||||
|
"test@test.com",
|
||||||
|
);
|
||||||
|
masterKey = bundle.masterKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should successfully encrypt and decrypt object content", async () => {
|
it("should successfully encrypt and decrypt object content", async () => {
|
||||||
@@ -100,7 +130,11 @@ describe("encryptMetadata / decryptMetadata", () => {
|
|||||||
describe("encryptImage / decryptImage", () => {
|
describe("encryptImage / decryptImage", () => {
|
||||||
let masterKey: CryptoKey;
|
let masterKey: CryptoKey;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
masterKey = await CryptoUtils.deriveMasterKey("password", "test@test.com");
|
const bundle = await CryptoUtils.deriveKeyBundle(
|
||||||
|
"password",
|
||||||
|
"test@test.com",
|
||||||
|
);
|
||||||
|
masterKey = bundle.masterKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should transform a File into an encrypted .bin Blob", async () => {
|
it("should transform a File into an encrypted .bin Blob", async () => {
|
||||||
@@ -138,7 +172,8 @@ describe("encryptImage / decryptImage", () => {
|
|||||||
describe("Sharing Key Decryption (TDD)", () => {
|
describe("Sharing Key Decryption (TDD)", () => {
|
||||||
let masterKey: CryptoKey;
|
let masterKey: CryptoKey;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
masterKey = await CryptoUtils.deriveMasterKey("pass", "salt");
|
const bundle = await CryptoUtils.deriveKeyBundle("password", "salt");
|
||||||
|
masterKey = bundle.masterKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should decrypt a letter using ONLY the sharing key", async () => {
|
it("should decrypt a letter using ONLY the sharing key", async () => {
|
||||||
|
|||||||
@@ -65,34 +65,54 @@ export class CryptoUtils {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a Master Key from a password + email (salt).
|
* Derives a Key Bundle (MasterKey + AuthHash) from a password + email.
|
||||||
* Same credentials = same key.
|
* Absolute zero knowledge!!
|
||||||
*/
|
*/
|
||||||
public static async deriveMasterKey(
|
public static async deriveKeyBundle(
|
||||||
password: string,
|
password: string,
|
||||||
email: string,
|
email: string,
|
||||||
): Promise<CryptoKey> {
|
): Promise<{ masterKey: CryptoKey; authHash: string }> {
|
||||||
const enc = new TextEncoder();
|
const enc = new TextEncoder();
|
||||||
|
const salt = enc.encode(email.toLowerCase());
|
||||||
|
|
||||||
const baseKey = await crypto.subtle.importKey(
|
const baseKey = await crypto.subtle.importKey(
|
||||||
"raw",
|
"raw",
|
||||||
enc.encode(password),
|
enc.encode(password),
|
||||||
"PBKDF2",
|
"PBKDF2",
|
||||||
false,
|
false,
|
||||||
["deriveKey"],
|
["deriveBits", "deriveKey"],
|
||||||
);
|
);
|
||||||
|
|
||||||
return crypto.subtle.deriveKey(
|
const masterSeed = await crypto.subtle.deriveBits(
|
||||||
{
|
{
|
||||||
name: "PBKDF2",
|
name: "PBKDF2",
|
||||||
salt: enc.encode(email.toLowerCase()),
|
salt,
|
||||||
iterations: CryptoUtils.PBKDF2_ITERATIONS,
|
iterations: CryptoUtils.PBKDF2_ITERATIONS,
|
||||||
hash: "SHA-256",
|
hash: "SHA-256",
|
||||||
},
|
},
|
||||||
baseKey,
|
baseKey,
|
||||||
|
512, // 512 bits to split
|
||||||
|
);
|
||||||
|
|
||||||
|
// first 256 bits for MasterKey, last 256 bits for AuthHash
|
||||||
|
const masterKeyBytes = masterSeed.slice(0, 32);
|
||||||
|
const authHashBytes = masterSeed.slice(32, 64);
|
||||||
|
|
||||||
|
// Create the MasterKey for client-side encryption
|
||||||
|
const masterKey = await crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
masterKeyBytes,
|
||||||
CryptoUtils.AES_GCM,
|
CryptoUtils.AES_GCM,
|
||||||
false,
|
false,
|
||||||
["encrypt", "decrypt", "wrapKey", "unwrapKey"],
|
["encrypt", "decrypt", "wrapKey", "unwrapKey"],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create the hex AuthHash for server-side verification
|
||||||
|
const authHash = Array.from(new Uint8Array(authHashBytes))
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return { masterKey, authHash };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper to encrypt data and wrap the key
|
// Internal helper to encrypt data and wrap the key
|
||||||
|
|||||||
@@ -8,22 +8,23 @@ afterEach(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function makeMasterKey() {
|
async function makeMasterKey() {
|
||||||
return CryptoUtils.deriveMasterKey("test-password", "test@example.com");
|
return await CryptoUtils.deriveKeyBundle("test-password", "test@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("keystore", () => {
|
describe("keystore", () => {
|
||||||
it("should save and load a CryptoKey successfully", async () => {
|
it("should save and load a CryptoKey successfully", async () => {
|
||||||
const key = await makeMasterKey();
|
const key = await makeMasterKey();
|
||||||
|
|
||||||
await saveMasterKey(key);
|
await saveMasterKey(key.masterKey);
|
||||||
const keyfromMemory = await loadMasterKey();
|
const keyfromMemory = await loadMasterKey();
|
||||||
|
|
||||||
expect(keyfromMemory).toBeInstanceOf(CryptoKey);
|
expect(keyfromMemory).toBeInstanceOf(CryptoKey);
|
||||||
expect(keyfromMemory).toEqual(key);
|
expect(keyfromMemory).toEqual(key.masterKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove the stored key from memory", async () => {
|
it("should remove the stored key from memory", async () => {
|
||||||
await saveMasterKey(await makeMasterKey());
|
const masterKey = await makeMasterKey();
|
||||||
|
await saveMasterKey(masterKey.masterKey);
|
||||||
await clearMasterKey();
|
await clearMasterKey();
|
||||||
|
|
||||||
const keyfromMemory = await loadMasterKey();
|
const keyfromMemory = await loadMasterKey();
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ describe("letterLogic image helpers", () => {
|
|||||||
let crypto: CryptoUtils;
|
let crypto: CryptoUtils;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
masterKey = await CryptoUtils.deriveMasterKey(
|
const keyBundle = await CryptoUtils.deriveKeyBundle(
|
||||||
"password123",
|
"password123",
|
||||||
"test@example.com",
|
"test@example.com",
|
||||||
);
|
);
|
||||||
|
masterKey = keyBundle.masterKey;
|
||||||
crypto = new CryptoUtils();
|
crypto = new CryptoUtils();
|
||||||
await crypto.initialize();
|
await crypto.initialize();
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
export const mockMasterKey: CryptoKey = {
|
||||||
|
type: "secret",
|
||||||
|
algorithm: { name: "AES-GCM" },
|
||||||
|
extractable: false,
|
||||||
|
usages: ["encrypt", "decrypt"],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user