perf: replace bcryptjs with native bcrypt for password hashing
Deploy / release (push) Skipped
Deploy / deploy (push) Failing after 58s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-21 20:37:02 +02:00
1 parent 830d252346
commit d2120987b0
6 files changed
+47 -18

No files matched your search

+3 -3
View File
@@ -1,9 +1,9 @@
"use server";
import crypto from "node:crypto";
import { hash } from "bcryptjs";
import { z } from "zod";
import { Prisma } from "@/generated/prisma/client";
import { hashPassword } from "@/lib/auth/password";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { adminAction } from "@/lib/safe-action";
@@ -30,7 +30,7 @@ export const createUser = adminAction(
throw new ActionError("Cannot assign rank equal or higher than your own");
}
const hashedPassword = await hash(password, 12);
const hashedPassword = await hashPassword(password);
const now = Math.floor(Date.now() / 1000);
try {
@@ -304,7 +304,7 @@ export const resetPassword = adminAction(
.randomBytes(12)
.toString("base64url")
.slice(0, 16);
const hashed = await hash(newPassword, 10);
const hashed = await hashPassword(newPassword);
await prisma.user.update({
where: { id: ctx.data.userId },
+3 -3
View File
@@ -1,4 +1,4 @@
import { hash as bcryptHash } from "bcryptjs";
import { hash as bcryptHash } from "bcrypt";
import { describe, expect, it } from "vitest";
import {
checkLogin,
@@ -50,9 +50,9 @@ describe("hashPassword (PASSWORD_HASH=argon2id)", () => {
describe("bcrypt", () => {
it("verifies a bcrypt hash and accepts the PHP $2y$ prefix", async () => {
const h = await bcryptHash("hunter2", 10); // bcryptjs emits $2a$
const h = await bcryptHash("hunter2", 10); // native bcrypt emits $2a$/$2b$
expect(await verifyPassword("hunter2", h)).toBe(true);
// PHP stores $2y$ — bcryptjs must accept it as equivalent.
// PHP stores $2y$ — bcrypt must accept it as equivalent.
const phpStyle = h.replace(/^\$2[ab]\$/, "$2y$");
expect(await verifyPassword("hunter2", phpStyle)).toBe(true);
expect(await verifyPassword("nope", h)).toBe(false);
+6 -4
View File
@@ -1,5 +1,5 @@
import { randomBytes } from "node:crypto";
import { compare as bcryptCompare, hash as bcryptHash } from "bcryptjs";
import { compare as bcryptCompare, hash as bcryptHash } from "bcrypt";
import { argon2id, argon2Verify, md5 } from "hash-wasm";
// AtomCMS hashing (config/hashing.php): argon2id with memory=65536 KiB, time=4,
@@ -49,8 +49,8 @@ export async function hashPassword(password: string): Promise<string> {
...ARGON2_PARAMS,
});
}
// bcryptjs emits $2a$/$2b$; normalise to the PHP-canonical $2y$ the emulator
// and existing AtomCMS rows use.
// native bcrypt emits $2a$/$2b$; normalise to the PHP-canonical $2y$ the
// emulator and existing AtomCMS rows use.
const h = await bcryptHash(password, BCRYPT_ROUNDS);
return h.replace(/^\$2[ab]\$/, "$2y$");
}
@@ -84,7 +84,9 @@ export async function verifyPassword(
}
if (/^\$2[aby]\$/.test(stored)) {
try {
return await bcryptCompare(password, stored);
// PHP/AtomCMS store $2y$; native bcrypt only accepts $2a$/$2b$.
const normalized = stored.replace(/^\$2y\$/, "$2a$");
return await bcryptCompare(password, normalized);
} catch {
return false;
}