Compare commits
No commits in common. "5f043ed80a5416992f69e4501ba06852bd9c32ff" and "287d078b4901a8e0a584aae302d92b589794d80b" have entirely different histories.
5f043ed80a
...
287d078b49
|
|
@ -40,8 +40,7 @@
|
||||||
"uglify-js": "^3.19.3",
|
"uglify-js": "^3.19.3",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
"wireguard-tools": "^0.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.26.4",
|
"@babel/cli": "^7.26.4",
|
||||||
|
|
@ -6199,12 +6198,6 @@
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.mergewith": {
|
|
||||||
"version": "4.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
|
|
||||||
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.once": {
|
"node_modules/lodash.once": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
|
@ -9345,15 +9338,6 @@
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wireguard-tools": {
|
|
||||||
"version": "0.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/wireguard-tools/-/wireguard-tools-0.1.0.tgz",
|
|
||||||
"integrity": "sha512-oBnnSB6wXGEAB1ZVPfbfpX8WlHNw7wv45r13ILX73felVnrGpMxMjFcKGAsMeRfBOGzXcW0LgAWe+T53dRcI+g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"lodash.mergewith": "^4.6.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/wordwrap": {
|
"node_modules/wordwrap": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,7 @@
|
||||||
"uglify-js": "^3.19.3",
|
"uglify-js": "^3.19.3",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
"wireguard-tools": "^0.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.26.4",
|
"@babel/cli": "^7.26.4",
|
||||||
|
|
|
||||||
|
|
@ -492,11 +492,9 @@ async function validateStep(step) {
|
||||||
} else if (step === 1) {
|
} else if (step === 1) {
|
||||||
const email = document.getElementById('email').value;
|
const email = document.getElementById('email').value;
|
||||||
const companyName = document.getElementById('company-name').value;
|
const companyName = document.getElementById('company-name').value;
|
||||||
const password = document.getElementById('password').value;
|
if (!validateEmail(email) && !companyName) {
|
||||||
const confirmPassword = document.getElementById('confirmPassword').value;
|
|
||||||
if (!validateEmail(email) && !companyName && !password && !confirmPassword) {
|
|
||||||
isValid = false;
|
isValid = false;
|
||||||
showError('Fields are Required', null, step);
|
showError('Both Fields are Required', null, step);
|
||||||
let requiredFields1 = form.querySelectorAll('[required]');
|
let requiredFields1 = form.querySelectorAll('[required]');
|
||||||
requiredFields1.forEach(field => {
|
requiredFields1.forEach(field => {
|
||||||
if (!field.value.trim()) {
|
if (!field.value.trim()) {
|
||||||
|
|
@ -524,21 +522,6 @@ async function validateStep(step) {
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.length < 8) {
|
|
||||||
showError('Password must be at least 8 characters long', 'password', step);
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password !== confirmPassword) {
|
|
||||||
document.getElementById('password-match-error').style.display = 'block';
|
|
||||||
document.getElementById('confirmPassword').classList.add('field-error');
|
|
||||||
showError('Passwords do not match', 'confirmPassword', step);
|
|
||||||
isValid = false;
|
|
||||||
} else {
|
|
||||||
document.getElementById('password-match-error').style.display = 'none';
|
|
||||||
document.getElementById('confirmPassword').classList.remove('field-error');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate other required fields
|
// Validate other required fields
|
||||||
const requiredFields = form.querySelectorAll('[required]');
|
const requiredFields = form.querySelectorAll('[required]');
|
||||||
requiredFields.forEach(field => {
|
requiredFields.forEach(field => {
|
||||||
|
|
@ -629,7 +612,6 @@ function saveStepData(step) {
|
||||||
case 1:
|
case 1:
|
||||||
formData.userDetails.name = document.getElementById('company-name').value;
|
formData.userDetails.name = document.getElementById('company-name').value;
|
||||||
formData.userDetails.email = document.getElementById('email').value;
|
formData.userDetails.email = document.getElementById('email').value;
|
||||||
formData.userDetails.password = document.getElementById('password').value;
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
formData.userDetails.address = document.getElementById('address').value;
|
formData.userDetails.address = document.getElementById('address').value;
|
||||||
|
|
@ -1973,51 +1955,3 @@ function preventInvalidAccountHolderChars(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Function to generate a strong password
|
|
||||||
function generatePassword() {
|
|
||||||
const length = 12;
|
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
|
|
||||||
let password = "";
|
|
||||||
|
|
||||||
// Ensure at least one of each required character type
|
|
||||||
password += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(Math.random() * 26)];
|
|
||||||
password += "abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 26)];
|
|
||||||
password += "0123456789"[Math.floor(Math.random() * 10)];
|
|
||||||
password += "!@#$%^&*"[Math.floor(Math.random() * 8)];
|
|
||||||
|
|
||||||
// Fill the rest randomly
|
|
||||||
for (let i = password.length; i < length; i++) {
|
|
||||||
password += charset.charAt(Math.floor(Math.random() * charset.length));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle the password
|
|
||||||
password = password.split('').sort(() => 0.5 - Math.random()).join('');
|
|
||||||
|
|
||||||
// Set the password field
|
|
||||||
document.getElementById('password').value = password;
|
|
||||||
document.getElementById('confirmPassword').value = password;
|
|
||||||
|
|
||||||
// Trigger input events to update UI
|
|
||||||
document.getElementById('password').dispatchEvent(new Event('input'));
|
|
||||||
document.getElementById('confirmPassword').dispatchEvent(new Event('input'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to toggle password visibility
|
|
||||||
function togglePasswordVisibility() {
|
|
||||||
const passwordField = document.getElementById('password');
|
|
||||||
const confirmField = document.getElementById('confirmPassword');
|
|
||||||
const toggleButton = document.querySelector('.password-toggle');
|
|
||||||
|
|
||||||
if (passwordField.type === 'password') {
|
|
||||||
passwordField.type = 'text';
|
|
||||||
confirmField.type = 'text';
|
|
||||||
toggleButton.textContent = 'Hide Password';
|
|
||||||
} else {
|
|
||||||
passwordField.type = 'password';
|
|
||||||
confirmField.type = 'password';
|
|
||||||
toggleButton.textContent = 'Show Password';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -25,10 +25,10 @@ async function va_register_client(req, res) {
|
||||||
try {
|
try {
|
||||||
const payload = req.body || {};
|
const payload = req.body || {};
|
||||||
const { userDetails } = payload;
|
const { userDetails } = payload;
|
||||||
if (!userDetails || !userDetails.email || !userDetails.name || !userDetails.password) {
|
if (!userDetails || !userDetails.email || !userDetails.name) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: "Missing required fields: name, email, and password are required"
|
message: "Missing required fields: name and email are required"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ async function project_add_sf(req, res) {
|
||||||
hourlyRate: hourlyRate || '0',
|
hourlyRate: hourlyRate || '0',
|
||||||
clientId: clientObjectId,
|
clientId: clientObjectId,
|
||||||
vaAssigned: vaAssigned,
|
vaAssigned: vaAssigned,
|
||||||
dedicatedHours: dedicatedHours || ["0", "0", "0", "0", "0", "0", "0"],
|
dedicatedHours: dedicatedHours ? parseFloat(dedicatedHours) : 0,
|
||||||
vaWorkingScheduleStart: vaWorkingScheduleStart || null,
|
vaWorkingScheduleStart: vaWorkingScheduleStart || null,
|
||||||
vaWorkingScheduleEnd: vaWorkingScheduleEnd || null,
|
vaWorkingScheduleEnd: vaWorkingScheduleEnd || null,
|
||||||
isFlexibleAllowed: isFlexibleAllowed || true,
|
isFlexibleAllowed: isFlexibleAllowed || true,
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ async function update_project_sf(req, res) {
|
||||||
|
|
||||||
// Update dedicated hours if provided
|
// Update dedicated hours if provided
|
||||||
if (dedicatedHours !== undefined) {
|
if (dedicatedHours !== undefined) {
|
||||||
updateData.dedicatedHours = dedicatedHours || ["0", "0", "0", "0", "0", "0", "0"];
|
updateData.dedicatedHours = parseFloat(dedicatedHours) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total logged hours if provided
|
// Update total logged hours if provided
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js";
|
import { VAModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// function to close open work tracks and create logout
|
// function to close open work tracks and create logout
|
||||||
async function closePreviousSession(vaId, lastLog) {
|
async function closePreviousSession(vaId, lastLog) {
|
||||||
|
|
@ -54,7 +53,7 @@ async function closePreviousSession(vaId, lastLog) {
|
||||||
"time": logoutLog.time,
|
"time": logoutLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, LeavesModel } from "../../../models/vaModel.js";
|
import { VAModel, LeavesModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Apply for leave
|
// Apply for leave
|
||||||
async function va_leave_apply_function(req, res) {
|
async function va_leave_apply_function(req, res) {
|
||||||
|
|
@ -165,7 +164,7 @@ async function va_leave_apply_function(req, res) {
|
||||||
"rejectionReason": ""
|
"rejectionReason": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { VAModel } from "../../../models/vaModel.js";
|
import { VAModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
async function va_payslip_data_function(req, res) {
|
async function va_payslip_data_function(req, res) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -12,7 +11,7 @@ async function va_payslip_data_function(req, res) {
|
||||||
|
|
||||||
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-payslip/${vaId}`;
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-payslip/${vaId}`;
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ async function va_payroll_schedule_function(req, res) {
|
||||||
try {
|
try {
|
||||||
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/payroll-schedule`;
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/payroll-schedule`;
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import { VAModel } from "../../../models/vaModel.js";
|
import { VAModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Register a new VA
|
// Register a new VA
|
||||||
async function va_register(req, res) {
|
async function va_register(req, res) {
|
||||||
|
|
@ -54,7 +53,7 @@ async function va_register(req, res) {
|
||||||
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/virtualassistant`;
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/virtualassistant`;
|
||||||
delete created.userDetails._id;
|
delete created.userDetails._id;
|
||||||
created.userDetails._id = created._id; // Include VA's MongoDB ID
|
created.userDetails._id = created._id; // Include VA's MongoDB ID
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { LoggedModel, WorkTrackModel, ProjectModel, VAModel } from "../../../models/vaModel.js";
|
import { LoggedModel, WorkTrackModel, ProjectModel, VAModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Helper function to get start and end of day in UTC
|
// Helper function to get start and end of day in UTC
|
||||||
function getDayRange(date = new Date()) {
|
function getDayRange(date = new Date()) {
|
||||||
|
|
@ -198,7 +197,7 @@ async function closePreviousSession(vaId, lastLog, logTime = new Date()) {
|
||||||
"time": logoutLog.time,
|
"time": logoutLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -374,7 +373,7 @@ async function va_project_log_callback(req, res) {
|
||||||
"time": createdLog.time,
|
"time": createdLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -472,7 +471,7 @@ async function va_project_log_callback(req, res) {
|
||||||
"time": createdLog.time,
|
"time": createdLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -534,7 +533,7 @@ async function va_project_log_callback(req, res) {
|
||||||
"time": createdLog.time,
|
"time": createdLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, ProjectModel, AdjustedHoursModel } from "../../../models/vaModel.js";
|
import { VAModel, ProjectModel, AdjustedHoursModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Adjust missing hours by creating new records
|
// Adjust missing hours by creating new records
|
||||||
async function va_timesheet_adjustment_function(req, res) {
|
async function va_timesheet_adjustment_function(req, res) {
|
||||||
|
|
@ -156,7 +155,7 @@ async function va_timesheet_adjustment_function(req, res) {
|
||||||
"adjustedHours": formatTime(adjustedSeconds)
|
"adjustedHours": formatTime(adjustedSeconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { VAModel, TipsModel, ClientModel } from "../../../models/vaModel.js";
|
import { VAModel, TipsModel, ClientModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Approve or reject leave
|
// Approve or reject leave
|
||||||
async function va_tips_manage_function(req, res) {
|
async function va_tips_manage_function(req, res) {
|
||||||
|
|
@ -39,7 +38,7 @@ async function va_tips_manage_function(req, res) {
|
||||||
"tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
|
"tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -117,7 +116,7 @@ async function va_tips_delete(req, res) {
|
||||||
// "tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
|
// "tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ const projectSchema = new Schema({
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dedicatedHours: {
|
dedicatedHours: {
|
||||||
type: Array,
|
type: String,
|
||||||
default: ["0", "0", "0", "0", "0", "0", "0"]
|
default: "0"
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,13 @@ import { project_add_sf } from "../../controllers/project/projectAddSf.controlle
|
||||||
import { update_project_sf } from "../../controllers/project/projectUpdateSf.controller.js"
|
import { update_project_sf } from "../../controllers/project/projectUpdateSf.controller.js"
|
||||||
import { authValidator } from "../../middlewares/auth.middleware.js";
|
import { authValidator } from "../../middlewares/auth.middleware.js";
|
||||||
import { delete_project_sf } from "../../controllers/project/deleteProjectSf.controller.js"
|
import { delete_project_sf } from "../../controllers/project/deleteProjectSf.controller.js"
|
||||||
import { createWireGuardConnection, disconnectWireGuardConnection } from '../../vpnHandler/vpn.js';
|
|
||||||
|
|
||||||
const v1Routes = express.Router()
|
const v1Routes = express.Router()
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
v1Routes.get("/auth", function (req, res) {
|
v1Routes.get("/auth", function(req, res) {
|
||||||
res.sendFile(path.join(__dirname, '/auth.html'));
|
res.sendFile(path.join(__dirname, '/auth.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -35,50 +34,9 @@ v1Routes.delete("/project/:projectId", authValidator, delete_project_sf);
|
||||||
|
|
||||||
v1Routes.post("/password/reset", process_reset);
|
v1Routes.post("/password/reset", process_reset);
|
||||||
|
|
||||||
v1Routes.get("/", function (req, res) {
|
v1Routes.get("/", function(req, res) {
|
||||||
res.json({ "connected": true })
|
res.json({"connected": true})
|
||||||
})
|
})
|
||||||
|
|
||||||
v1Routes.post("/vpn/connect", (req, res) => {
|
|
||||||
try {
|
|
||||||
const data = createWireGuardConnection();
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: "VPN connection created",
|
|
||||||
...data
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
error: err.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
v1Routes.post("/vpn/disconnect", (req, res) => {
|
|
||||||
try {
|
|
||||||
const { ip } = req.body;
|
|
||||||
if (!ip) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
error: "IP address is required"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = disconnectWireGuardConnection(ip);
|
|
||||||
if (result && result.success) {
|
|
||||||
return res.status(200).json(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(400).json({success: false, error: result.message});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
error: err.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// v1Routes.use("/form_submit", authValidator, formRoutes);
|
// v1Routes.use("/form_submit", authValidator, formRoutes);
|
||||||
export default v1Routes;
|
export default v1Routes;
|
||||||
|
|
@ -114,6 +114,10 @@ app.get('/clientregister/:sf_token?', (req, res) => {
|
||||||
|
|
||||||
app.get('/auth/password/reset-form', render_reset_form);
|
app.get('/auth/password/reset-form', render_reset_form);
|
||||||
|
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.status(201).send({ status: "Server is healthy" });
|
||||||
|
})
|
||||||
|
|
||||||
app.get("/client/login", (req, res) => {
|
app.get("/client/login", (req, res) => {
|
||||||
res.render("client_login")
|
res.render("client_login")
|
||||||
})
|
})
|
||||||
|
|
@ -503,8 +507,12 @@ app.get("/forgot-password", (req, res) => {
|
||||||
res.render("forgot_password")
|
res.render("forgot_password")
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get("/health", (req, res) => {
|
app.get("/heath", () => {
|
||||||
return res.status(200).json({ "success": true, "message": "Server is Healthy" })
|
if (Math.round(Math.random()) === 1) {
|
||||||
|
return res.status(200).json({ "success": true, "message": "Server is Healthy" })
|
||||||
|
} else {
|
||||||
|
return res.status(403).json({ "success": false, "message": "Server is not Healthy" })
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import bcrypt from "bcrypt";
|
||||||
import { VAModel, ClientModel } from "../models/vaModel.js";
|
import { VAModel, ClientModel } from "../models/vaModel.js";
|
||||||
import logger from "../config/logger.js";
|
import logger from "../config/logger.js";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import authFetch from "../utils/authFetch.js";
|
|
||||||
|
|
||||||
class PasswordResetService {
|
class PasswordResetService {
|
||||||
constructor(passwordResetRepository) {
|
constructor(passwordResetRepository) {
|
||||||
|
|
@ -170,19 +169,14 @@ class PasswordResetService {
|
||||||
try {
|
try {
|
||||||
const userTypeText = userType === 'va' ? 'VA' : 'CLIENT';
|
const userTypeText = userType === 'va' ? 'VA' : 'CLIENT';
|
||||||
|
|
||||||
const res = await authFetch(`${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/forgot-password`, {
|
await axios.post(`${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/forgot-password`, {
|
||||||
method: 'POST',
|
data: {
|
||||||
headers: {
|
email: email,
|
||||||
'Content-Type': 'application/json',
|
url: resetLink,
|
||||||
},
|
userType: userTypeText
|
||||||
body: JSON.stringify({
|
}
|
||||||
data: {
|
|
||||||
email: email,
|
|
||||||
url: resetLink,
|
|
||||||
userType: userTypeText
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`PasswordReset Service - sendResetEmail error for ${userType}: ${error.message}`);
|
logger.error(`PasswordReset Service - sendResetEmail error for ${userType}: ${error.message}`);
|
||||||
throw new Error("Failed to send reset email");
|
throw new Error("Failed to send reset email");
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class VaClientService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize and prepare data
|
// Sanitize and prepare data
|
||||||
const clientData = await this.sanitizeClientData(data);
|
const clientData = this.sanitizeClientData(data);
|
||||||
const response = await this.clientRepository.create(clientData);
|
const response = await this.clientRepository.create(clientData);
|
||||||
|
|
||||||
if(response) {
|
if(response) {
|
||||||
|
|
@ -263,17 +263,13 @@ class VaClientService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to sanitize data
|
// Helper method to sanitize data
|
||||||
async sanitizeClientData(payload) {
|
sanitizeClientData(payload) {
|
||||||
const { userDetails } = payload;
|
const { userDetails } = payload;
|
||||||
|
|
||||||
const saltRounds = 10;
|
|
||||||
const hashedPassword = await bcrypt.hash(userDetails.password, saltRounds);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userDetails: {
|
userDetails: {
|
||||||
name: userDetails.name,
|
name: userDetails.name,
|
||||||
email: userDetails.email.toLowerCase(),
|
email: userDetails.email.toLowerCase(),
|
||||||
password: hashedPassword,
|
|
||||||
address: userDetails.address || null,
|
address: userDetails.address || null,
|
||||||
city: userDetails.city || null,
|
city: userDetails.city || null,
|
||||||
state: userDetails.state || null,
|
state: userDetails.state || null,
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import fetch from "node-fetch"; // or global fetch in Node 18+
|
|
||||||
|
|
||||||
let preOptions = {
|
|
||||||
token: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const getAccessToken = async () => {
|
|
||||||
try {
|
|
||||||
|
|
||||||
|
|
||||||
const response = await fetch(`${process.env.SF_DOCTRACKER_API_URL}services/oauth2/token`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
grant_type: 'client_credentials',
|
|
||||||
client_id: process.env.SF_CLIENT_ID,
|
|
||||||
client_secret: process.env.SF_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
console.log(response)
|
|
||||||
const access_token = (await response.json()).access_token;
|
|
||||||
preOptions.token = access_token;
|
|
||||||
return access_token;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching access token:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export const authFetch = async (url, options = {}) => {
|
|
||||||
let isvalid = false;
|
|
||||||
|
|
||||||
if (preOptions.token != "" && preOptions.token != null && preOptions.token != undefined) {
|
|
||||||
isvalid = await fetch(`${process.env.SF_DOCTRACKER_TOKEN_VALIDATION_URL}services/apexrest/docva/health}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${preOptions.token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const a = JSON.stringify(isvalid.body);
|
|
||||||
console.log(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isvalid.status !== 200) {
|
|
||||||
await getAccessToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
...(options.headers || {}),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${preOptions.token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default authFetch;
|
|
||||||
export { getAccessToken }
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
import fs from "fs";
|
|
||||||
import { execSync } from "child_process";
|
|
||||||
|
|
||||||
const WG_CONF = "/etc/wireguard/wg0.conf";
|
|
||||||
const PEER_DB = "/etc/wireguard/peers.json";
|
|
||||||
const CLIENT_DIR = "/etc/wireguard/clients";
|
|
||||||
|
|
||||||
const SERVER_PUBLIC_KEY = process.env.SERVER_PUBLIC_KEY || "jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U=";
|
|
||||||
const SERVER_IP = process.env.SERVER_PUBLIC_IP || "35.95.209.161";
|
|
||||||
|
|
||||||
function loadDb() {
|
|
||||||
try {
|
|
||||||
return JSON.parse(fs.readFileSync(PEER_DB, "utf8"));
|
|
||||||
} catch {
|
|
||||||
const initial = { lastIp: 1, active: {}, free: [] };
|
|
||||||
fs.writeFileSync(PEER_DB, JSON.stringify(initial, null, 2));
|
|
||||||
return initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveDb(db) {
|
|
||||||
fs.writeFileSync(PEER_DB, JSON.stringify(db, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
function allocateIp(db) {
|
|
||||||
if (db.free.length > 0) {
|
|
||||||
return db.free.shift(); // reuse old IP
|
|
||||||
}
|
|
||||||
|
|
||||||
db.lastIp++;
|
|
||||||
if (db.lastIp >= 255) db.lastIp = 2;
|
|
||||||
|
|
||||||
return `10.0.0.${db.lastIp}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateKeyPair() {
|
|
||||||
const privateKey = execSync("wg genkey").toString().trim();
|
|
||||||
const publicKey = execSync(`echo ${privateKey} | wg pubkey`).toString().trim();
|
|
||||||
return { privateKey, publicKey };
|
|
||||||
}
|
|
||||||
|
|
||||||
// function addPeerToServer(publicKey, ip) {
|
|
||||||
// const ipLast = ip.split(".")[3];
|
|
||||||
|
|
||||||
// const peerEntry = `
|
|
||||||
// [Peer]
|
|
||||||
// PublicKey = ${publicKey}
|
|
||||||
// AllowedIPs = 10.0.0.${ipLast}/32
|
|
||||||
// `;
|
|
||||||
|
|
||||||
// fs.appendFileSync(WG_CONF, peerEntry);
|
|
||||||
|
|
||||||
// // apply config instantly
|
|
||||||
// // execSync(`wg syncconf wg0 <(wg-quick strip wg0)`, { shell: "/bin/bash" });
|
|
||||||
// execSync(`wg-quick strip wg0 > /tmp/wg0.tmp`);
|
|
||||||
// execSync(`wg syncconf wg0 /tmp/wg0.tmp`);
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
function addPeerToServer(publicKey, ip) {
|
|
||||||
const peerEntry = `
|
|
||||||
[Peer]
|
|
||||||
PublicKey = ${publicKey}
|
|
||||||
AllowedIPs = ${ip}/32
|
|
||||||
`;
|
|
||||||
|
|
||||||
fs.appendFileSync(WG_CONF, peerEntry);
|
|
||||||
|
|
||||||
execSync("wg-quick strip wg0 > /tmp/wg0.tmp");
|
|
||||||
execSync("wg syncconf wg0 /tmp/wg0.tmp");
|
|
||||||
execSync("rm -f /tmp/wg0.tmp");
|
|
||||||
|
|
||||||
execSync(
|
|
||||||
`wg set wg0 peer ${publicKey} allowed-ips ${ip}/32`,
|
|
||||||
{ stdio: "inherit" }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function generateClientConf(privateKey, ip) {
|
|
||||||
return `
|
|
||||||
[Interface]
|
|
||||||
PrivateKey = ${privateKey}
|
|
||||||
Address = ${ip}/32
|
|
||||||
DNS = 1.1.1.1
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = ${SERVER_PUBLIC_KEY}
|
|
||||||
Endpoint = ${SERVER_IP}:51820
|
|
||||||
AllowedIPs = 10.0.0.0/24
|
|
||||||
PersistentKeepalive = 25
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createWireGuardConnection() {
|
|
||||||
let db = loadDb();
|
|
||||||
|
|
||||||
// 1. allocate IP
|
|
||||||
const ip = allocateIp(db);
|
|
||||||
|
|
||||||
// 2. generate keys
|
|
||||||
const { privateKey, publicKey } = generateKeyPair();
|
|
||||||
|
|
||||||
// 3. update server config
|
|
||||||
addPeerToServer(publicKey, ip);
|
|
||||||
|
|
||||||
// 4. update DB
|
|
||||||
db.active[ip] = publicKey;
|
|
||||||
saveDb(db);
|
|
||||||
|
|
||||||
// 5. prepare client.conf
|
|
||||||
const clientConf = generateClientConf(privateKey, ip);
|
|
||||||
|
|
||||||
// (optional) save it
|
|
||||||
if (!fs.existsSync(CLIENT_DIR)) fs.mkdirSync(CLIENT_DIR);
|
|
||||||
fs.writeFileSync(`${CLIENT_DIR}/client-${ip}.conf`, clientConf);
|
|
||||||
|
|
||||||
// 6. return to Electron app
|
|
||||||
return { ip, publicKey, clientConf };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -------- Disconnect VPN PART START -----------
|
|
||||||
const removePeerFromConf = (publicKey) => {
|
|
||||||
const conf = fs.readFileSync(WG_CONF, "utf8");
|
|
||||||
|
|
||||||
const peerRegex = new RegExp(
|
|
||||||
`\\n\\[Peer\\][\\s\\S]*?PublicKey = ${publicKey}[\\s\\S]*?(?=\\n\\[Peer\\]|$)`,
|
|
||||||
"g"
|
|
||||||
);
|
|
||||||
|
|
||||||
const updated = conf.replace(peerRegex, "\n");
|
|
||||||
|
|
||||||
fs.writeFileSync(WG_CONF, updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncWireGuard() {
|
|
||||||
execSync("wg-quick strip wg0 > /tmp/wg0.tmp");
|
|
||||||
execSync("wg syncconf wg0 /tmp/wg0.tmp");
|
|
||||||
execSync("rm -f /tmp/wg0.tmp");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disconnectWireGuardConnection(ip) {
|
|
||||||
const db = loadDb();
|
|
||||||
|
|
||||||
const publicKey = db.active[ip];
|
|
||||||
if (!db.active[ip]) {
|
|
||||||
return { success: false, message: "VPN already disconnected" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1️⃣ Remove peer from live kernel
|
|
||||||
execSync(`wg set wg0 peer ${publicKey} remove`);
|
|
||||||
|
|
||||||
// 2️⃣ Remove from config file
|
|
||||||
removePeerFromConf(publicKey);
|
|
||||||
|
|
||||||
// 3️⃣ Sync config
|
|
||||||
syncWireGuard();
|
|
||||||
|
|
||||||
// 4️⃣ Update DB (free IP)
|
|
||||||
delete db.active[ip];
|
|
||||||
db.free.push(ip);
|
|
||||||
saveDb(db);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: "VPN disconnected",
|
|
||||||
ip
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -111,24 +111,6 @@
|
||||||
<label for="email" class="input-label">Email *</label>
|
<label for="email" class="input-label">Email *</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<div class="input-container">
|
|
||||||
<input type="password" id="password" class="input-field" placeholder=" " required autocomplete="new-password">
|
|
||||||
<label for="password" class="input-label">Create Password *</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="input-container">
|
|
||||||
<input type="password" id="confirmPassword" class="input-field" placeholder=" " required autocomplete="new-password">
|
|
||||||
<label for="confirmPassword" class="input-label">Confirm Password *</label>
|
|
||||||
</div>
|
|
||||||
<div class="invalid-feedback" id="password-match-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">Passwords do not match</div>
|
|
||||||
<div class="password-actions">
|
|
||||||
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()">Show Password</button>
|
|
||||||
<button type="button" class="password-generate" onclick="generatePassword()">Generate Strong Password</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<div></div> <!-- Empty div for spacing -->
|
<div></div> <!-- Empty div for spacing -->
|
||||||
<button type="button" class="btn btn-primary" onclick="handleNextStep(2)">Continue</button>
|
<button type="button" class="btn btn-primary" onclick="handleNextStep(2)">Continue</button>
|
||||||
|
|
@ -260,7 +242,7 @@
|
||||||
<div class="error-message" id="step-4-error-message"></div>
|
<div class="error-message" id="step-4-error-message"></div>
|
||||||
</div>
|
</div>
|
||||||
<p style="margin-bottom: 20px; color: #6b7280; font-size: 14px;">
|
<p style="margin-bottom: 20px; color: #6b7280; font-size: 14px;">
|
||||||
We will use the details below in the agreement that we will send to your prior to starting the project.
|
We will use the details below in the agreement that we will send to your prior to entering the project.
|
||||||
</p>
|
</p>
|
||||||
<form id="signatory-form">
|
<form id="signatory-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
@ -340,7 +322,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="payment-option" onclick="selectPaymentOption('bank')">
|
<div class="payment-option" onclick="selectPaymentOption('bank')">
|
||||||
<h4>Bank Account (ACH)</h4>
|
<h4>Bank Account (ACR)</h4>
|
||||||
<p>Direct bank transfer</p>
|
<p>Direct bank transfer</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="bank-details" class="payment-details">
|
<div id="bank-details" class="payment-details">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue