const prisma = require("../utils/prisma"); const { EventLogs } = require("./eventLogs"); const User = { create: async function ({ username, password, role = "default" }) { const passwordCheck = this.checkPasswordComplexity(password); if (!passwordCheck.checkedOK) { return { user: null, error: passwordCheck.error }; } try { const bcrypt = require("bcrypt"); const hashedPassword = bcrypt.hashSync(password, 10); const user = await prisma.users.create({ data: { username, password: hashedPassword, role, }, }); return { user, error: null }; } catch (error) { console.error("FAILED TO CREATE USER.", error.message); return { user: null, error: error.message }; } }, // Log the changes to a user object, but omit sensitive fields // that are not meant to be logged. loggedChanges: function (updates, prev = {}) { const changes = {}; const sensitiveFields = ["password"]; Object.keys(updates).forEach((key) => { if (!sensitiveFields.includes(key) && updates[key] !== prev[key]) { changes[key] = `${prev[key]} => ${updates[key]}`; } }); return changes; }, update: async function (userId, updates = {}) { try { const currentUser = await prisma.users.findUnique({ where: { id: parseInt(userId) }, }); if (!currentUser) { return { success: false, error: "User not found" }; } if (updates.hasOwnProperty("password")) { const passwordCheck = this.checkPasswordComplexity(updates.password); if (!passwordCheck.checkedOK) { return { success: false, error: passwordCheck.error }; } const bcrypt = require("bcrypt"); updates.password = bcrypt.hashSync(updates.password, 10); } const user = await prisma.users.update({ where: { id: parseInt(userId) }, data: updates, }); await EventLogs.logEvent( "user_updated", { username: user.username, changes: this.loggedChanges(updates, currentUser), }, userId ); return { success: true, error: null }; } catch (error) { console.error(error.message); return { success: false, error: error.message }; } }, get: async function (clause = {}) { try { const user = await prisma.users.findFirst({ where: clause }); return user ? { ...user } : null; } catch (error) { console.error(error.message); return null; } }, count: async function (clause = {}) { try { const count = await prisma.users.count({ where: clause }); return count; } catch (error) { console.error(error.message); return 0; } }, delete: async function (clause = {}) { try { await prisma.users.deleteMany({ where: clause }); return true; } catch (error) { console.error(error.message); return false; } }, where: async function (clause = {}, limit = null) { try { const users = await prisma.users.findMany({ where: clause, ...(limit !== null ? { take: limit } : {}), }); return users; } catch (error) { console.error(error.message); return []; } }, checkPasswordComplexity: function (passwordInput = "") { const passwordComplexity = require("joi-password-complexity"); // Can be set via ENV variable on boot. No frontend config at this time. // Docs: https://www.npmjs.com/package/joi-password-complexity const complexityOptions = { min: process.env.PASSWORDMINCHAR || 8, max: process.env.PASSWORDMAXCHAR || 250, lowerCase: process.env.PASSWORDLOWERCASE || 0, upperCase: process.env.PASSWORDUPPERCASE || 0, numeric: process.env.PASSWORDNUMERIC || 0, symbol: process.env.PASSWORDSYMBOL || 0, // reqCount should be equal to how many conditions you are testing for (1-4) requirementCount: process.env.PASSWORDREQUIREMENTS || 0, }; const complexityCheck = passwordComplexity( complexityOptions, "password" ).validate(passwordInput); if (complexityCheck.hasOwnProperty("error")) { let myError = ""; let prepend = ""; for (let i = 0; i < complexityCheck.error.details.length; i++) { myError += prepend + complexityCheck.error.details[i].message; prepend = ", "; } return { checkedOK: false, error: myError }; } return { checkedOK: true, error: "No error." }; }, }; module.exports = { User };