feat(auth): password hashing service

This commit is contained in:
2026-05-20 22:45:21 +02:00
parent afd51571c5
commit 0e6bc8c640
4 changed files with 54 additions and 0 deletions

18
package-lock.json generated
View File

@@ -2005,6 +2005,13 @@
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/better-sqlite3": {
"version": "7.6.13",
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
@@ -2559,6 +2566,15 @@
"node": ">=6.0.0"
}
},
"node_modules/bcryptjs": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
"integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
"license": "BSD-3-Clause",
"bin": {
"bcrypt": "bin/bcrypt"
}
},
"node_modules/better-sqlite3": {
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
@@ -7838,6 +7854,7 @@
"version": "0.1.0",
"dependencies": {
"@flashcard/shared": "*",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^11.0.0",
"drizzle-orm": "^0.33.0",
"express": "^4.19.0",
@@ -7846,6 +7863,7 @@
"zod": "^3.23.0"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.0",
"@types/express": "^4.17.0",
"@types/multer": "^1.4.0",

View File

@@ -17,6 +17,7 @@
},
"dependencies": {
"@flashcard/shared": "*",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^11.0.0",
"drizzle-orm": "^0.33.0",
"express": "^4.19.0",
@@ -25,6 +26,7 @@
"zod": "^3.23.0"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.0",
"@types/express": "^4.17.0",
"@types/multer": "^1.4.0",

View File

@@ -0,0 +1,19 @@
import { describe, it, expect } from 'vitest';
import { hashPassword, verifyPassword } from './passwords.js';
describe('passwords', () => {
it('hashes a password and verifies it', async () => {
const hash = await hashPassword('correcthorse');
expect(hash).toMatch(/^\$2[aby]\$/);
expect(await verifyPassword('correcthorse', hash)).toBe(true);
});
it('rejects a wrong password', async () => {
const hash = await hashPassword('correcthorse');
expect(await verifyPassword('wrong', hash)).toBe(false);
});
it('returns false on malformed hash', async () => {
expect(await verifyPassword('x', 'not-a-bcrypt-hash')).toBe(false);
});
});

View File

@@ -0,0 +1,15 @@
import bcrypt from 'bcryptjs';
const COST = 12;
export async function hashPassword(plain: string): Promise<string> {
return bcrypt.hash(plain, COST);
}
export async function verifyPassword(plain: string, hash: string): Promise<boolean> {
try {
return await bcrypt.compare(plain, hash);
} catch {
return false;
}
}