From 87cde274362f17126efa1722afcca24ef130aba5 Mon Sep 17 00:00:00 2001 From: Marcos Date: Wed, 20 May 2026 17:25:46 +0200 Subject: [PATCH] Add delete-all-data endpoint to auth API for user data removal --- docs/API.md | 16 ++++++++++++++ src/controllers/authController.js | 36 +++++++++++++++++++++++++++++-- src/routes/authRoutes.js | 3 +++ src/services/authService.js | 19 ++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/docs/API.md b/docs/API.md index 0152a36..7b3dcdf 100644 --- a/docs/API.md +++ b/docs/API.md @@ -147,6 +147,22 @@ Respuesta ejemplo: } ``` +### `POST /api/auth/delete-all-data` + +Elimina todas las notas, categorías y refresh tokens del usuario autenticado. El usuario se conserva. + +Headers: + +- `Authorization: Bearer `: obligatorio. + +Respuesta ejemplo: + +```json +{ + "message": "Datos del usuario eliminados correctamente; se eliminaron todos los tokens." +} +``` + ### `POST /api/sync` Endpoint único de sincronización offline-first. diff --git a/src/controllers/authController.js b/src/controllers/authController.js index 7c99529..3f03f7b 100644 --- a/src/controllers/authController.js +++ b/src/controllers/authController.js @@ -1,4 +1,11 @@ const authService = require('../services/authService'); +const jwt = require('jsonwebtoken'); + +const JWT_SECRET = process.env.JWT_SECRET; + +if (!JWT_SECRET) { + throw new Error('JWT_SECRET no está definido'); +} // 1. REGISTRO const register = async (req, res) => { @@ -73,5 +80,30 @@ const logout = async (req, res) => { } }; -// ¡EXPORTAMOS LAS 4 FUNCIONES! -module.exports = { register, login, refresh, logout }; \ No newline at end of file +const deleteAllData = async (req, res) => { + try { + const authorizationHeader = req.headers.authorization || ''; + if (!authorizationHeader.startsWith('Bearer ')) { + return res.status(401).json({ error: 'Authorization header missing or invalid' }); + } + + const token = authorizationHeader.slice(7).trim(); + const payload = jwt.verify(token, JWT_SECRET); + const userId = payload && payload.id; + if (!userId) return res.status(401).json({ error: 'Usuario inválido en token' }); + + const result = await authService.deleteAllUserData(userId); + + res.json(result); + } catch (error) { + if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') { + return res.status(401).json({ error: 'Token inválido o expirado' }); + } + + const statusCode = error.message.includes('obligatorio') || error.message.includes('inválido') ? 400 : 500; + res.status(statusCode).json({ error: error.message }); + } +}; + +// Exportamos los handlers de auth. +module.exports = { register, login, refresh, logout, deleteAllData }; \ No newline at end of file diff --git a/src/routes/authRoutes.js b/src/routes/authRoutes.js index 4544f7b..b2c7fb3 100644 --- a/src/routes/authRoutes.js +++ b/src/routes/authRoutes.js @@ -14,4 +14,7 @@ router.post('/refresh', authController.refresh); // POST /api/auth/logout router.post('/logout', authController.logout); +// POST /api/auth/delete-all-data +router.post('/delete-all-data', authController.deleteAllData); + module.exports = router; \ No newline at end of file diff --git a/src/services/authService.js b/src/services/authService.js index 17b1297..f13b2b8 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -1,7 +1,10 @@ const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const crypto = require('crypto'); +const sequelize = require('../config/database'); const User = require('../models/User'); +const Note = require('../models/Note'); +const Category = require('../models/Category'); const RefreshToken = require('../models/RefreshToken'); const JWT_SECRET = process.env.JWT_SECRET; @@ -85,6 +88,22 @@ class AuthService { await RefreshToken.destroy({ where: { userId } }); return { message: 'Se ha cerrado sesión en todos los dispositivos de forma global.' }; } + + async deleteAllUserData(userId) { + if (!userId) { + throw new Error('userId es obligatorio'); + } + + return sequelize.transaction(async (transaction) => { + await Note.destroy({ where: { userId }, transaction }); + await Category.destroy({ where: { userId }, transaction }); + await RefreshToken.destroy({ where: { userId }, transaction }); + + return { + message: 'Datos del usuario eliminados correctamente; se eliminaron todos los tokens.' + }; + }); + } } module.exports = new AuthService(); \ No newline at end of file