feat(services): improve UserService with enhanced error handling and data validation

This commit includes updates to `user.service.ts` that enhance error handling and data validation. The `getUserFromUsername` function has been renamed to `getUserByEmail` reflecting the new identifier being used to retrieve users. Both `getUserByEmail` and `getUserFromIdService` have been revised to include error handling and logging capability. The `register` function now incorporates stronger validation checks and proper GDPR verification. It is also updated to return JWT token on successful registration.

Issue: #18
Signed-off-by: Mathis <yidhra@tuta.io>
This commit is contained in:
Mathis H (Avnyr) 2024-04-30 12:38:26 +02:00
parent 9225337e95
commit 3232e5fac1
Signed by: Mathis
GPG Key ID: DD9E0666A747D126

View File

@ -1,10 +1,14 @@
import type {IDbUser} from "@interfaces/database/IDbUser";
import type {IReqLogin} from "@interfaces/requests/IReqLogin";
import type {IReqRegister} from "@interfaces/requests/IReqRegister";
import {ErrorType, type ISError} from "@interfaces/services/ISError";
import CredentialService from "@services/credential.service";
import JwtService from "@services/jwt.service";
import MySqlService from "@services/mysql.service";
import MysqlService from "@services/mysql.service";
import {Logger} from "tslog";
import {v4} from "uuid";
const logger = new Logger({
name: "UserService",
@ -13,92 +17,124 @@ const logger = new Logger({
const DbHandler = new MySqlService.Handler("UserService");
/**
* Retrieves a user object from the database based on the given username.
* Retrieves a user from the database by the given email address.
*
* @param {string} username - The username of the user to retrieve.
* @returns {Promise<Object | null>} - The user object if found, or null if not found.
* @param {string} targetEmail - The email address of the user to retrieve.
* @returns {Promise<IDbUser | ISError>}
* - A promise that resolves with the user.
* - If the user is not found, an error object is returned.
* - If an error occurs during the database operation, an error object is returned.
*/
async function getUserFromUsername(username: string): Promise<object | null> {
const dbUser = await MySqlService.User.getByUsername(DbHandler, username);
if (dbUser === undefined) return null;
return dbUser;
}
async function getUserFromIdService(id: string | undefined) {
const dbUser = await MySqlService.User.getById(DbHandler, id);
if (dbUser === undefined) return null;
return dbUser;
}
async function register(ReqData: IReqRegister) {
if (ReqData.password.length < 6) {
logger.info(`REGISTER :> Invalid password (${ReqData.username})`);
async function getUserByEmail(targetEmail: string): Promise<IDbUser | ISError> {
try {
const dbUser = await MySqlService.User.getByEmail(DbHandler, targetEmail);
if (dbUser === undefined) {
logger.info(`User not found (${targetEmail})`);
return {
error: "invalidPassword",
error: ErrorType.NotFound,
message: "The user was not fund.",
};
}
const passwordHash = await CredentialService.hash(ReqData.password);
return dbUser;
} catch (err) {
logger.error(err);
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
/**
* Retrieves a user from the database based on the provided ID.
*
* @param {string} id - The ID of the user to retrieve.
* @returns {Promise<IDbUser | ISError>} - A promise that resolves with the user object if found, or an error object if not.
*/
async function getUserFromIdService(id: string): Promise<IDbUser | ISError> {
try {
if (!id || id.length !== 36) {
logger.info(`Invalid ID (${id})`);
return {
error: ErrorType.InvalidData,
message: "Invalid ID length.",
};
}
const dbUser = await MySqlService.User.getById(DbHandler, id);
if (dbUser === undefined) {
logger.info(`User not found (${id})`);
return {
error: ErrorType.NotFound,
message: "The user was not found.",
};
}
return dbUser;
} catch (err) {
return {
error: ErrorType.DatabaseError,
message: "An unknown error occurred.",
};
}
}
async function register(ReqData: IReqRegister): Promise<ISError | string> {
if (ReqData.password.length < 6) {
return {
error: ErrorType.InvalidData,
message: "Password must be at least 6 characters long.",
};
}
const passwordHash = await CredentialService.hash(`${ReqData.password}`);
// Does the new user has accepted GDPR ?
if (ReqData.gdpr !== true) {
logger.info(`REGISTER :> GDPR not validated (${ReqData.username})`);
return {
error: "gdprNotApproved",
error: ErrorType.InvalidData,
message: "GDPR acceptance is required.",
};
}
// Check if exist and return
const dbUserIfExist = await getUserFromUsername(ReqData.username);
if (dbUserIfExist) {
logger.info(
`REGISTER :> User exist (${dbUserIfExist.username})\n ID:${dbUserIfExist.id}`,
);
return {
error: "exist",
};
}
const currentDate = new Date();
// New UserService (class)
// Check if exist and return
const dbUserIfExist: IDbUser | ISError = await getUserByEmail(ReqData.email);
if ("error" in dbUserIfExist) {
return {
error: dbUserIfExist.error,
message: dbUserIfExist.message,
};
}
const NewUser = new User(
ReqData.username,
ReqData.displayName,
passwordHash,
currentDate,
);
NewUser.setFirstName(ReqData.firstName);
NewUser.setLastName(ReqData.lastName);
const NewUser = await MySqlService.User.insert(DbHandler, {
id: v4(),
email: ReqData.email,
username: ReqData.username,
firstname: ReqData.firstName,
lastname: ReqData.lastName,
dob: ReqData.dob,
hash: passwordHash,
gdpr: currentDate,
is_admin: false,
is_mail_verified: false,
});
if ("error" in NewUser || !NewUser.id) {
return {
error: ErrorType.DatabaseError,
message: 'Error when inserting user in database.'
};
}
// JWT
const alg = "HS512";
const token = await JwtService.sign(
{
sub: NewUser.id,
},
alg,
{
alg: "HS512",
},
"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;
return token;
}
async function login(ReqData: IReqLogin) {