mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-10-26 11:27:04 +00:00
Merge 6ca5b1aeb2 into e5205f285b
This commit is contained in:
commit
faa944ea95
2
.gitignore
vendored
2
.gitignore
vendored
@ -311,4 +311,4 @@ __pycache__/
|
||||
# When running mkdocs locally as dev
|
||||
docs/__pycache__/
|
||||
docs/env/
|
||||
docker-compose.yaml
|
||||
docker-compose.yaml
|
||||
@ -272,7 +272,7 @@ body {
|
||||
padding-top: 5px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
right: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@ -348,7 +348,7 @@ body {
|
||||
-ms-box-sizing:border-box;
|
||||
-moz-box-sizing:border-box;
|
||||
box-sizing:border-box;
|
||||
-webkit-box-sizing:border-box;
|
||||
-webkit-box-sizing:border-box;
|
||||
}
|
||||
|
||||
.night #column_l {
|
||||
@ -562,7 +562,7 @@ body {
|
||||
padding: 3px;
|
||||
margin-right: 3px;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#id_dialogtitle {
|
||||
@ -761,7 +761,7 @@ body {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Example if <table> is relplaced with <div><p> then image can be defined in css
|
||||
/* Example if <table> is relplaced with <div><p> then image can be defined in css
|
||||
#NoMeshesPanel {
|
||||
background: url(../images/info.png) no-repeat 23px 20px;
|
||||
height: 48px;
|
||||
@ -2075,7 +2075,7 @@ nav .lbbuttonsel2 {
|
||||
#d2notifyMsg,
|
||||
#d2devNotes,
|
||||
#d2devEvent,
|
||||
#d2runcmd,
|
||||
#d2runcmd,
|
||||
#d2devMessage,
|
||||
#d2smsText,
|
||||
#d2emailSubject,
|
||||
@ -2657,7 +2657,7 @@ nav .lbbuttonsel2 {
|
||||
.deskToolsBar:hover {
|
||||
background-color: #EFE8B6;
|
||||
}
|
||||
|
||||
|
||||
.night .deskToolsBar {
|
||||
color: #ddd;
|
||||
}
|
||||
@ -3291,7 +3291,7 @@ nav .lbbuttonsel2 {
|
||||
.sidebar .nav-link {
|
||||
font-size: xx-large;
|
||||
}
|
||||
|
||||
|
||||
.card:hover, #p2AccountImage:hover, #p2canvas:hover {
|
||||
background: #f3f5f7 !important;
|
||||
}
|
||||
@ -3326,6 +3326,20 @@ nav .lbbuttonsel2 {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Shared styles for all custom icons */
|
||||
.custom-icon svg {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
width: 1em !important;
|
||||
height: 1em !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
.custom-icon svg path {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* hide .sidebar when on mobile */
|
||||
@media (max-width: 768px) {
|
||||
#page_leftbar {
|
||||
@ -3397,11 +3411,11 @@ nav .lbbuttonsel2 {
|
||||
/* .select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__rendered {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
|
||||
.select2-container--bootstrap-5 .select2-selection--multiple .select2-selection__rendered .select2-selection__choice {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
|
||||
.select2-container--bootstrap-5 .select2-selection--multiple .select2-search {
|
||||
display: inline !important;
|
||||
} */
|
||||
@ -598,6 +598,7 @@
|
||||
</span>
|
||||
<span id="accountCreateLoginTokenSpan" style="display:none"><a href=# onclick="return account_createLoginToken()">Create login token</a><br /></span>
|
||||
<a href=# onclick="return account_showThemesSwitcher()">Switch theme</a><br />
|
||||
<span id="accountCustomIconsSpan" style="display:none"><a href=# onclick="return showIconCustomization()">Icons Customization</a><br /></span>
|
||||
</p>
|
||||
<br style=clear:both />
|
||||
</div>
|
||||
@ -2804,6 +2805,7 @@
|
||||
QV('p2ServerActions', (siteRights & 21) && ((serverFeatures & 143) != 0));
|
||||
QV('LeftMenuMyServer', (siteRights & 21) && ((serverFeatures & 64) != 0)); // 16 + 4 + 1
|
||||
QV('MainMenuMyServer', siteRights & 21);
|
||||
QV('accountCustomIconsSpan', (userinfo.siteadmin === 0xFFFFFFFF));
|
||||
QV('p2ServerActionsBackup', (siteRights & 1) && ((serverFeatures & 1) != 0));
|
||||
QV('p2ServerActionsRestore', (siteRights & 4) && ((serverFeatures & 2) != 0));
|
||||
QV('p2ServerActionsVersion', (siteRights & 16) && ((serverFeatures & 4) != 0));
|
||||
@ -3822,6 +3824,8 @@
|
||||
|
||||
var webstate = JSON.parse(message.event.state);
|
||||
for (var i in webstate) { localStorage.setItem(i, webstate[i]); }
|
||||
customIconValues = loadCustomIconState();
|
||||
applyIconCustomization(customIconValues);
|
||||
|
||||
// Update the web page
|
||||
//if ((webstate.deskAspectRatio != null) && (webstate.deskAspectRatio != deskAspectRatio)) { deskAspectRatio = webstate.deskAspectRatio; deskAdjust(); }
|
||||
@ -18883,7 +18887,7 @@
|
||||
if (rec.meshname) { x += addHtmlValue4("Device Group", EscapeHtml(rec.meshname)); }
|
||||
if (rec.size) { x += addHtmlValue4("Size", format("{0} bytes", rec.size)); }
|
||||
if (rec.startTime) { x += addHtmlValue4("Start Time", printTime(new Date(rec.startTime))); }
|
||||
if (rec.startTime && rec.lengthTime) { x += addHtmlValue4("End Time", printTime(new Date(rec.startTime + (rec.lengthTime * 1000)))); }
|
||||
if (rec.startTime && rec.lengthTime) { x += addHtmlValue4("End Time", printTime(new Date(rec.startTime + (rec.lengthTime * 1000)))); }
|
||||
if (rec.lengthTime) { x += addHtmlValue4("Duration", pad2(Math.floor(rec.lengthTime / 3600)) + ':' + pad2(Math.floor((rec.lengthTime % 3600) / 60)) + ':' + pad2(Math.floor(rec.lengthTime % 60))); }
|
||||
if (rec.multiplex == true) { x += addHtmlValue4("Multiplexor", "Enabled"); }
|
||||
if (rec.userids) { for (var i in rec.userids) { x += addHtmlValue4("User", rec.userids[i].split('/')[2]); } }
|
||||
@ -20819,11 +20823,152 @@
|
||||
}
|
||||
}
|
||||
|
||||
// --- Icons Customization ---
|
||||
var customIconValues = {};
|
||||
const customIconConfig = [
|
||||
{ key: 'myDevices', label: 'My Devices', elementId: 'LeftMenuMyDevices' },
|
||||
{ key: 'myAccount', label: 'My Account', elementId: 'LeftMenuMyAccount' },
|
||||
{ key: 'myEvents', label: 'My Events', elementId: 'LeftMenuMyEvents' },
|
||||
{ key: 'myFiles', label: 'My Files', elementId: 'LeftMenuMyFiles' },
|
||||
{ key: 'myUsers', label: 'My Users', elementId: 'LeftMenuMyUsers' },
|
||||
{ key: 'myServer', label: 'My Server', elementId: 'LeftMenuMyServer' }
|
||||
];
|
||||
|
||||
function loadCustomIconState() {
|
||||
var raw = getstore('customIcons', '{}');
|
||||
if ((typeof raw !== 'string') || (raw.length === 0)) { return {}; }
|
||||
try { return JSON.parse(raw); } catch (ex) { return {}; }
|
||||
}
|
||||
|
||||
function sanitizeCustomIconState(state) {
|
||||
var sanitized = {};
|
||||
if (state == null) { return sanitized; }
|
||||
for (var i = 0; i < customIconConfig.length; i++) {
|
||||
var key = customIconConfig[i].key;
|
||||
var value = state[key];
|
||||
if (typeof value === 'string') {
|
||||
var trimmed = value.trim();
|
||||
if (trimmed.length > 0) { sanitized[key] = trimmed; }
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
function persistCustomIconState(state) {
|
||||
var sanitized = sanitizeCustomIconState(state);
|
||||
putstore('customIcons', JSON.stringify(sanitized));
|
||||
customIconValues = sanitized;
|
||||
applyIconCustomization(sanitized);
|
||||
}
|
||||
|
||||
function showIconCustomization() {
|
||||
customIconValues = loadCustomIconState();
|
||||
var x = '<p class="text-muted">Upload custom SVG icons or provide a URL/data URL. Uploaded files are stored in the public icons directory.</p>';
|
||||
for (var i = 0; i < customIconConfig.length; i++) {
|
||||
var cfg = customIconConfig[i];
|
||||
var currentValue = customIconValues[cfg.key] || '';
|
||||
x += '<div class="mb-3">';
|
||||
x += '<label class="form-label" for="iconInput_' + cfg.key + '">' + EscapeHtml(cfg.label) + '</label>';
|
||||
x += '<div class="input-group">';
|
||||
x += '<input type="text" class="form-control" id="iconInput_' + cfg.key + '" value="' + EscapeHtml(currentValue) + '" placeholder="Enter URL or data URL for ' + EscapeHtml(cfg.label) + ' icon" />';
|
||||
x += '<button class="btn btn-outline-secondary" type="button" onclick="return triggerIconFileInput(\'' + cfg.key + '\')">Upload SVG</button>';
|
||||
x += '</div>';
|
||||
x += '<input type="file" class="d-none" accept=".svg,image/svg+xml" id="iconFile_' + cfg.key + '" onchange="handleIconFileChange(\'' + cfg.key + '\', this)" />';
|
||||
x += '</div>';
|
||||
}
|
||||
|
||||
setModalContent('xxAddAgent', 'Icons Customization', x, 'large');
|
||||
showModal('xxAddAgentModal', 'idx_dlgOkButton', saveIconCustomization);
|
||||
return false;
|
||||
}
|
||||
|
||||
function triggerIconFileInput(iconKey) {
|
||||
var fileInput = document.getElementById('iconFile_' + iconKey);
|
||||
if (fileInput) { fileInput.click(); }
|
||||
return false;
|
||||
}
|
||||
|
||||
async function handleIconFileChange(iconKey, input) {
|
||||
if (!input || !input.files || (input.files.length === 0)) { return; }
|
||||
try {
|
||||
var result = await uploadCustomIcon(iconKey, input.files[0]);
|
||||
if ((result == null) || (result.path == null)) {
|
||||
messagebox('Icons Customization', 'The server did not return a valid icon path.');
|
||||
} else {
|
||||
customIconValues[iconKey] = result.path;
|
||||
var textInput = document.getElementById('iconInput_' + iconKey);
|
||||
if (textInput) { textInput.value = result.path; }
|
||||
persistCustomIconState(customIconValues);
|
||||
}
|
||||
} catch (ex) {
|
||||
messagebox('Icons Customization', (ex && ex.message) ? ex.message : 'Failed to upload the icon.');
|
||||
} finally {
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadCustomIcon(iconKey, file) {
|
||||
var formData = new FormData();
|
||||
formData.append('iconType', iconKey);
|
||||
if (customIconValues && typeof customIconValues[iconKey] === 'string' && customIconValues[iconKey].length > 0) {
|
||||
formData.append('previousIcon', customIconValues[iconKey]);
|
||||
}
|
||||
formData.append('iconFile', file);
|
||||
|
||||
var response = await fetch('customiconupload.ashx', { method: 'POST', body: formData, credentials: 'same-origin' });
|
||||
if (!response.ok) {
|
||||
var message = 'Failed to upload the icon.';
|
||||
try {
|
||||
var errorInfo = await response.json();
|
||||
if (errorInfo && typeof errorInfo.error === 'string' && errorInfo.error.length > 0) { message = errorInfo.error; }
|
||||
} catch (ex) { }
|
||||
throw new Error(message);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function saveIconCustomization() {
|
||||
var updatedState = {};
|
||||
for (var i = 0; i < customIconConfig.length; i++) {
|
||||
var cfg = customIconConfig[i];
|
||||
var input = document.getElementById('iconInput_' + cfg.key);
|
||||
if (input) { updatedState[cfg.key] = input.value; }
|
||||
}
|
||||
persistCustomIconState(updatedState);
|
||||
if (xxModal) { xxModal.hide(); }
|
||||
}
|
||||
|
||||
function applyIconCustomization(icons) {
|
||||
var state = sanitizeCustomIconState(icons);
|
||||
for (var i = 0; i < customIconConfig.length; i++) {
|
||||
var cfg = customIconConfig[i];
|
||||
var anchor = document.getElementById(cfg.elementId);
|
||||
if (!anchor) { continue; }
|
||||
var svg = anchor.querySelector('svg');
|
||||
if (!svg) { continue; }
|
||||
if (state[cfg.key]) {
|
||||
anchor.classList.add('custom-icon');
|
||||
svg.style.backgroundImage = `url(${state[cfg.key]})`;
|
||||
} else {
|
||||
anchor.classList.remove('custom-icon');
|
||||
svg.style.removeProperty('background-image');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
customIconValues = loadCustomIconState();
|
||||
applyIconCustomization(customIconValues);
|
||||
});
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
applyIconCustomization(customIconValues);
|
||||
});
|
||||
|
||||
// Request Confirmation if closing while a desktop, terminal session is active
|
||||
window.addEventListener('beforeunload', function (e) {
|
||||
if (((desktop != null) && (xxcurrentView == 11)) || ((terminal != null) && (xxcurrentView == 12))) { e.preventDefault(); e.returnValue = ''; }
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
241
webserver.js
241
webserver.js
@ -39,6 +39,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
obj.net = require('net');
|
||||
obj.tls = require('tls');
|
||||
obj.path = require('path');
|
||||
obj.os = require('os');
|
||||
obj.bodyParser = require('body-parser');
|
||||
obj.exphbs = require('express-handlebars');
|
||||
obj.crypto = require('crypto');
|
||||
@ -94,6 +95,37 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
obj.renderLanguages = [];
|
||||
obj.destroyedSessions = {}; // userid/req.session.x --> destroyed session time
|
||||
|
||||
const isWindowsPlatform = (obj.os.platform() === 'win32');
|
||||
const safeUploadTempRoots = (function () {
|
||||
const roots = [];
|
||||
const addRoot = function (p) {
|
||||
if (typeof p !== 'string') { return; }
|
||||
var resolved;
|
||||
try { resolved = obj.path.normalize(obj.path.resolve(p)); } catch (ex) { return; }
|
||||
if (resolved.length === 0) { return; }
|
||||
if ((resolved.length > 1) && resolved.endsWith(obj.path.sep)) { resolved = resolved.slice(0, -1); }
|
||||
const comparison = isWindowsPlatform ? resolved.toLowerCase() : resolved;
|
||||
const comparisonWithSep = comparison + obj.path.sep;
|
||||
roots.push({ comparison: comparison, comparisonWithSep: comparisonWithSep });
|
||||
};
|
||||
addRoot(obj.os.tmpdir());
|
||||
if (typeof obj.parent.filespath === 'string') { addRoot(obj.path.join(obj.parent.filespath, 'tmp')); }
|
||||
return roots;
|
||||
})();
|
||||
function resolveSafeUploadTempPath(tempPath) {
|
||||
if (typeof tempPath !== 'string') { return null; }
|
||||
var resolvedPath;
|
||||
try { resolvedPath = obj.path.normalize(obj.path.resolve(tempPath)); } catch (ex) { return null; }
|
||||
var comparisonPath = isWindowsPlatform ? resolvedPath.toLowerCase() : resolvedPath;
|
||||
var comparisonPathNoTrailing = comparisonPath;
|
||||
if ((comparisonPathNoTrailing.length > 1) && comparisonPathNoTrailing.endsWith(obj.path.sep)) { comparisonPathNoTrailing = comparisonPathNoTrailing.slice(0, -1); }
|
||||
for (var i = 0; i < safeUploadTempRoots.length; i++) {
|
||||
var root = safeUploadTempRoots[i];
|
||||
if ((comparisonPathNoTrailing === root.comparison) || comparisonPath.startsWith(root.comparisonWithSep)) { return resolvedPath; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Web relay sessions
|
||||
var webRelayNextSessionId = 1;
|
||||
var webRelaySessions = {} // UserId/SessionId/Host --> Web Relay Session
|
||||
@ -2863,12 +2895,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
res.set('Content-Type', 'text/html');
|
||||
let url = domain.url;
|
||||
if (Object.keys(req.query).length > 0) { url += "?" + Object.keys(req.query).map(function(key) { return encodeURIComponent(key) + "=" + encodeURIComponent(req.query[key]); }).join("&"); }
|
||||
|
||||
|
||||
// check for relaystate is set, test against configured server name and accepted query params
|
||||
if(req.body && req.body.RelayState !== undefined){
|
||||
var relayState = decodeURIComponent(req.body.RelayState);
|
||||
var serverName = (obj.getWebServerName(domain, req)).replaceAll('.','\\.');
|
||||
|
||||
|
||||
var regexstr = `(?<=https:\\/\\/(?:.+?\\.)?${serverName}\\/?)` +
|
||||
`.*((?<=([\\?&])gotodevicename=(.{64})|` +
|
||||
`gotonode=(.{64})|` +
|
||||
@ -2888,13 +2920,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
`webrtc=|` +
|
||||
`hide=|` +
|
||||
`viewmode=(\\d+)(?=[\\&]|\\b)))`;
|
||||
|
||||
|
||||
var regex = new RegExp(regexstr);
|
||||
if(regex.test(relayState)){
|
||||
url = relayState;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
|
||||
}
|
||||
|
||||
@ -3208,7 +3240,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
// Get WebRTC configuration
|
||||
var webRtcConfig = null;
|
||||
if (obj.parent.config.settings && obj.parent.config.settings.webrtcconfig && (typeof obj.parent.config.settings.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(obj.parent.config.settings.webrtcconfig)).replace(/'/g, '%27'); }
|
||||
else if (args.webrtcconfig && (typeof args.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(args.webrtcconfig)).replace(/'/g, '%27'); }
|
||||
else if (args.webrtcconfig && (typeof args.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(args.webrtcconfig)).replace(/'/g, '%27'); }
|
||||
|
||||
// Load default page style or new modern ui
|
||||
var uiViewMode = 'default';
|
||||
@ -3240,7 +3272,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
passRequirements: passRequirements,
|
||||
customui: customui,
|
||||
webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'),
|
||||
footer: (domain.footer == null) ? '' : obj.common.replacePlaceholders(domain.footer, {
|
||||
footer: (domain.footer == null) ? '' : obj.common.replacePlaceholders(domain.footer, {
|
||||
'serverversion': obj.parent.currentVer,
|
||||
'servername': obj.getWebServerName(domain, req),
|
||||
'agentsessions': Object.keys(parent.webserver.wsagents).length,
|
||||
@ -3493,7 +3525,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
sessiontime: (args.sessiontime) ? args.sessiontime : 60, // Session time in minutes, 60 minutes is the default
|
||||
passRequirements: passRequirements,
|
||||
customui: customui,
|
||||
footer: (domain.loginfooter == null) ? '' : obj.common.replacePlaceholders(domain.loginfooter, {
|
||||
footer: (domain.loginfooter == null) ? '' : obj.common.replacePlaceholders(domain.loginfooter, {
|
||||
'serverversion': obj.parent.currentVer,
|
||||
'servername': obj.getWebServerName(domain, req),
|
||||
'agentsessions': Object.keys(parent.webserver.wsagents).length,
|
||||
@ -3506,8 +3538,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
messageid: msgid,
|
||||
flashErrors: JSON.stringify(flashErrors),
|
||||
passhint: passhint,
|
||||
|
||||
welcometext: domain.welcometext ? encodeURIComponent(obj.common.replacePlaceholders(domain.welcometext, {
|
||||
|
||||
welcometext: domain.welcometext ? encodeURIComponent(obj.common.replacePlaceholders(domain.welcometext, {
|
||||
'serverversion': obj.parent.currentVer,
|
||||
'servername': obj.getWebServerName(domain, req),
|
||||
'agentsessions': Object.keys(parent.webserver.wsagents).length,
|
||||
@ -4478,11 +4510,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if ((node == null) || (rights != 0xFFFFFFFF) || (visible == false)) { res.sendStatus(404); return; } // We don't have remote control rights to this device
|
||||
for (var i in files.files) {
|
||||
var file = files.files[i];
|
||||
obj.fs.readFile(file.path, 'utf8', function (err, data) {
|
||||
const uploadTempPath = resolveSafeUploadTempPath(file.path);
|
||||
if (uploadTempPath == null) { res.sendStatus(400); return; }
|
||||
obj.fs.readFile(uploadTempPath, 'utf8', function (err, data) {
|
||||
if (err != null) return;
|
||||
data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
|
||||
obj.sendMeshAgentCore(user, domain, fields.attrib[0], 'custom', data); // Upload the core
|
||||
try { obj.fs.unlinkSync(file.path); } catch (e) { }
|
||||
try { obj.fs.unlinkSync(uploadTempPath); } catch (e) { }
|
||||
});
|
||||
}
|
||||
res.send('');
|
||||
@ -4521,11 +4555,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if ((node == null) || (rights != 0xFFFFFFFF) || (visible == false)) { res.sendStatus(404); return; } // We don't have remote control rights to this device
|
||||
for (var i in files.files) {
|
||||
var file = files.files[i];
|
||||
const uploadTempPath = resolveSafeUploadTempPath(file.path);
|
||||
if (uploadTempPath == null) { res.sendStatus(400); return; }
|
||||
|
||||
// Event Intel AMT One Click Recovery, this will cause Intel AMT wake operations on this and other servers.
|
||||
parent.DispatchEvent('*', obj, { action: 'oneclickrecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: file.path });
|
||||
parent.DispatchEvent('*', obj, { action: 'oneclickrecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: uploadTempPath });
|
||||
|
||||
//try { obj.fs.unlinkSync(file.path); } catch (e) { } // TODO: Remove this file after 30 minutes.
|
||||
//try { obj.fs.unlinkSync(uploadTempPath); } catch (e) { } // TODO: Remove this file after 30 minutes.
|
||||
}
|
||||
res.send('');
|
||||
});
|
||||
@ -4533,6 +4569,99 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
}
|
||||
|
||||
// Upload a file to the server
|
||||
function handleCustomIconUpload(req, res) {
|
||||
const domain = checkUserIpAddress(req, res);
|
||||
if (domain == null) { return; }
|
||||
if ((req.session == null) || (typeof req.session.userid !== 'string')) { res.sendStatus(401); return; }
|
||||
const user = obj.users[req.session.userid];
|
||||
if (user == null) { res.sendStatus(401); return; }
|
||||
if (user.siteadmin !== SITERIGHT_ADMIN) { res.sendStatus(401); return; }
|
||||
|
||||
const multiparty = require('multiparty');
|
||||
const form = new multiparty.Form();
|
||||
form.parse(req, function (err, fields, files) {
|
||||
if (err) { res.status(400).json({ success: false, error: 'Invalid form submission.' }); return; }
|
||||
|
||||
const allowedTypes = { myDevices: 1, myAccount: 1, myEvents: 1, myFiles: 1, myUsers: 1, myServer: 1 };
|
||||
const iconType = (fields && fields.iconType && fields.iconType[0]) ? fields.iconType[0] : null;
|
||||
if ((typeof iconType !== 'string') || (allowedTypes[iconType] !== 1)) { res.status(400).json({ success: false, error: 'Invalid icon type.' }); return; }
|
||||
|
||||
const iconFile = (files && files.iconFile && files.iconFile[0]) ? files.iconFile[0] : null;
|
||||
if ((iconFile == null) || (typeof iconFile.path !== 'string')) { res.status(400).json({ success: false, error: 'Missing icon file.' }); return; }
|
||||
const iconTempPath = resolveSafeUploadTempPath(iconFile.path);
|
||||
if (iconTempPath == null) { res.status(400).json({ success: false, error: 'Invalid icon file location.' }); return; }
|
||||
|
||||
const cleanupTempFile = function () { try { obj.fs.unlink(iconTempPath, function () { }); } catch (ex) { } };
|
||||
|
||||
const extension = obj.path.extname(iconFile.originalFilename || '').toLowerCase();
|
||||
if (extension !== '.svg') { cleanupTempFile(); res.status(400).json({ success: false, error: 'Only SVG files are supported.' }); return; }
|
||||
|
||||
const iconsRoot = obj.path.join(obj.parent.datapath, 'icons');
|
||||
const customDir = obj.path.join(iconsRoot, 'custom');
|
||||
try { obj.fs.mkdirSync(iconsRoot); } catch (ex) { if (ex.code !== 'EEXIST') { cleanupTempFile(); res.status(500).json({ success: false, error: 'Unable to prepare icons directory.' }); return; } }
|
||||
try { obj.fs.mkdirSync(customDir); } catch (ex) { if (ex.code !== 'EEXIST') { cleanupTempFile(); res.status(500).json({ success: false, error: 'Unable to prepare icons directory.' }); return; } }
|
||||
|
||||
const resolveExistingIconName = function (requestPath) {
|
||||
if (typeof requestPath !== 'string') { return null; }
|
||||
if (requestPath.startsWith('http://') || requestPath.startsWith('https://') || requestPath.startsWith('data:')) { return null; }
|
||||
if (requestPath.startsWith('/icons/custom/') === false) { return null; }
|
||||
const name = requestPath.substring('/icons/custom/'.length);
|
||||
if (name.indexOf('/') !== -1 || name.indexOf('\\') !== -1) { return null; }
|
||||
if (obj.common.IsFilenameValid(name) !== true) { return null; }
|
||||
if (name.toLowerCase().endsWith('.svg') === false) { return null; }
|
||||
return name;
|
||||
};
|
||||
|
||||
const previousIcon = (fields && fields.previousIcon && fields.previousIcon[0]) ? fields.previousIcon[0] : null;
|
||||
const previousName = resolveExistingIconName(previousIcon);
|
||||
if (previousName != null) {
|
||||
try { obj.fs.unlinkSync(obj.path.join(customDir, previousName)); } catch (ex) { }
|
||||
}
|
||||
|
||||
const newFilename = iconType + '-' + Date.now().toString(36) + '-' + Math.random().toString(36).substring(2, 8) + '.svg';
|
||||
const destinationPath = obj.path.join(customDir, newFilename);
|
||||
|
||||
const respondSuccess = function () { res.json({ success: true, path: '/icons/custom/' + newFilename }); };
|
||||
|
||||
obj.fs.rename(iconTempPath, destinationPath, function (renameErr) {
|
||||
if (renameErr == null) { respondSuccess(); return; }
|
||||
if ((renameErr != null) && (renameErr.code === 'EXDEV')) {
|
||||
obj.common.copyFile(iconTempPath, destinationPath, function (copyErr) {
|
||||
cleanupTempFile();
|
||||
if (copyErr) { res.status(500).json({ success: false, error: 'Failed to save uploaded icon.' }); return; }
|
||||
respondSuccess();
|
||||
});
|
||||
} else {
|
||||
cleanupTempFile();
|
||||
res.status(500).json({ success: false, error: 'Failed to save uploaded icon.' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleCustomIconDownload(req, res) {
|
||||
const domain = getDomain(req);
|
||||
if (domain == null) { res.sendStatus(404); return; }
|
||||
|
||||
if ((req.params == null) || (typeof req.params[0] !== 'string')) { res.sendStatus(404); return; }
|
||||
const iconName = req.params[0];
|
||||
if ((iconName.length === 0) || (iconName.indexOf('/') !== -1) || (iconName.indexOf('\\') !== -1)) { res.sendStatus(404); return; }
|
||||
if (obj.common.IsFilenameValid(iconName) !== true) { res.sendStatus(404); return; }
|
||||
if (iconName.toLowerCase().endsWith('.svg') === false) { res.sendStatus(404); return; }
|
||||
|
||||
const customDir = obj.path.join(obj.parent.datapath, 'icons', 'custom');
|
||||
var resolvedPath;
|
||||
try { resolvedPath = obj.path.normalize(obj.path.join(customDir, iconName)); } catch (ex) { res.sendStatus(404); return; }
|
||||
const customDirNormalized = obj.path.normalize(customDir + obj.path.sep);
|
||||
if ((resolvedPath !== obj.path.normalize(customDir)) && (resolvedPath.startsWith(customDirNormalized) === false)) { res.sendStatus(404); return; }
|
||||
|
||||
obj.fs.readFile(resolvedPath, function (err, data) {
|
||||
if (err) { res.sendStatus(404); return; }
|
||||
res.set({ 'Content-Type': 'image/svg+xml' });
|
||||
res.send(data);
|
||||
});
|
||||
}
|
||||
|
||||
function handleUploadFile(req, res) {
|
||||
const domain = checkUserIpAddress(req, res);
|
||||
if (domain == null) { return; }
|
||||
@ -4574,7 +4703,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
var names = fields.name[0].split('*'), sizes = fields.size[0].split('*'), types = fields.type[0].split('*'), datas = fields.data[0].split('*');
|
||||
if ((names.length == sizes.length) && (types.length == datas.length) && (names.length == types.length)) {
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
if (obj.common.IsFilenameValid(names[i]) == false) { res.sendStatus(404); return; }
|
||||
var originalName = names[i];
|
||||
var safeName = obj.path.basename(originalName);
|
||||
if ((safeName !== originalName) || (obj.common.IsFilenameValid(safeName) == false)) { res.sendStatus(404); return; }
|
||||
var filedata = Buffer.from(datas[i].split(',')[1], 'base64');
|
||||
if ((xfile.quota == null) || ((totalsize + filedata.length) < xfile.quota)) { // Check if quota would not be broken if we add this file
|
||||
// Create the user folder if needed
|
||||
@ -4585,7 +4716,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
|
||||
});
|
||||
});
|
||||
})(xfile.fullpath, names[i], filedata);
|
||||
})(xfile.fullpath, safeName, filedata);
|
||||
} else {
|
||||
// Send a notification
|
||||
obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: "Disk quota exceed", value: names[i], nolog: 1, id: Math.random() });
|
||||
@ -4595,8 +4726,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
} else {
|
||||
// More typical upload method, the file data is in a multipart mime post.
|
||||
for (var i in files.files) {
|
||||
var file = files.files[i], fpath = obj.path.join(xfile.fullpath, file.originalFilename);
|
||||
if (obj.common.IsFilenameValid(file.originalFilename) && ((xfile.quota == null) || ((totalsize + file.size) < xfile.quota))) { // Check if quota would not be broken if we add this file
|
||||
var file = files.files[i];
|
||||
var originalFilename = (typeof file.originalFilename === 'string') ? file.originalFilename : '';
|
||||
var safeOriginalFilename = obj.path.basename(originalFilename);
|
||||
var isFilenameAcceptable = (safeOriginalFilename === originalFilename) && obj.common.IsFilenameValid(safeOriginalFilename);
|
||||
const uploadTempPath = resolveSafeUploadTempPath(file.path);
|
||||
if (uploadTempPath == null) { res.sendStatus(400); return; }
|
||||
if (isFilenameAcceptable && ((xfile.quota == null) || ((totalsize + file.size) < xfile.quota))) { // Check if quota would not be broken if we add this file
|
||||
var fpath = obj.path.join(xfile.fullpath, safeOriginalFilename);
|
||||
|
||||
// See if we need to create the folder
|
||||
var domainx = 'domain';
|
||||
@ -4606,11 +4743,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
try { obj.fs.mkdirSync(xfile.fullpath); } catch (e) { }
|
||||
|
||||
// Rename the file
|
||||
obj.fs.rename(file.path, fpath, function (err) {
|
||||
obj.fs.rename(uploadTempPath, fpath, function (err) {
|
||||
if (err && (err.code === 'EXDEV')) {
|
||||
// On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
|
||||
obj.common.copyFile(file.path, fpath, function (err) {
|
||||
obj.fs.unlink(file.path, function (err) {
|
||||
obj.common.copyFile(uploadTempPath, fpath, function (err) {
|
||||
obj.fs.unlink(uploadTempPath, function (err) {
|
||||
obj.parent.DispatchEvent([user._id], obj, 'updatefiles'); // Fire an event causing this user to update this files
|
||||
});
|
||||
});
|
||||
@ -4621,7 +4758,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
} else {
|
||||
// Send a notification
|
||||
obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: "Disk quota exceed", value: file.originalFilename, nolog: 1, id: Math.random() });
|
||||
try { obj.fs.unlink(file.path, function (err) { }); } catch (e) { }
|
||||
try { obj.fs.unlink(uploadTempPath, function (err) { }); } catch (e) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4676,13 +4813,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
|
||||
// More typical upload method, the file data is in a multipart mime post.
|
||||
for (var i in files.files) {
|
||||
var file = files.files[i], ftarget = getRandomPassword() + '-' + file.originalFilename, fpath = obj.path.join(serverpath, ftarget);
|
||||
var file = files.files[i];
|
||||
const ftarget = getRandomPassword() + '-' + file.originalFilename;
|
||||
const targetPath = obj.path.join(serverpath, ftarget);
|
||||
const uploadTempPath = resolveSafeUploadTempPath(file.path);
|
||||
if (uploadTempPath == null) { res.sendStatus(400); return; }
|
||||
cmd.files.push({ name: file.originalFilename, target: ftarget });
|
||||
// Rename the file
|
||||
obj.fs.rename(file.path, fpath, function (err) {
|
||||
obj.fs.rename(uploadTempPath, targetPath, function (err) {
|
||||
if (err && (err.code === 'EXDEV')) {
|
||||
// On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
|
||||
obj.common.copyFile(file.path, fpath, function (err) { obj.fs.unlink(file.path, function (err) { }); });
|
||||
obj.common.copyFile(uploadTempPath, targetPath, function (err) { obj.fs.unlink(uploadTempPath, function (err) { }); });
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -5022,9 +5163,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
}
|
||||
|
||||
// Close the recording file
|
||||
if (ws.logfile != null) {
|
||||
if (ws.logfile != null) {
|
||||
setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
|
||||
obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
|
||||
obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
|
||||
obj.fs.close(logfile.fd);
|
||||
parent.debug('relay', 'Relay: Finished recording to file: ' + ws.logfile.filename);
|
||||
// Compute session length
|
||||
@ -5076,7 +5217,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
// Close the recording file
|
||||
if (ws.logfile != null) {
|
||||
setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
|
||||
obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
|
||||
obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
|
||||
obj.fs.close(logfile.fd);
|
||||
parent.debug('relay', 'Relay: Finished recording to file: ' + ws.logfile.filename);
|
||||
// Compute session length
|
||||
@ -5152,7 +5293,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
// Close the recording file
|
||||
if (ws.logfile != null) {
|
||||
setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
|
||||
obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
|
||||
obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
|
||||
obj.fs.close(logfile.fd);
|
||||
parent.debug('relay', 'Relay: Finished recording to file: ' + ws.logfile.filename);
|
||||
// Compute session length
|
||||
@ -5194,7 +5335,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
// Close the recording file
|
||||
if (ws.logfile != null) {
|
||||
setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
|
||||
obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
|
||||
obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
|
||||
obj.fs.close(logfile.fd);
|
||||
parent.debug('relay', 'Relay: Finished recording to file: ' + ws.logfile.filename);
|
||||
// Compute session length
|
||||
@ -5297,7 +5438,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if (req.query.p == 2) { // Only log event if Intel Redirection, otherwise hundreds of logs for WSMAN are recorded
|
||||
var msg = 'Started relay session', msgid = 13, ip = ((ciraconn != null) ? ciraconn.remoteAddr : (((conn & 4) != 0) ? node.host : req.clientIp));
|
||||
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msgid: msgid, msgArgs: [ws.id, req.clientIp, ip], msg: msg + ' \"' + ws.id + '\" from ' + req.clientIp + ' to ' + ip, protocol: 101, nodeid: node._id };
|
||||
obj.parent.DispatchEvent(['*', user._id], obj, event);
|
||||
obj.parent.DispatchEvent(['*', user._id], obj, event);
|
||||
}
|
||||
|
||||
// Update user last access time
|
||||
@ -5610,7 +5751,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights
|
||||
|
||||
// Require modules
|
||||
const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method to maximum.
|
||||
const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method to maximum.
|
||||
|
||||
// Good practice to catch this error explicitly
|
||||
archive.on('error', function (err) { throw err; });
|
||||
@ -5618,13 +5759,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
// Set the archive name
|
||||
res.attachment((domain.title ? domain.title : 'MeshCentral') + '-Backup-' + new Date().toLocaleDateString().replace('/', '-').replace('/', '-') + '.zip');
|
||||
|
||||
// Pipe archive data to the file
|
||||
// Pipe archive data to the file
|
||||
archive.pipe(res);
|
||||
|
||||
// Append files from a glob pattern
|
||||
archive.directory(obj.parent.datapath, false);
|
||||
|
||||
// Finalize the archive (ie we are done appending files but streams have to finish yet)
|
||||
// Finalize the archive (ie we are done appending files but streams have to finish yet)
|
||||
archive.finalize();
|
||||
}
|
||||
|
||||
@ -6780,14 +6921,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
extraFrameSrc = ' https://' + req.headers.host + ':' + parent.webrelayserver.port;
|
||||
if ((xforwardedhost != null) && (xforwardedhost != req.headers.host)) { extraFrameSrc += ' https://' + xforwardedhost + ':' + parent.webrelayserver.port; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// If using duo add apihostname to CSP
|
||||
var duoSrc = '';
|
||||
if ((typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.apihostname == 'string')) {
|
||||
duoSrc = domain.duo2factor.apihostname;
|
||||
}
|
||||
|
||||
|
||||
// Finish setup security headers
|
||||
const headers = {
|
||||
'Referrer-Policy': 'no-referrer',
|
||||
@ -6944,6 +7085,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
obj.app.get(url + 'commander.ashx', handleMeshCommander);
|
||||
obj.app.post(url + 'uploadfile.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadFile);
|
||||
obj.app.post(url + 'uploadfilebatch.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadFileBatch);
|
||||
obj.app.post(url + 'customiconupload.ashx', handleCustomIconUpload);
|
||||
obj.app.get(url + 'icons/custom/*', handleCustomIconDownload);
|
||||
obj.app.post(url + 'uploadmeshcorefile.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadMeshCoreFile);
|
||||
obj.app.post(url + 'oneclickrecovery.ashx', obj.bodyParser.urlencoded({ extended: false }), handleOneClickRecoveryFile);
|
||||
obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
|
||||
@ -7018,7 +7161,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
}
|
||||
obj.app.get(url + 'invite', handleInviteRequest);
|
||||
obj.app.post(url + 'invite', obj.bodyParser.urlencoded({ extended: false }), handleInviteRequest);
|
||||
|
||||
|
||||
if (parent.pluginHandler != null) {
|
||||
obj.app.get(url + 'pluginadmin.ashx', obj.handlePluginAdminReq);
|
||||
obj.app.post(url + 'pluginadmin.ashx', obj.bodyParser.urlencoded({ extended: false }), obj.handlePluginAdminPostReq);
|
||||
@ -7343,7 +7486,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
// Notify account 2fa failed login
|
||||
const ua = obj.getUserAgentInfo(req);
|
||||
obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { action: 'authfail', username: user.name, userid: user._id, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + req.clientIp, msgid: 108, msgArgs: [req.clientIp, ua.browserStr, ua.osStr] });
|
||||
obj.setbad2Fa(req);
|
||||
obj.setbad2Fa(req);
|
||||
res.redirect(domain.url + getQueryPortion(req));
|
||||
});
|
||||
} else {
|
||||
@ -7682,7 +7825,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
parent.debug('web', '404 Error ' + req.url);
|
||||
var domain = getDomain(req);
|
||||
if ((domain == null) || (domain.auth == 'sspi')) { res.sendStatus(404); return; }
|
||||
if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL
|
||||
if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL
|
||||
const cspNonce = obj.crypto.randomBytes(15).toString('base64');
|
||||
res.set({ 'Content-Security-Policy': "default-src 'none'; script-src 'self' 'nonce-" + cspNonce + "'; img-src 'self'; style-src 'self' 'nonce-" + cspNonce + "';" }); // This page supports very tight CSP policy
|
||||
res.status(404).render(getRenderPage((domain.sitestyle >= 2) ? 'error4042' : 'error404', req, domain), getRenderArgs({ cspNonce: cspNonce }, req, domain));
|
||||
@ -7704,7 +7847,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
parent.debug('web', '404 Error ' + req.url);
|
||||
var domain = getDomain(req);
|
||||
if ((domain == null) || (domain.auth == 'sspi')) { res.sendStatus(404); return; }
|
||||
if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL
|
||||
if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL
|
||||
if (obj.args.nice404 == false) { res.sendStatus(404); return; }
|
||||
const cspNonce = obj.crypto.randomBytes(15).toString('base64');
|
||||
res.set({ 'Content-Security-Policy': "default-src 'none'; script-src 'self' 'nonce-" + cspNonce + "'; img-src 'self'; style-src 'self' 'nonce-" + cspNonce + "';" }); // This page supports very tight CSP policy
|
||||
@ -8138,7 +8281,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if (Buffer.isBuffer(data[0])) {
|
||||
data = Buffer.concat(data);
|
||||
data = data.toString();
|
||||
} else { // else if (typeof data[0] == 'string')
|
||||
} else { // else if (typeof data[0] == 'string')
|
||||
data = data.join();
|
||||
}
|
||||
} catch (err) {
|
||||
@ -8193,7 +8336,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
return authStrategyFlags;
|
||||
}
|
||||
|
||||
// Handle an incoming request as a web relay
|
||||
// Handle an incoming request as a web relay
|
||||
function handleWebRelayRequest(req, res) {
|
||||
var webRelaySessionId = null;
|
||||
if ((req.session.userid != null) && (req.session.x != null)) { webRelaySessionId = req.session.userid + '/' + req.session.x; }
|
||||
@ -8213,7 +8356,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
}
|
||||
}
|
||||
|
||||
// Handle an incoming websocket connection as a web relay
|
||||
// Handle an incoming websocket connection as a web relay
|
||||
function handleWebRelayWebSocket(ws, req) {
|
||||
var webRelaySessionId = null;
|
||||
if ((req.session.userid != null) && (req.session.x != null)) { webRelaySessionId = req.session.userid + '/' + req.session.x; }
|
||||
@ -8657,7 +8800,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if (emailcheck && (user.email != null) && (!(user._id.split('/')[2].startsWith('~'))) && (user.emailVerified !== true)) {
|
||||
parent.debug('web', 'Invalid login, asking for email validation');
|
||||
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, email2fasent: true })); ws.close(); } catch (e) { }
|
||||
} else {
|
||||
} else {
|
||||
req.session.userid = user._id;
|
||||
req.session.ip = req.clientIp;
|
||||
setSessionRandom(req);
|
||||
@ -9418,7 +9561,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
xargs.title1 = domain.title1 ? domain.title1 : '';
|
||||
xargs.title2 = (domain.title1 && domain.title2) ? domain.title2 : '';
|
||||
}
|
||||
xargs.title2 = obj.common.replacePlaceholders(xargs.title2, {
|
||||
xargs.title2 = obj.common.replacePlaceholders(xargs.title2, {
|
||||
'serverversion': obj.parent.currentVer,
|
||||
'servername': obj.getWebServerName(domain, req),
|
||||
'agentsessions': Object.keys(parent.webserver.wsagents).length,
|
||||
@ -9776,7 +9919,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if (ua.browser && ua.browser.name) { ua.browserStr = ua.browser.name; if (ua.browser.version) { ua.browserStr += '/' + ua.browser.version } }
|
||||
if (ua.os && ua.os.name) { ua.osStr = ua.os.name; if (ua.os.version) { ua.osStr += '/' + ua.os.version } }
|
||||
// If the platform is set, use that instead of the OS
|
||||
if (ua.platform) {
|
||||
if (ua.platform) {
|
||||
ua.osStr = ua.platform;
|
||||
// Special case for Windows 11
|
||||
if (ua.platformVersion) {
|
||||
@ -9790,9 +9933,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
} catch (ex) { return { browserStr: browser, osStr: os } }
|
||||
}
|
||||
|
||||
// Return the query string portion of the URL, the ? and anything after BUT remove secret keys from authentication providers
|
||||
// Return the query string portion of the URL, the ? and anything after BUT remove secret keys from authentication providers
|
||||
function getQueryPortion(req) {
|
||||
var removeKeys = ['duo_code', 'state']; // Keys to remove
|
||||
var removeKeys = ['duo_code', 'state']; // Keys to remove
|
||||
var s = req.url.indexOf('?');
|
||||
if (s == -1) {
|
||||
if (req.body && req.body.urlargs) {
|
||||
@ -9881,7 +10024,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if (parent.config.settings.maxinvalidlogin === false) return true;
|
||||
if (typeof ip == 'object') { ip = ip.clientIp; }
|
||||
var splitip = ip.split('.');
|
||||
if (splitip.length == 4) { ip = (splitip[0] + '.' + splitip[1] + '.' + splitip[2] + '.*'); } // If this is IPv4, keep only the 3 first
|
||||
if (splitip.length == 4) { ip = (splitip[0] + '.' + splitip[1] + '.' + splitip[2] + '.*'); } // If this is IPv4, keep only the 3 first
|
||||
var cutoffTime = Date.now() - (parent.config.settings.maxinvalidlogin.time * 60000); // Time in minutes
|
||||
var ipTable = obj.badLoginTable[ip];
|
||||
if (ipTable == null) return true;
|
||||
@ -9939,7 +10082,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if (parent.config.settings.maxinvalid2fa === false) return true;
|
||||
if (typeof ip == 'object') { ip = ip.clientIp; }
|
||||
var splitip = ip.split('.');
|
||||
if (splitip.length == 4) { ip = (splitip[0] + '.' + splitip[1] + '.' + splitip[2] + '.*'); } // If this is IPv4, keep only the 3 first
|
||||
if (splitip.length == 4) { ip = (splitip[0] + '.' + splitip[1] + '.' + splitip[2] + '.*'); } // If this is IPv4, keep only the 3 first
|
||||
var cutoffTime = Date.now() - (parent.config.settings.maxinvalid2fa.time * 60000); // Time in minutes
|
||||
var ipTable = obj.bad2faTable[ip];
|
||||
if (ipTable == null) return true;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user