From 7e435452142635ee30407db72f86adf7d7e4c23e Mon Sep 17 00:00:00 2001 From: Victoria Rodriguez Date: Tue, 5 Aug 2025 11:11:39 -0300 Subject: [PATCH 1/2] docs: Se actualiza el readme, con las rutas disponibles --- README.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c6c00ef..912fed2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,110 @@ -# Obelisco-api +# Proyecto Node.js + MariaDB -## Crear base de datos +Este proyecto implementa un backend desarrollado en Node.js con TypeScript, utilizando MariaDB como base de datos. A continuación se detallan los pasos para configurar, instalar dependencias y ejecutar el entorno en modo producción. -En psql o similar: -CREATE DATABASE obelisco_db_testing; +--- -## Seed base de datos +## 📦 Requisitos -npm run seed +- Node.js (v18 o superior) +- npm (v9 o superior) +- MariaDB (v11.8.2 o compatible) + +--- + +## 🗃️ Base de datos + +### 1. Crear la base de datos y tablas + +El archivo de configuración SQL se encuentra en: + +database/config.sql + +Podés ejecutar el script usando el cliente de MariaDB: + +```bash + +mysql -u tu_usuario -p < database/config.sql + +``` + +--- + +### Esto creará: + +- La base de datos +- Las tablas necesarias +- Los registros iniciales + +## Levantar el proyecto + +```bash + +cd source/ + +``` + +### Instalar dependencias + +```bash + +npm install + +``` + +## Configurar las variables de entorno + +Creá un archivo .env en la raíz del directorio source/ con las siguientes variables: + +```bash + +env +NODE_ENV=production +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_USER=tu_usuario +DB_PASSWORD=tu_password +DB_NAME=nombre_de_base + +``` + +Asegurate de que NODE_ENV esté en production para el entorno productivo. + +## Iniciar el servidor + +```bash + +npm start + +``` + +📁 Estructura del proyecto +├── database/ +│ └── config.sql # Script de creación de base de datos y tablas +├── source/ +│ ├── dist/ # Código compilado (si está presente) +│ ├── .env # Variables de entorno +│ ├── package.json +│ └── ... + +### RUTA RAIZ + +```bash +# Ruta raíz +http://localhost:3000/api/ +``` + +```bash +# Ruta no existente +http://localhost:3000/api/ +``` + +```bash +# Ruta footer completo +http://localhost:3000/api/footer/complete +``` + +```bash +# Ruta footer legales +http://localhost:3000/api/footer/legales +``` From 34f98b8fd04b226de2e0d50f6ddda0aa0c9383e7 Mon Sep 17 00:00:00 2001 From: Mathias Barbosa Velazquez Date: Tue, 16 Sep 2025 13:36:46 -0300 Subject: [PATCH 2/2] feat: jwt, login, auth in header --- api.http | 9 +- package-lock.json | 113 ++++++++++++++++++++++ package.json | 2 + src/controllers/auth.ts | 29 ++++++ src/controllers/{headeer.ts => header.ts} | 2 +- src/middlewares/jwt.ts | 25 +++++ src/routes/auth.ts | 8 ++ src/routes/header.ts | 5 +- src/routes/index.ts | 4 +- src/services/auth.ts | 28 ++++++ src/utils/index.ts | 18 ++++ src/utils/types.ts | 16 +++ 12 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 src/controllers/auth.ts rename src/controllers/{headeer.ts => header.ts} (89%) create mode 100644 src/middlewares/jwt.ts create mode 100644 src/routes/auth.ts create mode 100644 src/services/auth.ts diff --git a/api.http b/api.http index dcfc53c..3364eff 100644 --- a/api.http +++ b/api.http @@ -12,4 +12,11 @@ GET http://localhost:3000/api/footer/legales ### RUTA header -GET http://localhost:3000/api/header/complete \ No newline at end of file +GET http://localhost:3000/api/header/complete +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywidXJsIjoiaHR0cHM6Ly9taS1mcm9udGVuZC5jb20iLCJpYXQiOjE3NTc5NjIwNzIsImV4cCI6MTc1Nzk2NTY3Mn0.76rmtC6h_gScmso5sKT5g0cVWZAdsDYgdopAabap5Yg + + +### RUTA AUTH +GET http://localhost:3000/api/auth/login +Origin: https://mi-frontend.com +Referer: https://mi-frontend.com/dashboard \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a5d28e7..63b3752 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", "mariadb": "^3.4.2", "module-alias": "^2.2.3", "mysql2": "^3.14.1", @@ -25,6 +26,7 @@ "devDependencies": { "@types/cors": "^2.8.18", "@types/express": "^5.0.1", + "@types/jsonwebtoken": "^9.0.10", "@types/module-alias": "^2.0.4", "@types/node": "^22.15.3", "@types/swagger-jsdoc": "^6.0.4", @@ -244,6 +246,17 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -471,6 +484,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -703,6 +722,15 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1170,6 +1198,49 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1183,6 +1254,18 @@ "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -1190,12 +1273,42 @@ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "license": "MIT" }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", diff --git a/package.json b/package.json index 97507f9..40b6878 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "devDependencies": { "@types/cors": "^2.8.18", "@types/express": "^5.0.1", + "@types/jsonwebtoken": "^9.0.10", "@types/module-alias": "^2.0.4", "@types/node": "^22.15.3", "@types/swagger-jsdoc": "^6.0.4", @@ -35,6 +36,7 @@ "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", "mariadb": "^3.4.2", "module-alias": "^2.2.3", "mysql2": "^3.14.1", diff --git a/src/controllers/auth.ts b/src/controllers/auth.ts new file mode 100644 index 0000000..739bd05 --- /dev/null +++ b/src/controllers/auth.ts @@ -0,0 +1,29 @@ +import { Request, Response } from 'express'; +import { login as loginS } from '@services/auth'; +import sendResponse from '@utils/sendResponse'; + +const login = (req: Request, res: Response) => { + // const referer = req.get('referer'); // o "referrer" + const origin = req.get('origin'); + if (!origin) { + sendResponse(res, 400, { + status: 'error', + message: 'API Obelisco: No se espesifico un origen', + }); + return; + } + try { + const response = loginS({ credential: origin }); + sendResponse(res, 200, { + token: response, + }); + } catch (error) { + console.log('[ERROR]: ', error); + sendResponse(res, 500, { + status: 'error', + message: 'API Obelisco: Error al intentar validar credencial', + }); + } +}; + +export { login }; diff --git a/src/controllers/headeer.ts b/src/controllers/header.ts similarity index 89% rename from src/controllers/headeer.ts rename to src/controllers/header.ts index 9ec4d3e..00fa662 100644 --- a/src/controllers/headeer.ts +++ b/src/controllers/header.ts @@ -12,7 +12,7 @@ export const getComplete = async (req: Request, res: Response) => { } catch (error) { sendResponse(res, 500, { status: 'error', - message: 'API Obelisco: Error al obtener footer.', + message: 'API Obelisco: Error al obtener headeer.', }); } }; diff --git a/src/middlewares/jwt.ts b/src/middlewares/jwt.ts new file mode 100644 index 0000000..df424a4 --- /dev/null +++ b/src/middlewares/jwt.ts @@ -0,0 +1,25 @@ +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import 'dotenv/config'; +import { JWT_PAYLOAD } from '@utils/types'; + +const verifyToken = (req: Request, res: Response, next: NextFunction) => { + const token = req.headers.authorization?.split(' ')[1]; + + if (!token) { + return next(new Error('No token')); + } + + try { + const decoded = jwt.verify( + token, + process.env.JWT_SECRET || '' + ) as JWT_PAYLOAD; + req.user = decoded; + next(); + } catch (error) { + return next(new Error('Invalid token')); + } +}; + +export { verifyToken }; diff --git a/src/routes/auth.ts b/src/routes/auth.ts new file mode 100644 index 0000000..b898671 --- /dev/null +++ b/src/routes/auth.ts @@ -0,0 +1,8 @@ +import { login } from '@controllers/auth'; +import express from 'express'; + +const router = express.Router(); + +router.get('/login', login); + +export default router; diff --git a/src/routes/header.ts b/src/routes/header.ts index 1163577..f244f6d 100644 --- a/src/routes/header.ts +++ b/src/routes/header.ts @@ -1,8 +1,9 @@ -import { getComplete } from '@controllers/headeer'; +import { getComplete } from '@controllers/header'; +import { verifyToken } from '@middlewares/jwt'; import express from 'express'; const router = express.Router(); -router.get('/complete', getComplete); +router.get('/complete', /**/ verifyToken, getComplete); export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts index 250af51..8a2ec4b 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -2,14 +2,16 @@ import { Router } from 'express'; import footerRoutes from './footer'; import headerRoutes from './header'; +import authRoutes from './auth'; import sendResponse from '@utils/sendResponse'; const router = Router(); router.use('/footer', footerRoutes); router.use('/header', headerRoutes); +router.use('/auth', authRoutes); router.get('/', (req, res) => { - sendResponse(res, 200, { message: 'API Obelisco' }); + sendResponse(res, 200, { message: 'API Obelisco: Service 🟢' }); }); export default router; diff --git a/src/services/auth.ts b/src/services/auth.ts new file mode 100644 index 0000000..1ede3d8 --- /dev/null +++ b/src/services/auth.ts @@ -0,0 +1,28 @@ +import { generateToken } from '@utils/index'; + +const USERS = [ + { + id: 1, + url: 'http://localhost:8080', + }, + { + id: 2, + url: 'http://localhost:8080', + }, + { + id: 3, + url: 'https://mi-frontend.com', + }, +]; + +export const login = ({ credential }: { credential: string }) => { + const exist = USERS.find((u) => u.url === credential); + + if (!exist) { + throw new Error('Ocurrio un error con su credencial'); + } + + const token = generateToken({ payload: exist }); + + return token; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 460aaf9..26b03d2 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1,19 @@ // /src/utils/index.ts +// general functions shared by different modules +import jwt from 'jsonwebtoken'; +import { JWT_PAYLOAD } from './types'; +import 'dotenv/config'; + +const generateToken = ({ payload }: { payload: JWT_PAYLOAD }) => { + const token = jwt.sign( + { ...payload }, + (process.env.JWT_SECRET as string) || 'DEFAULT_KEY_WHEN_ENV_DOES_NOT_EXIST', + { + expiresIn: '24h', + } + ); + + return token; +}; + +export { generateToken }; diff --git a/src/utils/types.ts b/src/utils/types.ts index 1308235..ec765a4 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,21 @@ +declare global { + namespace Express { + interface Request { + user?: { + id: number; + url: string; + }; + } + } +} + export type UUID = string; +export interface JWT_PAYLOAD { + id: number; + url: string; +} + export interface Phone { id: UUID; name: string;