All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 36s
287 lines
9.7 KiB
JavaScript
287 lines
9.7 KiB
JavaScript
const db = require("../models");
|
|
const Employee = db.employee;
|
|
const ExtUserPermission = db.ext_usr_perm;
|
|
const config = require("../config/auth.config");
|
|
const axios = require("axios");
|
|
|
|
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 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 }
|
|
);
|
|
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"
|
|
});
|
|
});
|
|
}; |