Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { router } from "./src/router/index.js";

// Datos del proyecto
const projectInfo = {
name: "CRM Kinder Garden",
name: "CRM Kinder Garden Backend",
description: "CRM para Gestión y Administración de una escuela",
version: "1.0.0",
authorName: "Erick Gonzalez",
Expand Down Expand Up @@ -72,6 +72,6 @@ server.on("error", (error) => {

server.listen(currentPort, () => {
console.log(
`🟢 Server is listening on port localhost:${server.address().port}`,
`🟢 API funcionando correctamente, servidor corriendo en el puerto localhost:${server.address().port}`,
);
});
6 changes: 6 additions & 0 deletions src/controllers/token/functions/token.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { refreshToken } from "../../../helpers/jwt.js";

export const RefreshToken = async (token) => {
const refresh = await refreshToken(token);
return refresh;
};
1 change: 1 addition & 0 deletions src/controllers/token/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./functions/token.controller.js";
17 changes: 8 additions & 9 deletions src/controllers/users/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
export * from "./functions/deleteUser.js";
export * from "./functions/editUser.js";
export * from "./functions/insertUsers.js";
export * from "./functions/listUsers.js";
export * from "./functions/login.js";
export * from "./functions/loginGoogle.js";
export * from "./functions/registerUser.js";
export * from "./functions/searchUser.js";
export * from "./index.js";
export * from "./functions/deleteUser.controller.js";
export * from "./functions/editUser.controller.js";
export * from "./functions/insertUsers.controller.js";
export * from "./functions/listUsers.controller.js";
export * from "./functions/login.controller.js";
export * from "./functions/loginGoogle.controller.js";
export * from "./functions/registerUser.controller.js";
export * from "./functions/searchUser.controller.js";
34 changes: 33 additions & 1 deletion src/helpers/jwt.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addHour } from "@formkit/tempo";
import { addDay, addHour } from "@formkit/tempo";
import dotenv from "dotenv";
import jwt from "jsonwebtoken";

Expand Down Expand Up @@ -28,3 +28,35 @@ export const createToken = (user) => {
};
return jwt.sign(payload, secret);
};

export const refreshToken = (token) => {
const decoded = jwt.verify(token, secret);

if (decoded.accountStatus === "Inactivo") {
throw {
statusCode: 403,
message: "Cuenta inactiva, porfavor contacta al administrador.",
code: "ACCOUNT_INACTIVE",
details:
"La cuenta esta desactivada, no se puede generar un nuevo token.",
};
}

const expirationDate = addDay(new Date(), 20);

const expirationTime = Math.floor(expirationDate.getTime() / 1000);

const payload = {
id: decoded.id,
nameUser: decoded.nameUser,
email: decoded.email,
profilePicture: decoded.profilePicture,
role: decoded.role,
accountType: decoded.accountType,
lastLogin: decoded.lastLogin,
accountStatus: decoded.accountStatus,
iat: Math.floor(Date.now() / 1000),
exp: expirationTime,
};
return jwt.sign(payload, secret);
};
11 changes: 6 additions & 5 deletions src/middleware/errorHandler.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import crypto from "node:crypto";

export const errorHandler = (err, req, res, next) => {
export const errorHandler = (err, request, response, next) => {
const status = err.statusCode || 500;
const timestamp = new Date().toISOString();
const errorId = crypto.randomUUID();

res.status(status).json({
console.log(request);
response.status(status).json({
success: false,
error: {
message: err.message || "Se produjo un error inesperado.",
Expand All @@ -16,8 +16,9 @@ export const errorHandler = (err, req, res, next) => {
timestamp,
errorId,
stack: process.env.NODE_ENV === "production" ? undefined : err.stack,
path: req.originalUrl,
method: req.method,
path: request.originalUrl,
method: request.method,
query: request.query,
},
});
};
5 changes: 0 additions & 5 deletions src/middleware/loadChalk.js

This file was deleted.

16 changes: 8 additions & 8 deletions src/middleware/rateLimitRequest.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { request, response } from "express";
import { rateLimit } from "express-rate-limit";

import { methodTooManyRequests } from "../server/serverMethods.js";

export const rateLimitRequest = (time, limit, messageRequest) => {
return rateLimit({
windowMs: time * 60 * 1000,
limit: limit,
handler: (req, res) => {
methodTooManyRequests(
req,
res,
messageRequest + `inténtelo nuevamente después de ${time} minuto(s)`,
);
handler: (request, response, next) => {
next({
statusCode: 429,
message: `${messageRequest}. Inténtelo nuevamente después de ${time} minuto(s).`,
code: "TOO_MANY_REQUESTS",
details: "Has excedido el número máximo de solicitudes permitidas.",
});
},
standardHeaders: true,
legacyHeaders: false,
Expand Down
43 changes: 23 additions & 20 deletions src/middleware/verificarToken.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
import dotenv from "dotenv";
import jwt from "jsonwebtoken";

import { methodUnauthorized } from "../server/serverMethods.js";

dotenv.config();

export const verificarToken = (req, res, next) => {
const token = req.header("Authorization");
export const verificarToken = (request, response, next) => {
const token = request.header("Authorization");

if (!token) {
return methodUnauthorized(
req,
res,
"Acceso no autorizado. Token no proporcionado.",
);
throw {
statusCode: 401,
message: "Acceso no autorizado, token no proporcionado",
code: "TOKEN_NOT_FOUND",
details:
"El token no se mando o no esta autorizado para realizar esta peticion",
};
}

const bearerToken = token.split(" ")[1];
if (!bearerToken) {
return methodUnauthorized(
req,
res,
"Acceso no autorizado. Token no proporcionado.",
);
throw {
statusCode: 401,
message: "Acceso no autorizado, token no proporcionado",
code: "TOKEN_NOT_FOUND",
details:
"El token no se mando o no esta autorizado para realizar esta peticion",
};
}

try {
const secretKey = process.env.JWT_SECRET;
const decoded = jwt.verify(bearerToken, secretKey);
req.usuario = decoded;
request.usuario = decoded;
next();
} catch (error) {
return methodUnauthorized(
req,
res,
`Acceso no autorizado. Token inválido ${error}`,
);
throw {
statusCode: 401,
message: "Acceso no autorizado: token inválido",
code: "TOKEN_INVALID",
details: error.message || "El token no pudo ser verificado",
};
}
};
2 changes: 2 additions & 0 deletions src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { apiGoogle } from "./google.route.js";
import { apiMaestros } from "./maestrosRouter.js";
import { apiPadres } from "./padresRouter.js";
import { apiEstudiantes } from "./studentsRouter.js";
import { apiToken } from "./token.routes.js";
import { apiUsuarios } from "./users.routes.js";

const router = express.Router();
Expand All @@ -23,5 +24,6 @@ const router = express.Router();
// );

router.use("/api/v1/users", apiUsuarios, apiGoogle);
router.use("/api/v1/token", apiToken);

export { router };
81 changes: 81 additions & 0 deletions src/router/token.routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import express from "express";

import { RefreshToken } from "../controllers/token/index.js";
import { verificarToken } from "../middleware/verificarToken.js";
import { methodOK } from "../server/serverMethods.js";

const apiToken = express.Router();

/**
* @swagger
* /token/refresh:
* post:
* summary: Refresca el token de sesión
* description: Genera un nuevo token de acceso si el token anterior es válido y aún no ha expirado. Se recomienda usar este endpoint desde el frontend cuando el token está por expirar.
* tags:
* - Token
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - token
* properties:
* token:
* type: string
* description: Token de acceso actual que está por expirar.
* example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
* responses:
* 200:
* description: Token actualizado exitosamente.
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: Consulta realizada correctamente
* data:
* type: string
* description: Nuevo token generado
* example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
* metadata:
* type: object
* properties:
* timestamp:
* type: string
* format: date-time
* example: 2025-07-04T08:06:58.280Z
* requestId:
* type: string
* format: uuid
* example: baeea511-fa16-437a-a122-b99a35f76bd8
* dataCount:
* type: string
* example: "1"
* 403:
* description: El usuario está inactivo. Por favor contacte al administrador.
* 500:
* description: Error interno del servidor.
*/

//POST /api/token/refresh
apiToken.post("/refresh", verificarToken, async (request, response, next) => {
try {
const { token } = request.body;
console.log(token);
const newToken = await RefreshToken(token);
console.log(newToken);
methodOK(request, response, newToken);
} catch (error) {
next(error);
}
});

export { apiToken };