✨ Servicio de Cambio de Contraseña en Vertical por Medio de API
La API update-password fue diseñada para permitir a los usuarios autenticados modificar la contraseña de una vertical de manera segura.
Esta API está protegida por autenticación JWT-> Se obtiene con la nueva API de Login, lo que garantiza que solo los usuarios autenticados puedan modificar su información.
🔐 Autenticación
Section titled “🔐 Autenticación”El acceso a esta API requiere un token JWT válido. Este token debe enviarse en el encabezado de la solicitud como:
Authorization: Bearer TU_TOKEN_JWTSi el token es inválido o no se proporciona, la API rechazará la solicitud.
🧠 Controlador
Section titled “🧠 Controlador”Este archivo contiene la lógica principal para el manejo de la actualización de la contraseña de una vertical.
Ruta del archivo
Section titled “Ruta del archivo”src/api/user-endpoints/controllers/update-password.ts
⚙️ Funcionamiento interno de la API
Section titled “⚙️ Funcionamiento interno de la API”El componente completo del registro es el siguiente:
async updatePassword(ctx) { try { const { vertical, currentPassword, newPassword } = ctx.request.body;
validateKeys(ctx.request.body, updateValidKeys, FIELD_ROOT); validateRequiredFields(ctx.request.body, VALID_NEW_PASSWORD_KEYS);
validateFieldPatterns( { password: newPassword }, { password: FIELD_VALIDATIONS.password }, );
const authHeader = ctx.request.header.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return ctx.unauthorized(ERROR_UNAUTHORIZED); }
const token = authHeader && authHeader.split(' ')[1]; if (!token) return ctx.unauthorized(ERROR_UNAUTHORIZED);
const jwtService = strapi.plugins[USER_STRIPE_USERS_PLUGIN].services.jwt; const payload = await jwtService.verify(token); const verticalFromToken = payload.vertical;
const recivedUser = ctx.state.user;
if (!recivedUser || verticalFromToken !== vertical) { return ctx.unauthorized(ERROR_UNAUTHORIZED); } const { email } = recivedUser;
const user = await findOne( USER, FIELDS_USER, { email: email }, COMPONENT_NAME_USER_PASSWORDS, );
if (!user) return ctx.notFound(ERROR_USER_NOT_FOUND);
if (currentPassword === newPassword) { return ctx.badRequest(ERROR_SAME_PASSWORD); }
const passwordComponent = user.verticalsUserPasswords?.find( (item) => item.vertical === vertical, );
if (!passwordComponent) { return ctx.badRequest(ERROR_VERTICAL_PASSWORD_NOT_FOUND); }
const match = await bcrypt.compare( currentPassword, passwordComponent.verticalPassword, ); if (!match) { return ctx.unauthorized(ERROR_INVALID_PASSWORD); }
await updateVerticalPassword(email, vertical, newPassword);
const lang = validateLang(ctx.request.body.lang);
const { subject, text, html } = EMAIL_CONTENT[lang].updated;
await sendEmail(email, subject, text(vertical), html(vertical));
return ctx.send(UPDATED_PASSWORD_MESSAGE); } catch (error) { return ctx.badRequest(error, ERROR_UPDATE); } },Cuando se realiza una solicitud a esta API, el primer paso es la validación del cuerpo para garantizar que no se estén enviando campos inesperados o mal estructurados.
- Se valida tanto que existan las claves obligatorias (
vertical,currentPassword,newPassword) mediante el util:validateRequiredFields(body, requiredUserKeys);
asegurandose contengan únicamente las propiedades válidas.
const token = authHeader && authHeader.split(" ")[1];if (!token) return ctx.unauthorized(ERROR_UNAUTHORIZED);
const jwtService = strapi.plugins[USER_STRIPE_USERS_PLUGIN].services.jwt;const payload = await jwtService.verify(token);const verticalFromToken = payload.vertical;
const recivedUser = ctx.state.user;
if (!recivedUser || verticalFromToken !== vertical) { return ctx.unauthorized(ERROR_UNAUTHORIZED);}Esta parte es la encargada de asegurarse de que un usuario solamente actualice la contraseña desde la misma vertical en la cual lo está solicitando.
🧾 Datos esperados en el cuerpo de la solicitud (body)
Section titled “🧾 Datos esperados en el cuerpo de la solicitud (body)”El cuerpo debe enviarse en formato JSON y puede incluir los siguientes campos:
vertical(obligatorio): nombre de la vertical para la cual se está cambiando la contraseña.currentPassword(obligatorio): contraseña antigua de la vertical.newPassword(obligatorio): nueva contraseña que se asociará a la vertical.
Antes de procesar cualquier operación, el controlador valida que solo se estén enviando las claves permitidas, tanto a nivel raíz como en las direcciones anidadas.
validateKeys(ctx.request.body, VALID_NEW_PASSWORD_KEYS, FIELD_ROOT);validateRequiredFields(ctx.request.body, VALID_NEW_PASSWORD_KEYS);Luego, el sistema intenta localizar un usuario existente con el correo electrónico proporcionado por el token.
const recivedUser = ctx.state.user;if (!recivedUser) { return ctx.unauthorized(ERROR_UNAUTHORIZED);}const { email } = recivedUser;
const user = await findOne( USER, FIELDS_USER, { email: email }, COMPONENT_NAME_USER_PASSWORDS);Se comprueba que las contraseñas antigua y nueva no sean la misma. Y q la contraseña antigua coincida con la pasada por el body.
if (currentPassword === newPassword) { return ctx.badRequest(ERROR_SAME_PASSWORD);}
const passwordComponent = user.verticalsUserPasswords?.find( (item) => item.vertical === vertical);
if (!passwordComponent) { return ctx.badRequest(ERROR_VERTICAL_PASSWORD_NOT_FOUND);}
const match = await bcrypt.compare( currentPassword, passwordComponent.verticalPassword);if (!match) { return ctx.unauthorized(ERROR_INVALID_PASSWORD);}Finalmente se llama a la funcion para actualizr la contraseña 👍
await updateVerticalPassword(email, vertical, newPassword);🔐 ¿Qué hace exactamente updateVerticalPassword?
Section titled “🔐 ¿Qué hace exactamente updateVerticalPassword?”- Vease su funcionamiento Aquí.
📤 Ejemplo de solicitud
Section titled “📤 Ejemplo de solicitud”URL: /api/user-endpoints/updatee-password
Método: POST
Body ejemplo:
{ "vertical": "Emergencias", "currentPassword": "1234", "newPassword": "123456"}✅ Respuestas esperadas
Section titled “✅ Respuestas esperadas”✔️ Registro exitoso
Section titled “✔️ Registro exitoso”{ "message": { "id": 22, "documentId": "p800i3wc6h36p6jnghtbhv8c", "username": "Marti", "email": "example@example.com", "confirmed": false, "phone": null, "origin": "local", "identificationNumber": "01234567X", "nameLastName": "Marta Quiros", "verticalsUserPasswords": [ { "id": 18, "vertical": "Emergencias", "verticalPassword": "$2a$10$xxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxx.xxxxxxxxxxx" } ] }}❌ Campos inválidos o faltantes
Section titled “❌ Campos inválidos o faltantes”{ "data": null, "error": { "status": 400, "name": "Error", "message": "Missing required parameters: vertical", "details": "Error on update " }}{ "data": null, "error": { "status": 400, "name": "Error", "message": "Not Allowed fields at root: email", "details": "Error on update " }}❌ La nueva contraseña es igual que la antigua
Section titled “❌ La nueva contraseña es igual que la antigua”{ "data": null, "error": { "status": 400, "name": "BadRequestError", "message": "New password can not be the same as the old one", "details": {} }}🔗 Funciones auxiliares
Section titled “🔗 Funciones auxiliares”-
validateKeys,validateRequiredFields: validación de la estructura del body. -
validateFieldPatterns: asegura que contraseñas cumplan con las reglas.🐿️ -> Veanse aqui validaciones de campos.
-
findOne,updateDocument: funciones de acceso a la base de datos. -
sendEmail: abstracta el envío de correos con plantillas multilenguaje.🐿️ -> Veanse aqui strapi queries.
-
updateVerticalPassword: actualiza la contraseña cifrada de la vertical correspondiente.🐿️ -> Veanse aqui strapi passwords.