push de la fleme

This commit is contained in:
2024-04-17 16:55:04 +02:00
parent 66e402cbf6
commit f7fcc0d051
28 changed files with 2042 additions and 131 deletions

View File

@@ -0,0 +1,18 @@
const Argon2id = require("@node-rs/argon2");
/**
* Generates a hash from a given password using Argon2id algorithm.
*
* @param {string} password - The password to generate a hash for.
* @return {Promise<string>} - The generated hash.
*/
async function getHashFromPassword(password) {
return await Argon2id.hash(password,{
secret: Buffer.from(`${process.env.HASH_SECRET}`),
algorithm: 2
})
}
module.exports = {
getHashFromPassword
}

193
services/EventService.js Normal file
View File

@@ -0,0 +1,193 @@
const {getDatabase} = require("./MongodbService");
let Db = null
getDatabase("brief04").then((value)=>{Db = value})
const { Logger } = require('tslog');
const logger = new Logger({ name: "Auth Controller" });
//TODO Better return error integration ===
/**
* Retrieves an event from the database using the specified ID.
* @param {string} id - The ID of the event to retrieve.
* @return {Promise<Object>} - A Promise that resolves to an object representing the event.
*/
async function getEventFromIdService(id) {
return await Db.collection("events").findOne({id: id});
}
//TODO - To test
/**
* Retrieves all events from the database.
*
* @param {string} sourceId - The source ID to query the events from.
* @return {object} - An object containing:
* - updatedAt : The timestamp when the events were last updated.
* - events : An array of sanitized event objects. Each event object contains the following properties:
* - id : The event ID.
* - title : The event title.
* - subTitle : The event subtitle.
* - base64Banner : The base64 encoded banner image of the event.
* - desc : The event description.
* - date : The event date.
* - were : The event location.
* - maxMembers : The maximum number of members allowed for the event.
* - authorId : The ID of the event author.
* - members : The number of members currently participating in the event.
* - length : The number of events returned.
*/
async function getAllEventsService(sourceId) {
logger.info(`EVENT :> Query all threads (${sourceId})`)
let eventsArray = []
eventsArray = await Db.collection("events").find().toArray();
if (!eventsArray) {
logger.error('No event found.')
return null
}
const sanitizedEvents = eventsArray.map((event)=>{
return {
id: event.id,
title: event.title,
subTitle: event.subTitle,
base64Banner: event.base64Banner,
desc: event.desc,
date: event.date,
were: event.were,
maxMembers: event.maxMembers,
authorId: event.authorId,
members: event.members
}
})
console.log(` -> Returned ${sanitizedEvents.length} event(s)`)
return {
updatedAt: Date.now(),
events: sanitizedEvents,
length: sanitizedEvents.length
}
}
//TODO - To test
/**
* Retrieves the subscribed event(s) for a given user.
*
* @param {string} targetId - The id of the target user.
*
* @return {Promise<Object>}
* - A promise that resolves to an object containing information about the
* subscribed event(s).
* - If there are subscribed event(s), the resolved object will include the
* following properties:
* - iat: The current timestamp in milliseconds.
* - subscribedEvent: An array of the subscribed event(s) retrieved from the database.
* - length: The number of subscribed event(s).
* - If no subscribed event is found for the given targetId, the resolved object will
* include the following error property:
* - error: "noSubscribedEventFound"
*/
async function getUserSubscribedEventService(targetId) {
const subscribedEvent = await Db.collection("events").find({
members: {
$eltMatch: {
$eq: {targetId}
}}}).toArray();
if (!subscribedEvent) {
logger.error(`No subscribed event found for USERID:${targetId}`)
return {
error: "noSubscribedEventFound"
}
}
return {
iat: Date.now(),
subscribedEvent: subscribedEvent,
length: subscribedEvent.length
}
}
//TODO - To test
/**
* Modify the subscription state of a user for an event.
*
* @param {string} userId - The ID of the user.
* @param {string} eventId - The ID of the event.
* @param {boolean} state - The desired subscription state (true for subscribed, false for unsubscribed).
*
* @return {object} - The result of the operation.
* - If the user is not found, { error: "userNotFound" } is returned.
* - If the event is not found, { error: "eventNotFound" } is returned.
* - If the user is already subscribed to the event and the state is true, { error: "alreadySubscribed" } is returned.
* - If the user is not subscribed to the event and the state is false, { error: "notSubscribed" } is returned.
* - If the event update fails, { error: "updateFailed" } is returned.
* - If the operation is successful, { error: "none" } is returned.
*/
async function alterUserSubscribedEventStateService(userId, eventId, state){
const user = await Db.collection("users").findOne({id: userId});
if (!user) {
logger.error(`User not found (${userId})`);
return { error: "userNotFound" };
}
const event = await Db.collection("events").findOne({id: eventId});
if (!event) {
logger.error(`Event not found (${eventId})`);
return { error: "eventNotFound" };
}
const isUserSubscribed = event.members.includes(userId);
if (state === true && isUserSubscribed) {
logger.error(`User already subscribed to the event (${userId}, ${eventId})`);
return { error: "alreadySubscribed" };
}
if (state === false && !isUserSubscribed) {
logger.error(`User is not subscribed to the event (${userId}, ${eventId})`);
return { error: "notSubscribed" };
}
if (state === true) {
event.members.push(userId);
} else if (state === false) {
event.members = event.members.filter(memberId => memberId !== userId);
}
const updatedEventResult = await Db.collection("events").updateOne({id: eventId}, {$set: event});
if (updatedEventResult.modifiedCount === 0) {
logger.error(`Failed to update event (${eventId})`);
return { error: "updateFailed" };
}
logger.info(`User ${state === true ? "subscribed" : "unsubscribed"} successfully to the event. (${userId}, ${eventId})`);
return { error: "none" };
}
//TODO - To test
/**
* Edits an event in the database.
*
* @param {string} eventId
* - The ID of the event to be edited.
* @param {object} sanitizedData
* - The sanitized data used to update the event.
*
* @return {object}
* - An object with an "error" property.
* - If the event is not found, the "error" property will be "eventNotFound".
* - If the event is edited successfully, the "error" property will be "none".
*/
async function editEventService(eventId, sanitizedData) {
try {
const updatedEventResult = await Db.collection("events").updateOne({id: eventId}, {$set: sanitizedData});
if (updatedEventResult.modifiedCount === 0) {
logger.info(`EDIT :> Event not found (${eventId})`)
return { error: "eventNotFound" };
}
logger.info(`EDIT :> Event edited successfully (${eventId})`);
return { error: "none" };
} catch (e) {
logger.error(e)
}
}
//TODO Delete event - Owner || Admin ===
module.exports = {
getEventFromIdService,
getAllEventsService,
getUserSubscribedEventService,
alterUserSubscribedEventStateService,
editEventService
}

