const db = require("../models"); const Employee = db.employee; const ExtUserPermission = db.ext_usr_perm; const config = require("../config/auth.config"); const axios = require("axios"); const https = require("https"); var jwt = require("jsonwebtoken"); var bcrypt = require("bcryptjs"); const { splitSite } = require("../middlewares"); const HR_AUTH_LOGIN_ENDPOINT = "https://ws-hr.mayosolution.com/api/auth/login"; const SYSTEM_ACCESS_PERMISSION = "System Access"; const HR_INSECURE_HTTPS_AGENT = new https.Agent({ rejectUnauthorized: false }); const ALL_PERMISSIONS = [ 'Dashboard', 'Admin View', 'View_Info Screen', 'Edit_Info Screen', 'View_Customer Info _Personal Info', 'View_Customer Info _Care & Services', 'View_Customer Info _Medical & Insurance', 'View_Customer Info _Confidential Details', 'View_Customer Info _Form Submission', 'Edit_Customer Info _ Personal Info', 'Edit_Customer Info _ Care & Services', 'Edit_Customer Info _ Medical & Insurance', 'Edit_Customer Info _ Confidential Details', 'Edit_Customer Info _ Form Submission', 'Discharge_Customer', 'Reactivate_Customer', 'Create_Customer', 'Export_Customer Report', 'View _Calendar _Medical Appointment', 'View _Calendar _Activities', 'View _Calendar _Attendance Notes', 'View _Calendar _Meal Plan', 'View _Calendar _Important Dates', 'Edit&Create _Calendar _Medical Appointment', 'Edit&Create _Calendar _Activities', 'Edit&Create _Calendar _Attendance Notes', 'Edit&Create _Calendar _Meal Plan', 'Edit&Create _Calendar _Important Dates', 'View_Messaging', 'Sent_Messaging', 'View_Messaging Template', 'Create&Edit_Messaging Template', 'View_Vehicle info_Basic Info', 'View_Vehicle info_Documents', 'View_Vehicle info_Repair Records', 'Edit_Vehicle info_Basic Info', 'Edit_Vehicle info_Documents', 'Edit_Vehicle info_Repair Records', 'Add_New Vehicle', 'Archive_Vehicle', 'Export_Vehicle Report', 'View_Transportation Schedule_Route Overview', 'Create&Edit_Transportation Schedule', 'Export_Transportation Schedule Report', 'View_Route Template', 'Create&Edit_Route Template', 'View_Driver Assignment for Appointment', 'Edit_Driver Assignment for Appointment', 'isDriver', 'View_Provider Info', 'Create & Edit _Provider Info', 'View_Appointment Request', 'Edit & Create_Appointment Request', 'View_Appointment Calendar', 'Edit & Create_Appointment Calendar', 'Medical Template', 'View_Meal Status', 'Edit_Meal Status', 'View_Seating Chart', 'Edit_Seating Chart', 'Employee page', 'Set Permission for Employee' ]; const getEffectivePermissions = (employeeDoc) => { const username = `${employeeDoc?.username || ''}`.trim().toLowerCase(); const roles = Array.isArray(employeeDoc?.roles) ? employeeDoc.roles : []; const permissions = Array.isArray(employeeDoc?.permissions) ? employeeDoc.permissions : []; // Keep hardcoded full permission override. if (username === 'testadmin03') return ALL_PERMISSIONS; // Backward-compatible fallback for old admin records with no permissions assigned yet. if (permissions.length === 0 && roles.includes('admin')) return ALL_PERMISSIONS; return permissions; }; const isEmployeeActive = (employeeDoc) => { const rawStatus = employeeDoc?.status; // Backward compatibility: older records may not have status set. if (rawStatus === undefined || rawStatus === null || `${rawStatus}`.trim() === "") { return true; } return `${rawStatus}`.trim().toLowerCase() === "active"; }; const parseExternalTokenId = (value = "") => { const token = `${value || ""}`; if (!token.startsWith("ext:")) return null; const chunks = token.split(":"); if (chunks.length < 3) return null; return { externalUserId: chunks[1], site: Number(chunks[2]) || null }; }; const getExternalPermissionBySite = async (externalUserId, site) => { if (!externalUserId || !site) return null; return ExtUserPermission.findOne({ external_user_id: `${externalUserId}`, allow_site: Number(site) }).sort({ updatedAt: -1 }); }; const buildExternalAuthResponse = async (externalUser, site) => { const permissionDoc = await getExternalPermissionBySite(externalUser?.id, site); const permissions = Array.isArray(permissionDoc?.permissions) ? permissionDoc.permissions : []; const tokenPayload = { id: `ext:${externalUser?.id}:${site}`, authSource: "external", externalUserId: externalUser?.id, site: Number(site), username: externalUser?.username || "", email: externalUser?.email || "", name: externalUser?.name || "", firstname: externalUser?.firstname || "", lastname: externalUser?.lastname || "", status: externalUser?.status || "" }; const accessToken = jwt.sign(tokenPayload, config.secret, { expiresIn: 86400 }); return { accessToken, username: externalUser?.username || "", email: externalUser?.email || "", roles: [], permissions, id: externalUser?.id, name: externalUser?.name || "", firstname: externalUser?.firstname || "", lastname: externalUser?.lastname || "", site: Number(site), status: externalUser?.status || "", auth_source: "external" }; }; // Create and Save a new User exports.login = async (req, res) => { const emailUsername = req.body.emailUsername; const password = req.body.password; const requestedSite = Number(req.body.site); const site = Number.isInteger(requestedSite) && requestedSite > 0 ? requestedSite : splitSite.findSiteNumber(req); if (!emailUsername) { return res.status(400).send({ message: "email or username is required" }); } try { let condition = { $or: [{ email: emailUsername }, { username: emailUsername }] }; condition = splitSite.splitSiteGet(req, condition); const localEmployees = await Employee.find(condition); if (localEmployees && localEmployees.length > 0) { const localEmployee = localEmployees[0]; const activeEmployee = isEmployeeActive(localEmployee); const isPasswordCorrect = bcrypt.compareSync(password, localEmployee.password || ""); if (localEmployees.length === 1 && isPasswordCorrect && activeEmployee) { const token = jwt.sign({ id: localEmployee.id }, config.secret, { expiresIn: 86400 // 24 hours }); return res.send({ accessToken: token, username: localEmployee.username, email: localEmployee.email, roles: localEmployee.roles, permissions: getEffectivePermissions(localEmployee), id: localEmployee.id, name: localEmployee.name, name_cn: localEmployee.name_cn }); } if (!activeEmployee) { throw Error("User is not activated"); } // Local account exists but credential doesn't match. Continue to external auth fallback. } try { const externalAuthResponse = await axios.post( HR_AUTH_LOGIN_ENDPOINT, { emailUsername, password, site }, { timeout: 15000, httpsAgent: HR_INSECURE_HTTPS_AGENT } ); const externalUser = externalAuthResponse?.data; const isExternalUserValid = externalUser && externalUser.id && `${externalUser?.status || ""}`.trim().toLowerCase() === "active"; if (isExternalUserValid) { const loginPayload = await buildExternalAuthResponse(externalUser, site); return res.send(loginPayload); } } catch (_externalError) { // Fall through to existing invalid-login response. } throw Error("Email or Password Is Invalid"); } catch (err) { return res.status(500).send({ message: err.message || "Email Or Password Invalid" }); } }; exports.me = (req, res) => { const authPayload = req.authPayload || {}; const parsedExternalToken = parseExternalTokenId(req.userId); const isExternalAuth = authPayload?.authSource === "external" || !!parsedExternalToken?.externalUserId; if (isExternalAuth) { const site = Number(authPayload?.site) || parsedExternalToken?.site || splitSite.findSiteNumber(req); const externalUserId = authPayload?.externalUserId || parsedExternalToken?.externalUserId; return getExternalPermissionBySite(externalUserId, site) .then((permissionDoc) => { let permissions = Array.isArray(permissionDoc?.permissions) ? permissionDoc.permissions : []; if (permissions.includes(SYSTEM_ACCESS_PERMISSION) === false) { // Keep login valid; UI permission gate will lock down non-system users. permissions = permissions.filter(Boolean); } return res.send({ username: authPayload?.username || "", email: authPayload?.email || "", roles: [], permissions, id: externalUserId, name: authPayload?.name || "", firstname: authPayload?.firstname || "", lastname: authPayload?.lastname || "", site, status: authPayload?.status || "", auth_source: "external" }); }) .catch((err) => { return res.status(500).send({ message: err.message || "Failed to fetch current user" }); }); } const condition = splitSite.splitSiteGet(req, { _id: req.userId }); Employee.findOne(condition) .then((employee) => { if (!employee) { return res.status(404).send({ message: "User not found" }); } if (!isEmployeeActive(employee)) { return res.status(403).send({ message: "User is not activated" }); } return res.send({ username: employee.username, email: employee.email, roles: employee.roles, permissions: getEffectivePermissions(employee), id: employee.id, name: employee.name, name_cn: employee.name_cn }); }) .catch((err) => { res.status(500).send({ message: err.message || "Failed to fetch current user" }); }); };