Skip to content

Commit

Permalink
Added ability to manually configure the package
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmadnasriya committed Oct 1, 2024
1 parent 4944b7c commit e827e84
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 90 deletions.
60 changes: 47 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ ___
**AuthCrypto** is a powerful library for handling cryptographic operations and JWT (JSON Web Tokens) in Node.js applications. It provides utilities for hashing passwords, generating JWT tokens, and more.

> [!IMPORTANT]
>
> 🌟 **Support Our Open-Source Development!** 🌟
> We need your support to keep our projects going! If you find our > work valuable, please consider contributing. Your support helps us > continue to develop and maintain these tools.
> We need your support to keep our projects going! If you find our work valuable, please consider contributing. Your support helps us continue to develop and maintain these tools.
>
> **[Click here to support us!](https://fund.nasriya.net/)**
>
> Every contribution, big or small, makes a difference. Thank you for > your generosity and support!
> Every contribution, big or small, makes a difference. Thank you for your generosity and support!
___
## Features

Expand Down Expand Up @@ -53,14 +54,10 @@ ___

## Configuration

**AuthCrypto** reads configuration values from environment variables:
**AuthCrypto** reads configuration values from environment variables or by setting them up manually:

- `AuthCrypto_ROUNDS`: The number of hashing rounds for password hashing.
- `AuthCrypto_PASSWORDS_MIN`: Minimum length for passwords (default: `8`).
- `AuthCrypto_PASSWORDS_MAX`: Maximum length for passwords (default: `32`).
- `AuthCrypto_SECRET`**`*`**: A secret phrase to generate and verify JWT. Can be generated from [crypto.generateSecret()](#generating-secrets).

You can set these values in your `.env` file:
### A) Environment Variables
If you have full control over the source code, you can setup a `.env` file with the following properties:

```env
AuthCrypto_ROUNDS=10
Expand All @@ -69,9 +66,34 @@ AuthCrypto_PASSWORDS_MAX=32
AuthCrypto_SECRET=Your_secret
```

- `AuthCrypto_ROUNDS`: The number of hashing rounds for password hashing.
- `AuthCrypto_PASSWORDS_MIN`: Minimum length for passwords (default: `8`, min: `8`).
- `AuthCrypto_PASSWORDS_MAX`: Maximum length for passwords (default: `32`).
- `AuthCrypto_SECRET`**`*`**: A secret phrase to generate and verify JWT. Can be generated from [crypto.generateSecret()](#generating-secrets).

### B) Manual Configuration
You can manually set some or all configurations using the `config` module as follows:

```js
authCrypto.config.hashingRounds = 500;
authCrypto.config.minPasswordLength = 10;
authCrypto.config.maxPasswordLength = 32;
authCrypto.config.jwtSecret = '<a 64 bytes secret>';
```

Or you can configure them all like this:

```js
authCrypto.config.set({
hashingRounds: 500,
minPasswordLength: 10,
maxPasswordLength: 32,
jwtSecret: '<a 64 bytes secret>'
})
```
> **:warning: Important Note**
>
> You must specify the `Crypto JWT_SECRET` variable in your environment, otherwise, your system might be at risk of forgery
> You must specify the `Crypto JWT_SECRET` variable in your environment, or set it using `authCrypto.config.jwtSecret`, otherwise, your system might be at risk of forgery
___

## Usage
Expand Down Expand Up @@ -158,7 +180,7 @@ Explanation:
The `verify` method checks if a provided password matches a previously hashed password.

Example Usage:
```ts
```js
const plainPassword = 'mySecretPassword';
const hashedPassword = 'hashedPasswordFromDatabase'; // Assume this is a valid hashed password

Expand All @@ -167,13 +189,25 @@ const isMatch = Passwords.verify(plainPassword, hashedPassword);
console.log(isMatch); // ⇨ true if the password matches, otherwise false
```

Example with different algorith:
```js
const options = {
algorithm: 'SHA256' // Default: SHA512
}

const isMatch = Passwords.verify(plainPassword, hashedPassword, options);

console.log(isMatch); // ⇨ true if the password matches, otherwise false
```
Example Usage with **salting**:
```ts
const plainPassword = 'mySecretPassword';
const hashedPassword = 'hashedPasswordFromDatabase'; // Assume this is a valid hashed password
const salt = 'optionalSalt'; // If a salt was used during hashing
const options = {
salt: 'optionalSalt', // If a salt was used during hashing
}

const isMatch = Passwords.verify(plainPassword, hashedPassword, salt);
const isMatch = Passwords.verify(plainPassword, hashedPassword, options);

console.log(isMatch); // ⇨ true if the password matches, otherwise false
```
Expand Down
12 changes: 8 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nasriya/authcrypto",
"version": "1.0.1",
"version": "1.1.0",
"description": "AuthCrypto is a versatile cryptographic toolkit for handling JSON Web Tokens (JWT), password hashing, and secure token generation and verification. It provides robust methods for creating and managing JWTs, hashing and verifying passwords with secure algorithms, and generating cryptographically strong random values for various use cases.",
"main": "./dist/cjs/manager.js",
"module": "./dist/esm/manager.js",
Expand Down Expand Up @@ -62,4 +62,4 @@
"type": "individual",
"url": "https://fund.nasriya.net/"
}
}
}
179 changes: 179 additions & 0 deletions src/assets/config/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
class ConfigManager {
readonly #_helpers = {
is: {
validRounds: (r: number) => {
if (isNaN(r)) { throw new SyntaxError(`The number of rounds in the .env file must be a valid number, instead got ${r}`) }
if (!Number.isInteger(r)) { throw new SyntaxError('The number of rounds must be a valid number and cannot be less than one') }
if (r < 1) { throw new RangeError(`The number of rounds cannot be less than one`) }
}
}
}

/**
* The number of hashing rounds to use for password hashing.
* This value is read from the `AuthCrypto_ROUNDS` environment variable.
* If the value is not a valid number or is less than one, a `SyntaxError` will be thrown.
* If the value is not set, `undefined` will be returned.
* @returns {number | undefined} The number of hashing rounds to use.
*/
get hashingRounds(): number | undefined {
const rStr = process.env.AuthCrypto_ROUNDS;
if (!(rStr && typeof rStr === 'string')) { return undefined }

const r = Number.parseInt(rStr, 10);
this.#_helpers.is.validRounds(r);
return r;
}

/**
* Sets the number of hashing rounds for AuthCrypto. This value must be a positive integer of at least 1.
* @throws {TypeError} If the number of rounds is not a valid number.
* @throws {RangeError} If the number of rounds is less than 1.
* @param {number} r The number of hashing rounds to set.
*/
set hashingRounds(r: number) {
if (typeof r !== 'number') { throw new TypeError(`The number of rounds must be a number, instead got ${typeof r}`) }
this.#_helpers.is.validRounds(r);
process.env.AuthCrypto_ROUNDS = r.toString();
}

/**
* The minimum length of a password for AuthCrypto.
* This value is read from the `AuthCrypto_PASSWORDS_MIN` environment variable.
* If the value is not a valid number or is less than 8, a `SyntaxError` or `RangeError` will be thrown.
* If the value is not set, `undefined` will be returned.
* @returns {number | undefined} The minimum length of a password.
*/
get minPasswordLength(): number | undefined {
const minStr = process.env.AuthCrypto_PASSWORDS_MIN;
if (minStr === undefined) { return undefined }
if (!(minStr && typeof minStr === 'string')) { throw new TypeError(`The min password length in the .env file must be a valid number, instead got ${minStr}`) }

const min = Number.parseInt(minStr, 10);

if (isNaN(min)) { throw new SyntaxError(`The min password length in the .env file must be a valid number, instead got ${minStr}`) }
if (!Number.isInteger(min)) { throw new SyntaxError('The min password length must be a valid number and cannot be less than 8') }
if (min < 8) { throw new RangeError('The min password length cannot be less than 8') }

return min;
}

/**
* Sets the minimum length of a password for AuthCrypto. This value must be a positive integer of at least 8.
* @throws {TypeError} If the min password length is not a valid number.
* @throws {RangeError} If the min password length is less than 8.
* @param {number} min The minimum length of a password to set.
*/
set minPasswordLength(min: number) {
if (!Number.isInteger(min)) { throw new TypeError('The min password length must be a valid number and cannot be less than 8') }
if (min < 8) { throw new RangeError('The min password length cannot be less than 8') }
process.env.AuthCrypto_PASSWORDS_MIN = min.toString();
}


/**
* The maximum length of a password for AuthCrypto.
* This value is read from the `AuthCrypto_PASSWORDS_MAX` environment variable.
* If the value is not a valid number or is greater than 32, a `SyntaxError` or `RangeError` will be thrown.
* If the value is not set, `undefined` will be returned.
* @returns {number | undefined} The maximum length of a password.
*/
get maxPasswordLength(): number | undefined {
const maxStr = process.env.AuthCrypto_PASSWORDS_MAX;
if (maxStr === undefined) { return undefined }
if (!(maxStr && typeof maxStr === 'string')) { throw new TypeError(`The max password length in the .env file must be a valid number, instead got ${maxStr}`) }

const max = Number.parseInt(maxStr, 10);

if (isNaN(max)) { throw new SyntaxError(`The max password length in the .env file must be a valid number, instead got ${maxStr}`) }
if (!Number.isInteger(max)) { throw new SyntaxError('The max password length must be a valid number and cannot be greater than 32') }
if (max < 8) { throw new RangeError('The max password length cannot be less than 8') }

return max;
}

/**
* Sets the maximum length of a password for AuthCrypto. This value must be a positive integer of at most 32.
* @throws {TypeError} If the max password length is not a valid number.
* @throws {RangeError} If the max password length is greater than 32.
* @param {number} max The maximum length of a password to set.
*/
set maxPasswordLength(max: number) {
if (!Number.isInteger(max)) { throw new TypeError('The max password length must be a valid number and cannot be greater than 32') }
if (max < 8) { throw new RangeError('The max password length cannot be less than 8') }
if (this.minPasswordLength && max > this.minPasswordLength) { throw new RangeError('The max password length cannot be greater than the min password length') }
process.env.AuthCrypto_PASSWORDS_MAX = max.toString();
}


/**
* Returns the secret key used to sign and verify JSON Web Tokens.
* This value is read from the `AuthCrypto_JWT_SECRET` environment variable.
* If the value is not a valid string or is not at least 64 bytes long for HS512, a `TypeError` will be thrown.
* If the value is not set, `undefined` will be returned.
* @returns {string | undefined} The secret key used for JWT signing and verification.
*/
get jwtSecret(): string | undefined {
const secret = process.env.AuthCrypto_JWT_SECRET;
if (secret === undefined) { return undefined }
if (typeof secret !== 'string') { throw new TypeError('The JWT secret must be a string') }

const keyBytes = Buffer.byteLength(secret, 'utf8');
if (keyBytes < 64) { throw new Error(`The "AuthCrypto_JWT_SECRET" key must be at least 64 bytes long for HS512. Found: ${keyBytes} bytes.`); }

return secret;
}

/**
* Sets the secret key used to sign and verify JSON Web Tokens.
* This value must be a string and at least 64 bytes long for HS512.
* @throws {TypeError} If the JWT secret is not a string.
* @throws {Error} If the JWT secret is less than 64 bytes long.
* @param {string} secret The secret key used to sign and verify JSON Web Tokens.
*/
set jwtSecret(secret: string) {
if (typeof secret !== 'string') { throw new TypeError('The JWT secret must be a string') }

const keyBytes = Buffer.byteLength(secret, 'utf8');
if (keyBytes < 64) { throw new Error(`The "AuthCrypto_JWT_SECRET" key must be at least 64 bytes long for HS512. Found: ${keyBytes} bytes.`); }

process.env.AuthCrypto_JWT_SECRET = secret;
}

/**
* Sets the configuration options for AuthCrypto.
* @param {Configs} configs An object with the configuration options.
* @throws {TypeError} If the config is not an object.
* @throws {Error} If any of the required properties are missing from the config object.
*/
set(configs: Configs) {
if (configs && typeof configs === 'object' && !Array.isArray(configs) && configs !== null && Object.keys(configs).length > 0) {
if ('minPasswordLength' in configs) {
this.minPasswordLength = configs.minPasswordLength;
}

if ('maxPasswordLength' in configs) {
this.maxPasswordLength = configs.maxPasswordLength;
}

if ('jwtSecret' in configs) {
this.jwtSecret = configs.jwtSecret;
}

if ('hashingRounds' in configs) {
this.hashingRounds = configs.hashingRounds;
}
} else {
throw new TypeError('The config must be an object with at least one property')
}
}
}

interface Configs {
minPasswordLength: number;
maxPasswordLength: number;
jwtSecret: string;
hashingRounds: number;
}

export default ConfigManager;
23 changes: 10 additions & 13 deletions src/assets/crypto/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import crypto from 'crypto';
import { HashAlgorithm } from '../../docs/docs';
import ConfigManager from '../config/config';

class CryptoManager {
readonly #_configManager: ConfigManager;
readonly #_supportedAlgorithms: HashAlgorithm[] = ['SHA256', 'SHA512', 'MD5', 'SHA1'];
readonly #_config = Object.seal({
rounds: 37,
})
readonly #_defaultRounds = 37;

constructor() {
const roundsStr = process.env.AuthCrypto_ROUNDS;
constructor(configManager: ConfigManager) {
this.#_configManager = configManager;
}

if (roundsStr) {
if (typeof roundsStr !== 'string') { throw new Error(`You must specify the number of rounds (AuthCrypto_ROUNDS) for the AuthCrypto module in the .env file`) }
const rounds = Number.parseInt(roundsStr, 10);
if (isNaN(rounds) || rounds < 1) { throw new SyntaxError('The number of rounds must be a valid number and cannot be less than one') }
this.#_config.rounds = rounds;
}
get #_rounds() {
return this.#_configManager.hashingRounds || this.#_defaultRounds
}

/**
Expand Down Expand Up @@ -65,7 +62,7 @@ class CryptoManager {
}

let hashedInput = input
for (let i = 0; i < this.#_config.rounds; i++) {
for (let i = 0; i < this.#_rounds; i++) {
hashedInput = crypto.createHash(algorithm).update(hashedInput).digest('hex');
}

Expand Down Expand Up @@ -100,4 +97,4 @@ class CryptoManager {
}
}

export default new CryptoManager();
export default CryptoManager;
Loading

0 comments on commit e827e84

Please sign in to comment.