This repository has been archived on 2024-04-19. You can view files and clone it, but cannot push or open issues or pull requests.
brief-04-back/services/UserService.js
2024-04-17 16:55:04 +02:00

229 lines
8.0 KiB
JavaScript

const Argon2id = require("@node-rs/argon2")
const {getDatabase} = require("./MongodbService");
const {getHashFromPassword} = require("./CredentialService")
const {JwtSign} = require("./JwtService");
const User = require("../models/User");
const { Logger } = require('tslog')
const logger = new Logger({ name: "UserService" });
let Db = null
getDatabase("brief04").then((value)=>{Db = value})
/**
* Retrieves a user from the database based on the provided username.
*
* @param {string} username - The username of the user to retrieve.
*
* @return {Promise<object>} - A promise that resolves with the user object if found,
* or null if not found.
*/
async function getUserFromUsername(username) {
const dbUser = await Db.collection("users").findOne({username: `${username}`})
if (dbUser === undefined) return null;
return dbUser;
}
/**
* Retrieves a user from the database based on the provided id.
*
* @param {string} id - The id of the user.
* @returns {Promise<object|null>} - A Promise that resolves with the user object if found,
* or null if no user is found.
*/
async function getUserFromIdService(id) {
return await Db.collection("users").findOne({id: id});
}
/**
* Registers a new user by creating a UserService object, generating a JWT token, and inserting the user into the database.
*
* @param {Object} sanitizedData - The sanitized user data.
* @param {string} sanitizedData.username - The username of the new user.
* @param {string} sanitizedData.displayName - The display namcoe of the new user.
* @param {string} sanitizedData.firstName
* @param {string} sanitizedData.lastName
* @param {string} sanitizedData.password - The password of the new user.
* @param {boolean} sanitizedData.gdpr - Indicates whether the new user has accepted GDPR.
*
* @returns {Object} - An object containing the registered user's data and JWT token.
* @returns {string} error - The error name, if any. "none" if registration was successful.
* @returns {string|null} jwt - The JWT token for the registered user. Null if registration was not successful.
* @returns {Object|null} user - The registered user's data. Null if registration was not successful.
* @returns {string|null} user.id - The ID of the registered user. Null if registration was not successful.
* @returns {string|null} user.username - The username of the registered user. Null if registration was not successful.
* @returns {string|null} user.displayName - The display name of the registered user. Null if registration was not successful.
*/
async function RegisterService(sanitizedData) {
if (sanitizedData.password.length < 6) {
logger.info(`REGISTER :> Invalid password (${sanitizedData.username})`)
return { error: "invalidPassword" };
}
const passwordHash = await getHashFromPassword(sanitizedData.password)
// Does the new user has accepted GDPR ?
if (sanitizedData.gdpr !== true) {
logger.info(`REGISTER :> GDPR not validated (${sanitizedData.username})`)
return { error: "gdprNotApproved" }
}
// Check if exist and return
const dbUserIfExist = await getUserFromUsername(sanitizedData.username)
if (dbUserIfExist) {
logger.info(`REGISTER :> User exist (${dbUserIfExist.username})\n ID:${dbUserIfExist.id}`)
return { error: "exist" }
}
const currentDate = new Date();
// New UserService (class)
const NewUser = new User(sanitizedData.username, sanitizedData.displayName, passwordHash, currentDate);
NewUser.setFirstName(sanitizedData.firstName);
NewUser.setLastName(sanitizedData.lastName);
// JWT
const alg = 'HS512'
const token = await JwtSign({
sub: NewUser.id
}, alg,
'1d',
'user')
const userData = {
error: "none",
jwt: token,
user: {
id: NewUser.id,
username: NewUser.username,
displayName: NewUser.displayName,
firstName: NewUser.firstName,
lastName: NewUser.lastName
}};
logger.info(userData)
await Db.collection("users").insertOne(NewUser);
logger.info(`REGISTER :> Inserted new user (${NewUser.username})`)
return userData
}
/**
* Performs the login process by verifying the provided credentials.
* @param {Object} sanitizedData - The sanitized user login data.
* @param {string} sanitizedData.username - The username provided by the user.
* @param {string} sanitizedData.password - The password provided by the user.
* @returns {Object} - The login result object.
* @returns {string} result.error - The error code if there is an error during the login process.
* @returns {string} result.jwt - The JSON Web Token (JWT) generated upon successful login.
* @returns {Object} result.user - The user information.
* @returns {number} result.user.id - The ID of the user.
* @returns {string} result.user.username - The username of the user.
* @returns {string} result.user.displayName - The display name of the user.
*/
async function LoginService(sanitizedData) {
//const passwordHash = await getHashFromPassword(sanitizedData.password);
const dbUser = await getUserFromUsername(sanitizedData.username);
if (!dbUser) {
console.log(`LoginService :> User does not exist (${sanitizedData.username})`);
return { error: "userNotFound" };
}
if (sanitizedData.password.length < 6) {
console.log('X')
console.log(`LoginService :> Invalid password (${sanitizedData.username})`);
return { error: "invalidPassword" };
}
const isPasswordValid = await Argon2id.verify(
Buffer.from(dbUser.passwordHash),
Buffer.from(sanitizedData.password),
{
secret: Buffer.from(`${process.env.HASH_SECRET}`),
algorithm: 2
});
if (!isPasswordValid) {
console.log(isPasswordValid)
console.log(`LoginService :> Invalid password (${sanitizedData.username})`);
return { error: "invalidPassword" };
}
// biome-ignore lint/style/useConst: <explanation>
let userData = {
error: "none",
jwt: null,
user: {
id: dbUser.id,
username: dbUser.username,
displayName: dbUser.displayName,
}
};
const alg = 'HS512';
userData.jwt = await JwtSign({sub: dbUser.id}, alg, '1d', 'user')
console.log("USERDATA :>");
console.log(userData);
return userData;
}
/**
* Retrieves all users from the database.
*
* @async
* @function getAllUsersService
* @returns {Promise<{iat: number, users: Array<user>, length: number}>} - The response object containing the users array and its length.
*/
async function getAllUsersService() {
const users = await Db.collection("users").find().toArray();
logger.info(`Query ${users.length} user(s)`)
return {
iat: Date.now(),
users: users,
length: users.length
}
}
/**
* Edits a user in the database.
*
* @param {string} targetId - The ID of the user to be edited.
* @param {object} sanitizedData - The sanitized data to update the user with.
* @returns {object} - An object indicating the result of the operation.
* If the user is not found, the error property will be a string "userNotFound".
* Otherwise, the error property will be a string "none".
*/
async function editUserService(targetId, sanitizedData) {
const updatedUserResult = await Db.collection("users").updateOne({id: targetId}, {$set: sanitizedData});
if (updatedUserResult.modifiedCount === 0) {
logger.info(`EDIT :> User not found (${targetId})`);
return { error: "userNotFound" };
}
logger.info(`EDIT :> User updated (${targetId})`);
return { error: "none" };
}
/**
* Delete a user from the database.
*
* @param {string} targetId - The ID of the user to be deleted.
* @return {Promise<boolean>} - A promise that resolves to true if the user is successfully deleted, or false if an error occurs.
*/
async function deleteUserService(targetId) {
logger.info(`Deleting user ${targetId}`)
try {
await Db.collection("users").deleteOne({id: targetId});
return true
} catch (e) {
logger.warn(e)
return false
}
}
module.exports = {
RegisterService,
LoginService,
getAllUsersService,
getUserFromIdService,
editUserService,
deleteUserService
}