@adonisjs/hash
Advanced tools
Comparing version 8.3.1-6 to 8.3.1-7
@@ -1,1 +0,31 @@ | ||
export { HashManagerFactory } from './hash_manager.js'; | ||
import { ManagerDriverFactory } from '../src/types.js'; | ||
import { HashManager } from '../index.js'; | ||
import { Scrypt } from '../src/drivers/scrypt.js'; | ||
type Config<KnownHashers extends Record<string, ManagerDriverFactory>> = { | ||
default?: keyof KnownHashers; | ||
list: KnownHashers; | ||
}; | ||
/** | ||
* Hash manager factory is used to create an instance of hash manager | ||
* for testing | ||
*/ | ||
declare class HashManagerFactory<KnownHashers extends Record<string, ManagerDriverFactory> = { | ||
scrypt: () => Scrypt; | ||
}> { | ||
#private; | ||
constructor(config?: { | ||
default?: keyof KnownHashers; | ||
list: KnownHashers; | ||
}); | ||
/** | ||
* Merge factory parameters | ||
*/ | ||
merge<Hashers extends Record<string, ManagerDriverFactory>>(config: Config<Hashers>): HashManagerFactory<Hashers>; | ||
/** | ||
* Create hash manager instance | ||
*/ | ||
create(): HashManager<KnownHashers>; | ||
} | ||
export { HashManagerFactory }; |
@@ -1,9 +0,40 @@ | ||
/* | ||
* @adonisjs/hash | ||
* | ||
* (c) AdonisJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
export { HashManagerFactory } from './hash_manager.js'; | ||
import { | ||
HashManager | ||
} from "../chunk-WTHUFHHU.js"; | ||
import { | ||
Scrypt | ||
} from "../chunk-ZUKNXYT4.js"; | ||
import "../chunk-7RS6HCBK.js"; | ||
import "../chunk-JSA56AE7.js"; | ||
import "../chunk-AXCKF2IS.js"; | ||
// factories/hash_manager.ts | ||
var HashManagerFactory = class _HashManagerFactory { | ||
/** | ||
* Config accepted by hash manager | ||
*/ | ||
#config; | ||
constructor(config) { | ||
this.#config = config || { | ||
default: "scrypt", | ||
list: { | ||
scrypt: () => new Scrypt({}) | ||
} | ||
}; | ||
} | ||
/** | ||
* Merge factory parameters | ||
*/ | ||
merge(config) { | ||
return new _HashManagerFactory(config); | ||
} | ||
/** | ||
* Create hash manager instance | ||
*/ | ||
create() { | ||
return new HashManager(this.#config); | ||
} | ||
}; | ||
export { | ||
HashManagerFactory | ||
}; |
@@ -1,2 +0,109 @@ | ||
export { Hash } from './src/hash.js'; | ||
export { HashManager } from './src/hash_manager.js'; | ||
import { HashDriverContract, ManagerDriverFactory } from './src/types.js'; | ||
/** | ||
* Hash and verify values using a dedicated hash driver. The Hash | ||
* works as an adapter across different drivers. | ||
* | ||
* ```ts | ||
* const hash = new Hash(new Argon()) | ||
* const hashedPassword = await hash.make('secret') | ||
* | ||
* const isValid = await hash.verify(hashedPassword, 'secret') | ||
* console.log(isValid) | ||
* ``` | ||
*/ | ||
declare class Hash implements HashDriverContract { | ||
#private; | ||
constructor(driver: HashDriverContract); | ||
/** | ||
* Check if the value is a valid hash. This method just checks | ||
* for the formatting of the hash | ||
*/ | ||
isValidHash(value: string): boolean; | ||
/** | ||
* Hash plain text value | ||
*/ | ||
make(value: string): Promise<string>; | ||
/** | ||
* Verify the plain text value against an existing hash | ||
*/ | ||
verify(hashedValue: string, plainValue: string): Promise<boolean>; | ||
/** | ||
* Find if the hash value needs a rehash or not. | ||
*/ | ||
needsReHash(hashedValue: string): boolean; | ||
/** | ||
* Assert the plain value passes the hash verification | ||
*/ | ||
assertEquals(hashedValue: string, plainValue: string): Promise<void>; | ||
/** | ||
* Assert the plain value fails the hash verification | ||
*/ | ||
assertNotEquals(hashedValue: string, plainValue: string): Promise<void>; | ||
} | ||
/** | ||
* HashManager implements the manager/builder pattern to create a use multiple | ||
* hashing algorithm without self managing hash instance. | ||
* | ||
* ```ts | ||
* const manager = new HashManager({ | ||
* default: 'argon', | ||
* list: { | ||
* argon: () => new ArgonDriver(), | ||
* bcrypt: () => new BcryptDriver(), | ||
* } | ||
* }) | ||
* ``` | ||
*/ | ||
declare class HashManager<KnownHashers extends Record<string, ManagerDriverFactory>> implements HashDriverContract { | ||
#private; | ||
constructor(config: { | ||
default?: keyof KnownHashers; | ||
list: KnownHashers; | ||
}); | ||
/** | ||
* Use one of the registered hashers to hash values. | ||
* | ||
* ```ts | ||
* manager.use() // returns default hasher | ||
* manager.use('argon') | ||
* ``` | ||
*/ | ||
use<Hasher extends keyof KnownHashers>(hasher?: Hasher): Hash; | ||
/** | ||
* Fake hash drivers to disable hashing values | ||
*/ | ||
fake(): void; | ||
/** | ||
* Restore fake | ||
*/ | ||
restore(): void; | ||
/** | ||
* Check if the value is a valid hash. This method just checks | ||
* for the formatting of the hash | ||
*/ | ||
isValidHash(value: string): boolean; | ||
/** | ||
* Hash plain text value | ||
*/ | ||
make(value: string): Promise<string>; | ||
/** | ||
* Verify the plain text value against an existing hash | ||
*/ | ||
verify(hashedValue: string, plainValue: string): Promise<boolean>; | ||
/** | ||
* Find if the hash value needs a rehash or not. | ||
*/ | ||
needsReHash(hashedValue: string): boolean; | ||
/** | ||
* Assert the plain value passes the hash verification | ||
*/ | ||
assertEquals(hashedValue: string, plainValue: string): Promise<void>; | ||
/** | ||
* Assert the plain value fails the hash verification | ||
*/ | ||
assertNotEquals(hashedValue: string, plainValue: string): Promise<void>; | ||
} | ||
export { Hash, HashManager }; |
@@ -1,10 +0,9 @@ | ||
/* | ||
* @adonisjs/hash | ||
* | ||
* (c) AdonisJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
export { Hash } from './src/hash.js'; | ||
export { HashManager } from './src/hash_manager.js'; | ||
import { | ||
Hash, | ||
HashManager | ||
} from "./chunk-WTHUFHHU.js"; | ||
import "./chunk-AXCKF2IS.js"; | ||
export { | ||
Hash, | ||
HashManager | ||
}; |
@@ -1,2 +0,3 @@ | ||
import type { ArgonConfig, HashDriverContract } from '../types.js'; | ||
import { HashDriverContract, ArgonConfig } from '../types.js'; | ||
/** | ||
@@ -16,3 +17,3 @@ * Hash driver built on top of "argon2" hash algorigthm. Under the hood | ||
*/ | ||
export declare class Argon implements HashDriverContract { | ||
declare class Argon implements HashDriverContract { | ||
#private; | ||
@@ -69,1 +70,3 @@ constructor(config: ArgonConfig); | ||
} | ||
export { Argon }; |
@@ -1,295 +0,241 @@ | ||
/* | ||
* @adonisjs/hash | ||
* | ||
* (c) AdonisJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
import { safeEqual } from '@poppinss/utils'; | ||
import { PhcFormatter } from '../phc_formatter.js'; | ||
import { MAX_UINT24, MAX_UINT32, EnumValidator, RangeValidator, randomBytesAsync, } from '../helpers.js'; | ||
/** | ||
* Hash driver built on top of "argon2" hash algorigthm. Under the hood | ||
* we make use of the "argon2" npm package. | ||
* | ||
* The Argon implementation uses the PHC formatting for creating | ||
* and verifying hashes. | ||
* | ||
* ```ts | ||
* const argon = new Argon({}) | ||
* | ||
* await argon.make('secret') | ||
* // $argon2id$v=19$t=3,m=4096,p=1$drxJBWzWahR5tMubp+a1Sw$L/Oh2uw6QKW77i/KQ8eGuOt3ui52hEmmKlu1KBVBxiM | ||
* ``` | ||
*/ | ||
export class Argon { | ||
/** | ||
* Lazily loaded argon2 binding. Since it is a peer dependency | ||
* we cannot import it at top level | ||
*/ | ||
#binding; | ||
/** | ||
* Config with defaults merged | ||
*/ | ||
#config; | ||
/** | ||
* Formatter to serialize and deserialize phc string | ||
*/ | ||
#phcFormatter = new PhcFormatter(); | ||
/** | ||
* Supported variants | ||
*/ | ||
#variants = { | ||
i: 0, | ||
d: 1, | ||
id: 2, | ||
import { | ||
EnumValidator, | ||
MAX_UINT24, | ||
MAX_UINT32, | ||
RangeValidator, | ||
randomBytesAsync | ||
} from "../../chunk-7RS6HCBK.js"; | ||
import { | ||
PhcFormatter | ||
} from "../../chunk-JSA56AE7.js"; | ||
import "../../chunk-AXCKF2IS.js"; | ||
// src/drivers/argon.ts | ||
import { safeEqual } from "@poppinss/utils"; | ||
var Argon = class { | ||
/** | ||
* Lazily loaded argon2 binding. Since it is a peer dependency | ||
* we cannot import it at top level | ||
*/ | ||
#binding; | ||
/** | ||
* Config with defaults merged | ||
*/ | ||
#config; | ||
/** | ||
* Formatter to serialize and deserialize phc string | ||
*/ | ||
#phcFormatter = new PhcFormatter(); | ||
/** | ||
* Supported variants | ||
*/ | ||
#variants = { | ||
i: 0, | ||
d: 1, | ||
id: 2 | ||
}; | ||
/** | ||
* A list of supported argon ids | ||
*/ | ||
#ids = ["argon2d", "argon2i", "argon2id"]; | ||
constructor(config) { | ||
this.#config = { | ||
version: 19, | ||
variant: "id", | ||
iterations: 3, | ||
memory: 65536, | ||
parallelism: 4, | ||
saltSize: 16, | ||
hashLength: 32, | ||
...config | ||
}; | ||
/** | ||
* A list of supported argon ids | ||
*/ | ||
#ids = ['argon2d', 'argon2i', 'argon2id']; | ||
constructor(config) { | ||
this.#config = { | ||
version: 0x13, | ||
variant: 'id', | ||
iterations: 3, | ||
memory: 65536, | ||
parallelism: 4, | ||
saltSize: 16, | ||
hashLength: 32, | ||
...config, | ||
}; | ||
this.#validateConfig(); | ||
this.#validateConfig(); | ||
} | ||
/** | ||
* Dynamically importing underlying binding | ||
*/ | ||
async #importBinding() { | ||
if (this.#binding) { | ||
return this.#binding; | ||
} | ||
/** | ||
* Dynamically importing underlying binding | ||
*/ | ||
async #importBinding() { | ||
if (this.#binding) { | ||
return this.#binding; | ||
} | ||
this.#binding = await import('argon2'); | ||
return this.#binding; | ||
this.#binding = await import("argon2"); | ||
return this.#binding; | ||
} | ||
/** | ||
* Validate configuration options | ||
*/ | ||
#validateConfig() { | ||
RangeValidator.validate("iterations", this.#config.iterations, [2, MAX_UINT32]); | ||
RangeValidator.validate("parallelism", this.#config.parallelism, [1, MAX_UINT24]); | ||
RangeValidator.validate("memory", this.#config.memory, [ | ||
8 * this.#config.parallelism, | ||
MAX_UINT32 | ||
]); | ||
EnumValidator.validate("variant", this.#config.variant, Object.keys(this.#variants)); | ||
RangeValidator.validate("saltSize", this.#config.saltSize, [8, 1024]); | ||
RangeValidator.validate("hashLength", this.#config.hashLength, [4, MAX_UINT32]); | ||
EnumValidator.validate("version", this.#config.version, [16, 19]); | ||
Object.freeze(this.#config); | ||
} | ||
/** | ||
* Validate phc hash string | ||
*/ | ||
#validatePhcString(phcString) { | ||
const phcNode = this.#phcFormatter.deserialize(phcString); | ||
if (!phcNode.version) { | ||
phcNode.version = 16; | ||
} | ||
/** | ||
* Validate configuration options | ||
*/ | ||
#validateConfig() { | ||
RangeValidator.validate('iterations', this.#config.iterations, [2, MAX_UINT32]); | ||
RangeValidator.validate('parallelism', this.#config.parallelism, [1, MAX_UINT24]); | ||
RangeValidator.validate('memory', this.#config.memory, [ | ||
8 * this.#config.parallelism, | ||
MAX_UINT32, | ||
]); | ||
EnumValidator.validate('variant', this.#config.variant, Object.keys(this.#variants)); | ||
RangeValidator.validate('saltSize', this.#config.saltSize, [8, 1024]); | ||
RangeValidator.validate('hashLength', this.#config.hashLength, [4, MAX_UINT32]); | ||
EnumValidator.validate('version', this.#config.version, [0x10, 0x13]); | ||
Object.freeze(this.#config); | ||
if (!phcNode.params) { | ||
throw new TypeError(`No "params" found in the phc string`); | ||
} | ||
/** | ||
* Validate phc hash string | ||
*/ | ||
#validatePhcString(phcString) { | ||
const phcNode = this.#phcFormatter.deserialize(phcString); | ||
/** | ||
* Old argon strings without version | ||
*/ | ||
if (!phcNode.version) { | ||
phcNode.version = 0x10; | ||
} | ||
/** | ||
* Validate top level properties to exist | ||
*/ | ||
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`); | ||
} | ||
RangeValidator.validate('salt.byteLength', phcNode.salt.byteLength, [8, 1024]); | ||
RangeValidator.validate('hash.byteLength', phcNode.hash.byteLength, [4, MAX_UINT32]); | ||
/** | ||
* Validate id | ||
*/ | ||
EnumValidator.validate('id', phcNode.id, this.#ids); | ||
/** | ||
* Validate variant and extract it | ||
*/ | ||
const variant = phcNode.id.split('argon2')[1]; | ||
EnumValidator.validate('variant', variant, Object.keys(this.#variants)); | ||
/** | ||
* Validate rest of the properties | ||
*/ | ||
EnumValidator.validate('version', phcNode.version, [0x10, 0x13]); | ||
RangeValidator.validate('t', phcNode.params.t, [1, MAX_UINT32]); | ||
RangeValidator.validate('p', phcNode.params.p, [1, MAX_UINT24]); | ||
RangeValidator.validate('m', phcNode.params.m, [8 * phcNode.params.p, MAX_UINT32]); | ||
return { | ||
id: phcNode.id, | ||
version: phcNode.version, | ||
hash: phcNode.hash, | ||
salt: phcNode.salt, | ||
params: { | ||
t: phcNode.params.t, | ||
m: phcNode.params.m, | ||
p: phcNode.params.p, | ||
}, | ||
variant: variant, | ||
}; | ||
if (!phcNode.salt) { | ||
throw new TypeError(`No "salt" found in the phc string`); | ||
} | ||
/** | ||
* Check if the value is a valid hash. This method just checks | ||
* for the formatting of the hash. | ||
* | ||
* ```ts | ||
* argon.isValidHash('hello world') // false | ||
* argon.isValidHash('$argon2id$v=19$t=3,m=4096,p=1$drxJBWzWahR5tMubp+a1Sw$L/Oh2uw6QKW77i/KQ8eGuOt3ui52hEmmKlu1KBVBxiM') | ||
* ``` | ||
*/ | ||
isValidHash(value) { | ||
try { | ||
this.#validatePhcString(value); | ||
return true; | ||
} | ||
catch { | ||
return false; | ||
} | ||
if (!phcNode.hash) { | ||
throw new TypeError(`No "hash" found in the phc string`); | ||
} | ||
/** | ||
* Hash a plain text value | ||
* | ||
* ```ts | ||
* const hash = await argon.make('password') | ||
* ``` | ||
*/ | ||
async make(value) { | ||
const driver = await this.#importBinding(); | ||
const salt = await randomBytesAsync(this.#config.saltSize); | ||
/** | ||
* Generate hash | ||
*/ | ||
const hash = await driver.hash(value, { | ||
salt, | ||
version: this.#config.version, | ||
type: this.#variants[this.#config.variant], | ||
timeCost: this.#config.iterations, | ||
memoryCost: this.#config.memory, | ||
parallelism: this.#config.parallelism, | ||
hashLength: this.#config.hashLength, | ||
raw: true, | ||
}); | ||
/** | ||
* Serialize hash | ||
*/ | ||
return this.#phcFormatter.serialize(salt, hash, { | ||
id: `argon2${this.#config.variant}`, | ||
version: this.#config.version, | ||
params: { | ||
t: this.#config.iterations, | ||
m: this.#config.memory, | ||
p: this.#config.parallelism, | ||
}, | ||
}); | ||
RangeValidator.validate("salt.byteLength", phcNode.salt.byteLength, [8, 1024]); | ||
RangeValidator.validate("hash.byteLength", phcNode.hash.byteLength, [4, MAX_UINT32]); | ||
EnumValidator.validate("id", phcNode.id, this.#ids); | ||
const variant = phcNode.id.split("argon2")[1]; | ||
EnumValidator.validate("variant", variant, Object.keys(this.#variants)); | ||
EnumValidator.validate("version", phcNode.version, [16, 19]); | ||
RangeValidator.validate("t", phcNode.params.t, [1, MAX_UINT32]); | ||
RangeValidator.validate("p", phcNode.params.p, [1, MAX_UINT24]); | ||
RangeValidator.validate("m", phcNode.params.m, [8 * phcNode.params.p, MAX_UINT32]); | ||
return { | ||
id: phcNode.id, | ||
version: phcNode.version, | ||
hash: phcNode.hash, | ||
salt: phcNode.salt, | ||
params: { | ||
t: phcNode.params.t, | ||
m: phcNode.params.m, | ||
p: phcNode.params.p | ||
}, | ||
variant | ||
}; | ||
} | ||
/** | ||
* Check if the value is a valid hash. This method just checks | ||
* for the formatting of the hash. | ||
* | ||
* ```ts | ||
* argon.isValidHash('hello world') // false | ||
* argon.isValidHash('$argon2id$v=19$t=3,m=4096,p=1$drxJBWzWahR5tMubp+a1Sw$L/Oh2uw6QKW77i/KQ8eGuOt3ui52hEmmKlu1KBVBxiM') | ||
* ``` | ||
*/ | ||
isValidHash(value) { | ||
try { | ||
this.#validatePhcString(value); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
/** | ||
* Verify the plain text value against an existing hash | ||
* | ||
* ```ts | ||
* if (await argon.verify(hash, plainText)) { | ||
* | ||
* } | ||
* ``` | ||
*/ | ||
async verify(hashedValue, plainValue) { | ||
const driver = await this.#importBinding(); | ||
try { | ||
/** | ||
* De-serialize hash and ensure all Phc string properties | ||
* to exist. | ||
*/ | ||
const phcNode = this.#validatePhcString(hashedValue); | ||
/** | ||
* Generate a new hash with the same properties | ||
* as the existing hash | ||
*/ | ||
const newHash = await driver.hash(plainValue, { | ||
salt: phcNode.salt, | ||
version: phcNode.version, | ||
type: this.#variants[phcNode.variant], | ||
timeCost: phcNode.params.t, | ||
memoryCost: phcNode.params.m, | ||
parallelism: phcNode.params.p, | ||
hashLength: phcNode.hash.byteLength, | ||
raw: true, | ||
}); | ||
/** | ||
* Ensure both are equal | ||
*/ | ||
return safeEqual(newHash, phcNode.hash); | ||
} | ||
catch { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Hash a plain text value | ||
* | ||
* ```ts | ||
* const hash = await argon.make('password') | ||
* ``` | ||
*/ | ||
async make(value) { | ||
const driver = await this.#importBinding(); | ||
const salt = await randomBytesAsync(this.#config.saltSize); | ||
const hash = await driver.hash(value, { | ||
salt, | ||
version: this.#config.version, | ||
type: this.#variants[this.#config.variant], | ||
timeCost: this.#config.iterations, | ||
memoryCost: this.#config.memory, | ||
parallelism: this.#config.parallelism, | ||
hashLength: this.#config.hashLength, | ||
raw: true | ||
}); | ||
return this.#phcFormatter.serialize(salt, hash, { | ||
id: `argon2${this.#config.variant}`, | ||
version: this.#config.version, | ||
params: { | ||
t: this.#config.iterations, | ||
m: this.#config.memory, | ||
p: this.#config.parallelism | ||
} | ||
}); | ||
} | ||
/** | ||
* Verify the plain text value against an existing hash | ||
* | ||
* ```ts | ||
* if (await argon.verify(hash, plainText)) { | ||
* | ||
* } | ||
* ``` | ||
*/ | ||
async verify(hashedValue, plainValue) { | ||
const driver = await this.#importBinding(); | ||
try { | ||
const phcNode = this.#validatePhcString(hashedValue); | ||
const newHash = await driver.hash(plainValue, { | ||
salt: phcNode.salt, | ||
version: phcNode.version, | ||
type: this.#variants[phcNode.variant], | ||
timeCost: phcNode.params.t, | ||
memoryCost: phcNode.params.m, | ||
parallelism: phcNode.params.p, | ||
hashLength: phcNode.hash.byteLength, | ||
raw: true | ||
}); | ||
return safeEqual(newHash, phcNode.hash); | ||
} catch { | ||
return false; | ||
} | ||
/** | ||
* Find if the hash value needs a rehash or not. The rehash is | ||
* required when. | ||
* | ||
* 1. The argon2 version is changed | ||
* 2. Number of iterations are changed | ||
* 3. The memory value is changed | ||
* 4. The parellelism value is changed | ||
* 5. The argon variant is changed | ||
* | ||
* ```ts | ||
* const isValid = await argon.verify(hash, plainText) | ||
* | ||
* // Plain password is valid and hash needs a rehash | ||
* if (isValid && await argon.needsReHash(hash)) { | ||
* const newHash = await argon.make(plainText) | ||
* } | ||
* ``` | ||
*/ | ||
needsReHash(value) { | ||
const phcNode = this.#phcFormatter.deserialize(value); | ||
if (!this.#ids.includes(phcNode.id)) { | ||
throw new TypeError('Value is not a valid argon hash'); | ||
} | ||
/** | ||
* If config version is separate from hash version, then a | ||
* re-hash is needed | ||
*/ | ||
if (phcNode.version !== this.#config.version) { | ||
return true; | ||
} | ||
/** | ||
* If config variant is not same as the hash variant, then a | ||
* re-hash is needed | ||
*/ | ||
if (phcNode.id !== `argon2${this.#config.variant}`) { | ||
return true; | ||
} | ||
/** | ||
* Make sure all the encoded params are same as the config. | ||
* Otherwise a re-hash is needed | ||
*/ | ||
if (!phcNode.params) { | ||
return true; | ||
} | ||
if (phcNode.params.m !== this.#config.memory) { | ||
return true; | ||
} | ||
if (phcNode.params.t !== this.#config.iterations) { | ||
return true; | ||
} | ||
if (phcNode.params.p !== this.#config.parallelism) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
/** | ||
* Find if the hash value needs a rehash or not. The rehash is | ||
* required when. | ||
* | ||
* 1. The argon2 version is changed | ||
* 2. Number of iterations are changed | ||
* 3. The memory value is changed | ||
* 4. The parellelism value is changed | ||
* 5. The argon variant is changed | ||
* | ||
* ```ts | ||
* const isValid = await argon.verify(hash, plainText) | ||
* | ||
* // Plain password is valid and hash needs a rehash | ||
* if (isValid && await argon.needsReHash(hash)) { | ||
* const newHash = await argon.make(plainText) | ||
* } | ||
* ``` | ||
*/ | ||
needsReHash(value) { | ||
const phcNode = this.#phcFormatter.deserialize(value); | ||
if (!this.#ids.includes(phcNode.id)) { | ||
throw new TypeError("Value is not a valid argon hash"); | ||
} | ||
} | ||
if (phcNode.version !== this.#config.version) { | ||
return true; | ||
} | ||
if (phcNode.id !== `argon2${this.#config.variant}`) { | ||
return true; | ||
} | ||
if (!phcNode.params) { | ||
return true; | ||
} | ||
if (phcNode.params.m !== this.#config.memory) { | ||
return true; | ||
} | ||
if (phcNode.params.t !== this.#config.iterations) { | ||
return true; | ||
} | ||
if (phcNode.params.p !== this.#config.parallelism) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
}; | ||
export { | ||
Argon | ||
}; |
@@ -1,2 +0,3 @@ | ||
import type { HashDriverContract, BcryptConfig } from '../types.js'; | ||
import { HashDriverContract, BcryptConfig } from '../types.js'; | ||
/** | ||
@@ -16,3 +17,3 @@ * Hash driver built on top of "bcrypt" hash algorigthm. Under the hood | ||
*/ | ||
export declare class Bcrypt implements HashDriverContract { | ||
declare class Bcrypt implements HashDriverContract { | ||
#private; | ||
@@ -67,1 +68,3 @@ constructor(config: BcryptConfig); | ||
} | ||
export { Bcrypt }; |
@@ -1,240 +0,411 @@ | ||
/* | ||
* @adonisjs/hash | ||
* | ||
* (c) AdonisJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
import * as bcryptBase64 from '../legacy/bcrypt_base64.cjs'; | ||
import { safeEqual } from '@poppinss/utils'; | ||
import { PhcFormatter } from '../phc_formatter.js'; | ||
import { EnumValidator, randomBytesAsync, RangeValidator } from '../helpers.js'; | ||
/** | ||
* Hash driver built on top of "bcrypt" hash algorigthm. Under the hood | ||
* we make use of the "bcrypt" npm package. | ||
* | ||
* The Bcrypt implementation uses the PHC formatting for creating | ||
* and verifying hashes. | ||
* | ||
* ```ts | ||
* const bcrypt = new Bcrypt({}) | ||
* | ||
* await bcrypt.make('secret') | ||
* // $bcrypt$v=98$r=10$Jtxi46WJ26OQ0khsYLLlnw$knXGfuRFsSjXdj88JydPOnUIglvm1S8 | ||
* ``` | ||
*/ | ||
export class Bcrypt { | ||
/** | ||
* Lazily loaded bcrypt binding. Since it is a peer dependency | ||
* we cannot import it at top level | ||
*/ | ||
#binding; | ||
/** | ||
* Config with defaults merged | ||
*/ | ||
#config; | ||
/** | ||
* Formatter to serialize and deserialize phc string | ||
*/ | ||
#phcFormatter = new PhcFormatter(); | ||
constructor(config) { | ||
this.#config = { | ||
rounds: 10, | ||
saltSize: 16, | ||
version: 0x62, | ||
...config, | ||
}; | ||
this.#validateConfig(); | ||
} | ||
/** | ||
* Dynamically importing underlying binding | ||
*/ | ||
async #importBinding() { | ||
if (this.#binding) { | ||
return this.#binding; | ||
import { | ||
EnumValidator, | ||
RangeValidator, | ||
randomBytesAsync | ||
} from "../../chunk-7RS6HCBK.js"; | ||
import { | ||
PhcFormatter | ||
} from "../../chunk-JSA56AE7.js"; | ||
import { | ||
__commonJS, | ||
__toESM | ||
} from "../../chunk-AXCKF2IS.js"; | ||
// src/legacy/bcrypt_base64.cjs | ||
var require_bcrypt_base64 = __commonJS({ | ||
"src/legacy/bcrypt_base64.cjs"(exports, module) { | ||
var BASE64_CODE = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""); | ||
var BASE64_INDEX = [ | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
0, | ||
1, | ||
54, | ||
55, | ||
56, | ||
57, | ||
58, | ||
59, | ||
60, | ||
61, | ||
62, | ||
63, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
2, | ||
3, | ||
4, | ||
5, | ||
6, | ||
7, | ||
8, | ||
9, | ||
10, | ||
11, | ||
12, | ||
13, | ||
14, | ||
15, | ||
16, | ||
17, | ||
18, | ||
19, | ||
20, | ||
21, | ||
22, | ||
23, | ||
24, | ||
25, | ||
26, | ||
27, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
28, | ||
29, | ||
30, | ||
31, | ||
32, | ||
33, | ||
34, | ||
35, | ||
36, | ||
37, | ||
38, | ||
39, | ||
40, | ||
41, | ||
42, | ||
43, | ||
44, | ||
45, | ||
46, | ||
47, | ||
48, | ||
49, | ||
50, | ||
51, | ||
52, | ||
53, | ||
-1, | ||
-1, | ||
-1, | ||
-1, | ||
-1 | ||
]; | ||
function encode2(buff) { | ||
const len = buff.byteLength; | ||
let off = 0; | ||
const stra = []; | ||
while (off < len) { | ||
let c1 = buff[off++] & 255; | ||
stra.push(BASE64_CODE[c1 >> 2 & 63]); | ||
c1 = (c1 & 3) << 4; | ||
if (off >= len) { | ||
stra.push(BASE64_CODE[c1 & 63]); | ||
break; | ||
} | ||
this.#binding = await import('bcrypt'); | ||
return this.#binding; | ||
let c2 = buff[off++] & 255; | ||
c1 |= c2 >> 4 & 15; | ||
stra.push(BASE64_CODE[c1 & 63]); | ||
c1 = (c2 & 15) << 2; | ||
if (off >= len) { | ||
stra.push(BASE64_CODE[c1 & 63]); | ||
break; | ||
} | ||
c2 = buff[off++] & 255; | ||
c1 |= c2 >> 6 & 3; | ||
stra.push(BASE64_CODE[c1 & 63]); | ||
stra.push(BASE64_CODE[c2 & 63]); | ||
} | ||
return stra.join(""); | ||
} | ||
/** | ||
* Generates salt for bcrypt | ||
*/ | ||
#generateBcryptSalt(salt, version, rounds) { | ||
const bcryptVersionCharCode = String.fromCharCode(version); | ||
const paddedRounds = rounds > 9 ? `${rounds}` : `0${rounds}`; | ||
return `$2${bcryptVersionCharCode}$${paddedRounds}$${bcryptBase64.encode(salt)}`; | ||
function decode2(str) { | ||
let off = 0; | ||
let olen = 0; | ||
const slen = str.length; | ||
const stra = []; | ||
const len = str.length; | ||
while (off < slen - 1 && olen < len) { | ||
let code = str.charCodeAt(off++); | ||
const c1 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1; | ||
code = str.charCodeAt(off++); | ||
const c2 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1; | ||
if (c1 === -1 || c2 === -1) | ||
break; | ||
let o = c1 << 2 >>> 0; | ||
o |= (c2 & 48) >> 4; | ||
stra.push(String.fromCharCode(o)); | ||
if (++olen >= len || off >= slen) | ||
break; | ||
code = str.charCodeAt(off++); | ||
const c3 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1; | ||
if (c3 === -1) | ||
break; | ||
o = (c2 & 15) << 4 >>> 0; | ||
o |= (c3 & 60) >> 2; | ||
stra.push(String.fromCharCode(o)); | ||
if (++olen >= len || off >= slen) | ||
break; | ||
code = str.charCodeAt(off++); | ||
const c4 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1; | ||
o = (c3 & 3) << 6 >>> 0; | ||
o |= c4; | ||
stra.push(String.fromCharCode(o)); | ||
++olen; | ||
} | ||
const buffa = []; | ||
for (off = 0; off < olen; off++) | ||
buffa.push(stra[off].charCodeAt(0)); | ||
return Buffer.from(buffa); | ||
} | ||
/** | ||
* Validate config | ||
*/ | ||
#validateConfig() { | ||
RangeValidator.validate('rounds', this.#config.rounds, [4, 31]); | ||
RangeValidator.validate('saltSize', this.#config.saltSize, [8, 1024]); | ||
EnumValidator.validate('version', this.#config.version, [0x61, 0x62]); | ||
Object.freeze(this.#config); | ||
module.exports = { | ||
encode: encode2, | ||
decode: decode2 | ||
}; | ||
} | ||
}); | ||
// src/drivers/bcrypt.ts | ||
var bcryptBase64 = __toESM(require_bcrypt_base64(), 1); | ||
import { safeEqual } from "@poppinss/utils"; | ||
var Bcrypt = class { | ||
/** | ||
* Lazily loaded bcrypt binding. Since it is a peer dependency | ||
* we cannot import it at top level | ||
*/ | ||
#binding; | ||
/** | ||
* Config with defaults merged | ||
*/ | ||
#config; | ||
/** | ||
* Formatter to serialize and deserialize phc string | ||
*/ | ||
#phcFormatter = new PhcFormatter(); | ||
constructor(config) { | ||
this.#config = { | ||
rounds: 10, | ||
saltSize: 16, | ||
version: 98, | ||
...config | ||
}; | ||
this.#validateConfig(); | ||
} | ||
/** | ||
* Dynamically importing underlying binding | ||
*/ | ||
async #importBinding() { | ||
if (this.#binding) { | ||
return this.#binding; | ||
} | ||
/** | ||
* Validate phc hash string | ||
*/ | ||
#validatePhcString(phcString) { | ||
const phcNode = this.#phcFormatter.deserialize(phcString); | ||
/** | ||
* Old bcrypt strings without version | ||
*/ | ||
if (!phcNode.version) { | ||
phcNode.version = 0x61; | ||
} | ||
/** | ||
* Validate top level properties to exist | ||
*/ | ||
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]); | ||
/** | ||
* Validate rest of the properties | ||
*/ | ||
EnumValidator.validate('version', phcNode.version, [0x61, 0x62]); | ||
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, | ||
}, | ||
}; | ||
this.#binding = await import("bcrypt"); | ||
return this.#binding; | ||
} | ||
/** | ||
* Generates salt for bcrypt | ||
*/ | ||
#generateBcryptSalt(salt, version, rounds) { | ||
const bcryptVersionCharCode = String.fromCharCode(version); | ||
const paddedRounds = rounds > 9 ? `${rounds}` : `0${rounds}`; | ||
return `$2${bcryptVersionCharCode}$${paddedRounds}$${bcryptBase64.encode(salt)}`; | ||
} | ||
/** | ||
* Validate config | ||
*/ | ||
#validateConfig() { | ||
RangeValidator.validate("rounds", this.#config.rounds, [4, 31]); | ||
RangeValidator.validate("saltSize", this.#config.saltSize, [8, 1024]); | ||
EnumValidator.validate("version", this.#config.version, [97, 98]); | ||
Object.freeze(this.#config); | ||
} | ||
/** | ||
* Validate phc hash string | ||
*/ | ||
#validatePhcString(phcString) { | ||
const phcNode = this.#phcFormatter.deserialize(phcString); | ||
if (!phcNode.version) { | ||
phcNode.version = 97; | ||
} | ||
/** | ||
* 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') | ||
* ``` | ||
*/ | ||
isValidHash(value) { | ||
try { | ||
this.#validatePhcString(value); | ||
return true; | ||
} | ||
catch { | ||
return false; | ||
} | ||
if (phcNode.id !== "bcrypt") { | ||
throw new TypeError(`Invalid "id" found in the phc string`); | ||
} | ||
/** | ||
* Hash a plain text value | ||
* | ||
* ```ts | ||
* const hash = await bcrypt.make('password') | ||
* ``` | ||
*/ | ||
async make(value) { | ||
const driver = await this.#importBinding(); | ||
/** | ||
* Generate salt including bcrypt formatted salt | ||
*/ | ||
const salt = await randomBytesAsync(this.#config.saltSize); | ||
const bcryptSalt = this.#generateBcryptSalt(salt, this.#config.version, this.#config.rounds); | ||
/** | ||
* Generate hash | ||
*/ | ||
const bcryptHash = await driver.hash(value, bcryptSalt); | ||
const hash = bcryptBase64.decode(bcryptHash.split(bcryptSalt)[1]); | ||
return this.#phcFormatter.serialize(salt, hash, { | ||
id: 'bcrypt', | ||
version: this.#config.version, | ||
params: { | ||
r: this.#config.rounds, | ||
}, | ||
}); | ||
if (!phcNode.params) { | ||
throw new TypeError(`No "params" found in the phc string`); | ||
} | ||
/** | ||
* Verify the plain text value against an existing hash | ||
* | ||
* ```ts | ||
* if (await bcrypt.verify(hash, plainText)) { | ||
* | ||
* } | ||
* ``` | ||
*/ | ||
async verify(hashedValue, plainValue) { | ||
const driver = await this.#importBinding(); | ||
try { | ||
if (hashedValue.startsWith('$2b') || hashedValue.startsWith('$2a')) { | ||
return await driver.compare(plainValue, hashedValue); | ||
} | ||
/** | ||
* De-serialize hash and ensure all Phc string properties | ||
* to exist. | ||
*/ | ||
const phcNode = this.#validatePhcString(hashedValue); | ||
const bcryptSalt = this.#generateBcryptSalt(phcNode.salt, phcNode.version, phcNode.params.r); | ||
const bcryptHash = await driver.hash(plainValue, bcryptSalt); | ||
const hash = bcryptBase64.decode(bcryptHash.split(bcryptSalt)[1]); | ||
return safeEqual(hash, phcNode.hash); | ||
} | ||
catch { | ||
return false; | ||
} | ||
if (!phcNode.salt) { | ||
throw new TypeError(`No "salt" found in the phc string`); | ||
} | ||
/** | ||
* Find if the hash value needs a rehash or not. The rehash is | ||
* required when. | ||
* | ||
* 1. The bcrypt version is changed | ||
* 2. Number of rounds are changed | ||
* 3. Bcrypt hash is not using MCF hash format | ||
* | ||
* ```ts | ||
* const isValid = await bcrypt.verify(hash, plainText) | ||
* | ||
* // Plain password is valid and hash needs a rehash | ||
* if (isValid && await bcrypt.needsReHash(hash)) { | ||
* const newHash = await bcrypt.make(plainText) | ||
* } | ||
* ``` | ||
*/ | ||
needsReHash(value) { | ||
if (value.startsWith('$2b') || value.startsWith('$2a')) { | ||
return true; | ||
} | ||
const phcNode = this.#phcFormatter.deserialize(value); | ||
if (phcNode.id !== 'bcrypt') { | ||
throw new TypeError('Value is not a valid bcrypt hash'); | ||
} | ||
/** | ||
* If config version is separate from hash version, then a | ||
* re-hash is needed | ||
*/ | ||
if (phcNode.version !== this.#config.version) { | ||
return true; | ||
} | ||
/** | ||
* Make sure all the encoded params are same as the config. | ||
* Otherwise a re-hash is needed | ||
*/ | ||
if (!phcNode.params) { | ||
return true; | ||
} | ||
if (phcNode.params.r !== this.#config.rounds) { | ||
return true; | ||
} | ||
return false; | ||
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 | ||
} | ||
}; | ||
} | ||
/** | ||
* 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') | ||
* ``` | ||
*/ | ||
isValidHash(value) { | ||
try { | ||
this.#validatePhcString(value); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Hash a plain text value | ||
* | ||
* ```ts | ||
* const hash = await bcrypt.make('password') | ||
* ``` | ||
*/ | ||
async make(value) { | ||
const driver = await this.#importBinding(); | ||
const salt = await randomBytesAsync(this.#config.saltSize); | ||
const bcryptSalt = this.#generateBcryptSalt(salt, this.#config.version, this.#config.rounds); | ||
const bcryptHash = await driver.hash(value, bcryptSalt); | ||
const hash = bcryptBase64.decode(bcryptHash.split(bcryptSalt)[1]); | ||
return this.#phcFormatter.serialize(salt, hash, { | ||
id: "bcrypt", | ||
version: this.#config.version, | ||
params: { | ||
r: this.#config.rounds | ||
} | ||
}); | ||
} | ||
/** | ||
* Verify the plain text value against an existing hash | ||
* | ||
* ```ts | ||
* if (await bcrypt.verify(hash, plainText)) { | ||
* | ||
* } | ||
* ``` | ||
*/ | ||
async verify(hashedValue, plainValue) { | ||
const driver = await this.#importBinding(); | ||
try { | ||
if (hashedValue.startsWith("$2b") || hashedValue.startsWith("$2a")) { | ||
return await driver.compare(plainValue, hashedValue); | ||
} | ||
const phcNode = this.#validatePhcString(hashedValue); | ||
const bcryptSalt = this.#generateBcryptSalt(phcNode.salt, phcNode.version, phcNode.params.r); | ||
const bcryptHash = await driver.hash(plainValue, bcryptSalt); | ||
const hash = bcryptBase64.decode(bcryptHash.split(bcryptSalt)[1]); | ||
return safeEqual(hash, phcNode.hash); | ||
} catch { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Find if the hash value needs a rehash or not. The rehash is | ||
* required when. | ||
* | ||
* 1. The bcrypt version is changed | ||
* 2. Number of rounds are changed | ||
* 3. Bcrypt hash is not using MCF hash format | ||
* | ||
* ```ts | ||
* const isValid = await bcrypt.verify(hash, plainText) | ||
* | ||
* // Plain password is valid and hash needs a rehash | ||
* if (isValid && await bcrypt.needsReHash(hash)) { | ||
* const newHash = await bcrypt.make(plainText) | ||
* } | ||
* ``` | ||
*/ | ||
needsReHash(value) { | ||
if (value.startsWith("$2b") || value.startsWith("$2a")) { | ||
return true; | ||
} | ||
const phcNode = this.#phcFormatter.deserialize(value); | ||
if (phcNode.id !== "bcrypt") { | ||
throw new TypeError("Value is not a valid bcrypt hash"); | ||
} | ||
if (phcNode.version !== this.#config.version) { | ||
return true; | ||
} | ||
if (!phcNode.params) { | ||
return true; | ||
} | ||
if (phcNode.params.r !== this.#config.rounds) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
}; | ||
export { | ||
Bcrypt | ||
}; |
@@ -1,2 +0,3 @@ | ||
import type { ScryptConfig, HashDriverContract } from '../types.js'; | ||
import { HashDriverContract, ScryptConfig } from '../types.js'; | ||
/** | ||
@@ -16,3 +17,3 @@ * Hash driver built on top of "scrypt" hash algorigthm. Under the hood | ||
*/ | ||
export declare class Scrypt implements HashDriverContract { | ||
declare class Scrypt implements HashDriverContract { | ||
#private; | ||
@@ -67,1 +68,3 @@ constructor(config: ScryptConfig); | ||
} | ||
export { Scrypt }; |
@@ -1,236 +0,9 @@ | ||
/* | ||
* @adonisjs/hash | ||
* | ||
* (c) AdonisJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
import { safeEqual } from '@poppinss/utils'; | ||
import { PhcFormatter } from '../phc_formatter.js'; | ||
import { randomBytesAsync, RangeValidator, scryptAsync, MAX_UINT32 } from '../helpers.js'; | ||
/** | ||
* Hash driver built on top of "scrypt" hash algorigthm. Under the hood | ||
* we make use of the Node.js crypto module | ||
* | ||
* The Scrypt implementation uses the PHC formatting for creating | ||
* and verifying hashes. | ||
* | ||
* ```ts | ||
* const scrypt = new Scrypt({}) | ||
* | ||
* await scrypt.make('secret') | ||
* // $scrypt$n=16384,r=8,p=1$iILKD1gVSx6bqualYqyLBQ$DNzIISdmTQS6sFdQ1tJ3UCZ7Uun4uGHNjj0x8FHOqB0pf2LYsu9Xaj5MFhHg21qBz8l5q/oxpeV+ZkgTAj+OzQ | ||
* ``` | ||
*/ | ||
export class Scrypt { | ||
/** | ||
* Config with defaults merged | ||
*/ | ||
#config; | ||
/** | ||
* Formatter to serialize and deserialize phc string | ||
*/ | ||
#phcFormatter = new PhcFormatter(); | ||
constructor(config) { | ||
this.#config = { | ||
cost: 16384, | ||
blockSize: 8, | ||
parallelization: 1, | ||
saltSize: 16, | ||
keyLength: 64, | ||
maxMemory: 32 * 1024 * 1024, | ||
...config, | ||
}; | ||
this.#validateConfig(); | ||
} | ||
/** | ||
* Validate config | ||
*/ | ||
#validateConfig() { | ||
RangeValidator.validate('blockSize', this.#config.blockSize, [1, MAX_UINT32]); | ||
RangeValidator.validate('cost', this.#config.cost, [2, MAX_UINT32]); | ||
RangeValidator.validate('parallelization', this.#config.parallelization, [ | ||
1, | ||
Math.floor(((Math.pow(2, 32) - 1) * 32) / (128 * this.#config.blockSize)), | ||
]); | ||
RangeValidator.validate('saltSize', this.#config.saltSize, [8, 1024]); | ||
RangeValidator.validate('keyLength', this.#config.keyLength, [64, 128]); | ||
RangeValidator.validate('maxMemory', this.#config.maxMemory, [ | ||
128 * this.#config.cost * this.#config.blockSize + 1, | ||
MAX_UINT32, | ||
]); | ||
Object.freeze(this.#config); | ||
} | ||
/** | ||
* Validate phc hash string | ||
*/ | ||
#validatePhcString(phcString) { | ||
const phcNode = this.#phcFormatter.deserialize(phcString); | ||
/** | ||
* Validate top level properties to exist | ||
*/ | ||
if (phcNode.id !== 'scrypt') { | ||
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`); | ||
} | ||
RangeValidator.validate('hash.byteLength', phcNode.hash.byteLength, [64, 128]); | ||
RangeValidator.validate('salt.byteLength', phcNode.salt.byteLength, [8, 1024]); | ||
/** | ||
* blockSize | ||
*/ | ||
RangeValidator.validate('r', phcNode.params.r, [1, MAX_UINT32]); | ||
/** | ||
* Cost | ||
*/ | ||
RangeValidator.validate('n', phcNode.params.n, [1, MAX_UINT32]); | ||
/** | ||
* Parallelization | ||
*/ | ||
RangeValidator.validate('p', phcNode.params.p, [ | ||
1, | ||
Math.floor(((Math.pow(2, 32) - 1) * 32) / (128 * phcNode.params.r)), | ||
]); | ||
return { | ||
id: phcNode.id, | ||
hash: phcNode.hash, | ||
salt: phcNode.salt, | ||
params: { | ||
r: phcNode.params.r, | ||
n: phcNode.params.n, | ||
p: phcNode.params.p, | ||
}, | ||
}; | ||
} | ||
/** | ||
* Check if the value is a valid hash. This method just checks | ||
* for the formatting of the hash. | ||
* | ||
* ```ts | ||
* scrypt.isValidHash('hello world') // false | ||
* scrypt.isValidHash('$scrypt$n=16384,r=8,p=1$iILKD1gVSx6bqualYqyLBQ$DNzIISdmTQS6sFdQ1tJ3UCZ7Uun4uGHNjj0x8FHOqB0pf2LYsu9Xaj5MFhHg21qBz8l5q/oxpeV+ZkgTAj+OzQ') | ||
* ``` | ||
*/ | ||
isValidHash(value) { | ||
try { | ||
this.#validatePhcString(value); | ||
return true; | ||
} | ||
catch { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Hash a plain text value | ||
* | ||
* ```ts | ||
* const hash = await scrypt.make('password') | ||
* ``` | ||
*/ | ||
async make(value) { | ||
const salt = await randomBytesAsync(this.#config.saltSize); | ||
/** | ||
* Generate hash | ||
*/ | ||
const hash = await scryptAsync(value, salt, this.#config.keyLength, { | ||
cost: this.#config.cost, | ||
blockSize: this.#config.blockSize, | ||
parallelization: this.#config.parallelization, | ||
maxmem: this.#config.maxMemory, | ||
}); | ||
/** | ||
* Serialize hash | ||
*/ | ||
return this.#phcFormatter.serialize(salt, hash, { | ||
id: 'scrypt', | ||
params: { | ||
n: this.#config.cost, | ||
r: this.#config.blockSize, | ||
p: this.#config.parallelization, | ||
}, | ||
}); | ||
} | ||
/** | ||
* Verify the plain text value against an existing hash | ||
* | ||
* ```ts | ||
* if (await scrypt.verify(hash, plainText)) { | ||
* | ||
* } | ||
* ``` | ||
*/ | ||
async verify(hashedValue, plainValue) { | ||
try { | ||
/** | ||
* De-serialize hash and ensure all Phc string properties | ||
* to exist. | ||
*/ | ||
const phcNode = this.#validatePhcString(hashedValue); | ||
/** | ||
* Generate a new hash with the same properties | ||
* as the existing hash | ||
*/ | ||
const newHash = await scryptAsync(plainValue, phcNode.salt, phcNode.hash.byteLength, { | ||
cost: phcNode.params.n, | ||
blockSize: phcNode.params.r, | ||
parallelization: phcNode.params.p, | ||
maxmem: this.#config.maxMemory, | ||
}); | ||
/** | ||
* Ensure both are equal | ||
*/ | ||
return safeEqual(newHash, phcNode.hash); | ||
} | ||
catch { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Find if the hash value needs a rehash or not. The rehash is | ||
* required when. | ||
* | ||
* 1. The cost value is changed | ||
* 2. The blockSize value is changed | ||
* 3. The parallelization value is changed | ||
* | ||
* ```ts | ||
* const isValid = await scrypt.verify(hash, plainText) | ||
* | ||
* // Plain password is valid and hash needs a rehash | ||
* if (isValid && await scrypt.needsReHash(hash)) { | ||
* const newHash = await scrypt.make(plainText) | ||
* } | ||
* ``` | ||
*/ | ||
needsReHash(value) { | ||
const phcNode = this.#phcFormatter.deserialize(value); | ||
if (phcNode.id !== 'scrypt') { | ||
throw new TypeError('Value is not a valid scrypt hash'); | ||
} | ||
/** | ||
* Make sure all the encoded params are same as the config. | ||
* Otherwise a re-hash is needed | ||
*/ | ||
if (!phcNode.params) { | ||
return true; | ||
} | ||
if (phcNode.params.n !== this.#config.cost) { | ||
return true; | ||
} | ||
if (phcNode.params.r !== this.#config.blockSize) { | ||
return true; | ||
} | ||
if (phcNode.params.p !== this.#config.parallelization) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
} | ||
import { | ||
Scrypt | ||
} from "../../chunk-ZUKNXYT4.js"; | ||
import "../../chunk-7RS6HCBK.js"; | ||
import "../../chunk-JSA56AE7.js"; | ||
import "../../chunk-AXCKF2IS.js"; | ||
export { | ||
Scrypt | ||
}; |
@@ -1,3 +0,3 @@ | ||
/// <reference types="@types/node" resolution-mode="require"/> | ||
import { PhcNode } from './types.js'; | ||
/** | ||
@@ -7,3 +7,3 @@ * Phc formatter is used to serialize a hash to a phc string and | ||
*/ | ||
export declare class PhcFormatter<Params extends Record<string, string | number> = Record<string, string | number>> { | ||
declare class PhcFormatter<Params extends Record<string, string | number> = Record<string, string | number>> { | ||
/** | ||
@@ -22,1 +22,3 @@ * Serialize salt and hash with predefined options. | ||
} | ||
export { PhcFormatter }; |
@@ -1,34 +0,7 @@ | ||
/* | ||
* @adonisjs/hash | ||
* | ||
* (c) AdonisJS | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
// @ts-expect-error | ||
import phc from '@phc/format'; | ||
/** | ||
* Phc formatter is used to serialize a hash to a phc string and | ||
* deserialize it back to a PHC object. | ||
*/ | ||
export class PhcFormatter { | ||
/** | ||
* Serialize salt and hash with predefined options. | ||
*/ | ||
serialize(salt, hash, options) { | ||
return phc.serialize({ | ||
id: options.id, | ||
version: options.version, | ||
params: options.params, | ||
salt, | ||
hash, | ||
}); | ||
} | ||
/** | ||
* Deserialize a PHC string to an object | ||
*/ | ||
deserialize(phcString) { | ||
return phc.deserialize(phcString); | ||
} | ||
} | ||
import { | ||
PhcFormatter | ||
} from "../chunk-JSA56AE7.js"; | ||
import "../chunk-AXCKF2IS.js"; | ||
export { | ||
PhcFormatter | ||
}; |
@@ -1,6 +0,5 @@ | ||
/// <reference types="@types/node" resolution-mode="require"/> | ||
/** | ||
* The contract Hash drivers should adhere to | ||
*/ | ||
export interface HashDriverContract { | ||
interface HashDriverContract { | ||
/** | ||
@@ -27,3 +26,3 @@ * Check if the value is a valid hash. This method just checks | ||
*/ | ||
export type PhcNode<Params extends Record<string, string | number> = Record<string, string | number>> = { | ||
type PhcNode<Params extends Record<string, string | number> = Record<string, string | number>> = { | ||
id: string; | ||
@@ -38,7 +37,7 @@ salt: Buffer; | ||
*/ | ||
export type ArgonVariants = 'd' | 'i' | 'id'; | ||
type ArgonVariants = 'd' | 'i' | 'id'; | ||
/** | ||
* Shape of argon2 config | ||
*/ | ||
export type ArgonConfig = { | ||
type ArgonConfig = { | ||
/** | ||
@@ -96,3 +95,3 @@ * The argon hash type to use. | ||
*/ | ||
export type BcryptConfig = { | ||
type BcryptConfig = { | ||
/** | ||
@@ -121,3 +120,3 @@ * The cost of processing the data | ||
*/ | ||
export type ScryptConfig = { | ||
type ScryptConfig = { | ||
/** | ||
@@ -165,2 +164,4 @@ * CPU/memory cost parameter. Must be a power of two greater than one. | ||
*/ | ||
export type ManagerDriverFactory = () => HashDriverContract; | ||
type ManagerDriverFactory = () => HashDriverContract; | ||
export { ArgonConfig, ArgonVariants, BcryptConfig, HashDriverContract, ManagerDriverFactory, PhcNode, ScryptConfig }; |
{ | ||
"name": "@adonisjs/hash", | ||
"version": "8.3.1-6", | ||
"version": "8.3.1-7", | ||
"description": "Framework agnostic Password hashing package with support for PHC string format", | ||
@@ -8,6 +8,3 @@ "main": "build/index.js", | ||
"files": [ | ||
"build/src", | ||
"build/factories", | ||
"build/index.d.ts", | ||
"build/index.js" | ||
"build" | ||
], | ||
@@ -31,3 +28,3 @@ "exports": { | ||
"typecheck": "tsc --noEmit", | ||
"compile": "npm run lint && npm run clean && tsc", | ||
"compile": "npm run lint && npm run clean && tsup-node", | ||
"build": "npm run compile", | ||
@@ -53,26 +50,27 @@ "release": "np", | ||
"@adonisjs/tsconfig": "^1.1.8", | ||
"@commitlint/cli": "^17.7.1", | ||
"@commitlint/config-conventional": "^17.7.0", | ||
"@japa/assert": "^2.0.0-1", | ||
"@japa/expect-type": "^2.0.0-0", | ||
"@japa/runner": "^3.0.0-3", | ||
"@swc/core": "^1.3.78", | ||
"@commitlint/cli": "^17.8.0", | ||
"@commitlint/config-conventional": "^17.8.0", | ||
"@japa/assert": "^2.0.0", | ||
"@japa/expect-type": "^2.0.0", | ||
"@japa/runner": "^3.0.2", | ||
"@swc/core": "1.3.82", | ||
"@types/bcrypt": "^5.0.0", | ||
"@types/node": "^20.5.3", | ||
"argon2": "^0.31.0", | ||
"@types/node": "^20.8.6", | ||
"argon2": "^0.31.1", | ||
"bcrypt": "^5.1.1", | ||
"c8": "^8.0.1", | ||
"cross-env": "^7.0.3", | ||
"del-cli": "^5.0.0", | ||
"eslint": "^8.47.0", | ||
"del-cli": "^5.1.0", | ||
"eslint": "^8.51.0", | ||
"github-label-sync": "^2.3.1", | ||
"husky": "^8.0.3", | ||
"np": "^8.0.4", | ||
"prettier": "^3.0.2", | ||
"prettier": "^3.0.3", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^5.1.6" | ||
"tsup": "^7.2.0", | ||
"typescript": "^5.2.2" | ||
}, | ||
"dependencies": { | ||
"@phc/format": "^1.0.0", | ||
"@poppinss/utils": "^6.5.0-5" | ||
"@poppinss/utils": "^6.5.0" | ||
}, | ||
@@ -127,3 +125,19 @@ "peerDependencies": { | ||
}, | ||
"prettier": "@adonisjs/prettier-config" | ||
"prettier": "@adonisjs/prettier-config", | ||
"tsup": { | ||
"entry": [ | ||
"./index.ts", | ||
"./src/drivers/argon.ts", | ||
"./src/drivers/bcrypt.ts", | ||
"./src/drivers/scrypt.ts", | ||
"./src/phc_formatter.ts", | ||
"./src/types.ts", | ||
"./factories/main.ts" | ||
], | ||
"outDir": "./build", | ||
"clean": true, | ||
"format": "esm", | ||
"dts": true, | ||
"target": "esnext" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50870
24
22
1716
1
Updated@poppinss/utils@^6.5.0