diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts new file mode 100644 index 0000000..844d407 --- /dev/null +++ b/src/controllers/AuthController.ts @@ -0,0 +1,461 @@ +import JwtService from "@services/jwt.service"; + + +import {Logger} from "tslog"; +import type {Request, Response} from "express"; +import UserService from "@services/user.service"; + + +const logger = new Logger({ name: "AuthController" }); + +//TODO Better return object interface +/** + * Registers a user with the given request data. + * + * @param {Request} req - The request object containing user data. + * @param {Response} res - The response object to send the registration result. + * + * @return {Promise} A promise that resolves to the registration result. + * It can have the following properties: + * - error: "gdprNotApproved" if GDPR is not approved + * - error: "exist" if the user already exists + * - Otherwise, the registered user data + */ +async function registerUser(req: Request, res: Response): Promise { + const body = req.body; + if (!body) { + logger.warn(`Invalid input data (${req.ip})`); + return res + .type('application/json') + .status(400) + .json({ error: 'Invalid input data' }); + } + if (!body.password || !body.username || !body.firstName || !body.lastName || !body.displayName) { + logger.warn(`Field(s) missing (${req.ip})`); + return res + .type('application/json') + .status(400) + .json({ error: 'Field(s) missing' }); + } + + let gdpr = false + if (body.gdpr === true) {gdpr = true} + const sanitizeData= { + username: `${body.username}`, + displayName: `${body.displayName}`, + gdpr: gdpr, + password: `${body.password}`, + firstName: `${body.firstName}`, + lastName: `${body.lastName}`, + }; + + const RegisterServiceResult = await UserService.register(sanitizeData) + + if (RegisterServiceResult.error === "gdprNotApproved") { + logger.warn(`GDPR not approved (${req.ip})`); + return res + .status(400) + .json({ + error: RegisterServiceResult.error, + message: "GDPR not accepted." + }); + } + if (RegisterServiceResult.error === "exist") { + logger.warn(`The user already exists (${req.ip})`); + return res + .type('application/json') + .status(400) + .json({ + error: RegisterServiceResult.error, + message: "The user already exists." + }); + } + + // SUCCESS + logger.info(`User registered successfully (${req.ip})`); + return res + .type('application/json') + .status(201) + .json(RegisterServiceResult); +} + +/** + * Logs in a user with the provided credentials. + * + * @param {Request} req - The request object. + * @param {Response} res - The response object. + * + * @return {Promise} A promise that resolves when the user is logged in or rejects with an error. + */ +async function loginUser(req: Request, res: Response): Promise { + + const body = req.body; + if (!body) { + res + .type('application/json') + .status(400) + .json({ error: 'Invalid input data' }); + } + if (!body.password || !body.username) { + logger.warn(`Field(s) missing (${req.ip})`); + res + .type('application/json') + .status(400) + .json({ error: 'Field(s) missing' }); + } + + const loginData = { + username: `${body.username}`, + password: `${body.password}` + }; + console.log(body) + const LoginServiceResult = await UserService.login(loginData); + console.log(LoginServiceResult) + + if (LoginServiceResult.error === "userNotFound") { + console.log('POOL') + res + .type('application/json') + .status(404) + .json({ + error: LoginServiceResult.error, + message: "User not found." + }); + } + if (LoginServiceResult.error === "invalidPassword") { + res + .type('application/json') + .status(401) + .json({ + error: LoginServiceResult.error, + message: "Invalid password." + }); + } + res + .type('application/json') + .status(200) + .json(LoginServiceResult); +} + +async function getAllUsers(req: Request, res: Response) { + const authHeader = req.headers.authorization; + const bearerToken = authHeader?.split(' ')[1]; + if (!bearerToken) { + logger.warn(`Bearer token not provided (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const payload = await JwtService.verify(bearerToken); + if (!payload) { + logger.warn(`Unauthorized access attempt (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const sourceUser = await UserService.getFromId(payload.sub) + if (!sourceUser) { + return res + .type('application/json') + .status(404) + .json({ error: 'You dont exist anymore' }); + } + if (!sourceUser.isAdmin) { + return res + .type('application/json') + .status(403) + .json({ error: 'Unauthorized' }); + } + const AllUserResponse = await UserService.getAll() + if (!AllUserResponse.users) { + return res + .type('application/json') + .status(500) + .json({ error: 'Internal server error' }); + } + return res + .type('application/json') + .status(200) + .json(AllUserResponse); +} + +async function getUser(req: Request, res: Response) { + const authHeader = req.headers.authorization; + const bearerToken = authHeader?.split(' ')[1]; + if (!bearerToken) { + logger.warn(`Bearer token not provided (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const payload = await JwtService.verify(bearerToken); + if (!payload) { + logger.warn(`Unauthorized access attempt (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const sourceUser = await UserService.getFromId(payload.sub) + if (!sourceUser) { + return res + .type('application/json') + .status(404) + .json({ error: 'You dont exist anymore' }); + } + if (!sourceUser.isAdmin) { + return res + .type('application/json') + .status(403) + .json({ error: 'Unauthorized' }); + } + const userId = req.params["id"]; + const dbUser = await UserService.getFromId(userId); + if (!dbUser) { + logger.warn(`User not found (${req.ip})`); + return res + .type('application/json') + .status(404) + .json({ error: 'User not found' }); + } + // @ts-ignore + delete dbUser.passwordHash + // @ts-ignore + delete dbUser._id + return res + .type('application/json') + .status(200) + .json(dbUser); +} + +//TODO - Implement re-auth by current password in case of password change +async function editUser(req: Request, res: Response) { + const body = req.body; + if (!body) { + return res + .type('application/json') + .status(400) + .json({ error: 'Field(s) missing' }); + } + const authHeader = req.headers.authorization; + const bearerToken = authHeader?.split(' ')[1]; + if (!bearerToken) { + logger.warn(`Bearer token not provided (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const payload = await JwtService.verify(bearerToken); + if (!payload) { + logger.warn(`Unauthorized access attempt (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const sourceUser = await UserService.getFromId(payload.sub) + + //@ts-ignore + const targetUserId = req.params.id || payload.sub + console.log(targetUserId) + + if (!sourceUser) { + logger.warn(`Unauthorized access attempt (${req.ip})`); + return res + .type('application/json') + .status(404) + .json({ error: 'You dont exist anymore' }); + } + if (sourceUser.isAdmin || sourceUser.id === payload.sub) { + if (sourceUser.isAdmin) { + logger.info(`EDIT :> Source user is an admin (${sourceUser.displayName})`) + } else { + logger.info(`EDIT :> Source user modify itself (${sourceUser.displayName})`) + } + + //TODO Interface + const modifiedData= {} + if (body.firstName) modifiedData.firstName = `${body.firstName}`; + if (body.lastName) modifiedData.lastName = `${body.lastName}`; + if (body.displayName) modifiedData.displayName = `${body.displayName}`; + // Case handled with hashing by the service. + if (body.password) modifiedData.password = `${body.password}`; + + //Call service + const EditUserServiceResult = await UserService.edit(`${targetUserId}`, modifiedData); + if (EditUserServiceResult.error === 'userNotFound') { + logger.warn(`User not found (${req.ip})`); + return res + .type('application/json') + .status(404) + .json({ error: 'User not found' }); + } + if (EditUserServiceResult.error !== 'none') { + logger.error(`Error occurred during user edit (${req.ip})`); + return res + .type('application/json') + .status(500) + .json({ error: 'Internal server error' }); + } + return res + .type('application/json') + .status(200) + .json(EditUserServiceResult); + } + //Not itself or + logger.warn(`Unauthorized access attempt, not self or admin (${req.ip})`); + return res + .type('application/json') + .status(403) + .json({ error: 'Unauthorized' }); + + +} + +async function deleteUser(req: Request, res: Response): Promise { + const authHeader = req.headers.authorization; + const bearerToken = authHeader?.split(' ')[1]; + if (!bearerToken) { + logger.warn(`Bearer token not provided (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const payload = await JwtService.verify(bearerToken); + + if (!payload) { + logger.warn(`Invalid token (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Invalid token' }); + } + const sourceUser = await UserService.getFromId(payload?.sub) + const targetUserId = req.params["id"] + if (!sourceUser) { + logger.warn(`Unauthorized access attempt (${req.ip})`); + return res + .type('application/json') + .status(404) + .json({ error: 'You dont exist anymore' }); + } + if (sourceUser.isAdmin || sourceUser.id === payload.sub) { + const deleteUserServiceResult = await UserService.delete(`${targetUserId}`); + if (!deleteUserServiceResult) { + logger.error(`Error occurred during user delete (${req.ip})`); + return res + .type('application/json') + .status(500) + .json({ error: 'Internal server error' }); + } + + return res + .type('application/json') + .status(200) + .json({ message: 'User deleted successfully' }); + } + return res + .type('application/json') + .status(403) + .json({ error: 'Unauthorized' }); +} + +async function deleteSelf(req: Request, res: Response) { + const authHeader = req.headers.authorization; + const bearerToken = authHeader?.split(' ')[1]; + if (!bearerToken) { + logger.warn(`Bearer token not provided (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const payload = await JwtService.verify(bearerToken); + if (!payload) { + logger.warn(`Unauthorized access attempt (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const sourceUser = await UserService.getFromId(payload.sub) + if (!sourceUser) { + return res + .type('application/json') + .status(404) + .json({ error: 'You dont exist anymore' }); + } + if (sourceUser.id !== req.params["id"]) { + return res + .type('application/json') + .status(403) + .json({ error: 'Unauthorized' }); + } + const deleteResult = await UserService.delete(sourceUser.id); + if (!deleteResult) { + logger.error(`Failed to delete user (${req.ip})`); + return res + .type('application/json') + .status(500) + .json({ error: 'Failed to delete user' }); + } + return res + .type('application/json') + .status(200) + .json({ message: 'User deleted successfully' }); +} + +async function getSelf(req: Request, res: Response) { + const authHeader = req.headers.authorization; + const bearerToken = authHeader?.split(' ')[1]; + if (!bearerToken) { + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const payload = await JwtService.verify(bearerToken); + if (!payload) { + logger.warn(`Unauthorized access attempt (${req.ip})`); + return res + .type('application/json') + .status(401) + .json({ error: 'Unauthorized' }); + } + const dbUser = await UserService.getFromId(payload.sub) + if (!dbUser) { + return res + .type('application/json') + .status(404) + .json({ error: 'User not found' }); + } + return res + .type('application/json') + .status(200) + .json({ + id: dbUser.id, + username: dbUser.username, + displayName: dbUser.displayName, + firstName: dbUser.firstName, + lastName: dbUser.lastName, + isAdmin: dbUser.isAdmin + }); +} + +const AuthController = { + register: registerUser, + login: loginUser, + getAllUsers, + getUser, + editUser, + deleteUser, + deleteSelf, + getSelf +} + +export default AuthController; \ No newline at end of file