Skip to content

Commit

Permalink
actually added documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPlayGamez committed Oct 12, 2024
1 parent a2df9ce commit 91ae9a0
Show file tree
Hide file tree
Showing 3 changed files with 386 additions and 46 deletions.
141 changes: 129 additions & 12 deletions file.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ function loadUsersFromFile(filePath, password) {
// Load users from file on startup

class Authenticator {

/**
* Constructor for the Authenticator class
* @param {string} QR_LABEL - label for the QR code
* @param {number} rounds - number of rounds for bcrypt
* @param {string} JWT_SECRET_KEY - secret key for signing JWTs
* @param {object} JWT_OPTIONS - options for JWTs such as expiresIn
* @param {number} maxLoginAttempts - maximum number of login attempts
* @param {string} DB_FILE_PATH - path to the file where the users are stored
* @param {string} DB_PASSWORD - password to decrypt the file
*/
constructor(QR_LABEL, rounds, JWT_SECRET_KEY, JWT_OPTIONS, maxLoginAttempts, DB_FILE_PATH, DB_PASSWORD) {
this.QR_LABEL = QR_LABEL;
this.rounds = rounds;
Expand All @@ -56,6 +67,13 @@ class Authenticator {
this.users = loadUsersFromFile(DB_FILE_PATH, DB_PASSWORD);
this.DB_FILE_PATH = DB_FILE_PATH
this.DB_PASSWORD = DB_PASSWORD
this.OTP_ENCODING = 'base32'
this.lockedText = "User is locked"
this.OTP_WINDOW = 1 // How many OTP codes can be used before and after the current one (usefull for slower people, recommended 1)
this.INVALID_2FA_CODE_TEXT = "Invalid 2FA code"
this.REMOVED_USER_TEXT = "User has been removed"
this.USER_ALREADY_EXISTS_TEXT = "User already exists"
this.ALLOW_DB_DUMP = false // Allowing DB Dumping is disabled by default can be enabled by setting ALLOW_DB_DUMP to true after initializing your class

// Override methods to update file when users array changes
const originalPush = this.users.push;
Expand All @@ -69,6 +87,12 @@ class Authenticator {



/**
* Registers a new user
* @param {object} userObject - object with required keys: email, password, wants2FA, you can add custom keys too
* @returns {object} - registered user object, or "Gebruiker bestaat al" if user already exists
* @throws {Error} - any other error
*/
async register(userObject) {
if (!userObject.loginAttempts) userObject.loginAttempts = 0
if (!userObject.locked) userObject.locked = false
Expand All @@ -81,7 +105,7 @@ class Authenticator {
const otpauth_url = speakeasy.otpauthURL({
secret: secret.base32,
label: this.QR_LABEL,
encoding: 'base32'
encoding: this.OTP_ENCODING
});
const qrCode = await QRCode.toDataURL(otpauth_url);
userObject.secret2FA = secret.base32;
Expand All @@ -91,7 +115,7 @@ class Authenticator {
userObject.password = hash;
userObject.jwt_version = 1

if (this.users.find(u => u.email === userObject.email)) return "User already exists"
if (this.users.find(u => u.email === userObject.email)) return this.USER_ALREADY_EXISTS_TEXT
this.users.push(userObject);
return returnedUser;
} catch (err) {
Expand All @@ -100,13 +124,21 @@ class Authenticator {

}

/**
* Logs in a user
* @param {string} email - email address of user
* @param {string} password - password of user
* @param {number} twoFactorCode - 2FA code of user
* @returns {object} - user object with jwt_token, or null if login was unsuccessful, or "User is locked" if user is locked
* @throws {Error} - any other error
*/
async login(email, password, twoFactorCode) {
const account = this.users.find(u => u.email === email);
if (!email) return null;
if (!password) return null;

try {
if (account.locked) return "User is locked"
if (account.locked) return this.lockedText
const result = await bcrypt.compare(password, account.password);

if (!result) {
Expand All @@ -124,11 +156,11 @@ class Authenticator {

const verified = speakeasy.totp.verify({
secret: account.secret2FA,
encoding: 'base32',
token: twoFactorCode, // Verwijder Number()
window: 2 // Sta 2 tijdstippen toe voor en na de huidige tijd
encoding: this.OTP_ENCODING,
token: twoFactorCode,
window: this.OTP_WINDOW
});
if (!verified) return "Invalid 2FA code";
if (!verified) return this.INVALID_2FA_CODE_TEXT;

}
const jwt_token = jwt.sign({ _id: account._id, version: account.jwt_version }, this.JWT_SECRET_KEY, this.JWT_OPTIONS);
Expand All @@ -140,6 +172,12 @@ class Authenticator {
throw err;
}
}
/**
* Generates a random string (emailCode) and updates the user object with it
* @param {string} email - email address of user
* @returns {string} - emailCode
* @throws {Error} - any other error
*/
async registerEmailSignin(email) {
let emailCode = Crypto.randomUUID()
try {
Expand All @@ -156,6 +194,12 @@ class Authenticator {
console.error(error)
}
}
/**
* Verifies a emailCode and returns a valid JWT token for the user
* @param {string} emailCode - the emailCode to verify
* @returns {object} - an object with the user info and a valid JWT token
* @throws {Error} - any error that occurs during the process
*/
async verifyEmailSignin(emailCode) {
if (emailCode === null) return null
const user = await this.users.find(user => user.emailCode == emailCode);
Expand All @@ -168,36 +212,60 @@ class Authenticator {
this.users.push()
return { ...user, jwt_token };
}
/**
* Retrieves user information based on the user ID
* @param {string} userId - the user ID to retrieve information
* @returns {object} - an object with the user information
* @throws {Error} - any error that occurs during the process
*/
getInfoFromUser(userId) {
const user = this.users.find(u => u._id === userId);
if (!user) return null;
return user
}

/**
* Verifies a JWT token and returns the user information if the token is valid
* @param {string} token - the JWT token to verify
* @returns {object} - the user information if the token is valid, otherwise false
* @throws {Error} - any error that occurs during the process
*/
async verifyToken(token) {
if (jwt.verify(token, this.JWT_SECRET_KEY, this.JWT_OPTIONS)) {
let jwt_token = jwt.decode(token);
let user = await this.getInfoFromUser(jwt_token._id)
return (user.jwt_version === jwt_token.version) ? this.getInfoFromUser(jwt_token._id) : false;
}
}
/**
* Verifies a 2FA code for a user
* @param {string} userId - the user ID to verify the 2FA code for
* @param {string} twofactorcode - the 2FA code to verify
* @returns {boolean} - true if the code is valid, false otherwise
*/
async verify2FA(userId, twofactorcode) {
let user = this.users.find(user => user._id === userId)
if (!user) return null
const verified = speakeasy.totp.verify({
secret: user.secret2FA,
encoding: 'base32',
encoding: this.OTP_ENCODING,
token: twofactorcode,
window: 2 // Sta 2 tijdstippen toe voor en na de huidige tijd
window: this.OTP_WINDOW
});
return verified;

}
/**
* Resets the password of a user
* @param {string} userId - the user ID to reset the password for
* @param {string} newPassword - the new password to set
* @returns {object} - the user object after the password has been reset
* @throws {Error} - any error that occurs during the process
*/
async resetPassword(userId, newPassword) {
const user = this.users.find(u => u._id === userId);
if (!user) return null;
user.password = await bcrypt.hash(newPassword, this.rounds);
// Vervang het wachtwoord van de gebruiker in de array
const userIndex = this.users.findIndex(u => u._id === userId);
if (userIndex !== -1) {
this.users[userIndex].password = user.password;
Expand All @@ -207,6 +275,13 @@ class Authenticator {

return user;
}
/**
* Changes the number of login attempts for a user
* @param {string} userId - the user ID to change the login attempts for
* @param {number} attempts - the new number of login attempts
* @returns {object} - the user object after the login attempts have been changed
* @throws {Error} - any error that occurs during the process
*/
async changeLoginAttempts(userId, attempts) {
const user = this.users.find(u => u._id === userId);
if (!user) return null;
Expand All @@ -218,6 +293,12 @@ class Authenticator {

return user;
}
/**
* Locks a user from logging in
* @param {string} userId - the user ID to lock
* @returns {object} - the user object after the user has been locked
* @throws {Error} - any error that occurs during the process
*/
async lockUser(userId) {
const user = this.users.find(u => u._id === userId);
if (!user) return null;
Expand All @@ -229,6 +310,12 @@ class Authenticator {

return user;
}
/**
* Unlocks a user from logging in
* @param {string} userId - the user ID to unlock
* @returns {object} - the user object after the user has been unlocked
* @throws {Error} - any error that occurs during the process
*/
async unlockUser(userId) {
const user = this.users.find(u => u._id === userId);
if (!user) return null;
Expand All @@ -239,6 +326,12 @@ class Authenticator {
this.users.push()
return user;
}
/**
* Revokes all user tokens for a user
* @param {string} userId - the user ID to revoke all tokens for
* @returns {undefined}
* @throws {Error} - any error that occurs during the process
*/
async revokeUserTokens(userId) {
const userIndex = this.users.findIndex(u => u._id === userId);
if (userIndex !== -1) {
Expand All @@ -248,6 +341,12 @@ class Authenticator {


}
/**
* Removes 2FA for a user
* @param {string} userId - the user ID to remove 2FA for
* @returns {object} - the user object after 2FA has been removed
* @throws {Error} - any error that occurs during the process
*/
async remove2FA(userId) {
const user = this.users.find(u => u._id === userId);
if (!user) return null;
Expand All @@ -264,6 +363,12 @@ class Authenticator {

return user;
}
/**
* Adds 2FA for a user
* @param {string} userId - the user ID to add 2FA for
* @returns {object} - the user object after 2FA has been added
* @throws {Error} - any error that occurs during the process
*/
async add2FA(userId) {
const user = this.users.find(u => u._id === userId);
if (!user) return null;
Expand All @@ -272,7 +377,7 @@ class Authenticator {
const otpauth_url = speakeasy.otpauthURL({
secret: secret.base32,
label: this.QR_LABEL,
encoding: 'base32'
encoding: this.OTP_ENCODING
});
const qrCode = await QRCode.toDataURL(otpauth_url);
if (userIndex !== -1) {
Expand All @@ -286,6 +391,12 @@ class Authenticator {

return user;
}
/**
* Removes a user from the database
* @param {string} userId - the user ID to remove
* @returns {string} - "User has been removed" if successful, otherwise an error message
* @throws {Error} - any error that occurs during the process
*/
async removeUser(userId) {
try {
const user = this.users.find(u => u._id === userId);
Expand All @@ -295,7 +406,7 @@ class Authenticator {
this.users.splice(userIndex, 1);
}
this.users.push()
return "User has been removed"
return this.REMOVED_USER_TEXT

} catch (error) {
return `User with ID ${userId} couldn't be removed`
Expand All @@ -304,7 +415,13 @@ class Authenticator {


}
/**
* Retrieves all users from the database
* @returns {object[]} - an array of user objects
* @throws {Error} - any error that occurs during the process
*/
async dumpDB() {
if (this.ALLOW_DB_DUMP === false) return "DB dumping is disabled"
return this.users
}

Expand Down
Loading

0 comments on commit 91ae9a0

Please sign in to comment.