From b640dc7671a703a2d5b9990d333f60e5a68cda92 Mon Sep 17 00:00:00 2001 From: Mathis Date: Wed, 22 May 2024 17:04:41 +0200 Subject: [PATCH] feat: Add getSelf endpoint and improve user service This commit introduces a new endpoint, `GetSelfController`, for fetching authenticated user information in the `AuthController`. It also improves token handling by refactoring token retrieval from headers into a utility function. Additionally, the User service has been extended with new methods for getting a user by ID and for fetching follower and following information. Token UUID handling during registration has been tweaked for consistency. Improvements in logging levels and responses status codes have been made as well. --- .idea/inspectionProfiles/Project_Default.xml | 10 ++++++ package.json | 3 +- src/controllers/auth.controller.ts | 27 +++++++++++++++- src/middlewares/authentication.middleware.ts | 31 +++++++------------ src/routers/auth.router.ts | 2 ++ .../authentication/register.service.ts | 5 +-- src/services/databases/databases.service.ts | 22 ++++++++++--- src/services/databases/mariadb.service.ts | 2 +- src/services/databases/mongodb.service.ts | 2 +- src/services/users.service.ts | 17 +++++++++- src/utils/headers.util.ts | 14 +++++++++ 11 files changed, 104 insertions(+), 31 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 src/utils/headers.util.ts diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..c18c684 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/package.json b/package.json index 1497340..12e5b75 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "private": true, "license": "MIT", "scripts": { - "bun:dev": "bun run --watch src/app.ts", + "bun:dev": "bun run --allow-ffi --watch src/app.ts", "bun:check": "bunx biome check --skip-errors --apply src" }, "dependencies": { @@ -24,6 +24,7 @@ "mysql2": "^3.9.7", "randomatic": "^3.1.1", "react-hook-form": "^7.51.4", + "sharp": "^0.33.4", "tslog": "^4.9.2", "uuid": "^9.0.1" }, diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index f76914f..293fb6e 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -5,6 +5,11 @@ import {IRegisterInput} from "@interfaces/services/register.types"; import ValidatorsUtils from "@utils/validators.util"; import UsersService from "@services/users.service"; import RegisterService from "@services/authentication/register.service"; +import {HeaderUtil} from "@utils/headers.util"; +import JwtService from "@services/authentication/jwt.service"; +import {JWTPayload} from "jose"; +import {UserInDatabase} from "@interfaces/db/mariadb.interface"; +import ConvertersUtils from "@utils/converters.util"; const logs = new LogsUtils('AuthController') @@ -62,9 +67,29 @@ async function registerController(req: Request, res: Response) { }); } +async function GetSelfController(req: Request, res: Response) { + const originToken = await HeaderUtil.getToken(req) as string; + const token = await JwtService.verify(originToken) as JWTPayload; + const userInDatabases = await UsersService.get.byId(token.sub as string) as Array; + if (userInDatabases.length === 0 || !userInDatabases[0]) { + logs.warn('User not found', req.ip); + return res.status(HttpStatusCode.Gone).json({ error: 'User not found' }); + } + const User = userInDatabases[0] + return res.status(HttpStatusCode.Found).json({ + id: User.id, + username: User.username, + email: User.email, + displayName: User.display_name, + isAdmin: ConvertersUtils.bufferToBool(User.admin as Buffer), + followers: await UsersService.get.followers(User.id), + following: await UsersService.get.following(User.id), + }); +} const AuthController = { - register: registerController + register: registerController, + me: GetSelfController } export default AuthController; \ No newline at end of file diff --git a/src/middlewares/authentication.middleware.ts b/src/middlewares/authentication.middleware.ts index ae2cd79..043261b 100644 --- a/src/middlewares/authentication.middleware.ts +++ b/src/middlewares/authentication.middleware.ts @@ -3,57 +3,50 @@ import JwtService from "@services/authentication/jwt.service"; import {DatabasesService} from "@services/databases/databases.service"; import {UserInDatabase} from "@interfaces/db/mariadb.interface"; import {HttpStatusCode} from "axios"; +import {HeaderUtil} from "@utils/headers.util"; const db = new DatabasesService('OnlyDevs'); -async function getTokenFromHeader(req: Request) { - const token: string | undefined = req.headers.authorization?.split(" ")[1]; - if (!token ||token.length <= 0) { - return false - } - return token; -} - - async function UserMiddleware(req: Request, res: Response, next: NextFunction) { - const originToken = getTokenFromHeader(req); + const originToken = await HeaderUtil.getToken(req); if (!originToken) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "Unauthorized" }); + return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized" }); } const tokenPayload = await JwtService.verify(`${originToken}`); if (!tokenPayload || !tokenPayload.sub) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "Unauthorized" }); + return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized" }); } const UserFound = await db.getUserById(tokenPayload.sub) const User: UserInDatabase | undefined = UserFound[0] as UserInDatabase if (!User) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "Unauthorized, you dont exist." }); + return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized, you dont exist." }); } if (User.email_activation) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "You should verify your email first."}) + return res.status(HttpStatusCode.Forbidden).json({ message: "You should verify your email first."}) } return next() } async function AdminMiddleware(req: Request, res: Response, next: NextFunction) { - const originToken = getTokenFromHeader(req); + const originToken = await HeaderUtil.getToken(req); if (!originToken) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "Unauthorized" }); + return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized" }); } const tokenPayload = await JwtService.verify(`${originToken}`); if (!tokenPayload || !tokenPayload.sub) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "Unauthorized" }); + return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized" }); } const UserFound = await db.getUserById(tokenPayload.sub) const User: UserInDatabase | undefined = UserFound[0] as UserInDatabase if (!User) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "Unauthorized, you dont exist." }); + return res.status(HttpStatusCode.Forbidden).json({ message: "Unauthorized, you dont exist." }); } if (User.email_activation) { - return res.status(HttpStatusCode.PreconditionRequired).json({ message: "You should verify your email first."}) + return res.status(HttpStatusCode.Forbidden).json({ message: "You should verify your email first."}) } const adminState = User.admin + console.log(adminState); if (!adminState) { return res.status(HttpStatusCode.PreconditionRequired).json({ message: "Unauthorized, you are not an admin." }); diff --git a/src/routers/auth.router.ts b/src/routers/auth.router.ts index df1e91c..78d5a1f 100644 --- a/src/routers/auth.router.ts +++ b/src/routers/auth.router.ts @@ -1,10 +1,12 @@ import AuthController from "@controllers/auth.controller"; import express, {type Router} from "express"; +import {AuthMiddleware} from "src/middlewares/authentication.middleware"; const AuthRouter: Router = express.Router(); //AuthRouter.route("/login").post(AuthController.login); AuthRouter.route("/register").post(AuthController.register); +AuthRouter.route("/me").get(AuthMiddleware.user, AuthController.me); export default AuthRouter; \ No newline at end of file diff --git a/src/services/authentication/register.service.ts b/src/services/authentication/register.service.ts index a24ec55..d3b1404 100644 --- a/src/services/authentication/register.service.ts +++ b/src/services/authentication/register.service.ts @@ -10,8 +10,9 @@ const db = new DatabasesService('OnlyDevs') //TODO Logs async function registerService(data: IRegisterInput): Promise { + const uuid = v4().toString() const User: UserInDatabase = { - id: v4(), + id: `${uuid}`, username: data.username, display_name: data.displayName || data.username, hash: await CredentialService.hash(data.password), @@ -24,7 +25,7 @@ async function registerService(data: IRegisterInput): Promise { if (dbResult) { //await sendActivationEmail(User.email, User.email_activation); const token = await JwtService.sign({ - sub: User.id, + sub: uuid, iat: Date.now(), }, { alg: "HS256" diff --git a/src/services/databases/databases.service.ts b/src/services/databases/databases.service.ts index 5f62999..2b99ce1 100644 --- a/src/services/databases/databases.service.ts +++ b/src/services/databases/databases.service.ts @@ -1,7 +1,7 @@ import {MariadbService} from "@services/databases/mariadb.service"; //import {MongodbService} from "@services/databases/mongodb.service"; import {LogsUtils} from "@utils/logs.util"; -import {UserInDatabase} from "@interfaces/db/mariadb.interface"; +import {FollowInDatabase, UserInDatabase} from "@interfaces/db/mariadb.interface"; interface MariaDbStatusResult { fieldCount: number; @@ -26,29 +26,41 @@ export class DatabasesService { } async getAllUsers() { - const result: Array = await this.maria.query("SELECT * FROM users") as unknown as Array; + const result = await this.maria.query("SELECT * FROM users") as unknown as Array; this.logs.debug('Fetching users from database...', `${result?.length} user(s) found.`) return result; } async getUserById(id: string) { - const result: Array = await this.maria.execute(`SELECT * FROM users WHERE id = ?`, [id]) as unknown as Array; + const result = await this.maria.execute(`SELECT * FROM users WHERE id = ?`, [id]) as unknown as Array; this.logs.debug(`Fetching user with id ${id} from database...`, `${result?.length} user(s) found.`); return result; } async getUserByUsername(username: string) { - const result: Array = await this.maria.execute(`SELECT * FROM users WHERE username = ?`, [username]) as unknown as Array; + const result = await this.maria.execute(`SELECT * FROM users WHERE username = ?`, [username]) as unknown as Array; this.logs.debug(`Fetching user with username "${username}" from database...`, `${result?.length} user(s) found.`); return result; } async getUserByEmail(email: string) { - const result: Array = await this.maria.execute(`SELECT * FROM users WHERE email = ?`, [email]) as unknown as Array; + const result = await this.maria.execute(`SELECT * FROM users WHERE email = ?`, [email]) as unknown as Array; this.logs.debug(`Fetching user with email "${email}" from database...`, `${result?.length} user(s) found.`); return result; } + async getFollowingById(id: string) { + const result = await this.maria.execute(`SELECT * FROM follows WHERE source_id = ?`, [id]) as unknown as Array; + this.logs.debug(`Fetching followed users for user with id ${id} from database...`, `${result?.length} user(s) found.`); + return result; + } + + async getFollowersById(id:string) { + const result = await this.maria.execute(`SELECT * FROM follows WHERE target_id = ?`, [id]) as unknown as Array; + this.logs.debug(`Fetching followers for user with id ${id} from database...`, `${result?.length} user(s) found.`); + return result; + } + async insertUser(user: UserInDatabase): Promise { const factorized = await this.maria.factorize({ values: user, diff --git a/src/services/databases/mariadb.service.ts b/src/services/databases/mariadb.service.ts index 07fe910..2fb26bb 100644 --- a/src/services/databases/mariadb.service.ts +++ b/src/services/databases/mariadb.service.ts @@ -26,7 +26,7 @@ export class MariadbService { if (err) { this.logs.error(`Error connecting to MySQL:`, err); } - this.logs.info('Connected to MariaDB', this.envs.get('MYSQL_DATABASE')) + this.logs.debug('Connected to MariaDB', this.envs.get('MYSQL_DATABASE')) }); } closeConnection() { diff --git a/src/services/databases/mongodb.service.ts b/src/services/databases/mongodb.service.ts index c7122f2..a1af8ca 100644 --- a/src/services/databases/mongodb.service.ts +++ b/src/services/databases/mongodb.service.ts @@ -16,7 +16,7 @@ export class MongodbService { this.client .connect() .then(() => { - this.logs.info("Connected to MongoDB", 'All databases'); + this.logs.debug("Connected to MongoDB", 'All databases'); }) } catch (error) { this.logs.error(`Error connecting to MongoDB:`, error); diff --git a/src/services/users.service.ts b/src/services/users.service.ts index 3e2df0d..b379f14 100644 --- a/src/services/users.service.ts +++ b/src/services/users.service.ts @@ -15,10 +15,25 @@ async function getAll() { return db.getAllUsers() } +async function getById(id: string) { + return await db.getUserById(id) +} + +async function getFollowers(id: string) { + return await db.getFollowersById(id) +} + +async function getFollowing(id: string) { + return await db.getFollowingById(id) +} + const get = { byUsername: getByUsername, byEmail: getByEmail, - all: getAll + byId: getById, + all: getAll, + followers: getFollowers, + following: getFollowing } const UsersService = { diff --git a/src/utils/headers.util.ts b/src/utils/headers.util.ts new file mode 100644 index 0000000..6bbd4c8 --- /dev/null +++ b/src/utils/headers.util.ts @@ -0,0 +1,14 @@ +import type {Request} from "express"; + + +async function getTokenFromHeader(req: Request) { + const token: string | undefined = req.headers.authorization?.split(" ")[1]; + if (!token ||token.length <= 0) { + return false + } + return token; +} + +export const HeaderUtil = { + getToken: getTokenFromHeader, +} \ No newline at end of file