Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/haris-khan-durrani/client-portal
Built White label Client Portal using Nodejs Axios Express EJS Express-session
https://github.com/haris-khan-durrani/client-portal
axios decrypted ejs ejs-express encrypted express express-session gohighlevel gohighlevel-api node nodejs
Last synced: about 2 months ago
JSON representation
Built White label Client Portal using Nodejs Axios Express EJS Express-session
- Host: GitHub
- URL: https://github.com/haris-khan-durrani/client-portal
- Owner: Haris-khan-Durrani
- Created: 2024-08-29T12:56:14.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2024-08-29T13:11:49.000Z (4 months ago)
- Last Synced: 2024-11-08T20:09:36.970Z (about 2 months ago)
- Topics: axios, decrypted, ejs, ejs-express, encrypted, express, express-session, gohighlevel, gohighlevel-api, node, nodejs
- Language: JavaScript
- Homepage: https://portal.ebmsbusiness.com
- Size: 131 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
![Project](https://api.hariskhandurrani.com/portal1.jpg)
# Express.js Application with MySQL, Sessions, and OTP AuthenticationThis project is an advanced Express.js application that integrates MySQL, session management, OTP-based authentication, file handling, and dynamic page rendering. The application is built to handle domain-specific configurations and communicate with external services via Socket.IO.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Middleware](#middleware)
- [Routes](#routes)
- [Encryption/Decryption](#encryptiondecryption)
- [OTP Email Sending](#otp-email-sending)
- [Database Schema](#database-schema)
- [Socket.IO Integration](#socketio-integration)
- [Environment Variables](#environment-variables)
- [CORS Configuration](#cors-configuration)
- [Running the Application](#running-the-application)
- [License](#license)## Features
- **Session Management**: Securely manages user sessions using `express-session` with MySQL as the session store.
- **Domain-Based Configuration**: Middleware to fetch, cache, and utilize configurations based on the domain.
- **OTP Authentication**: Secure OTP generation, email sending, and verification for login authentication.
- **File Handling**: Supports secure file uploads with encryption for sensitive data.
- **Dynamic Page Rendering**: Renders pages dynamically based on domain-specific settings stored in the database.
- **Socket.IO Integration**: Connects to a Socket.IO server for real-time data fetching and interaction.
- **Email Notifications**: Sends OTPs and other notifications via a configured email service (e.g., Gmail SMTP).
- **CORS Support**: Allows cross-origin requests with configurable CORS settings.## Installation
To set up the application locally, follow these steps:
### 1. Clone the Repository
```bash
git clone https://github.com/your-repo-name.git
cd your-repo-name
```## Usage
### Middleware
- **`domainCheck` Middleware**:
- **Purpose**: Fetches domain-specific configurations from the database and stores them in the session.
- **How it Works**:
- It checks the `x-forwarded-host` header in the request to determine the domain.
- If the configuration for the domain is already stored in the session, it uses that; otherwise, it fetches the configuration from the `domain_config` table in the database.
- If no configuration is found for the domain, a 404 error is returned.
- Example:
```javascript
const domainCheck = async (req, res, next) => {
const domain = req.headers['x-forwarded-host'];
if (req.session && req.session.config && req.session.config.domain === domain) {
req.config = req.session.config;
return next();
}try {
const [rows] = await pool.query('SELECT * FROM domain_config WHERE domain = ?', [domain]);
if (rows.length > 0) {
req.config = rows[0];
req.session.config = req.config;
next();
} else {
res.status(404).send('Configuration not found for this domain.');
}
} catch (error) {
res.status(500).send('Internal Server Error: Unable to fetch configuration.');
}
};
```### Routes
- **`/` (GET)**:
- **Description**: Renders the login page. If the user is already authenticated, redirects to the dashboard.
- **Example Usage**:
```javascript
app.get('/', (req, res) => {
if (req.session.user) {
res.redirect('/dashboard');
} else {
res.render('login', { logo: req.config.logo_url });
}
});
```- **`/login` (POST)**:
- **Description**: Handles user login by generating and sending an OTP to the user's email.
- **Process**:
- Receives the username (email).
- Generates a 6-digit OTP and stores it in a map for verification.
- Sends the OTP via email and a webhook.
- Redirects the user to an OTP entry page.
- **Example Usage**:
```javascript
app.post('/login', async (req, res) => {
const { username } = req.body;
const otp = generateOTP();
otpMap.set(username, otp);const emailStatus = await sendOTPEmailAndTriggerWebhook(username, otp);
if (emailStatus) {
res.render('otp', { username });
} else {
res.send('Error occurred while sending OTP.');
}
});
```- **`/verifyOTP` (POST)**:
- **Description**: Verifies the OTP entered by the user. If the OTP is correct, the user is redirected to the dashboard.
- **Process**:
- Receives the OTP and username.
- Verifies if the OTP matches the one stored in the map.
- If valid, the session is updated, and the user is redirected to the dashboard.
- **Example Usage**:
```javascript
app.post('/verifyOTP', (req, res) => {
const { username, otp } = req.body;
if (otpMap.has(username) && otpMap.get(username) === otp) {
req.session.user = username;
otpMap.delete(username);
res.redirect('/dashboard');
} else {
res.redirect('/?error=Invalid OTP');
}
});
```- **`/dashboard` (GET)**:
- **Description**: Displays the user dashboard with domain-specific configurations.
- **Process**:
- The user's session is checked to ensure they are logged in.
- Real-time data is fetched using Socket.IO and rendered on the dashboard.
- **Example Usage**:
```javascript
app.get('/dashboard', sessionCheck, async (req, res) => {
socket.emit('contact', {
query1: req.session.user,
query2: req.config.location_id,
query3: 10,
});socket.once('searchResult', async (data) => {
if (data.error) {
res.status(500).send('An error occurred.');
} else {
const htmlContent = generateHtmlContent(data.contacts);
res.render('dashboard', {
title: 'Dashboard',
user: req.session.user,
logo: req.config.logo_url,
contacts: htmlContent,
});
}
});
});
```- **`/company` (GET)**:
- **Description**: Displays detailed information about a specific company, including downloadable files and other details.
- **Process**:
- The company ID is passed as a query parameter.
- The company details are fetched using Socket.IO.
- The data is then rendered dynamically on the page.
- **Example Usage**:
```javascript
app.get('/company', sessionCheck, async (req, res) => {
const companyId = req.query.id;
socket.emit('contactid', {
query1: companyId,
query2: req.config.location_id,
});socket.once('searchResult', async (data) => {
const htmlContent = generateCompanyHtml(data.contact);
res.render('company', {
title: `${data.contact.companyName} | Company`,
user: req.session.user,
contacts: htmlContent,
});
});
});
```- **`/single` (GET)**:
- **Description**: Displays visa-related information for a specific company.
- **Process**:
- The visa data is fetched based on the company ID using Socket.IO.
- The visa information is rendered dynamically.
- **Example Usage**:
```javascript
app.get('/single', sessionCheck, async (req, res) => {
const companyId = req.query.id;
socket.emit('contactid', {
query1: companyId,
query2: req.config.v_location_id,
});socket.once('searchResult', async (data) => {
const htmlContent = generateVisaHtml(data.contact);
res.render('single', {
title: `${data.contact.companyName} | Visa`,
user: req.session.user,
contacts: htmlContent,
});
});
});
```- **`/add-page` (GET/POST)**:
- **Description**: Admin interface to add new pages. Pages are tied to specific domains and can be managed through this route.
- **GET**: Displays a form to add a new page.
- **POST**: Handles form submission and adds a new page to the database.
- **Example Usage**:
```javascript
app.get('/add-page', sessionCheck, async (req, res) => {
const allPages = await fetchAllPages();
res.render('add-page', {
user: req.session.user,
pages: allPages,
});
});app.post('/add-page', express.json(), sessionCheck, (req, res) => {
const { domain, icon, title, featureImageUrl, landingPageUrl } = req.body;
const sql = 'INSERT INTO pages (domain, icon, title, feature_image_url, landing_page_url) VALUES (?, ?, ?, ?, ?)';
db.query(sql, [domain, icon, title, featureImageUrl, landingPageUrl], (err, result) => {
if (err) {
return res.status(500).send('Error adding new page');
}
res.redirect('/add-page');
});
});
```- **`/edit-addonpage/:id` (GET/POST)**:
- **Description**: Edit an existing page by its ID. The form is pre-filled with the current page data.
- **GET**: Fetches the existing page data and displays it in a form for editing.
- **POST**: Handles form submission and updates the page in the database.
- **Example Usage**:
```javascript
app.get('/edit-addonpage/:id', sessionCheck, async (req, res) => {
const pageId = req.params.id;
const pageData = await fetchPageById(pageId);
res.render('edit-page', {
user: req.session.user,
page: pageData,
});
});app.post('/edit-addonpage/:id', express.json(), sessionCheck, (req, res) => {
const { id, domain, icon, title, featureImageUrl, landingPageUrl } = req.body;
const sql = 'UPDATE pages SET domain = ?, icon = ?, title = ?, feature_image_url = ?, landing_page_url = ? WHERE id = ?';
db.query(sql, [domain, icon, title, featureImageUrl, landingPageUrl, id], (err, result) => {
if (err) {
return res.status(500).send('Error updating page');
}
res.redirect('/add-page');
});
});
```- **`/addon/:page` (GET)**:
- **Description**: Renders addon pages dynamically based on the domain and user session.
- **Process**:
- The page URL is decrypted and rendered with additional parameters for the user.
- **Example Usage**:
```javascript
app.get('/addon/:page', sessionCheck, async (req, res) => {
const addonPageUrl = decrypt(req.params.page, "667766");
const userParams = {
full_name: req.session.namo,
email: req.session.user,
};
const updatedUrl = `${addonPageUrl}?${new URLSearchParams(userParams).toString()}`;
res.render('addon', {
user: req.session.user,
addonPageUrl: updatedUrl,
});
});
```