47
services/JwtService.js Normal file
View File

@@ -0,0 +1,47 @@
const Jose = require("jose")
const { Logger } = require('tslog')
const logger = new Logger({ name: "JwtService" });
/**
* Validates a JWT and returns the result.
*
* @param {string} jwt - The JWT to be validated.
* @return {Promise<boolean|object>} A promise that resolves to the validation result. It returns `false` if the JWT is invalid, otherwise it returns the decoded payload as an object.
*/
async function JwtVerifyService(jwt) {
try {
const result = await Jose.jwtVerify(
jwt,
new TextEncoder()
.encode(`${process.env.JWT_SECRET}`),
{
})
return result.payload;
} catch (error) {
logger.error(error)
return false
}
}
/**
* Sign a JWT with the given payload, algorithm, expiration time, and audience.
*
* @param {object} payload - The payload of the JWT.
* @param {string} alg - The algorithm to use for signing the JWT.
* @param {string} expTime - The expiration time of the JWT in format "4h || 7d || 1w".
* @param {string|string[]} audience - The audience(s) of the JWT.
*
* @returns {Promise<string>} - A promise that resolves to the signed JWT.
*/
async function JwtSignService(payload, alg, expTime, audience) {
return await new Jose.SignJWT(payload)
.setProtectedHeader({alg})
.setIssuedAt(new Date())
.setIssuer('Brief 03 - Mathis HERRIOT')
.setAudience(audience)
.setExpirationTime(expTime)
.sign(new TextEncoder().encode(`${process.env.JWT_SECRET}`))
}
module.exports = {JwtVerify: JwtVerifyService, JwtSign: JwtSignService}

