add:module:auth:*logout | add logout module and clean all session in system

This commit is contained in:
rafiarrafif
2025-06-19 17:16:54 +07:00
parent fdfafcd2e0
commit ac82676505
15 changed files with 130 additions and 5 deletions

View File

@ -0,0 +1,31 @@
import { Context } from "elysia";
import { getCookie } from "../../../helpers/http/userHeader/cookies/getCookies";
import { clearCookies } from "../../../helpers/http/userHeader/cookies/clearCookies";
import { mainErrorHandler } from "../../../helpers/error/handler";
import { COOKIE_KEYS } from "../../../constants/cookie.keys";
import {
returnErrorResponse,
returnWriteResponse,
} from "../../../helpers/callback/httpResponse";
import { logoutService } from "../services/logout.service";
export const logoutController = async (ctx: Context) => {
try {
const userCookie = getCookie(ctx);
if (!userCookie || !userCookie.auth_token) {
return returnErrorResponse(ctx.set, 401, "You're not logged in yet");
}
const clearSession = logoutService(userCookie.auth_token);
clearCookies(ctx.set, [COOKIE_KEYS.AUTH]);
return returnWriteResponse(
ctx.set,
200,
"Successfully logged out",
clearSession
);
} catch (error) {
return mainErrorHandler(ctx.set, error);
}
};

View File

@ -2,9 +2,11 @@ import Elysia from "elysia";
import { loginWithPassword } from "./controller/loginWithPassword.controller";
import { authMiddleware } from "../../middleware/auth.middleware";
import { authVerification } from "./controller/authVerification.controller";
import { logoutController } from "./controller/logout.controller";
export const authModule = new Elysia({ prefix: "/auth" })
.post("/legacy", loginWithPassword)
.post("/verification", authVerification, {
beforeHandle: authMiddleware,
});
})
.post("/logout", logoutController);

View File

@ -1,3 +1,4 @@
import { Prisma } from "@prisma/client";
import { AppError } from "../../../helpers/error/instances/app";
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
import { jwtDecode } from "../../../helpers/http/jwt/decode";

View File

@ -0,0 +1 @@
export const loginFromSystemService = (userId: string) => {};

View File

@ -0,0 +1,15 @@
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
import { jwtDecode } from "../../../helpers/http/jwt/decode";
import { deleteUserSessionInCacheAndDBService } from "../../userSession/services/deleteUserSessionInCacheAndDB.service";
export const logoutService = async (userCookie: string) => {
try {
const jwtToken = jwtDecode(userCookie);
const deleteUserSessionInCacheAndDB =
deleteUserSessionInCacheAndDBService(jwtToken);
return deleteUserSessionInCacheAndDB;
} catch (error) {
ErrorForwarder(error, 500, "Logout service had encountered error");
}
};

View File

