add:module:auth:*logout | add logout module and clean all session in system
This commit is contained in:
31
src/modules/auth/controller/logout.controller.ts
Normal file
31
src/modules/auth/controller/logout.controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2,9 +2,11 @@ import Elysia from "elysia";
|
|||||||
import { loginWithPassword } from "./controller/loginWithPassword.controller";
|
import { loginWithPassword } from "./controller/loginWithPassword.controller";
|
||||||
import { authMiddleware } from "../../middleware/auth.middleware";
|
import { authMiddleware } from "../../middleware/auth.middleware";
|
||||||
import { authVerification } from "./controller/authVerification.controller";
|
import { authVerification } from "./controller/authVerification.controller";
|
||||||
|
import { logoutController } from "./controller/logout.controller";
|
||||||
|
|
||||||
export const authModule = new Elysia({ prefix: "/auth" })
|
export const authModule = new Elysia({ prefix: "/auth" })
|
||||||
.post("/legacy", loginWithPassword)
|
.post("/legacy", loginWithPassword)
|
||||||
.post("/verification", authVerification, {
|
.post("/verification", authVerification, {
|
||||||
beforeHandle: authMiddleware,
|
beforeHandle: authMiddleware,
|
||||||
});
|
})
|
||||||
|
.post("/logout", logoutController);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { Prisma } from "@prisma/client";
|
||||||
import { AppError } from "../../../helpers/error/instances/app";
|
import { AppError } from "../../../helpers/error/instances/app";
|
||||||
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
|
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
|
||||||
import { jwtDecode } from "../../../helpers/http/jwt/decode";
|
import { jwtDecode } from "../../../helpers/http/jwt/decode";
|
||||||
|
|||||||
1
src/modules/auth/services/loginFromSystem.service.ts
Normal file
1
src/modules/auth/services/loginFromSystem.service.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const loginFromSystemService = (userId: string) => {};
|
||||||
15
src/modules/auth/services/logout.service.ts
Normal file
15
src/modules/auth/services/logout.service.ts
Normal 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");
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -7,6 +7,7 @@ import { Context } from "elysia";
|
|||||||
import { createUserService } from "../services/createUser.service";
|
import { createUserService } from "../services/createUser.service";
|
||||||
import { mainErrorHandler } from "../../../helpers/error/handler";
|
import { mainErrorHandler } from "../../../helpers/error/handler";
|
||||||
import { createUserSchema } from "../schemas/createUser.schema";
|
import { createUserSchema } from "../schemas/createUser.schema";
|
||||||
|
import { getCookie } from "../../../helpers/http/userHeader/cookies/getCookies";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function createUser
|
* @function createUser
|
||||||
@ -30,6 +31,19 @@ import { createUserSchema } from "../schemas/createUser.schema";
|
|||||||
export const createUserController = async (
|
export const createUserController = async (
|
||||||
ctx: Context & { body: Prisma.UserCreateInput }
|
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
|
// Validate the user input using a validation schema
|
||||||
const { error } = createUserSchema.validate(ctx.body);
|
const { error } = createUserSchema.validate(ctx.body);
|
||||||
if (error)
|
if (error)
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export const editUserService = async (
|
|||||||
"The username or email has already taken by another user."
|
"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> = {
|
const fieldsToUpdate: Partial<Prisma.UserUpdateInput> = {
|
||||||
...(payload.username && payload.username !== jwtSession.user.username
|
...(payload.username && payload.username !== jwtSession.user.username
|
||||||
? { username: payload.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(
|
const updateUser = await updateUserRepo(
|
||||||
jwtSession.user.username,
|
jwtSession.user.username,
|
||||||
fieldsToUpdate
|
fieldsToUpdate
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -19,7 +19,6 @@ export const findUniqueUserSessionInDBRepo = async (identifier: string) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
omit: {
|
omit: {
|
||||||
deletedAt: true,
|
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,7 +20,6 @@ export const createUserSessionRepo = async (
|
|||||||
},
|
},
|
||||||
omit: {
|
omit: {
|
||||||
lastOnline: true,
|
lastOnline: true,
|
||||||
deletedAt: true,
|
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export const storeUserSessionToCacheRepo = async (
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await redis.set(
|
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),
|
String(userSession.validUntil),
|
||||||
"EX",
|
"EX",
|
||||||
timeExpires
|
timeExpires
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export const checkUserSessionInCacheService = async (
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
// Construct the Redis key name using the userId and sessionId
|
// 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
|
// Check if the user session exists in Redis
|
||||||
const userSessionInRedis = await checkUserSessionInCacheRepo(redisKeyName);
|
const userSessionInRedis = await checkUserSessionInCacheRepo(redisKeyName);
|
||||||
|
|||||||
@ -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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -10,6 +10,7 @@ export const getUserSessionFromDBService = async (identifier: string) => {
|
|||||||
if (
|
if (
|
||||||
!userSession ||
|
!userSession ||
|
||||||
!userSession.isAuthenticated ||
|
!userSession.isAuthenticated ||
|
||||||
|
userSession.deletedAt ||
|
||||||
new Date(userSession.validUntil) < new Date()
|
new Date(userSession.validUntil) < new Date()
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user