2023-03-03 15:54:28 +00:00
|
|
|
// const bcrypt = require("bcrypt");
|
2023-06-27 16:23:18 +00:00
|
|
|
import bcrypt from 'bcryptjs';
|
2024-03-14 19:25:27 +00:00
|
|
|
// import bcrypt from 'bcrypt/bcrypt.js'
|
|
|
|
import { HashDriverContract, ManagerDriverFactory } from '@adonisjs/core/types/hash';
|
|
|
|
import { EnumValidator, RangeValidator, PhcFormatter } from './helpers.js';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Config accepted by the hash driver
|
|
|
|
*/
|
|
|
|
export type PbkdfConfig = {
|
|
|
|
rounds: number;
|
|
|
|
saltSize?: number;
|
|
|
|
version?: number;
|
2024-04-23 17:36:45 +00:00
|
|
|
};
|
2023-03-03 15:54:28 +00:00
|
|
|
|
|
|
|
const saltRounds = 10;
|
2024-03-14 19:25:27 +00:00
|
|
|
export class LaravelDriver implements HashDriverContract {
|
|
|
|
private config: PbkdfConfig;
|
|
|
|
// private binding;
|
|
|
|
private phcFormatter = new PhcFormatter();
|
|
|
|
|
|
|
|
constructor(config: {}) {
|
|
|
|
this.config = {
|
|
|
|
rounds: 10,
|
|
|
|
saltSize: 16,
|
2024-04-23 17:36:45 +00:00
|
|
|
version: 98,
|
|
|
|
...config,
|
2024-03-14 19:25:27 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-03 15:54:28 +00:00
|
|
|
public async make(value: string) {
|
2023-07-17 17:13:30 +00:00
|
|
|
const hashedValue = bcrypt.hashSync(value, saltRounds);
|
|
|
|
return hashedValue;
|
2023-03-03 15:54:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async verify(hashedValue: string, plainValue: string) {
|
|
|
|
let newHash: string;
|
2023-06-27 16:23:18 +00:00
|
|
|
if (hashedValue.includes('$2y$10$')) {
|
|
|
|
newHash = hashedValue.replace('$2y$10$', '$2a$10$');
|
2023-03-03 15:54:28 +00:00
|
|
|
} else {
|
|
|
|
newHash = hashedValue;
|
|
|
|
}
|
|
|
|
return await bcrypt.compareSync(plainValue, newHash);
|
|
|
|
}
|
2024-03-14 19:25:27 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the value is a valid hash. This method just checks
|
|
|
|
* for the formatting of the hash.
|
|
|
|
*
|
|
|
|
* ```ts
|
|
|
|
* bcrypt.isValidHash('hello world') // false
|
|
|
|
* bcrypt.isValidHash('$bcrypt$v=98$r=10$Jtxi46WJ26OQ0khsYLLlnw$knXGfuRFsSjXdj88JydPOnUIglvm1S8')
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
public isValidHash(value: string) {
|
|
|
|
try {
|
|
|
|
this.validatePhcString(value);
|
|
|
|
return true;
|
|
|
|
} catch {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public needsReHash(value: string): boolean {
|
|
|
|
if (value.startsWith('$2b') || value.startsWith('$2a')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const phcNode = this.phcFormatter.deserialize(value);
|
|
|
|
if (phcNode.id !== 'bcrypt') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (phcNode.version !== this.config.version) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (!phcNode.params) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (phcNode.params.r !== this.config.rounds) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate phc hash string
|
|
|
|
*/
|
|
|
|
private validatePhcString(phcString: string) {
|
|
|
|
const phcNode = this.phcFormatter.deserialize(phcString);
|
|
|
|
if (!phcNode.version) {
|
|
|
|
phcNode.version = 97;
|
|
|
|
}
|
|
|
|
if (phcNode.id !== 'bcrypt') {
|
|
|
|
throw new TypeError(`Invalid "id" found in the phc string`);
|
|
|
|
}
|
|
|
|
if (!phcNode.params) {
|
|
|
|
throw new TypeError(`No "params" found in the phc string`);
|
|
|
|
}
|
|
|
|
if (!phcNode.salt) {
|
|
|
|
throw new TypeError(`No "salt" found in the phc string`);
|
|
|
|
}
|
|
|
|
if (!phcNode.hash) {
|
|
|
|
throw new TypeError(`No "hash" found in the phc string`);
|
|
|
|
}
|
|
|
|
if (!phcNode.hash.byteLength) {
|
|
|
|
throw new TypeError(`No "hash" found in the phc string`);
|
|
|
|
}
|
|
|
|
RangeValidator.validate('salt.byteLength', phcNode.salt.byteLength, [8, 1024]);
|
|
|
|
EnumValidator.validate('version', phcNode.version, [97, 98]);
|
|
|
|
RangeValidator.validate('r', phcNode.params.r, [4, 31]);
|
|
|
|
return {
|
|
|
|
id: phcNode.id,
|
|
|
|
version: phcNode.version,
|
|
|
|
hash: phcNode.hash,
|
|
|
|
salt: phcNode.salt,
|
|
|
|
params: {
|
|
|
|
r: phcNode.params.r,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dynamically importing underlying binding
|
|
|
|
*/
|
|
|
|
// private async importBinding() {
|
|
|
|
// if (this.binding) {
|
|
|
|
// return this.binding;
|
|
|
|
// }
|
|
|
|
// this.binding = await import('bcrypt');
|
|
|
|
// return this.binding;
|
|
|
|
// }
|
2023-06-27 16:23:18 +00:00
|
|
|
}
|
2024-03-14 19:25:27 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Factory function to reference the driver
|
|
|
|
* inside the config file.
|
|
|
|
*/
|
2024-04-23 17:36:45 +00:00
|
|
|
export function laravelDriver(config: PbkdfConfig): ManagerDriverFactory {
|
2024-03-14 19:25:27 +00:00
|
|
|
return () => {
|
2024-04-23 17:36:45 +00:00
|
|
|
return new LaravelDriver(config);
|
|
|
|
};
|
|
|
|
}
|