@ -7,6 +7,7 @@ import { Context } from "elysia";
import { createUserService } from "../services/createUser.service";
import { mainErrorHandler } from "../../../helpers/error/handler";
import { createUserSchema } from "../schemas/createUser.schema";
import { getCookie } from "../../../helpers/http/userHeader/cookies/getCookies";
/**
* @function createUser
@ -30,6 +31,19 @@ import { createUserSchema } from "../schemas/createUser.schema";
export const createUserController = async (
ctx: Context & { body: Prisma.UserCreateInput }
) => {
// Check if the user is already logged in by checking the auth token in cookies. If the user is logged in, return an error response
try {
const cookie = getCookie(ctx);
if (cookie && cookie.auth_token)
return returnErrorResponse(
ctx.set,
401,
"You are already logged in. Please log out first if you want to create a new account."
);
} catch (_) {
// Pass
}
// Validate the user input using a validation schema
const { error } = createUserSchema.validate(ctx.body);
if (error)

View File

@ -25,6 +25,7 @@ export const editUserService = async (
"The username or email has already taken by another user."
);
// Prepare the fields to update, only include fields that are provided in the payload
const fieldsToUpdate: Partial<Prisma.UserUpdateInput> = {
...(payload.username && payload.username !== jwtSession.user.username
? { username: payload.username }
@ -53,6 +54,7 @@ export const editUserService = async (
: {}),
};
// Update the user in the database, use username from the JWT session to find the user
const updateUser = await updateUserRepo(
jwtSession.user.username,
fieldsToUpdate

View File

@ -0,0 +1,16 @@
import { AppError } from "../../../helpers/error/instances/app";
import { redis } from "../../../utils/databases/redis/connection";
export const deleteUserSessionFromCacheRepo = async (
userId: string,
sessionId: string
) => {
try {
const deleteUserSessionFromCache = redis.del(
`${process.env.APP_NAME}:users:${userId}:sessions:${sessionId}`
);
return deleteUserSessionFromCache;
} catch (error) {
throw new AppError(500, "Error while remove data from cache", error);
}
};

View File

@ -0,0 +1,19 @@
import { AppError } from "../../../helpers/error/instances/app";
import { prisma } from "../../../utils/databases/prisma/connection";
export const deleteUserSessionFromDBRepo = async (sessionId: string) => {
try {
const deleteUserSessionFromCacheDB = await prisma.userSession.update({
where: {
id: sessionId,
},
data: {
deletedAt: new Date(),
},
});
return deleteUserSessionFromCacheDB;
} catch (error) {
throw new AppError(500, "Error while remove delete from database", error);
}
};

View File

@ -19,7 +19,6 @@ export const findUniqueUserSessionInDBRepo = async (identifier: string) => {
},
},
omit: {
deletedAt: true,
updatedAt: true,
},
});

View File

@ -20,7 +20,6 @@ export const createUserSessionRepo = async (
},
omit: {
lastOnline: true,
deletedAt: true,
createdAt: true,
updatedAt: true,
},

View File

@ -8,7 +8,7 @@ export const storeUserSessionToCacheRepo = async (
) => {
try {
await redis.set(
`${process.env.app_name}:users:${userSession.userId}:sessions:${userSession.id}`,
`${process.env.APP_NAME}:users:${userSession.userId}:sessions:${userSession.id}`,
String(userSession.validUntil),
"EX",
timeExpires

View File

@ -7,7 +7,7 @@ export const checkUserSessionInCacheService = async (
) => {
try {
// Construct the Redis key name using the userId and sessionId
const redisKeyName = `${process.env.app_name}:users:${userId}:sessions:${sessionId}`;
const redisKeyName = `${process.env.APP_NAME}:users:${userId}:sessions:${sessionId}`;
// Check if the user session exists in Redis
const userSessionInRedis = await checkUserSessionInCacheRepo(redisKeyName);

View File

@ -0,0 +1,25 @@
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
import { JWTAuthToken } from "../../../helpers/http/jwt/decode/types";
import { deleteUserSessionFromCacheRepo } from "../repositories/deleteUserSessionFromCache.repository";
import { deleteUserSessionFromDBRepo } from "../repositories/deleteUserSessionFromDB.repository";
export const deleteUserSessionInCacheAndDBService = async (
jwtToken: JWTAuthToken
) => {
try {
const userId = jwtToken.userId;
const sessionId = jwtToken.id;
await deleteUserSessionFromCacheRepo(userId, sessionId);
const deleteUserSessionFromDB = await deleteUserSessionFromDBRepo(
sessionId
);
return deleteUserSessionFromDB;
} catch (error) {
ErrorForwarder(
error,
500,
"Delete user session service had encountered error"
);
}
};

View File

@ -10,6 +10,7 @@ export const getUserSessionFromDBService = async (identifier: string) => {
if (
!userSession ||
!userSession.isAuthenticated ||
userSession.deletedAt ||
new Date(userSession.validUntil) < new Date()
)
return false;