<commit_before>import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
import { Db } from 'mongodb';
import { promisify } from 'util';
import * as cryptoUtils from './cryptoUtils';
import * as mail from './mail';
import * as users from './users';

// Passwords not allowed
export const passwordBlacklist = [
	'', // Empty string
	'Nick is not a nerd' // Because he is
];

/**
 * Checks whether a plaintext password matches with the user's hashed password.
 * @param db Database connection.
 * @param user Username.
 * @param password Plaintext password.
 * @returns Whether the password matched and whether the user's account is confirmed.
 */
export async function passwordMatches(db: Db, user: string, password: string) {
	if (typeof db !== 'object') { throw new Error('Invalid database connection!'); }
	if (typeof password !== 'string') { throw new Error('Invalid password!'); }

	const { isUser, userDoc } = await users.get(db, user);
	// If invalid user, we just want to say username / password doesn't match
	if (!isUser) { return { matches: false, confirmed: false }; }

	const hash = userDoc!.password;

	let res: boolean;
	try {
		res = await promisify(bcrypt.compare)(password, hash);
	} catch (e) {
		throw new Error('There was a problem comparing the passwords!');
	}

	return { matches: res, confirmed: !!userDoc!.confirmed };
}

/**
 * Changes a user's password if they still know their old password.
 * @param db Database connection.
 * @param user Username.
 * @param oldPassword The user's current/old password.
 * @param newPassword The new password to set.
 */
export async function changePassword(db: Db, user: string, oldPassword: string, newPassword: string) {
	if (typeof db !== 'object') { throw new Error('Invalid database connection!'); }
	if (typeof newPassword !== 'string') { throw new Error('Invalid new password!'); }

	if (typeof oldPassword !== 'string' || passwordBlacklist.includes(newPassword)) {
		throw new Error('Invalid old password!');
	}

	const { isUser } = await users.get(db, user);
	if (!isUser) { throw new Error('Invalid user!'); }

	// Compare oldPassword with password in database
	const { matches } = await passwordMatches(db, user, oldPassword);
	if (!matches) { throw new Error('Password does not match!'); }

	const hash = await cryptoUtils.hashPassword(newPassword);

	// Update new password into database
	const userdata = db.collection('users');

	try {
		await userdata.updateOne({ user }, { $set: { password: hash }, $currentDate: { lastPasswordChange: true } });
	} catch (e) {
		throw new Error('There was a problem updating the password in the database!');
	}
}

/**
 * Creates a reset hash and sends a reset password email.
 * @param db Database connection.
 * @param user Username.
 */
export async function resetPasswordEmail(db: Db, user: string) {
	if (typeof db !== 'object') { throw new Error('Invalid database connection!'); }

	const { isUser, userDoc } = await users.get(db, user);
	if (!isUser) { throw new Error('User doesn\'t exist!'); }

	// Generate password hash for confirmation link
	let buf: Buffer;
	try {
		buf = await promisify(crypto.randomBytes)(16);
	} catch (e) {
		throw new Error('There was a problem generating a random confirmation hash!');
	}

	const hash = buf.toString('hex');
	const hashedHash = cryptoUtils.shaHash(hash);

	// Now let's insert the passwordChangeHash into the database
	const userdata = db.collection('users');

	try {
		await userdata.updateOne({
			_id: userDoc!._id,
			user: userDoc!.user
		}, { $set: { passwordChangeHash: hashedHash } }, { upsert: true });
	} catch (e) {<commit_msg>don't rehash password change (fixes the hotfix lol)<commit_after>import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
import { Db } from 'mongodb';
import { promisify } from 'util';
import * as cryptoUtils from './cryptoUtils';
import * as mail from './mail';
import * as users from './users';

// Passwords not allowed
export const passwordBlacklist = [
	'', // Empty string
	'Nick is not a nerd' // Because he is
];

/**
 * Checks whether a plaintext password matches with the user's hashed password.
 * @param db Database connection.
 * @param user Username.
 * @param password Plaintext password.
 * @returns Whether the password matched and whether the user's account is confirmed.
 */
export async function passwordMatches(db: Db, user: string, password: string) {
	if (typeof db !== 'object') { throw new Error('Invalid database connection!'); }
	if (typeof password !== 'string') { throw new Error('Invalid password!'); }

	const { isUser, userDoc } = await users.get(db, user);
	// If invalid user, we just want to say username / password doesn't match
	if (!isUser) { return { matches: false, confirmed: false }; }

	const hash = userDoc!.password;

	let res: boolean;
	try {
		res = await promisify(bcrypt.compare)(password, hash);
	} catch (e) {
		throw new Error('There was a problem comparing the passwords!');
	}

	return { matches: res, confirmed: !!userDoc!.confirmed };
}

/**
 * Changes a user's password if they still know their old password.
 * @param db Database connection.
 * @param user Username.
 * @param oldPassword The user's current/old password.
 * @param newPassword The new password to set.
 */
export async function changePassword(db: Db, user: string, oldPassword: string, newPassword: string) {
	if (typeof db !== 'object') { throw new Error('Invalid database connection!'); }
	if (typeof newPassword !== 'string') { throw new Error('Invalid new password!'); }

	if (typeof oldPassword !== 'string' || passwordBlacklist.includes(newPassword)) {
		throw new Error('Invalid old password!');
	}

	const { isUser } = await users.get(db, user);
	if (!isUser) { throw new Error('Invalid user!'); }

	// Compare oldPassword with password in database
	const { matches } = await passwordMatches(db, user, oldPassword);
	if (!matches) { throw new Error('Password does not match!'); }

	const hash = await cryptoUtils.hashPassword(newPassword);

	// Update new password into database
	const userdata = db.collection('users');

	try {
		await userdata.updateOne({ user }, { $set: { password: hash }, $currentDate: { lastPasswordChange: true } });
	} catch (e) {
		throw new Error('There was a problem updating the password in the database!');
	}
}

/**
 * Creates a reset hash and sends a reset password email.
 * @param db Database connection.
 * @param user Username.
 */
export async function resetPasswordEmail(db: Db, user: string) {
	if (typeof db !== 'object') { throw new Error('Invalid database connection!'); }

	const { isUser, userDoc } = await users.get(db, user);
	if (!isUser) { throw new Error('User doesn\'t exist!'); }

	// Generate password hash for confirmation link
	let buf: Buffer;
	try {
		buf = await promisify(crypto.randomBytes)(16);
	} catch (e) {
		throw new Error('There was a problem generating a random confirmation hash!');
	}

	const hash = buf.toString('hex');

	// Now let's insert the passwordChangeHash into the database
	const userdata = db.collection('users');

	try {
		await userdata.updateOne({
			_id: userDoc!._id,
			user: userDoc!.user
		}, { $set: { passwordChangeHash: hash } }, { upsert: true });
	} catch (e) {
{'fraction_non_alphanumeric': 0.1313929313929314, 'fraction_numerical': 0.0005544005544005544, 'mean_word_length': 4.236574746008708, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5485874', 'n_tokens_mistral': 2142, 'n_tokens_neox': 2045, 'n_words': 806}