View File

@@ -0,0 +1,28 @@
const {MongoClient} = require('mongodb')
const { Logger } = require('tslog')
/**
* Establishes a connection to a MongoDB server using the MongoClient.
*
* @returns {MongoClient} A Promise that resolves with a MongoDB client object connected to the server.
* The client object can be used to perform database operations.
* @throws {Error} If an error occurs while attempting to connect to the MongoDB server.
*/
async function connect() {
try {
return await MongoClient.connect("mongodb://127.0.0.1:27017/")
} catch (err) {
throw new Error(err)
}
}
async function getDatabase(name) {
const {connect} = require("./MongodbService")
const client = await connect()
//console.log(client)
const db = client.db(name);
return db;
}
module.exports = {connect, getDatabase}

197
services/ThreadService.js Normal file
View File

@@ -0,0 +1,197 @@
const {getDatabase} = require("./MongodbService");
const Thread = require("../models/Event");
let Db = null
getDatabase("brief04").then((value)=>{Db = value})
async function getThreadFromId(id) {
return await Db.collection("threads").findOne({id: id});
}
/**
* Asynchronously creates a thread service.
*
* @param {Object} sanitizedData - The sanitized data object containing necessary information for creating the thread service.
* @param {string} sanitizedData.userId
* @param {string} sanitizedData.title
* @param {string} sanitizedData.subTitle
* @param {string} sanitizedData.base64Banner
* @param {string} sanitizedData.desc
* @param {number} sanitizedData.price
*
* @return {Promise} A Promise that resolves to the created thread service.
*/
async function CreateThreadService(sanitizedData) {
console.log(`SERV :> Create thread (${sanitizedData.title})`)
const NewThread = new Thread(sanitizedData.title, sanitizedData.subTitle, sanitizedData.base64Banner, sanitizedData.desc, sanitizedData.price, sanitizedData.userId)
const dbResult = await Db.collection("threads").insertOne(NewThread);
if (!dbResult.acknowledged) {
console.log(" -> FAIL")
return null
}
console.log(` -> ${NewThread.id} = Success`)
return NewThread.id;
}
// TODO OK
/**
* Retrieve a thread by its ID.
*
* @param {string} threadId - The ID of the thread to retrieve.
* @return {Promise<Object>} The thread object if found, otherwise an error object.
* The error object has the following properties:
* - error: "ThreadNotFound"
* - message: "Thread not found"
*/
async function GetThreadByIdService(threadId) {
console.log(`SERV :> Get thread (${threadId})`)
const targetThread = await getThreadFromId(threadId);
if (!targetThread) {
console.log(` -> Thread not found (${threadId})`)
return {
error: "ThreadNotFound",
message: "Thread not found"
}
}
console.log(` -> Thread found (${threadId})`)
return {
id: targetThread.id,
title: targetThread.title,
subTitle: targetThread.subTitle,
base64Banner: targetThread.base64Banner,
desc: targetThread.desc,
price: targetThread.price,
userId: targetThread.userId
};
}
// TODO OK
/**
* Retrieves all threads for a given user.
*
* @param {object} sanitizedData - The sanitized data object containing user ID.
*
* @return {object} - An object containing the updated timestamp, an array of sanitized threads, and the length of the array.
* If the user is not found, an error object with the corresponding error code and message is returned.
*/
async function GetAllThreadService(sanitizedData) {
console.log(`SERV :> Query all threads (${sanitizedData.userId})`)
const sourceUser = Db.collection('users').findOne({id: sanitizedData.userId})
if (!sourceUser) {
console.log(` -> User not found (${sanitizedData.userId})`)
return {
error: "UserNotFound",
message: "User not found"
}
}
let threadsArray = []
threadsArray = await Db.collection("threads").find().toArray();
const sanitizedThreads = threadsArray.map((thread)=>{
return {
id: thread.id,
title: thread.title,
subTitle: thread.subTitle,
base64Banner: thread.base64Banner,
desc: thread.desc,
price: thread.price,
userId: thread.userId
}
})
console.log(` -> Returned ${sanitizedThreads.length} thread(s)`)
return {
updatedAt: Date.now(),
threads: sanitizedThreads,
length: sanitizedThreads.length
}
}
/**
* Retrieves the user thread(s) for the given userId.
*
* @param {string} userId - The ID of the user.
* @return {Promise<Object>} - A promise that resolves to an object containing the user threads, or an error object if no threads are found.
*/
async function GetUserThreadService(userId) {
console.log(`SERV :> Get user thread(s) (${userId})`)
const userThreads = await Db.collection("threads").find({ userId: userId }).toArray();
if (!userThreads) {
console.log(` -> Thread not found (${userId})`)
return {
error: "ThreadNotFound",
message: "Thread not found"
}
}
console.log(` -> ${userThreads.length} thread(s) found.`)
const cleanUserThreads = userThreads.map((thread) => {
return {
id: thread.id,
title: thread.title,
subTitle: thread.subTitle,
base64Banner: thread.base64Banner,
desc: thread.desc,
price: thread.price,
userId: thread.userId
};
});
console.log(cleanUserThreads)
return {
iat: Date.now(),
threads : cleanUserThreads,
length: cleanUserThreads.length
};
}
/**
* Updates a thread in the database with the given threadId and sanitized data.
*
* @param {string} threadId - The unique identifier of the thread to update.
* @param {object} sanitizedData - The sanitized data to update the thread with.
* @return {object} - An object indicating the result of the update operation.
* If the thread is not found, an error object with the error type and message is returned.
* If the thread is found and successfully updated, an object with an "error" property set to "none" is returned.
*/
async function UpdateThreadService(threadId, sanitizedData) {
const updatedThread = await Db.collection("threads").findOneAndUpdate({ id: threadId }, { $set: sanitizedData }, { returnOriginal: false });
console.log(updatedThread)
if (!updatedThread) {
console.log(` -> Thread not found (${threadId})`);
return {
error: "ThreadNotFound",
message: "Thread not found"
};
}
console.log(` -> Thread updated (${threadId})`);
return {
error: "none"
};
}
/**
* Deletes a thread with the given thread ID from the database.
*
* @param {string} threadId - The ID of the thread to delete.
* @return {Promise<{ error: string } | { error: string, message: string }>} - A promise that resolves to an object with either an "error" property set to "none" if the thread was deleted successfully, or an "error" property set to "ThreadNotFound" with a "message" property set to "Thread not found" if the thread was not found.
*/
async function DeleteThreadService(threadId) {
console.log(`SERV :> Delete thread (${threadId})`);
const deletedThread = await Db.collection("threads").findOneAndDelete({ id: threadId });
if (!deletedThread) {
console.log(` -> Thread not found (${threadId})`);
return {
error: "ThreadNotFound",
message: "Thread not found"
};
}
console.log(` -> Thread deleted (${threadId})`);
return {
error: "none"
}
}
module.exports = {
CreateThreadService,
GetThreadByIdService,
GetAllThreadService,
GetUserThreadService,
UpdateThreadService,
DeleteThreadService
}

229
services/UserService.js Normal file
View File

@@ -0,0 +1,229 @@
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
}