Compare commits

..

22 Commits

Author SHA1 Message Date
Aakash Mittal 5f043ed80a Added password option in client registration 2025-12-17 12:27:19 +05:30
=bhagwanSingh efa6ce929e A 2025-12-17 09:29:28 +05:30
=bhagwanSingh b64962738a a 2025-12-17 09:26:04 +05:30
=bhagwanSingh 3d337780da A 2025-12-17 09:24:11 +05:30
=bhagwanSingh 2e5fed545f Auth Fetch and Server Health Update 2025-12-17 09:13:45 +05:30
=bhagwanSingh dba68ed584 Password Rest update 2025-12-16 19:25:41 +05:30
=bhagwanSingh 320d44f4b5 Added authFetch currently getting access toke for all request as health check api is not available on sf ye 2025-12-16 19:01:13 +05:30
=bhagwanSingh 166cf4db45 Merge branch 'backend' of http://git.hicglobalsolutions.in/BhawanSingh/va-tracker-middleware 2025-12-16 15:41:57 +05:30
=bhagwanSingh 085e0c0e6d VPN Update 2025-12-16 14:45:30 +05:30
=bhagwanSingh 15bbbd8a98 a 2025-12-16 14:26:17 +05:30
=bhagwanSingh c20db85ada a 2025-12-16 14:21:00 +05:30
=bhagwanSingh 0e8ce1f850 a 2025-12-16 13:42:50 +05:30
=bhagwanSingh 9e4d118009 a 2025-12-16 12:47:57 +05:30
=bhagwanSingh 668c81b86e a 2025-12-16 12:33:20 +05:30
=bhagwanSingh b4b0e27ecf a 2025-12-16 12:21:10 +05:30
=bhagwanSingh c74f792df6 localhost not binding with port 2025-12-16 12:09:46 +05:30
=bhagwanSingh f538371116 Server Keys env update 2025-12-16 11:47:27 +05:30
=bhagwanSingh fd382ccb93 a 2025-12-16 07:29:45 +05:30
=bhagwanSingh e79383c1cf Update 2025-12-15 18:58:56 +05:30
=bhagwanSingh 67af7df83c Added VPN Disconnect 2025-12-15 16:26:26 +05:30
=bhagwanSingh cbfa03e57c wg0 permission issue 2025-12-15 13:44:59 +05:30
=bhagwanSingh 03814c6c99 VPN Setup 2025-12-15 12:40:27 +05:30
22 changed files with 442 additions and 49 deletions

18
package-lock.json generated
View File

@ -40,7 +40,8 @@
"uglify-js": "^3.19.3",
"uuid": "^11.0.3",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
"winston-daily-rotate-file": "^5.0.0",
"wireguard-tools": "^0.1.0"
},
"devDependencies": {
"@babel/cli": "^7.26.4",
@ -6198,6 +6199,12 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"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": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
@ -9338,6 +9345,15 @@
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",

View File

@ -45,7 +45,8 @@
"uglify-js": "^3.19.3",
"uuid": "^11.0.3",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
"winston-daily-rotate-file": "^5.0.0",
"wireguard-tools": "^0.1.0"
},
"devDependencies": {
"@babel/cli": "^7.26.4",
@ -58,4 +59,4 @@
"webpack-cli": "^6.0.1",
"webpack-node-externals": "^3.0.0"
}
}
}

View File

