Files
Livebeat/backend/lib/crypto.ts
Mondei1 19b7c05d75 Socket connection now works
- Pairing a new device works

(I did a lot since the last commit)
2021-11-14 01:42:21 +01:00

116 lines
4.2 KiB
TypeScript

import { hash, verify } from 'argon2';
import { verify as jwtVerify } from 'jsonwebtoken';
import { config } from '../config';
import { IS_DEBUG, JWT_SECRET, logger } from '../app';
export async function hashPassword(input: string): Promise<string> {
const start = Date.now();
return new Promise<string>(async (resolve, reject) => {
// Get real password (remove salt and pepper)
const realPassword = input.substring(0, input.length - config.authentification.salt_length - 1);
if (!check_password_requirements(realPassword)) {
if (!IS_DEBUG) reject("This password does not meet the minimum requirements!");
else {
reject(`This password does not meet the minimum requirements! (${input} => ${realPassword})`);
}
return;
}
try {
const hashed = await hash(input, {
hashLength: 42,
memoryCost: 1024*32,
parallelism: 2,
timeCost: config.authentification.argonTimecost
});
const finished = Date.now();
logger.debug("Hashing took " + (finished-start) + "ms to finish! " + input);
resolve(hashed);
} catch(err) {
reject(err);
}
});
}
/**
* Verfiy If given password matches with provided hash by brute forcing the pepper.
* @param password Password with salt appended
* @param hashInput Hash from database
*/
export async function verifyPassword(password: string, hashInput: string): Promise<boolean> {
return new Promise<boolean>(async (resolve, reject) => {
const start = Date.now();
const peppers = config.authentification.pepper.split('');
const realPassword = password.substring(0, password.length - config.authentification.salt_length);
while (peppers.length !== 0) {
const pepper = peppers[Math.floor(Math.random()*peppers.length)];
if (IS_DEBUG) {
logger.debug(`Try ${password}}${pepper} (left: ${peppers})`);
} else {
// Show censored (with fixed length) password in non-debug mode to prevent password leaking.
logger.debug(`Try ********${password.replace(realPassword, '')}${pepper} (left: ${peppers})`);
}
if (await verify(hashInput, password + pepper)) {
const finished = Date.now();
logger.debug("Verifying took " + (finished-start) + "ms to complete!");
resolve(true);
return;
} else {
peppers.splice(peppers.indexOf(pepper), 1);
}
}
resolve(false);
});
}
export async function verifyJWT(token: string): Promise<boolean> {
return new Promise<boolean>(async (resolve, reject) => {
try {
jwtVerify(token, JWT_SECRET, { algorithms: ['HS256'] });
resolve(true);
} catch {
resolve(false);
}
});
}
export function randomString(length: number, characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'): string {
let result = '';
const charactersLength = characters.length;
for ( let i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
export function randomPepper(): string {
return config.authentification.pepper.charAt(Math.floor(Math.random() * config.authentification.pepper.length));
}
/**
* Requirements are:
* - at least 8 characters
* - min one upper-, lowercase- and special character
* - min one number
*/
export function check_password_requirements(password: string): boolean {
// Check for length
if (password.length < config.authentification.minPasswordLength ||
password.length > config.authentification.maxPasswordLength) return false;
// Check for one lowercase
if (!(/^(?=.*[a-z]).+$/.test(password))) return false;
// Check for one uppercase
if (!(/^(?=.*[A-Z]).+$/.test(password))) return false;
// Check for one uppercase
if (!(/^(?=.*[0-9]).+$/.test(password))) return false;
// Check for special characters
if (!(/[^A-Za-z0-9]/.test(password))) return false;
return true;
}