From 34526dc0cf5ec5224bdd14170856f43dff553056 Mon Sep 17 00:00:00 2001 From: AgustinRodriguez-Andes <63318331+agustin1996ra@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:10:14 -0300 Subject: [PATCH] =?UTF-8?q?REC-208:=20Vencimiento=20de=20sesi=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.ts | 9 +-- src/controllers/auth.controller.ts | 13 ++-- src/database/dbconfig.ts | 3 +- .../passport-config-andes.middleware.ts | 49 ++++++++------ src/middlewares/passport-config.middleware.ts | 66 +++++++++++-------- src/server.ts | 6 +- 6 files changed, 79 insertions(+), 67 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 0a9f3ce..db35b5e 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -2,13 +2,6 @@ import dotenv from 'dotenv'; dotenv.config(); -export const env = { - API_URI_PREFIX: '/api', - JWT_SECRET: 'e18a33b0-9866-4867-800a-d6ffcd8f1cbd', - TOKEN_LIFETIME: 1, - MONGODB_CONNECTION: 'mongodb://localhost/recetar' -}; - export const httpCodes = { UNAUTHORIZED: 401, FORBIDDEN: 403, @@ -19,4 +12,4 @@ export const httpCodes = { INTERNAL_SERVER_ERROR: 500, OK: 200, NOT_FOUND: 404, - }; +}; diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 8b8a4e7..08d26f2 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import * as JWT from 'jsonwebtoken'; -import { env, httpCodes } from '../config/config'; +import { httpCodes } from '../config/config'; import { v4 as uuidv4 } from 'uuid'; import IUser from '../interfaces/user.interface'; import User from '../models/user.model'; @@ -309,8 +309,9 @@ class AuthController { usrn: user.username, bsname: user.businessName, rl: roles, - iat: new Date().getTime() - }, (process.env.JWT_SECRET || env.JWT_SECRET), { + iat: moment().unix(), + exp: moment().add((process.env.TOKEN_LIFETIME || 12), 'hours').unix() + }, (process.env.JWT_SECRET || ''), { algorithm: 'HS256' }); return res.status(200).json({ jwt: token }); @@ -328,9 +329,9 @@ class AuthController { usrn: username, bsname: businessName, rl: role, - iat: new Date().getTime(), - exp: new Date().setDate(new Date().getDate() + env.TOKEN_LIFETIME) - }, (process.env.JWT_SECRET || env.JWT_SECRET), { + iat: moment().unix(), + exp: moment().add((process.env.TOKEN_LIFETIME || 12), 'hours').unix() + }, (process.env.JWT_SECRET || ''), { algorithm: 'HS256' }); return token; diff --git a/src/database/dbconfig.ts b/src/database/dbconfig.ts index a39d448..8a75650 100644 --- a/src/database/dbconfig.ts +++ b/src/database/dbconfig.ts @@ -1,8 +1,7 @@ import mongoose from 'mongoose'; -import { env } from '../config/config'; export const initializeMongo = async (): Promise => { - const MONGO_URI = `${(process.env.MONGODB_URI || env.MONGODB_CONNECTION)}`; + const MONGO_URI = `${(process.env.MONGODB_URI)}`; try { await mongoose.connect(MONGO_URI, { useNewUrlParser: true, diff --git a/src/middlewares/passport-config-andes.middleware.ts b/src/middlewares/passport-config-andes.middleware.ts index 465331a..50e3d35 100644 --- a/src/middlewares/passport-config-andes.middleware.ts +++ b/src/middlewares/passport-config-andes.middleware.ts @@ -1,55 +1,66 @@ -import {Request, Response, NextFunction} from 'express'; +import { Request, Response, NextFunction } from 'express'; import passport from 'passport'; import passportJwt from 'passport-jwt'; -import { env, httpCodes } from '../config/config'; +import { httpCodes } from '../config/config'; import User from '../models/user.model'; import IUser from '../interfaces/user.interface'; const JwtStrategy = passportJwt.Strategy; const ExtractJwt = passportJwt.ExtractJwt; +const IAT_FUTURE_SKEW_SECONDS = 60; // Config passport JWT strategy // We will use Bearer token to authenticate // This configuration checks: // -token expiration // -user exists +// -token iat is not in the future passport.use(new JwtStrategy({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: (process.env.JWT_SECRET || env.JWT_SECRET) -}, async (payload, done: (err?: any, user?: IUser | boolean, info?: {code: number, message: string}) => any | Response) => { - try{ + secretOrKey: (process.env.JWT_SECRET) +}, async (payload, done: (err?: any, user?: IUser | boolean, info?: { code: number; message: string }) => any | Response) => { + try { // find the user specified in token const user = await User.findOne({ _id: payload.sub }).select('_id'); - // if user doesn't exists, handle it - if(!user){ - return done(null, false, {code: httpCodes.EXPECTATION_FAILED, message: 'Debe iniciar sesión'}); + if (!user) { + return done(null, false, { code: httpCodes.EXPECTATION_FAILED, message: 'Debe iniciar sesión' }); + } + const nowUnix = Math.floor(Date.now() / 1000); + if (typeof payload.exp === 'number' && payload.exp < nowUnix) { + return done(null, false, { code: httpCodes.EXPIRED_TOKEN, message: 'El token ha expirado' }); + } + + if (typeof payload.iat === 'number' && payload.iat > nowUnix + IAT_FUTURE_SKEW_SECONDS) { + if (payload.sub) { + await User.updateOne({ _id: payload.sub }, { refreshToken: '' }); + } + return done(null, false, { code: httpCodes.UNAUTHORIZED, message: 'Sesión inválida, debe volver a iniciar sesión' }); } // otherwise, return the user done(null, user); - }catch(err){ + } catch (err) { + // eslint-disable-next-line no-console console.log('in error'); done(err, false); } })); - const authenticationMiddleware = (req: Request, res: Response, next: NextFunction, authenticationType: string) => { - passport.authenticate(authenticationType, {session: false}, (err, user: IUser | boolean, info?: {code: number, message: string}): any | Response => { - try{ + passport.authenticate(authenticationType, { session: false }, (err, user: IUser | boolean, info?: { code: number; message: string }): any | Response => { + try { - if (err) return next(err) + if (err) { return next(err); } - if(typeof(info) !== 'undefined') return res.status(info.code).json({message: info.message}); + if (typeof (info) !== 'undefined') { return res.status(info.code).json({ message: info.message }); } req.user = user; next(); - }catch(error){ - if(error.code == 'ERR_HTTP_INVALID_STATUS_CODE') return res.status(httpCodes.EXPECTATION_FAILED).json({message: 'Debe iniciar sesión'}); - - return res.status(500).json('Server Error') + } catch (error) { + if (error.code === 'ERR_HTTP_INVALID_STATUS_CODE') {return res.status(httpCodes.EXPECTATION_FAILED).json({ message: 'Debe iniciar sesión' });} + return res.status(500).json('Server Error'); } })(req, res, next); @@ -57,4 +68,4 @@ const authenticationMiddleware = (req: Request, res: Response, next: NextFunctio export const checkAuthAndes = (req: Request, res: Response, next: NextFunction) => { authenticationMiddleware(req, res, next, 'jwt'); -} +}; diff --git a/src/middlewares/passport-config.middleware.ts b/src/middlewares/passport-config.middleware.ts index a64c801..95f65cd 100644 --- a/src/middlewares/passport-config.middleware.ts +++ b/src/middlewares/passport-config.middleware.ts @@ -1,40 +1,50 @@ -import {Request, Response, NextFunction} from 'express'; +import { Request, Response, NextFunction } from 'express'; import passport from 'passport'; import passportJwt from 'passport-jwt'; import passportLocal from 'passport-local'; -import { env, httpCodes } from '../config/config'; +import { httpCodes } from '../config/config'; import User from '../models/user.model'; import IUser from '../interfaces/user.interface'; const JwtStrategy = passportJwt.Strategy; const LocalStrategy = passportLocal.Strategy; const ExtractJwt = passportJwt.ExtractJwt; +const IAT_FUTURE_SKEW_SECONDS = 60; // Config passport JWT strategy // We will use Bearer token to authenticate // This configuration checks: // -token expiration // -user exists -passport.use(new JwtStrategy({ +passport.use('jwt', new JwtStrategy({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: (process.env.JWT_SECRET || env.JWT_SECRET) -}, async (payload, done: (err?: any, user?: IUser | boolean, info?: {code: number, message: string}) => any | Response) => { - try{ - const expirationDate = new Date(payload.exp); - if(expirationDate < new Date()) { - return done(null, false, {code: httpCodes.EXPIRED_TOKEN, message: 'El token ha expirado'}); + secretOrKey: (process.env.JWT_SECRET) +}, async (payload, done: (err?: any, user?: IUser | boolean, info?: { code: number; message: string }) => any | Response) => { + try { + const nowUnix = Math.floor(Date.now() / 1000); + if (typeof payload.exp === 'number' && payload.exp < nowUnix) { + return done(null, false, { code: httpCodes.EXPIRED_TOKEN, message: 'El token ha expirado' }); } + + if (typeof payload.iat === 'number' && payload.iat > nowUnix + IAT_FUTURE_SKEW_SECONDS) { + if (payload.sub) { + await User.updateOne({ _id: payload.sub }, { refreshToken: '' }); + } + return done(null, false, { code: httpCodes.UNAUTHORIZED, message: 'Sesión inválida, debe volver a iniciar sesión' }); + } + // find the user specified in token const user = await User.findOne({ _id: payload.sub }).select('_id'); // if user doesn't exists, handle it - if(!user){ - return done(null, false, {code: httpCodes.EXPECTATION_FAILED, message: 'Debe iniciar sesión'}); + if (!user) { + return done(null, false, { code: httpCodes.EXPECTATION_FAILED, message: 'Debe iniciar sesión' }); } // otherwise, return the user done(null, user); - }catch(err){ + } catch (err) { + // eslint-disable-next-line no-console console.log('in error'); done(err, false); } @@ -47,16 +57,16 @@ passport.use(new JwtStrategy({ passport.use(new LocalStrategy({ usernameField: 'identifier', passwordField: 'password' -}, async (identifier, password, done: (err?: any, user?: IUser | boolean, info?: {code: number, message: string}) => any | Response ) => { - try{ +}, async (identifier, password, done: (err?: any, user?: IUser | boolean, info?: { code: number; message: string }) => any | Response) => { + try { // find the user given the identifier let user = await User.findOne({ email: identifier }); // if not, handle it - if(!user){ + if (!user) { user = await User.findOne({ username: identifier }); - if(!user){ - return done(null, false, {code: httpCodes.UNAUTHORIZED, message: 'El usuario o contraseña que has ingresado es incorrecto. Por favor intenta de nuevo.'}); + if (!user) { + return done(null, false, { code: httpCodes.UNAUTHORIZED, message: 'El usuario o contraseña que has ingresado es incorrecto. Por favor intenta de nuevo.' }); } } @@ -64,30 +74,30 @@ passport.use(new LocalStrategy({ const isMatch = await user.schema.methods.isValidPassword(user, password); // if not, handle it - if(!isMatch){ - return done(null, false, {code: httpCodes.UNAUTHORIZED, message: 'El usuario o contraseña que has ingresado es incorrecto. Por favor intenta de nuevo.'}); + if (!isMatch) { + return done(null, false, { code: httpCodes.UNAUTHORIZED, message: 'El usuario o contraseña que has ingresado es incorrecto. Por favor intenta de nuevo.' }); } // otherwise, return the user done(null, user); - }catch(err){ + } catch (err) { done(err, false); } })); const authenticationMiddleware = (req: Request, res: Response, next: NextFunction, authenticationType: string) => { - passport.authenticate(authenticationType, {session: false}, (err, user: IUser | boolean, info?: {code: number, message: string}): any | Response => { - try{ + passport.authenticate(authenticationType, { session: false }, (err, user: IUser | boolean, info?: { code: number; message: string }): any | Response => { + try { - if (err) return next(err) + if (err) { return next(err); } - if(typeof(info) !== 'undefined') return res.status(info.code).json({message: info.message}); + if (typeof (info) !== 'undefined') { return res.status(info.code).json({ message: info.message }); } req.user = user; next(); - }catch(error){ - if(error.code == 'ERR_HTTP_INVALID_STATUS_CODE') return res.status(httpCodes.EXPECTATION_FAILED).json({message: 'Debe iniciar sesión'}); + } catch (error) { + if (error.code == 'ERR_HTTP_INVALID_STATUS_CODE') return res.status(httpCodes.EXPECTATION_FAILED).json({ message: 'Debe iniciar sesión' }); - return res.status(500).json('Server Error') + return res.status(500).json({ message: 'Server Error' }); } })(req, res, next); @@ -100,4 +110,4 @@ export const passportMiddlewareLocal = (req: Request, res: Response, next: NextF export const checkAuth = (req: Request, res: Response, next: NextFunction) => { authenticationMiddleware(req, res, next, 'jwt'); -} +}; diff --git a/src/server.ts b/src/server.ts index fe001cd..f0b0ea5 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,8 +6,6 @@ import compression from 'compression'; import { errorHandler } from './middlewares/error.middleware'; import { notFoundHandler } from './middlewares/notFound.middleware'; import * as db from './database/dbconfig'; -// config -import { env } from './config/config'; // services import routes from './routes/routes'; @@ -41,7 +39,7 @@ class Server { } routes() { - this.app.use(`${(process.env.API_URI_PRFIX || env.API_URI_PREFIX)}`, routes); + this.app.use(`${(process.env.API_URI_PREFIX)}`, routes); } async start() { @@ -50,7 +48,7 @@ class Server { // eslint-disable-next-line no-console console.log(`🚀 API Server running on port ${this.app.get('port')}`); // eslint-disable-next-line no-console - console.log(`📋 API disponible en: http://localhost:${this.app.get('port')}${env.API_URI_PREFIX}`); + console.log(`📋 API disponible en: http://localhost:${this.app.get('port')}${process.env.API_URI_PREFIX}`); }); }