Compare commits
No commits in common. "main" and "what" have entirely different histories.
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -139,9 +139,3 @@ dist
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
.vite/
|
.vite/
|
||||||
|
|
||||||
slingtoken.txt
|
|
||||||
sessions/
|
|
||||||
auth.json
|
|
||||||
buttons.json
|
|
||||||
phones.json
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
"text": "6PM Announcement",
|
"text": "6PM Announcement",
|
||||||
"btnClass": "btn-secondary",
|
"btnClass": "btn-secondary",
|
||||||
"faIcon": "",
|
"faIcon": "",
|
||||||
"name": "6PMAnnouncement",
|
"name": "LivePage",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
"context": "custom-emergency.9001.1",
|
"context": "custom-emergency.9001.1",
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
"text": "15 Min Warning",
|
"text": "15 Min Warning",
|
||||||
"btnClass": "btn-info",
|
"btnClass": "btn-info",
|
||||||
"faIcon": "",
|
"faIcon": "",
|
||||||
"name": "CloseWarning",
|
"name": "LivePage",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
"context": "custom-emergency.9001.1",
|
"context": "custom-emergency.9001.1",
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
"text": "Pool Closed",
|
"text": "Pool Closed",
|
||||||
"btnClass": "btn-warning",
|
"btnClass": "btn-warning",
|
||||||
"faIcon": "",
|
"faIcon": "",
|
||||||
"name": "PoolClosed",
|
"name": "LivePage",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
"context": "custom-emergency.9001.1",
|
"context": "custom-emergency.9001.1",
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
"text": "Health Code Violation",
|
"text": "Health Code Violation",
|
||||||
"btnClass": "btn-warning",
|
"btnClass": "btn-warning",
|
||||||
"faIcon": "",
|
"faIcon": "",
|
||||||
"name": "HealthCodeViolation",
|
"name": "LivePage",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
"context": "custom-emergency.9001.1",
|
"context": "custom-emergency.9001.1",
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
"text": "Snack Shop 15 Min Warning",
|
"text": "Snack Shop 15 Min Warning",
|
||||||
"btnClass": "btn-info",
|
"btnClass": "btn-info",
|
||||||
"faIcon": "",
|
"faIcon": "",
|
||||||
"name": "SnackShopWarning",
|
"name": "LivePage",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
"context": "custom-emergency.9001.1",
|
"context": "custom-emergency.9001.1",
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
"text": "Snack Shop Closed",
|
"text": "Snack Shop Closed",
|
||||||
"btnClass": "btn-warning",
|
"btnClass": "btn-warning",
|
||||||
"faIcon": "",
|
"faIcon": "",
|
||||||
"name": "SnackShopClosed",
|
"name": "LivePage",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
"context": "custom-emergency.9001.1",
|
"context": "custom-emergency.9001.1",
|
||||||
|
|
@ -98,9 +98,7 @@
|
||||||
"context": "custom-emergency.9002.1",
|
"context": "custom-emergency.9002.1",
|
||||||
"timeout": 30000,
|
"timeout": 30000,
|
||||||
"cid": "Emergency Announcement"
|
"cid": "Emergency Announcement"
|
||||||
},
|
}
|
||||||
"sling_chat_id": "17132563",
|
|
||||||
"sling_chat_message": "%initiatingUser% has initiated an Emergency Announcement."
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Lightning Alert",
|
"text": "Lightning Alert",
|
||||||
|
|
@ -118,7 +116,7 @@
|
||||||
{
|
{
|
||||||
"text": "EAP Stand By",
|
"text": "EAP Stand By",
|
||||||
"btnClass": "btn-primary",
|
"btnClass": "btn-primary",
|
||||||
"name": "EAPStandBy",
|
"name": "LightningAlert",
|
||||||
"faIcon": "fa-duotone fa-solid fa-pause",
|
"faIcon": "fa-duotone fa-solid fa-pause",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
|
|
@ -126,14 +124,12 @@
|
||||||
"timeout": 30000,
|
"timeout": 30000,
|
||||||
"cid": "\"Weather Alert\" <9000>",
|
"cid": "\"Weather Alert\" <9000>",
|
||||||
"dial": "9000"
|
"dial": "9000"
|
||||||
},
|
}
|
||||||
"sling_chat_id": "17132563",
|
|
||||||
"sling_chat_message": "CUDA ALERT: EAP Activation - Stand By - Initated by %initiatingUser%"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "EAP Evacuate",
|
"text": "EAP Evacuate",
|
||||||
"btnClass": "btn-danger",
|
"btnClass": "btn-danger",
|
||||||
"name": "EAPEvac",
|
"name": "LightningAlert",
|
||||||
"faIcon": "fa-duotone fa-solid fa-person-to-door",
|
"faIcon": "fa-duotone fa-solid fa-person-to-door",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
|
|
@ -141,14 +137,12 @@
|
||||||
"timeout": 30000,
|
"timeout": 30000,
|
||||||
"cid": "\"Weather Alert\" <9000>",
|
"cid": "\"Weather Alert\" <9000>",
|
||||||
"dial": "9000"
|
"dial": "9000"
|
||||||
},
|
}
|
||||||
"sling_chat_id": "17132563",
|
|
||||||
"sling_chat_message": "CUDA ALERT: EAP Activation - Evacuate - Initated by %initiatingUser%"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Fire Evac",
|
"text": "Fire Evac",
|
||||||
"btnClass": "btn-danger",
|
"btnClass": "btn-danger",
|
||||||
"name": "FireEvac",
|
"name": "LightningAlert",
|
||||||
"faIcon": "fa-duotone fa-solid fa-fire",
|
"faIcon": "fa-duotone fa-solid fa-fire",
|
||||||
"doubleHeight": false,
|
"doubleHeight": false,
|
||||||
"context": {
|
"context": {
|
||||||
|
|
@ -156,9 +150,7 @@
|
||||||
"timeout": 30000,
|
"timeout": 30000,
|
||||||
"cid": "\"Weather Alert\" <9000>",
|
"cid": "\"Weather Alert\" <9000>",
|
||||||
"dial": "9000"
|
"dial": "9000"
|
||||||
},
|
}
|
||||||
"sling_chat_id": "17132563",
|
|
||||||
"sling_chat_message": "Fire Evacuation Initiated by %initiatingUser%"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
11
features.md
11
features.md
|
|
@ -0,0 +1,11 @@
|
||||||
|
Default Roles
|
||||||
|
- SuperAdmin (Do all the thing)
|
||||||
|
- Admin (Can't edit superadmins)
|
||||||
|
- User (Normal user stuff (No user editing lol))
|
||||||
|
|
||||||
|
Individual Perms (For custom roles?)
|
||||||
|
- Can Page
|
||||||
|
- View Users
|
||||||
|
- Create Users
|
||||||
|
- Delete Users
|
||||||
|
-
|
||||||
211
index.js
211
index.js
|
|
@ -5,17 +5,7 @@ require('dotenv').config();
|
||||||
|
|
||||||
const phonesCfg = require('./phones.json');
|
const phonesCfg = require('./phones.json');
|
||||||
const buttonsCfg = require('./buttons.json');
|
const buttonsCfg = require('./buttons.json');
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
|
|
||||||
if (!fs.existsSync(path.join(__dirname, 'slingtoken.txt'))) {
|
|
||||||
// Create empty file
|
|
||||||
fs.writeFileSync(path.join(__dirname, 'slingtoken.txt'), '', 'utf8');
|
|
||||||
console.warn('slingtoken.txt file created. Please add your Sling token to this file to use sling messaging!');
|
|
||||||
}
|
|
||||||
|
|
||||||
global.slingToken = fs.readFileSync(path.join(__dirname, 'slingtoken.txt'), 'utf8').trim();
|
|
||||||
const contexts = {};
|
const contexts = {};
|
||||||
|
|
||||||
// Generate contexts from buttonsCfg
|
// Generate contexts from buttonsCfg
|
||||||
|
|
@ -26,53 +16,25 @@ Object.keys(buttonsCfg).forEach(category => {
|
||||||
context: button.context.context,
|
context: button.context.context,
|
||||||
timeout: button.context.timeout,
|
timeout: button.context.timeout,
|
||||||
cid: button.context.cid,
|
cid: button.context.cid,
|
||||||
...(button.context.dial && { dial: button.context.dial }),
|
...(button.context.dial && { dial: button.context.dial })
|
||||||
...(button.sling_chat_id && { sling_chat_id: button.sling_chat_id }),
|
|
||||||
...(button.sling_chat_message && { sling_chat_message: button.sling_chat_message })
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
//console.log('Generated contexts:', contexts);
|
|
||||||
|
|
||||||
function trigCall(pageType, phone, variables = {}) {
|
console.log('Generated contexts:', contexts);
|
||||||
console.log(variables)
|
|
||||||
|
function trigCall(pageType, phone) {
|
||||||
// If contexts[pageType] does not exist, return an error
|
// If contexts[pageType] does not exist, return an error
|
||||||
if (!contexts[pageType]) {
|
if (!contexts[pageType]) {
|
||||||
throw new Error(`Invalid page type: ${pageType}`);
|
throw new Error(`Invalid page type: ${pageType}`);
|
||||||
}
|
}
|
||||||
const { context, timeout, cid, dial, sling_chat_id, sling_chat_message } = contexts[pageType];
|
const { context, timeout, cid, dial } = contexts[pageType];
|
||||||
const targetNumber = dial || phone;
|
const targetNumber = dial || phone;
|
||||||
if (!targetNumber) {
|
if (!targetNumber) {
|
||||||
throw new Error(`Phone number is required for page type: ${pageType}`);
|
throw new Error(`Phone number is required for page type: ${pageType}`);
|
||||||
}
|
}
|
||||||
|
originateCall(targetNumber, context, 0, timeout, cid).then((output) => {
|
||||||
// Sling message
|
|
||||||
if (sling_chat_id && sling_chat_message && global.slingToken) {
|
|
||||||
const replacedMessage = typeof sling_chat_message === 'string'
|
|
||||||
? sling_chat_message.replace(/%initiatingUser%/g, variables.initiatingUser || '')
|
|
||||||
: sling_chat_message;
|
|
||||||
fetch(`https://api.getsling.com/v1/conversations/${sling_chat_id}/messages`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `${global.slingToken}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
'content': replacedMessage
|
|
||||||
})
|
|
||||||
}).then(res => res.json()).then(data => {
|
|
||||||
if (data && data.success) {
|
|
||||||
console.log('Sling chat message sent successfully.');
|
|
||||||
} else {
|
|
||||||
console.error('Error sending Sling chat message:', data);
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('Error sending Sling chat message:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
originateCall(targetNumber, context, 0, timeout, cid, {initiatingUser: variables.username, slingChannel: sling_chat_id || undefined, slingMessage: sling_chat_message || undefined}).then((output) => {
|
|
||||||
console.log(`Call originated: ${output}`);
|
console.log(`Call originated: ${output}`);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error(`Error originating call: ${error}`);
|
console.error(`Error originating call: ${error}`);
|
||||||
|
|
@ -87,7 +49,7 @@ function originateCall(number, context, delay, timeout, cid, variables = {}) {
|
||||||
// Add variables if provided
|
// Add variables if provided
|
||||||
if (variables && typeof variables === 'object') {
|
if (variables && typeof variables === 'object') {
|
||||||
const varString = Object.entries(variables)
|
const varString = Object.entries(variables)
|
||||||
.map(([key, value]) => `"${key}=${value}"`)
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
.join(' ');
|
.join(' ');
|
||||||
if (varString) {
|
if (varString) {
|
||||||
command += ` ${varString}`;
|
command += ` ${varString}`;
|
||||||
|
|
@ -106,163 +68,60 @@ function originateCall(number, context, delay, timeout, cid, variables = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Sling chat stuff; Check current token with GET https://api.getsling.com/v1/account/session, refresh with POST https://api.getsling.com/v1/account/session (GET NEW TOKEN FROM RETURN AUTHORIZATION HEADER)
|
|
||||||
async function slingAuthLoop() {
|
|
||||||
console.log("Start slingAuthLoop")
|
|
||||||
if (!global.slingToken) {
|
|
||||||
console.warn('No Sling token provided.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTimeout(slingAuthLoop, 15 * 60 * 1000); // Refresh every 15 minutes
|
|
||||||
const sessionCheck = await fetch('https://api.getsling.com/v1/account/session', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `${global.slingToken}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(sessionCheck)
|
|
||||||
if (sessionCheck && sessionCheck.ok) {
|
|
||||||
console.log('Sling session is valid. Refreshing token...');
|
|
||||||
const sessionRefresh = await fetch('https://api.getsling.com/v1/account/session', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `${global.slingToken}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const newToken = sessionRefresh.headers.get('Authorization');
|
|
||||||
if (newToken) {
|
|
||||||
global.slingToken = newToken;
|
|
||||||
fs.writeFileSync(path.join(__dirname, 'slingtoken.txt'), newToken, 'utf8');
|
|
||||||
console.log('Sling token refreshed.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('Sling session is invalid. Please get a new token manually!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slingAuthLoop();
|
|
||||||
|
|
||||||
global.contexts = contexts;
|
|
||||||
global.trigCall = trigCall;
|
|
||||||
global.buttonsCfg = buttonsCfg;
|
|
||||||
global.phonesCfg = phonesCfg;
|
|
||||||
global.originateCall = originateCall;
|
|
||||||
global.exec = exec;
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const HOST = process.env.HOST || 'localhost';
|
const HOST = process.env.HOST || 'localhost';
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: true }));
|
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.set('views', './views');
|
app.set('views', './views');
|
||||||
app.use(express.static('static'));
|
app.use(express.static('static'));
|
||||||
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(require('express-session')({
|
app.use(require('express-session')({
|
||||||
secret: process.env.SESSION_SECRET || 'fallback-secret-key',
|
secret: process.env.SESSION_SECRET || 'fallback-secret-key',
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 },
|
cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 }
|
||||||
store: new (require('session-file-store')(require('express-session')))({
|
|
||||||
path: path.join(__dirname, 'sessions'),
|
|
||||||
ttl: 24 * 60 * 60, // 1 day
|
|
||||||
retries: 1,
|
|
||||||
reapInterval: 60 * 60 // prune expired sessions every hour
|
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function auth(req, res, next) {
|
function auth(req, res, next) {
|
||||||
if (!req.session || !req.session.authenticated) {
|
if (!req.session || !req.session.authenticated) {
|
||||||
return res.redirect('/login');
|
return res.redirect('/login');
|
||||||
}
|
}
|
||||||
if (req.session.user && req.session.user.remember) {
|
|
||||||
// Extend session expiration
|
|
||||||
req.session.cookie.maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
||||||
}
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiAuth(req, res, next) {
|
app.get('/', (req, res) => {
|
||||||
console.log(req.headers)
|
res.render('index', { session: req.session, phones: phonesCfg, buttons: require("./buttons.json") });
|
||||||
if (req.headers["authorization"]) {
|
|
||||||
if (req.headers["authorization"] === process.env.API_TOKEN) {
|
|
||||||
return next();
|
|
||||||
} else {
|
|
||||||
console.log('Unauthorized API access attempt with invalid token');
|
|
||||||
return res.status(401).json({ success: false, message: 'Unauthorized' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!req.session || !req.session.authenticated) {
|
|
||||||
console.log('Unauthorized API access attempt without valid session');
|
|
||||||
return res.status(401).json({ success: false, message: 'Unauthorized' });
|
|
||||||
}
|
|
||||||
if (req.session.user && req.session.user.remember) {
|
|
||||||
// Extend session expiration
|
|
||||||
req.session.cookie.maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
global.auth = auth;
|
|
||||||
global.apiAuth = apiAuth;
|
|
||||||
|
|
||||||
// folder where routes live
|
|
||||||
const ROUTES_DIR = path.join(__dirname, "routes");
|
|
||||||
|
|
||||||
// Recursive route loader
|
|
||||||
function loadRoutes(dir, baseRoute = "") {
|
|
||||||
fs.readdirSync(dir).forEach((file) => {
|
|
||||||
const fullPath = path.join(dir, file);
|
|
||||||
const stat = fs.statSync(fullPath);
|
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
// If folder, recurse deeper
|
|
||||||
const nestedBase = baseRoute + "/" + file;
|
|
||||||
loadRoutes(fullPath, nestedBase);
|
|
||||||
} else if (file.endsWith(".js")) {
|
|
||||||
// If .js file → mount route
|
|
||||||
const routePath =
|
|
||||||
baseRoute +
|
|
||||||
"/" +
|
|
||||||
file.replace(".js", "").replace("index", "");
|
|
||||||
|
|
||||||
const router = require(fullPath);
|
|
||||||
|
|
||||||
console.log(`Mounting route: ${routePath || "/"}`);
|
|
||||||
app.use(routePath || "/", router);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Load everything in routes/
|
app.get('/login', (req, res) => {
|
||||||
loadRoutes(ROUTES_DIR);
|
res.render('login', { session: req.session });
|
||||||
|
|
||||||
|
|
||||||
const convertPasswords = () => { // Read auth.json and convert plaintext to hashed passwords
|
|
||||||
const authFilePath = path.join(__dirname, 'auth.json');
|
|
||||||
if (fs.existsSync(authFilePath)) {
|
|
||||||
let authData = JSON.parse(fs.readFileSync(authFilePath, 'utf8'));
|
|
||||||
let updated = false;
|
|
||||||
authData = authData.map(user => {
|
|
||||||
if (user.password) {
|
|
||||||
const hashedPassword = bcrypt.hashSync(user.password, 10);
|
|
||||||
updated = true;
|
|
||||||
return { ...user, passwordHash: hashedPassword, password: undefined };
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
});
|
});
|
||||||
if (updated) {
|
|
||||||
fs.writeFileSync(authFilePath, JSON.stringify(authData, null, 4), 'utf8');
|
|
||||||
console.log('Converted plaintext passwords to hashed passwords in auth.json');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('auth.json file not found for password conversion.');
|
|
||||||
}
|
|
||||||
|
|
||||||
global.authUsers = JSON.parse(fs.readFileSync(authFilePath, 'utf8'));
|
app.post('/trig', async (req, res) => {
|
||||||
};
|
console.log('Triggering call with data:', req.body);
|
||||||
|
trigCall(req.body.pageType, req.body.phone);
|
||||||
|
res.status(200).send('Call triggered');
|
||||||
|
});
|
||||||
|
|
||||||
convertPasswords();
|
app.post('/stop', async (req, res) => {
|
||||||
|
console.log('Stopping page for phone:', req.body.phone);
|
||||||
|
// Logic to stop the page would go here.
|
||||||
|
// For now we will just log it, as the specific asterisk command to hangup a channel depends on implementation details not provided.
|
||||||
|
// Typically it might involve 'asterisk -rx "channel request hangup <channel>"' or similar via AMI.
|
||||||
|
// Assuming we might want to run a command similar to originate but for hangup if we knew the channel.
|
||||||
|
// Since we don't have the channel ID easily available without tracking it, we might need to implement channel tracking or use a broad command.
|
||||||
|
exec(`/usr/bin/ast_drop ${process.env.PAGE_GROUP || '9000'}`, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(`Error stopping page: ${error}`);
|
||||||
|
return res.status(500).send('Error stopping page');
|
||||||
|
}
|
||||||
|
console.log(`Page stopped: ${stdout}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).send('Stop request received');
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(PORT, HOST, () => {
|
app.listen(PORT, HOST, () => {
|
||||||
console.log(`Server running on http://${HOST}:${PORT}`);
|
console.log(`Server running on http://${HOST}:${PORT}`);
|
||||||
|
|
|
||||||
195
package-lock.json
generated
195
package-lock.json
generated
|
|
@ -9,12 +9,10 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^6.0.0",
|
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"express-session": "^1.18.2",
|
"express-session": "^1.18.2"
|
||||||
"session-file-store": "^1.5.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
|
|
@ -30,56 +28,18 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/asn1.js": {
|
|
||||||
"version": "5.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
|
||||||
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"bn.js": "^4.0.0",
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"minimalistic-assert": "^1.0.0",
|
|
||||||
"safer-buffer": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/async": {
|
"node_modules/async": {
|
||||||
"version": "3.2.6",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/bagpipe": {
|
|
||||||
"version": "0.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz",
|
|
||||||
"integrity": "sha512-42sAlmPDKes1nLm/aly+0VdaopSU9br+jkRELedhQxI5uXHgtk47I83Mpmf4zoNTRMASdLFtUkimlu/Z9zQ8+g==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/bcrypt": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
|
|
||||||
"integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"node-addon-api": "^8.3.0",
|
|
||||||
"node-gyp-build": "^4.8.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bn.js": {
|
|
||||||
"version": "4.12.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
|
||||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
|
||||||
|
|
@ -449,20 +409,6 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs-extra": {
|
|
||||||
"version": "8.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.2.0",
|
|
||||||
"jsonfile": "^4.0.0",
|
|
||||||
"universalify": "^0.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6 <7 || >=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
|
@ -521,12 +467,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/graceful-fs": {
|
|
||||||
"version": "4.2.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/has-symbols": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
|
@ -587,15 +527,6 @@
|
||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/imurmurhash": {
|
|
||||||
"version": "0.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
|
||||||
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8.19"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
|
@ -617,12 +548,6 @@
|
||||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/is-typedarray": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/jake": {
|
"node_modules/jake": {
|
||||||
"version": "10.9.4",
|
"version": "10.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
||||||
|
|
@ -640,27 +565,6 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsonfile": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optionalDependencies": {
|
|
||||||
"graceful-fs": "^4.1.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/kruptein": {
|
|
||||||
"version": "2.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/kruptein/-/kruptein-2.2.3.tgz",
|
|
||||||
"integrity": "sha512-BTwprBPTzkFT9oTugxKd3WnWrX630MqUDsnmBuoa98eQs12oD4n4TeI0GbpdGcYn/73Xueg2rfnw+oK4dovnJg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"asn1.js": "^5.4.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
|
@ -716,12 +620,6 @@
|
||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimalistic-assert": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "5.1.6",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||||
|
|
@ -749,35 +647,6 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-addon-api": {
|
|
||||||
"version": "8.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
|
|
||||||
"integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "^18 || ^20 || >= 21"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-gyp-build": {
|
|
||||||
"version": "4.8.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
|
||||||
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"node-gyp-build": "bin.js",
|
|
||||||
"node-gyp-build-optional": "optional.js",
|
|
||||||
"node-gyp-build-test": "build-test.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/object-assign": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.13.4",
|
"version": "1.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||||
|
|
@ -906,15 +775,6 @@
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/retry": {
|
|
||||||
"version": "0.12.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
|
||||||
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/router": {
|
"node_modules/router": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||||
|
|
@ -1002,23 +862,6 @@
|
||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/session-file-store": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/session-file-store/-/session-file-store-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-60IZaJNzyu2tIeHutkYE8RiXVx3KRvacOxfLr2Mj92SIsRIroDsH0IlUUR6fJAjoTW4RQISbaOApa2IZpIwFdQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"bagpipe": "^0.3.5",
|
|
||||||
"fs-extra": "^8.0.1",
|
|
||||||
"kruptein": "^2.0.4",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"retry": "^0.12.0",
|
|
||||||
"write-file-atomic": "3.0.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/setprototypeof": {
|
"node_modules/setprototypeof": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
|
|
@ -1097,12 +940,6 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/signal-exit": {
|
|
||||||
"version": "3.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||||
|
|
@ -1135,15 +972,6 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typedarray-to-buffer": {
|
|
||||||
"version": "3.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
|
||||||
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-typedarray": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/uid-safe": {
|
"node_modules/uid-safe": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
|
|
@ -1156,15 +984,6 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
|
||||||
"version": "0.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
|
||||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
|
@ -1188,18 +1007,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
|
||||||
"node_modules/write-file-atomic": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"imurmurhash": "^0.1.4",
|
|
||||||
"is-typedarray": "^1.0.0",
|
|
||||||
"signal-exit": "^3.0.2",
|
|
||||||
"typedarray-to-buffer": "^3.1.5"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,9 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^6.0.0",
|
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"express-session": "^1.18.2",
|
"express-session": "^1.18.2"
|
||||||
"session-file-store": "^1.5.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
routes/.js
11
routes/.js
|
|
@ -1,11 +0,0 @@
|
||||||
// routes/users.js
|
|
||||||
const express = require("express");
|
|
||||||
const router = express.Router();
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
router.get('/', global.auth, (req, res) => {
|
|
||||||
res.render('index', { session: req.session, phones: global.phonesCfg, buttons: global.buttonsCfg });
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
const express = require("express");
|
|
||||||
const router = express.Router();
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
|
|
||||||
|
|
||||||
router.get("/session", (req, res) => {
|
|
||||||
if (req.session && req.session.authenticated) {
|
|
||||||
res.status(200).json({ authenticated: true, user: req.session.user, sessionID: req.sessionID });
|
|
||||||
} else {
|
|
||||||
res.status(401).json({ authenticated: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/login", (req, res) => {
|
|
||||||
const { username, password } = req.body;
|
|
||||||
// Locate user in global.authUsers [{username, password}]. password is bcrypted.
|
|
||||||
const user = global.authUsers.find(u => u.username === username);
|
|
||||||
if (user) {
|
|
||||||
// Compare password
|
|
||||||
bcrypt.compare(password, user.passwordHash, (err, result) => {
|
|
||||||
if (result) {
|
|
||||||
// Passwords match
|
|
||||||
req.session.authenticated = true;
|
|
||||||
req.session.user = { username: user.username, fullname: user.fullname, remember: req.body.remember || false };
|
|
||||||
res.status(200).json({ success: true, message: 'Login successful' });
|
|
||||||
} else {
|
|
||||||
// Passwords don't match
|
|
||||||
res.status(401).json({ success: false, message: 'Invalid credentials' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// User not found
|
|
||||||
res.status(401).json({ success: false, message: 'Invalid credentials' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/logout", (req, res) => {
|
|
||||||
req.session.destroy(err => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ success: false, message: 'Logout failed' });
|
|
||||||
}
|
|
||||||
res.status(200).json({ success: true, message: 'Logged out successfully' });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
// routes/users.js
|
|
||||||
const express = require("express");
|
|
||||||
const router = express.Router();
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
router.post("/trigger", global.apiAuth, (req, res) => {
|
|
||||||
|
|
||||||
console.log('Triggering call with data:', req.body, {username: req.session ? req.session.user.fullname || 'Unknown' : 'Unknown'});
|
|
||||||
global.exec(`/usr/sbin/asterisk -x "confbridge kick rsc_page all"`, (error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(`Error stopping page: ${error}`);
|
|
||||||
return res.status(500).send('Error stopping page');
|
|
||||||
}
|
|
||||||
console.log(`Page stopped: ${stdout}`);
|
|
||||||
setTimeout(() => {
|
|
||||||
global.trigCall(req.body.pageType, req.body.phone, {username: req.session ? req.session.user.fullname || 'Unknown' : 'Unknown'});
|
|
||||||
}, 1000);
|
|
||||||
if (stderr) {
|
|
||||||
console.error(`stderr: ${stderr}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res.status(200).send('Call triggered');
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/stop", global.apiAuth, (req, res) => {
|
|
||||||
console.log('Stopping all calls');
|
|
||||||
global.exec(`/usr/sbin/asterisk -x "confbridge kick rsc_page all"`, (error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(`Error stopping page: ${error}`);
|
|
||||||
return res.status(500).send('Error stopping page');
|
|
||||||
}
|
|
||||||
console.log(`Page stopped: ${stdout}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).send('Stop request received');
|
|
||||||
});
|
|
||||||
module.exports = router;
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
// fetch(`https://api.getsling.com/v1/conversations/${sling_chat_id}/messages`, {
|
|
||||||
// method: 'POST',
|
|
||||||
// headers: {
|
|
||||||
// 'Authorization': `${global.slingToken}`,
|
|
||||||
// 'Content-Type': 'application/json'
|
|
||||||
// },
|
|
||||||
// body: JSON.stringify({
|
|
||||||
// 'content': sling_chat_message
|
|
||||||
// })
|
|
||||||
// }).then(res => res.json()).then(data => {
|
|
||||||
// if (data && data.success) {
|
|
||||||
// console.log('Sling chat message sent successfully.');
|
|
||||||
// } else {
|
|
||||||
// console.error('Error sending Sling chat message:', data);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
const express = require("express");
|
|
||||||
const router = express.Router();
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
router.post("/message", global.apiAuth, async (req, res) => {
|
|
||||||
const { channel, content } = req.body;
|
|
||||||
console.log('Sling chat message request received:', req.body);
|
|
||||||
if (!channel || !content) {
|
|
||||||
return res.status(400).json({ error: "Missing channel or content" });
|
|
||||||
}
|
|
||||||
if (!global.slingToken) {
|
|
||||||
return res.status(500).json({ error: "Sling token not configured" });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const replacedContent = typeof content === 'string'
|
|
||||||
? content.replace(/%initiatingUser%/g, req.body.initiatingUser || '')
|
|
||||||
: content;
|
|
||||||
const response = await fetch(`https://api.getsling.com/v1/conversations/${channel}/messages`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `${global.slingToken}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
'content': replacedContent
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
if (data && data.success) {
|
|
||||||
return res.json({ success: true, message: "Message sent successfully" });
|
|
||||||
} else {
|
|
||||||
return res.status(500).json({ error: "Error sending message", details: data });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending Sling chat message:', error);
|
|
||||||
return res.status(500).json({ error: "Internal server error" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
// routes/users.js
|
|
||||||
const express = require("express");
|
|
||||||
const router = express.Router();
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
|
||||||
res.render('login');
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// routes/users.js
|
|
||||||
const express = require("express");
|
|
||||||
const router = express.Router();
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
|
||||||
if (req.session && req.session.authenticated) {
|
|
||||||
req.session.destroy(err => {
|
|
||||||
if (err) {
|
|
||||||
return res.redirect('/');
|
|
||||||
}
|
|
||||||
res.redirect('/login');
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
res.redirect('/login');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|
@ -35,29 +35,14 @@ $callback_callerid = isset($argv[5]) ? base64_decode($argv[5]) : "";
|
||||||
*/
|
*/
|
||||||
$variable = [];
|
$variable = [];
|
||||||
|
|
||||||
for ($i = 6; $i < $argc; $i++) {
|
/*for ($i = 6; $i < $argc; $i++) {
|
||||||
if (strpos($argv[$i], '=') !== false) {
|
if (strpos($argv[$i], '=') !== false) {
|
||||||
list($k, $v) = explode('=', $argv[$i], 2);
|
list($k, $v) = explode('=', $argv[$i], 2);
|
||||||
$variable[$k] = $v;
|
$variable[$k] = $v;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Debug: echo main parameters
|
|
||||||
echo "DEBUG: callback_number={$callback_number}\n";
|
|
||||||
echo "DEBUG: callback_destination={$callback_destination}\n";
|
|
||||||
echo "DEBUG: pause_seconds={$pause_seconds}\n";
|
|
||||||
echo "DEBUG: callback_timeout={$callback_timeout}\n";
|
|
||||||
echo "DEBUG: callback_callerid={$callback_callerid}\n";
|
|
||||||
|
|
||||||
// Echo parsed VAR=value channel variables
|
|
||||||
if (!empty($variable)) {
|
|
||||||
echo "DEBUG: channel variables:\n";
|
|
||||||
foreach ($variable as $k => $v) {
|
|
||||||
echo " {$k}={$v}\n";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo "DEBUG: channel variables: (none)\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AMI expects variables as:
|
* AMI expects variables as:
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://site-assets.fontawesome.com/releases/v7.1.0/css/all.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/tuta-amb/fontawesome-pro@latest/web/css/all.min.css">
|
||||||
<link rel="stylesheet" href="https://site-assets.fontawesome.com/releases/v7.1.0/css/sharp-solid.css" />
|
|
||||||
<link rel="stylesheet" href="https://site-assets.fontawesome.com/releases/v7.1.0/css/sharp-regular.css" />
|
|
||||||
<link rel="stylesheet" href="https://site-assets.fontawesome.com/releases/v7.1.0/css/sharp-light.css" />
|
|
||||||
<link rel="stylesheet" href="https://site-assets.fontawesome.com/releases/v7.1.0/css/duotone.css" />
|
|
||||||
<title>Paging Console</title>
|
<title>Paging Console</title>
|
||||||
<style type="text/tailwindcss">
|
<style type="text/tailwindcss">
|
||||||
@layer components {
|
@layer components {
|
||||||
|
|
@ -84,10 +80,6 @@
|
||||||
<i class="fa-solid fa-chevron-down text-xs text-gray-400"></i>
|
<i class="fa-solid fa-chevron-down text-xs text-gray-400"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/logout" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg shadow-sm transition-colors duration-150 ease-in-out flex items-center gap-2" title="Logout">
|
|
||||||
<i class="fa-solid fa-right-from-bracket"></i>
|
|
||||||
<span>Logout</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
@ -154,7 +146,7 @@
|
||||||
dial: button.dataset.dial
|
dial: button.dataset.dial
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetch('/api/portal/trigger', {
|
fetch('/trig', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
|
|
@ -164,7 +156,7 @@
|
||||||
function stopPage() {
|
function stopPage() {
|
||||||
const phoneSelect = document.getElementById('phoneSelect');
|
const phoneSelect = document.getElementById('phoneSelect');
|
||||||
const phone = phoneSelect.value;
|
const phone = phoneSelect.value;
|
||||||
fetch('/api/portal/stop', {
|
fetch('/stop', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<form novalidate class="space-y-6">
|
<form action="/login" method="post" novalidate class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label for="username" class="block text-sm font-semibold text-gray-300 mb-2">Username</label>
|
<label for="username" class="block text-sm font-semibold text-gray-300 mb-2">Username</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
|
|
@ -66,63 +66,8 @@
|
||||||
Sign in
|
Sign in
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="mt-8 pt-6 border-t border-gray-700 text-xs text-gray-500 text-center space-y-2">
|
|
||||||
<p>This system is the property of the Riverside Swim Club and is intended for use by authorized users only.</p>
|
|
||||||
<p>All users should have no expectation of privacy while using this system. Activities may be monitored, recorded, and reviewed to ensure security and compliance.</p>
|
|
||||||
<p>Unauthorized access, misuse, or attempts to circumvent security controls are strictly prohibited and may result in disciplinary or legal action.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.querySelector('form').addEventListener('submit', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const username = document.getElementById('username').value;
|
|
||||||
const password = document.getElementById('password').value;
|
|
||||||
const remember = document.getElementById('remember').checked;
|
|
||||||
|
|
||||||
fetch('/api/auth/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ username, password, remember })
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
const form = document.querySelector('form');
|
|
||||||
// remove existing client-side alert if present
|
|
||||||
const existing = document.querySelector('.client-error');
|
|
||||||
if (existing) existing.remove();
|
|
||||||
|
|
||||||
if (data && data.success) {
|
|
||||||
window.location.href = '/';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const msg = (data && data.message) ? data.message : 'Sign in failed';
|
|
||||||
const alert = document.createElement('div');
|
|
||||||
alert.className = 'client-error bg-red-900/50 border-l-4 border-red-500 text-red-200 p-4 rounded-r mb-6 flex items-start gap-3';
|
|
||||||
alert.setAttribute('role', 'alert');
|
|
||||||
alert.innerHTML = '<i class="fa-solid fa-circle-exclamation mt-1"></i><div>' + msg + '</div>';
|
|
||||||
|
|
||||||
form.parentNode.insertBefore(alert, form);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
const form = document.querySelector('form');
|
|
||||||
const existing = document.querySelector('.client-error');
|
|
||||||
if (existing) existing.remove();
|
|
||||||
|
|
||||||
const alert = document.createElement('div');
|
|
||||||
alert.className = 'client-error bg-red-900/50 border-l-4 border-red-500 text-red-200 p-4 rounded-r mb-6 flex items-start gap-3';
|
|
||||||
alert.setAttribute('role', 'alert');
|
|
||||||
alert.innerHTML = '<i class="fa-solid fa-circle-exclamation mt-1"></i><div>Network error. Please try again.</div>';
|
|
||||||
|
|
||||||
form.parentNode.insertBefore(alert, form);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
Reference in a new issue