perf: replace bcryptjs with native bcrypt for password hashing
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
830d252346
commit
d2120987b0
6 files changed
+47
-18
No files matched your search
+2
-1
@@ -32,7 +32,7 @@
|
||||
"@prisma/client": "^7.9.0",
|
||||
"@sentry/nextjs": "^10.67.0",
|
||||
"@tanstack/react-virtual": "^3.14.6",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bcrypt": "^6.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
@@ -73,6 +73,7 @@
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/postcss": "^4.3.3",
|
||||
"@tailwindcss/typography": "^0.5.20",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/node": "^26.1.1",
|
||||
"@types/nodemailer": "^7.0.12",
|
||||
"@types/react": "^19.2.17",
|
||||
|
||||
Generated
+32
-7
@@ -40,9 +40,9 @@ importers:
|
||||
'@tanstack/react-virtual':
|
||||
specifier: ^3.14.6
|
||||
version: 3.14.6([email protected]([email protected]))([email protected])
|
||||
bcryptjs:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.3
|
||||
bcrypt:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@@ -158,6 +158,9 @@ importers:
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.20
|
||||
version: 0.5.20([email protected])
|
||||
'@types/bcrypt':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
'@types/node':
|
||||
specifier: ^26.1.1
|
||||
version: 26.1.1
|
||||
@@ -2374,6 +2377,9 @@ packages:
|
||||
'@tybys/[email protected]':
|
||||
resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
|
||||
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==}
|
||||
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||
|
||||
@@ -2750,9 +2756,9 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
bcryptjs@3.0.3:
|
||||
resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==}
|
||||
hasBin: true
|
||||
bcrypt@6.0.0:
|
||||
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==}
|
||||
@@ -3640,6 +3646,10 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
@@ -3649,6 +3659,10 @@ packages:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -6117,6 +6131,10 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/[email protected]':
|
||||
dependencies:
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@types/[email protected]':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
@@ -6500,7 +6518,10 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
bcrypt[email protected]: {}
|
||||
bcrypt@6.0.0:
|
||||
dependencies:
|
||||
node-addon-api: 8.9.0
|
||||
node-gyp-build: 4.8.4
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -7291,10 +7312,14 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
@@ -7,6 +7,7 @@ onlyBuiltDependencies:
|
||||
- "@parcel/watcher"
|
||||
- "@swc/core"
|
||||
- "@sentry/cli"
|
||||
- bcrypt
|
||||
|
||||
overrides:
|
||||
fast-uri: "^3.1.3"
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user