diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index 6fc8266..8917e3e 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -7,22 +7,16 @@ import { api } from "./client"; import { ENDPOINTS } from "./endpoints"; -import type { LoginRequest, LoginResponse, LogoutResponse } from "../types/auth"; -import { sha256Hex } from "../utils/crypto"; +import type { LoginResponse, LogoutResponse } from "../types/auth"; /** * Authenticate with the master password. * - * The password is SHA-256 hashed client-side before transmission so that - * the plaintext never leaves the browser. The backend bcrypt-verifies the - * received hash against the stored bcrypt(sha256) digest. - * * @param password - The master password entered by the user. * @returns The login response containing the session token. */ export async function login(password: string): Promise { - const body: LoginRequest = { password: await sha256Hex(password) }; - return api.post(ENDPOINTS.authLogin, body); + return api.post(ENDPOINTS.authLogin, { password }); } /** diff --git a/frontend/src/pages/SetupPage.tsx b/frontend/src/pages/SetupPage.tsx index 1c86628..47db271 100644 --- a/frontend/src/pages/SetupPage.tsx +++ b/frontend/src/pages/SetupPage.tsx @@ -22,7 +22,6 @@ import { useNavigate } from "react-router-dom"; import type { ChangeEvent, FormEvent } from "react"; import { ApiError } from "../api/client"; import { getSetupStatus, submitSetup } from "../api/setup"; -import { sha256Hex } from "../utils/crypto"; // --------------------------------------------------------------------------- // Styles @@ -177,11 +176,8 @@ export function SetupPage(): React.JSX.Element { setSubmitting(true); try { - // Hash the password client-side before transmission — the plaintext - // never leaves the browser. The backend bcrypt-hashes the received hash. - const hashedPassword = await sha256Hex(values.masterPassword); await submitSetup({ - master_password: hashedPassword, + master_password: values.masterPassword, database_path: values.databasePath, fail2ban_socket: values.fail2banSocket, timezone: values.timezone, diff --git a/frontend/src/pages/__tests__/SetupPage.test.tsx b/frontend/src/pages/__tests__/SetupPage.test.tsx index 96c76aa..65dbbda 100644 --- a/frontend/src/pages/__tests__/SetupPage.test.tsx +++ b/frontend/src/pages/__tests__/SetupPage.test.tsx @@ -10,11 +10,6 @@ vi.mock("../../api/setup", () => ({ submitSetup: vi.fn(), })); -// Mock the crypto utility — we only need it to resolve without testing SHA256. -vi.mock("../../utils/crypto", () => ({ - sha256Hex: vi.fn().mockResolvedValue("hashed-password"), -})); - import { getSetupStatus } from "../../api/setup"; const mockedGetSetupStatus = vi.mocked(getSetupStatus); diff --git a/frontend/src/utils/crypto.ts b/frontend/src/utils/crypto.ts deleted file mode 100644 index 721b521..0000000 --- a/frontend/src/utils/crypto.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Client-side cryptography utilities. - * - * Uses the browser-native SubtleCrypto API so no third-party bundle is required. - */ - -/** - * Return the SHA-256 hex digest of `input`. - * - * Hashing passwords before transmission means the plaintext never leaves the - * browser, even when HTTPS is not enforced in a development environment. - * The backend then applies bcrypt on top of the received hash. - * - * @param input - The string to hash (e.g. the master password). - * @returns Lowercase hex-encoded SHA-256 digest. - */ -export async function sha256Hex(input: string): Promise { - const data = new TextEncoder().encode(input); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - return Array.from(new Uint8Array(hashBuffer)) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); -}