@ -492,9 +492,11 @@ async function validateStep(step) {
} else if (step === 1) {
const email = document.getElementById('email').value;
const companyName = document.getElementById('company-name').value;
if (!validateEmail(email) && !companyName) {
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (!validateEmail(email) && !companyName && !password && !confirmPassword) {
isValid = false;
showError('Both Fields are Required', null, step);
showError('Fields are Required', null, step);
let requiredFields1 = form.querySelectorAll('[required]');
requiredFields1.forEach(field => {
if (!field.value.trim()) {
@ -522,6 +524,21 @@ async function validateStep(step) {
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
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
@ -612,6 +629,7 @@ function saveStepData(step) {
case 1:
formData.userDetails.name = document.getElementById('company-name').value;
formData.userDetails.email = document.getElementById('email').value;
formData.userDetails.password = document.getElementById('password').value;
break;
case 2:
formData.userDetails.address = document.getElementById('address').value;
@ -1954,4 +1972,52 @@ function preventInvalidAccountHolderChars(event) {
event.preventDefault();
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';
}
}

View File

@ -25,10 +25,10 @@ async function va_register_client(req, res) {
try {
const payload = req.body || {};
const { userDetails } = payload;
if (!userDetails || !userDetails.email || !userDetails.name) {
if (!userDetails || !userDetails.email || !userDetails.name || !userDetails.password) {
return res.status(400).json({
success: false,
message: "Missing required fields: name and email are required"
message: "Missing required fields: name, email, and password are required"
});
}

View File

@ -71,7 +71,7 @@ async function project_add_sf(req, res) {
hourlyRate: hourlyRate || '0',
clientId: clientObjectId,
vaAssigned: vaAssigned,
dedicatedHours: dedicatedHours ? parseFloat(dedicatedHours) : 0,
dedicatedHours: dedicatedHours || ["0", "0", "0", "0", "0", "0", "0"],
vaWorkingScheduleStart: vaWorkingScheduleStart || null,
vaWorkingScheduleEnd: vaWorkingScheduleEnd || null,
isFlexibleAllowed: isFlexibleAllowed || true,

View File

@ -74,7 +74,7 @@ async function update_project_sf(req, res) {
// Update dedicated hours if provided
if (dedicatedHours !== undefined) {
updateData.dedicatedHours = parseFloat(dedicatedHours) || 0;
updateData.dedicatedHours = dedicatedHours || ["0", "0", "0", "0", "0", "0", "0"];
}
// Update total logged hours if provided

View File

@ -1,6 +1,7 @@
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { VAModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js";
import authFetch from "../../../utils/authFetch.js";
// function to close open work tracks and create logout
async function closePreviousSession(vaId, lastLog) {
@ -53,7 +54,7 @@ async function closePreviousSession(vaId, lastLog) {
"time": logoutLog.time,
}
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -1,5 +1,6 @@
import jwt from "jsonwebtoken";
import { VAModel, LeavesModel } from "../../../models/vaModel.js";
import authFetch from "../../../utils/authFetch.js";
// Apply for leave
async function va_leave_apply_function(req, res) {
@ -164,7 +165,7 @@ async function va_leave_apply_function(req, res) {
"rejectionReason": ""
}
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -1,4 +1,5 @@
import { VAModel } from "../../../models/vaModel.js";
import authFetch from "../../../utils/authFetch.js";
async function va_payslip_data_function(req, res) {
try {
@ -11,7 +12,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 response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',

View File

@ -2,7 +2,7 @@ async function va_payroll_schedule_function(req, res) {
try {
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/payroll-schedule`;
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',

View File

@ -1,5 +1,6 @@
import bcrypt from "bcrypt";
import { VAModel } from "../../../models/vaModel.js";
import authFetch from "../../../utils/authFetch.js";
// Register a new VA
async function va_register(req, res) {
@ -53,7 +54,7 @@ async function va_register(req, res) {
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/virtualassistant`;
delete created.userDetails._id;
created.userDetails._id = created._id; // Include VA's MongoDB ID
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -1,4 +1,5 @@
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
function getDayRange(date = new Date()) {
@ -197,7 +198,7 @@ async function closePreviousSession(vaId, lastLog, logTime = new Date()) {
"time": logoutLog.time,
}
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -373,7 +374,7 @@ async function va_project_log_callback(req, res) {
"time": createdLog.time,
}
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -471,7 +472,7 @@ async function va_project_log_callback(req, res) {
"time": createdLog.time,
}
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -533,7 +534,7 @@ async function va_project_log_callback(req, res) {
"time": createdLog.time,
}
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -1,5 +1,6 @@
import jwt from "jsonwebtoken";
import { VAModel, ProjectModel, AdjustedHoursModel } from "../../../models/vaModel.js";
import authFetch from "../../../utils/authFetch.js";
// Adjust missing hours by creating new records
async function va_timesheet_adjustment_function(req, res) {
@ -155,7 +156,7 @@ async function va_timesheet_adjustment_function(req, res) {
"adjustedHours": formatTime(adjustedSeconds)
}
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -1,4 +1,5 @@
import { VAModel, TipsModel, ClientModel } from "../../../models/vaModel.js";
import authFetch from "../../../utils/authFetch.js";
// Approve or reject leave
async function va_tips_manage_function(req, res) {
@ -38,7 +39,7 @@ async function va_tips_manage_function(req, res) {
"tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
}
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -116,7 +117,7 @@ async function va_tips_delete(req, res) {
// "tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
// }
const response = await fetch(salesforceUrl, {
const response = await authFetch(salesforceUrl, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',

View File

@ -101,8 +101,8 @@ const projectSchema = new Schema({
}
],
dedicatedHours: {
type: String,
default: "0"
type: Array,
default: ["0", "0", "0", "0", "0", "0", "0"]
},
status: {
type: String,

View File

@ -13,13 +13,14 @@ import { project_add_sf } from "../../controllers/project/projectAddSf.controlle
import { update_project_sf } from "../../controllers/project/projectUpdateSf.controller.js"
import { authValidator } from "../../middlewares/auth.middleware.js";
import { delete_project_sf } from "../../controllers/project/deleteProjectSf.controller.js"
import { createWireGuardConnection, disconnectWireGuardConnection } from '../../vpnHandler/vpn.js';
const v1Routes = express.Router()
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
v1Routes.get("/auth", function(req, res) {
v1Routes.get("/auth", function (req, res) {
res.sendFile(path.join(__dirname, '/auth.html'));
});
@ -34,9 +35,50 @@ v1Routes.delete("/project/:projectId", authValidator, delete_project_sf);
v1Routes.post("/password/reset", process_reset);
v1Routes.get("/", function(req, res) {
res.json({"connected": true})
})
v1Routes.get("/", function (req, res) {
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);
export default v1Routes;

View File

@ -114,10 +114,6 @@ app.get('/clientregister/:sf_token?', (req, res) => {
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) => {
res.render("client_login")
})
@ -507,12 +503,8 @@ app.get("/forgot-password", (req, res) => {
res.render("forgot_password")
})
app.get("/heath", () => {
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.get("/health", (req, res) => {
return res.status(200).json({ "success": true, "message": "Server is Healthy" })
})
app.use((req, res) => {

View File

@ -3,6 +3,7 @@ import bcrypt from "bcrypt";
import { VAModel, ClientModel } from "../models/vaModel.js";
import logger from "../config/logger.js";
import axios from "axios";
import authFetch from "../utils/authFetch.js";
class PasswordResetService {
constructor(passwordResetRepository) {
@ -169,14 +170,19 @@ class PasswordResetService {
try {
const userTypeText = userType === 'va' ? 'VA' : 'CLIENT';
await axios.post(`${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/forgot-password`, {
data: {
email: email,
url: resetLink,
userType: userTypeText
}
const res = await authFetch(`${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/forgot-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: {
email: email,
url: resetLink,
userType: userTypeText
}
})
});
} catch (error) {
logger.error(`PasswordReset Service - sendResetEmail error for ${userType}: ${error.message}`);
throw new Error("Failed to send reset email");

View File

@ -18,7 +18,7 @@ class VaClientService {
}
// Sanitize and prepare data
const clientData = this.sanitizeClientData(data);
const clientData = await this.sanitizeClientData(data);
const response = await this.clientRepository.create(clientData);
if(response) {
@ -263,13 +263,17 @@ class VaClientService {
}
// Helper method to sanitize data
sanitizeClientData(payload) {
async sanitizeClientData(payload) {
const { userDetails } = payload;
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(userDetails.password, saltRounds);
return {
userDetails: {
name: userDetails.name,
email: userDetails.email.toLowerCase(),
password: hashedPassword,
address: userDetails.address || null,
city: userDetails.city || null,
state: userDetails.state || null,

68
src/utils/authFetch.js Normal file
View File

@ -0,0 +1,68 @@
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 }

173
src/vpnHandler/vpn.js Normal file
View File

@ -0,0 +1,173 @@
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
};
}

View File

@ -111,6 +111,24 @@
<label for="email" class="input-label">Email *</label>
</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></div> <!-- Empty div for spacing -->
<button type="button" class="btn btn-primary" onclick="handleNextStep(2)">Continue</button>
@ -242,7 +260,7 @@
<div class="error-message" id="step-4-error-message"></div>
</div>
<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 entering the project.
We will use the details below in the agreement that we will send to your prior to starting the project.
</p>
<form id="signatory-form">
<div class="form-group">
@ -322,7 +340,7 @@
</div>
</div>
<div class="payment-option" onclick="selectPaymentOption('bank')">
<h4>Bank Account (ACR)</h4>
<h4>Bank Account (ACH)</h4>
<p>Direct bank transfer</p>
</div>
<div id="bank-details" class="payment-details">