more secure password hashing

main
Cogent Apps 2023-03-17 18:57:32 +00:00
parent b0d8d5c20d
commit b04e136ee0
4 changed files with 29 additions and 25 deletions

View File

@ -10,6 +10,7 @@
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.282.0",
"@types/bcrypt": "^5.0.0",
"@types/compression": "^1.7.2",
"@types/connect-sqlite3": "^0.9.2",
"@types/debug": "^4.1.7",
@ -23,6 +24,7 @@
"@types/pg": "^8.6.6",
"@types/sqlite3": "^3.1.8",
"axios": "^1.3.4",
"bcrypt": "^5.1.0",
"compression": "^1.7.4",
"connect-sqlite3": "^0.9.13",
"debug": "^4.3.4",

View File

@ -1,11 +1,11 @@
export default abstract class Database {
public async initialize() {}
public abstract createUser(email: string, passwordHash: Buffer, salt: Buffer): Promise<void>;
public abstract createUser(email: string, passwordHash: Buffer): Promise<void>;
public abstract getUser(email: string): Promise<{
id: string;
email: string;
passwordHash: Buffer;
salt: Buffer;
salt: Buffer | null;
}>;
public abstract getChats(userID: string): Promise<any[]>;
public abstract getMessages(userID: string): Promise<any[]>;

View File

@ -61,14 +61,14 @@ export class SQLiteAdapter extends Database {
});
}
public createUser(email: string, passwordHash: Buffer, salt: Buffer): Promise<void> {
public createUser(email: string, passwordHash: Buffer): Promise<void> {
return new Promise((resolve, reject) => {
if (!validateEmailAddress(email)) {
reject(new Error('invalid email address'));
return;
}
db.run(`INSERT INTO authentication (id, email, password_hash, salt) VALUES (?, ?, ?, ?)`, [email, email, passwordHash, salt], (err) => {
db.run(`INSERT INTO authentication (id, email, password_hash) VALUES (?, ?, ?)`, [email, email, passwordHash], (err) => {
if (err) {
reject(err);
console.log(`[database:sqlite] failed to create user ${email}`);
@ -90,7 +90,7 @@ export class SQLiteAdapter extends Database {
resolve({
...row,
passwordHash: Buffer.from(row.password_hash),
salt: Buffer.from(row.salt),
salt: row.salt ? Buffer.from(row.salt) : null,
});
console.log(`[database:sqlite] retrieved user ${email}`);
}

View File

@ -1,3 +1,4 @@
import bcrypt from 'bcrypt';
import crypto from 'crypto';
import passport from 'passport';
import session from 'express-session';
@ -18,17 +19,20 @@ export function configurePassport(context: ChatServer) {
return cb(null, false, { message: 'Incorrect username or password.' });
}
crypto.pbkdf2(password, user.salt, 310000, 32, 'sha256', (err, hashedPassword) => {
if (err) {
return cb(err);
}
try {
console.log(user.salt ? 'Using pbkdf2' : 'Using bcrypt');
const isPasswordCorrect = user.salt
? crypto.timingSafeEqual(user.passwordHash, crypto.pbkdf2Sync(password, user.salt, 310000, 32, 'sha256'))
: await bcrypt.compare(password, user.passwordHash.toString());
if (!crypto.timingSafeEqual(user.passwordHash, hashedPassword)) {
if (!isPasswordCorrect) {
return cb(null, false, { message: 'Incorrect username or password.' });
}
return cb(null, user);
});
} catch (e) {
cb(e);
}
}));
passport.serializeUser((user: any, cb: any) => {
@ -58,21 +62,19 @@ export function configurePassport(context: ChatServer) {
context.app.post('/chatapi/register', async (req, res, next) => {
const { username, password } = req.body;
const salt = crypto.randomBytes(32);
crypto.pbkdf2(password, salt, 310000, 32, 'sha256', async (err, hashedPassword) => {
if (err) {
return next(err);
}
try {
await context.database.createUser(username, hashedPassword, salt);
passport.authenticate('local')(req, res, () => {
res.redirect('/');
});
} catch (err) {
res.redirect('/?error=register');
}
});
const hashedPassword = await bcrypt.hash(password, 12);
try {
await context.database.createUser(username, Buffer.from(hashedPassword));
passport.authenticate('local')(req, res, () => {
res.redirect('/');
});
} catch (err) {
console.error(err);
res.redirect('/?error=register');
}
});
context.app.all('/chatapi/logout', (req, res, next) => {