diff --git a/README b/README new file mode 100644 index 0000000..8f2a4b7 --- /dev/null +++ b/README @@ -0,0 +1,268 @@ + +--- +# Guardian - Secure Your Applications with Confidence + +## Description + +Guardian is a powerful JavaScript library designed to fortify your application's security with essential features for password management, authentication, and rate limiting. Whether you're developing user authentication, safeguarding against brute-force attacks, or enforcing password policies, Guardian has got you covered. + +This library is easy to integrate into your project and offers a comprehensive set of features, making it a reliable choice for enhancing the security of your application. + +## Installation + +To install Guardian, simply run the following command using npm: + +```bash +npm install Guardian +``` + +## Features + +### JSON Web Token (JWT) Generation and Verification + +Guardian simplifies the generation and verification of JSON Web Tokens (JWTs) with its `JwtAuth` class. JWTs are widely used for user authentication, single sign-on, and session management. Guardian streamlines JWT handling to ensure a secure authentication process in your application. + +### Password Validation and Complexity Checks + +With Guardian's `PassPolicy` class, you can enforce strong password complexity rules. Define minimum length, character requirements, and other policy settings to ensure that your users' passwords meet stringent security standards. Guardian helps protect your users' accounts by enforcing robust password policies. + +### Password Hashing and Verification + +Guardian simplifies the secure hashing and verification of passwords using the bcrypt algorithm through its `PassCheck` class. Bcrypt is a well-established and trusted password hashing algorithm. Guardian ensures that your password management is secure, enhancing the overall safety of your user data. + +### Rate Limiting for Enhanced Security + +Guardian offers a `RateLimiter` class to implement rate limits on actions like login attempts. Protect your application from brute-force attacks and excessive usage by controlling the rate at which users can perform specific actions. Guardian adds an extra layer of security to your application by preventing abuse. + +### Secure Password Generation + +Guardian's `PasswordGenerator` class allows you to generate strong, random passwords based on your specified criteria. This feature is invaluable when creating secure user accounts, managing password resets, or enhancing password security in your application. + +## Usage/Examples + +### JSON Web Token (JWT) Management - JwtAuth + +#### Initialize JwtAuth + +```javascript +const jwtAuth = new JwtAuth('mySecretKey'); +``` + +#### Generate a JWT + +```javascript +const token = await jwtAuth.generateJWT({ userId: 123 }, { expiresIn: '1h' }); +``` + +#### Verify a JWT + +```javascript +const result = await jwtAuth.verifyJWT(token); +``` + +#### Decode a JWT + +```javascript +const payload = await jwtAuth.decodeJWT(token); +``` + +#### Get JWT Expiration Date + +```javascript +const expirationDate = await jwtAuth.getJWTExpirationDate(token); +``` + +#### Check JWT Expiration + +```javascript +const isExpired = await jwtAuth.isJWTExpired(token); +``` + +#### Refresh a JWT + +```javascript +const refreshedToken = await jwtAuth.refreshJWT(token, { expiresIn: '1h' }); +``` + +#### Blacklist a JWT + +```javascript +const result = await jwtAuth.blacklistJWT(token); +``` + +### Password Policy Validation - PassPolicy + +#### Initialize PassPolicy + +```javascript +const passPolicy = new PassPolicy({ + minLength: 8, + minUpper: 1, + minLower: 1, + minNum: 1, + minSpecial: 1, + specialChars: "!@#$%^&*()_+~`|}{[]:;?><,./-=", +}); +``` + +#### Validate a Password + +```javascript +const validation = passPolicy.validate('Strong1@Password'); +``` + +#### Check Password Difference + +```javascript +const differenceValidation = passPolicy.checkDifference('NewPassword123', 'OldPassword123', 5); +``` + +### Password Hashing and Verification - PassCheck + +#### Initialize PassCheck + +```javascript +const passCheck = new PassCheck(10, { minLength: 8, requireDigits: true }); +``` + +#### Verify a Password + +```javascript +const isMatch = await passCheck.verifyPassword('myPassword', 'hashedPassword'); +``` + +#### Hash a Password + +```javascript +const hashedPassword = await passCheck.hashPassword('myPassword'); +``` + +### Rate Limiting for Enhanced Security - RateLimiter + +#### Initialize RateLimiter + +```javascript +const rateLimiter = new RateLimiter({ + login: { max: 5, timespan: 60000 }, + signup: { max: 3, timespan: 3600000 }, +}); +``` + +#### Add a User + +```javascript +const userLimits = rateLimiter.addUser('user123'); +``` + +#### Add an Event + +```javascript +const eventLimits = rateLimiter.addEvent('login', 5, 60000); +``` + +#### Remove a User + +```javascript +rateLimiter.removeUser('user123'); +``` + +#### Attempt an Event + +```javascript +const result = rateLimiter.attemptEvent('user123', 'login'); +``` + +#### Reset an Event for a User + +```javascript +rateLimiter.resetEventUser('user123', 'login'); +``` + +#### Reset an Event + +```javascript +rateLimiter.resetEvent('login'); +``` + +#### Reset a User + +```javascript +rateLimiter.resetUser('user123'); +``` + +#### Reset All + +```javascript +rateLimiter.resetAll(); +``` + +#### Get Last Attempt Time + +```javascript +const lastAttemptTime = rateLimiter.lastAttempt('user123', 'login'); +``` + +#### Get User Attempts + +```javascript +const allAttempts = rateLimiter.userAttempts('user123', 'login'); +``` + +#### Get Remaining Attempts + +```javascript +const remaining = rateLimiter.remainingAttempts('user123', 'login'); +``` + +### Password Generation - PasswordGenerator + +#### Initialize PasswordGenerator + +```javascript +const options = { + minLength: 8, + maxLength: 16, + minLower: 2, + minUpper: 2, + minNum: 2, + minSpecial: 2, + specialChars: "!@#$%^&*()_+~`|}{[]:;?><,./-=" +}; + +const passwordGenerator = new PasswordGenerator(options); +``` + +#### Generate a Random Password + +```javascript +const randomPassword = passwordGenerator.Generate(); +``` + +## Notes + +- Guardian offers a comprehensive set of features for enhancing the security of your applications, including JWT handling, password validation, hashing, rate limiting, and password generation. +- Each feature is encapsulated within its respective class, making it easy to integrate into your project. +- These classes provide asynchronous methods that return promises, making them suitable for use in asynchronous code. +- Ensure that you handle errors appropriately, especially when working with rate limiting to account for cases where users or events are not found or other errors occur. + +- The `PassPolicy` class provides methods for validating passwords based on complexity rules and checking the difference between old and new passwords. + +- The `PassCheck` class securely hashes passwords using bcrypt for storage and verification. + +- The `RateLimiter` class manages rate limits for events, useful for protecting against abuse or overuse of specific functionality. + +- The `JwtAuth` class simplifies JWT generation, verification, and management, including a token blacklist. + +- The `PasswordGenerator` class creates random passwords based on specified criteria. + +## Authors + +- [kajvan](https://www.github.com/kajvan) + +## License + +[MIT License](https://choosealicense.com/licenses/mit/) + +Guardian - Secure your applications with confidence! + +--- \ No newline at end of file diff --git a/passgen.js b/passgen.js index be09b6d..3e16b3d 100644 --- a/passgen.js +++ b/passgen.js @@ -12,7 +12,7 @@ class PasswordGenerator{ this.options = { ...defaultOptions, ...options }; } - async Generate(){ + Generate(){ // Generate random password that complies with the options const { minLength, maxLength, minLower, minUpper, minNum, minSpecial, specialChars } = this.options; diff --git a/passpolicy.js b/passpolicy.js index caed612..6d2ad03 100644 --- a/passpolicy.js +++ b/passpolicy.js @@ -46,7 +46,7 @@ class PassPolicy { return { valid: true }; } - async CheckDifference(newPassword, oldPassword, neededDifference) { + CheckDifference(newPassword, oldPassword, neededDifference) { //check if new password is different from old password if (newPassword === oldPassword) { return { valid: false, message: "New password must be different from old password." }; diff --git a/ratelimit.js b/ratelimit.js index 7fd36ad..c3a6bfa 100644 --- a/ratelimit.js +++ b/ratelimit.js @@ -1,73 +1,147 @@ -class RateLimit{ - constructor(maxRequests, timeFrame){ - this.maxRequests = maxRequests; - this.timeFrame = timeFrame; +class RateLimiter { + constructor(events){ + if(events) { + for(const event in events){ + events[event]["attempttimestamp"] = []; + events[event]["lastAttempt"] = 0; + } + } + + this.events = events; this.users = {}; } - async addUser(user){ - this.users[user] = { - requests: this.maxRequests, - lastRequest: Date.now() - }; - } + addUser(user){ + this.users[user] = {}; + for(const event in this.events){ + this.users[user][event] = this.events[event]; + } - async removeUser(user){ - delete this.users[user]; - } - - async checkUser(user){ - if(!this.users[user]) await this.addUser(user); return this.users[user]; } - async checkRateLimit(user){ - const { requests, lastRequest } = await this.checkUser(user); + addEvent(event, max, timespan){ + this.events[event] = { + "max": max, + "timespan": timespan, + "lastAttempt": 0, + "attempttimestamp": [], + }; + + for(const user in this.users){ + this.users[user][event] = this.events[event]; + } + + return this.events[event]; + } + + removeUser(user){ + delete this.users[user]; + } + + attemptEvent(user, event){ + if(!this.users.has(user)) return "User not found."; + if(!this.users[user].has(event)) return "Event not found."; + const now = Date.now(); - if(now - lastRequest > this.timeFrame) { - this.users[user].requests = this.maxRequests; - this.users[user].lastRequest = now; + const max = this.users[user][event].max; + const timespan = this.users[user][event].timespan; + const attempttimestamp = this.users[user][event].attempttimestamp; + + for(let i = 0; i < attempttimestamp.length; i++){ + if(attempttimestamp[i] < now - timespan){ + attempttimestamp.splice(i, 1); + } + else { + break; + } + } + + const attempts = attempttimestamp.length; + + if(attempts >= max){ + return [false, "limit reached"]; + } + else { + attempttimestamp.push(now); + this.users[user][event].attempttimestamp = attempttimestamp; + this.users[user][event].lastAttempt = now; return true; } - if(requests > 0) { - this.users[user].requests--; - return true; - } - return false; } - async getRateLimit(user){ - const { requests, lastRequest } = await this.checkUser(user); - const now = Date.now(); - if(now - lastRequest > this.timeFrame) { - this.users[user].requests = this.maxRequests; - this.users[user].lastRequest = now; - return this.maxRequests; - } - return requests; + resetEventUser(user, event){ + if(!this.users.has(user)) return "User not found."; + if(!this.users[user].has(event)) return "Event not found."; + + this.users[user][event].attempttimestamp = []; + this.users[user][event].lastAttempt = 0; } - async getRateLimitReset(user){ - const { requests, lastRequest } = await this.checkUser(user); - const now = Date.now(); - if(now - lastRequest > this.timeFrame) { - this.users[user].requests = this.maxRequests; - this.users[user].lastRequest = now; - return this.timeFrame; + resetEvent(event){ + if(!this.events.has(event)) return "Event not found."; + for(const user in this.users){ + if(this.users[user].has(event)){ + this.users[user][event].attempttimestamp = []; + this.users[user][event].lastAttempt = 0; + } } - return this.timeFrame - (now - lastRequest); } - async getRateLimitRemaining(user){ - const { requests, lastRequest } = await this.checkUser(user); - const now = Date.now(); - if(now - lastRequest > this.timeFrame) { - this.users[user].requests = this.maxRequests; - this.users[user].lastRequest = now; - return this.maxRequests; + resetUser(user){ + if(!this.users.has(user)) return "User not found."; + + for(const event in this.users[user]){ + this.users[user][event].attempttimestamp = []; + this.users[user][event].lastAttempt = 0; } - return requests; + } + + resetAll(){ + for(const user in this.users){ + for(const event in this.users[user]){ + this.users[user][event].attempttimestamp = []; + this.users[user][event].lastAttempt = 0; + } + } + } + + lastAttempt(user, event){ + if(!this.users.has(user)) return "User not found."; + if(!this.users[user].has(event)) return "Event not found."; + + return this.users[user][event].lastAttempt; + } + + userAttempts(user, event){ + if(!this.users.has(user)) return "User not found."; + if(!this.users[user].has(event)) return "Event not found."; + + return this.users[user][event].attempttimestamp; + } + + remainingAttempts(user, event){ + if(!this.users.has(user)) return "User not found."; + if(!this.users[user].has(event)) return "Event not found."; + + const now = Date.now(); + const max = this.users[user][event].max; + const timespan = this.users[user][event].timespan; + const attempttimestamp = this.users[user][event].attempttimestamp; + + for(let i = 0; i < attempttimestamp.length; i++){ + if(attempttimestamp[i] < now - timespan){ + attempttimestamp.splice(i, 1); + } + else { + break; + } + } + + const attempts = attempttimestamp.length; + + return max - attempts; } } -module.exports = RateLimit; \ No newline at end of file +module.exports = RateLimiter; \ No newline at end of file diff --git a/readme.md b/readme.md deleted file mode 100644 index e69de29..0000000