🔐 Servicio de loginUser - API Autenticación de usuario por vertical
⚙️ Funcionamiento interno de la API
Section titled “⚙️ Funcionamiento interno de la API”El API completo de Login es el siguiente:
async function verifyComponentData(verticalsUserPasswords, vertical) { if ( !Array.isArray(verticalsUserPasswords) || verticalsUserPasswords.length === 0 ) { throw new Error(ERROR_PASSWORD_NOT_FOUND); }
const existsOnVertical = verticalsUserPasswords.find( (pass) => pass.vertical === vertical ); if (!existsOnVertical) throw new Error(ERROR_VERTICAL_PASSWORD_NOT_FOUND);
return existsOnVertical;}
export default { async loginUser(ctx) { const body = ctx.request.body;
try { const { email, password, vertical } = body; validateKeys(body, VERIFICATION_KEYS, FIELD_ROOT);
const requiredFields = VERIFICATION_KEYS.filter( (key) => key !== USER_FIELD_PASSWORD ); validateRequiredFields(body, requiredFields);
const strapiUser = await findOne( USER, FIELDS_USER, { email }, COMPONENT_NAME_USER_PASSWORDS ); if (strapiUser === NOT_FOUND) throw new Error(ERROR_USER_NOT_FOUND);
const existsOnVertical = await verifyComponentData( strapiUser.verticalsUserPasswords, vertical );
if (existsOnVertical.originWps) { if (!existsOnVertical.verticalPassword) { return ctx.send({ code: PASSWORD_RESET_REQUIRED_CODE, message: PASSWORD_RESET_REQUIRED_MESSAGE, }); } }
validateRequiredFields(body, VERIFICATION_KEYS);
const existingPassword = await bcrypt.compare( password, existsOnVertical.verticalPassword ); if (!existingPassword) throw new Error(ERROR_INVALID_PASSWORD);
const jwt = strapi.plugins[USER_STRIPE_USERS_PLUGIN].services.jwt.issue({ id: strapiUser.id, email: strapiUser.email, vertical, });
const sanitizedUser = { id: strapiUser.id, documentId: strapiUser.documentId, username: strapiUser.username, email: strapiUser.email, confirmed: strapiUser.confirmed, phone: strapiUser.phone, origin: strapiUser.origin, identificationNumber: strapiUser.identificationNumber, nameLastName: strapiUser.nameLastName, };
return ctx.send( { jwt, user: sanitizedUser, }, 200 ); } catch (error) { return ctx.badRequest(error); } },
async existsEmailVertical(ctx) { const { email, vertical } = ctx.request.body; try { validateKeys( ctx.request.body, EXISTS_EMAIL_VERTICAL_VERIFICATION_KEYS, FIELD_ROOT ); validateRequiredFields( ctx.request.body, EXISTS_EMAIL_VERTICAL_VERIFICATION_KEYS ); const strapiUser = await findOne( USER, FIELDS_USER, { email }, COMPONENT_NAME_USER_PASSWORDS ); if (strapiUser === NOT_FOUND) { return ctx.send({ status: 411, error: ERROR_EMAIL_NOT_FOUND, exists: false, }); }
let isWordpressUser = false; const existsOnVertical = await verifyComponentData( strapiUser.verticalsUserPasswords, vertical ); if (existsOnVertical.originWps) { if (!existsOnVertical.verticalPassword) { isWordpressUser = true; } }
return ctx.send({ status: 200, code: isWordpressUser ? PASSWORD_RESET_REQUIRED_CODE : EMAIL_EXISTS_CODE, message: EMAIL_EXISTS_MESSAGE, exists: true, isWordpressUser, }); } catch (error) { return ctx.send({ status: 411, code: ERROR_EMAIL_NOT_FOUND, message: error.message || "An error occurred while checking email.", exists: false, }); } },};Estas APIs están encargadas de hacer el inicio de sesión de los usuarios registrados, ya sea de usuarios normales (creados directamente en el sistema) o usuarios migrados desde WordPress. Por esa razón, no todos los usuarios siguen el mismo flujo de validación: algunos requerirán contraseña, otros podrían ser redirigidos a un flujo de recuperación.
La API existsEmailVertical Se encarga unicamente de verificar que el usuario este registrado en una vertical registrada con ese correo, si existe de igual manera verifica si este es un usuario migrado desde WordPress.
🧑💻 loginUser
Section titled “🧑💻 loginUser”loginUser autentica a un usuario en función de su correo, contraseña y vertical específico. Retorna un token JWT y los datos esenciales del usuario.
El proceso comienza recibiendo el body de la solicitud HTTP (ctx.request.body), el cual debe contener tres campos: email, password y vertical. Esto permite que un mismo usuario pueda tener distintas contraseñas y roles dependiendo del contexto.
📨 Recepción del body y validación de claves
Section titled “📨 Recepción del body y validación de claves”const { email, password, vertical } = body;validateKeys(body, VERIFICATION_KEYS, FIELD_ROOT);Antes de realizar cualquier consulta a la base de datos, el sistema valida que el objeto recibido (body) no contenga claves inesperadas. Esto se logra con la función validateKeys, que compara las claves del objeto con un arreglo permitido (VERIFICATION_KEYS). Este paso es fundamental para evitar que se cuelen campos no deseados que puedan ser utilizados para inyecciones o errores lógicos.
✅ Validación inicial de campos requeridos (sin password todavía)
Section titled “✅ Validación inicial de campos requeridos (sin password todavía)”const requiredFields = VERIFICATION_KEYS.filter( (key) => key !== USER_FIELD_PASSWORD);validateRequiredFields(body, requiredFields);Luego, se verifica que el body contenga las claves obligatorias mínimas validateRequiredFields.
Inicialmente, no se incluye password como obligatoria ya que algunos usuarios migrados desde WordPress podrían no tenerla configurada aún. Por eso, en esta etapa se exige solamente email y vertical, permitiendo que la validación de password se haga más adelante si realmente se requiere.
🔍 Búsqueda del usuario en la base de datos
Section titled “🔍 Búsqueda del usuario en la base de datos”const strapiUser = await findOne( USER, FIELDS_USER, { email }, COMPONENT_NAME_USER_PASSWORDS);Después de las validaciones iniciales, se intenta buscar al usuario en la base de datos mediante la función findOne.
🧩 Validación especial para usuarios migrados desde WordPress
Section titled “🧩 Validación especial para usuarios migrados desde WordPress”const existsOnVertical = await verifyComponentData( strapiUser.verticalsUserPasswords, vertical);
if (existsOnVertical.originWps) { if (!existsOnVertical.verticalPassword) { return ctx.send({ code: PASSWORD_RESET_REQUIRED_CODE, message: PASSWORD_RESET_REQUIRED_MESSAGE, }); }}Una vez obtenido el usuario, se evalúa si fue migrado desde WordPress. Esto se hace revisando el campo originWps del componente de contraseñas, el cual debe estar establecido en true (LA UNICA FORMA DE QUE ESTÉ EN TRUE ES SI ESTE FUE MIGRADO). En caso afirmativo, se ejecuta una validación adicional: se verifica que el usuario NO tenga una contraseña registrada para el vertical que intenta acceder.
Esta lógica se encapsula en la función auxiliar verifyComponentData, que explora el arreglo verticalsUserPasswords buscando una entrada que coincida con el vertical solicitado. Si se encuentra esa entrada Y si la entrada NO tiene una contraseña asociada (verticalPassword === null), el sistema devuelve una respuesta PASSWORD_RESET_REQUIRED, indicando que es necesario restablecer la contraseña.
🔁 Segunda validación con password incluida
Section titled “🔁 Segunda validación con password incluida”validateRequiredFields(body, VERIFICATION_KEYS);Una vez pasado ese filtro, se vuelve a validar que todos los campos estén presentes, incluyendo ahora sí el password.
Esto con el motivo de que si se descarta la posibilidad de que sea un usuario de wordpress entonces si debe ser obligatorio el que envie un password.
🧪 Comparación de contraseñas por vertical
Section titled “🧪 Comparación de contraseñas por vertical”const existsOnVertical = await verifyComponentData(...);const existingPassword = await bcrypt.compare(password, existsOnVertical.verticalPassword);if (!existingPassword) throw new Error(ERROR_INVALID_PASSWORD);Con todos los datos verificados, se utiliza nuevamente verifyComponentData para obtener la contraseña cifrada del vertical indicado. Después se usa bcrypt.compare para comparar el hash almacenado con la contraseña ingresada. Si no coinciden, el login se rechaza con el error ERROR_INVALID_PASSWORD.
🔐 Generación de JWT personalizado
Section titled “🔐 Generación de JWT personalizado”const jwt = strapi.plugins[USER_STRIPE_USERS_PLUGIN].services.jwt.issue({ id: strapiUser.id, email: strapiUser.email, vertical,});En caso de éxito, se genera un token JWT usando los servicios internos del plugin JWT (strapi.plugins[USER_STRIPE_USERS_PLUGIN].services.jwt.issue). Este token se construye con el id y email del usuario, junto con el vertical, y servirá para autenticar futuras peticiones del cliente.
🧼 Sanitización del usuario para la respuesta
Section titled “🧼 Sanitización del usuario para la respuesta”const sanitizedUser = { id: strapiUser.id, documentId: strapiUser.documentId, username: strapiUser.username, ... // demás campos seguros};Finalmente, se crea un objeto sanitizedUser que incluye únicamente los datos seguros y relevantes del usuario.
📤 Respuesta final al cliente
Section titled “📤 Respuesta final al cliente”return ctx.send({ jwt, user: sanitizedUser }, 200);Se responde al cliente con un 200 OK, el JWT y el objeto user resultante.
🧑💻 existsEmailVertical
Section titled “🧑💻 existsEmailVertical”existsEmailVertical verifica si un correo electrónico existe dentro de un vertical específico del sistema y determina si es un usuario migrado desde WordPress que aún no ha configurado contraseña.
async existsEmailVertical(ctx) { const { email, vertical } = ctx.request.body; try { validateKeys( ctx.request.body, EXISTS_EMAIL_VERTICAL_VERIFICATION_KEYS, FIELD_ROOT ); validateRequiredFields( ctx.request.body, EXISTS_EMAIL_VERTICAL_VERIFICATION_KEYS ); const strapiUser = await findOne( USER, FIELDS_USER, { email }, COMPONENT_NAME_USER_PASSWORDS ); if (strapiUser === NOT_FOUND) { return ctx.send({ status: 411, error: ERROR_EMAIL_NOT_FOUND, exists: false, }); }
let isWordpressUser = false; const existsOnVertical = await verifyComponentData( strapiUser.verticalsUserPasswords, vertical ); if (existsOnVertical.originWps) { if (!existsOnVertical.verticalPassword) { isWordpressUser = true; } }
return ctx.send({ status: 200, code: isWordpressUser ? PASSWORD_RESET_REQUIRED_CODE : EMAIL_EXISTS_CODE, message: EMAIL_EXISTS_MESSAGE, exists: true, isWordpressUser, }); } catch (error) { return ctx.send({ status: 411, code: ERROR_EMAIL_NOT_FOUND, message: error.message || "An error occurred while checking email.", exists: false, }); } },Esta función se utiliza típicamente antes de iniciar un login, para decidir si se debe mostrar un formulario de contraseña o un flujo de recuperación/reset de contraseña.
📨 Recepción del body y validación de claves
Section titled “📨 Recepción del body y validación de claves”const { email, vertical } = ctx.request.body;
validateKeys( ctx.request.body, EXISTS_EMAIL_VERTICAL_VERIFICATION_KEYS, FIELD_ROOT);El primer paso es validar que el body de la petición contenga únicamente las claves esperadas (email, vertical). Esto protege contra claves inesperadas o maliciosas mediante la función validateKeys.
✅ Validación de campos requeridos
Section titled “✅ Validación de campos requeridos”validateRequiredFields( ctx.request.body, EXISTS_EMAIL_VERTICAL_VERIFICATION_KEYS);A continuación, se asegura que todos los campos requeridos estén presentes (email y vertical). Se usa validateRequiredFields para lanzar un error si alguno falta.
🔍 Búsqueda del usuario por correo electrónico
Section titled “🔍 Búsqueda del usuario por correo electrónico”const strapiUser = await findOne( USER, FIELDS_USER, { email }, COMPONENT_NAME_USER_PASSWORDS);Si la validación es exitosa, se busca al usuario en la base de datos filtrando por email, y se cargan sus componentes de contraseñas (donde vive la relación con verticales).
Si el usuario no se encuentra (NOT_FOUND), se retorna inmediatamente:
return ctx.send({ status: 411, error: ERROR_EMAIL_NOT_FOUND, // -> "The provided email does not exist" exists: false,});🧩 Verificación de datos del componente por vertical
Section titled “🧩 Verificación de datos del componente por vertical”const existsOnVertical = await verifyComponentData( strapiUser.verticalsUserPasswords, vertical);Se utiliza la función verifyComponentData para buscar, dentro del arreglo verticalsUserPasswords, una entrada que coincida con el vertical solicitado.
🌐 Evaluación de origen WordPress
Section titled “🌐 Evaluación de origen WordPress”let isWordpressUser = false;
if (existsOnVertical.originWps) { if (!existsOnVertical.verticalPassword) { isWordpressUser = true; }}Si la entrada del vertical tiene el campo originWps: true y no hay una contraseña configurada (verticalPassword === null), entonces el sistema marca al usuario como isWordpressUser, lo cual indica que necesita restablecer su contraseña antes de continuar.
📤 Respuesta final al cliente
Section titled “📤 Respuesta final al cliente”return ctx.send({ status: 200, code: isWordpressUser ? PASSWORD_RESET_REQUIRED_CODE : EMAIL_EXISTS_CODE, // codes -> "PASSWORD_RESET_REQUIRED" || "EMAIL_EXISTS_IN_VERTICAL" message: EMAIL_EXISTS_MESSAGE, // -> "Email exists in the vertical" exists: true, isWordpressUser,});La respuesta incluye:
exists: truesi el correo fue encontrado.isWordpressUser: truesi es un usuario migrado de WordPress sin contraseña configurada.- Un
codepersonalizado que permite distinguir entre un usuario existente normal o uno que requiere reset de contraseña.
En caso de error, se responde con:
return ctx.send({ status: 411, code: ERROR_EMAIL_NOT_FOUND, // -> "The provided email does not exist" message: error.message || "An error occurred while checking email.", exists: false,});📤 Ejemplo de solicitudes
Section titled “📤 Ejemplo de solicitudes”URL: /api/user-endpoints/login
Método: POST
Body ejemplo:
{ "email": "prueba2@prueba2.com", "password": "MiContraseñaSegura123", "vertical": "Pharma"}URL: /api/login-user/exists-email-vertical
Método: POST
Body ejemplo:
{ "email": "prueba2@prueba2.com", "vertical": "Pharma"}✅ Respuestas esperadas
Section titled “✅ Respuestas esperadas”✔️ Login exitoso
Section titled “✔️ Login exitoso”{ "jwt": " ... ", "user": { "id": 130, "documentId": "yr1xmn5o0d1bq7cdh06iwymf", "username": "prueba@prueba.com", "email": "prueba@prueba.com", "confirmed": false, "phone": "5551234567", "origin": null, "identificationNumber": "ABC123456", "nameLastName": "Fernando Rodríguez" }}❌ Campos inválidos o faltantes
Section titled “❌ Campos inválidos o faltantes”{ "data": null, "error": { "status": 400, "name": "Error", "message": "Missing required parameters: password", "details": {} }}{ "data": null, "error": { "status": 400, "name": "Error", "message": "Not Allowed fields at root: passwordss", "details": {} }}✔️ Usuario de wordpress
Section titled “✔️ Usuario de wordpress”return ctx.send({ status: 200, code: isWordpressUser ? PASSWORD_RESET_REQUIRED_CODE : EMAIL_EXISTS_CODE, // codes -> "PASSWORD_RESET_REQUIRED" || "EMAIL_EXISTS_IN_VERTICAL" message: EMAIL_EXISTS_MESSAGE, // -> "Email exists in the vertical" exists: true, isWordpressUser,});Esta respuesta puede aparecer en ambos endpoints con el campo coincidiendo en un CODE: “PASSWORD_RESET_REQUIRED”