forked from Yara724/api
69 lines
1.6 KiB
TypeScript
69 lines
1.6 KiB
TypeScript
import {
|
|
BinaryLike,
|
|
randomBytes,
|
|
scrypt,
|
|
ScryptOptions,
|
|
timingSafeEqual,
|
|
} from "node:crypto";
|
|
import { promisify } from "node:util";
|
|
import { Injectable } from "@nestjs/common";
|
|
import { ConfigService } from "@nestjs/config";
|
|
import { KEY_LENGTH, SCRYPT_PARAMS } from "../constants/hash.constants";
|
|
|
|
const scryptAsync = promisify<
|
|
BinaryLike,
|
|
BinaryLike,
|
|
number,
|
|
ScryptOptions,
|
|
Buffer
|
|
>(scrypt);
|
|
|
|
@Injectable()
|
|
export class HashService {
|
|
private readonly pepper: string;
|
|
|
|
constructor(private readonly configService: ConfigService) {
|
|
this.pepper = this.configService.get<string>("HASH_PEPPER");
|
|
}
|
|
|
|
private applyPepper(input: string): string {
|
|
return this.pepper ? `${input}${this.pepper}` : input;
|
|
}
|
|
|
|
private generateSalt(bytes: number = 16): string {
|
|
return randomBytes(bytes).toString("hex");
|
|
}
|
|
|
|
private async scrypt(password: string): Promise<[string, string]> {
|
|
const salt = this.generateSalt();
|
|
const pepperedInput = this.applyPepper(password);
|
|
const hashedPassword = await scryptAsync(
|
|
pepperedInput,
|
|
salt,
|
|
KEY_LENGTH,
|
|
SCRYPT_PARAMS,
|
|
);
|
|
|
|
return [salt, hashedPassword.toString("hex")];
|
|
}
|
|
|
|
private async verifyScrypt(
|
|
password: string,
|
|
salt: string,
|
|
hashedPassword: string,
|
|
): Promise<boolean> {
|
|
const pepperedInput = this.applyPepper(password);
|
|
const derived = await scryptAsync(
|
|
pepperedInput,
|
|
salt,
|
|
KEY_LENGTH,
|
|
SCRYPT_PARAMS,
|
|
);
|
|
const storedBuffer = Buffer.from(hashedPassword, "hex");
|
|
|
|
if (derived.length !== storedBuffer.length) return false;
|
|
|
|
return timingSafeEqual(derived, storedBuffer);
|
|
}
|
|
}
|