diff --git a/src/helpers/http/jwt/decode/index.ts b/src/helpers/http/jwt/decode/index.ts index caed720..36aee54 100644 --- a/src/helpers/http/jwt/decode/index.ts +++ b/src/helpers/http/jwt/decode/index.ts @@ -9,7 +9,7 @@ export const jwtDecode = (payload: string) => { try { const decodedPayload = jwt.verify(payload, JWTKey); return decodedPayload as JWTAuthToken; - } catch (error) { - throw new AppError(401, "Invalid or expired token", error); + } catch { + throw new AppError(403, "Invalid or expired token"); } }; diff --git a/src/modules/auth/controllers/logout.controller.ts b/src/modules/auth/controllers/logout.controller.ts index 75a42ef..083b802 100644 --- a/src/modules/auth/controllers/logout.controller.ts +++ b/src/modules/auth/controllers/logout.controller.ts @@ -6,7 +6,7 @@ import { parse } from "cookie"; export const logoutController = async (ctx: Context) => { try { - const jwtToken = parse(ctx.request.headers.get("auth_token") || "") + const jwtToken = parse(ctx.request.headers.get("Cookie") || "") .auth_token as string; const serviceResponse = await logoutService(jwtToken); return returnWriteResponse( diff --git a/src/modules/auth/controllers/tokenValidation.controller.ts b/src/modules/auth/controllers/tokenValidation.controller.ts index 7e19545..49f641e 100644 --- a/src/modules/auth/controllers/tokenValidation.controller.ts +++ b/src/modules/auth/controllers/tokenValidation.controller.ts @@ -4,10 +4,10 @@ import { returnReadResponse } from "../../../helpers/callback/httpResponse"; import { mainErrorHandler } from "../../../helpers/error/handler"; import { parse } from "cookie"; -export const tokenValidationController = (ctx: Context) => { +export const tokenValidationController = async (ctx: Context) => { try { const { auth_token } = parse(ctx.request.headers.get("cookie") || ""); - const validationResult = tokenValidationService(auth_token as string); + const validationResult = await tokenValidationService(auth_token as string); return returnReadResponse( ctx.set, 200, diff --git a/src/modules/auth/services/http/tokenValidation.service.ts b/src/modules/auth/services/http/tokenValidation.service.ts index 602748c..f078b91 100644 --- a/src/modules/auth/services/http/tokenValidation.service.ts +++ b/src/modules/auth/services/http/tokenValidation.service.ts @@ -1,10 +1,33 @@ +import { AppError } from "../../../../helpers/error/instances/app"; import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; import { jwtDecode } from "../../../../helpers/http/jwt/decode"; +import { redis } from "../../../../utils/databases/redis/connection"; +import { checkUserSessionInDBService } from "../../../userSession/services/internal/checkUserSessionInDB.service"; +import { createUserSessionInRedisService } from "../../../userSession/services/internal/createUserSessionInRedis.service"; -export const tokenValidationService = (payload: string) => { +export const tokenValidationService = async (payload: string) => { try { - if (!payload) return null; + if (!payload || payload.trim() === "") + throw new AppError(401, "Unauthorized: No token provided"); const decoded = jwtDecode(payload); + + const redisValidationResult = await redis.hgetall( + `${process.env.APP_NAME}:users:${decoded.user.id}:sessions:${decoded.id}`, + ); + if ( + !redisValidationResult || + Object.keys(redisValidationResult).length === 0 + ) { + const dbValidationResult = await checkUserSessionInDBService(decoded.id); + if (!dbValidationResult) + throw new AppError(403, "Unauthorized: Invalid session"); + await createUserSessionInRedisService({ + userId: decoded.user.id, + sessionId: decoded.id, + validUntil: decoded.validUntil, + }); + } + return decoded; } catch (error) { ErrorForwarder(error); diff --git a/src/modules/userSession/repositories/checkUserSession.repository.ts b/src/modules/userSession/repositories/checkUserSession.repository.ts new file mode 100644 index 0000000..9a8bb31 --- /dev/null +++ b/src/modules/userSession/repositories/checkUserSession.repository.ts @@ -0,0 +1,16 @@ +import { AppError } from "../../../helpers/error/instances/app"; +import { userSessionModel } from "../userSession.model"; + +export const checkUserSessionRepository = async (sessionId: string) => { + try { + return await userSessionModel.findUnique({ + where: { + id: sessionId, + isAuthenticated: true, + deletedAt: null, + }, + }); + } catch (error) { + throw new AppError(500, "Database error during session validation", error); + } +}; diff --git a/src/modules/userSession/services/createUserSession.service.ts b/src/modules/userSession/services/createUserSession.service.ts index cefb850..a59cc6d 100644 --- a/src/modules/userSession/services/createUserSession.service.ts +++ b/src/modules/userSession/services/createUserSession.service.ts @@ -2,12 +2,12 @@ import { Prisma } from "@prisma/client"; import { UserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation/types"; import { ErrorForwarder } from "../../../helpers/error/instances/forwarder"; import { createUserSessionRepository } from "../repositories/createUserSession.repository"; -import { redis } from "../../../utils/databases/redis/connection"; import { jwtEncode } from "../../../helpers/http/jwt/encode"; +import { createUserSessionInRedisService } from "./internal/createUserSessionInRedis.service"; export const createUserSessionService = async ( userId: string, - userHeaderInfo: UserHeaderInformation + userHeaderInfo: UserHeaderInformation, ) => { try { // set the date when the token will expire @@ -29,13 +29,11 @@ export const createUserSessionService = async ( const createUserSession = await createUserSessionRepository(constructData); // caching user session data into Redis - const createRedisKey = `${process.env.APP_NAME}:users:${userId}:sessions:${createUserSession.id}`; - await redis.hset(createRedisKey, { + await createUserSessionInRedisService({ userId, sessionId: createUserSession.id, validUntil: createUserSession.validUntil, }); - await redis.expire(createRedisKey, Number(process.env.SESSION_EXPIRE)); // create a jwt token with a payload containing the created user session, then return jwt return jwtEncode(createUserSession); diff --git a/src/modules/userSession/services/internal/checkUserSessionInDB.service.ts b/src/modules/userSession/services/internal/checkUserSessionInDB.service.ts new file mode 100644 index 0000000..85ab0e3 --- /dev/null +++ b/src/modules/userSession/services/internal/checkUserSessionInDB.service.ts @@ -0,0 +1,13 @@ +import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; +import { checkUserSessionRepository } from "../../repositories/checkUserSession.repository"; + +export const checkUserSessionInDBService = async ( + sessionId: string, +): Promise => { + try { + const dbValidationResult = await checkUserSessionRepository(sessionId); + return dbValidationResult ? true : false; + } catch (error) { + ErrorForwarder(error); + } +}; diff --git a/src/modules/userSession/services/internal/createUserSessionInRedis.service.ts b/src/modules/userSession/services/internal/createUserSessionInRedis.service.ts new file mode 100644 index 0000000..77cf6d8 --- /dev/null +++ b/src/modules/userSession/services/internal/createUserSessionInRedis.service.ts @@ -0,0 +1,30 @@ +import { AppError } from "../../../../helpers/error/instances/app"; +import { redis } from "../../../../utils/databases/redis/connection"; + +export const createUserSessionInRedisService = async ({ + userId, + sessionId, + validUntil, + exp, +}: { + userId: string; + sessionId: string; + validUntil?: Date; + exp?: number; +}) => { + try { + const createRedisKey = `${process.env.APP_NAME}:users:${userId}:sessions:${sessionId}`; + await redis.hset(createRedisKey, { + userId, + sessionId, + validUntil: validUntil, + }); + await redis.expire( + createRedisKey, + exp || Number(process.env.SESSION_CACHE_EXPIRE!), + ); + return true; + } catch (error) { + throw new AppError(500, "Error creating user session in Redis", error); + } +};