2017-08-28 19:27:45 +03:00
/ * *
2018-01-04 23:15:21 +03:00
* @ description MeshCentral web server
2017-08-28 19:27:45 +03:00
* @ author Ylian Saint - Hilaire
2022-01-24 10:21:24 +03:00
* @ copyright Intel Corporation 2018 - 2022
2018-01-04 23:15:21 +03:00
* @ license Apache - 2.0
2017-08-28 19:27:45 +03:00
* @ version v0 . 0.1
* /
2018-08-30 22:05:23 +03:00
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
2019-12-27 09:53:01 +03:00
'use strict' ;
2017-08-28 19:27:45 +03:00
2022-08-22 21:43:45 +03:00
// SerialTunnel object is used to embed TLS within another connection.
2017-08-28 19:27:45 +03:00
function SerialTunnel ( options ) {
var obj = new require ( 'stream' ) . Duplex ( options ) ;
obj . forwardwrite = null ;
2018-08-30 22:05:23 +03:00
obj . updateBuffer = function ( chunk ) { this . push ( chunk ) ; } ;
obj . _write = function ( chunk , encoding , callback ) { if ( obj . forwardwrite != null ) { obj . forwardwrite ( chunk ) ; } else { console . err ( "Failed to fwd _write." ) ; } if ( callback ) callback ( ) ; } ; // Pass data written to forward
obj . _read = function ( size ) { } ; // Push nothing, anything to read should be pushed from updateBuffer()
2017-08-28 19:27:45 +03:00
return obj ;
}
// ExpressJS login sample
// https://github.com/expressjs/express/blob/master/examples/auth/index.js
// Polyfill startsWith/endsWith for older NodeJS
if ( ! String . prototype . startsWith ) { String . prototype . startsWith = function ( searchString , position ) { position = position || 0 ; return this . substr ( position , searchString . length ) === searchString ; } ; }
if ( ! String . prototype . endsWith ) { String . prototype . endsWith = function ( searchString , position ) { var subjectString = this . toString ( ) ; if ( typeof position !== 'number' || ! isFinite ( position ) || Math . floor ( position ) !== position || position > subjectString . length ) { position = subjectString . length ; } position -= searchString . length ; var lastIndex = subjectString . lastIndexOf ( searchString , position ) ; return lastIndex !== - 1 && lastIndex === position ; } ; }
2019-01-03 05:34:50 +03:00
// Construct a HTTP server object
2021-12-03 07:20:37 +03:00
module . exports . CreateWebServer = function ( parent , db , args , certificates , doneFunc ) {
2019-10-04 22:18:56 +03:00
var obj = { } , i = 0 ;
2017-08-28 19:27:45 +03:00
2018-01-10 07:13:41 +03:00
// Modules
2017-08-28 19:27:45 +03:00
obj . fs = require ( 'fs' ) ;
obj . net = require ( 'net' ) ;
obj . tls = require ( 'tls' ) ;
obj . path = require ( 'path' ) ;
obj . bodyParser = require ( 'body-parser' ) ;
obj . exphbs = require ( 'express-handlebars' ) ;
obj . crypto = require ( 'crypto' ) ;
obj . common = require ( './common.js' ) ;
obj . express = require ( 'express' ) ;
obj . meshAgentHandler = require ( './meshagent.js' ) ;
2018-08-30 22:05:23 +03:00
obj . meshRelayHandler = require ( './meshrelay.js' ) ;
2020-08-27 04:42:41 +03:00
obj . meshDeviceFileHandler = require ( './meshdevicefile.js' ) ;
2020-04-24 09:19:28 +03:00
obj . meshDesktopMultiplexHandler = require ( './meshdesktopmultiplex.js' ) ;
2019-04-24 21:38:28 +03:00
obj . meshIderHandler = require ( './amt/amt-ider.js' ) ;
2018-08-30 22:05:23 +03:00
obj . meshUserHandler = require ( './meshuser.js' ) ;
2017-08-28 19:27:45 +03:00
obj . interceptor = require ( './interceptor' ) ;
2023-08-08 18:13:31 +03:00
obj . uaparser = require ( 'ua-parser-js' ) ;
2019-02-27 02:49:44 +03:00
const constants = ( obj . crypto . constants ? obj . crypto . constants : require ( 'constants' ) ) ; // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
2018-05-24 07:43:44 +03:00
2019-03-23 23:28:17 +03:00
// Setup WebAuthn / FIDO2
2019-12-27 09:53:01 +03:00
obj . webauthn = require ( './webauthn.js' ) . CreateWebAuthnModule ( ) ;
2019-03-23 08:33:53 +03:00
2017-08-28 19:27:45 +03:00
// Variables
2020-05-03 20:46:20 +03:00
obj . args = args ;
2017-08-28 19:27:45 +03:00
obj . parent = parent ;
obj . filespath = parent . filespath ;
obj . db = db ;
obj . app = obj . express ( ) ;
2020-05-03 20:46:20 +03:00
if ( obj . args . agentport ) { obj . agentapp = obj . express ( ) ; }
2020-06-20 01:56:48 +03:00
if ( args . compression !== false ) { obj . app . use ( require ( 'compression' ) ( ) ) ; }
obj . app . disable ( 'x-powered-by' ) ;
2017-08-28 19:27:45 +03:00
obj . tlsServer = null ;
2018-08-30 22:05:23 +03:00
obj . tcpServer = null ;
2017-08-28 19:27:45 +03:00
obj . certificates = certificates ;
2019-12-27 09:53:01 +03:00
obj . users = { } ; // UserID --> User
obj . meshes = { } ; // MeshID --> Mesh (also called device group)
2020-01-02 11:30:14 +03:00
obj . userGroups = { } ; // UGrpID --> User Group
2024-03-10 10:45:10 +03:00
obj . useNodeDefaultTLSCiphers = args . usenodedefaulttlsciphers ; // Use TLS ciphers provided by node
obj . tlsCiphers = args . tlsciphers ; // List of TLS ciphers to use
2019-12-27 09:53:01 +03:00
obj . userAllowedIp = args . userallowedip ; // List of allowed IP addresses for users
obj . agentAllowedIp = args . agentallowedip ; // List of allowed IP addresses for agents
obj . agentBlockedIp = args . agentblockedip ; // List of blocked IP addresses for agents
2018-08-30 22:05:23 +03:00
obj . tlsSniCredentials = null ;
2018-01-03 03:52:49 +03:00
obj . dnsDomains = { } ;
2019-02-19 04:14:00 +03:00
obj . relaySessionCount = 0 ;
obj . relaySessionErrorCount = 0 ;
2020-01-10 22:19:23 +03:00
obj . blockedUsers = 0 ;
obj . blockedAgents = 0 ;
2019-10-22 01:59:20 +03:00
obj . renderPages = null ;
2019-10-22 21:59:19 +03:00
obj . renderLanguages = [ ] ;
2022-07-09 23:32:55 +03:00
obj . destroyedSessions = { } ; // userid/req.session.x --> destroyed session time
2018-01-03 03:52:49 +03:00
2022-07-03 10:44:58 +03:00
// Web relay sessions
var webRelayNextSessionId = 1 ;
2022-10-25 21:14:26 +03:00
var webRelaySessions = { } // UserId/SessionId/Host --> Web Relay Session
2022-07-03 10:44:58 +03:00
var webRelayCleanupTimer = null ;
2022-07-20 22:57:24 +03:00
2022-10-25 21:14:26 +03:00
// Monitor web relay session removals
parent . AddEventDispatch ( [ 'server-shareremove' ] , obj ) ;
obj . HandleEvent = function ( source , event , ids , id ) {
if ( event . action == 'removedDeviceShare' ) {
for ( var relaySessionId in webRelaySessions ) {
// A share was removed that matches an active session, close the web relay session.
if ( webRelaySessions [ relaySessionId ] . xpublicid === event . publicid ) { webRelaySessions [ relaySessionId ] . close ( ) ; }
}
}
}
2017-10-24 00:09:58 +03:00
// Mesh Rights
2022-07-20 22:57:24 +03:00
const MESHRIGHT _EDITMESH = 0x00000001 ;
const MESHRIGHT _MANAGEUSERS = 0x00000002 ;
const MESHRIGHT _MANAGECOMPUTERS = 0x00000004 ;
const MESHRIGHT _REMOTECONTROL = 0x00000008 ;
const MESHRIGHT _AGENTCONSOLE = 0x00000010 ;
const MESHRIGHT _SERVERFILES = 0x00000020 ;
const MESHRIGHT _WAKEDEVICE = 0x00000040 ;
const MESHRIGHT _SETNOTES = 0x00000080 ;
const MESHRIGHT _REMOTEVIEWONLY = 0x00000100 ;
const MESHRIGHT _NOTERMINAL = 0x00000200 ;
const MESHRIGHT _NOFILES = 0x00000400 ;
const MESHRIGHT _NOAMT = 0x00000800 ;
const MESHRIGHT _DESKLIMITEDINPUT = 0x00001000 ;
const MESHRIGHT _LIMITEVENTS = 0x00002000 ;
const MESHRIGHT _CHATNOTIFY = 0x00004000 ;
const MESHRIGHT _UNINSTALL = 0x00008000 ;
const MESHRIGHT _NODESKTOP = 0x00010000 ;
const MESHRIGHT _REMOTECOMMAND = 0x00020000 ;
const MESHRIGHT _RESETOFF = 0x00040000 ;
const MESHRIGHT _GUESTSHARING = 0x00080000 ;
const MESHRIGHT _ADMIN = 0xFFFFFFFF ;
2021-06-04 22:46:41 +03:00
2021-10-09 06:52:22 +03:00
// Site rights
2022-07-20 22:57:24 +03:00
const SITERIGHT _SERVERBACKUP = 0x00000001 ;
const SITERIGHT _MANAGEUSERS = 0x00000002 ;
const SITERIGHT _SERVERRESTORE = 0x00000004 ;
const SITERIGHT _FILEACCESS = 0x00000008 ;
const SITERIGHT _SERVERUPDATE = 0x00000010 ;
const SITERIGHT _LOCKED = 0x00000020 ;
const SITERIGHT _NONEWGROUPS = 0x00000040 ;
const SITERIGHT _NOMESHCMD = 0x00000080 ;
const SITERIGHT _USERGROUPS = 0x00000100 ;
const SITERIGHT _RECORDINGS = 0x00000200 ;
const SITERIGHT _LOCKSETTINGS = 0x00000400 ;
const SITERIGHT _ALLEVENTS = 0x00000800 ;
const SITERIGHT _NONEWDEVICES = 0x00001000 ;
const SITERIGHT _ADMIN = 0xFFFFFFFF ;
2017-10-24 00:09:58 +03:00
2018-01-03 03:52:49 +03:00
// Setup SSPI authentication if needed
if ( ( obj . parent . platform == 'win32' ) && ( obj . args . nousers != true ) && ( obj . parent . config != null ) && ( obj . parent . config . domains != null ) ) {
2021-09-03 03:52:37 +03:00
for ( i in obj . parent . config . domains ) { if ( obj . parent . config . domains [ i ] . auth == 'sspi' ) { var nodeSSPI = require ( 'node-sspi' ) ; obj . parent . config . domains [ i ] . sspi = new nodeSSPI ( { retrieveGroups : false , offerBasic : false } ) ; } }
2018-01-03 03:52:49 +03:00
}
2017-08-28 19:27:45 +03:00
// Perform hash on web certificate and agent certificate
2021-02-15 09:22:53 +03:00
obj . webCertificateHash = parent . certificateOperations . getPublicKeyHashBinary ( obj . certificates . web . cert ) ;
2018-01-03 03:52:49 +03:00
obj . webCertificateHashs = { '' : obj . webCertificateHash } ;
2019-01-03 05:03:34 +03:00
obj . webCertificateHashBase64 = Buffer . from ( obj . webCertificateHash , 'binary' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
2021-02-15 09:22:53 +03:00
obj . webCertificateFullHash = parent . certificateOperations . getCertHashBinary ( obj . certificates . web . cert ) ;
2018-12-01 03:42:58 +03:00
obj . webCertificateFullHashs = { '' : obj . webCertificateFullHash } ;
2023-08-21 09:29:08 +03:00
obj . webCertificateExpire = { '' : parent . certificateOperations . getCertificateExpire ( parent . certificates . web . cert ) } ;
2018-12-01 03:42:58 +03:00
obj . agentCertificateHashHex = parent . certificateOperations . getPublicKeyHash ( obj . certificates . agent . cert ) ;
2019-01-03 05:03:34 +03:00
obj . agentCertificateHashBase64 = Buffer . from ( obj . agentCertificateHashHex , 'hex' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
2017-10-19 02:28:05 +03:00
obj . agentCertificateAsn1 = parent . certificateOperations . forge . asn1 . toDer ( parent . certificateOperations . forge . pki . certificateToAsn1 ( parent . certificateOperations . forge . pki . certificateFromPem ( parent . certificates . agent . cert ) ) ) . getBytes ( ) ;
2022-08-27 01:43:12 +03:00
obj . defaultWebCertificateHash = obj . certificates . webdefault ? parent . certificateOperations . getPublicKeyHashBinary ( obj . certificates . webdefault . cert ) : null ;
obj . defaultWebCertificateFullHash = obj . certificates . webdefault ? parent . certificateOperations . getCertHashBinary ( obj . certificates . webdefault . cert ) : null ;
2018-11-01 02:03:09 +03:00
// Compute the hash of all of the web certificates for each domain
for ( var i in obj . parent . config . domains ) {
if ( obj . parent . config . domains [ i ] . certhash != null ) {
// If the web certificate hash is provided, use it.
2019-01-03 05:03:34 +03:00
obj . webCertificateHashs [ i ] = obj . webCertificateFullHashs [ i ] = Buffer . from ( obj . parent . config . domains [ i ] . certhash , 'hex' ) . toString ( 'binary' ) ;
if ( obj . parent . config . domains [ i ] . certkeyhash != null ) { obj . webCertificateHashs [ i ] = Buffer . from ( obj . parent . config . domains [ i ] . certkeyhash , 'hex' ) . toString ( 'binary' ) ; }
2021-04-24 00:17:13 +03:00
delete obj . webCertificateExpire [ i ] ; // Expire time is not provided
2018-11-01 02:03:09 +03:00
} else if ( ( obj . parent . config . domains [ i ] . dns != null ) && ( obj . parent . config . domains [ i ] . certs != null ) ) {
// If the domain has a different DNS name, use a different certificate hash.
2018-12-01 03:42:58 +03:00
// Hash the full certificate
obj . webCertificateFullHashs [ i ] = parent . certificateOperations . getCertHashBinary ( obj . parent . config . domains [ i ] . certs . cert ) ;
2021-04-24 00:17:13 +03:00
obj . webCertificateExpire [ i ] = Date . parse ( parent . certificateOperations . forge . pki . certificateFromPem ( obj . parent . config . domains [ i ] . certs . cert ) . validity . notAfter ) ;
2018-11-01 02:03:09 +03:00
try {
2018-12-01 03:42:58 +03:00
// Decode a RSA certificate and hash the public key.
obj . webCertificateHashs [ i ] = parent . certificateOperations . getPublicKeyHashBinary ( obj . parent . config . domains [ i ] . certs . cert ) ;
2018-11-01 02:03:09 +03:00
} catch ( ex ) {
2018-12-01 03:42:58 +03:00
// This may be a ECDSA certificate, hash the entire cert.
obj . webCertificateHashs [ i ] = obj . webCertificateFullHashs [ i ] ;
2018-11-01 02:03:09 +03:00
}
2019-03-14 22:28:58 +03:00
} else if ( ( obj . parent . config . domains [ i ] . dns != null ) && ( obj . certificates . dns [ i ] != null ) ) {
// If this domain has a DNS and a matching DNS cert, use it. This case works for wildcard certs.
obj . webCertificateFullHashs [ i ] = parent . certificateOperations . getCertHashBinary ( obj . certificates . dns [ i ] . cert ) ;
obj . webCertificateHashs [ i ] = parent . certificateOperations . getPublicKeyHashBinary ( obj . certificates . dns [ i ] . cert ) ;
2021-04-24 00:17:13 +03:00
obj . webCertificateExpire [ i ] = Date . parse ( parent . certificateOperations . forge . pki . certificateFromPem ( obj . certificates . dns [ i ] . cert ) . validity . notAfter ) ;
2019-04-05 00:47:52 +03:00
} else if ( i != '' ) {
// For any other domain, use the default cert.
obj . webCertificateFullHashs [ i ] = obj . webCertificateFullHashs [ '' ] ;
obj . webCertificateHashs [ i ] = obj . webCertificateHashs [ '' ] ;
2021-04-24 00:17:13 +03:00
obj . webCertificateExpire [ i ] = obj . webCertificateExpire [ '' ] ;
2018-11-01 02:03:09 +03:00
}
}
// If we are running the legacy swarm server, compute the hash for that certificate
2017-11-08 04:05:22 +03:00
if ( parent . certificates . swarmserver != null ) {
obj . swarmCertificateAsn1 = parent . certificateOperations . forge . asn1 . toDer ( parent . certificateOperations . forge . pki . certificateToAsn1 ( parent . certificateOperations . forge . pki . certificateFromPem ( parent . certificates . swarmserver . cert ) ) ) . getBytes ( ) ;
obj . swarmCertificateHash384 = parent . certificateOperations . forge . pki . getPublicKeyFingerprint ( parent . certificateOperations . forge . pki . certificateFromPem ( obj . certificates . swarmserver . cert ) . publicKey , { md : parent . certificateOperations . forge . md . sha384 . create ( ) , encoding : 'binary' } ) ;
obj . swarmCertificateHash256 = parent . certificateOperations . forge . pki . getPublicKeyFingerprint ( parent . certificateOperations . forge . pki . certificateFromPem ( obj . certificates . swarmserver . cert ) . publicKey , { md : parent . certificateOperations . forge . md . sha256 . create ( ) , encoding : 'binary' } ) ;
}
2017-08-28 19:27:45 +03:00
2018-11-01 02:03:09 +03:00
// Main lists
2019-10-30 02:17:29 +03:00
obj . wsagents = { } ; // NodeId --> Agent
obj . wsagentsWithBadWebCerts = { } ; // NodeId --> Agent
2019-01-23 06:34:55 +03:00
obj . wsagentsDisconnections = { } ;
obj . wsagentsDisconnectionsTimer = null ;
2019-05-22 00:19:32 +03:00
obj . duplicateAgentsLog = { } ;
2019-10-30 02:17:29 +03:00
obj . wssessions = { } ; // UserId --> Array Of Sessions
obj . wssessions2 = { } ; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
obj . wsPeerSessions = { } ; // ServerId --> Array Of "UserId + SessionRnd"
obj . wsPeerSessions2 = { } ; // "UserId + SessionRnd" --> ServerId
obj . wsPeerSessions3 = { } ; // ServerId --> UserId --> [ SessionId ]
obj . sessionsCount = { } ; // Merged session counters, used when doing server peering. UserId --> SessionCount
obj . wsrelays = { } ; // Id -> Relay
2022-09-07 17:40:36 +03:00
obj . desktoprelays = { } ; // Id -> Desktop Multiplexer Relay
2019-10-30 02:17:29 +03:00
obj . wsPeerRelays = { } ; // Id -> { ServerId, Time }
var tlsSessionStore = { } ; // Store TLS session information for quick resume.
var tlsSessionStoreCount = 0 ; // Number of cached TLS session information in store.
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Setup randoms
2017-10-15 09:22:19 +03:00
obj . crypto . randomBytes ( 48 , function ( err , buf ) { obj . httpAuthRandom = buf ; } ) ;
2017-08-28 19:27:45 +03:00
obj . crypto . randomBytes ( 16 , function ( err , buf ) { obj . httpAuthRealm = buf . toString ( 'hex' ) ; } ) ;
2017-10-15 09:22:19 +03:00
obj . crypto . randomBytes ( 48 , function ( err , buf ) { obj . relayRandom = buf ; } ) ;
2017-08-28 19:27:45 +03:00
2020-04-05 05:29:20 +03:00
// Get non-english web pages and emails
2019-10-22 01:59:20 +03:00
getRenderList ( ) ;
2020-04-05 05:29:20 +03:00
getEmailLanguageList ( ) ;
2019-10-22 01:59:20 +03:00
2018-01-03 03:52:49 +03:00
// Setup DNS domain TLS SNI credentials
{
var dnscount = 0 ;
obj . tlsSniCredentials = { } ;
2018-08-30 22:05:23 +03:00
for ( i in obj . certificates . dns ) { if ( obj . parent . config . domains [ i ] . dns != null ) { obj . dnsDomains [ obj . parent . config . domains [ i ] . dns . toLowerCase ( ) ] = obj . parent . config . domains [ i ] ; obj . tlsSniCredentials [ obj . parent . config . domains [ i ] . dns ] = obj . tls . createSecureContext ( obj . certificates . dns [ i ] ) . context ; dnscount ++ ; } }
2018-01-15 08:01:06 +03:00
if ( dnscount > 0 ) { obj . tlsSniCredentials [ '' ] = obj . tls . createSecureContext ( { cert : obj . certificates . web . cert , key : obj . certificates . web . key , ca : obj . certificates . web . ca } ) . context ; } else { obj . tlsSniCredentials = null ; }
2018-01-03 03:52:49 +03:00
}
2018-12-12 04:52:37 +03:00
function TlsSniCallback ( name , cb ) {
var c = obj . tlsSniCredentials [ name ] ;
if ( c != null ) {
cb ( null , c ) ;
} else {
cb ( null , obj . tlsSniCredentials [ '' ] ) ;
}
}
2018-01-03 03:52:49 +03:00
2019-12-27 09:53:01 +03:00
function EscapeHtml ( x ) { if ( typeof x == 'string' ) return x . replace ( /&/g , '&' ) . replace ( />/g , '>' ) . replace ( /</g , '<' ) . replace ( /"/g , '"' ) . replace ( /'/g , ''' ) ; if ( typeof x == 'boolean' ) return x ; if ( typeof x == 'number' ) return x ; }
2018-08-30 22:05:23 +03:00
//function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, ''').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, ' '); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
2017-08-28 19:27:45 +03:00
// Fetch all users from the database, keep this in memory
obj . db . GetAllType ( 'user' , function ( err , docs ) {
2020-03-27 05:33:13 +03:00
obj . common . unEscapeAllLinksFieldName ( docs ) ;
2018-08-30 22:05:23 +03:00
var domainUserCount = { } , i = 0 ;
for ( i in parent . config . domains ) { domainUserCount [ i ] = 0 ; }
for ( i in docs ) { var u = obj . users [ docs [ i ] . _id ] = docs [ i ] ; domainUserCount [ u . domain ] ++ ; }
for ( i in parent . config . domains ) {
2020-12-30 02:13:09 +03:00
if ( ( parent . config . domains [ i ] . share == null ) && ( domainUserCount [ i ] == 0 ) ) {
2022-09-07 17:40:36 +03:00
// If newaccounts is set to no new accounts, but no accounts exists, temporarily allow account creation.
2019-04-23 23:42:54 +03:00
//if ((parent.config.domains[i].newaccounts === 0) || (parent.config.domains[i].newaccounts === false)) { parent.config.domains[i].newaccounts = 2; }
2017-08-28 19:27:45 +03:00
console . log ( 'Server ' + ( ( i == '' ) ? '' : ( i + ' ' ) ) + 'has no users, next new account will be site administrator.' ) ;
}
}
2018-05-24 07:43:44 +03:00
2020-01-02 11:30:14 +03:00
// Fetch all device groups (meshes) from the database, keep this in memory
2020-03-31 03:47:04 +03:00
// As we load things in memory, we will also be doing some cleaning up.
// We will not save any clean up in the database right now, instead it will be saved next time there is a change.
2019-01-23 02:40:08 +03:00
obj . db . GetAllType ( 'mesh' , function ( err , docs ) {
obj . common . unEscapeAllLinksFieldName ( docs ) ;
2019-10-04 22:18:56 +03:00
for ( var i in docs ) { obj . meshes [ docs [ i ] . _id ] = docs [ i ] ; } // Get all meshes, including deleted ones.
2019-01-23 02:40:08 +03:00
2020-01-02 11:30:14 +03:00
// Fetch all user groups from the database, keep this in memory
obj . db . GetAllType ( 'ugrp' , function ( err , docs ) {
obj . common . unEscapeAllLinksFieldName ( docs ) ;
2020-03-31 03:47:04 +03:00
// Perform user group link cleanup
for ( var i in docs ) {
const ugrp = docs [ i ] ;
if ( ugrp . links != null ) {
for ( var j in ugrp . links ) {
if ( j . startsWith ( 'user/' ) && ( obj . users [ j ] == null ) ) { delete ugrp . links [ j ] ; } // User group has a link to a user that does not exist
else if ( j . startsWith ( 'mesh/' ) && ( ( obj . meshes [ j ] == null ) || ( obj . meshes [ j ] . deleted != null ) ) ) { delete ugrp . links [ j ] ; } // User has a link to a device group that does not exist
}
}
obj . userGroups [ docs [ i ] . _id ] = docs [ i ] ; // Get all user groups
}
// Perform device group link cleanup
for ( var i in obj . meshes ) {
const mesh = obj . meshes [ i ] ;
if ( mesh . links != null ) {
for ( var j in mesh . links ) {
if ( j . startsWith ( 'ugrp/' ) && ( obj . userGroups [ j ] == null ) ) { delete mesh . links [ j ] ; } // Device group has a link to a user group that does not exist
else if ( j . startsWith ( 'user/' ) && ( obj . users [ j ] == null ) ) { delete mesh . links [ j ] ; } // Device group has a link to a user that does not exist
}
}
2020-05-27 10:52:31 +03:00
}
2020-03-31 03:47:04 +03:00
// Perform user link cleanup
for ( var i in obj . users ) {
const user = obj . users [ i ] ;
if ( user . links != null ) {
for ( var j in user . links ) {
if ( j . startsWith ( 'ugrp/' ) && ( obj . userGroups [ j ] == null ) ) { delete user . links [ j ] ; } // User has a link to a user group that does not exist
else if ( j . startsWith ( 'mesh/' ) && ( ( obj . meshes [ j ] == null ) || ( obj . meshes [ j ] . deleted != null ) ) ) { delete user . links [ j ] ; } // User has a link to a device group that does not exist
//else if (j.startsWith('node/') && (obj.nodes[j] == null)) { delete user.links[j]; } // TODO
}
//if (Object.keys(user.links).length == 0) { delete user.links; }
}
}
// We loaded the users, device groups and user group state, start the server
2020-01-02 11:30:14 +03:00
serverStart ( ) ;
} ) ;
2019-01-23 02:40:08 +03:00
} ) ;
} ) ;
2017-08-28 19:27:45 +03:00
2020-03-31 03:47:04 +03:00
// Clean up a device, used before saving it in the database
obj . cleanDevice = function ( device ) {
// Check device links, if a link points to an unknown user, remove it.
if ( device . links != null ) {
for ( var j in device . links ) {
2020-04-13 08:51:57 +03:00
if ( ( obj . users [ j ] == null ) && ( obj . userGroups [ j ] == null ) ) {
2020-03-31 03:47:04 +03:00
delete device . links [ j ] ;
if ( Object . keys ( device . links ) . length == 0 ) { delete device . links ; }
}
}
}
return device ;
}
2019-05-02 01:02:03 +03:00
// Return statistics about this web server
obj . getStats = function ( ) {
return {
users : Object . keys ( obj . users ) . length ,
meshes : Object . keys ( obj . meshes ) . length ,
dnsDomains : Object . keys ( obj . dnsDomains ) . length ,
relaySessionCount : obj . relaySessionCount ,
relaySessionErrorCount : obj . relaySessionErrorCount ,
wsagents : Object . keys ( obj . wsagents ) . length ,
wsagentsDisconnections : Object . keys ( obj . wsagentsDisconnections ) . length ,
wsagentsDisconnectionsTimer : Object . keys ( obj . wsagentsDisconnectionsTimer ) . length ,
wssessions : Object . keys ( obj . wssessions ) . length ,
wssessions2 : Object . keys ( obj . wssessions2 ) . length ,
wsPeerSessions : Object . keys ( obj . wsPeerSessions ) . length ,
wsPeerSessions2 : Object . keys ( obj . wsPeerSessions2 ) . length ,
wsPeerSessions3 : Object . keys ( obj . wsPeerSessions3 ) . length ,
sessionsCount : Object . keys ( obj . sessionsCount ) . length ,
wsrelays : Object . keys ( obj . wsrelays ) . length ,
wsPeerRelays : Object . keys ( obj . wsPeerRelays ) . length ,
2020-01-10 22:19:23 +03:00
tlsSessionStore : Object . keys ( tlsSessionStore ) . length ,
blockedUsers : obj . blockedUsers ,
blockedAgents : obj . blockedAgents
2019-05-02 01:02:03 +03:00
} ;
}
// Agent counters
obj . agentStats = {
createMeshAgentCount : 0 ,
2019-05-25 01:11:00 +03:00
agentClose : 0 ,
agentBinaryUpdate : 0 ,
2021-02-04 02:36:25 +03:00
agentMeshCoreBinaryUpdate : 0 ,
2019-05-02 01:02:03 +03:00
coreIsStableCount : 0 ,
verifiedAgentConnectionCount : 0 ,
clearingCoreCount : 0 ,
updatingCoreCount : 0 ,
recoveryCoreIsStableCount : 0 ,
meshDoesNotExistCount : 0 ,
invalidPkcsSignatureCount : 0 ,
invalidRsaSignatureCount : 0 ,
invalidJsonCount : 0 ,
unknownAgentActionCount : 0 ,
agentBadWebCertHashCount : 0 ,
agentBadSignature1Count : 0 ,
agentBadSignature2Count : 0 ,
agentMaxSessionHoldCount : 0 ,
invalidDomainMeshCount : 0 ,
invalidMeshTypeCount : 0 ,
invalidDomainMesh2Count : 0 ,
invalidMeshType2Count : 0 ,
2019-06-06 01:24:07 +03:00
duplicateAgentCount : 0 ,
2021-02-07 11:38:20 +03:00
maxDomainDevicesReached : 0 ,
agentInTrouble : 0 ,
agentInBigTrouble : 0
2019-05-02 01:02:03 +03:00
}
obj . getAgentStats = function ( ) { return obj . agentStats ; }
2021-05-05 12:24:23 +03:00
// Traffic counters
obj . trafficStats = {
httpRequestCount : 0 ,
2021-05-05 21:37:26 +03:00
httpWebSocketCount : 0 ,
httpIn : 0 ,
httpOut : 0 ,
2021-05-05 12:24:23 +03:00
relayCount : { } ,
relayIn : { } ,
relayOut : { } ,
localRelayCount : { } ,
localRelayIn : { } ,
2021-05-05 12:30:23 +03:00
localRelayOut : { } ,
AgentCtrlIn : 0 ,
2021-05-05 20:59:47 +03:00
AgentCtrlOut : 0 ,
LMSIn : 0 ,
LMSOut : 0 ,
CIRAIn : 0 ,
CIRAOut : 0
2021-05-05 12:24:23 +03:00
}
2021-05-06 00:15:26 +03:00
obj . trafficStats . time = Date . now ( ) ;
2021-05-05 12:24:23 +03:00
obj . getTrafficStats = function ( ) { return obj . trafficStats ; }
2021-05-06 00:15:26 +03:00
obj . getTrafficDelta = function ( oldTraffic ) { // Return the difference between the old and new data along with the delta time.
const data = obj . common . Clone ( obj . trafficStats ) ;
data . time = Date . now ( ) ;
const delta = calcDelta ( oldTraffic ? oldTraffic : { } , data ) ;
if ( oldTraffic && oldTraffic . time ) { delta . delta = ( data . time - oldTraffic . time ) ; }
delta . time = data . time ;
return { current : data , delta : delta }
}
function calcDelta ( oldData , newData ) { // Recursive function that computes the difference of all numbers
const r = { } ;
for ( var i in newData ) {
if ( typeof newData [ i ] == 'object' ) { r [ i ] = calcDelta ( oldData [ i ] ? oldData [ i ] : { } , newData [ i ] ) ; }
if ( typeof newData [ i ] == 'number' ) { if ( typeof oldData [ i ] == 'number' ) { r [ i ] = ( newData [ i ] - oldData [ i ] ) ; } else { r [ i ] = newData [ i ] ; } }
}
return r ;
}
2021-05-05 12:24:23 +03:00
2021-02-04 02:36:25 +03:00
// Keep a record of the last agent issues.
obj . getAgentIssues = function ( ) { return obj . agentIssues ; }
2021-06-15 18:12:45 +03:00
obj . setAgentIssue = function ( agent , issue ) { obj . agentIssues . push ( [ new Date ( ) . toLocaleString ( ) , agent . remoteaddrport , issue ] ) ; while ( obj . setAgentIssue . length > 50 ) { obj . agentIssues . shift ( ) ; } }
2021-02-04 02:36:25 +03:00
obj . agentIssues = [ ] ;
2017-08-28 19:27:45 +03:00
// Authenticate the user
obj . authenticate = function ( name , pass , domain , fn ) {
2019-01-16 05:21:03 +03:00
if ( ( typeof ( name ) != 'string' ) || ( typeof ( pass ) != 'string' ) || ( typeof ( domain ) != 'object' ) ) { fn ( new Error ( 'invalid fields' ) ) ; return ; }
2021-06-15 00:36:00 +03:00
if ( name . startsWith ( '~t:' ) ) {
// Login token, try to fetch the token from the database
obj . db . Get ( 'logintoken-' + name , function ( err , docs ) {
if ( err != null ) { fn ( err ) ; return ; }
if ( ( docs == null ) || ( docs . length != 1 ) ) { fn ( new Error ( 'login token not found' ) ) ; return ; }
const loginToken = docs [ 0 ] ;
if ( ( loginToken . expire != 0 ) && ( loginToken . expire < Date . now ( ) ) ) { fn ( new Error ( 'login token expired' ) ) ; return ; }
// Default strong password hashing (pbkdf2 SHA384)
require ( './pass' ) . hash ( pass , loginToken . salt , function ( err , hash , tag ) {
if ( err ) return fn ( err ) ;
if ( hash == loginToken . hash ) {
// Login username and password are valid.
var user = obj . users [ loginToken . userid ] ;
if ( ! user ) { fn ( new Error ( 'cannot find user' ) ) ; return ; }
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { fn ( 'locked' ) ; return ; }
2022-09-07 17:40:36 +03:00
// Successful login token authentication
2021-06-15 00:36:00 +03:00
var loginOptions = { tokenName : loginToken . name , tokenUser : loginToken . tokenUser } ;
if ( loginToken . expire != 0 ) { loginOptions . expire = loginToken . expire ; }
return fn ( null , user . _id , null , loginOptions ) ;
}
fn ( new Error ( 'invalid password' ) ) ;
} , 0 ) ;
} ) ;
} else if ( domain . auth == 'ldap' ) {
2022-07-19 23:50:40 +03:00
// This method will handle LDAP login
const ldapHandler = function ldapHandlerFunc ( err , xxuser ) {
2022-07-20 23:35:59 +03:00
if ( err ) { parent . debug ( 'ldap' , 'LDAP Error: ' + err ) ; if ( ldapHandlerFunc . ldapobj ) { try { ldapHandlerFunc . ldapobj . close ( ) ; } catch ( ex ) { console . log ( ex ) ; } } fn ( new Error ( 'invalid password' ) ) ; return ; }
2022-07-19 23:50:40 +03:00
// Save this LDAP user to file if needed
if ( typeof domain . ldapsaveusertofile == 'string' ) {
2024-03-04 03:03:27 +03:00
obj . fs . appendFile ( domain . ldapsaveusertofile , JSON . stringify ( xxuser ) + '\r\n\r\n' , function ( err ) { } ) ;
2022-07-19 23:50:40 +03:00
}
// Work on getting the userid for this LDAP user
var shortname = null ;
var username = xxuser [ 'displayName' ] ;
2022-07-20 10:50:32 +03:00
if ( typeof domain . ldapusername == 'string' ) {
if ( domain . ldapusername . indexOf ( '{{{' ) >= 0 ) { username = assembleStringFromObject ( domain . ldapusername , xxuser ) ; } else { username = xxuser [ domain . ldapusername ] ; }
2022-07-20 23:35:59 +03:00
} else { username = xxuser [ 'displayName' ] ? xxuser [ 'displayName' ] : xxuser [ 'name' ] ; }
2022-07-19 23:50:40 +03:00
if ( domain . ldapuserbinarykey ) {
// Use a binary key as the userid
if ( xxuser [ domain . ldapuserbinarykey ] ) { shortname = Buffer . from ( xxuser [ domain . ldapuserbinarykey ] , 'binary' ) . toString ( 'hex' ) . toLowerCase ( ) ; }
} else if ( domain . ldapuserkey ) {
// Use a string key as the userid
if ( xxuser [ domain . ldapuserkey ] ) { shortname = xxuser [ domain . ldapuserkey ] ; }
2019-04-11 23:41:51 +03:00
} else {
2022-07-19 23:50:40 +03:00
// Use the default key as the userid
2022-07-20 22:57:24 +03:00
if ( xxuser [ 'objectSid' ] ) { shortname = Buffer . from ( xxuser [ 'objectSid' ] , 'binary' ) . toString ( 'hex' ) . toLowerCase ( ) ; }
else if ( xxuser [ 'objectGUID' ] ) { shortname = Buffer . from ( xxuser [ 'objectGUID' ] , 'binary' ) . toString ( 'hex' ) . toLowerCase ( ) ; }
else if ( xxuser [ 'name' ] ) { shortname = xxuser [ 'name' ] ; }
else if ( xxuser [ 'cn' ] ) { shortname = xxuser [ 'cn' ] ; }
2022-07-19 23:50:40 +03:00
}
2022-07-20 22:57:24 +03:00
if ( shortname == null ) { fn ( new Error ( 'no user identifier' ) ) ; if ( ldapHandlerFunc . ldapobj ) { try { ldapHandlerFunc . ldapobj . close ( ) ; } catch ( ex ) { console . log ( ex ) ; } } return ; }
2022-07-19 23:50:40 +03:00
if ( username == null ) { username = shortname ; }
var userid = 'user/' + domain . id + '/' + shortname ;
2022-08-22 07:19:34 +03:00
// Get the list of groups this user is a member of.
var userMemberships = xxuser [ ( typeof domain . ldapusergroups == 'string' ) ? domain . ldapusergroups : 'memberOf' ] ;
if ( typeof userMemberships == 'string' ) { userMemberships = [ userMemberships ] ; }
if ( Array . isArray ( userMemberships ) == false ) { userMemberships = [ ] ; }
2022-08-22 00:05:51 +03:00
// See if the user is required to be part of an LDAP user group in order to log into this server.
2024-03-04 03:03:27 +03:00
if ( typeof domain . ldapuserrequiredgroupmembership == 'string' ) { domain . ldapuserrequiredgroupmembership = [ domain . ldapuserrequiredgroupmembership ] ; }
2022-09-06 10:30:07 +03:00
if ( Array . isArray ( domain . ldapuserrequiredgroupmembership ) ) {
2022-08-22 00:05:51 +03:00
// Look for a matching LDAP user group
var userMembershipMatch = false ;
for ( var i in domain . ldapuserrequiredgroupmembership ) { if ( userMemberships . indexOf ( domain . ldapuserrequiredgroupmembership [ i ] ) >= 0 ) { userMembershipMatch = true ; } }
2024-03-04 03:03:27 +03:00
if ( userMembershipMatch === false ) { parent . authLog ( 'ldapHandler' , 'LDAP denying login to a user that is not a member of a LDAP required group.' ) ; fn ( 'denied' ) ; return ; } // If there is no match, deny the login
2022-09-06 10:30:07 +03:00
}
// Check if user is in an site administrator group
var siteAdminGroup = null ;
2024-03-04 03:03:27 +03:00
if ( typeof domain . ldapsiteadmingroups == 'string' ) { domain . ldapsiteadmingroups = [ domain . ldapsiteadmingroups ] ; }
2022-09-06 10:30:07 +03:00
if ( Array . isArray ( domain . ldapsiteadmingroups ) ) {
siteAdminGroup = false ;
for ( var i in domain . ldapsiteadmingroups ) {
if ( userMemberships . indexOf ( domain . ldapsiteadmingroups [ i ] ) >= 0 ) { siteAdminGroup = domain . ldapsiteadmingroups [ i ] ; }
}
2022-08-22 00:05:51 +03:00
}
2022-08-22 21:43:45 +03:00
// See if we need to sync LDAP user memberships with user groups
if ( domain . ldapsyncwithusergroups === true ) { domain . ldapsyncwithusergroups = { } ; }
if ( typeof domain . ldapsyncwithusergroups == 'object' ) {
// LDAP user memberships sync is enabled, see if there are any filters to apply
if ( typeof domain . ldapsyncwithusergroups . filter == 'string' ) { domain . ldapsyncwithusergroups . filter = [ domain . ldapsyncwithusergroups . filter ] ; }
if ( Array . isArray ( domain . ldapsyncwithusergroups . filter ) ) {
const g = [ ] ;
for ( var i in userMemberships ) {
var match = false ;
for ( var j in domain . ldapsyncwithusergroups . filter ) {
if ( userMemberships [ i ] . indexOf ( domain . ldapsyncwithusergroups . filter [ j ] ) >= 0 ) { match = true ; }
}
if ( match ) { g . push ( userMemberships [ i ] ) ; }
}
userMemberships = g ;
}
} else {
// LDAP user memberships sync is disabled, sync the user with empty membership
userMemberships = [ ] ;
}
2022-07-20 10:10:09 +03:00
// Get the email address for this LDAP user
2022-07-19 23:50:40 +03:00
var email = null ;
2022-09-07 17:40:36 +03:00
if ( domain . ldapuseremail ) { email = xxuser [ domain . ldapuseremail ] ; } else if ( xxuser [ 'mail' ] ) { email = xxuser [ 'mail' ] ; } // Use given field name or default
2022-07-20 10:50:32 +03:00
if ( Array . isArray ( email ) ) { email = email [ 0 ] ; } // Mail may be multivalued in LDAP in which case, answer is an array. Use the 1st value.
2022-09-07 17:40:36 +03:00
if ( email ) { email = email . toLowerCase ( ) ; } // it seems some code elsewhere also lowercase the emailaddress, so let's be consistent.
2022-07-19 23:50:40 +03:00
2022-07-20 10:10:09 +03:00
// Get the real name for this LDAP user
2022-07-19 23:50:40 +03:00
var realname = null ;
2022-07-20 10:50:32 +03:00
if ( typeof domain . ldapuserrealname == 'string' ) {
if ( domain . ldapuserrealname . indexOf ( '{{{' ) >= 0 ) { realname = assembleStringFromObject ( domain . ldapuserrealname , xxuser ) ; } else { realname = xxuser [ domain . ldapuserrealname ] ; }
}
2022-07-20 10:10:09 +03:00
else { if ( typeof xxuser [ 'name' ] == 'string' ) { realname = xxuser [ 'name' ] ; } }
2022-07-19 23:50:40 +03:00
2022-07-20 10:10:09 +03:00
// Get the phone number for this LDAP user
2022-07-19 23:50:40 +03:00
var phonenumber = null ;
if ( domain . ldapuserphonenumber ) { phonenumber = xxuser [ domain . ldapuserphonenumber ] ; }
2022-07-20 10:10:09 +03:00
else { if ( typeof xxuser [ 'telephoneNumber' ] == 'string' ) { phonenumber = xxuser [ 'telephoneNumber' ] ; } }
2022-07-19 23:50:40 +03:00
// Work on getting the image of this LDAP user
2022-07-20 10:10:09 +03:00
var userimage = null , userImageBuffer = null ;
2022-07-20 22:57:24 +03:00
if ( xxuser . _raw ) { // Using _raw allows us to get data directly as buffer.
if ( domain . ldapuserimage && xxuser [ domain . ldapuserimage ] ) { userImageBuffer = xxuser . _raw [ domain . ldapuserimage ] ; }
else if ( xxuser [ 'thumbnailPhoto' ] ) { userImageBuffer = xxuser . _raw [ 'thumbnailPhoto' ] ; }
else if ( xxuser [ 'jpegPhoto' ] ) { userImageBuffer = xxuser . _raw [ 'jpegPhoto' ] ; }
if ( userImageBuffer != null ) {
if ( ( userImageBuffer [ 0 ] == 0xFF ) && ( userImageBuffer [ 1 ] == 0xD8 ) && ( userImageBuffer [ 2 ] == 0xFF ) && ( userImageBuffer [ 3 ] == 0xE0 ) ) { userimage = 'data:image/jpeg;base64,' + userImageBuffer . toString ( 'base64' ) ; }
if ( ( userImageBuffer [ 0 ] == 0x89 ) && ( userImageBuffer [ 1 ] == 0x50 ) && ( userImageBuffer [ 2 ] == 0x4E ) && ( userImageBuffer [ 3 ] == 0x47 ) ) { userimage = 'data:image/png;base64,' + userImageBuffer . toString ( 'base64' ) ; }
}
2022-07-19 23:50:40 +03:00
}
// Display user information extracted from LDAP data
2024-03-04 03:03:27 +03:00
parent . authLog ( 'ldapHandler' , 'LDAP user login, id: ' + shortname + ', username: ' + username + ', email: ' + email + ', realname: ' + realname + ', phone: ' + phonenumber + ', image: ' + ( userimage != null ) ) ;
2022-07-19 23:50:40 +03:00
// If there is a testing userid, use that
if ( ldapHandlerFunc . ldapShortName ) {
shortname = ldapHandlerFunc . ldapShortName ;
userid = 'user/' + domain . id + '/' + shortname ;
}
2022-07-19 02:12:53 +03:00
2022-07-20 10:10:09 +03:00
// Save the user image
if ( userimage != null ) { parent . db . Set ( { _id : 'im' + userid , image : userimage } ) ; } else { db . Remove ( 'im' + userid ) ; }
2022-07-20 22:57:24 +03:00
// Close the LDAP object
if ( ldapHandlerFunc . ldapobj ) { try { ldapHandlerFunc . ldapobj . close ( ) ; } catch ( ex ) { console . log ( ex ) ; } }
2022-07-19 23:50:40 +03:00
// Check if the user already exists
var user = obj . users [ userid ] ;
if ( user == null ) {
// This user does not exist, create a new account.
var user = { type : 'user' , _id : userid , name : username , creation : Math . floor ( Date . now ( ) / 1000 ) , login : Math . floor ( Date . now ( ) / 1000 ) , access : Math . floor ( Date . now ( ) / 1000 ) , domain : domain . id } ;
if ( email ) { user [ 'email' ] = email ; user [ 'emailVerified' ] = true ; }
if ( domain . newaccountsrights ) { user . siteadmin = domain . newaccountsrights ; }
if ( obj . common . validateStrArray ( domain . newaccountrealms ) ) { user . groups = domain . newaccountrealms ; }
var usercount = 0 ;
for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { usercount ++ ; } }
if ( usercount == 0 ) { user . siteadmin = 4294967295 ; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
// Auto-join any user groups
if ( typeof domain . newaccountsusergroups == 'object' ) {
for ( var i in domain . newaccountsusergroups ) {
var ugrpid = domain . newaccountsusergroups [ i ] ;
if ( ugrpid . indexOf ( '/' ) < 0 ) { ugrpid = 'ugrp/' + domain . id + '/' + ugrpid ; }
var ugroup = obj . userGroups [ ugrpid ] ;
if ( ugroup != null ) {
// Add group to the user
if ( user . links == null ) { user . links = { } ; }
user . links [ ugroup . _id ] = { rights : 1 } ;
// Add user to the group
ugroup . links [ user . _id ] = { userid : user . _id , name : user . name , rights : 1 } ;
db . Set ( ugroup ) ;
// Notify user group change
var event = { etype : 'ugrp' , ugrpid : ugroup . _id , name : ugroup . name , desc : ugroup . desc , action : 'usergroupchange' , links : ugroup . links , msgid : 71 , msgArgs : [ user . name , ugroup . name ] , msg : 'Added user ' + user . name + ' to user group ' + ugroup . name , addUserDomain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
parent . DispatchEvent ( [ '*' , ugroup . _id , user . _id ] , obj , event ) ;
2020-05-28 04:26:54 +03:00
}
}
2022-07-19 23:50:40 +03:00
}
2020-05-28 04:26:54 +03:00
2022-07-20 10:10:09 +03:00
// Check the user real name
if ( realname ) { user . realname = realname ; }
// Check the user phone number
if ( phonenumber ) { user . phone = phonenumber ; }
// Indicate that this user has a image
if ( userimage != null ) { user . flags = 1 ; }
2022-09-06 10:30:07 +03:00
// See if the user is a member of the site admin group.
if ( typeof siteAdminGroup === 'string' ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'ldapHandler' , ` LDAP: Granting site admin privilages to new user " ${ user . name } " found in admin group: ${ siteAdminGroup } ` ) ;
2022-09-06 10:30:07 +03:00
user . siteadmin = 0xFFFFFFFF ;
}
2022-09-04 22:18:53 +03:00
// Sync the user with LDAP matching user groups
2022-08-22 07:19:34 +03:00
if ( syncExternalUserGroups ( domain , user , userMemberships , 'ldap' ) == true ) { userChanged = true ; }
2022-07-19 23:50:40 +03:00
obj . users [ user . _id ] = user ;
obj . db . SetUser ( user ) ;
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountcreate' , msgid : 128 , msgArgs : [ user . name ] , msg : 'Account created, name is ' + user . name , domain : domain . id } ;
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , event ) ;
return fn ( null , user . _id ) ;
} else {
2022-07-20 10:10:09 +03:00
var userChanged = false ;
2022-07-19 23:50:40 +03:00
// This is an existing user
// If the display username has changes, update it.
2022-07-20 10:10:09 +03:00
if ( user . name != username ) { user . name = username ; userChanged = true ; }
2022-07-19 23:50:40 +03:00
// Check if user email has changed
if ( user . email && ! email ) { // email unset in ldap => unset
delete user . email ;
delete user . emailVerified ;
2022-07-20 10:10:09 +03:00
userChanged = true ;
2022-07-19 23:50:40 +03:00
} else if ( user . email != email ) { // update email
user [ 'email' ] = email ;
user [ 'emailVerified' ] = true ;
2022-07-20 10:10:09 +03:00
userChanged = true ;
2022-07-19 23:50:40 +03:00
}
2022-07-20 10:10:09 +03:00
// Check the user real name
if ( realname != user . realname ) { user . realname = realname ; userChanged = true ; }
// Check the user phone number
if ( phonenumber != user . phone ) { user . phone = phonenumber ; userChanged = true ; }
// Check the user image flag
if ( ( userimage != null ) && ( ( user . flags == null ) || ( ( user . flags & 1 ) == 0 ) ) ) { if ( user . flags == null ) { user . flags = 1 ; } else { user . flags += 1 ; } userChanged = true ; }
if ( ( userimage == null ) && ( user . flags != null ) && ( ( user . flags & 1 ) != 0 ) ) { if ( user . flags == 1 ) { delete user . flags ; } else { user . flags -= 1 ; } userChanged = true ; }
2022-09-06 10:30:07 +03:00
// See if the user is a member of the site admin group.
if ( ( typeof siteAdminGroup === 'string' ) && ( user . siteadmin !== 0xFFFFFFFF ) ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'ldapHandler' , ` LDAP: Granting site admin privilages to user " ${ user . name } " found in administrator group: ${ siteAdminGroup } ` ) ;
2022-09-06 10:30:07 +03:00
user . siteadmin = 0xFFFFFFFF ;
userChanged = true ;
} else if ( ( siteAdminGroup === false ) && ( user . siteadmin === 0xFFFFFFFF ) ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'ldapHandler' , ` LDAP: Revoking site admin privilages from user " ${ user . name } " since they are not found in any administrator groups. ` ) ;
2022-09-06 10:30:07 +03:00
delete user . siteadmin ;
userChanged = true ;
}
2022-08-22 07:19:34 +03:00
// Synd the user with LDAP matching user groups
if ( syncExternalUserGroups ( domain , user , userMemberships , 'ldap' ) == true ) { userChanged = true ; }
2022-07-20 10:10:09 +03:00
// If the user changed, save the changes to the database here
if ( userChanged ) {
2019-04-12 00:52:23 +03:00
obj . db . SetUser ( user ) ;
2022-07-20 10:10:09 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msgid : 154 , msg : 'Account changed to sync with LDAP data.' , domain : domain . id } ;
2022-07-19 23:50:40 +03:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
}
2022-07-20 10:10:09 +03:00
2022-07-19 23:50:40 +03:00
// If user is locker out, block here.
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { fn ( 'locked' ) ; return ; }
return fn ( null , user . _id ) ;
}
}
if ( domain . ldapoptions . url == 'test' ) {
// Test LDAP login
var xxuser = domain . ldapoptions [ name . toLowerCase ( ) ] ;
if ( xxuser == null ) { fn ( new Error ( 'invalid password' ) ) ; return ; } else {
ldapHandler . ldapShortName = name . toLowerCase ( ) ;
if ( typeof xxuser == 'string' ) {
2022-07-20 10:10:09 +03:00
// The test LDAP user points to a JSON file where the user information is, load it.
2022-07-19 23:50:40 +03:00
ldapHandler ( null , require ( xxuser ) ) ;
2019-04-12 00:52:23 +03:00
} else {
2022-07-20 10:10:09 +03:00
// The test user information is in the config.json, use it.
2022-07-19 23:50:40 +03:00
ldapHandler ( null , xxuser ) ;
2019-04-12 00:52:23 +03:00
}
2019-04-11 23:41:51 +03:00
}
2019-04-12 00:52:23 +03:00
} else {
// LDAP login
var LdapAuth = require ( 'ldapauth-fork' ) ;
2022-07-20 22:57:24 +03:00
if ( domain . ldapoptions == null ) { domain . ldapoptions = { } ; }
domain . ldapoptions . includeRaw = true ; // This allows us to get data as buffers which is useful for images.
2019-04-12 00:52:23 +03:00
var ldap = new LdapAuth ( domain . ldapoptions ) ;
2022-07-19 23:50:40 +03:00
ldapHandler . ldapobj = ldap ;
2022-07-20 23:35:59 +03:00
ldap . on ( 'error' , function ( err ) { parent . debug ( 'ldap' , 'LDAP OnError: ' + err ) ; try { ldap . close ( ) ; } catch ( ex ) { console . log ( ex ) ; } } ) ; // Close the LDAP object
2022-07-19 23:50:40 +03:00
ldap . authenticate ( name , pass , ldapHandler ) ;
2019-04-12 00:52:23 +03:00
}
2017-08-28 19:27:45 +03:00
} else {
2019-04-11 23:41:51 +03:00
// Regular login
var user = obj . users [ 'user/' + domain . id + '/' + name . toLowerCase ( ) ] ;
// Query the db for the given username
if ( ! user ) { fn ( new Error ( 'cannot find user' ) ) ; return ; }
// Apply the same algorithm to the POSTed password, applying the hash against the pass / salt, if there is a match we found the user
if ( user . salt == null ) {
fn ( new Error ( 'invalid password' ) ) ;
2017-11-03 04:44:27 +03:00
} else {
2019-04-11 23:41:51 +03:00
if ( user . passtype != null ) {
// IIS default clear or weak password hashing (SHA-1)
require ( './pass' ) . iishash ( user . passtype , pass , user . salt , function ( err , hash ) {
if ( err ) return fn ( err ) ;
if ( hash == user . hash ) {
// Update the password to the stronger format.
2019-05-15 00:39:26 +03:00
require ( './pass' ) . hash ( pass , function ( err , salt , hash , tag ) { if ( err ) throw err ; user . salt = salt ; user . hash = hash ; delete user . passtype ; obj . db . SetUser ( user ) ; } , 0 ) ;
2019-04-11 23:41:51 +03:00
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { fn ( 'locked' ) ; return ; }
return fn ( null , user . _id ) ;
}
fn ( new Error ( 'invalid password' ) , null , user . passhint ) ;
} ) ;
} else {
// Default strong password hashing (pbkdf2 SHA384)
2019-05-15 00:39:26 +03:00
require ( './pass' ) . hash ( pass , user . salt , function ( err , hash , tag ) {
2019-04-11 23:41:51 +03:00
if ( err ) return fn ( err ) ;
if ( hash == user . hash ) {
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { fn ( 'locked' ) ; return ; }
return fn ( null , user . _id ) ;
}
fn ( new Error ( 'invalid password' ) , null , user . passhint ) ;
2019-05-15 00:39:26 +03:00
} , 0 ) ;
2019-04-11 23:41:51 +03:00
}
2017-11-03 04:44:27 +03:00
}
2017-08-28 19:27:45 +03:00
}
2018-08-30 22:05:23 +03:00
} ;
2017-11-03 04:44:27 +03:00
2017-08-28 19:27:45 +03:00
/ *
obj . restrict = function ( req , res , next ) {
console . log ( 'restrict' , req . url ) ;
var domain = getDomain ( req ) ;
if ( req . session . userid ) {
next ( ) ;
} else {
2019-11-18 01:33:29 +03:00
req . session . messageid = 111 ; // Access denied.
2017-08-28 19:27:45 +03:00
res . redirect ( domain . url + 'login' ) ;
}
2018-08-30 22:05:23 +03:00
} ;
2017-08-28 19:27:45 +03:00
* /
2019-01-31 00:43:42 +03:00
// Check if the source IP address is in the IP list, return false if not.
2021-10-14 05:12:49 +03:00
function checkIpAddressEx ( req , res , ipList , closeIfThis , redirectUrl ) {
2017-09-27 22:43:20 +03:00
try {
2020-05-22 07:25:11 +03:00
if ( req . connection ) {
// HTTP(S) request
2021-10-14 05:12:49 +03:00
if ( req . clientIp ) { for ( var i = 0 ; i < ipList . length ; i ++ ) { if ( require ( 'ipcheck' ) . match ( req . clientIp , ipList [ i ] ) ) { if ( closeIfThis === true ) { if ( typeof redirectUrl == 'string' ) { res . redirect ( redirectUrl ) ; } else { res . sendStatus ( 401 ) ; } } return true ; } } }
if ( closeIfThis === false ) { if ( typeof redirectUrl == 'string' ) { res . redirect ( redirectUrl ) ; } else { res . sendStatus ( 401 ) ; } }
2020-05-22 07:25:11 +03:00
} else {
// WebSocket request
if ( res . clientIp ) { for ( var i = 0 ; i < ipList . length ; i ++ ) { if ( require ( 'ipcheck' ) . match ( res . clientIp , ipList [ i ] ) ) { if ( closeIfThis === true ) { try { req . close ( ) ; } catch ( e ) { } } return true ; } } }
2019-01-31 00:43:42 +03:00
if ( closeIfThis === false ) { try { req . close ( ) ; } catch ( e ) { } }
2019-10-04 22:18:56 +03:00
}
2019-01-31 00:43:42 +03:00
} catch ( e ) { console . log ( e ) ; } // Should never happen
2017-09-27 22:43:20 +03:00
return false ;
}
2017-10-03 00:12:29 +03:00
// Check if the source IP address is allowed, return domain if allowed
2020-03-26 22:06:34 +03:00
// If there is a fail and null is returned, the request or connection is closed already.
2019-01-05 23:04:18 +03:00
function checkUserIpAddress ( req , res ) {
2021-10-14 05:12:49 +03:00
if ( ( parent . config . settings . userblockedip != null ) && ( checkIpAddressEx ( req , res , parent . config . settings . userblockedip , true , parent . config . settings . ipblockeduserredirect ) == true ) ) { obj . blockedUsers ++ ; return null ; }
if ( ( parent . config . settings . userallowedip != null ) && ( checkIpAddressEx ( req , res , parent . config . settings . userallowedip , false , parent . config . settings . ipblockeduserredirect ) == false ) ) { obj . blockedUsers ++ ; return null ; }
2019-01-05 23:04:18 +03:00
const domain = ( req . url ? getDomain ( req ) : getDomain ( res ) ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { parent . debug ( 'web' , 'handleRootRequest: invalid domain.' ) ; try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2021-10-14 05:12:49 +03:00
if ( ( domain . userblockedip != null ) && ( checkIpAddressEx ( req , res , domain . userblockedip , true , domain . ipblockeduserredirect ) == true ) ) { obj . blockedUsers ++ ; return null ; }
if ( ( domain . userallowedip != null ) && ( checkIpAddressEx ( req , res , domain . userallowedip , false , domain . ipblockeduserredirect ) == false ) ) { obj . blockedUsers ++ ; return null ; }
2019-01-31 00:43:42 +03:00
return domain ;
}
// Check if the source IP address is allowed, return domain if allowed
2020-03-26 22:06:34 +03:00
// If there is a fail and null is returned, the request or connection is closed already.
2019-01-31 00:43:42 +03:00
function checkAgentIpAddress ( req , res ) {
2020-03-26 22:06:34 +03:00
if ( ( parent . config . settings . agentblockedip != null ) && ( checkIpAddressEx ( req , res , parent . config . settings . agentblockedip , null ) == true ) ) { obj . blockedAgents ++ ; return null ; }
if ( ( parent . config . settings . agentallowedip != null ) && ( checkIpAddressEx ( req , res , parent . config . settings . agentallowedip , null ) == false ) ) { obj . blockedAgents ++ ; return null ; }
2019-01-31 00:43:42 +03:00
const domain = ( req . url ? getDomain ( req ) : getDomain ( res ) ) ;
2020-01-10 22:19:23 +03:00
if ( ( domain . agentblockedip != null ) && ( checkIpAddressEx ( req , res , domain . agentblockedip , null ) == true ) ) { obj . blockedAgents ++ ; return null ; }
if ( ( domain . agentallowedip != null ) && ( checkIpAddressEx ( req , res , domain . agentallowedip , null ) == false ) ) { obj . blockedAgents ++ ; return null ; }
2017-10-03 00:12:29 +03:00
return domain ;
}
2017-08-28 19:27:45 +03:00
// Return the current domain of the request
2020-03-26 22:06:34 +03:00
// Request or connection says open regardless of the response
2017-08-28 19:27:45 +03:00
function getDomain ( req ) {
2018-01-10 07:13:41 +03:00
if ( req . xdomain != null ) { return req . xdomain ; } // Domain already set for this request, return it.
2022-07-15 01:18:41 +03:00
if ( ( req . hostname == 'localhost' ) && ( req . query . domainid != null ) ) { const d = parent . config . domains [ req . query . domainid ] ; if ( d != null ) return d ; } // This is a localhost access with the domainid specified in the URL
if ( req . hostname != null ) { const d = obj . dnsDomains [ req . hostname . toLowerCase ( ) ] ; if ( d != null ) return d ; } // If this is a DNS name domain, return it here.
const x = req . url . split ( '/' ) ;
2017-08-28 19:27:45 +03:00
if ( x . length < 2 ) return parent . config . domains [ '' ] ;
2022-07-15 01:18:41 +03:00
const y = parent . config . domains [ x [ 1 ] . toLowerCase ( ) ] ;
2019-01-05 02:29:27 +03:00
if ( ( y != null ) && ( y . dns == null ) ) { return parent . config . domains [ x [ 1 ] . toLowerCase ( ) ] ; }
2017-08-28 19:27:45 +03:00
return parent . config . domains [ '' ] ;
}
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
function handleLogoutRequest ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
if ( domain . auth == 'sspi' ) { parent . debug ( 'web' , 'handleLogoutRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-16 23:04:48 +03:00
2022-05-19 04:01:34 +03:00
// If a HTTP header is required, check new UserRequiredHttpHeader
if ( domain . userrequiredhttpheader && ( typeof domain . userrequiredhttpheader == 'object' ) ) { var ok = false ; for ( var i in req . headers ) { if ( domain . userrequiredhttpheader [ i . toLowerCase ( ) ] == req . headers [ i ] ) { ok = true ; } } if ( ok == false ) { res . sendStatus ( 404 ) ; return ; } }
2020-10-02 23:17:53 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2017-08-28 19:27:45 +03:00
// Destroy the user's session to log them out will be re-created next request
2021-10-17 09:55:34 +03:00
var userid = req . session . userid ;
2017-08-28 19:27:45 +03:00
if ( req . session . userid ) {
2018-08-30 22:05:23 +03:00
var user = obj . users [ req . session . userid ] ;
2022-09-02 08:06:08 +03:00
if ( user != null ) {
obj . parent . authLog ( 'https' , 'User ' + user . name + ' logout from ' + req . clientIp + ' port ' + req . connection . remotePort , { sessionid : req . session . x , useragent : req . headers [ 'user-agent' ] } ) ;
obj . parent . DispatchEvent ( [ '*' ] , obj , { etype : 'user' , userid : user . _id , username : user . name , action : 'logout' , msgid : 2 , msg : 'Account logout' , domain : domain . id } ) ;
}
2022-01-07 02:05:45 +03:00
if ( req . session . x ) { clearDestroyedSessions ( ) ; obj . destroyedSessions [ req . session . userid + '/' + req . session . x ] = Date . now ( ) ; } // Destroy this session
2017-08-28 19:27:45 +03:00
}
2018-08-23 02:18:01 +03:00
req . session = null ;
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleLogoutRequest: success.' ) ;
2021-10-17 09:55:34 +03:00
// If this user was logged in using an authentication strategy and there is a logout URL, use it.
2024-03-04 03:03:27 +03:00
if ( ( userid != null ) && ( domain . authstrategies ? . authStrategyFlags != null ) ) {
let logouturl = null ;
let userStrategy = ( ( userid . split ( '/' ) [ 2 ] ) . split ( ':' ) [ 0 ] ) . substring ( 1 ) ;
// Setup logout url for oidc
if ( userStrategy == 'oidc' && domain . authstrategies . oidc != null ) {
if ( typeof domain . authstrategies . oidc . logouturl == 'string' ) {
logouturl = domain . authstrategies . oidc . logouturl ;
} else if ( typeof domain . authstrategies . oidc . issuer . end _session _endpoint == 'string' && typeof domain . authstrategies . oidc . client . post _logout _redirect _uri == 'string' ) {
logouturl = domain . authstrategies . oidc . issuer . end _session _endpoint + '?post_logout_redirect_uri=' + domain . authstrategies . oidc . client . post _logout _redirect _uri ;
} else if ( typeof domain . authstrategies . oidc . issuer . end _session _endpoint == 'string' ) {
logouturl = domain . authstrategies . oidc . issuer . end _session _endpoint ;
}
// Log out all other strategies
} else if ( ( domain . authstrategies [ userStrategy ] != null ) && ( typeof domain . authstrategies [ userStrategy ] . logouturl == 'string' ) ) { logouturl = domain . authstrategies [ userStrategy ] . logouturl ; }
// If custom logout was setup, use it
if ( logouturl != null ) {
parent . authLog ( 'handleLogoutRequest' , userStrategy . toUpperCase ( ) + ': LOGOUT: ' + logouturl ) ;
res . redirect ( logouturl ) ;
return ;
}
2021-10-17 09:55:34 +03:00
}
// This is the default logout redirect to the login page
2024-09-27 07:09:34 +03:00
if ( req . query . key != null ) { res . redirect ( domain . url + 'login?key=' + encodeURIComponent ( req . query . key ) ) ; } else { res . redirect ( domain . url + 'login' ) ; }
2017-08-28 19:27:45 +03:00
}
2018-05-24 07:43:44 +03:00
2022-03-05 02:56:19 +03:00
// Return an object with 2FA type if 2-step auth can be skipped
function checkUserOneTimePasswordSkip ( domain , user , req , loginOptions ) {
if ( parent . config . settings . no2factorauth == true ) return null ;
2022-09-07 17:40:36 +03:00
// If this login occurred using a login token, no 2FA needed.
2022-03-05 02:56:19 +03:00
if ( ( loginOptions != null ) && ( typeof loginOptions . tokenName === 'string' ) ) { return { twoFactorType : 'tokenlogin' } ; }
// Check if we can skip 2nd factor auth because of the source IP address
if ( ( req != null ) && ( req . clientIp != null ) && ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . skip2factor != null ) ) {
for ( var i in domain . passwordrequirements . skip2factor ) { if ( require ( 'ipcheck' ) . match ( req . clientIp , domain . passwordrequirements . skip2factor [ i ] ) === true ) { return { twoFactorType : 'ipaddr' } ; } }
}
// Check if a 2nd factor cookie is present
if ( typeof req . headers . cookie == 'string' ) {
const cookies = req . headers . cookie . split ( '; ' ) ;
for ( var i in cookies ) {
if ( cookies [ i ] . startsWith ( 'twofactor=' ) ) {
2022-09-07 17:40:36 +03:00
var twoFactorCookie = obj . parent . decodeCookie ( decodeURIComponent ( cookies [ i ] . substring ( 10 ) ) , obj . parent . loginCookieEncryptionKey , ( 30 * 24 * 60 ) ) ; // If the cookies does not have an expire field, assume 30 day timeout.
2022-04-10 03:12:52 +03:00
if ( ( twoFactorCookie != null ) && ( ( twoFactorCookie . ip == null ) || checkCookieIp ( twoFactorCookie . ip , req . clientIp ) ) && ( twoFactorCookie . userid == user . _id ) ) { return { twoFactorType : 'cookie' } ; }
2022-03-05 02:56:19 +03:00
}
}
}
return null ;
}
2019-02-08 09:30:33 +03:00
// Return true if this user has 2-step auth active
2021-04-16 22:42:54 +03:00
function checkUserOneTimePasswordRequired ( domain , user , req , loginOptions ) {
2022-09-07 17:40:36 +03:00
// If this login occurred using a login token, no 2FA needed.
2021-04-16 22:42:54 +03:00
if ( ( loginOptions != null ) && ( typeof loginOptions . tokenName === 'string' ) ) { return false ; }
2019-11-29 07:57:34 +03:00
// Check if we can skip 2nd factor auth because of the source IP address
2020-05-22 07:25:11 +03:00
if ( ( req != null ) && ( req . clientIp != null ) && ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . skip2factor != null ) ) {
for ( var i in domain . passwordrequirements . skip2factor ) { if ( require ( 'ipcheck' ) . match ( req . clientIp , domain . passwordrequirements . skip2factor [ i ] ) === true ) return false ; }
2019-11-29 07:57:34 +03:00
}
2019-11-29 09:29:22 +03:00
// Check if a 2nd factor cookie is present
if ( typeof req . headers . cookie == 'string' ) {
const cookies = req . headers . cookie . split ( '; ' ) ;
for ( var i in cookies ) {
if ( cookies [ i ] . startsWith ( 'twofactor=' ) ) {
2022-09-07 17:40:36 +03:00
var twoFactorCookie = obj . parent . decodeCookie ( decodeURIComponent ( cookies [ i ] . substring ( 10 ) ) , obj . parent . loginCookieEncryptionKey , ( 30 * 24 * 60 ) ) ; // If the cookies does not have an expire field, assume 30 day timeout.
2022-04-10 03:12:52 +03:00
if ( ( twoFactorCookie != null ) && ( ( twoFactorCookie . ip == null ) || checkCookieIp ( twoFactorCookie . ip , req . clientIp ) ) && ( twoFactorCookie . userid == user . _id ) ) { return false ; }
2019-11-29 09:29:22 +03:00
}
}
}
2020-04-23 01:29:26 +03:00
// See if SMS 2FA is available
var sms2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) && ( parent . smsserver != null ) && ( user . phone != null ) ) ;
2022-10-22 22:59:12 +03:00
// See if Messenger 2FA is available
var msg2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( user . msghandle != null ) ) ;
2019-11-29 07:57:34 +03:00
// Check if a 2nd factor is present
2022-10-22 22:59:12 +03:00
return ( ( parent . config . settings . no2factorauth !== true ) && ( msg2fa || sms2fa || ( user . otpsecret != null ) || ( ( user . email != null ) && ( user . emailVerified == true ) && ( domain . mailserver != null ) && ( user . otpekey != null ) ) || ( ( user . otphkeys != null ) && ( user . otphkeys . length > 0 ) ) ) ) ;
2019-02-08 09:30:33 +03:00
}
// Check the 2-step auth token
2019-02-11 03:04:36 +03:00
function checkUserOneTimePassword ( req , domain , user , token , hwtoken , func ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'checkUserOneTimePassword()' ) ;
2019-03-27 00:11:51 +03:00
const twoStepLoginSupported = ( ( domain . auth != 'sspi' ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . nousers !== true ) && ( parent . config . settings . no2factorauth !== true ) ) ;
2019-08-24 01:45:36 +03:00
if ( twoStepLoginSupported == false ) { parent . debug ( 'web' , 'checkUserOneTimePassword: not supported.' ) ; func ( true ) ; return ; } ;
2019-02-08 09:30:33 +03:00
2020-03-14 06:39:21 +03:00
// Check if we can use OTP tokens with email
2021-02-10 22:28:21 +03:00
var otpemail = ( domain . mailserver != null ) ;
2020-03-15 01:03:50 +03:00
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . email2factor == false ) ) { otpemail = false ; }
2020-04-23 01:29:26 +03:00
var otpsms = ( parent . smsserver != null ) ;
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . sms2factor == false ) ) { otpsms = false ; }
2022-10-22 22:59:12 +03:00
var otpmsg = ( ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) ) ;
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . msg2factor == false ) ) { otpmsg = false ; }
2020-03-14 06:39:21 +03:00
2020-09-17 02:16:58 +03:00
// Check 2FA login cookie
2020-09-17 20:42:09 +03:00
if ( ( token != null ) && ( token . startsWith ( 'cookie=' ) ) ) {
2022-09-07 17:40:36 +03:00
var twoFactorCookie = obj . parent . decodeCookie ( decodeURIComponent ( token . substring ( 7 ) ) , obj . parent . loginCookieEncryptionKey , ( 30 * 24 * 60 ) ) ; // If the cookies does not have an expire field, assume 30 day timeout.
2022-04-10 03:12:52 +03:00
if ( ( twoFactorCookie != null ) && ( ( twoFactorCookie . ip == null ) || checkCookieIp ( twoFactorCookie . ip , req . clientIp ) ) && ( twoFactorCookie . userid == user . _id ) ) { func ( true , { twoFactorType : 'cookie' } ) ; return ; }
2020-09-17 02:16:58 +03:00
}
2020-03-14 06:39:21 +03:00
// Check email key
if ( ( otpemail ) && ( user . otpekey != null ) && ( user . otpekey . d != null ) && ( user . otpekey . k === token ) ) {
var deltaTime = ( Date . now ( ) - user . otpekey . d ) ;
if ( ( deltaTime > 0 ) && ( deltaTime < 300000 ) ) { // Allow 5 minutes to use the email token (10000 * 60 * 5).
user . otpekey = { } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'checkUserOneTimePassword: success (email).' ) ;
2022-03-05 02:56:19 +03:00
func ( true , { twoFactorType : 'email' } ) ;
2020-03-14 06:39:21 +03:00
return ;
}
}
2022-10-22 22:59:12 +03:00
// Check SMS key
2020-04-23 01:29:26 +03:00
if ( ( otpsms ) && ( user . phone != null ) && ( user . otpsms != null ) && ( user . otpsms . d != null ) && ( user . otpsms . k === token ) ) {
var deltaTime = ( Date . now ( ) - user . otpsms . d ) ;
if ( ( deltaTime > 0 ) && ( deltaTime < 300000 ) ) { // Allow 5 minutes to use the SMS token (10000 * 60 * 5).
delete user . otpsms ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'checkUserOneTimePassword: success (SMS).' ) ;
2022-03-05 02:56:19 +03:00
func ( true , { twoFactorType : 'sms' } ) ;
2020-04-23 01:29:26 +03:00
return ;
}
}
2022-10-22 22:59:12 +03:00
// Check messenger key
if ( ( otpmsg ) && ( user . msghandle != null ) && ( user . otpmsg != null ) && ( user . otpmsg . d != null ) && ( user . otpmsg . k === token ) ) {
var deltaTime = ( Date . now ( ) - user . otpmsg . d ) ;
if ( ( deltaTime > 0 ) && ( deltaTime < 300000 ) ) { // Allow 5 minutes to use the Messenger token (10000 * 60 * 5).
delete user . otpmsg ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'checkUserOneTimePassword: success (Messenger).' ) ;
func ( true , { twoFactorType : 'messenger' } ) ;
return ;
}
}
2019-03-23 22:14:53 +03:00
// Check hardware key
2019-02-11 03:04:36 +03:00
if ( user . otphkeys && ( user . otphkeys . length > 0 ) && ( typeof ( hwtoken ) == 'string' ) && ( hwtoken . length > 0 ) ) {
2019-03-23 22:14:53 +03:00
var authResponse = null ;
try { authResponse = JSON . parse ( hwtoken ) ; } catch ( ex ) { }
2019-05-17 00:55:07 +03:00
if ( ( authResponse != null ) && ( authResponse . clientDataJSON ) ) {
// Get all WebAuthn keys
var webAuthnKeys = [ ] ;
for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( user . otphkeys [ i ] . type == 3 ) { webAuthnKeys . push ( user . otphkeys [ i ] ) ; } }
if ( webAuthnKeys . length > 0 ) {
// Decode authentication response
var clientAssertionResponse = { response : { } } ;
clientAssertionResponse . id = authResponse . id ;
clientAssertionResponse . rawId = Buffer . from ( authResponse . id , 'base64' ) ;
clientAssertionResponse . response . authenticatorData = Buffer . from ( authResponse . authenticatorData , 'base64' ) ;
clientAssertionResponse . response . clientDataJSON = Buffer . from ( authResponse . clientDataJSON , 'base64' ) ;
clientAssertionResponse . response . signature = Buffer . from ( authResponse . signature , 'base64' ) ;
clientAssertionResponse . response . userHandle = Buffer . from ( authResponse . userHandle , 'base64' ) ;
// Look for the key with clientAssertionResponse.id
var webAuthnKey = null ;
for ( var i = 0 ; i < webAuthnKeys . length ; i ++ ) { if ( webAuthnKeys [ i ] . keyId == clientAssertionResponse . id ) { webAuthnKey = webAuthnKeys [ i ] ; } }
// If we found a valid key to use, let's validate the response
if ( webAuthnKey != null ) {
// Figure out the origin
var httpport = ( ( args . aliasport != null ) ? args . aliasport : args . port ) ;
2019-12-21 01:50:06 +03:00
var origin = 'https://' + ( domain . dns ? domain . dns : parent . certificates . CommonName ) ;
2019-05-17 00:55:07 +03:00
if ( httpport != 443 ) { origin += ':' + httpport ; }
2022-07-22 03:17:08 +03:00
var u2fchallenge = null ;
if ( ( req . session != null ) && ( req . session . e != null ) ) { const sec = parent . decryptSessionData ( req . session . e ) ; if ( sec != null ) { u2fchallenge = sec . u2f ; } }
2019-05-17 00:55:07 +03:00
var assertionExpectations = {
2022-07-22 03:17:08 +03:00
challenge : u2fchallenge ,
2019-05-17 00:55:07 +03:00
origin : origin ,
2019-12-21 01:50:06 +03:00
factor : 'either' ,
fmt : 'fido-u2f' ,
2019-05-17 00:55:07 +03:00
publicKey : webAuthnKey . publicKey ,
prevCounter : webAuthnKey . counter ,
2019-12-07 03:49:40 +03:00
userHandle : Buffer . from ( user . _id , 'binary' ) . toString ( 'base64' )
2019-05-17 00:55:07 +03:00
} ;
var webauthnResponse = null ;
2019-08-24 01:45:36 +03:00
try { webauthnResponse = obj . webauthn . verifyAuthenticatorAssertionResponse ( clientAssertionResponse . response , assertionExpectations ) ; } catch ( ex ) { parent . debug ( 'web' , 'checkUserOneTimePassword: exception ' + ex ) ; console . log ( ex ) ; }
2019-05-17 00:55:07 +03:00
if ( ( webauthnResponse != null ) && ( webauthnResponse . verified === true ) ) {
// Update the hardware key counter and accept the 2nd factor
webAuthnKey . counter = webauthnResponse . counter ;
obj . db . SetUser ( user ) ;
2020-03-14 06:39:21 +03:00
parent . debug ( 'web' , 'checkUserOneTimePassword: success (hardware).' ) ;
2022-03-05 02:56:19 +03:00
func ( true , { twoFactorType : 'fido' } ) ;
2019-05-17 00:55:07 +03:00
} else {
2020-03-14 06:39:21 +03:00
parent . debug ( 'web' , 'checkUserOneTimePassword: fail (hardware).' ) ;
2019-05-17 00:55:07 +03:00
func ( false ) ;
2019-03-26 00:43:58 +03:00
}
2019-03-23 22:14:53 +03:00
return ;
}
2019-02-08 20:24:00 +03:00
}
2019-02-08 09:30:33 +03:00
}
}
// Check Google Authenticator
const otplib = require ( 'otplib' )
2019-02-11 03:04:36 +03:00
otplib . authenticator . options = { window : 2 } ; // Set +/- 1 minute window
2020-03-14 06:39:21 +03:00
if ( user . otpsecret && ( typeof ( token ) == 'string' ) && ( token . length == 6 ) && ( otplib . authenticator . check ( token , user . otpsecret ) == true ) ) {
parent . debug ( 'web' , 'checkUserOneTimePassword: success (authenticator).' ) ;
2022-03-05 02:56:19 +03:00
func ( true , { twoFactorType : 'otp' } ) ;
2020-03-14 06:39:21 +03:00
return ;
} ;
2019-02-08 09:30:33 +03:00
// Check written down keys
2019-02-08 20:24:00 +03:00
if ( ( user . otpkeys != null ) && ( user . otpkeys . keys != null ) && ( typeof ( token ) == 'string' ) && ( token . length == 8 ) ) {
2019-02-08 09:30:33 +03:00
var tokenNumber = parseInt ( token ) ;
2020-03-14 06:39:21 +03:00
for ( var i = 0 ; i < user . otpkeys . keys . length ; i ++ ) {
if ( ( tokenNumber === user . otpkeys . keys [ i ] . p ) && ( user . otpkeys . keys [ i ] . u === true ) ) {
parent . debug ( 'web' , 'checkUserOneTimePassword: success (one-time).' ) ;
2022-03-05 02:56:19 +03:00
user . otpkeys . keys [ i ] . u = false ; func ( true , { twoFactorType : 'backup' } ) ; return ;
2020-03-14 06:39:21 +03:00
}
}
2019-02-08 09:30:33 +03:00
}
2022-03-05 02:56:19 +03:00
// Check OTP hardware key (Yubikey OTP)
2019-02-09 01:46:07 +03:00
if ( ( domain . yubikey != null ) && ( domain . yubikey . id != null ) && ( domain . yubikey . secret != null ) && ( user . otphkeys != null ) && ( user . otphkeys . length > 0 ) && ( typeof ( token ) == 'string' ) && ( token . length == 44 ) ) {
2019-02-08 20:24:00 +03:00
var keyId = token . substring ( 0 , 12 ) ;
2019-02-11 07:13:03 +03:00
// Find a matching OTP key
2019-02-08 20:24:00 +03:00
var match = false ;
for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( ( user . otphkeys [ i ] . type === 2 ) && ( user . otphkeys [ i ] . keyid === keyId ) ) { match = true ; } }
// If we have a match, check the OTP
if ( match === true ) {
var yubikeyotp = require ( 'yubikeyotp' ) ;
var request = { otp : token , id : domain . yubikey . id , key : domain . yubikey . secret , timestamp : true }
if ( domain . yubikey . proxy ) { request . requestParams = { proxy : domain . yubikey . proxy } ; }
2020-03-14 06:39:21 +03:00
yubikeyotp . verifyOTP ( request , function ( err , results ) {
if ( ( results != null ) && ( results . status == 'OK' ) ) {
parent . debug ( 'web' , 'checkUserOneTimePassword: success (Yubikey).' ) ;
2022-03-05 02:56:19 +03:00
func ( true , { twoFactorType : 'hwotp' } ) ;
2020-03-14 06:39:21 +03:00
} else {
parent . debug ( 'web' , 'checkUserOneTimePassword: fail (Yubikey).' ) ;
func ( false ) ;
}
} ) ;
2019-02-08 20:24:00 +03:00
return ;
}
}
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'checkUserOneTimePassword: fail (2).' ) ;
2019-02-08 20:24:00 +03:00
func ( false ) ;
2019-02-08 09:30:33 +03:00
}
2019-02-08 20:24:00 +03:00
// Return a U2F hardware key challenge
2019-02-11 03:04:36 +03:00
function getHardwareKeyChallenge ( req , domain , user , func ) {
2022-07-31 21:25:28 +03:00
var sec = { } ;
if ( req . session == null ) { req . session = { } ; } else { try { sec = parent . decryptSessionData ( req . session . e ) ; } catch ( ex ) { } }
2022-07-13 03:45:19 +03:00
2019-02-08 09:30:33 +03:00
if ( user . otphkeys && ( user . otphkeys . length > 0 ) ) {
2019-03-23 08:33:53 +03:00
// Get all WebAuthn keys
2019-05-17 00:55:07 +03:00
var webAuthnKeys = [ ] ;
for ( var i = 0 ; i < user . otphkeys . length ; i ++ ) { if ( user . otphkeys [ i ] . type == 3 ) { webAuthnKeys . push ( user . otphkeys [ i ] ) ; } }
if ( webAuthnKeys . length > 0 ) {
// Generate a Webauthn challenge, this is really easy, no need to call any modules to do this.
var authnOptions = { type : 'webAuthn' , keyIds : [ ] , timeout : 60000 , challenge : obj . crypto . randomBytes ( 64 ) . toString ( 'base64' ) } ;
for ( var i = 0 ; i < webAuthnKeys . length ; i ++ ) { authnOptions . keyIds . push ( webAuthnKeys [ i ] . keyId ) ; }
2022-07-13 03:45:19 +03:00
sec . u2f = authnOptions . challenge ;
req . session . e = parent . encryptSessionData ( sec ) ;
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'getHardwareKeyChallenge: success' ) ;
2019-05-17 00:55:07 +03:00
func ( JSON . stringify ( authnOptions ) ) ;
2019-05-22 00:19:32 +03:00
return ;
2019-02-08 20:24:00 +03:00
}
2019-02-08 09:30:33 +03:00
}
2022-07-13 03:45:19 +03:00
2022-07-31 21:25:28 +03:00
// Remove the challenge if present
2022-07-13 03:45:19 +03:00
if ( sec . u2f != null ) { delete sec . u2f ; req . session . e = parent . encryptSessionData ( sec ) ; }
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'getHardwareKeyChallenge: fail' ) ;
2019-05-22 00:19:32 +03:00
func ( '' ) ;
2019-02-08 09:30:33 +03:00
}
2020-12-30 02:13:09 +03:00
// Redirect a root request to a different page
function handleRootRedirect ( req , res , direct ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
res . redirect ( domain . rootredirect + getQueryPortion ( req ) ) ;
}
2019-08-26 22:20:24 +03:00
function handleLoginRequest ( req , res , direct ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2022-07-07 09:39:36 +03:00
if ( req . body == null ) { res . sendStatus ( 404 ) ; return ; } // Post body is empty or can't be parsed
2022-07-22 03:17:08 +03:00
if ( req . session == null ) { req . session = { } ; }
2019-01-16 05:21:03 +03:00
2019-11-19 04:31:42 +03:00
// Check if this is a banned ip address
if ( obj . checkAllowLogin ( req ) == false ) {
// Wait and redirect the user
setTimeout ( function ( ) {
req . session . messageid = 114 ; // IP address blocked, try again later.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
} , 2000 + ( obj . crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % 4095 ) ) ;
return ;
}
2019-01-16 05:21:03 +03:00
// Normally, use the body username/password. If this is a token, use the username/password in the session.
var xusername = req . body . username , xpassword = req . body . password ;
2022-07-13 03:45:19 +03:00
if ( ( xusername == null ) && ( xpassword == null ) && ( req . body . token != null ) ) {
const sec = parent . decryptSessionData ( req . session . e ) ;
xusername = sec . tuser ; xpassword = sec . tpass ;
}
2019-01-16 05:21:03 +03:00
// Authenticate the user
2021-04-16 22:42:54 +03:00
obj . authenticate ( xusername , xpassword , domain , function ( err , userid , passhint , loginOptions ) {
2017-08-28 19:27:45 +03:00
if ( userid ) {
var user = obj . users [ userid ] ;
2017-12-14 01:52:57 +03:00
2020-11-21 03:43:58 +03:00
// Check if we are in maintenance mode
if ( ( parent . config . settings . maintenancemode != null ) && ( user . siteadmin != 4294967295 ) ) {
req . session . messageid = 115 ; // Server under maintenance
2021-05-12 20:34:04 +03:00
req . session . loginmode = 1 ;
2020-11-21 03:43:58 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2021-02-10 22:28:21 +03:00
var email2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( domain . mailserver != null ) && ( user . email != null ) && ( user . emailVerified == true ) && ( user . otpekey != null ) ) ;
2020-04-23 01:29:26 +03:00
var sms2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) && ( parent . smsserver != null ) && ( user . phone != null ) ) ;
2022-10-22 22:59:12 +03:00
var msg2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( user . msghandle != null ) ) ;
2021-04-14 09:23:09 +03:00
var push2fa = ( ( parent . firebase != null ) && ( user . otpdev != null ) ) ;
2020-04-23 01:29:26 +03:00
2022-03-05 02:56:19 +03:00
// Check if two factor can be skipped
const twoFactorSkip = checkUserOneTimePasswordSkip ( domain , user , req , loginOptions ) ;
2019-02-08 09:30:33 +03:00
// Check if this user has 2-step login active
2022-03-05 02:56:19 +03:00
if ( ( twoFactorSkip == null ) && ( req . session . loginmode != 6 ) && checkUserOneTimePasswordRequired ( domain , user , req , loginOptions ) ) {
2021-07-25 01:14:21 +03:00
if ( ( req . body . hwtoken == '**timeout**' ) ) {
delete req . session ; // Clear the session
res . redirect ( domain . url + getQueryPortion ( req ) ) ;
return ;
}
2020-04-23 01:29:26 +03:00
if ( ( req . body . hwtoken == '**email**' ) && email2fa ) {
2020-03-14 06:39:21 +03:00
user . otpekey = { k : obj . common . zeroPad ( getRandomEightDigitInteger ( ) , 8 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA email to: ' + user . email ) ;
2021-02-10 22:28:21 +03:00
domain . mailserver . sendAccountLoginMail ( domain , user . email , user . otpekey . k , obj . getLanguageCodes ( req ) , req . query . key ) ;
2020-03-14 06:39:21 +03:00
req . session . messageid = 2 ; // "Email sent" message
2021-05-12 20:34:04 +03:00
req . session . loginmode = 4 ;
2020-03-14 06:39:21 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2020-04-23 01:29:26 +03:00
if ( ( req . body . hwtoken == '**sms**' ) && sms2fa ) {
// Cause a token to be sent to the user's phone number
user . otpsms = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA SMS to: ' + user . phone ) ;
parent . smsserver . sendToken ( domain , user . phone , user . otpsms . k , obj . getLanguageCodes ( req ) ) ;
// Ask for a login token & confirm sms was sent
req . session . messageid = 4 ; // "SMS sent" message
2021-05-12 20:34:04 +03:00
req . session . loginmode = 4 ;
2020-04-23 01:29:26 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2022-10-22 22:59:12 +03:00
if ( ( req . body . hwtoken == '**msg**' ) && msg2fa ) {
// Cause a token to be sent to the user's messenger account
user . otpmsg = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA message to: ' + user . msghandle ) ;
parent . msgserver . sendToken ( domain , user . msghandle , user . otpmsg . k , obj . getLanguageCodes ( req ) ) ;
// Ask for a login token & confirm message was sent
req . session . messageid = 6 ; // "Message sent" message
req . session . loginmode = 4 ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2021-04-15 00:06:31 +03:00
// Handle device push notification 2FA request
// We create a browser cookie, send it back and when the browser connects it's web socket, it will trigger the push notification.
if ( ( req . body . hwtoken == '**push**' ) && push2fa && ( ( domain . passwordrequirements == null ) || ( domain . passwordrequirements . push2factor != false ) ) ) {
2021-04-14 23:28:51 +03:00
const logincodeb64 = Buffer . from ( obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) ) . toString ( 'base64' ) ;
const sessioncode = obj . crypto . randomBytes ( 24 ) . toString ( 'base64' ) ;
// Create a browser cookie so the browser can connect using websocket and wait for device accept/reject.
const browserCookie = parent . encodeCookie ( { a : 'waitAuth' , c : logincodeb64 , u : user . _id , n : user . otpdev , s : sessioncode , d : domain . id } ) ;
// Get the HTTPS port
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port if specified
// Get the agent connection server name
2022-08-25 00:10:40 +03:00
var serverName = obj . getWebServerName ( domain , req ) ;
2021-04-14 23:28:51 +03:00
if ( typeof obj . args . agentaliasdns == 'string' ) { serverName = obj . args . agentaliasdns ; }
// Build the connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += '/' ;
var url = 'wss://' + serverName + ':' + httpsPort + '/' + xdomain + '2fahold.ashx?c=' + browserCookie ;
// Request that the login page wait for device auth
req . session . messageid = 5 ; // "Sending notification..." message
req . session . passhint = url ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 8 ;
2021-04-14 23:28:51 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2021-04-14 09:23:09 +03:00
return ;
}
2022-03-05 02:56:19 +03:00
checkUserOneTimePassword ( req , domain , user , req . body . token , req . body . hwtoken , function ( result , authData ) {
2019-02-08 20:24:00 +03:00
if ( result == false ) {
2019-02-13 06:23:40 +03:00
var randomWaitTime = 0 ;
2019-10-04 22:18:56 +03:00
2022-01-04 00:12:10 +03:00
// Check if 2FA is allowed for this IP address
if ( obj . checkAllow2Fa ( req ) == false ) {
// Wait and redirect the user
setTimeout ( function ( ) {
req . session . messageid = 114 ; // IP address blocked, try again later.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
} , 2000 + ( obj . crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % 4095 ) ) ;
return ;
}
2019-02-06 06:07:12 +03:00
// 2-step auth is required, but the token is not present or not valid.
2019-02-13 06:23:40 +03:00
if ( ( req . body . token != null ) || ( req . body . hwtoken != null ) ) {
randomWaitTime = 2000 + ( obj . crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % 4095 ) ; // This is a fail, wait a random time. 2 to 6 seconds.
2019-11-18 01:33:29 +03:00
req . session . messageid = 108 ; // Invalid token, try again.
2022-09-02 08:06:08 +03:00
obj . parent . authLog ( 'https' , 'Failed 2FA for ' + xusername + ' from ' + cleanRemoteAddr ( req . clientIp ) + ' port ' + req . port , { useragent : req . headers [ 'user-agent' ] } ) ;
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleLoginRequest: invalid 2FA token' ) ;
2022-09-02 08:06:08 +03:00
const ua = obj . getUserAgentInfo ( req ) ;
2021-02-26 11:58:38 +03:00
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 ] } ) ;
2022-01-04 00:12:10 +03:00
obj . setbad2Fa ( req ) ;
2019-08-23 21:51:48 +03:00
} else {
parent . debug ( 'web' , 'handleLoginRequest: 2FA token required' ) ;
2019-02-13 06:23:40 +03:00
}
// Wait and redirect the user
setTimeout ( function ( ) {
2021-05-12 20:34:04 +03:00
req . session . loginmode = 4 ;
if ( ( user . email != null ) && ( user . emailVerified == true ) && ( domain . mailserver != null ) && ( user . otpekey != null ) ) { req . session . temail = 1 ; }
if ( ( user . phone != null ) && ( parent . smsserver != null ) ) { req . session . tsms = 1 ; }
2022-10-22 22:59:12 +03:00
if ( ( user . msghandle != null ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) ) { req . session . tmsg = 1 ; }
2021-05-12 20:34:04 +03:00
if ( ( user . otpdev != null ) && ( parent . firebase != null ) ) { req . session . tpush = 1 ; }
2022-07-13 03:45:19 +03:00
req . session . e = parent . encryptSessionData ( { tuserid : userid , tuser : xusername , tpass : xpassword } ) ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-13 06:23:40 +03:00
} , randomWaitTime ) ;
2019-02-08 20:24:00 +03:00
} else {
2019-11-29 09:29:22 +03:00
// Check if we need to remember this device
2020-04-30 01:45:31 +03:00
if ( ( req . body . remembertoken === 'on' ) && ( ( domain . twofactorcookiedurationdays == null ) || ( domain . twofactorcookiedurationdays > 0 ) ) ) {
var maxCookieAge = domain . twofactorcookiedurationdays ;
if ( typeof maxCookieAge != 'number' ) { maxCookieAge = 30 ; }
2020-05-22 07:25:11 +03:00
const twoFactorCookie = obj . parent . encodeCookie ( { userid : user . _id , expire : maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ } , obj . parent . loginCookieEncryptionKey ) ;
2022-04-13 00:03:52 +03:00
res . cookie ( 'twofactor' , twoFactorCookie , { maxAge : ( maxCookieAge * 24 * 60 * 60 * 1000 ) , httpOnly : true , sameSite : parent . config . settings . sessionsamesite , secure : true } ) ;
2019-11-29 09:29:22 +03:00
}
2020-04-21 11:50:27 +03:00
// Check if email address needs to be confirmed
2022-02-04 18:29:18 +03:00
const emailcheck = ( ( domain . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
2020-04-21 11:50:27 +03:00
if ( emailcheck && ( user . emailVerified !== true ) ) {
parent . debug ( 'web' , 'Redirecting using ' + user . name + ' to email check login page' ) ;
req . session . messageid = 3 ; // "Email verification required" message
2021-05-12 20:34:04 +03:00
req . session . loginmode = 7 ;
2020-04-21 11:50:27 +03:00
req . session . passhint = user . email ;
req . session . cuserid = userid ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2019-11-29 09:29:22 +03:00
// Login successful
parent . debug ( 'web' , 'handleLoginRequest: successful 2FA login' ) ;
2022-03-05 02:56:19 +03:00
if ( authData != null ) { if ( loginOptions == null ) { loginOptions = { } ; } loginOptions . twoFactorType = authData . twoFactorType ; }
2021-04-16 22:42:54 +03:00
completeLoginRequest ( req , res , domain , user , userid , xusername , xpassword , direct , loginOptions ) ;
2018-08-30 22:05:23 +03:00
}
} ) ;
2019-02-08 20:24:00 +03:00
return ;
2018-08-30 22:05:23 +03:00
}
2017-12-14 01:52:57 +03:00
2020-04-21 11:50:27 +03:00
// Check if email address needs to be confirmed
2022-02-04 18:29:18 +03:00
const emailcheck = ( ( domain . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
2020-04-21 11:50:27 +03:00
if ( emailcheck && ( user . emailVerified !== true ) ) {
parent . debug ( 'web' , 'Redirecting using ' + user . name + ' to email check login page' ) ;
req . session . messageid = 3 ; // "Email verification required" message
2021-05-12 20:34:04 +03:00
req . session . loginmode = 7 ;
2020-04-21 11:50:27 +03:00
req . session . passhint = user . email ;
req . session . cuserid = userid ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2019-11-29 09:29:22 +03:00
// Login successful
parent . debug ( 'web' , 'handleLoginRequest: successful login' ) ;
2022-03-05 02:56:19 +03:00
if ( twoFactorSkip != null ) { if ( loginOptions == null ) { loginOptions = { } ; } loginOptions . twoFactorType = twoFactorSkip . twoFactorType ; }
2021-04-16 22:42:54 +03:00
completeLoginRequest ( req , res , domain , user , userid , xusername , xpassword , direct , loginOptions ) ;
2017-08-28 19:27:45 +03:00
} else {
2020-02-17 21:24:32 +03:00
// Login failed, log the error
2022-09-02 08:06:08 +03:00
obj . parent . authLog ( 'https' , 'Failed password for ' + xusername + ' from ' + req . clientIp + ' port ' + req . connection . remotePort , { useragent : req . headers [ 'user-agent' ] } ) ;
2020-02-17 21:24:32 +03:00
// Wait a random delay
2019-02-13 06:23:40 +03:00
setTimeout ( function ( ) {
// If the account is locked, display that.
2019-11-19 01:17:27 +03:00
if ( typeof xusername == 'string' ) {
var xuserid = 'user/' + domain . id + '/' + xusername . toLowerCase ( ) ;
if ( err == 'locked' ) {
parent . debug ( 'web' , 'handleLoginRequest: login failed, locked account' ) ;
req . session . messageid = 110 ; // Account locked.
2022-09-02 08:06:08 +03:00
const ua = obj . getUserAgentInfo ( req ) ;
2021-02-26 11:58:38 +03:00
obj . parent . DispatchEvent ( [ '*' , 'server-users' , xuserid ] , obj , { action : 'authfail' , userid : xuserid , username : xusername , domain : domain . id , msg : 'User login attempt on locked account from ' + req . clientIp , msgid : 109 , msgArgs : [ req . clientIp , ua . browserStr , ua . osStr ] } ) ;
2019-11-19 04:31:42 +03:00
obj . setbadLogin ( req ) ;
2022-08-22 00:05:51 +03:00
} else if ( err == 'denied' ) {
parent . debug ( 'web' , 'handleLoginRequest: login failed, access denied' ) ;
req . session . messageid = 111 ; // Access denied.
2022-09-02 08:06:08 +03:00
const ua = obj . getUserAgentInfo ( req ) ;
2022-08-22 00:05:51 +03:00
obj . parent . DispatchEvent ( [ '*' , 'server-users' , xuserid ] , obj , { action : 'authfail' , userid : xuserid , username : xusername , domain : domain . id , msg : 'Denied user login from ' + req . clientIp , msgid : 155 , msgArgs : [ req . clientIp , ua . browserStr , ua . osStr ] } ) ;
obj . setbadLogin ( req ) ;
2019-11-19 01:17:27 +03:00
} else {
parent . debug ( 'web' , 'handleLoginRequest: login failed, bad username and password' ) ;
req . session . messageid = 112 ; // Login failed, check username and password.
2022-09-02 08:06:08 +03:00
const ua = obj . getUserAgentInfo ( req ) ;
2021-02-26 11:58:38 +03:00
obj . parent . DispatchEvent ( [ '*' , 'server-users' , xuserid ] , obj , { action : 'authfail' , userid : xuserid , username : xusername , domain : domain . id , msg : 'Invalid user login attempt from ' + req . clientIp , msgid : 110 , msgArgs : [ req . clientIp , ua . browserStr , ua . osStr ] } ) ;
2019-11-19 04:31:42 +03:00
obj . setbadLogin ( req ) ;
2019-11-19 01:17:27 +03:00
}
2019-08-23 21:51:48 +03:00
}
2019-02-13 06:23:40 +03:00
// Clean up login mode and display password hint if present.
delete req . session . loginmode ;
if ( ( passhint != null ) && ( passhint . length > 0 ) ) {
req . session . passhint = passhint ;
} else {
delete req . session . passhint ;
}
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-13 06:23:40 +03:00
} , 2000 + ( obj . crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % 4095 ) ) ; // Wait for 2 to ~6 seconds.
2017-08-28 19:27:45 +03:00
}
} ) ;
}
2018-05-24 07:43:44 +03:00
2021-04-16 22:42:54 +03:00
function completeLoginRequest ( req , res , domain , user , userid , xusername , xpassword , direct , loginOptions ) {
2019-02-28 05:48:50 +03:00
// Check if we need to change the password
if ( ( typeof user . passchange == 'number' ) && ( ( user . passchange == - 1 ) || ( ( typeof domain . passwordrequirements == 'object' ) && ( typeof domain . passwordrequirements . reset == 'number' ) && ( user . passchange + ( domain . passwordrequirements . reset * 86400 ) < Math . floor ( Date . now ( ) / 1000 ) ) ) ) ) {
// Request a password change
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleLoginRequest: login ok, password change requested' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 6 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 113 ; // Password change requested.
2022-07-13 03:45:19 +03:00
// Decrypt any session data
const sec = parent . decryptSessionData ( req . session . e ) ;
sec . rtuser = xusername ;
sec . rtpass = xpassword ;
req . session . e = parent . encryptSessionData ( sec ) ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-28 05:48:50 +03:00
return ;
}
2019-02-08 20:24:00 +03:00
// Save login time
2019-11-18 03:20:53 +03:00
user . pastlogin = user . login ;
2021-12-05 00:08:17 +03:00
user . login = user . access = Math . floor ( Date . now ( ) / 1000 ) ;
2019-02-08 20:24:00 +03:00
obj . db . SetUser ( user ) ;
2019-05-04 22:55:46 +03:00
// Notify account login
2021-03-11 01:24:55 +03:00
const targets = [ '*' , 'server-users' , user . _id ] ;
2019-05-04 22:55:46 +03:00
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2022-09-02 08:06:08 +03:00
const ua = obj . getUserAgentInfo ( req ) ;
2022-05-11 05:31:21 +03:00
const loginEvent = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'login' , msgid : 107 , msgArgs : [ req . clientIp , ua . browserStr , ua . osStr ] , msg : 'Account login from ' + req . clientIp + ', ' + ua . browserStr + ', ' + ua . osStr , domain : domain . id , ip : req . clientIp , userAgent : req . headers [ 'user-agent' ] , rport : req . connection . remotePort } ;
2022-03-05 02:56:19 +03:00
if ( loginOptions != null ) {
if ( ( loginOptions . tokenName != null ) && ( loginOptions . tokenUser != null ) ) { loginEvent . tokenName = loginOptions . tokenName ; loginEvent . tokenUser = loginOptions . tokenUser ; } // If a login token was used, add it to the event.
if ( loginOptions . twoFactorType != null ) { loginEvent . twoFactorType = loginOptions . twoFactorType ; }
}
2021-02-26 11:58:38 +03:00
obj . parent . DispatchEvent ( targets , obj , loginEvent ) ;
2019-02-08 20:24:00 +03:00
// Regenerate session when signing in to prevent fixation
//req.session.regenerate(function () {
// Store the user's primary key in the session store to be retrieved, or in this case the entire user object
2022-07-13 03:45:19 +03:00
delete req . session . e ;
2021-05-12 20:34:04 +03:00
delete req . session . u2f ;
2019-02-08 20:24:00 +03:00
delete req . session . loginmode ;
2021-05-12 20:34:04 +03:00
delete req . session . tuserid ;
delete req . session . tuser ;
delete req . session . tpass ;
delete req . session . temail ;
delete req . session . tsms ;
2022-10-22 22:59:12 +03:00
delete req . session . tmsg ;
2021-05-12 20:34:04 +03:00
delete req . session . tpush ;
2019-11-18 01:33:29 +03:00
delete req . session . messageid ;
2019-02-11 07:13:03 +03:00
delete req . session . passhint ;
2020-04-21 11:50:27 +03:00
delete req . session . cuserid ;
2021-04-17 04:55:03 +03:00
delete req . session . expire ;
2021-05-12 20:34:04 +03:00
delete req . session . currentNode ;
2019-02-08 20:24:00 +03:00
req . session . userid = userid ;
2020-05-22 07:25:11 +03:00
req . session . ip = req . clientIp ;
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2024-03-03 23:21:21 +03:00
obj . parent . authLog ( 'https' , 'Accepted password for ' + ( xusername ? xusername : userid ) + ' from ' + req . clientIp + ' port ' + req . connection . remotePort , { useragent : req . headers [ 'user-agent' ] , sessionid : req . session . x } ) ;
2021-04-16 22:42:54 +03:00
// If a login token was used, add this information and expire time to the session.
if ( ( loginOptions != null ) && ( loginOptions . tokenName != null ) && ( loginOptions . tokenUser != null ) ) {
req . session . loginToken = loginOptions . tokenUser ;
if ( loginOptions . expire != null ) { req . session . expire = loginOptions . expire ; }
}
2019-02-08 20:24:00 +03:00
if ( req . body . viewmode ) { req . session . viewmode = req . body . viewmode ; }
if ( req . body . host ) {
// TODO: This is a terrible search!!! FIX THIS.
/ *
obj . db . GetAllType ( 'node' , function ( err , docs ) {
for ( var i = 0 ; i < docs . length ; i ++ ) {
if ( docs [ i ] . name == req . body . host ) {
req . session . currentNode = docs [ i ] . _id ;
break ;
}
}
console . log ( "CurrentNode: " + req . session . currentNode ) ;
// This redirect happens after finding node is completed
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-08 20:24:00 +03:00
} ) ;
* /
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleLoginRequest: login ok (1)' ) ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; } // Temporary
2019-02-08 20:24:00 +03:00
} else {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleLoginRequest: login ok (2)' ) ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-08 20:24:00 +03:00
}
//});
}
2019-08-26 22:20:24 +03:00
function handleCreateAccountRequest ( req , res , direct ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { parent . debug ( 'web' , 'handleCreateAccountRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2021-04-17 04:55:03 +03:00
if ( req . session . loginToken != null ) { res . sendStatus ( 404 ) ; return ; } // Do not allow this command when logged in using a login token
2022-07-07 09:39:36 +03:00
if ( req . body == null ) { res . sendStatus ( 404 ) ; return ; } // Post body is empty or can't be parsed
2019-01-16 23:04:48 +03:00
2020-11-21 03:43:58 +03:00
// Check if we are in maintenance mode
if ( parent . config . settings . maintenancemode != null ) {
req . session . messageid = 115 ; // Server under maintenance
2021-05-12 20:34:04 +03:00
req . session . loginmode = 1 ;
2020-11-21 03:43:58 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2019-07-15 20:24:31 +03:00
// Always lowercase the email address
if ( req . body . email ) { req . body . email = req . body . email . toLowerCase ( ) ; }
2019-07-09 01:59:44 +03:00
// If the email is the username, set this here.
if ( domain . usernameisemail ) { req . body . username = req . body . email ; }
2022-07-12 00:35:05 +03:00
// Check if there is domain.newAccountToken, check if supplied token is valid
2022-09-07 17:41:20 +03:00
if ( ( domain . newaccountspass != null ) && ( domain . newaccountspass != '' ) && ( req . body . newaccountspass != domain . newaccountspass ) ) {
2022-07-12 00:35:05 +03:00
parent . debug ( 'web' , 'handleCreateAccountRequest: Invalid account creation token' ) ;
req . session . loginmode = 2 ;
req . session . messageid = 103 ; // Invalid account creation token.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
// If needed, check the new account creation CAPTCHA
if ( ( domain . newaccountscaptcha != null ) && ( domain . newaccountscaptcha !== false ) ) {
const c = parent . decodeCookie ( req . body . captchaargs , parent . loginCookieEncryptionKey , 10 ) ; // 10 minute timeout
if ( ( c == null ) || ( c . type != 'newAccount' ) || ( typeof c . captcha != 'string' ) || ( c . captcha . length < 5 ) || ( c . captcha != req . body . anewaccountcaptcha ) ) {
req . session . loginmode = 2 ;
req . session . messageid = 117 ; // Invalid security check
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
}
2020-05-14 08:30:59 +03:00
// Accounts that start with ~ are not allowed
if ( ( typeof req . body . username != 'string' ) || ( req . body . username . length < 1 ) || ( req . body . username [ 0 ] == '~' ) ) {
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (0)' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 2 ;
2020-05-14 08:30:59 +03:00
req . session . messageid = 100 ; // Unable to create account.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
2019-09-27 20:15:45 +03:00
// Count the number of users in this domain
var domainUserCount = 0 ;
for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { domainUserCount ++ ; } }
2019-04-23 23:42:54 +03:00
// Check if we are allowed to create new users using the login screen
2019-09-27 20:15:45 +03:00
if ( ( domain . newaccounts !== 1 ) && ( domain . newaccounts !== true ) && ( domainUserCount > 0 ) ) {
parent . debug ( 'web' , 'handleCreateAccountRequest: domainUserCount > 1.' ) ;
res . sendStatus ( 401 ) ;
return ;
2019-04-23 23:42:54 +03:00
}
2019-02-11 07:13:03 +03:00
2019-02-26 03:07:27 +03:00
// Check if this request is for an allows email domain
if ( ( domain . newaccountemaildomains != null ) && Array . isArray ( domain . newaccountemaildomains ) ) {
var i = - 1 ;
if ( typeof req . body . email == 'string' ) { i = req . body . email . indexOf ( '@' ) ; }
if ( i == - 1 ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (1)' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 2 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 100 ; // Unable to create account.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-26 03:07:27 +03:00
return ;
}
var emailok = false , emaildomain = req . body . email . substring ( i + 1 ) . toLowerCase ( ) ;
for ( var i in domain . newaccountemaildomains ) { if ( emaildomain == domain . newaccountemaildomains [ i ] . toLowerCase ( ) ) { emailok = true ; } }
if ( emailok == false ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (2)' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 2 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 100 ; // Unable to create account.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-26 03:07:27 +03:00
return ;
}
}
2019-02-11 07:13:03 +03:00
// Check if we exceed the maximum number of user accounts
2019-02-12 01:41:15 +03:00
obj . db . isMaxType ( domain . limits . maxuseraccounts , 'user' , domain . id , function ( maxExceed ) {
2019-02-11 07:13:03 +03:00
if ( maxExceed ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleCreateAccountRequest: account limit reached' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 2 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 101 ; // Account limit reached.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-11 07:13:03 +03:00
} else {
if ( ! obj . common . validateUsername ( req . body . username , 1 , 64 ) || ! obj . common . validateEmail ( req . body . email , 1 , 256 ) || ! obj . common . validateString ( req . body . password1 , 1 , 256 ) || ! obj . common . validateString ( req . body . password2 , 1 , 256 ) || ( req . body . password1 != req . body . password2 ) || req . body . username == '~' || ! obj . common . checkPasswordRequirements ( req . body . password1 , domain . passwordrequirements ) ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (3)' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 2 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 100 ; // Unable to create account.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2017-12-13 03:04:54 +03:00
} else {
2019-02-11 07:13:03 +03:00
// Check if this email was already verified
obj . db . GetUserWithVerifiedEmail ( domain . id , req . body . email , function ( err , docs ) {
2020-11-03 13:58:29 +03:00
if ( ( docs != null ) && ( docs . length > 0 ) ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleCreateAccountRequest: Existing account with this email address' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 2 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 102 ; // Existing account with this email address.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-11 07:13:03 +03:00
} else {
// Check if user exists
if ( obj . users [ 'user/' + domain . id + '/' + req . body . username . toLowerCase ( ) ] ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleCreateAccountRequest: Username already exists' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 2 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 104 ; // Username already exists.
2019-02-11 07:13:03 +03:00
} else {
2021-12-05 00:08:17 +03:00
var user = { type : 'user' , _id : 'user/' + domain . id + '/' + req . body . username . toLowerCase ( ) , name : req . body . username , email : req . body . email , creation : Math . floor ( Date . now ( ) / 1000 ) , login : Math . floor ( Date . now ( ) / 1000 ) , access : Math . floor ( Date . now ( ) / 1000 ) , domain : domain . id } ;
2019-05-13 05:14:24 +03:00
if ( domain . newaccountsrights ) { user . siteadmin = domain . newaccountsrights ; }
2020-05-25 22:52:13 +03:00
if ( obj . common . validateStrArray ( domain . newaccountrealms ) ) { user . groups = domain . newaccountrealms ; }
2019-03-01 03:17:22 +03:00
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) && ( req . body . apasswordhint ) ) { var hint = req . body . apasswordhint ; if ( hint . length > 250 ) { hint = hint . substring ( 0 , 250 ) ; } user . passhint = hint ; }
2019-06-27 08:13:56 +03:00
if ( domainUserCount == 0 ) { user . siteadmin = 4294967295 ; /*if (domain.newaccounts === 2) { delete domain.newaccounts; }*/ } // If this is the first user, give the account site admin.
2020-05-28 04:26:54 +03:00
// Auto-join any user groups
if ( typeof domain . newaccountsusergroups == 'object' ) {
for ( var i in domain . newaccountsusergroups ) {
var ugrpid = domain . newaccountsusergroups [ i ] ;
if ( ugrpid . indexOf ( '/' ) < 0 ) { ugrpid = 'ugrp/' + domain . id + '/' + ugrpid ; }
var ugroup = obj . userGroups [ ugrpid ] ;
if ( ugroup != null ) {
// Add group to the user
if ( user . links == null ) { user . links = { } ; }
user . links [ ugroup . _id ] = { rights : 1 } ;
// Add user to the group
ugroup . links [ user . _id ] = { userid : user . _id , name : user . name , rights : 1 } ;
db . Set ( ugroup ) ;
// Notify user group change
var event = { etype : 'ugrp' , ugrpid : ugroup . _id , name : ugroup . name , desc : ugroup . desc , action : 'usergroupchange' , links : ugroup . links , msg : 'Added user ' + user . name + ' to user group ' + ugroup . name , addUserDomain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
parent . DispatchEvent ( [ '*' , ugroup . _id , user . _id ] , obj , event ) ;
}
}
}
2019-02-11 07:13:03 +03:00
obj . users [ user . _id ] = user ;
req . session . userid = user . _id ;
2020-05-22 07:25:11 +03:00
req . session . ip = req . clientIp ; // Bind this session to the IP address of the request
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2019-02-11 07:13:03 +03:00
// Create a user, generate a salt and hash the password
2019-05-15 00:39:26 +03:00
require ( './pass' ) . hash ( req . body . password1 , function ( err , salt , hash , tag ) {
2019-02-11 07:13:03 +03:00
if ( err ) throw err ;
user . salt = salt ;
user . hash = hash ;
2019-02-13 06:23:40 +03:00
delete user . passtype ;
2019-02-11 07:13:03 +03:00
obj . db . SetUser ( user ) ;
// Send the verification email
2021-02-10 22:28:21 +03:00
if ( ( domain . mailserver != null ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) && ( obj . common . validateEmail ( user . email , 1 , 256 ) == true ) ) { domain . mailserver . sendAccountCheckMail ( domain , user . name , user . _id , user . email , obj . getLanguageCodes ( req ) , req . query . key ) ; }
2019-05-15 00:39:26 +03:00
} , 0 ) ;
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountcreate' , msg : 'Account created, email is ' + req . body . email , domain : domain . id } ;
2019-05-30 00:36:14 +03:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , event ) ;
2019-02-11 07:13:03 +03:00
}
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-11 07:13:03 +03:00
}
} ) ;
2017-12-13 03:04:54 +03:00
}
2019-02-11 07:13:03 +03:00
}
} ) ;
2017-12-13 03:04:54 +03:00
}
2019-02-28 05:48:50 +03:00
// Called to process an account password reset
2019-08-26 22:20:24 +03:00
function handleResetPasswordRequest ( req , res , direct ) {
2019-02-28 05:48:50 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2021-04-17 04:55:03 +03:00
if ( req . session . loginToken != null ) { res . sendStatus ( 404 ) ; return ; } // Do not allow this command when logged in using a login token
2022-07-07 09:39:36 +03:00
if ( req . body == null ) { res . sendStatus ( 404 ) ; return ; } // Post body is empty or can't be parsed
2019-02-28 05:48:50 +03:00
2022-07-13 03:45:19 +03:00
// Decrypt any session data
const sec = parent . decryptSessionData ( req . session . e ) ;
2019-02-28 05:48:50 +03:00
// Check everything is ok
2024-09-30 19:30:07 +03:00
const allowAccountReset = ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . allowaccountreset !== false ) ) ;
2022-07-13 03:45:19 +03:00
if ( ( allowAccountReset === false ) || ( domain == null ) || ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) || ( typeof req . body . rpassword1 != 'string' ) || ( typeof req . body . rpassword2 != 'string' ) || ( req . body . rpassword1 != req . body . rpassword2 ) || ( typeof req . body . rpasswordhint != 'string' ) || ( req . session == null ) || ( typeof sec . rtuser != 'string' ) || ( typeof sec . rtpass != 'string' ) ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetPasswordRequest: checks failed' ) ;
2022-07-13 03:45:19 +03:00
delete req . session . e ;
2021-05-12 20:34:04 +03:00
delete req . session . u2f ;
2019-02-28 05:48:50 +03:00
delete req . session . loginmode ;
2021-05-12 20:34:04 +03:00
delete req . session . tuserid ;
delete req . session . tuser ;
delete req . session . tpass ;
delete req . session . temail ;
delete req . session . tsms ;
2022-10-22 22:59:12 +03:00
delete req . session . tmsg ;
2021-05-12 20:34:04 +03:00
delete req . session . tpush ;
2019-11-18 01:33:29 +03:00
delete req . session . messageid ;
2019-02-28 05:48:50 +03:00
delete req . session . passhint ;
2020-04-21 11:50:27 +03:00
delete req . session . cuserid ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-28 05:48:50 +03:00
return ;
}
// Authenticate the user
2022-07-13 03:45:19 +03:00
obj . authenticate ( sec . rtuser , sec . rtpass , domain , function ( err , userid , passhint , loginOptions ) {
2019-02-28 05:48:50 +03:00
if ( userid ) {
// Login
var user = obj . users [ userid ] ;
// If we have password requirements, check this here.
if ( ! obj . common . checkPasswordRequirements ( req . body . rpassword1 , domain . passwordrequirements ) ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetPasswordRequest: password rejected, use a different one (1)' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 6 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 105 ; // Password rejected, use a different one.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-28 05:48:50 +03:00
return ;
}
2020-08-16 21:10:02 +03:00
// Check if the password is the same as a previous one
obj . checkOldUserPasswords ( domain , user , req . body . rpassword1 , function ( result ) {
2020-08-16 23:44:54 +03:00
if ( result != 0 ) {
2020-08-16 21:10:02 +03:00
// This is the same password as an older one, request a password change again
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetPasswordRequest: password rejected, use a different one (2)' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 6 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 105 ; // Password rejected, use a different one.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-28 05:48:50 +03:00
} else {
// Update the password, use a different salt.
2019-05-15 00:39:26 +03:00
require ( './pass' ) . hash ( req . body . rpassword1 , function ( err , salt , hash , tag ) {
2020-08-16 21:10:02 +03:00
const nowSeconds = Math . floor ( Date . now ( ) / 1000 ) ;
if ( err ) { parent . debug ( 'web' , 'handleResetPasswordRequest: hash error.' ) ; throw err ; }
if ( domain . passwordrequirements != null ) {
// Save password hint if this feature is enabled
if ( ( domain . passwordrequirements . hint === true ) && ( req . body . apasswordhint ) ) { var hint = req . body . apasswordhint ; if ( hint . length > 250 ) hint = hint . substring ( 0 , 250 ) ; user . passhint = hint ; } else { delete user . passhint ; }
// Save previous password if this feature is enabled
if ( ( typeof domain . passwordrequirements . oldpasswordban == 'number' ) && ( domain . passwordrequirements . oldpasswordban > 0 ) ) {
if ( user . oldpasswords == null ) { user . oldpasswords = [ ] ; }
user . oldpasswords . push ( { salt : user . salt , hash : user . hash , start : user . passchange , end : nowSeconds } ) ;
const extraOldPasswords = user . oldpasswords . length - domain . passwordrequirements . oldpasswordban ;
if ( extraOldPasswords > 0 ) { user . oldpasswords . splice ( 0 , extraOldPasswords ) ; }
}
}
2019-02-28 05:48:50 +03:00
user . salt = salt ;
user . hash = hash ;
2021-12-05 00:08:17 +03:00
user . passchange = user . access = nowSeconds ;
2019-02-28 05:48:50 +03:00
delete user . passtype ;
obj . db . SetUser ( user ) ;
2020-08-16 21:10:02 +03:00
// Event the account change
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'User password reset' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
2019-02-28 05:48:50 +03:00
2019-11-29 09:29:22 +03:00
// Login successful
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetPasswordRequest: success' ) ;
2019-02-28 05:48:50 +03:00
req . session . userid = userid ;
2020-05-22 07:25:11 +03:00
req . session . ip = req . clientIp ; // Bind this session to the IP address of the request
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2022-07-13 03:45:19 +03:00
const sec = parent . decryptSessionData ( req . session . e ) ;
completeLoginRequest ( req , res , domain , obj . users [ userid ] , userid , sec . tuser , sec . tpass , direct , loginOptions ) ;
2019-05-15 00:39:26 +03:00
} , 0 ) ;
2019-02-28 05:48:50 +03:00
}
2019-05-15 00:39:26 +03:00
} , 0 ) ;
2019-02-28 05:48:50 +03:00
} else {
// Failed, error out.
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetPasswordRequest: failed authenticate()' ) ;
2022-07-13 03:45:19 +03:00
delete req . session . e ;
2021-05-12 20:34:04 +03:00
delete req . session . u2f ;
2019-02-28 05:48:50 +03:00
delete req . session . loginmode ;
2021-05-12 20:34:04 +03:00
delete req . session . tuserid ;
delete req . session . tuser ;
delete req . session . tpass ;
delete req . session . temail ;
delete req . session . tsms ;
2022-10-22 22:59:12 +03:00
delete req . session . tmsg ;
2021-05-12 20:34:04 +03:00
delete req . session . tpush ;
2019-11-18 01:33:29 +03:00
delete req . session . messageid ;
2019-02-28 05:48:50 +03:00
delete req . session . passhint ;
2020-04-21 11:50:27 +03:00
delete req . session . cuserid ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-28 05:48:50 +03:00
return ;
}
} ) ;
}
2017-12-13 03:04:54 +03:00
// Called to process an account reset request
2019-08-26 22:20:24 +03:00
function handleResetAccountRequest ( req , res , direct ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2022-02-04 18:29:18 +03:00
const allowAccountReset = ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . allowaccountreset !== false ) ) ;
if ( ( allowAccountReset === false ) || ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) || ( obj . args . lanonly == true ) || ( obj . parent . certificates . CommonName == null ) || ( obj . parent . certificates . CommonName . indexOf ( '.' ) == - 1 ) ) { parent . debug ( 'web' , 'handleResetAccountRequest: check failed' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2021-04-17 04:55:03 +03:00
if ( req . session . loginToken != null ) { res . sendStatus ( 404 ) ; return ; } // Do not allow this command when logged in using a login token
2022-07-07 09:39:36 +03:00
if ( req . body == null ) { res . sendStatus ( 404 ) ; return ; } // Post body is empty or can't be parsed
2019-01-16 23:04:48 +03:00
2019-07-15 20:24:31 +03:00
// Always lowercase the email address
if ( req . body . email ) { req . body . email = req . body . email . toLowerCase ( ) ; }
2019-02-12 01:59:04 +03:00
// Get the email from the body or session.
2019-02-12 01:41:15 +03:00
var email = req . body . email ;
2021-05-12 20:34:04 +03:00
if ( ( email == null ) || ( email == '' ) ) { email = req . session . temail ; }
2019-02-12 01:41:15 +03:00
2019-06-11 21:33:44 +03:00
// Check the email string format
2019-02-12 01:41:15 +03:00
if ( ! email || checkEmail ( email ) == false ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetAccountRequest: Invalid email' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 3 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 106 ; // Invalid email.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2017-12-13 03:04:54 +03:00
} else {
2019-02-12 01:41:15 +03:00
obj . db . GetUserWithVerifiedEmail ( domain . id , email , function ( err , docs ) {
2020-12-10 09:14:51 +03:00
// Remove all accounts that start with ~ since they are special accounts.
var cleanDocs = [ ] ;
if ( ( err == null ) && ( docs . length > 0 ) ) {
for ( var i in docs ) {
const user = docs [ i ] ;
const locked = ( ( user . siteadmin != null ) && ( user . siteadmin != 0xFFFFFFFF ) && ( ( user . siteadmin & 1024 ) != 0 ) ) ; // No password recovery for locked accounts
const specialAccount = ( user . _id . split ( '/' ) [ 2 ] . startsWith ( '~' ) ) ; // No password recovery for special accounts
if ( ( specialAccount == false ) && ( locked == false ) ) { cleanDocs . push ( user ) ; }
}
}
docs = cleanDocs ;
// Check if we have any account that match this email address
2019-02-12 01:41:15 +03:00
if ( ( err != null ) || ( docs . length == 0 ) ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetAccountRequest: Account not found' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 3 ;
2020-07-07 01:04:22 +03:00
req . session . messageid = 1 ; // If valid, reset mail sent. Instead of "Account not found" (107), we send this hold on message so users can't know if this account exists or not.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2017-12-13 03:04:54 +03:00
} else {
2019-06-11 21:33:44 +03:00
// If many accounts have the same validated e-mail, we are going to use the first one for display, but sent a reset email for all accounts.
var responseSent = false ;
for ( var i in docs ) {
var user = docs [ i ] ;
2019-11-29 07:57:34 +03:00
if ( checkUserOneTimePasswordRequired ( domain , user , req ) == true ) {
2019-06-11 21:33:44 +03:00
// Second factor setup, request it now.
2022-03-05 02:56:19 +03:00
checkUserOneTimePassword ( req , domain , user , req . body . token , req . body . hwtoken , function ( result , authData ) {
2019-06-11 21:33:44 +03:00
if ( result == false ) {
if ( i == 0 ) {
2022-01-04 00:12:10 +03:00
// Check if 2FA is allowed for this IP address
if ( obj . checkAllow2Fa ( req ) == false ) {
// Wait and redirect the user
setTimeout ( function ( ) {
req . session . messageid = 114 ; // IP address blocked, try again later.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
} , 2000 + ( obj . crypto . randomBytes ( 2 ) . readUInt16BE ( 0 ) % 4095 ) ) ;
return ;
}
2019-06-11 21:33:44 +03:00
// 2-step auth is required, but the token is not present or not valid.
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetAccountRequest: Invalid 2FA token, try again' ) ;
2019-11-18 01:33:29 +03:00
if ( ( req . body . token != null ) || ( req . body . hwtoken != null ) ) {
2020-12-10 09:14:51 +03:00
var sms2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) && ( parent . smsserver != null ) && ( user . phone != null ) ) ;
2022-10-22 22:59:12 +03:00
var msg2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( user . msghandle != null ) ) ;
2020-12-10 09:14:51 +03:00
if ( ( req . body . hwtoken == '**sms**' ) && sms2fa ) {
// Cause a token to be sent to the user's phone number
user . otpsms = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA SMS for password recovery to: ' + user . phone ) ;
parent . smsserver . sendToken ( domain , user . phone , user . otpsms . k , obj . getLanguageCodes ( req ) ) ;
req . session . messageid = 4 ; // SMS sent.
2022-10-22 22:59:12 +03:00
} else if ( ( req . body . hwtoken == '**msg**' ) && msg2fa ) {
// Cause a token to be sent to the user's messager account
user . otpmsg = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA message for password recovery to: ' + user . msghandle ) ;
parent . msgserver . sendToken ( domain , user . msghandle , user . otpmsg . k , obj . getLanguageCodes ( req ) ) ;
req . session . messageid = 6 ; // Message sent.
2020-12-10 09:14:51 +03:00
} else {
req . session . messageid = 108 ; // Invalid token, try again.
2022-09-02 08:06:08 +03:00
const ua = obj . getUserAgentInfo ( req ) ;
2021-02-26 11:58:38 +03:00
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 ] } ) ;
2022-01-04 00:12:10 +03:00
obj . setbad2Fa ( req ) ;
2020-12-10 09:14:51 +03:00
}
2019-11-18 01:33:29 +03:00
}
2021-05-12 20:34:04 +03:00
req . session . loginmode = 5 ;
req . session . temail = email ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-06-11 21:33:44 +03:00
}
} else {
// Send email to perform recovery.
2021-05-12 20:34:04 +03:00
delete req . session . temail ;
2021-02-10 22:28:21 +03:00
if ( domain . mailserver != null ) {
domain . mailserver . sendAccountResetMail ( domain , user . name , user . _id , user . email , obj . getLanguageCodes ( req ) , req . query . key ) ;
2019-06-11 21:33:44 +03:00
if ( i == 0 ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetAccountRequest: Hold on, reset mail sent.' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 1 ;
2020-07-07 01:04:22 +03:00
req . session . messageid = 1 ; // If valid, reset mail sent.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-06-11 21:33:44 +03:00
}
} else {
if ( i == 0 ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetAccountRequest: Unable to sent email.' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 3 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 109 ; // Unable to sent email.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-06-11 21:33:44 +03:00
}
}
}
} ) ;
} else {
// No second factor, send email to perform recovery.
2021-02-10 22:28:21 +03:00
if ( domain . mailserver != null ) {
domain . mailserver . sendAccountResetMail ( domain , user . name , user . _id , user . email , obj . getLanguageCodes ( req ) , req . query . key ) ;
2019-06-11 21:33:44 +03:00
if ( i == 0 ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetAccountRequest: Hold on, reset mail sent.' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 1 ;
2020-07-07 01:04:22 +03:00
req . session . messageid = 1 ; // If valid, reset mail sent.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-06-11 21:33:44 +03:00
}
} else {
if ( i == 0 ) {
2019-08-23 21:51:48 +03:00
parent . debug ( 'web' , 'handleResetAccountRequest: Unable to sent email.' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 3 ;
2019-11-18 01:33:29 +03:00
req . session . messageid = 109 ; // Unable to sent email.
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2019-02-12 01:41:15 +03:00
}
}
}
2017-12-13 03:04:54 +03:00
}
}
} ) ;
}
}
2020-04-21 11:50:27 +03:00
// Handle account email change and email verification request
function handleCheckAccountEmailRequest ( req , res , direct ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
2021-02-10 22:28:21 +03:00
if ( ( domain . mailserver == null ) || ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) || ( typeof req . session . cuserid != 'string' ) || ( obj . users [ req . session . cuserid ] == null ) || ( ! obj . common . validateEmail ( req . body . email , 1 , 256 ) ) ) { parent . debug ( 'web' , 'handleCheckAccountEmailRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2020-04-21 11:50:27 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2021-04-17 04:55:03 +03:00
if ( req . session . loginToken != null ) { res . sendStatus ( 404 ) ; return ; } // Do not allow this command when logged in using a login token
2022-07-07 09:39:36 +03:00
if ( req . body == null ) { res . sendStatus ( 404 ) ; return ; } // Post body is empty or can't be parsed
2020-04-21 11:50:27 +03:00
// Always lowercase the email address
if ( req . body . email ) { req . body . email = req . body . email . toLowerCase ( ) ; }
// Get the email from the body or session.
var email = req . body . email ;
2021-05-12 20:34:04 +03:00
if ( ( email == null ) || ( email == '' ) ) { email = req . session . temail ; }
2020-04-21 11:50:27 +03:00
// Check if this request is for an allows email domain
if ( ( domain . newaccountemaildomains != null ) && Array . isArray ( domain . newaccountemaildomains ) ) {
var i = - 1 ;
if ( typeof req . body . email == 'string' ) { i = req . body . email . indexOf ( '@' ) ; }
if ( i == - 1 ) {
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (1)' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 7 ;
2020-04-21 11:50:27 +03:00
req . session . messageid = 106 ; // Invalid email.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
var emailok = false , emaildomain = req . body . email . substring ( i + 1 ) . toLowerCase ( ) ;
for ( var i in domain . newaccountemaildomains ) { if ( emaildomain == domain . newaccountemaildomains [ i ] . toLowerCase ( ) ) { emailok = true ; } }
if ( emailok == false ) {
parent . debug ( 'web' , 'handleCreateAccountRequest: unable to create account (2)' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 7 ;
2020-04-21 11:50:27 +03:00
req . session . messageid = 106 ; // Invalid email.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
}
}
// Check the email string format
if ( ! email || checkEmail ( email ) == false ) {
parent . debug ( 'web' , 'handleCheckAccountEmailRequest: Invalid email' ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 7 ;
2020-04-21 11:50:27 +03:00
req . session . messageid = 106 ; // Invalid email.
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
} else {
// Check is email already exists
obj . db . GetUserWithVerifiedEmail ( domain . id , email , function ( err , docs ) {
2022-03-16 00:31:28 +03:00
if ( ( err != null ) || ( ( docs . length > 0 ) && ( docs . find ( function ( u ) { return ( u . _id === req . session . cuserid ) ; } ) < 0 ) ) ) {
2022-09-07 17:40:36 +03:00
// Email already exists
2020-04-21 11:50:27 +03:00
req . session . messageid = 102 ; // Existing account with this email address.
} else {
// Update the user and notify of user email address change
var user = obj . users [ req . session . cuserid ] ;
if ( user . email != email ) {
user . email = email ;
db . SetUser ( user ) ;
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Account changed: ' + user . name , domain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . DispatchEvent ( targets , obj , event ) ;
}
// Send the verification email
2021-02-10 22:28:21 +03:00
domain . mailserver . sendAccountCheckMail ( domain , user . name , user . _id , user . email , obj . getLanguageCodes ( req ) , req . query . key ) ;
2020-04-21 11:50:27 +03:00
// Send the response
req . session . messageid = 2 ; // Email sent.
}
2021-05-12 20:34:04 +03:00
req . session . loginmode = 7 ;
2020-04-21 11:50:27 +03:00
delete req . session . cuserid ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
} ) ;
}
}
2017-12-13 03:04:54 +03:00
// Called to process a web based email verification request
function handleCheckMailRequest ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2021-02-10 22:28:21 +03:00
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) || ( domain . mailserver == null ) ) { parent . debug ( 'web' , 'handleCheckMailRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-01-16 23:04:48 +03:00
2017-12-13 03:04:54 +03:00
if ( req . query . c != null ) {
2021-02-10 22:28:21 +03:00
var cookie = obj . parent . decodeCookie ( req . query . c , domain . mailserver . mailCookieEncryptionKey , 30 ) ;
2020-12-10 09:14:51 +03:00
if ( ( cookie != null ) && ( cookie . u != null ) && ( cookie . u . startsWith ( 'user/' ) ) && ( cookie . e != null ) ) {
2017-12-13 03:04:54 +03:00
var idsplit = cookie . u . split ( '/' ) ;
2020-12-10 09:14:51 +03:00
if ( ( idsplit . length != 3 ) || ( idsplit [ 1 ] != domain . id ) ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleCheckMailRequest: Invalid domain.' ) ;
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 1 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2017-12-13 03:04:54 +03:00
} else {
2020-12-10 09:14:51 +03:00
obj . db . Get ( cookie . u , function ( err , docs ) {
2017-12-13 03:04:54 +03:00
if ( docs . length == 0 ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleCheckMailRequest: Invalid username.' ) ;
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 2 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) , arg1 : encodeURIComponent ( idsplit [ 1 ] ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2017-12-13 03:04:54 +03:00
} else {
var user = docs [ 0 ] ;
if ( user . email != cookie . e ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleCheckMailRequest: Invalid e-mail.' ) ;
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 3 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) , arg1 : encodeURIComponent ( user . email ) . replace ( /'/g , '%27' ) , arg2 : encodeURIComponent ( user . name ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2017-12-13 03:04:54 +03:00
} else {
if ( cookie . a == 1 ) {
// Account email verification
if ( user . emailVerified == true ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleCheckMailRequest: email already verified.' ) ;
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 4 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) , arg1 : encodeURIComponent ( user . email ) . replace ( /'/g , '%27' ) , arg2 : encodeURIComponent ( user . name ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2017-12-13 03:04:54 +03:00
} else {
obj . db . GetUserWithVerifiedEmail ( domain . id , user . email , function ( err , docs ) {
2022-03-16 06:58:44 +03:00
if ( ( docs . length > 0 ) && ( docs . find ( function ( u ) { return ( u . _id === user . _id ) ; } ) < 0 ) ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleCheckMailRequest: email already in use.' ) ;
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 5 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) , arg1 : encodeURIComponent ( user . email ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2017-12-13 03:04:54 +03:00
} else {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleCheckMailRequest: email verification success.' ) ;
2017-12-13 03:04:54 +03:00
// Set the verified flag
obj . users [ user . _id ] . emailVerified = true ;
user . emailVerified = true ;
obj . db . SetUser ( user ) ;
// Event the change
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Verified email of user ' + EscapeHtml ( user . name ) + ' (' + EscapeHtml ( user . email ) + ')' , domain : domain . id } ;
2019-05-29 03:25:23 +03:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
2017-12-13 03:04:54 +03:00
// Send the confirmation page
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 6 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) , arg1 : encodeURIComponent ( user . email ) . replace ( /'/g , '%27' ) , arg2 : encodeURIComponent ( user . name ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2017-12-14 01:52:57 +03:00
// Send a notification
2020-07-15 23:25:18 +03:00
obj . parent . DispatchEvent ( [ user . _id ] , obj , { action : 'notify' , title : 'Email verified' , value : user . email , nolog : 1 , id : Math . random ( ) } ) ;
2020-04-21 03:19:25 +03:00
2024-03-04 03:03:27 +03:00
// Send to authLog
2022-09-02 08:06:08 +03:00
obj . parent . authLog ( 'https' , 'Verified email address ' + user . email + ' for user ' + user . name , { useragent : req . headers [ 'user-agent' ] } ) ;
2017-12-13 03:04:54 +03:00
}
} ) ;
}
} else if ( cookie . a == 2 ) {
// Account reset
if ( user . emailVerified != true ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleCheckMailRequest: email not verified.' ) ;
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 7 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) , arg1 : EscapeHtml ( user . email ) , arg2 : EscapeHtml ( user . name ) } , req , domain ) ) ;
2017-12-13 03:04:54 +03:00
} else {
2020-12-10 09:14:51 +03:00
if ( req . query . confirm == 1 ) {
// Set a temporary password
obj . crypto . randomBytes ( 16 , function ( err , buf ) {
var newpass = buf . toString ( 'base64' ) . split ( '=' ) . join ( '' ) . split ( '/' ) . join ( '' ) . split ( '+' ) . join ( '' ) ;
require ( './pass' ) . hash ( newpass , function ( err , salt , hash , tag ) {
if ( err ) throw err ;
// Change the password
var userinfo = obj . users [ user . _id ] ;
userinfo . salt = salt ;
userinfo . hash = hash ;
delete userinfo . passtype ;
2021-12-05 00:08:17 +03:00
userinfo . passchange = userinfo . access = Math . floor ( Date . now ( ) / 1000 ) ;
2020-12-10 09:14:51 +03:00
delete userinfo . passhint ;
obj . db . SetUser ( userinfo ) ;
// Event the change
var event = { etype : 'user' , userid : user . _id , username : userinfo . name , account : obj . CloneSafeUser ( userinfo ) , action : 'accountchange' , msg : 'Password reset for user ' + EscapeHtml ( user . name ) , domain : domain . id } ;
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' , user . _id ] , obj , event ) ;
// Send the new password
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 8 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) , arg1 : EscapeHtml ( user . name ) , arg2 : EscapeHtml ( newpass ) } , req , domain ) ) ;
2020-12-10 09:14:51 +03:00
parent . debug ( 'web' , 'handleCheckMailRequest: send temporary password.' ) ;
2024-03-04 03:03:27 +03:00
// Send to authLog
2022-09-02 08:06:08 +03:00
obj . parent . authLog ( 'https' , 'Performed account reset for user ' + user . name ) ;
2020-12-10 09:14:51 +03:00
} , 0 ) ;
} ) ;
} else {
// Display a link for the user to confirm password reset
// We must do this because GMail will also load this URL a few seconds after the user does and we don't want to cause two password resets.
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 14 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2020-12-10 09:14:51 +03:00
}
2017-12-13 03:04:54 +03:00
}
} else {
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 9 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2017-12-13 03:04:54 +03:00
}
}
}
} ) ;
}
2017-08-28 19:27:45 +03:00
} else {
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 1 , msgid : 10 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2017-08-28 19:27:45 +03:00
}
}
}
2017-12-13 03:04:54 +03:00
2020-03-17 01:23:59 +03:00
// Called to process an agent invite GET/POST request
function handleInviteRequest ( req , res ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleInviteRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2024-04-11 20:51:54 +03:00
if ( domain . agentinvitecodes != true ) { nice404 ( req , res ) ; return ; }
2020-03-17 01:23:59 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2022-07-07 09:39:36 +03:00
if ( ( req . body == null ) || ( req . body . inviteCode == null ) || ( req . body . inviteCode == '' ) ) { render ( req , res , getRenderPage ( 'invite' , req , domain ) , getRenderArgs ( { messageid : 0 } , req , domain ) ) ; return ; } // No invitation code
2020-03-17 01:23:59 +03:00
2020-03-17 04:03:14 +03:00
// Each for a device group that has this invite code.
for ( var i in obj . meshes ) {
2021-05-23 09:51:56 +03:00
if ( ( obj . meshes [ i ] . domain == domain . id ) && ( obj . meshes [ i ] . deleted == null ) && ( obj . meshes [ i ] . invite != null ) && ( obj . meshes [ i ] . invite . codes . indexOf ( req . body . inviteCode ) >= 0 ) ) {
2020-03-17 04:03:14 +03:00
// Send invitation link, valid for 1 minute.
2024-09-27 07:09:34 +03:00
res . redirect ( domain . url + 'agentinvite?c=' + parent . encodeCookie ( { a : 4 , mid : i , f : obj . meshes [ i ] . invite . flags , ag : obj . meshes [ i ] . invite . ag , expire : 1 } , parent . invitationLinkEncryptionKey ) + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) + ( req . query . hide ? ( '&hide=' + encodeURIComponent ( req . query . hide ) ) : '' ) ) ;
2020-03-17 04:03:14 +03:00
return ;
}
}
2020-03-17 01:23:59 +03:00
2020-05-03 09:49:59 +03:00
render ( req , res , getRenderPage ( 'invite' , req , domain ) , getRenderArgs ( { messageid : 100 } , req , domain ) ) ; // Bad invitation code
2020-03-17 01:23:59 +03:00
}
2021-04-29 09:22:54 +03:00
// Called to render the MSTSC (RDP) or SSH web page
function handleMSTSCRequest ( req , res , page ) {
2020-06-12 23:56:29 +03:00
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleMSTSCRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2020-06-14 03:43:12 +03:00
2020-11-21 03:43:58 +03:00
// Check if we are in maintenance mode
2021-05-25 21:17:23 +03:00
if ( ( parent . config . settings . maintenancemode != null ) && ( req . query . loginscreen !== '1' ) ) {
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 3 , msgid : 13 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2020-11-21 03:43:58 +03:00
return ;
}
2022-03-16 03:00:43 +03:00
// Set features we want to send to this page
var features = 0 ;
if ( domain . allowsavingdevicecredentials === false ) { features |= 1 ; }
2024-05-24 19:27:22 +03:00
// Get the logged in user if present
var user = null ;
// If there is a login token, use that
if ( req . query . login != null ) {
var ucookie = parent . decodeCookie ( req . query . login , parent . loginCookieEncryptionKey , 60 ) ; // Cookie with 1 hour timeout
if ( ( ucookie != null ) && ( ucookie . a === 3 ) && ( typeof ucookie . u == 'string' ) ) { user = obj . users [ ucookie . u ] ; }
}
// If no token, see if we have an active session
if ( ( user == null ) && ( req . session . userid != null ) ) { user = obj . users [ req . session . userid ] ; }
// If still no user, see if we have a default user
if ( ( user == null ) && ( obj . args . user ) ) { user = obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ; }
// No user login, exit now
if ( user == null ) { res . sendStatus ( 401 ) ; return ; }
2020-06-14 03:43:12 +03:00
if ( req . query . ws != null ) {
// This is a query with a websocket relay cookie, check that the cookie is valid and use it.
2020-09-17 02:16:58 +03:00
var rcookie = parent . decodeCookie ( req . query . ws , parent . loginCookieEncryptionKey , 60 ) ; // Cookie with 1 hour timeout
2020-07-06 22:46:38 +03:00
if ( ( rcookie != null ) && ( rcookie . domainid == domain . id ) && ( rcookie . nodeid != null ) && ( rcookie . tcpport != null ) ) {
2021-06-30 03:13:18 +03:00
// Fetch the node from the database
obj . db . Get ( rcookie . nodeid , function ( err , nodes ) {
if ( ( err != null ) || ( nodes . length != 1 ) ) { res . sendStatus ( 404 ) ; return ; }
const node = nodes [ 0 ] ;
2022-05-11 03:44:34 +03:00
// Check if we have SSH/RDP credentials for this device
var serverCredentials = 0 ;
2022-03-16 03:00:43 +03:00
if ( domain . allowsavingdevicecredentials !== false ) {
if ( page == 'ssh' ) {
2022-05-11 03:44:34 +03:00
if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh . u == 'string' ) && ( typeof node . ssh . p == 'string' ) ) { serverCredentials = 1 ; } // Username and password
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh . k == 'string' ) && ( typeof node . ssh . kp == 'string' ) ) { serverCredentials = 2 ; } // Username, key and password
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh . k == 'string' ) ) { serverCredentials = 3 ; } // Username and key. No password.
2024-05-24 19:27:22 +03:00
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh [ user . _id ] == 'object' ) && ( typeof node . ssh [ user . _id ] . u == 'string' ) && ( typeof node . ssh [ user . _id ] . p == 'string' ) ) { serverCredentials = 1 ; } // Username and password in per user format
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh [ user . _id ] == 'object' ) && ( typeof node . ssh [ user . _id ] . k == 'string' ) && ( typeof node . ssh [ user . _id ] . kp == 'string' ) ) { serverCredentials = 2 ; } // Username, key and password in per user format
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh [ user . _id ] == 'object' ) && ( typeof node . ssh [ user . _id ] . k == 'string' ) ) { serverCredentials = 3 ; } // Username and key. No password. in per user format
2022-05-02 22:19:03 +03:00
} else {
2024-05-24 19:27:22 +03:00
if ( ( typeof node . rdp == 'object' ) && ( typeof node . rdp . d == 'string' ) && ( typeof node . rdp . u == 'string' ) && ( typeof node . rdp . p == 'string' ) ) { serverCredentials = 1 ; } // Username and password in legacy format
if ( ( typeof node . rdp == 'object' ) && ( typeof node . rdp [ user . _id ] == 'object' ) && ( typeof node . rdp [ user . _id ] . d == 'string' ) && ( typeof node . rdp [ user . _id ] . u == 'string' ) && ( typeof node . rdp [ user . _id ] . p == 'string' ) ) { serverCredentials = 1 ; } // Username and password in per user format
2022-03-16 03:00:43 +03:00
}
}
2021-06-30 03:13:18 +03:00
// Render the page
2022-03-16 03:00:43 +03:00
render ( req , res , getRenderPage ( page , req , domain ) , getRenderArgs ( { cookie : req . query . ws , name : encodeURIComponent ( req . query . name ) . replace ( /'/g , '%27' ) , serverCredentials : serverCredentials , features : features } , req , domain ) ) ;
2021-06-30 03:13:18 +03:00
} ) ;
return ;
2020-07-06 22:46:38 +03:00
}
2020-06-14 03:43:12 +03:00
}
// Check the nodeid
if ( req . query . node != null ) {
var nodeidsplit = req . query . node . split ( '/' ) ;
if ( nodeidsplit . length == 1 ) {
req . query . node = 'node/' + domain . id + '/' + nodeidsplit [ 0 ] ; // Format the nodeid correctly
} else if ( nodeidsplit . length == 3 ) {
if ( ( nodeidsplit [ 0 ] != 'node' ) || ( nodeidsplit [ 1 ] != domain . id ) ) { req . query . node = null ; } // Check the nodeid format
} else {
req . query . node = null ; // Bad nodeid
}
}
// If there is no nodeid, exit now
2022-03-16 03:00:43 +03:00
if ( req . query . node == null ) { render ( req , res , getRenderPage ( page , req , domain ) , getRenderArgs ( { cookie : '' , name : '' , features : features } , req , domain ) ) ; return ; }
2020-06-14 03:43:12 +03:00
// Fetch the node from the database
obj . db . Get ( req . query . node , function ( err , nodes ) {
if ( ( err != null ) || ( nodes . length != 1 ) ) { res . sendStatus ( 404 ) ; return ; }
const node = nodes [ 0 ] ;
// Check access rights, must have remote control rights
if ( ( obj . GetNodeRights ( user , node . meshid , node . _id ) & MESHRIGHT _REMOTECONTROL ) == 0 ) { res . sendStatus ( 401 ) ; return ; }
// Figure out the target port
2022-03-16 03:00:43 +03:00
var port = 0 , serverCredentials = false ;
2021-04-29 09:22:54 +03:00
if ( page == 'ssh' ) {
// SSH port
port = 22 ;
2021-05-09 04:09:49 +03:00
if ( typeof node . sshport == 'number' ) { port = node . sshport ; }
2022-03-16 03:00:43 +03:00
// Check if we have SSH credentials for this device
2022-05-11 03:44:34 +03:00
if ( domain . allowsavingdevicecredentials !== false ) {
if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh . u == 'string' ) && ( typeof node . ssh . p == 'string' ) ) { serverCredentials = 1 ; } // Username and password
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh . k == 'string' ) && ( typeof node . ssh . kp == 'string' ) ) { serverCredentials = 2 ; } // Username, key and password
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh . k == 'string' ) ) { serverCredentials = 3 ; } // Username and key. No password.
2024-05-24 19:27:22 +03:00
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh [ user . _id ] == 'object' ) && ( typeof node . ssh [ user . _id ] . u == 'string' ) && ( typeof node . ssh [ user . _id ] . p == 'string' ) ) { serverCredentials = 1 ; } // Username and password in per user format
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh [ user . _id ] == 'object' ) && ( typeof node . ssh [ user . _id ] . k == 'string' ) && ( typeof node . ssh [ user . _id ] . kp == 'string' ) ) { serverCredentials = 2 ; } // Username, key and password in per user format
else if ( ( typeof node . ssh == 'object' ) && ( typeof node . ssh [ user . _id ] == 'object' ) && ( typeof node . ssh [ user . _id ] . k == 'string' ) ) { serverCredentials = 3 ; } // Username and key. No password. in per user format
2022-05-11 03:44:34 +03:00
}
2021-04-29 09:22:54 +03:00
} else {
// RDP port
2021-05-09 04:09:49 +03:00
port = 3389 ;
2021-04-29 09:22:54 +03:00
if ( typeof node . rdpport == 'number' ) { port = node . rdpport ; }
2022-03-16 03:00:43 +03:00
// Check if we have RDP credentials for this device
2022-05-11 03:44:34 +03:00
if ( domain . allowsavingdevicecredentials !== false ) {
if ( ( typeof node . rdp == 'object' ) && ( typeof node . rdp . d == 'string' ) && ( typeof node . rdp . u == 'string' ) && ( typeof node . rdp . p == 'string' ) ) { serverCredentials = 1 ; } // Username and password
2024-05-24 19:27:22 +03:00
if ( ( typeof node . rdp == 'object' ) && ( typeof node . rdp [ user . _id ] == 'object' ) && ( typeof node . rdp [ user . _id ] . d == 'string' ) && ( typeof node . rdp [ user . _id ] . u == 'string' ) && ( typeof node . rdp [ user . _id ] . p == 'string' ) ) { serverCredentials = 1 ; } // Username and password in per user format
2022-05-11 03:44:34 +03:00
}
2021-04-29 09:22:54 +03:00
}
2020-06-14 03:43:12 +03:00
if ( req . query . port != null ) { var qport = 0 ; try { qport = parseInt ( req . query . port ) ; } catch ( ex ) { } if ( ( typeof qport == 'number' ) && ( qport > 0 ) && ( qport < 65536 ) ) { port = qport ; } }
// Generate a cookie and respond
var cookie = parent . encodeCookie ( { userid : user . _id , domainid : user . domain , nodeid : node . _id , tcpport : port } , parent . loginCookieEncryptionKey ) ;
2022-03-16 03:00:43 +03:00
render ( req , res , getRenderPage ( page , req , domain ) , getRenderArgs ( { cookie : cookie , name : encodeURIComponent ( node . name ) . replace ( /'/g , '%27' ) , serverCredentials : serverCredentials , features : features } , req , domain ) ) ;
2020-06-14 03:43:12 +03:00
} ) ;
2020-06-12 23:56:29 +03:00
}
2022-07-20 22:57:24 +03:00
2021-02-03 10:31:44 +03:00
// Called to handle push-only requests
function handleFirebasePushOnlyRelayRequest ( req , res ) {
parent . debug ( 'email' , 'handleFirebasePushOnlyRelayRequest' ) ;
if ( ( req . body == null ) || ( req . body . msg == null ) || ( obj . parent . firebase == null ) ) { res . sendStatus ( 404 ) ; return ; }
if ( obj . parent . config . firebase . pushrelayserver == null ) { res . sendStatus ( 404 ) ; return ; }
2021-02-04 08:48:54 +03:00
if ( ( typeof obj . parent . config . firebase . pushrelayserver == 'string' ) && ( req . query . key != obj . parent . config . firebase . pushrelayserver ) ) { res . sendStatus ( 404 ) ; return ; }
2021-02-03 10:31:44 +03:00
var data = null ;
try { data = JSON . parse ( req . body . msg ) } catch ( ex ) { res . sendStatus ( 404 ) ; return ; }
if ( typeof data != 'object' ) { res . sendStatus ( 404 ) ; return ; }
if ( typeof data . pmt != 'string' ) { res . sendStatus ( 404 ) ; return ; }
if ( typeof data . payload != 'object' ) { res . sendStatus ( 404 ) ; return ; }
if ( typeof data . payload . notification != 'object' ) { res . sendStatus ( 404 ) ; return ; }
if ( typeof data . payload . notification . title != 'string' ) { res . sendStatus ( 404 ) ; return ; }
if ( typeof data . payload . notification . body != 'string' ) { res . sendStatus ( 404 ) ; return ; }
if ( typeof data . options != 'object' ) { res . sendStatus ( 404 ) ; return ; }
if ( ( data . options . priority != 'Normal' ) && ( data . options . priority != 'High' ) ) { res . sendStatus ( 404 ) ; return ; }
if ( ( typeof data . options . timeToLive != 'number' ) || ( data . options . timeToLive < 1 ) ) { res . sendStatus ( 404 ) ; return ; }
parent . debug ( 'email' , 'handleFirebasePushOnlyRelayRequest - ok' ) ;
obj . parent . firebase . sendToDevice ( { pmt : data . pmt } , data . payload , data . options , function ( id , err , errdesc ) {
if ( err == null ) { res . sendStatus ( 200 ) ; } else { res . sendStatus ( 500 ) ; }
} ) ;
}
2021-02-04 08:48:54 +03:00
// Called to handle two-way push notification relay request
function handleFirebaseRelayRequest ( ws , req ) {
parent . debug ( 'email' , 'handleFirebaseRelayRequest' ) ;
if ( obj . parent . firebase == null ) { try { ws . close ( ) ; } catch ( e ) { } return ; }
if ( obj . parent . firebase . setupRelay == null ) { try { ws . close ( ) ; } catch ( e ) { } return ; }
if ( obj . parent . config . firebase . relayserver == null ) { try { ws . close ( ) ; } catch ( e ) { } return ; }
if ( ( typeof obj . parent . config . firebase . relayserver == 'string' ) && ( req . query . key != obj . parent . config . firebase . relayserver ) ) { res . sendStatus ( 404 ) ; try { ws . close ( ) ; } catch ( e ) { } return ; }
obj . parent . firebase . setupRelay ( ws ) ;
}
2019-06-03 23:15:38 +03:00
// Called to process an agent invite request
function handleAgentInviteRequest ( req , res ) {
2019-10-12 01:39:15 +03:00
const domain = getDomain ( req ) ;
2019-11-22 05:57:57 +03:00
if ( ( domain == null ) || ( ( req . query . m == null ) && ( req . query . c == null ) ) ) { parent . debug ( 'web' , 'handleAgentInviteRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-11-22 05:57:57 +03:00
2019-06-03 23:15:38 +03:00
if ( req . query . c != null ) {
// A cookie is specified in the query string, use that
2019-06-08 03:11:56 +03:00
var cookie = obj . parent . decodeCookie ( req . query . c , obj . parent . invitationLinkEncryptionKey ) ;
2019-06-03 23:15:38 +03:00
if ( cookie == null ) { res . sendStatus ( 404 ) ; return ; }
var mesh = obj . meshes [ cookie . mid ] ;
if ( mesh == null ) { res . sendStatus ( 404 ) ; return ; }
var installflags = cookie . f ;
if ( typeof installflags != 'number' ) { installflags = 0 ; }
2022-02-24 03:14:32 +03:00
var showagents = cookie . ag ;
if ( typeof showagents != 'number' ) { showagents = 0 ; }
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleAgentInviteRequest using cookie.' ) ;
2021-01-30 01:08:05 +03:00
// Build the mobile agent URL, this is used to connect mobile devices
2022-08-25 00:10:40 +03:00
var agentServerName = obj . getWebServerName ( domain , req ) ;
2021-01-30 01:08:05 +03:00
if ( typeof obj . args . agentaliasdns == 'string' ) { agentServerName = obj . args . agentaliasdns ; }
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
var agentHttpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
if ( obj . args . agentport != null ) { agentHttpsPort = obj . args . agentport ; } // If an agent only port is enabled, use that.
if ( obj . args . agentaliasport != null ) { agentHttpsPort = obj . args . agentaliasport ; } // If an agent alias port is specified, use that.
var magenturl = 'mc://' + agentServerName + ( ( agentHttpsPort != 443 ) ? ( ':' + agentHttpsPort ) : '' ) + ( ( xdomain != '' ) ? ( '/' + xdomain ) : '' ) + ',' + obj . agentCertificateHashBase64 + ',' + mesh . _id . split ( '/' ) [ 2 ] ;
2020-06-18 23:56:04 +03:00
var meshcookie = parent . encodeCookie ( { m : mesh . _id . split ( '/' ) [ 2 ] } , parent . invitationLinkEncryptionKey ) ;
2024-01-08 09:21:54 +03:00
render ( req , res , getRenderPage ( 'agentinvite' , req , domain ) , getRenderArgs ( { meshid : meshcookie , serverport : ( ( args . aliasport != null ) ? args . aliasport : args . port ) , serverhttps : 1 , servernoproxy : ( ( domain . agentnoproxy === true ) ? '1' : '0' ) , meshname : encodeURIComponent ( mesh . name ) . replace ( /'/g , '%27' ) , installflags : installflags , showagents : showagents , magenturl : magenturl , assistanttype : ( domain . assistanttypeagentinvite ? domain . assistanttypeagentinvite : 0 ) } , req , domain ) ) ;
2019-06-03 23:15:38 +03:00
} else if ( req . query . m != null ) {
// The MeshId is specified in the query string, use that
var mesh = obj . meshes [ 'mesh/' + domain . id + '/' + req . query . m . toLowerCase ( ) ] ;
if ( mesh == null ) { res . sendStatus ( 404 ) ; return ; }
var installflags = 0 ;
if ( req . query . f ) { installflags = parseInt ( req . query . f ) ; }
if ( typeof installflags != 'number' ) { installflags = 0 ; }
2022-02-24 03:14:32 +03:00
var showagents = 0 ;
if ( req . query . f ) { showagents = parseInt ( req . query . ag ) ; }
if ( typeof showagents != 'number' ) { showagents = 0 ; }
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleAgentInviteRequest using meshid.' ) ;
2021-01-30 01:08:05 +03:00
// Build the mobile agent URL, this is used to connect mobile devices
2022-08-25 00:10:40 +03:00
var agentServerName = obj . getWebServerName ( domain , req ) ;
2021-01-30 01:08:05 +03:00
if ( typeof obj . args . agentaliasdns == 'string' ) { agentServerName = obj . args . agentaliasdns ; }
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
var agentHttpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
if ( obj . args . agentport != null ) { agentHttpsPort = obj . args . agentport ; } // If an agent only port is enabled, use that.
if ( obj . args . agentaliasport != null ) { agentHttpsPort = obj . args . agentaliasport ; } // If an agent alias port is specified, use that.
var magenturl = 'mc://' + agentServerName + ( ( agentHttpsPort != 443 ) ? ( ':' + agentHttpsPort ) : '' ) + ( ( xdomain != '' ) ? ( '/' + xdomain ) : '' ) + ',' + obj . agentCertificateHashBase64 + ',' + mesh . _id . split ( '/' ) [ 2 ] ;
2020-06-18 23:56:04 +03:00
var meshcookie = parent . encodeCookie ( { m : mesh . _id . split ( '/' ) [ 2 ] } , parent . invitationLinkEncryptionKey ) ;
2024-01-08 09:21:54 +03:00
render ( req , res , getRenderPage ( 'agentinvite' , req , domain ) , getRenderArgs ( { meshid : meshcookie , serverport : ( ( args . aliasport != null ) ? args . aliasport : args . port ) , serverhttps : 1 , servernoproxy : ( ( domain . agentnoproxy === true ) ? '1' : '0' ) , meshname : encodeURIComponent ( mesh . name ) . replace ( /'/g , '%27' ) , installflags : installflags , showagents : showagents , magenturl : magenturl , assistanttype : ( domain . assistanttypeagentinvite ? domain . assistanttypeagentinvite : 0 ) } , req , domain ) ) ;
2019-06-03 23:15:38 +03:00
}
}
2021-03-19 14:12:45 +03:00
// Called to process an agent invite request
function handleUserImageRequest ( req , res ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleUserImageRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
if ( ( req . session == null ) || ( req . session . userid == null ) ) { parent . debug ( 'web' , 'handleUserImageRequest: failed checks 2.' ) ; res . sendStatus ( 404 ) ; return ; }
2021-03-20 00:49:40 +03:00
var imageUserId = req . session . userid ;
if ( ( req . query . id != null ) ) {
var user = obj . users [ req . session . userid ] ;
if ( ( user == null ) || ( user . siteadmin == null ) && ( ( user . siteadmin & 2 ) == 0 ) ) { res . sendStatus ( 404 ) ; return ; }
imageUserId = 'user/' + domain . id + '/' + req . query . id ;
}
obj . db . Get ( 'im' + imageUserId , function ( err , docs ) {
2021-03-19 14:12:45 +03:00
if ( ( err != null ) || ( docs == null ) || ( docs . length != 1 ) || ( typeof docs [ 0 ] . image != 'string' ) ) { res . sendStatus ( 404 ) ; return ; }
var imagebase64 = docs [ 0 ] . image ;
if ( imagebase64 . startsWith ( 'data:image/png;base64,' ) ) {
res . set ( 'Content-Type' , 'image/png' ) ;
2021-03-20 00:49:40 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2021-03-19 14:12:45 +03:00
res . send ( Buffer . from ( imagebase64 . substring ( 22 ) , 'base64' ) ) ;
} else if ( imagebase64 . startsWith ( 'data:image/jpeg;base64,' ) ) {
res . set ( 'Content-Type' , 'image/jpeg' ) ;
2021-03-20 00:49:40 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2021-03-19 14:12:45 +03:00
res . send ( Buffer . from ( imagebase64 . substring ( 23 ) , 'base64' ) ) ;
} else {
res . sendStatus ( 404 ) ;
}
} ) ;
}
2019-08-26 22:20:24 +03:00
function handleDeleteAccountRequest ( req , res , direct ) {
parent . debug ( 'web' , 'handleDeleteAccountRequest()' ) ;
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { parent . debug ( 'web' , 'handleDeleteAccountRequest: failed checks.' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2021-04-17 04:55:03 +03:00
if ( req . session . loginToken != null ) { res . sendStatus ( 404 ) ; return ; } // Do not allow this command when logged in using a login token
2022-07-07 09:39:36 +03:00
if ( req . body == null ) { res . sendStatus ( 404 ) ; return ; } // Post body is empty or can't be parsed
2019-01-16 23:04:48 +03:00
2019-08-26 22:20:24 +03:00
var user = null ;
if ( req . body . authcookie ) {
// If a authentication cookie is provided, decode it here
var loginCookie = obj . parent . decodeCookie ( req . body . authcookie , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { user = obj . users [ loginCookie . userid ] ; }
} else {
// Check if the user is logged and we have all required parameters
2021-05-12 20:34:04 +03:00
if ( ! req . session || ! req . session . userid || ! req . body . apassword1 || ( req . body . apassword1 != req . body . apassword2 ) || ( req . session . userid . split ( '/' ) [ 1 ] != domain . id ) ) {
2019-08-26 22:20:24 +03:00
parent . debug ( 'web' , 'handleDeleteAccountRequest: required parameters not present.' ) ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
} else {
user = obj . users [ req . session . userid ] ;
}
}
2019-08-24 01:45:36 +03:00
if ( ! user ) { parent . debug ( 'web' , 'handleDeleteAccountRequest: user not found.' ) ; res . sendStatus ( 404 ) ; return ; }
2020-08-06 06:15:34 +03:00
if ( ( user . siteadmin != 0xFFFFFFFF ) && ( ( user . siteadmin & 1024 ) != 0 ) ) { parent . debug ( 'web' , 'handleDeleteAccountRequest: account settings locked.' ) ; res . sendStatus ( 404 ) ; return ; }
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Check if the password is correct
2021-04-16 22:42:54 +03:00
obj . authenticate ( user . _id . split ( '/' ) [ 2 ] , req . body . apassword1 , domain , function ( err , userid , passhint , loginOptions ) {
2020-03-27 10:21:44 +03:00
var deluser = obj . users [ userid ] ;
2020-06-22 00:27:10 +03:00
if ( ( userid != null ) && ( deluser != null ) ) {
2020-03-27 10:21:44 +03:00
// Remove all links to this user
if ( deluser . links != null ) {
for ( var i in deluser . links ) {
if ( i . startsWith ( 'mesh/' ) ) {
// Get the device group
2020-05-28 04:26:54 +03:00
var mesh = obj . meshes [ i ] ;
2020-03-27 10:21:44 +03:00
if ( mesh ) {
// Remove user from the mesh
if ( mesh . links [ deluser . _id ] != null ) { delete mesh . links [ deluser . _id ] ; parent . db . Set ( mesh ) ; }
// Notify mesh change
2020-05-28 04:26:54 +03:00
var change = 'Removed user ' + deluser . name + ' from group ' + mesh . name ;
2020-03-27 10:21:44 +03:00
var event = { etype : 'mesh' , userid : user . _id , username : user . name , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , action : 'meshchange' , links : mesh . links , msg : change , domain : domain . id , invite : mesh . invite } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
2020-05-28 04:26:54 +03:00
parent . DispatchEvent ( [ '*' , mesh . _id , deluser . _id , user . _id ] , obj , event ) ;
2020-03-27 10:21:44 +03:00
}
} else if ( i . startsWith ( 'node/' ) ) {
// Get the node and the rights for this node
2020-05-28 04:26:54 +03:00
obj . GetNodeWithRights ( domain , deluser , i , function ( node , rights , visible ) {
2020-03-27 10:21:44 +03:00
if ( ( node == null ) || ( node . links == null ) || ( node . links [ deluser . _id ] == null ) ) return ;
// Remove the link and save the node to the database
delete node . links [ deluser . _id ] ;
if ( Object . keys ( node . links ) . length == 0 ) { delete node . links ; }
2020-03-31 03:47:04 +03:00
db . Set ( obj . cleanDevice ( node ) ) ;
2020-03-27 10:21:44 +03:00
// Event the node change
2020-05-28 04:26:54 +03:00
var event = { etype : 'node' , userid : user . _id , username : user . name , action : 'changenode' , nodeid : node . _id , domain : domain . id , msg : ( 'Removed user device rights for ' + node . name ) , node : obj . CloneSafeNode ( node ) }
2020-03-27 10:21:44 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
2020-05-28 04:26:54 +03:00
parent . DispatchEvent ( [ '*' , node . meshid , node . _id ] , obj , event ) ;
2020-03-27 10:21:44 +03:00
} ) ;
2020-05-28 04:26:54 +03:00
} else if ( i . startsWith ( 'ugrp/' ) ) {
// Get the device group
var ugroup = obj . userGroups [ i ] ;
if ( ugroup ) {
// Remove user from the user group
if ( ugroup . links [ deluser . _id ] != null ) { delete ugroup . links [ deluser . _id ] ; parent . db . Set ( ugroup ) ; }
// Notify user group change
var change = 'Removed user ' + deluser . name + ' from user group ' + ugroup . name ;
var event = { etype : 'ugrp' , userid : user . _id , username : user . name , ugrpid : ugroup . _id , name : ugroup . name , desc : ugroup . desc , action : 'usergroupchange' , links : ugroup . links , msg : 'Removed user ' + deluser . name + ' from user group ' + ugroup . name , addUserDomain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
parent . DispatchEvent ( [ '*' , ugroup . _id , user . _id , deluser . _id ] , obj , event ) ;
}
2018-03-27 03:13:32 +03:00
}
}
}
2021-03-25 23:28:33 +03:00
obj . db . Remove ( 'ws' + deluser . _id ) ; // Remove user web state
obj . db . Remove ( 'nt' + deluser . _id ) ; // Remove notes for this user
obj . db . Remove ( 'ntp' + deluser . _id ) ; // Remove personal notes for this user
obj . db . Remove ( 'im' + deluser . _id ) ; // Remove image for this user
2018-05-17 23:37:50 +03:00
2021-04-17 04:55:03 +03:00
// Delete any login tokens
parent . db . GetAllTypeNodeFiltered ( [ 'logintoken-' + deluser . _id ] , domain . id , 'logintoken' , null , function ( err , docs ) {
if ( ( err == null ) && ( docs != null ) ) { for ( var i = 0 ; i < docs . length ; i ++ ) { parent . db . Remove ( docs [ i ] . _id , function ( ) { } ) ; } }
} ) ;
// Delete all files on the server for this account
try {
var deluserpath = obj . getServerRootFilePath ( deluser ) ;
if ( deluserpath != null ) { obj . deleteFolderRec ( deluserpath ) ; }
} catch ( e ) { }
2018-03-27 03:13:32 +03:00
// Remove the user
2020-03-27 10:21:44 +03:00
obj . db . Remove ( deluser . _id ) ;
delete obj . users [ deluser . _id ] ;
2018-08-23 02:18:01 +03:00
req . session = null ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2020-03-27 10:21:44 +03:00
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , { etype : 'user' , userid : deluser . _id , username : deluser . name , action : 'accountremove' , msg : 'Account removed' , domain : domain . id } ) ;
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleDeleteAccountRequest: removed user.' ) ;
2017-08-28 19:27:45 +03:00
} else {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleDeleteAccountRequest: auth failed.' ) ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2017-08-28 19:27:45 +03:00
}
} ) ;
}
2018-05-24 07:43:44 +03:00
2019-02-13 06:23:40 +03:00
// Check a user's password
2019-10-04 22:18:56 +03:00
obj . checkUserPassword = function ( domain , user , password , func ) {
2019-02-13 06:23:40 +03:00
// Check the old password
if ( user . passtype != null ) {
// IIS default clear or weak password hashing (SHA-1)
require ( './pass' ) . iishash ( user . passtype , password , user . salt , function ( err , hash ) {
2019-08-24 01:45:36 +03:00
if ( err ) { parent . debug ( 'web' , 'checkUserPassword: SHA-1 fail.' ) ; return func ( false ) ; }
2019-02-13 06:23:40 +03:00
if ( hash == user . hash ) {
2019-08-24 01:45:36 +03:00
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { parent . debug ( 'web' , 'checkUserPassword: SHA-1 locked.' ) ; return func ( false ) ; } // Account is locked
2019-10-04 22:18:56 +03:00
parent . debug ( 'web' , 'checkUserPassword: SHA-1 ok.' ) ;
2019-02-13 06:23:40 +03:00
return func ( true ) ; // Allow password change
}
func ( false ) ;
} ) ;
} else {
// Default strong password hashing (pbkdf2 SHA384)
2019-05-15 00:39:26 +03:00
require ( './pass' ) . hash ( password , user . salt , function ( err , hash , tag ) {
2019-08-24 01:45:36 +03:00
if ( err ) { parent . debug ( 'web' , 'checkUserPassword: pbkdf2 SHA384 fail.' ) ; return func ( false ) ; }
2019-02-13 06:23:40 +03:00
if ( hash == user . hash ) {
2019-08-24 01:45:36 +03:00
if ( ( user . siteadmin ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & 32 ) != 0 ) { parent . debug ( 'web' , 'checkUserPassword: pbkdf2 SHA384 locked.' ) ; return func ( false ) ; } // Account is locked
2019-10-04 22:18:56 +03:00
parent . debug ( 'web' , 'checkUserPassword: pbkdf2 SHA384 ok.' ) ;
2019-02-13 06:23:40 +03:00
return func ( true ) ; // Allow password change
}
func ( false ) ;
2019-05-15 00:39:26 +03:00
} , 0 ) ;
2019-02-13 06:23:40 +03:00
}
}
2020-08-16 21:10:02 +03:00
// Check a user's old passwords
2020-08-16 23:44:54 +03:00
// Callback: 0=OK, 1=OldPass, 2=CommonPass
2020-08-16 21:10:02 +03:00
obj . checkOldUserPasswords = function ( domain , user , password , func ) {
// Check how many old passwords we need to check
2020-08-21 01:33:12 +03:00
if ( ( domain . passwordrequirements != null ) && ( typeof domain . passwordrequirements . oldpasswordban == 'number' ) && ( domain . passwordrequirements . oldpasswordban > 0 ) ) {
2020-08-16 21:10:02 +03:00
if ( user . oldpasswords != null ) {
const extraOldPasswords = user . oldpasswords . length - domain . passwordrequirements . oldpasswordban ;
if ( extraOldPasswords > 0 ) { user . oldpasswords . splice ( 0 , extraOldPasswords ) ; }
}
} else {
delete user . oldpasswords ;
}
// If there is no old passwords, exit now.
var oldPassCount = 1 ;
if ( user . oldpasswords != null ) { oldPassCount += user . oldpasswords . length ; }
2020-08-16 23:44:54 +03:00
var oldPassCheckState = { response : 0 , count : oldPassCount , user : user , func : func } ;
// Test against common passwords if this feature is enabled
// Example of common passwords: 123456789, password123
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . bancommonpasswords == true ) ) {
oldPassCheckState . count ++ ;
require ( 'wildleek' ) ( password ) . then ( function ( wild ) {
if ( wild == true ) { oldPassCheckState . response = 2 ; }
if ( -- oldPassCheckState . count == 0 ) { oldPassCheckState . func ( oldPassCheckState . response ) ; }
} ) ;
}
2020-08-16 21:10:02 +03:00
// Try current password
require ( './pass' ) . hash ( password , user . salt , function oldPassCheck ( err , hash , tag ) {
2020-08-16 23:44:54 +03:00
if ( ( err == null ) && ( hash == tag . user . hash ) ) { tag . response = 1 ; }
2020-08-16 21:10:02 +03:00
if ( -- tag . count == 0 ) { tag . func ( tag . response ) ; }
} , oldPassCheckState ) ;
// Try each old password
if ( user . oldpasswords != null ) {
for ( var i in user . oldpasswords ) {
const oldpassword = user . oldpasswords [ i ] ;
// Default strong password hashing (pbkdf2 SHA384)
require ( './pass' ) . hash ( password , oldpassword . salt , function oldPassCheck ( err , hash , tag ) {
2020-08-16 23:44:54 +03:00
if ( ( err == null ) && ( hash == tag . oldPassword . hash ) ) { tag . state . response = 1 ; }
2020-08-16 21:10:02 +03:00
if ( -- tag . state . count == 0 ) { tag . state . func ( tag . state . response ) ; }
} , { oldPassword : oldpassword , state : oldPassCheckState } ) ;
}
}
}
2017-08-28 19:27:45 +03:00
// Handle password changes
2019-08-26 22:20:24 +03:00
function handlePasswordChangeRequest ( req , res , direct ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { parent . debug ( 'web' , 'handlePasswordChangeRequest: failed checks (1).' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2021-04-17 04:55:03 +03:00
if ( req . session . loginToken != null ) { res . sendStatus ( 404 ) ; return ; } // Do not allow this command when logged in using a login token
2022-07-07 09:39:36 +03:00
if ( req . body == null ) { res . sendStatus ( 404 ) ; return ; } // Post body is empty or can't be parsed
2019-01-16 23:04:48 +03:00
2017-08-28 19:27:45 +03:00
// Check if the user is logged and we have all required parameters
2021-05-12 20:34:04 +03:00
if ( ! req . session || ! req . session . userid || ! req . body . apassword0 || ! req . body . apassword1 || ( req . body . apassword1 != req . body . apassword2 ) || ( req . session . userid . split ( '/' ) [ 1 ] != domain . id ) ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handlePasswordChangeRequest: failed checks (2).' ) ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
2019-08-24 01:45:36 +03:00
}
2018-05-24 07:43:44 +03:00
2019-02-13 06:23:40 +03:00
// Get the current user
var user = obj . users [ req . session . userid ] ;
2019-08-24 01:45:36 +03:00
if ( ! user ) {
parent . debug ( 'web' , 'handlePasswordChangeRequest: user not found.' ) ;
2019-08-26 22:20:24 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
return ;
2019-08-24 01:45:36 +03:00
}
2019-02-13 06:23:40 +03:00
2020-08-06 06:15:34 +03:00
// Check account settings locked
if ( ( user . siteadmin != 0xFFFFFFFF ) && ( ( user . siteadmin & 1024 ) != 0 ) ) {
parent . debug ( 'web' , 'handlePasswordChangeRequest: account settings locked.' ) ;
2020-08-16 21:10:02 +03:00
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2020-08-06 06:15:34 +03:00
return ;
}
2019-02-13 06:23:40 +03:00
// Check old password
2020-08-16 21:10:02 +03:00
obj . checkUserPassword ( domain , user , req . body . apassword1 , function ( result ) {
2019-02-13 06:23:40 +03:00
if ( result == true ) {
2020-08-16 21:10:02 +03:00
// Check if the new password is allowed, only do this if this feature is enabled.
parent . checkOldUserPasswords ( domain , user , command . newpass , function ( result ) {
2020-08-16 23:44:54 +03:00
if ( result == 1 ) {
2020-08-16 21:10:02 +03:00
parent . debug ( 'web' , 'handlePasswordChangeRequest: old password reuse attempt.' ) ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2020-08-16 23:44:54 +03:00
} else if ( result == 2 ) {
parent . debug ( 'web' , 'handlePasswordChangeRequest: commonly used password use attempt.' ) ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
2020-08-16 21:10:02 +03:00
} else {
// Update the password
require ( './pass' ) . hash ( req . body . apassword1 , function ( err , salt , hash , tag ) {
const nowSeconds = Math . floor ( Date . now ( ) / 1000 ) ;
if ( err ) { parent . debug ( 'web' , 'handlePasswordChangeRequest: hash error.' ) ; throw err ; }
if ( domain . passwordrequirements != null ) {
// Save password hint if this feature is enabled
if ( ( domain . passwordrequirements . hint === true ) && ( req . body . apasswordhint ) ) { var hint = req . body . apasswordhint ; if ( hint . length > 250 ) hint = hint . substring ( 0 , 250 ) ; user . passhint = hint ; } else { delete user . passhint ; }
// Save previous password if this feature is enabled
if ( ( typeof domain . passwordrequirements . oldpasswordban == 'number' ) && ( domain . passwordrequirements . oldpasswordban > 0 ) ) {
if ( user . oldpasswords == null ) { user . oldpasswords = [ ] ; }
user . oldpasswords . push ( { salt : user . salt , hash : user . hash , start : user . passchange , end : nowSeconds } ) ;
const extraOldPasswords = user . oldpasswords . length - domain . passwordrequirements . oldpasswordban ;
if ( extraOldPasswords > 0 ) { user . oldpasswords . splice ( 0 , extraOldPasswords ) ; }
}
}
user . salt = salt ;
user . hash = hash ;
2021-12-05 00:08:17 +03:00
user . passchange = user . access = nowSeconds ;
2020-08-16 21:10:02 +03:00
delete user . passtype ;
obj . db . SetUser ( user ) ;
req . session . viewmode = 2 ;
if ( direct === true ) { handleRootRequestEx ( req , res , domain ) ; } else { res . redirect ( domain . url + getQueryPortion ( req ) ) ; }
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , { etype : 'user' , userid : user . _id , username : user . name , action : 'passchange' , msg : 'Account password changed: ' + user . name , domain : domain . id } ) ;
} , 0 ) ;
}
} ) ;
2019-02-13 06:23:40 +03:00
}
2017-08-28 19:27:45 +03:00
} ) ;
}
2022-09-07 17:40:36 +03:00
// Called when a strategy login occurred
// This is called after a successful Oauth to Twitter, Google, GitHub...
2020-05-14 11:41:03 +03:00
function handleStrategyLogin ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
2022-10-27 00:45:22 +03:00
if ( ( req . user != null ) && ( req . user . sid != null ) && ( req . user . strategy != null ) ) {
2024-03-04 03:03:27 +03:00
const strategy = domain . authstrategies [ req . user . strategy ] ;
const groups = { 'enabled' : typeof strategy . groups == 'object' }
parent . authLog ( req . user . strategy . toUpperCase ( ) , ` User Authorized: ${ JSON . stringify ( req . user ) } ` ) ;
if ( groups . enabled ) { // Groups only available for OIDC strategy currently
2024-05-21 18:05:00 +03:00
groups . userMemberships = obj . common . convertStrArray ( req . user . groups ) ;
groups . syncEnabled = ( strategy . groups . sync === true || strategy . groups . sync ? . filter ) ? true : false ;
groups . syncMemberships = [ ] ;
groups . siteAdminEnabled = strategy . groups . siteadmin ? true : false ;
groups . grantAdmin = false ;
groups . revokeAdmin = strategy . groups . revokeAdmin ? strategy . groups . revokeAdmin : true ;
groups . requiredGroups = obj . common . convertStrArray ( strategy . groups . required ) ;
groups . siteAdmin = obj . common . convertStrArray ( strategy . groups . siteadmin ) ;
groups . syncFilter = obj . common . convertStrArray ( strategy . groups . sync ? . filter ) ;
2024-03-04 03:03:27 +03:00
// Fancy Logs
2024-05-21 18:05:00 +03:00
let groupMessage = '' ;
2024-03-04 03:03:27 +03:00
if ( groups . userMemberships . length == 1 ) { groupMessage = ` Found membership: " ${ groups . userMemberships [ 0 ] } " ` }
else { groupMessage = ` Found ${ groups . userMemberships . length } memberships: [" ${ groups . userMemberships . join ( '", "' ) } "] ` }
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " ` + groupMessage ) ;
// Check user membership in required groups
2024-05-21 18:05:00 +03:00
if ( groups . requiredGroups . length > 0 ) {
2024-03-04 03:03:27 +03:00
let match = false
for ( var i in groups . requiredGroups ) {
if ( groups . userMemberships . indexOf ( groups . requiredGroups [ i ] ) != - 1 ) {
match = true ;
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " Membership to required group found: " ${ groups . requiredGroups [ i ] } " ` ) ;
2022-09-06 07:29:22 +03:00
}
}
2024-03-04 03:03:27 +03:00
if ( match === false ) {
2024-05-21 18:05:00 +03:00
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " Login denied. No membership to required group. ` ) ;
2022-09-07 00:58:37 +03:00
req . session . loginmode = 1 ;
2024-03-04 03:03:27 +03:00
req . session . messageid = 111 ; // Access Denied.
2022-09-07 00:58:37 +03:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ;
return ;
}
2022-09-06 07:29:22 +03:00
}
2024-03-04 03:03:27 +03:00
// Check user membership in admin groups
if ( groups . siteAdminEnabled === true ) {
groups . grantAdmin = false ;
for ( var i in strategy . groups . siteadmin ) {
if ( groups . userMemberships . indexOf ( strategy . groups . siteadmin [ i ] ) >= 0 ) {
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " User membership found in site admin group: " ${ strategy . groups . siteadmin [ i ] } " ` ) ;
groups . siteAdmin = strategy . groups . siteadmin [ i ] ;
groups . grantAdmin = true ;
break ;
}
2022-09-06 07:29:22 +03:00
}
}
2024-03-04 03:03:27 +03:00
// Check if we need to sync user-memberships (IdP) with user-groups (meshcentral)
if ( groups . syncEnabled === true ) {
2024-05-21 18:05:00 +03:00
if ( groups . syncFilter . length > 0 ) { // config.json has specified sync.filter so loop and use it
for ( var i in groups . syncFilter ) {
if ( groups . userMemberships . indexOf ( groups . syncFilter [ i ] ) >= 0 ) { groups . syncMemberships . push ( groups . syncFilter [ i ] ) ; }
}
} else { // config.json doesnt have sync.filter specified so we are going to sync all the users groups from oidc instead
for ( var i in groups . userMemberships ) {
groups . syncMemberships . push ( groups . userMemberships [ i ] ) ;
}
2022-09-06 07:29:22 +03:00
}
2024-03-04 03:03:27 +03:00
if ( groups . syncMemberships . length > 0 ) {
2024-05-21 18:05:00 +03:00
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " User memberships to sync: ${ groups . syncMemberships . join ( ', ' ) } ` ) ;
2024-03-04 03:03:27 +03:00
} else {
groups . syncMemberships = null ;
2024-05-21 18:05:00 +03:00
groups . syncEnabled = false ;
if ( groups . syncFilter . length > 0 ) {
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " No sync memberships found using filters: ${ groups . syncFilter . join ( ', ' ) } ` ) ;
} else {
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " No sync memberships found ` ) ;
}
2022-09-06 10:30:07 +03:00
}
2022-09-06 07:29:22 +03:00
}
}
2022-09-06 10:30:07 +03:00
// Check if the user already exists
2020-05-25 09:22:49 +03:00
const userid = 'user/' + domain . id + '/' + req . user . sid ;
2020-05-14 11:41:03 +03:00
var user = obj . users [ userid ] ;
if ( user == null ) {
2020-05-25 09:22:49 +03:00
var newAccountAllowed = false ;
2020-05-25 22:52:13 +03:00
var newAccountRealms = null ;
2020-05-25 09:22:49 +03:00
if ( domain . newaccounts === true ) { newAccountAllowed = true ; }
2020-05-25 22:52:13 +03:00
if ( obj . common . validateStrArray ( domain . newaccountrealms ) ) { newAccountRealms = domain . newaccountrealms ; }
2024-03-04 03:03:27 +03:00
if ( domain . authstrategies [ req . user . strategy ] ) {
if ( domain . authstrategies [ req . user . strategy ] . newaccounts === true ) { newAccountAllowed = true ; }
if ( obj . common . validateStrArray ( domain . authstrategies [ req . user . strategy ] . newaccountrealms ) ) { newAccountRealms = domain . authstrategies [ req . user . strategy ] . newaccountrealms ; }
2020-05-25 22:52:13 +03:00
}
2020-05-25 09:22:49 +03:00
if ( newAccountAllowed === true ) {
2020-05-15 03:06:55 +03:00
// Create the user
2024-03-04 03:03:27 +03:00
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : USER: " ${ req . user . sid } " Creating new login user: " ${ userid } " ` ) ;
2021-12-05 00:08:17 +03:00
user = { type : 'user' , _id : userid , name : req . user . name , email : req . user . email , creation : Math . floor ( Date . now ( ) / 1000 ) , login : Math . floor ( Date . now ( ) / 1000 ) , access : Math . floor ( Date . now ( ) / 1000 ) , domain : domain . id } ;
2024-03-04 03:03:27 +03:00
if ( req . user . email != null ) { user . email = req . user . email ; user . emailVerified = req . user . email _verified ? req . user . email _verified : true ; }
2020-05-25 22:52:13 +03:00
if ( domain . newaccountsrights ) { user . siteadmin = domain . newaccountsrights ; } // New accounts automatically assigned server rights.
2024-03-04 03:03:27 +03:00
if ( domain . authstrategies [ req . user . strategy ] . newaccountsrights ) { user . siteadmin = obj . common . meshServerRightsArrayToNumber ( domain . authstrategies [ req . user . strategy ] . newaccountsrights ) ; } // If there are specific SSO server rights, use these instead.
2020-05-25 22:52:13 +03:00
if ( newAccountRealms ) { user . groups = newAccountRealms ; } // New accounts automatically part of some groups (Realms).
2020-05-15 03:06:55 +03:00
obj . users [ userid ] = user ;
2020-05-28 04:26:54 +03:00
// Auto-join any user groups
var newaccountsusergroups = null ;
if ( typeof domain . newaccountsusergroups == 'object' ) { newaccountsusergroups = domain . newaccountsusergroups ; }
2024-03-04 03:03:27 +03:00
if ( typeof domain . authstrategies [ req . user . strategy ] . newaccountsusergroups == 'object' ) { newaccountsusergroups = domain . authstrategies [ req . user . strategy ] . newaccountsusergroups ; }
2020-05-28 04:26:54 +03:00
if ( newaccountsusergroups ) {
for ( var i in newaccountsusergroups ) {
var ugrpid = newaccountsusergroups [ i ] ;
if ( ugrpid . indexOf ( '/' ) < 0 ) { ugrpid = 'ugrp/' + domain . id + '/' + ugrpid ; }
var ugroup = obj . userGroups [ ugrpid ] ;
if ( ugroup != null ) {
// Add group to the user
if ( user . links == null ) { user . links = { } ; }
user . links [ ugroup . _id ] = { rights : 1 } ;
// Add user to the group
ugroup . links [ user . _id ] = { userid : user . _id , name : user . name , rights : 1 } ;
db . Set ( ugroup ) ;
// Notify user group change
var event = { etype : 'ugrp' , ugrpid : ugroup . _id , name : ugroup . name , desc : ugroup . desc , action : 'usergroupchange' , links : ugroup . links , msg : 'Added user ' + user . name + ' to user group ' + ugroup . name , addUserDomain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
parent . DispatchEvent ( [ '*' , ugroup . _id , user . _id ] , obj , event ) ;
}
}
}
2024-03-04 03:03:27 +03:00
if ( groups . enabled === true ) {
2022-09-06 10:30:07 +03:00
// Sync the user groups if enabled
2024-03-04 03:03:27 +03:00
if ( groups . syncEnabled === true ) {
// Set groupType to the preset name if it exists, otherwise use the strategy name
const groupType = domain . authstrategies [ req . user . strategy ] . custom ? . preset ? domain . authstrategies [ req . user . strategy ] . custom . preset : req . user . strategy ;
syncExternalUserGroups ( domain , user , groups . syncMemberships , groupType ) ;
}
2022-09-06 10:30:07 +03:00
// See if the user is a member of the site admin group.
2024-03-04 03:03:27 +03:00
if ( groups . grantAdmin === true ) {
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " Granting site admin privilages ` ) ;
2022-09-06 10:30:07 +03:00
user . siteadmin = 0xFFFFFFFF ;
}
2022-09-06 07:29:22 +03:00
}
2020-05-28 04:26:54 +03:00
// Save the user
2020-05-15 03:06:55 +03:00
obj . db . SetUser ( user ) ;
// Event user creation
var targets = [ '*' , 'server-users' ] ;
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountcreate' , msg : 'Account created, username is ' + user . name , domain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
parent . DispatchEvent ( targets , obj , event ) ;
2020-05-25 09:22:49 +03:00
req . session . userid = userid ;
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2022-03-16 01:03:58 +03:00
// Notify account login using SSO
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2022-09-02 08:06:08 +03:00
const ua = obj . getUserAgentInfo ( req ) ;
2022-03-16 01:03:58 +03:00
const loginEvent = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'login' , msgid : 107 , msgArgs : [ req . clientIp , ua . browserStr , ua . osStr ] , msg : 'Account login' , domain : domain . id , ip : req . clientIp , userAgent : req . headers [ 'user-agent' ] , twoFactorType : 'sso' } ;
obj . parent . DispatchEvent ( targets , obj , loginEvent ) ;
2020-05-15 03:06:55 +03:00
} else {
// New users not allowed
2024-03-04 03:03:27 +03:00
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : LOGIN FAILED: USER: " ${ req . user . sid } " New accounts are not allowed ` ) ;
2021-05-12 20:34:04 +03:00
req . session . loginmode = 1 ;
2020-05-15 03:06:55 +03:00
req . session . messageid = 100 ; // Unable to create account.
res . redirect ( domain . url + getQueryPortion ( req ) ) ;
return ;
}
2022-09-06 07:29:22 +03:00
} else { // Login success
// Check for basic changes
var userChanged = false ;
if ( ( req . user . name != null ) && ( req . user . name != user . name ) ) { user . name = req . user . name ; userChanged = true ; }
if ( ( req . user . email != null ) && ( req . user . email != user . email ) ) { user . email = req . user . email ; user . emailVerified = true ; userChanged = true ; }
2024-03-04 03:03:27 +03:00
if ( groups . enabled === true ) {
// Sync the user groups if enabled
if ( groups . syncEnabled === true ) {
syncExternalUserGroups ( domain , user , groups . syncMemberships , req . user . strategy )
}
2022-09-07 17:31:02 +03:00
// See if the user is a member of the site admin group.
2024-03-04 03:03:27 +03:00
if ( groups . siteAdminEnabled === true ) {
if ( groups . grantAdmin === true ) {
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " Granting site admin privilages ` ) ;
if ( user . siteadmin !== 0xFFFFFFFF ) { user . siteadmin = 0xFFFFFFFF ; userChanged = true ; }
} else if ( ( groups . revokeAdmin === true ) && ( user . siteadmin === 0xFFFFFFFF ) ) {
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : GROUPS: USER: " ${ req . user . sid } " Revoking site admin privilages. ` ) ;
2022-09-07 17:31:02 +03:00
delete user . siteadmin ;
userChanged = true ;
}
2022-09-06 07:29:22 +03:00
}
}
// Update db record for user if there are changes detected
if ( userChanged ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : CHANGED: USER: " ${ req . user . sid } " Updating user database entry ` ) ;
2020-05-14 11:41:03 +03:00
obj . db . SetUser ( user ) ;
2020-05-20 10:39:17 +03:00
2022-03-16 01:03:58 +03:00
// Event user change
2020-05-20 10:39:17 +03:00
var targets = [ '*' , 'server-users' ] ;
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msg : 'Account changed' , domain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
parent . DispatchEvent ( targets , obj , event ) ;
2020-05-14 11:41:03 +03:00
}
2020-05-25 09:22:49 +03:00
req . session . userid = userid ;
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2022-03-16 01:03:58 +03:00
// Notify account login using SSO
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
2022-09-02 08:06:08 +03:00
const ua = obj . getUserAgentInfo ( req ) ;
2022-03-16 01:03:58 +03:00
const loginEvent = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'login' , msgid : 107 , msgArgs : [ req . clientIp , ua . browserStr , ua . osStr ] , msg : 'Account login' , domain : domain . id , ip : req . clientIp , userAgent : req . headers [ 'user-agent' ] , twoFactorType : 'sso' } ;
obj . parent . DispatchEvent ( targets , obj , loginEvent ) ;
2024-03-04 03:03:27 +03:00
parent . authLog ( 'handleStrategyLogin' , ` ${ req . user . strategy . toUpperCase ( ) } : LOGIN SUCCESS: USER: " ${ req . user . sid } " ` ) ;
2020-05-14 11:41:03 +03:00
}
2024-06-01 22:31:25 +03:00
} else if ( req . session && req . session . userid && obj . users [ req . session . userid ] ) {
parent . authLog ( 'handleStrategyLogin' , ` User Already Authorised " ${ ( req . session . passport && req . session . passport . user ) ? req . session . passport . user : req . session . userid } " ` ) ;
2024-03-04 03:03:27 +03:00
} else {
2024-06-01 22:31:25 +03:00
parent . authLog ( 'handleStrategyLogin' , ` LOGIN FAILED: REQUEST CONTAINS NO USER OR SID ` ) ;
2024-03-04 03:03:27 +03:00
}
2020-05-14 11:41:03 +03:00
//res.redirect(domain.url); // This does not handle cookie correctly.
res . set ( 'Content-Type' , 'text/html' ) ;
2024-07-17 17:19:05 +03:00
let url = domain . url ;
2024-09-27 07:09:34 +03:00
if ( Object . keys ( req . query ) . length > 0 ) { url += "?" + Object . keys ( req . query ) . map ( function ( key ) { return encodeURIComponent ( key ) + "=" + encodeURIComponent ( req . query [ key ] ) ; } ) . join ( "&" ) ; }
2024-07-17 17:19:05 +03:00
res . end ( '<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>' ) ;
2020-05-14 11:41:03 +03:00
}
2017-08-28 19:27:45 +03:00
// Indicates that any request to "/" should render "default" or "login" depending on login state
2019-08-26 22:20:24 +03:00
function handleRootRequest ( req , res , direct ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-08-23 21:51:48 +03:00
if ( ! obj . args ) { parent . debug ( 'web' , 'handleRootRequest: no obj.args.' ) ; res . sendStatus ( 500 ) ; return ; }
2018-01-03 03:52:49 +03:00
2022-05-19 04:01:34 +03:00
// If a HTTP header is required, check new UserRequiredHttpHeader
if ( domain . userrequiredhttpheader && ( typeof domain . userrequiredhttpheader == 'object' ) ) { var ok = false ; for ( var i in req . headers ) { if ( domain . userrequiredhttpheader [ i . toLowerCase ( ) ] == req . headers [ i ] ) { ok = true ; } } if ( ok == false ) { res . sendStatus ( 404 ) ; return ; } }
2021-04-17 04:55:03 +03:00
// If the session is expired, clear it.
if ( ( req . session != null ) && ( typeof req . session . expire == 'number' ) && ( ( req . session . expire - Date . now ( ) ) <= 0 ) ) { for ( var i in req . session ) { delete req . session [ i ] ; } }
2020-11-21 03:43:58 +03:00
// Check if we are in maintenance mode
2021-05-25 21:17:23 +03:00
if ( ( parent . config . settings . maintenancemode != null ) && ( req . query . loginscreen !== '1' ) ) {
2020-11-21 03:43:58 +03:00
parent . debug ( 'web' , 'handleLoginRequest: Server under maintenance.' ) ;
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 3 , msgid : 13 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2020-11-21 03:43:58 +03:00
return ;
}
2021-10-08 23:24:04 +03:00
// If set and there is no user logged in, redirect the root page. Make sure not to redirect if /login is used
if ( ( typeof domain . unknownuserrootredirect == 'string' ) && ( ( req . session == null ) || ( req . session . userid == null ) ) ) {
var q = require ( 'url' ) . parse ( req . url , true ) ;
if ( ! q . pathname . endsWith ( '/login' ) ) { res . redirect ( domain . unknownuserrootredirect + getQueryPortion ( req ) ) ; return ; }
}
2022-07-15 23:13:53 +03:00
2018-01-03 03:52:49 +03:00
if ( ( domain . sspi != null ) && ( ( req . query . login == null ) || ( obj . parent . loginCookieEncryptionKey == null ) ) ) {
// Login using SSPI
2019-08-24 01:45:36 +03:00
domain . sspi . authenticate ( req , res , function ( err ) {
if ( ( err != null ) || ( req . connection . user == null ) ) {
2022-09-02 08:06:08 +03:00
obj . parent . authLog ( 'https' , 'Failed SSPI-auth for ' + req . connection . user + ' from ' + req . clientIp + ' port ' + req . connection . remotePort , { useragent : req . headers [ 'user-agent' ] } ) ;
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequest: SSPI auth required.' ) ;
2022-09-03 10:32:49 +03:00
try { res . sendStatus ( 401 ) ; } catch ( ex ) { } // sspi.authenticate() should already have responded to this request.
2019-08-24 01:45:36 +03:00
} else {
parent . debug ( 'web' , 'handleRootRequest: SSPI auth ok.' ) ;
2019-08-26 22:20:24 +03:00
handleRootRequestEx ( req , res , domain , direct ) ;
2019-08-24 01:45:36 +03:00
}
} ) ;
2018-12-20 23:12:24 +03:00
} else if ( req . query . user && req . query . pass ) {
2020-02-17 21:24:32 +03:00
// User credentials are being passed in the URL. WARNING: Putting credentials in a URL is bad security... but people are requesting this option.
2021-04-16 22:42:54 +03:00
obj . authenticate ( req . query . user , req . query . pass , domain , function ( err , userid , passhint , loginOptions ) {
2023-07-05 20:15:18 +03:00
// 2FA is not supported in URL authentication method. If user has 2FA enabled, this login method fails.
2024-03-10 10:46:01 +03:00
var user = obj . users [ userid ] ;
2024-03-01 18:45:39 +03:00
if ( ( err == null ) && checkUserOneTimePasswordRequired ( domain , user , req , loginOptions ) == true ) {
2023-07-05 20:15:18 +03:00
handleRootRequestEx ( req , res , domain , direct ) ;
} else if ( ( userid != null ) && ( err == null ) ) {
2022-08-22 00:05:51 +03:00
// Login success
parent . debug ( 'web' , 'handleRootRequest: user/pass in URL auth ok.' ) ;
req . session . userid = userid ;
delete req . session . currentNode ;
req . session . ip = req . clientIp ; // Bind this session to the IP address of the request
setSessionRandom ( req ) ;
2022-09-02 08:06:08 +03:00
obj . parent . authLog ( 'https' , 'Accepted password for ' + userid + ' from ' + req . clientIp + ' port ' + req . connection . remotePort , { useragent : req . headers [ 'user-agent' ] , sessionid : req . session . x } ) ;
2022-08-22 00:05:51 +03:00
handleRootRequestEx ( req , res , domain , direct ) ;
} else {
// Login failed
handleRootRequestEx ( req , res , domain , direct ) ;
}
2019-04-11 23:41:51 +03:00
} ) ;
2021-04-17 09:15:57 +03:00
} else if ( ( req . session != null ) && ( typeof req . session . loginToken == 'string' ) ) {
// Check if the loginToken is still valid
obj . db . Get ( 'logintoken-' + req . session . loginToken , function ( err , docs ) {
if ( ( err != null ) || ( docs == null ) || ( docs . length != 1 ) || ( docs [ 0 ] . tokenUser != req . session . loginToken ) ) { for ( var i in req . session ) { delete req . session [ i ] ; } }
handleRootRequestEx ( req , res , domain , direct ) ; // Login using a different system
} ) ;
2018-01-03 03:52:49 +03:00
} else {
// Login using a different system
2019-08-26 22:20:24 +03:00
handleRootRequestEx ( req , res , domain , direct ) ;
2018-01-03 03:52:49 +03:00
}
}
2019-08-26 22:20:24 +03:00
function handleRootRequestEx ( req , res , domain , direct ) {
2021-07-15 23:58:30 +03:00
var nologout = false , user = null ;
2020-10-02 23:17:53 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2018-01-03 03:52:49 +03:00
2017-08-28 19:27:45 +03:00
// Check if we have an incomplete domain name in the path
2019-08-24 01:45:36 +03:00
if ( ( domain . id != '' ) && ( domain . dns == null ) && ( req . url . split ( '/' ) . length == 2 ) ) {
parent . debug ( 'web' , 'handleRootRequestEx: incomplete domain name in the path.' ) ;
2019-08-26 22:20:24 +03:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ; // BAD***
2019-08-24 01:45:36 +03:00
return ;
}
2018-01-03 03:52:49 +03:00
2017-08-28 19:27:45 +03:00
if ( obj . args . nousers == true ) {
// If in single user mode, setup things here.
2021-05-12 20:34:04 +03:00
delete req . session . loginmode ;
2017-08-28 19:27:45 +03:00
req . session . userid = 'user/' + domain . id + '/~' ;
2021-05-12 20:34:04 +03:00
delete req . session . currentNode ;
2020-05-22 07:25:11 +03:00
req . session . ip = req . clientIp ; // Bind this session to the IP address of the request
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2017-09-15 21:45:06 +03:00
if ( obj . users [ req . session . userid ] == null ) {
2017-08-28 19:27:45 +03:00
// Create the dummy user ~ with impossible password
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: created dummy user in nouser mode.' ) ;
2019-06-27 08:13:56 +03:00
obj . users [ req . session . userid ] = { type : 'user' , _id : req . session . userid , name : '~' , email : '~' , domain : domain . id , siteadmin : 4294967295 } ;
2017-08-28 19:27:45 +03:00
obj . db . SetUser ( obj . users [ req . session . userid ] ) ;
}
2018-09-12 00:28:47 +03:00
} else if ( obj . args . user && obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) {
2017-08-28 19:27:45 +03:00
// If a default user is active, setup the session here.
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: auth using default user.' ) ;
2021-05-12 20:34:04 +03:00
delete req . session . loginmode ;
2017-08-28 19:27:45 +03:00
req . session . userid = 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ;
2021-05-12 20:34:04 +03:00
delete req . session . currentNode ;
2020-05-22 07:25:11 +03:00
req . session . ip = req . clientIp ; // Bind this session to the IP address of the request
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2017-12-14 01:52:57 +03:00
} else if ( req . query . login && ( obj . parent . loginCookieEncryptionKey != null ) ) {
var loginCookie = obj . parent . decodeCookie ( req . query . login , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2022-09-07 17:40:36 +03:00
//if ((loginCookie != null) && (loginCookie.ip != null) && !checkCookieIp(loginCookie.ip, req.clientIp)) { loginCookie = null; } // If the cookie is bound to an IP address, check here.
2017-12-14 01:52:57 +03:00
if ( ( loginCookie != null ) && ( loginCookie . a == 3 ) && ( loginCookie . u != null ) && ( loginCookie . u . split ( '/' ) [ 1 ] == domain . id ) ) {
// If a login cookie was provided, setup the session here.
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: cookie auth ok.' ) ;
2021-05-12 20:34:04 +03:00
delete req . session . loginmode ;
2017-12-14 01:52:57 +03:00
req . session . userid = loginCookie . u ;
2021-05-12 20:34:04 +03:00
delete req . session . currentNode ;
2020-05-22 07:25:11 +03:00
req . session . ip = req . clientIp ; // Bind this session to the IP address of the request
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2019-08-24 01:45:36 +03:00
} else {
parent . debug ( 'web' , 'handleRootRequestEx: cookie auth failed.' ) ;
2017-12-14 01:52:57 +03:00
}
2018-01-03 03:52:49 +03:00
} else if ( domain . sspi != null ) {
// SSPI login (Windows only)
//console.log(req.connection.user, req.connection.userSid);
if ( ( req . connection . user == null ) || ( req . connection . userSid == null ) ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: SSPI no user auth.' ) ;
2018-01-03 03:52:49 +03:00
res . sendStatus ( 404 ) ; return ;
} else {
nologout = true ;
2019-01-22 01:05:50 +03:00
req . session . userid = 'user/' + domain . id + '/' + req . connection . user . toLowerCase ( ) ;
2018-01-03 03:52:49 +03:00
req . session . usersid = req . connection . userSid ;
req . session . usersGroups = req . connection . userGroups ;
2021-05-12 20:34:04 +03:00
delete req . session . currentNode ;
2020-05-22 07:25:11 +03:00
req . session . ip = req . clientIp ; // Bind this session to the IP address of the request
2022-01-07 02:05:45 +03:00
setSessionRandom ( req ) ;
2022-09-02 08:06:08 +03:00
obj . parent . authLog ( 'https' , 'Accepted SSPI-auth for ' + req . connection . user + ' from ' + req . clientIp + ' port ' + req . connection . remotePort , { useragent : req . headers [ 'user-agent' ] , sessionid : req . session . x } ) ;
2018-01-03 03:52:49 +03:00
// Check if this user exists, create it if not.
2018-08-30 22:05:23 +03:00
user = obj . users [ req . session . userid ] ;
2018-01-03 03:52:49 +03:00
if ( ( user == null ) || ( user . sid != req . session . usersid ) ) {
// Create the domain user
2021-12-05 00:08:17 +03:00
var usercount = 0 , user2 = { type : 'user' , _id : req . session . userid , name : req . connection . user , domain : domain . id , sid : req . session . usersid , creation : Math . floor ( Date . now ( ) / 1000 ) , login : Math . floor ( Date . now ( ) / 1000 ) , access : Math . floor ( Date . now ( ) / 1000 ) } ;
2019-05-13 05:14:24 +03:00
if ( domain . newaccountsrights ) { user2 . siteadmin = domain . newaccountsrights ; }
2020-05-25 22:52:13 +03:00
if ( obj . common . validateStrArray ( domain . newaccountrealms ) ) { user2 . groups = domain . newaccountrealms ; }
2018-01-03 03:52:49 +03:00
for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { usercount ++ ; } }
2019-06-27 08:13:56 +03:00
if ( usercount == 0 ) { user2 . siteadmin = 4294967295 ; } // If this is the first user, give the account site admin.
2020-05-28 04:26:54 +03:00
// Auto-join any user groups
if ( typeof domain . newaccountsusergroups == 'object' ) {
for ( var i in domain . newaccountsusergroups ) {
var ugrpid = domain . newaccountsusergroups [ i ] ;
if ( ugrpid . indexOf ( '/' ) < 0 ) { ugrpid = 'ugrp/' + domain . id + '/' + ugrpid ; }
var ugroup = obj . userGroups [ ugrpid ] ;
if ( ugroup != null ) {
// Add group to the user
if ( user2 . links == null ) { user2 . links = { } ; }
user2 . links [ ugroup . _id ] = { rights : 1 } ;
// Add user to the group
ugroup . links [ user2 . _id ] = { userid : user2 . _id , name : user2 . name , rights : 1 } ;
db . Set ( ugroup ) ;
// Notify user group change
var event = { etype : 'ugrp' , ugrpid : ugroup . _id , name : ugroup . name , desc : ugroup . desc , action : 'usergroupchange' , links : ugroup . links , msg : 'Added user ' + user2 . name + ' to user group ' + ugroup . name , addUserDomain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
parent . DispatchEvent ( [ '*' , ugroup . _id , user2 . _id ] , obj , event ) ;
}
}
}
2018-08-30 22:05:23 +03:00
obj . users [ req . session . userid ] = user2 ;
obj . db . SetUser ( user2 ) ;
2019-07-30 02:35:48 +03:00
var event = { etype : 'user' , userid : req . session . userid , username : req . connection . user , account : obj . CloneSafeUser ( user2 ) , action : 'accountcreate' , msg : 'Domain account created, user ' + req . connection . user , domain : domain . id } ;
2019-05-30 00:36:14 +03:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to create the user. Another event will come.
obj . parent . DispatchEvent ( [ '*' , 'server-users' ] , obj , event ) ;
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: SSPI new domain user.' ) ;
2018-01-03 03:52:49 +03:00
}
}
2017-08-28 19:27:45 +03:00
}
2018-01-03 03:52:49 +03:00
2018-12-21 01:14:37 +03:00
// Figure out the minimal password requirement
var passRequirements = null ;
if ( domain . passwordrequirements != null ) {
2019-12-12 22:44:03 +03:00
if ( domain . passrequirementstr == null ) {
var passRequirements = { } ;
if ( typeof domain . passwordrequirements . min == 'number' ) { passRequirements . min = domain . passwordrequirements . min ; }
if ( typeof domain . passwordrequirements . max == 'number' ) { passRequirements . max = domain . passwordrequirements . max ; }
if ( typeof domain . passwordrequirements . upper == 'number' ) { passRequirements . upper = domain . passwordrequirements . upper ; }
if ( typeof domain . passwordrequirements . lower == 'number' ) { passRequirements . lower = domain . passwordrequirements . lower ; }
if ( typeof domain . passwordrequirements . numeric == 'number' ) { passRequirements . numeric = domain . passwordrequirements . numeric ; }
if ( typeof domain . passwordrequirements . nonalpha == 'number' ) { passRequirements . nonalpha = domain . passwordrequirements . nonalpha ; }
domain . passwordrequirementsstr = encodeURIComponent ( JSON . stringify ( passRequirements ) ) ;
}
2018-12-21 01:14:37 +03:00
passRequirements = domain . passwordrequirementsstr ;
}
2018-11-07 06:41:21 +03:00
// If a user exists and is logged in, serve the default app, otherwise server the login app.
if ( req . session && req . session . userid && obj . users [ req . session . userid ] ) {
2021-01-26 22:46:22 +03:00
const user = obj . users [ req . session . userid ] ;
2021-02-10 04:36:30 +03:00
2021-05-25 21:17:23 +03:00
// Check if we are in maintenance mode
if ( ( parent . config . settings . maintenancemode != null ) && ( user . siteadmin != 4294967295 ) ) {
req . session . messageid = 115 ; // Server under maintenance
req . session . loginmode = 1 ;
res . redirect ( domain . url ) ;
return ;
}
2021-02-10 04:36:30 +03:00
// If the request has a "meshmessengerid", redirect to MeshMessenger
// This situation happens when you get a push notification for a chat session, but are not logged in.
if ( req . query . meshmessengerid != null ) {
2024-09-27 07:09:34 +03:00
res . redirect ( domain . url + 'messenger?id=' + encodeURIComponent ( req . query . meshmessengerid ) + ( ( req . query . key != null ) ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) ) ;
2021-02-10 04:36:30 +03:00
return ;
}
2021-01-26 22:46:22 +03:00
const xdbGetFunc = function dbGetFunc ( err , states ) {
2021-05-12 20:34:04 +03:00
if ( dbGetFunc . req . session . userid . split ( '/' ) [ 1 ] != domain . id ) { // Check if the session is for the correct domain
2021-01-26 22:46:22 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: incorrect domain.' ) ;
dbGetFunc . req . session = null ;
dbGetFunc . res . redirect ( domain . url + getQueryPortion ( dbGetFunc . req ) ) ; // BAD***
return ;
}
2019-02-11 07:13:03 +03:00
2021-01-26 22:46:22 +03:00
// Check if this is a locked account
if ( ( dbGetFunc . user . siteadmin != null ) && ( ( dbGetFunc . user . siteadmin & 32 ) != 0 ) && ( dbGetFunc . user . siteadmin != 0xFFFFFFFF ) ) {
// Locked account
parent . debug ( 'web' , 'handleRootRequestEx: locked account.' ) ;
delete dbGetFunc . req . session . userid ;
delete dbGetFunc . req . session . currentNode ;
delete dbGetFunc . req . session . passhint ;
delete dbGetFunc . req . session . cuserid ;
dbGetFunc . req . session . messageid = 110 ; // Account locked.
dbGetFunc . res . redirect ( domain . url + getQueryPortion ( dbGetFunc . req ) ) ; // BAD***
return ;
}
2019-02-11 07:13:03 +03:00
2021-01-26 22:46:22 +03:00
var viewmode = 1 ;
if ( dbGetFunc . req . session . viewmode ) {
viewmode = dbGetFunc . req . session . viewmode ;
delete dbGetFunc . req . session . viewmode ;
} else if ( dbGetFunc . req . query . viewmode ) {
viewmode = dbGetFunc . req . query . viewmode ;
}
var currentNode = '' ;
if ( dbGetFunc . req . session . currentNode ) {
currentNode = dbGetFunc . req . session . currentNode ;
delete dbGetFunc . req . session . currentNode ;
} else if ( dbGetFunc . req . query . node ) {
currentNode = 'node/' + domain . id + '/' + dbGetFunc . req . query . node ;
}
var logoutcontrols = { } ;
if ( obj . args . nousers != true ) { logoutcontrols . name = user . name ; }
2021-07-15 23:58:30 +03:00
// Give the web page a list of supported server features for this domain and user
const allFeatures = obj . getDomainUserFeatures ( domain , dbGetFunc . user , dbGetFunc . req ) ;
2021-01-26 22:46:22 +03:00
// Create a authentication cookie
const authCookie = obj . parent . encodeCookie ( { userid : dbGetFunc . user . _id , domainid : domain . id , ip : req . clientIp } , obj . parent . loginCookieEncryptionKey ) ;
2022-07-09 23:32:55 +03:00
const authRelayCookie = obj . parent . encodeCookie ( { ruserid : dbGetFunc . user . _id , x : req . session . x } , obj . parent . loginCookieEncryptionKey ) ;
2018-08-24 02:55:06 +03:00
2021-01-26 22:46:22 +03:00
// Send the main web application
var extras = ( dbGetFunc . req . query . key != null ) ? ( '&key=' + dbGetFunc . req . query . key ) : '' ;
if ( ( ! obj . args . user ) && ( obj . args . nousers != true ) && ( nologout == false ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
// Clean up the U2F challenge if needed
2021-05-12 20:34:04 +03:00
if ( dbGetFunc . req . session . u2f ) { delete dbGetFunc . req . session . u2f ; } ;
2022-07-13 03:45:19 +03:00
if ( dbGetFunc . req . session . e ) {
const sec = parent . decryptSessionData ( dbGetFunc . req . session . e ) ;
if ( sec . u2f != null ) { delete sec . u2f ; dbGetFunc . req . session . e = parent . encryptSessionData ( sec ) ; }
}
2019-12-03 01:00:28 +03:00
2021-01-26 22:46:22 +03:00
// Intel AMT Scanning options
var amtscanoptions = '' ;
if ( typeof domain . amtscanoptions == 'string' ) { amtscanoptions = encodeURIComponent ( domain . amtscanoptions ) ; }
else if ( obj . common . validateStrArray ( domain . amtscanoptions ) ) { domain . amtscanoptions = domain . amtscanoptions . join ( ',' ) ; amtscanoptions = encodeURIComponent ( domain . amtscanoptions ) ; }
// Fetch the web state
parent . debug ( 'web' , 'handleRootRequestEx: success.' ) ;
2020-06-19 20:37:55 +03:00
2021-01-25 03:09:15 +03:00
var webstate = '' ;
if ( ( err == null ) && ( states != null ) && ( Array . isArray ( states ) ) && ( states . length == 1 ) && ( states [ 0 ] . state != null ) ) { webstate = obj . filterUserWebState ( states [ 0 ] . state ) ; }
2020-02-28 05:05:18 +03:00
if ( ( webstate == '' ) && ( typeof domain . defaultuserwebstate == 'object' ) ) { webstate = JSON . stringify ( domain . defaultuserwebstate ) ; } // User has no web state, use defaults.
2021-01-25 03:09:15 +03:00
if ( typeof domain . forceduserwebstate == 'object' ) { // Forces initial user web state if present, use it.
2020-02-28 05:05:18 +03:00
var webstate2 = { } ;
try { if ( webstate != '' ) { webstate2 = JSON . parse ( webstate ) ; } } catch ( ex ) { }
for ( var i in domain . forceduserwebstate ) { webstate2 [ i ] = domain . forceduserwebstate [ i ] ; }
webstate = JSON . stringify ( webstate2 ) ;
}
2020-06-02 03:26:27 +03:00
// Custom user interface
var customui = '' ;
if ( domain . customui != null ) { customui = encodeURIComponent ( JSON . stringify ( domain . customui ) ) ; }
2020-09-28 23:28:08 +03:00
// Server features
2023-10-04 06:35:02 +03:00
var serverFeatures = 255 ;
2020-10-06 02:47:30 +03:00
if ( domain . myserver === false ) { serverFeatures = 0 ; } // 64 = Show "My Server" tab
else if ( typeof domain . myserver == 'object' ) {
2020-09-30 21:05:53 +03:00
if ( domain . myserver . backup !== true ) { serverFeatures -= 1 ; } // Disallow simple server backups
if ( domain . myserver . restore !== true ) { serverFeatures -= 2 ; } // Disallow simple server restore
if ( domain . myserver . upgrade !== true ) { serverFeatures -= 4 ; } // Disallow server upgrade
if ( domain . myserver . errorlog !== true ) { serverFeatures -= 8 ; } // Disallow show server crash log
if ( domain . myserver . console !== true ) { serverFeatures -= 16 ; } // Disallow server console
if ( domain . myserver . trace !== true ) { serverFeatures -= 32 ; } // Disallow server tracing
2023-10-04 06:35:02 +03:00
if ( domain . myserver . config !== true ) { serverFeatures -= 128 ; } // Disallow server configuration
2020-09-30 21:05:53 +03:00
}
if ( obj . db . databaseType != 1 ) { // If not using NeDB, we can't backup using the simple system.
if ( ( serverFeatures & 1 ) != 0 ) { serverFeatures -= 1 ; } // Disallow server backups
if ( ( serverFeatures & 2 ) != 0 ) { serverFeatures -= 2 ; } // Disallow simple server restore
2020-09-28 23:28:08 +03:00
}
2024-09-03 15:42:06 +03:00
// 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' ) ; }
2020-06-12 22:00:58 +03:00
// Refresh the session
2024-10-19 20:33:40 +03:00
render ( dbGetFunc . req , dbGetFunc . res , getRenderPage ( ( domain . sitestyle == 3 ? 'default3' : 'default' ) , dbGetFunc . req , domain ) , getRenderArgs ( {
2020-09-28 23:28:08 +03:00
authCookie : authCookie ,
authRelayCookie : authRelayCookie ,
viewmode : viewmode ,
currentNode : currentNode ,
logoutControls : encodeURIComponent ( JSON . stringify ( logoutcontrols ) ) . replace ( /'/g , '%27' ) ,
domain : domain . id ,
debuglevel : parent . debugLevel ,
2022-08-25 00:10:40 +03:00
serverDnsName : obj . getWebServerName ( domain , req ) ,
2020-09-28 23:28:08 +03:00
serverRedirPort : args . redirport ,
serverPublicPort : httpsPort ,
serverfeatures : serverFeatures ,
2021-07-15 23:58:30 +03:00
features : allFeatures . features ,
features2 : allFeatures . features2 ,
2021-01-06 10:38:52 +03:00
sessiontime : ( args . sessiontime ) ? args . sessiontime : 60 ,
2020-09-28 23:28:08 +03:00
mpspass : args . mpspass ,
passRequirements : passRequirements ,
customui : customui ,
webcerthash : Buffer . from ( obj . webCertificateFullHashs [ domain . id ] , 'binary' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ,
footer : ( domain . footer == null ) ? '' : domain . footer ,
webstate : encodeURIComponent ( webstate ) . replace ( /'/g , '%27' ) ,
amtscanoptions : amtscanoptions ,
2022-06-25 02:53:31 +03:00
pluginHandler : ( parent . pluginHandler == null ) ? 'null' : parent . pluginHandler . prepExports ( ) ,
2022-07-10 11:32:11 +03:00
webRelayPort : ( ( args . relaydns != null ) ? ( ( typeof args . aliasport == 'number' ) ? args . aliasport : args . port ) : ( ( parent . webrelayserver != null ) ? ( ( typeof args . relayaliasport == 'number' ) ? args . relayaliasport : parent . webrelayserver . port ) : 0 ) ) ,
2023-10-06 15:01:04 +03:00
webRelayDns : ( ( args . relaydns != null ) ? args . relaydns [ 0 ] : '' ) ,
2023-11-12 21:13:21 +03:00
hidePowerTimeline : ( domain . hidepowertimeline ? 'true' : 'false' ) ,
2024-06-14 11:56:02 +03:00
showNotesPanel : ( domain . shownotespanel ? 'true' : 'false' ) ,
2024-09-03 15:42:06 +03:00
userSessionsSort : ( domain . usersessionssort ? domain . usersessionssort : 'SessionId' ) ,
webrtcconfig : webRtcConfig
2022-01-23 11:34:47 +03:00
} , dbGetFunc . req , domain ) , user ) ;
2021-01-26 22:46:22 +03:00
}
xdbGetFunc . req = req ;
xdbGetFunc . res = res ;
xdbGetFunc . user = user ;
obj . db . Get ( 'ws' + user . _id , xdbGetFunc ) ;
2017-08-28 19:27:45 +03:00
} else {
// Send back the login application
2019-02-08 09:30:33 +03:00
// If this is a 2 factor auth request, look for a hardware key challenge.
2019-02-12 01:41:15 +03:00
// Normal login 2 factor request
2022-07-22 02:10:01 +03:00
if ( req . session && ( req . session . loginmode == 4 ) ) {
2022-07-22 02:08:38 +03:00
const sec = parent . decryptSessionData ( req . session . e ) ;
2022-07-22 02:10:01 +03:00
if ( ( sec != null ) && ( typeof sec . tuserid == 'string' ) ) {
2022-07-22 02:08:38 +03:00
const user = obj . users [ sec . tuserid ] ;
if ( user != null ) {
parent . debug ( 'web' , 'handleRootRequestEx: sending 2FA challenge.' ) ;
getHardwareKeyChallenge ( req , domain , user , function ( hwchallenge ) { handleRootRequestLogin ( req , res , domain , hwchallenge , passRequirements ) ; } ) ;
return ;
}
2019-02-08 09:30:33 +03:00
}
}
2019-02-12 01:41:15 +03:00
// Password recovery 2 factor request
2021-05-12 20:34:04 +03:00
if ( req . session && ( req . session . loginmode == 5 ) && ( req . session . temail ) ) {
obj . db . GetUserWithVerifiedEmail ( domain . id , req . session . temail , function ( err , docs ) {
2019-02-12 01:41:15 +03:00
if ( ( err != null ) || ( docs . length == 0 ) ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: password recover 2FA fail.' ) ;
2019-02-12 01:41:15 +03:00
req . session = null ;
2019-08-26 22:20:24 +03:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ; // BAD***
2019-02-12 01:41:15 +03:00
} else {
var user = obj . users [ docs [ 0 ] . _id ] ;
if ( user != null ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: password recover 2FA challenge.' ) ;
2019-03-26 00:43:58 +03:00
getHardwareKeyChallenge ( req , domain , user , function ( hwchallenge ) { handleRootRequestLogin ( req , res , domain , hwchallenge , passRequirements ) ; } ) ;
2019-02-12 01:41:15 +03:00
} else {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestEx: password recover 2FA no user.' ) ;
2019-02-12 01:41:15 +03:00
req . session = null ;
2019-08-26 22:20:24 +03:00
res . redirect ( domain . url + getQueryPortion ( req ) ) ; // BAD***
2019-02-12 01:41:15 +03:00
}
}
} ) ;
return ;
}
2019-02-11 03:04:36 +03:00
handleRootRequestLogin ( req , res , domain , '' , passRequirements ) ;
}
}
2019-02-08 09:30:33 +03:00
2021-07-15 23:58:30 +03:00
// Return a list of server supported features for a given domain and user
2022-07-20 22:57:24 +03:00
obj . getDomainUserFeatures = function ( domain , user , req ) {
2021-07-15 23:58:30 +03:00
var features = 0 ;
var features2 = 0 ;
if ( obj . args . wanonly == true ) { features += 0x00000001 ; } // WAN-only mode
if ( obj . args . lanonly == true ) { features += 0x00000002 ; } // LAN-only mode
if ( obj . args . nousers == true ) { features += 0x00000004 ; } // Single user mode
if ( domain . userQuota == - 1 ) { features += 0x00000008 ; } // No server files mode
if ( obj . args . mpstlsoffload ) { features += 0x00000010 ; } // No mutual-auth CIRA
if ( ( parent . config . settings . allowframing != null ) || ( domain . allowframing != null ) ) { features += 0x00000020 ; } // Allow site within iframe
if ( ( domain . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) ) { features += 0x00000040 ; } // Email invites
if ( obj . args . webrtc == true ) { features += 0x00000080 ; } // Enable WebRTC (Default false for now)
// 0x00000100 --> This feature flag is free for future use.
if ( obj . args . allowhighqualitydesktop !== false ) { features += 0x00000200 ; } // Enable AllowHighQualityDesktop (Default true)
if ( ( obj . args . lanonly == true ) || ( obj . args . mpsport == 0 ) ) { features += 0x00000400 ; } // No CIRA
2022-03-25 21:13:47 +03:00
if ( ( obj . parent . serverSelfWriteAllowed == true ) && ( user != null ) && ( ( user . siteadmin & 0x00000010 ) != 0 ) ) { features += 0x00000800 ; } // Server can self-write (Allows self-update)
2021-07-15 23:58:30 +03:00
if ( ( parent . config . settings . no2factorauth !== true ) && ( domain . auth != 'sspi' ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . nousers !== true ) && ( user . _id . split ( '/' ) [ 2 ] [ 0 ] != '~' ) ) { features += 0x00001000 ; } // 2FA login supported
if ( domain . agentnoproxy === true ) { features += 0x00002000 ; } // Indicates that agents should be installed without using a HTTP proxy
if ( ( parent . config . settings . no2factorauth !== true ) && domain . yubikey && domain . yubikey . id && domain . yubikey . secret && ( user . _id . split ( '/' ) [ 2 ] [ 0 ] != '~' ) ) { features += 0x00004000 ; } // Indicates Yubikey support
if ( domain . geolocation == true ) { features += 0x00008000 ; } // Enable geo-location features
if ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) ) { features += 0x00010000 ; } // Enable password hints
if ( parent . config . settings . no2factorauth !== true ) { features += 0x00020000 ; } // Enable WebAuthn/FIDO2 support
if ( ( obj . args . nousers != true ) && ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . force2factor === true ) && ( user . _id . split ( '/' ) [ 2 ] [ 0 ] != '~' ) ) {
// Check if we can skip 2nd factor auth because of the source IP address
var skip2factor = false ;
if ( ( req != null ) && ( req . clientIp != null ) && ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . skip2factor != null ) ) {
for ( var i in domain . passwordrequirements . skip2factor ) {
if ( require ( 'ipcheck' ) . match ( req . clientIp , domain . passwordrequirements . skip2factor [ i ] ) === true ) { skip2factor = true ; }
}
}
if ( skip2factor == false ) { features += 0x00040000 ; } // Force 2-factor auth
}
if ( ( domain . auth == 'sspi' ) || ( domain . auth == 'ldap' ) ) { features += 0x00080000 ; } // LDAP or SSPI in use, warn that users must login first before adding a user to a group.
if ( domain . amtacmactivation ) { features += 0x00100000 ; } // Intel AMT ACM activation/upgrade is possible
if ( domain . usernameisemail ) { features += 0x00200000 ; } // Username is email address
if ( parent . mqttbroker != null ) { features += 0x00400000 ; } // This server supports MQTT channels
if ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( domain . mailserver != null ) ) { features += 0x00800000 ; } // using email for 2FA is allowed
if ( domain . agentinvitecodes == true ) { features += 0x01000000 ; } // Support for agent invite codes
if ( parent . smsserver != null ) { features += 0x02000000 ; } // SMS messaging is supported
if ( ( parent . smsserver != null ) && ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) ) { features += 0x04000000 ; } // SMS 2FA is allowed
if ( domain . sessionrecording != null ) { features += 0x08000000 ; } // Server recordings enabled
if ( domain . urlswitching === false ) { features += 0x10000000 ; } // Disables the URL switching feature
if ( domain . novnc === false ) { features += 0x20000000 ; } // Disables noVNC
2022-04-29 21:13:58 +03:00
if ( domain . mstsc === false ) { features += 0x40000000 ; } // Disables MSTSC.js
2021-07-15 23:58:30 +03:00
if ( obj . isTrustedCert ( domain ) == false ) { features += 0x80000000 ; } // Indicate we are not using a trusted certificate
if ( obj . parent . amtManager != null ) { features2 += 0x00000001 ; } // Indicates that the Intel AMT manager is active
if ( obj . parent . firebase != null ) { features2 += 0x00000002 ; } // Indicates the server supports Firebase push messaging
if ( ( obj . parent . firebase != null ) && ( obj . parent . firebase . pushOnly != true ) ) { features2 += 0x00000004 ; } // Indicates the server supports Firebase two-way push messaging
if ( obj . parent . webpush != null ) { features2 += 0x00000008 ; } // Indicates web push is enabled
if ( ( ( obj . args . noagentupdate == 1 ) || ( obj . args . noagentupdate == true ) ) ) { features2 += 0x00000010 ; } // No agent update
if ( parent . amtProvisioningServer != null ) { features2 += 0x00000020 ; } // Intel AMT LAN provisioning server
if ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . push2factor != false ) ) && ( obj . parent . firebase != null ) ) { features2 += 0x00000040 ; } // Indicates device push notification 2FA is enabled
2022-03-25 01:05:20 +03:00
if ( ( typeof domain . passwordrequirements != 'object' ) || ( ( domain . passwordrequirements . logintokens !== false ) && ( ( Array . isArray ( domain . passwordrequirements . logintokens ) == false ) || ( domain . passwordrequirements . logintokens . indexOf ( user . _id ) >= 0 ) ) ) ) { features2 += 0x00000080 ; } // Indicates login tokens are allowed
2021-07-15 23:58:30 +03:00
if ( req . session . loginToken != null ) { features2 += 0x00000100 ; } // LoginToken mode, no account changes.
if ( domain . ssh == true ) { features2 += 0x00000200 ; } // SSH is enabled
if ( domain . localsessionrecording === false ) { features2 += 0x00000400 ; } // Disable local recording feature
if ( domain . clipboardget == false ) { features2 += 0x00000800 ; } // Disable clipboard get
if ( domain . clipboardset == false ) { features2 += 0x00001000 ; } // Disable clipboard set
2021-07-18 09:03:44 +03:00
if ( ( typeof domain . desktop == 'object' ) && ( domain . desktop . viewonly == true ) ) { features2 += 0x00002000 ; } // Indicates remote desktop is viewonly
2021-10-10 22:43:20 +03:00
if ( domain . mailserver != null ) { features2 += 0x00004000 ; } // Indicates email server is active
2021-11-08 22:04:32 +03:00
if ( domain . devicesearchbarserverandclientname ) { features2 += 0x00008000 ; } // Search bar will find both server name and client name
2021-12-03 05:34:18 +03:00
if ( domain . ipkvm ) { features2 += 0x00010000 ; } // Indicates support for IP KVM device groups
2021-12-12 03:08:26 +03:00
if ( ( domain . passwordrequirements ) && ( domain . passwordrequirements . otp2factor == false ) ) { features2 += 0x00020000 ; } // Indicates support for OTP 2FA is disabled
2022-07-05 20:42:00 +03:00
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . backupcode2factor === false ) ) { features2 += 0x00040000 ; } // Indicates 2FA backup codes are disabled
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . single2factorwarning === false ) ) { features2 += 0x00080000 ; } // Indicates no warning if a single 2FA is in use
2022-02-08 21:13:52 +03:00
if ( domain . nightmode === 1 ) { features2 += 0x00100000 ; } // Always night mode
if ( domain . nightmode === 2 ) { features2 += 0x00200000 ; } // Always day mode
2022-03-16 03:00:43 +03:00
if ( domain . allowsavingdevicecredentials == false ) { features2 += 0x00400000 ; } // Do not allow device credentials to be saved on the server
2022-07-06 00:25:38 +03:00
if ( ( typeof domain . files == 'object' ) && ( domain . files . sftpconnect === false ) ) { features2 += 0x00800000 ; } // Remove the "SFTP Connect" button in the "Files" tab when the device is agent managed
if ( ( typeof domain . terminal == 'object' ) && ( domain . terminal . sshconnect === false ) ) { features2 += 0x01000000 ; } // Remove the "SSH Connect" button in the "Terminal" tab when the device is agent managed
2022-10-22 17:23:55 +03:00
if ( ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) ) { features2 += 0x02000000 ; } // User messaging server is enabled
if ( ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) ) { features2 += 0x04000000 ; } // User messaging 2FA is allowed
2023-10-09 07:33:23 +03:00
if ( domain . scrolltotop == true ) { features2 += 0x08000000 ; } // Show the "Scroll to top" button
2023-11-13 02:18:00 +03:00
if ( domain . devicesearchbargroupname === true ) { features2 += 0x10000000 ; } // Search bar will find by group name too
2021-07-15 23:58:30 +03:00
return { features : features , features2 : features2 } ;
}
2019-02-11 03:04:36 +03:00
function handleRootRequestLogin ( req , res , domain , hardwareKeyChallenge , passRequirements ) {
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleRootRequestLogin()' ) ;
2019-02-11 03:04:36 +03:00
var features = 0 ;
2019-10-23 03:51:37 +03:00
if ( ( parent . config != null ) && ( parent . config . settings != null ) && ( ( parent . config . settings . allowframing == true ) || ( typeof parent . config . settings . allowframing == 'string' ) ) ) { features += 32 ; } // Allow site within iframe
2019-07-09 01:59:44 +03:00
if ( domain . usernameisemail ) { features += 0x00200000 ; } // Username is email address
2019-02-11 03:04:36 +03:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2021-05-12 20:34:04 +03:00
var loginmode = 0 ;
2019-08-26 22:20:24 +03:00
if ( req . session ) { loginmode = req . session . loginmode ; delete req . session . loginmode ; } // Clear this state, if the user hits refresh, we want to go back to the login page.
2019-02-11 03:04:36 +03:00
2019-02-11 07:13:03 +03:00
// Format an error message if needed
2019-11-18 01:33:29 +03:00
var passhint = null , msgid = 0 ;
2019-02-11 07:13:03 +03:00
if ( req . session != null ) {
2019-11-18 01:33:29 +03:00
msgid = req . session . messageid ;
2021-05-12 20:34:04 +03:00
if ( ( msgid == 5 ) || ( loginmode == 7 ) || ( ( domain . passwordrequirements != null ) && ( domain . passwordrequirements . hint === true ) ) ) { passhint = EscapeHtml ( req . session . passhint ) ; }
2019-11-18 01:33:29 +03:00
delete req . session . messageid ;
2019-02-11 07:13:03 +03:00
delete req . session . passhint ;
}
2022-02-04 18:29:18 +03:00
const allowAccountReset = ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . allowaccountreset !== false ) ) ;
const emailcheck = ( allowAccountReset && ( domain . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
2019-02-21 05:09:57 +03:00
2019-04-23 23:42:54 +03:00
// Check if we are allowed to create new users using the login screen
var newAccountsAllowed = true ;
if ( ( domain . newaccounts !== 1 ) && ( domain . newaccounts !== true ) ) { for ( var i in obj . users ) { if ( obj . users [ i ] . domain == domain . id ) { newAccountsAllowed = false ; break ; } } }
2020-11-21 03:43:58 +03:00
if ( parent . config . settings . maintenancemode != null ) { newAccountsAllowed = false ; }
2019-04-23 23:42:54 +03:00
2019-08-26 22:20:24 +03:00
// Encrypt the hardware key challenge state if needed
var hwstate = null ;
2022-07-22 03:17:08 +03:00
if ( hardwareKeyChallenge && req . session ) {
2022-07-13 03:45:19 +03:00
const sec = parent . decryptSessionData ( req . session . e ) ;
hwstate = obj . parent . encodeCookie ( { u : sec . tuser , p : sec . tpass , c : sec . u2f } , obj . parent . loginCookieEncryptionKey )
}
2019-08-26 22:20:24 +03:00
2020-12-10 09:14:51 +03:00
// Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5).
2021-05-12 20:34:04 +03:00
var otpemail = ( loginmode != 5 ) && ( domain . mailserver != null ) && ( req . session != null ) && ( ( req . session . temail === 1 ) || ( typeof req . session . temail == 'string' ) ) ;
2020-03-15 01:03:50 +03:00
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . email2factor == false ) ) { otpemail = false ; }
2021-05-12 20:34:04 +03:00
var otpsms = ( parent . smsserver != null ) && ( req . session != null ) && ( req . session . tsms === 1 ) ;
2020-04-23 01:29:26 +03:00
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . sms2factor == false ) ) { otpsms = false ; }
2022-10-22 22:59:12 +03:00
var otpmsg = ( parent . msgserver != null ) && ( req . session != null ) && ( req . session . tmsg === 1 ) ;
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . msg2factor == false ) ) { otpmsg = false ; }
2021-05-12 20:34:04 +03:00
var otppush = ( parent . firebase != null ) && ( req . session != null ) && ( req . session . tpush === 1 ) ;
2021-04-15 00:06:31 +03:00
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . push2factor == false ) ) { otppush = false ; }
2021-07-28 20:35:33 +03:00
const autofido = ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . autofido2fa == true ) ) ; // See if FIDO should be automatically prompted if user account has it.
2020-03-14 06:39:21 +03:00
2020-04-30 01:45:31 +03:00
// See if we support two-factor trusted cookies
var twoFactorCookieDays = 30 ;
if ( typeof domain . twofactorcookiedurationdays == 'number' ) { twoFactorCookieDays = domain . twofactorcookiedurationdays ; }
2020-05-14 06:49:58 +03:00
// See what authentication strategies we have
var authStrategies = [ ] ;
if ( typeof domain . authstrategies == 'object' ) {
2020-05-20 10:39:17 +03:00
if ( typeof domain . authstrategies . twitter == 'object' ) { authStrategies . push ( 'twitter' ) ; }
if ( typeof domain . authstrategies . google == 'object' ) { authStrategies . push ( 'google' ) ; }
if ( typeof domain . authstrategies . github == 'object' ) { authStrategies . push ( 'github' ) ; }
2020-05-24 10:04:49 +03:00
if ( typeof domain . authstrategies . azure == 'object' ) { authStrategies . push ( 'azure' ) ; }
2024-03-04 03:03:27 +03:00
if ( typeof domain . authstrategies . oidc == 'object' ) {
if ( obj . common . validateObject ( domain . authstrategies . oidc . custom ) && obj . common . validateString ( domain . authstrategies . oidc . custom . preset ) ) {
authStrategies . push ( 'oidc-' + domain . authstrategies . oidc . custom . preset ) ;
} else {
authStrategies . push ( 'oidc' ) ;
}
}
2020-05-20 10:39:17 +03:00
if ( typeof domain . authstrategies . intel == 'object' ) { authStrategies . push ( 'intel' ) ; }
if ( typeof domain . authstrategies . jumpcloud == 'object' ) { authStrategies . push ( 'jumpcloud' ) ; }
if ( typeof domain . authstrategies . saml == 'object' ) { authStrategies . push ( 'saml' ) ; }
2020-05-14 06:49:58 +03:00
}
2020-06-02 03:26:27 +03:00
// Custom user interface
var customui = '' ;
if ( domain . customui != null ) { customui = encodeURIComponent ( JSON . stringify ( domain . customui ) ) ; }
2021-07-25 01:14:21 +03:00
// Get two-factor screen timeout
var twoFactorTimeout = 300000 ; // Default is 5 minutes, 0 for no timeout.
if ( ( typeof domain . passwordrequirements == 'object' ) && ( typeof domain . passwordrequirements . twofactortimeout == 'number' ) ) {
twoFactorTimeout = domain . passwordrequirements . twofactortimeout * 1000 ;
}
2022-07-12 00:35:05 +03:00
// Setup CAPTCHA if needed
var newAccountCaptcha = '' , newAccountCaptchaImage = '' ;
if ( ( domain . newaccountscaptcha != null ) && ( domain . newaccountscaptcha !== false ) ) {
newAccountCaptcha = obj . parent . encodeCookie ( { type : 'newAccount' , captcha : require ( 'svg-captcha' ) . randomText ( 5 ) } , obj . parent . loginCookieEncryptionKey ) ;
newAccountCaptchaImage = 'newAccountCaptcha.ashx?x=' + newAccountCaptcha ;
}
2024-06-11 22:06:19 +03:00
// Check for flash errors from passport.js and make the array unique
var flashErrors = [ ] ;
if ( req . session . flash && req . session . flash . error ) {
flashErrors = obj . common . uniqueArray ( req . session . flash . error ) ;
2024-06-11 22:46:45 +03:00
req . session . flash = null ;
2024-06-11 22:06:19 +03:00
}
2019-08-25 21:08:32 +03:00
// Render the login page
2021-01-06 10:38:52 +03:00
render ( req , res ,
2024-10-19 21:28:23 +03:00
getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'login2' : 'login' , req , domain ) ,
2021-01-06 10:38:52 +03:00
getRenderArgs ( {
loginmode : loginmode ,
2021-07-10 23:22:46 +03:00
rootCertLink : getRootCertLink ( domain ) ,
2022-07-12 00:35:05 +03:00
newAccount : newAccountsAllowed , // True if new accounts are allowed from the login page
newAccountPass : ( ( ( domain . newaccountspass == null ) || ( domain . newaccountspass == '' ) ) ? 0 : 1 ) , // 1 if new account creation requires password
newAccountCaptcha : newAccountCaptcha , // If new account creation requires a CAPTCHA, this string will not be empty
newAccountCaptchaImage : newAccountCaptchaImage , // Set to the URL of the CAPTCHA image
2022-08-25 00:10:40 +03:00
serverDnsName : obj . getWebServerName ( domain , req ) ,
2021-01-06 10:38:52 +03:00
serverPublicPort : httpsPort ,
passlogin : ( typeof domain . showpasswordlogin == 'boolean' ) ? domain . showpasswordlogin : true ,
emailcheck : emailcheck ,
features : features ,
2022-07-12 00:35:05 +03:00
sessiontime : ( args . sessiontime ) ? args . sessiontime : 60 , // Session time in minutes, 60 minutes is the default
2021-01-06 10:38:52 +03:00
passRequirements : passRequirements ,
customui : customui ,
footer : ( domain . loginfooter == null ) ? '' : domain . loginfooter ,
hkey : encodeURIComponent ( hardwareKeyChallenge ) . replace ( /'/g , '%27' ) ,
messageid : msgid ,
2024-06-11 22:06:19 +03:00
flashErrors : JSON . stringify ( flashErrors ) ,
2021-01-06 10:38:52 +03:00
passhint : passhint ,
welcometext : domain . welcometext ? encodeURIComponent ( domain . welcometext ) . split ( '\'' ) . join ( '\\\'' ) : null ,
2021-05-10 22:19:53 +03:00
welcomePictureFullScreen : ( ( typeof domain . welcomepicturefullscreen == 'boolean' ) ? domain . welcomepicturefullscreen : false ) ,
2021-01-06 10:38:52 +03:00
hwstate : hwstate ,
otpemail : otpemail ,
otpsms : otpsms ,
2022-10-22 22:59:12 +03:00
otpmsg : otpmsg ,
2021-04-14 09:23:09 +03:00
otppush : otppush ,
2021-07-28 20:35:33 +03:00
autofido : autofido ,
2021-01-06 10:38:52 +03:00
twoFactorCookieDays : twoFactorCookieDays ,
authStrategies : authStrategies . join ( ',' ) ,
2021-07-25 01:14:21 +03:00
loginpicture : ( typeof domain . loginpicture == 'string' ) ,
2024-01-04 05:17:27 +03:00
tokenTimeout : twoFactorTimeout , // Two-factor authentication screen timeout in milliseconds,
renderLanguages : obj . renderLanguages ,
showLanguageSelect : domain . showlanguageselect ? domain . showlanguageselect : false ,
2024-10-19 21:28:23 +03:00
} , req , domain , ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'login2' : 'login' ) ) ;
2019-08-26 22:20:24 +03:00
}
// Handle a post request on the root
function handleRootPostRequest ( req , res ) {
2019-11-22 05:57:57 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-12-22 03:07:25 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . end ( "Not Found" ) ; return ; } // Check 3FA URL key
2022-07-07 09:39:36 +03:00
if ( req . body == null ) { req . body = { } ; }
2019-08-26 22:20:24 +03:00
parent . debug ( 'web' , 'handleRootPostRequest, action: ' + req . body . action ) ;
2020-02-01 01:44:11 +03:00
2022-05-19 04:01:34 +03:00
// If a HTTP header is required, check new UserRequiredHttpHeader
if ( domain . userrequiredhttpheader && ( typeof domain . userrequiredhttpheader == 'object' ) ) { var ok = false ; for ( var i in req . headers ) { if ( domain . userrequiredhttpheader [ i . toLowerCase ( ) ] == req . headers [ i ] ) { ok = true ; } } if ( ok == false ) { res . sendStatus ( 404 ) ; return ; } }
2019-08-26 22:20:24 +03:00
switch ( req . body . action ) {
case 'login' : { handleLoginRequest ( req , res , true ) ; break ; }
case 'tokenlogin' : {
if ( req . body . hwstate ) {
var cookie = obj . parent . decodeCookie ( req . body . hwstate , obj . parent . loginCookieEncryptionKey , 10 ) ;
2022-07-13 03:45:19 +03:00
if ( cookie != null ) { req . session . e = parent . encryptSessionData ( { tuser : cookie . u , tpass : cookie . p , u2f : cookie . c } ) ; }
2019-08-26 22:20:24 +03:00
}
handleLoginRequest ( req , res , true ) ; break ;
}
2021-04-14 23:28:51 +03:00
case 'pushlogin' : {
if ( req . body . hwstate ) {
var cookie = obj . parent . decodeCookie ( req . body . hwstate , obj . parent . loginCookieEncryptionKey , 1 ) ;
if ( ( cookie != null ) && ( typeof cookie . u == 'string' ) && ( cookie . d == domain . id ) && ( cookie . a == 'pushAuth' ) ) {
2021-04-15 00:06:31 +03:00
// Push authentication is a success, login the user
2021-05-12 20:34:04 +03:00
req . session = { userid : cookie . u } ;
2021-04-15 00:06:31 +03:00
// Check if we need to remember this device
if ( ( req . body . remembertoken === 'on' ) && ( ( domain . twofactorcookiedurationdays == null ) || ( domain . twofactorcookiedurationdays > 0 ) ) ) {
var maxCookieAge = domain . twofactorcookiedurationdays ;
if ( typeof maxCookieAge != 'number' ) { maxCookieAge = 30 ; }
const twoFactorCookie = obj . parent . encodeCookie ( { userid : cookie . u , expire : maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ } , obj . parent . loginCookieEncryptionKey ) ;
2022-04-13 00:03:52 +03:00
res . cookie ( 'twofactor' , twoFactorCookie , { maxAge : ( maxCookieAge * 24 * 60 * 60 * 1000 ) , httpOnly : true , sameSite : parent . config . settings . sessionsamesite , secure : true } ) ;
2021-04-15 00:06:31 +03:00
}
2021-04-14 23:28:51 +03:00
handleRootRequestEx ( req , res , domain ) ;
return ;
}
}
handleLoginRequest ( req , res , true ) ; break ;
}
2019-08-26 22:20:24 +03:00
case 'changepassword' : { handlePasswordChangeRequest ( req , res , true ) ; break ; }
case 'deleteaccount' : { handleDeleteAccountRequest ( req , res , true ) ; break ; }
case 'createaccount' : { handleCreateAccountRequest ( req , res , true ) ; break ; }
case 'resetpassword' : { handleResetPasswordRequest ( req , res , true ) ; break ; }
case 'resetaccount' : { handleResetAccountRequest ( req , res , true ) ; break ; }
2020-04-21 11:50:27 +03:00
case 'checkemail' : { handleCheckAccountEmailRequest ( req , res , true ) ; break ; }
2019-08-26 22:20:24 +03:00
default : { handleLoginRequest ( req , res , true ) ; break ; }
}
2017-08-28 19:27:45 +03:00
}
2018-05-24 07:43:44 +03:00
2019-02-15 01:44:38 +03:00
// Return true if it looks like we are using a real TLS certificate.
2020-02-01 01:44:11 +03:00
obj . isTrustedCert = function ( domain ) {
2019-08-23 01:31:39 +03:00
if ( ( domain != null ) && ( typeof domain . trustedcert == 'boolean' ) ) return domain . trustedcert ; // If the status of the cert specified, use that.
if ( typeof obj . args . trustedcert == 'boolean' ) return obj . args . trustedcert ; // If the status of the cert specified, use that.
2019-02-15 01:44:38 +03:00
if ( obj . args . tlsoffload != null ) return true ; // We are using TLS offload, a real cert is likely used.
2019-08-23 01:31:39 +03:00
if ( obj . parent . config . letsencrypt != null ) return ( obj . parent . config . letsencrypt . production === true ) ; // We are using Let's Encrypt, real cert in use if production is set to true.
2022-11-02 20:45:10 +03:00
if ( ( typeof obj . certificates . WebIssuer == 'string' ) && ( obj . certificates . WebIssuer . indexOf ( 'MeshCentralRoot-' ) == 0 ) ) return false ; // Our cert is issued by self-signed cert.
2019-03-05 10:48:45 +03:00
if ( obj . certificates . CommonName . indexOf ( '.' ) == - 1 ) return false ; // Our cert is named with a fake name
2019-02-15 01:44:38 +03:00
return true ; // This is a guess
}
2017-08-28 19:27:45 +03:00
// Get the link to the root certificate if needed
2021-07-10 23:22:46 +03:00
function getRootCertLink ( domain ) {
2018-08-30 03:40:30 +03:00
// Check if the HTTPS certificate is issued from MeshCentralRoot, if so, add download link to root certificate.
2021-07-10 23:22:46 +03:00
if ( obj . isTrustedCert ( domain ) == false ) {
// Get the domain suffix
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += '/' ;
return '<a href=/' + xdomain + 'MeshServerRootCert.cer title="Download the root certificate for this server">Root Certificate</a>' ;
}
2017-08-28 19:27:45 +03:00
return '' ;
}
2020-01-24 02:15:56 +03:00
// Serve the xterm page
function handleXTermRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2020-01-24 02:15:56 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
parent . debug ( 'web' , 'handleXTermRequest: sending xterm' ) ;
2020-10-02 23:17:53 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2020-01-24 02:15:56 +03:00
if ( req . session && req . session . userid ) {
2021-05-12 20:34:04 +03:00
if ( req . session . userid . split ( '/' ) [ 1 ] != domain . id ) { res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the session is for the correct domain
2020-01-24 02:15:56 +03:00
var user = obj . users [ req . session . userid ] ;
if ( ( user == null ) || ( req . query . nodeid == null ) ) { res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the user exists
// Check permissions
obj . GetNodeWithRights ( domain , user , req . query . nodeid , function ( node , rights , visible ) {
if ( ( node == null ) || ( ( rights & 8 ) == 0 ) || ( ( rights != 0xFFFFFFFF ) && ( ( rights & 512 ) != 0 ) ) ) { res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; }
var logoutcontrols = { name : user . name } ;
2024-09-27 07:09:34 +03:00
var extras = ( req . query . key != null ) ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ;
2020-01-24 02:15:56 +03:00
if ( ( domain . ldap == null ) && ( domain . sspi == null ) && ( obj . args . user == null ) && ( obj . args . nousers != true ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
// Create a authentication cookie
2020-05-22 07:25:11 +03:00
const authCookie = obj . parent . encodeCookie ( { userid : user . _id , domainid : domain . id , ip : req . clientIp } , obj . parent . loginCookieEncryptionKey ) ;
2020-01-24 02:15:56 +03:00
const authRelayCookie = obj . parent . encodeCookie ( { ruserid : user . _id , domainid : domain . id } , obj . parent . loginCookieEncryptionKey ) ;
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2022-08-25 00:10:40 +03:00
render ( req , res , getRenderPage ( 'xterm' , req , domain ) , getRenderArgs ( { serverDnsName : obj . getWebServerName ( domain , req ) , serverRedirPort : args . redirport , serverPublicPort : httpsPort , authCookie : authCookie , authRelayCookie : authRelayCookie , logoutControls : encodeURIComponent ( JSON . stringify ( logoutcontrols ) ) . replace ( /'/g , '%27' ) , name : EscapeHtml ( node . name ) } , req , domain ) ) ;
2020-01-24 02:15:56 +03:00
} ) ;
} else {
res . redirect ( domain . url + getQueryPortion ( req ) ) ;
return ;
}
}
2022-07-12 00:35:05 +03:00
// Handle new account Captcha GET
function handleNewAccountCaptchaRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
if ( ( domain . newaccountscaptcha == null ) || ( domain . newaccountscaptcha === false ) || ( req . query . x == null ) ) { res . sendStatus ( 404 ) ; return ; }
const c = obj . parent . decodeCookie ( req . query . x , obj . parent . loginCookieEncryptionKey ) ;
if ( ( c == null ) || ( c . type !== 'newAccount' ) || ( typeof c . captcha != 'string' ) ) { res . sendStatus ( 404 ) ; return ; }
res . type ( 'svg' ) ;
res . status ( 200 ) . end ( require ( 'svg-captcha' ) ( c . captcha , { } ) ) ;
}
2022-07-08 07:51:09 +03:00
// Handle Captcha GET
function handleCaptchaGetRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
if ( parent . crowdSecBounser == null ) { res . sendStatus ( 404 ) ; return ; }
parent . crowdSecBounser . applyCaptcha ( req , res , function ( ) { res . redirect ( ( ( ( domain . id == '' ) && ( domain . dns == null ) ) ? '/' : ( '/' + domain . id ) ) ) ; } ) ;
}
// Handle Captcha POST
function handleCaptchaPostRequest ( req , res ) {
if ( parent . crowdSecBounser == null ) { res . sendStatus ( 404 ) ; return ; }
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
req . originalUrl = ( ( ( domain . id == '' ) && ( domain . dns == null ) ) ? '/' : ( '/' + domain . id ) ) ;
parent . crowdSecBounser . applyCaptcha ( req , res , function ( ) { res . redirect ( req . originalUrl ) ; } ) ;
}
2018-12-20 23:12:24 +03:00
// Render the terms of service.
2017-08-28 19:27:45 +03:00
function handleTermsRequest ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2018-11-29 03:33:07 +03:00
2019-02-03 01:54:36 +03:00
// See if term.txt was loaded from the database
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ 'terms.txt' ] != null ) ) {
// Send the terms from the database
2020-10-02 23:17:53 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2019-02-03 01:54:36 +03:00
if ( req . session && req . session . userid ) {
2021-05-12 20:34:04 +03:00
if ( req . session . userid . split ( '/' ) [ 1 ] != domain . id ) { req . session = null ; res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the session is for the correct domain
2019-02-03 01:54:36 +03:00
var user = obj . users [ req . session . userid ] ;
2019-12-02 02:21:33 +03:00
var logoutcontrols = { name : user . name } ;
2024-09-27 07:09:34 +03:00
var extras = ( req . query . key != null ) ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ;
2019-12-02 02:21:33 +03:00
if ( ( domain . ldap == null ) && ( domain . sspi == null ) && ( obj . args . user == null ) && ( obj . args . nousers != true ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
2020-05-10 01:56:40 +03:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { terms : encodeURIComponent ( parent . configurationFiles [ 'terms.txt' ] . toString ( ) ) . split ( '\'' ) . join ( '\\\'' ) , logoutControls : encodeURIComponent ( JSON . stringify ( logoutcontrols ) ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2019-02-03 01:54:36 +03:00
} else {
2020-05-03 09:49:59 +03:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { terms : encodeURIComponent ( parent . configurationFiles [ 'terms.txt' ] . toString ( ) ) . split ( '\'' ) . join ( '\\\'' ) , logoutControls : encodeURIComponent ( '{}' ) } , req , domain ) ) ;
2019-02-03 01:54:36 +03:00
}
} else {
// See if there is a terms.txt file in meshcentral-data
var p = obj . path . join ( obj . parent . datapath , 'terms.txt' ) ;
if ( obj . fs . existsSync ( p ) ) {
obj . fs . readFile ( p , 'utf8' , function ( err , data ) {
2019-08-24 01:45:36 +03:00
if ( err != null ) { parent . debug ( 'web' , 'handleTermsRequest: no terms.txt' ) ; res . sendStatus ( 404 ) ; return ; }
2018-11-29 03:33:07 +03:00
2019-02-03 01:54:36 +03:00
// Send the terms from terms.txt
2020-10-02 23:17:53 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2019-02-03 01:54:36 +03:00
if ( req . session && req . session . userid ) {
2021-05-12 20:34:04 +03:00
if ( req . session . userid . split ( '/' ) [ 1 ] != domain . id ) { req . session = null ; res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the session is for the correct domain
2019-02-03 01:54:36 +03:00
var user = obj . users [ req . session . userid ] ;
2019-12-02 02:21:33 +03:00
var logoutcontrols = { name : user . name } ;
2024-09-27 07:09:34 +03:00
var extras = ( req . query . key != null ) ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ;
2019-12-02 02:21:33 +03:00
if ( ( domain . ldap == null ) && ( domain . sspi == null ) && ( obj . args . user == null ) && ( obj . args . nousers != true ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
2020-05-10 01:56:40 +03:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { terms : encodeURIComponent ( data ) . split ( '\'' ) . join ( '\\\'' ) , logoutControls : encodeURIComponent ( JSON . stringify ( logoutcontrols ) ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2019-02-03 01:54:36 +03:00
} else {
2020-05-03 09:49:59 +03:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { terms : encodeURIComponent ( data ) . split ( '\'' ) . join ( '\\\'' ) , logoutControls : encodeURIComponent ( '{}' ) } , req , domain ) ) ;
2019-02-03 01:54:36 +03:00
}
} ) ;
} else {
// Send the default terms
2019-08-24 01:45:36 +03:00
parent . debug ( 'web' , 'handleTermsRequest: sending default terms' ) ;
2020-10-02 23:17:53 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2018-11-29 03:33:07 +03:00
if ( req . session && req . session . userid ) {
2021-05-12 20:34:04 +03:00
if ( req . session . userid . split ( '/' ) [ 1 ] != domain . id ) { req . session = null ; res . redirect ( domain . url + getQueryPortion ( req ) ) ; return ; } // Check if the session is for the correct domain
2018-11-29 03:33:07 +03:00
var user = obj . users [ req . session . userid ] ;
2019-12-02 02:21:33 +03:00
var logoutcontrols = { name : user . name } ;
2024-09-27 07:09:34 +03:00
var extras = ( req . query . key != null ) ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ;
2019-12-02 02:21:33 +03:00
if ( ( domain . ldap == null ) && ( domain . sspi == null ) && ( obj . args . user == null ) && ( obj . args . nousers != true ) ) { logoutcontrols . logoutUrl = ( domain . url + 'logout?' + Math . random ( ) + extras ) ; } // If a default user is in use or no user mode, don't display the logout button
2020-05-10 01:56:40 +03:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { logoutControls : encodeURIComponent ( JSON . stringify ( logoutcontrols ) ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2018-11-29 03:33:07 +03:00
} else {
2020-05-03 09:49:59 +03:00
render ( req , res , getRenderPage ( 'terms' , req , domain ) , getRenderArgs ( { logoutControls : encodeURIComponent ( '{}' ) } , req , domain ) ) ;
2018-11-29 03:33:07 +03:00
}
}
2017-08-28 19:27:45 +03:00
}
}
2018-12-20 23:12:24 +03:00
// Render the messenger application.
function handleMessengerRequest ( req , res ) {
2019-10-10 21:43:21 +03:00
const domain = getDomain ( req ) ;
2019-08-24 01:45:36 +03:00
if ( domain == null ) { parent . debug ( 'web' , 'handleMessengerRequest: no domain' ) ; res . sendStatus ( 404 ) ; return ; }
parent . debug ( 'web' , 'handleMessengerRequest()' ) ;
2019-05-30 19:40:59 +03:00
2020-11-21 03:43:58 +03:00
// Check if we are in maintenance mode
if ( parent . config . settings . maintenancemode != null ) {
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 3 , msgid : 13 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ;
2020-11-21 03:43:58 +03:00
return ;
}
2021-02-10 04:36:30 +03:00
// Check if this session is for a user
if ( req . query . id == null ) { res . sendStatus ( 404 ) ; return ; }
var idSplit = decodeURIComponent ( req . query . id ) . split ( '/' ) ;
if ( ( idSplit . length != 7 ) || ( idSplit [ 0 ] != 'meshmessenger' ) ) { res . sendStatus ( 404 ) ; return ; }
if ( ( idSplit [ 1 ] == 'user' ) && ( idSplit [ 4 ] == 'user' ) ) {
// This is a user to user conversation, both users must be logged in.
var user1 = idSplit [ 1 ] + '/' + idSplit [ 2 ] + '/' + idSplit [ 3 ]
var user2 = idSplit [ 4 ] + '/' + idSplit [ 5 ] + '/' + idSplit [ 6 ]
if ( ! req . session || ! req . session . userid ) {
// Redirect to login page
2024-09-27 07:09:34 +03:00
if ( req . query . key != null ) { res . redirect ( domain . url + '?key=' + encodeURIComponent ( req . query . key ) + '&meshmessengerid=' + encodeURIComponent ( req . query . id ) ) ; } else { res . redirect ( domain . url + '?meshmessengerid=' + encodeURIComponent ( req . query . id ) ) ; }
2021-02-10 04:36:30 +03:00
return ;
}
if ( ( req . session . userid != user1 ) && ( req . session . userid != user2 ) ) { res . sendStatus ( 404 ) ; return ; }
}
2021-01-25 11:04:43 +03:00
// Get WebRTC configuration
2018-12-20 23:12:24 +03:00
var webRtcConfig = null ;
2024-09-03 15:42:06 +03:00
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' ) ; }
2021-01-25 11:04:43 +03:00
// Setup other options
2024-09-03 15:42:06 +03:00
var options = { webrtcconfig : webRtcConfig } ;
2021-01-25 11:04:43 +03:00
if ( typeof domain . meshmessengertitle == 'string' ) { options . meshMessengerTitle = domain . meshmessengertitle ; } else { options . meshMessengerTitle = '!' ; }
2021-01-26 22:46:22 +03:00
// Get the userid and name
if ( ( domain . meshmessengertitle != null ) && ( req . query . id != null ) && ( req . query . id . startsWith ( 'meshmessenger/node' ) ) ) {
if ( idSplit . length == 7 ) {
const user = obj . users [ idSplit [ 4 ] + '/' + idSplit [ 5 ] + '/' + idSplit [ 6 ] ] ;
if ( user != null ) {
2021-01-28 02:35:58 +03:00
if ( domain . meshmessengertitle . indexOf ( '{0}' ) >= 0 ) { options . username = encodeURIComponent ( user . realname ? user . realname : user . name ) . replace ( /'/g , '%27' ) ; }
2021-01-28 10:37:44 +03:00
if ( domain . meshmessengertitle . indexOf ( '{1}' ) >= 0 ) { options . userid = encodeURIComponent ( user . name ) . replace ( /'/g , '%27' ) ; }
2021-01-26 22:46:22 +03:00
}
}
}
2021-01-25 11:04:43 +03:00
// Render the page
2020-10-02 23:17:53 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2021-01-25 11:04:43 +03:00
render ( req , res , getRenderPage ( 'messenger' , req , domain ) , getRenderArgs ( options , req , domain ) ) ;
}
// Handle messenger image request
function handleMessengerImageRequest ( req , res ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleMessengerImageRequest: no domain' ) ; res . sendStatus ( 404 ) ; return ; }
parent . debug ( 'web' , 'handleMessengerImageRequest()' ) ;
// Check if we are in maintenance mode
if ( parent . config . settings . maintenancemode != null ) { res . sendStatus ( 404 ) ; return ; }
//res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
if ( domain . meshmessengerpicture ) {
// Use the configured messenger logo picture
2021-07-29 17:38:44 +03:00
try { res . sendFile ( obj . common . joinPath ( obj . parent . datapath , domain . meshmessengerpicture ) ) ; return ; } catch ( ex ) { }
2021-01-25 11:04:43 +03:00
}
var imagefile = 'images/messenger.png' ;
if ( domain . webpublicpath != null ) {
obj . fs . exists ( obj . path . join ( domain . webpublicpath , imagefile ) , function ( exists ) {
if ( exists ) {
// Use the domain logo picture
try { res . sendFile ( obj . path . join ( domain . webpublicpath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else {
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
} ) ;
} else if ( parent . webPublicOverridePath ) {
obj . fs . exists ( obj . path . join ( obj . parent . webPublicOverridePath , imagefile ) , function ( exists ) {
if ( exists ) {
// Use the override logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicOverridePath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else {
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
} ) ;
} else {
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
2018-12-20 23:12:24 +03:00
}
2017-08-28 19:27:45 +03:00
// Returns the server root certificate encoded in base64
function getRootCertBase64 ( ) {
var rootcert = obj . certificates . root . cert ;
2019-12-27 09:53:01 +03:00
var i = rootcert . indexOf ( '-----BEGIN CERTIFICATE-----\r\n' ) ;
2017-08-28 19:27:45 +03:00
if ( i >= 0 ) { rootcert = rootcert . substring ( i + 29 ) ; }
2019-12-27 09:53:01 +03:00
i = rootcert . indexOf ( '-----END CERTIFICATE-----' ) ;
2017-08-28 19:27:45 +03:00
if ( i >= 0 ) { rootcert = rootcert . substring ( i , 0 ) ; }
2019-01-03 05:03:34 +03:00
return Buffer . from ( rootcert , 'base64' ) . toString ( 'base64' ) ;
2017-08-28 19:27:45 +03:00
}
// Returns the mesh server root certificate
function handleRootCertRequest ( req , res ) {
2019-11-22 05:57:57 +03:00
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleRootCertRequest: no domain' ) ; res . sendStatus ( 404 ) ; return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-08-24 01:45:36 +03:00
if ( ( obj . userAllowedIp != null ) && ( checkIpAddressEx ( req , res , obj . userAllowedIp , false ) === false ) ) { parent . debug ( 'web' , 'handleRootCertRequest: invalid ip' ) ; return ; } // Check server-wide IP filter only.
parent . debug ( 'web' , 'handleRootCertRequest()' ) ;
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , certificates . RootName + '.cer' , null , 'rootcert.cer' ) ;
2019-01-03 05:03:34 +03:00
res . send ( Buffer . from ( getRootCertBase64 ( ) , 'base64' ) ) ;
2017-08-28 19:27:45 +03:00
}
2024-09-24 21:09:14 +03:00
// Return a customised mainifest.json for PWA
function handleManifestRequest ( req , res ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleManifestRequest: no domain' ) ; res . sendStatus ( 404 ) ; return ; }
if ( ( obj . userAllowedIp != null ) && ( checkIpAddressEx ( req , res , obj . userAllowedIp , false ) === false ) ) { parent . debug ( 'web' , 'handleManifestRequest: invalid ip' ) ; return ; } // Check server-wide IP filter only.
parent . debug ( 'web' , 'handleManifestRequest()' ) ;
var manifest = {
"name" : ( domain . title != null ) ? domain . title : 'MeshCentral' ,
"short_name" : ( domain . title != null ) ? domain . title : 'MeshCentral' ,
"description" : "Open source web based, remote computer management." ,
"scope" : "." ,
"start_url" : "/" ,
"display" : "fullscreen" ,
"orientation" : "portrait" ,
"theme_color" : "#ffffff" ,
"background_color" : "#ffffff" ,
"icons" : [ {
"src" : "pwalogo.png" ,
"sizes" : "512x512" ,
"type" : "image/png"
} ]
} ;
res . json ( manifest ) ;
}
2017-08-28 19:27:45 +03:00
// Handle user public file downloads
function handleDownloadUserFiles ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-11-22 05:57:57 +03:00
2019-01-05 04:59:13 +03:00
if ( obj . common . validateString ( req . path , 1 , 4096 ) == false ) { res . sendStatus ( 404 ) ; return ; }
2017-10-03 00:12:29 +03:00
var domainname = 'domain' , spliturl = decodeURIComponent ( req . path ) . split ( '/' ) , filename = '' ;
2024-10-01 14:10:48 +03:00
if ( spliturl [ 1 ] != 'userfiles' ) { spliturl . splice ( 1 , 1 ) ; } // remove domain.id from url for domains without dns
2017-08-28 19:27:45 +03:00
if ( ( spliturl . length < 3 ) || ( obj . common . IsFilenameValid ( spliturl [ 2 ] ) == false ) || ( domain . userQuota == - 1 ) ) { res . sendStatus ( 404 ) ; return ; }
if ( domain . id != '' ) { domainname = 'domain-' + domain . id ; }
2019-12-27 09:53:01 +03:00
var path = obj . path . join ( obj . filespath , domainname + '/user-' + spliturl [ 2 ] + '/Public' ) ;
2017-08-28 19:27:45 +03:00
for ( var i = 3 ; i < spliturl . length ; i ++ ) { if ( obj . common . IsFilenameValid ( spliturl [ i ] ) == true ) { path += '/' + spliturl [ i ] ; filename = spliturl [ i ] ; } else { res . sendStatus ( 404 ) ; return ; } }
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
var stat = null ;
2018-08-30 22:05:23 +03:00
try { stat = obj . fs . statSync ( path ) ; } catch ( e ) { }
2017-08-28 19:27:45 +03:00
if ( ( stat != null ) && ( ( stat . mode & 0x004000 ) == 0 ) ) {
if ( req . query . download == 1 ) {
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , filename , null , 'file.bin' ) ;
2017-08-28 19:27:45 +03:00
try { res . sendFile ( obj . path . resolve ( _ _dirname , path ) ) ; } catch ( e ) { res . sendStatus ( 404 ) ; }
} else {
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'download2' : 'download' , req , domain ) , getRenderArgs ( { rootCertLink : getRootCertLink ( domain ) , messageid : 1 , fileurl : req . path + '?download=1' , filename : filename , filesize : stat . size } , req , domain ) ) ;
2017-08-28 19:27:45 +03:00
}
} else {
2024-10-19 21:28:23 +03:00
render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'download2' : 'download' , req , domain ) , getRenderArgs ( { rootCertLink : getRootCertLink ( domain ) , messageid : 2 } , req , domain ) ) ;
2017-08-28 19:27:45 +03:00
}
}
2020-08-27 04:42:41 +03:00
// Handle device file request
function handleDeviceFile ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
2021-04-09 23:27:21 +03:00
if ( ( req . query . c == null ) || ( req . query . f == null ) ) { res . sendStatus ( 404 ) ; return ; }
2020-08-27 04:42:41 +03:00
// Check the inbound desktop sharing cookie
var c = obj . parent . decodeCookie ( req . query . c , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
if ( ( c == null ) || ( c . domainid !== domain . id ) ) { res . sendStatus ( 404 ) ; return ; }
// Check userid
const user = obj . users [ c . userid ] ;
if ( ( c == user ) ) { res . sendStatus ( 404 ) ; return ; }
2021-04-09 23:27:21 +03:00
// If this cookie has restricted usages, check that it's allowed to perform downloads
if ( Array . isArray ( c . usages ) && ( c . usages . indexOf ( 10 ) < 0 ) ) { res . sendStatus ( 404 ) ; return ; } // Check protocol #10
if ( c . nid != null ) { req . query . n = c . nid . split ( '/' ) [ 2 ] ; } // This cookie is restricted to a specific nodeid.
if ( req . query . n == null ) { res . sendStatus ( 404 ) ; return ; }
2020-08-27 04:42:41 +03:00
// Check if this user has permission to manage this computer
2021-04-09 10:28:02 +03:00
obj . GetNodeWithRights ( domain , user , 'node/' + domain . id + '/' + req . query . n , function ( node , rights , visible ) {
if ( ( node == null ) || ( ( rights & MESHRIGHT _REMOTECONTROL ) == 0 ) || ( visible == false ) ) { res . sendStatus ( 404 ) ; return ; } // We don't have remote control rights to this device
2020-08-27 04:42:41 +03:00
2021-04-09 10:28:02 +03:00
// All good, start the file transfer
req . query . id = getRandomLowerCase ( 12 ) ;
obj . meshDeviceFileHandler . CreateMeshDeviceFile ( obj , null , res , req , domain , user , node . meshid , node . _id ) ;
} ) ;
2020-08-27 04:42:41 +03:00
}
2020-11-08 12:13:23 +03:00
// Handle download of a server file by an agent
function handleAgentDownloadFile ( req , res ) {
2024-06-28 16:50:57 +03:00
const domain = checkAgentIpAddress ( req , res ) ;
2020-11-08 12:13:23 +03:00
if ( domain == null ) { return ; }
if ( req . query . c == null ) { res . sendStatus ( 404 ) ; return ; }
// Check the inbound desktop sharing cookie
var c = obj . parent . decodeCookie ( req . query . c , obj . parent . loginCookieEncryptionKey , 5 ) ; // 5 minute timeout
if ( ( c == null ) || ( c . a != 'tmpdl' ) || ( c . d != domain . id ) || ( c . nid == null ) || ( c . f == null ) || ( obj . common . IsFilenameValid ( c . f ) == false ) ) { res . sendStatus ( 404 ) ; return ; }
// Send the file back
try { res . sendFile ( obj . path . join ( obj . filespath , 'tmp' , c . f ) ) ; return ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
2018-12-22 01:39:26 +03:00
// Handle logo request
function handleLogoRequest ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2018-12-22 01:39:26 +03:00
2019-12-21 01:50:06 +03:00
//res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
2020-03-26 22:06:34 +03:00
if ( domain . titlepicture ) {
2019-02-03 01:54:36 +03:00
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ domain . titlepicture ] != null ) ) {
// Use the logo in the database
2022-07-20 22:57:24 +03:00
res . set ( { 'Content-Type' : domain . titlepicture . toLowerCase ( ) . endsWith ( '.png' ) ? 'image/png' : 'image/jpeg' } ) ;
2019-02-03 01:54:36 +03:00
res . send ( parent . configurationFiles [ domain . titlepicture ] ) ;
2019-09-15 22:47:59 +03:00
return ;
2019-02-03 01:54:36 +03:00
} else {
// Use the logo on file
2021-07-29 17:38:44 +03:00
try { res . sendFile ( obj . common . joinPath ( obj . parent . datapath , domain . titlepicture ) ) ; return ; } catch ( ex ) { }
2018-12-22 01:39:26 +03:00
}
2019-09-15 22:47:59 +03:00
}
2020-03-26 22:06:34 +03:00
if ( ( domain . webpublicpath != null ) && ( obj . fs . existsSync ( obj . path . join ( domain . webpublicpath , 'images/logoback.png' ) ) ) ) {
2020-03-25 23:21:14 +03:00
// Use the domain logo picture
try { res . sendFile ( obj . path . join ( domain . webpublicpath , 'images/logoback.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else if ( parent . webPublicOverridePath && obj . fs . existsSync ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/logoback.png' ) ) ) {
2019-09-15 22:47:59 +03:00
// Use the override logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/logoback.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2018-12-22 01:39:26 +03:00
} else {
2019-09-15 22:47:59 +03:00
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , 'images/logoback.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2018-12-22 01:39:26 +03:00
}
}
2020-09-03 12:19:17 +03:00
// Handle login logo request
function handleLoginLogoRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
//res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
if ( domain . loginpicture ) {
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ domain . loginpicture ] != null ) ) {
// Use the logo in the database
res . set ( { 'Content-Type' : domain . loginpicture . toLowerCase ( ) . endsWith ( '.png' ) ? 'image/png' : 'image/jpeg' } ) ;
res . send ( parent . configurationFiles [ domain . loginpicture ] ) ;
return ;
} else {
// Use the logo on file
2021-07-29 17:38:44 +03:00
try { res . sendFile ( obj . common . joinPath ( obj . parent . datapath , domain . loginpicture ) ) ; return ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2020-09-03 12:19:17 +03:00
}
2020-12-30 02:13:09 +03:00
} else {
res . sendStatus ( 404 ) ;
2020-09-03 12:19:17 +03:00
}
}
2024-09-24 21:09:14 +03:00
// Handle PWA logo request
function handlePWALogoRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
//res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
if ( domain . pwalogo ) {
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ domain . pwalogo ] != null ) ) {
// Use the logo in the database
res . set ( { 'Content-Type' : domain . pwalogo . toLowerCase ( ) . endsWith ( '.png' ) ? 'image/png' : 'image/jpeg' } ) ;
res . send ( parent . configurationFiles [ domain . pwalogo ] ) ;
return ;
} else {
// Use the logo on file
try { res . sendFile ( obj . common . joinPath ( obj . parent . datapath , domain . pwalogo ) ) ; return ; } catch ( ex ) { }
}
}
if ( ( domain . webpublicpath != null ) && ( obj . fs . existsSync ( obj . path . join ( domain . webpublicpath , 'android-chrome-512x512.png' ) ) ) ) {
// Use the domain logo picture
try { res . sendFile ( obj . path . join ( domain . webpublicpath , 'android-chrome-512x512.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else if ( parent . webPublicOverridePath && obj . fs . existsSync ( obj . path . join ( obj . parent . webPublicOverridePath , 'android-chrome-512x512.png' ) ) ) {
// Use the override logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicOverridePath , 'android-chrome-512x512.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else {
// Use the default logo picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , 'android-chrome-512x512.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
}
2019-12-06 02:33:22 +03:00
// Handle translation request
function handleTranslationsRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-12-06 02:33:22 +03:00
//if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
if ( ( obj . userAllowedIp != null ) && ( checkIpAddressEx ( req , res , obj . userAllowedIp , false ) === false ) ) { return ; } // Check server-wide IP filter only.
var user = null ;
if ( obj . args . user != null ) {
// A default user is active
user = obj . users [ 'user/' + domain . id + '/' + obj . args . user ] ;
if ( ! user ) { parent . debug ( 'web' , 'handleTranslationsRequest: user not found.' ) ; res . sendStatus ( 401 ) ; return ; }
} else {
// Check if the user is logged and we have all required parameters
if ( ! req . session || ! req . session . userid ) { parent . debug ( 'web' , 'handleTranslationsRequest: failed checks (2).' ) ; res . sendStatus ( 401 ) ; return ; }
// Get the current user
user = obj . users [ req . session . userid ] ;
if ( ! user ) { parent . debug ( 'web' , 'handleTranslationsRequest: user not found.' ) ; res . sendStatus ( 401 ) ; return ; }
if ( user . siteadmin != 0xFFFFFFFF ) { parent . debug ( 'web' , 'handleTranslationsRequest: user not site administrator.' ) ; res . sendStatus ( 401 ) ; return ; }
}
var data = '' ;
req . setEncoding ( 'utf8' ) ;
req . on ( 'data' , function ( chunk ) { data += chunk ; } ) ;
req . on ( 'end' , function ( ) {
try { data = JSON . parse ( data ) ; } catch ( ex ) { data = null ; }
if ( data == null ) { res . sendStatus ( 404 ) ; return ; }
if ( data . action == 'getTranslations' ) {
if ( obj . fs . existsSync ( obj . path . join ( obj . parent . datapath , 'translate.json' ) ) ) {
// Return the translation file (JSON)
try { res . sendFile ( obj . path . join ( obj . parent . datapath , 'translate.json' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else if ( obj . fs . existsSync ( obj . path . join ( _ _dirname , 'translate' , 'translate.json' ) ) ) {
// Return the default translation file (JSON)
try { res . sendFile ( obj . path . join ( _ _dirname , 'translate' , 'translate.json' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else { res . sendStatus ( 404 ) ; }
} else if ( data . action == 'setTranslations' ) {
2020-01-08 02:10:12 +03:00
obj . fs . writeFile ( obj . path . join ( obj . parent . datapath , 'translate.json' ) , obj . common . translationsToJson ( { strings : data . strings } ) , function ( err ) { if ( err == null ) { res . send ( JSON . stringify ( { response : 'ok' } ) ) ; } else { res . send ( JSON . stringify ( { response : err } ) ) ; } } ) ;
2019-12-07 03:49:40 +03:00
} else if ( data . action == 'translateServer' ) {
if ( obj . pendingTranslation === true ) { res . send ( JSON . stringify ( { response : 'Server is already performing a translation.' } ) ) ; return ; }
const nodeVersion = Number ( process . version . match ( /^v(\d+\.\d+)/ ) [ 1 ] ) ;
if ( nodeVersion < 8 ) { res . send ( JSON . stringify ( { response : 'Server requires NodeJS 8.x or better.' } ) ) ; return ; }
var translateFile = obj . path . join ( obj . parent . datapath , 'translate.json' ) ;
if ( obj . fs . existsSync ( translateFile ) == false ) { translateFile = obj . path . join ( _ _dirname , 'translate' , 'translate.json' ) ; }
if ( obj . fs . existsSync ( translateFile ) == false ) { res . send ( JSON . stringify ( { response : 'Unable to find translate.js file on the server.' } ) ) ; return ; }
res . send ( JSON . stringify ( { response : 'ok' } ) ) ;
console . log ( 'Started server translation...' ) ;
obj . pendingTranslation = true ;
require ( 'child_process' ) . exec ( 'node translate.js translateall \"' + translateFile + '\"' , { maxBuffer : 512000 , timeout : 120000 , cwd : obj . path . join ( _ _dirname , 'translate' ) } , function ( error , stdout , stderr ) {
delete obj . pendingTranslation ;
//console.log('error', error);
//console.log('stdout', stdout);
//console.log('stderr', stderr);
//console.log('Server restart...'); // Perform a server restart
//process.exit(0);
console . log ( 'Server translation completed.' ) ;
} ) ;
2019-12-06 02:33:22 +03:00
} else {
// Unknown request
res . sendStatus ( 404 ) ;
}
} ) ;
}
2019-02-14 00:22:52 +03:00
// Handle welcome image request
function handleWelcomeImageRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-02-14 00:22:52 +03:00
2019-12-21 01:50:06 +03:00
//res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
2020-03-26 22:06:34 +03:00
if ( domain . welcomepicture ) {
2019-02-14 00:22:52 +03:00
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ domain . welcomepicture ] != null ) ) {
// Use the welcome image in the database
2022-07-20 22:57:24 +03:00
res . set ( { 'Content-Type' : domain . welcomepicture . toLowerCase ( ) . endsWith ( '.png' ) ? 'image/png' : 'image/jpeg' } ) ;
2019-02-14 00:22:52 +03:00
res . send ( parent . configurationFiles [ domain . welcomepicture ] ) ;
2019-09-15 22:47:59 +03:00
return ;
2019-02-14 00:22:52 +03:00
}
2019-09-26 20:16:22 +03:00
// Use the configured logo picture
2021-07-29 17:38:44 +03:00
try { res . sendFile ( obj . common . joinPath ( obj . parent . datapath , domain . welcomepicture ) ) ; return ; } catch ( ex ) { }
2019-09-15 22:47:59 +03:00
}
2020-09-03 12:19:17 +03:00
var imagefile = 'images/mainwelcome.jpg' ;
2024-10-19 21:28:23 +03:00
if ( domain . sitestyle == 2 || domain . sitestyle == 3 ) { imagefile = 'images/login/back.png' ; }
2020-03-26 22:06:34 +03:00
if ( domain . webpublicpath != null ) {
2020-09-03 12:19:17 +03:00
obj . fs . exists ( obj . path . join ( domain . webpublicpath , imagefile ) , function ( exists ) {
2020-03-25 23:21:14 +03:00
if ( exists ) {
// Use the domain logo picture
2020-09-03 12:19:17 +03:00
try { res . sendFile ( obj . path . join ( domain . webpublicpath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2020-03-25 23:21:14 +03:00
} else {
// Use the default logo picture
2020-09-03 12:19:17 +03:00
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2020-03-25 23:21:14 +03:00
}
} ) ;
} else if ( parent . webPublicOverridePath ) {
2020-09-03 12:19:17 +03:00
obj . fs . exists ( obj . path . join ( obj . parent . webPublicOverridePath , imagefile ) , function ( exists ) {
2019-12-21 01:02:49 +03:00
if ( exists ) {
// Use the override logo picture
2020-09-03 12:19:17 +03:00
try { res . sendFile ( obj . path . join ( obj . parent . webPublicOverridePath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2019-12-21 01:02:49 +03:00
} else {
// Use the default logo picture
2020-09-03 12:19:17 +03:00
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2019-12-21 01:02:49 +03:00
}
} ) ;
2019-02-14 00:22:52 +03:00
} else {
2019-09-15 22:47:59 +03:00
// Use the default logo picture
2020-09-03 12:19:17 +03:00
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , imagefile ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2019-02-14 00:22:52 +03:00
}
}
2021-11-08 05:07:45 +03:00
// Download a session recording
2020-05-05 23:00:51 +03:00
function handleGetRecordings ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-06-22 00:27:10 +03:00
if ( domain == null ) return ;
2020-05-05 23:00:51 +03:00
// Check the query
2021-11-08 05:07:45 +03:00
if ( ( domain . sessionrecording == null ) || ( req . query . file == null ) || ( obj . common . IsFilenameValid ( req . query . file ) !== true ) || ( req . query . file . endsWith ( '.mcrec' ) == false ) ) { res . sendStatus ( 401 ) ; return ; }
2020-05-05 23:00:51 +03:00
// Get the recording path
var recordingsPath = null ;
if ( domain . sessionrecording . filepath ) { recordingsPath = domain . sessionrecording . filepath ; } else { recordingsPath = parent . recordpath ; }
if ( recordingsPath == null ) { res . sendStatus ( 401 ) ; return ; }
// Get the user and check user rights
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
const user = obj . users [ authUserid ] ;
if ( user == null ) { res . sendStatus ( 401 ) ; return ; }
if ( ( user . siteadmin & 512 ) == 0 ) { res . sendStatus ( 401 ) ; return ; } // Check if we have right to get recordings
// Send the recorded file
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , req . query . file , null , 'recording.mcrec' ) ;
2020-05-05 23:00:51 +03:00
try { res . sendFile ( obj . path . join ( recordingsPath , req . query . file ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
2021-11-08 05:07:45 +03:00
// Stream a session recording
function handleGetRecordingsWebSocket ( ws , req ) {
var domain = checkAgentIpAddress ( ws , req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'Got recordings file transfer connection with bad domain or blocked IP address ' + req . clientIp + ', dropping.' ) ; try { ws . close ( ) ; } catch ( ex ) { } return ; }
// Check the query
if ( ( domain . sessionrecording == null ) || ( req . query . file == null ) || ( obj . common . IsFilenameValid ( req . query . file ) !== true ) || ( req . query . file . endsWith ( '.mcrec' ) == false ) ) { try { ws . close ( ) ; } catch ( ex ) { } return ; }
// Get the recording path
var recordingsPath = null ;
if ( domain . sessionrecording . filepath ) { recordingsPath = domain . sessionrecording . filepath ; } else { recordingsPath = parent . recordpath ; }
if ( recordingsPath == null ) { try { ws . close ( ) ; } catch ( ex ) { } return ; }
// Get the user and check user rights
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
if ( authUserid == null ) { try { ws . close ( ) ; } catch ( ex ) { } return ; }
const user = obj . users [ authUserid ] ;
if ( user == null ) { try { ws . close ( ) ; } catch ( ex ) { } return ; }
if ( ( user . siteadmin & 512 ) == 0 ) { try { ws . close ( ) ; } catch ( ex ) { } return ; } // Check if we have right to get recordings
const filefullpath = obj . path . join ( recordingsPath , req . query . file ) ;
2022-07-20 22:57:24 +03:00
obj . fs . stat ( filefullpath , function ( err , stats ) {
2021-11-08 05:07:45 +03:00
if ( err ) {
try { ws . close ( ) ; } catch ( ex ) { } // File does not exist
} else {
2021-12-14 00:20:04 +03:00
obj . fs . open ( filefullpath , 'r' , function ( err , fd ) {
2021-11-08 05:07:45 +03:00
if ( err == null ) {
// When data is received from the web socket
ws . on ( 'message' , function ( msg ) {
if ( typeof msg != 'string' ) return ;
var command ;
try { command = JSON . parse ( msg ) ; } catch ( e ) { return ; }
if ( ( command == null ) || ( typeof command . action != 'string' ) ) return ;
switch ( command . action ) {
case 'get' : {
const buffer = Buffer . alloc ( 8 + command . size ) ;
//buffer.writeUInt32BE((command.ptr >> 32), 0);
buffer . writeUInt32BE ( ( command . ptr & 0xFFFFFFFF ) , 4 ) ;
obj . fs . read ( fd , buffer , 8 , command . size , command . ptr , function ( err , bytesRead , buffer ) { if ( bytesRead > ( buffer . length - 8 ) ) { buffer = buffer . slice ( 0 , bytesRead + 8 ) ; } ws . send ( buffer ) ; } ) ;
break ;
}
}
} ) ;
// If error, do nothing
ws . on ( 'error' , function ( err ) { try { ws . close ( ) ; } catch ( ex ) { } obj . fs . close ( fd , function ( err ) { } ) ; } ) ;
// If the web socket is closed
ws . on ( 'close' , function ( req ) { try { ws . close ( ) ; } catch ( ex ) { } obj . fs . close ( fd , function ( err ) { } ) ; } ) ;
ws . send ( JSON . stringify ( { "action" : "info" , "name" : req . query . file , "size" : stats . size } ) ) ;
} else {
try { ws . close ( ) ; } catch ( ex ) { }
}
} ) ;
}
} ) ;
}
2020-08-07 02:05:48 +03:00
// Serve the player page
2020-02-05 03:22:00 +03:00
function handlePlayerRequest ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2020-02-05 03:22:00 +03:00
parent . debug ( 'web' , 'handlePlayerRequest: sending player' ) ;
2020-10-02 23:17:53 +03:00
res . set ( { 'Cache-Control' : 'no-store' } ) ;
2020-05-03 09:49:59 +03:00
render ( req , res , getRenderPage ( 'player' , req , domain ) , getRenderArgs ( { } , req , domain ) ) ;
2020-02-05 03:22:00 +03:00
}
2021-04-09 10:28:02 +03:00
// Serve the guest sharing page
function handleSharingRequest ( req , res ) {
const domain = getDomain ( req , res ) ;
if ( domain == null ) { return ; }
if ( req . query . c == null ) { res . sendStatus ( 404 ) ; return ; }
if ( domain . guestdevicesharing === false ) { res . sendStatus ( 404 ) ; return ; } // This feature is not allowed.
2021-09-02 23:45:57 +03:00
// Check the inbound guest sharing cookie
var c = obj . parent . decodeCookie ( req . query . c , obj . parent . invitationLinkEncryptionKey , 9999999999 ) ; // Decode cookies with unlimited time.
2021-09-12 08:58:44 +03:00
if ( c == null ) { res . sendStatus ( 404 ) ; return ; }
2021-04-09 10:28:02 +03:00
2021-09-12 08:58:44 +03:00
if ( c . a === 5 ) {
// This is the older style sharing cookie with everything encoded within it.
// This cookie style gives a very large URL, so it's not used anymore.
if ( ( typeof c . p !== 'number' ) || ( c . p < 1 ) || ( c . p > 7 ) || ( typeof c . uid != 'string' ) || ( typeof c . nid != 'string' ) || ( typeof c . gn != 'string' ) || ( typeof c . cf != 'number' ) || ( typeof c . pid != 'string' ) ) { res . sendStatus ( 404 ) ; return ; }
handleSharingRequestEx ( req , res , domain , c ) ;
return ;
}
if ( c . a === 6 ) {
// This is the new style sharing cookie, just encodes the pointer to the sharing information in the database.
// Gives a much more compact URL.
if ( typeof c . pid != 'string' ) { res . sendStatus ( 404 ) ; return ; }
2021-09-12 21:17:54 +03:00
// Check the expired time, expire message.
2024-10-19 21:28:23 +03:00
if ( ( c . e != null ) && ( c . e <= Date . now ( ) ) ) { render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 2 , msgid : 12 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ; return ; }
2021-09-12 21:17:54 +03:00
2021-09-12 08:58:44 +03:00
obj . db . Get ( 'deviceshare-' + c . pid , function ( err , docs ) {
if ( ( err != null ) || ( docs == null ) || ( docs . length != 1 ) ) { res . sendStatus ( 404 ) ; return ; }
const doc = docs [ 0 ] ;
2022-01-17 05:10:56 +03:00
2022-09-07 17:40:36 +03:00
// If this is a recurrent share, check if we are at the correct time to make use of it
2022-01-17 05:10:56 +03:00
if ( typeof doc . recurring == 'number' ) {
const now = Date . now ( ) ;
if ( now >= doc . startTime ) { // We don't want to move the validity window before the start time
const deltaTime = ( now - doc . startTime ) ;
if ( doc . recurring === 1 ) {
// This moves the start time to the next valid daily window
const oneDay = ( 24 * 60 * 60 * 1000 ) ;
var addition = Math . floor ( deltaTime / oneDay ) ;
if ( ( deltaTime - ( addition * oneDay ) ) > ( doc . duration * 60000 ) ) { addition ++ ; } // If we are passed the current windows, move to the next one. This will show link as not being valid yet.
doc . startTime += ( addition * oneDay ) ;
} else if ( doc . recurring === 2 ) {
// This moves the start time to the next valid weekly window
const oneWeek = ( 7 * 24 * 60 * 60 * 1000 ) ;
var addition = Math . floor ( deltaTime / oneWeek ) ;
if ( ( deltaTime - ( addition * oneWeek ) ) > ( doc . duration * 60000 ) ) { addition ++ ; } // If we are passed the current windows, move to the next one. This will show link as not being valid yet.
doc . startTime += ( addition * oneWeek ) ;
}
}
}
2021-09-12 08:58:44 +03:00
// Generate an old style cookie from the information in the database
2022-08-26 06:11:47 +03:00
var cookie = { a : 5 , p : doc . p , gn : doc . guestName , nid : doc . nodeid , cf : doc . consent , pid : doc . publicid , k : doc . extrakey ? doc . extrakey : null , port : doc . port } ;
2021-11-11 04:56:12 +03:00
if ( doc . userid ) { cookie . uid = doc . userid ; }
2021-11-11 00:21:30 +03:00
if ( ( cookie . userid == null ) && ( cookie . pid . startsWith ( 'AS:node/' ) ) ) { cookie . nouser = 1 ; }
2022-01-17 05:10:56 +03:00
if ( doc . startTime != null ) {
if ( doc . expireTime != null ) { cookie . start = doc . startTime ; cookie . expire = doc . expireTime ; }
else if ( doc . duration != null ) { cookie . start = doc . startTime ; cookie . expire = doc . startTime + ( doc . duration * 60000 ) ; }
}
2021-09-12 08:58:44 +03:00
if ( doc . viewOnly === true ) { cookie . vo = 1 ; }
handleSharingRequestEx ( req , res , domain , cookie ) ;
} ) ;
return ;
}
res . sendStatus ( 404 ) ; return ;
}
// Serve the guest sharing page
function handleSharingRequestEx ( req , res , domain , c ) {
2021-04-09 10:28:02 +03:00
// Check the expired time, expire message.
2024-10-19 21:28:23 +03:00
if ( ( c . expire != null ) && ( c . expire <= Date . now ( ) ) ) { render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 2 , msgid : 12 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ; return ; }
2021-04-09 10:28:02 +03:00
// Check the public id
obj . db . GetAllTypeNodeFiltered ( [ c . nid ] , domain . id , 'deviceshare' , null , function ( err , docs ) {
2022-08-26 06:11:47 +03:00
// Check if any sharing links are present, expire message.
2024-10-19 21:28:23 +03:00
if ( ( err != null ) || ( docs . length == 0 ) ) { render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 2 , msgid : 12 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ; return ; }
2021-04-09 10:28:02 +03:00
// Search for the device share public identifier, expire message.
var found = false ;
2021-11-11 04:56:12 +03:00
for ( var i = 0 ; i < docs . length ; i ++ ) { if ( ( docs [ i ] . publicid == c . pid ) && ( ( docs [ i ] . extrakey == null ) || ( docs [ i ] . extrakey === c . k ) ) ) { found = true ; } }
2024-10-19 21:28:23 +03:00
if ( found == false ) { render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 2 , msgid : 12 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ; return ; }
2021-04-09 10:28:02 +03:00
// Get information about this node
obj . db . Get ( c . nid , function ( err , nodes ) {
if ( ( err != null ) || ( nodes == null ) || ( nodes . length != 1 ) ) { res . sendStatus ( 404 ) ; return ; }
var node = nodes [ 0 ] ;
// Check the start time, not yet valid message.
2024-10-19 21:28:23 +03:00
if ( ( c . start != null ) && ( c . expire != null ) && ( ( c . start > Date . now ( ) ) || ( c . start > c . expire ) ) ) { render ( req , res , getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'message2' : 'message' , req , domain ) , getRenderArgs ( { titleid : 2 , msgid : 11 , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) } , req , domain ) ) ; return ; }
2021-04-09 10:28:02 +03:00
2022-08-26 06:11:47 +03:00
// If this is a web relay share, check if this feature is active
if ( ( c . p == 8 ) || ( c . p == 16 ) ) {
// This is a HTTP or HTTPS share
var webRelayPort = ( ( args . relaydns != null ) ? ( ( typeof args . aliasport == 'number' ) ? args . aliasport : args . port ) : ( ( parent . webrelayserver != null ) ? ( ( typeof args . relayaliasport == 'number' ) ? args . relayaliasport : parent . webrelayserver . port ) : 0 ) ) ;
if ( webRelayPort == 0 ) { res . sendStatus ( 404 ) ; return ; }
// Create the authentication cookie
const authCookieData = { userid : c . uid , domainid : domain . id , nid : c . nid , ip : req . clientIp , p : c . p , gn : c . gn , r : 8 , expire : c . expire , pid : c . pid , port : c . port } ;
if ( ( authCookieData . userid == null ) && ( authCookieData . pid . startsWith ( 'AS:node/' ) ) ) { authCookieData . nouser = 1 ; }
const authCookie = obj . parent . encodeCookie ( authCookieData , obj . parent . loginCookieEncryptionKey ) ;
// Redirect to a URL
var webRelayDns = ( args . relaydns != null ) ? args . relaydns [ 0 ] : obj . getWebServerName ( domain , req ) ;
var url = 'https://' + webRelayDns + ':' + webRelayPort + '/control-redirect.ashx?n=' + c . nid + '&p=' + c . port + '&appid=' + c . p + '&c=' + authCookie ;
if ( c . addr != null ) { url += '&addr=' + c . addr ; }
if ( c . pid != null ) { url += '&relayid=' + c . pid ; }
parent . debug ( 'web' , 'handleSharingRequest: Redirecting guest to HTTP relay page for \"' + c . uid + '\", guest \"' + c . gn + '\".' ) ;
res . redirect ( url ) ;
} else {
// Looks good, let's create the outbound session cookies.
// This is a desktop, terminal or files share. We need to display the sharing page.
// Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
const authCookieData = { userid : c . uid , domainid : domain . id , nid : c . nid , ip : req . clientIp , p : c . p , gn : c . gn , cf : c . cf , r : 8 , expire : c . expire , pid : c . pid , vo : c . vo } ;
if ( ( authCookieData . userid == null ) && ( authCookieData . pid . startsWith ( 'AS:node/' ) ) ) { authCookieData . nouser = 1 ; }
if ( c . k != null ) { authCookieData . k = c . k ; }
const authCookie = obj . parent . encodeCookie ( authCookieData , obj . parent . loginCookieEncryptionKey ) ;
// Server features
var features2 = 0 ;
if ( obj . args . allowhighqualitydesktop !== false ) { features2 += 1 ; } // Enable AllowHighQualityDesktop (Default true)
// Lets respond by sending out the desktop viewer.
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
parent . debug ( 'web' , 'handleSharingRequest: Sending guest sharing page for \"' + c . uid + '\", guest \"' + c . gn + '\".' ) ;
res . set ( { 'Cache-Control' : 'no-store' } ) ;
render ( req , res , getRenderPage ( 'sharing' , req , domain ) , getRenderArgs ( { authCookie : authCookie , authRelayCookie : '' , domainurl : encodeURIComponent ( domain . url ) . replace ( /'/g , '%27' ) , nodeid : c . nid , serverDnsName : obj . getWebServerName ( domain , req ) , serverRedirPort : args . redirport , serverPublicPort : httpsPort , expire : c . expire , viewOnly : ( c . vo == 1 ) ? 1 : 0 , nodeName : encodeURIComponent ( node . name ) . replace ( /'/g , '%27' ) , features : c . p , features2 : features2 } , req , domain ) ) ;
}
2021-04-09 10:28:02 +03:00
} ) ;
} ) ;
}
2019-06-07 20:20:54 +03:00
// Handle domain redirection
2019-10-04 22:18:56 +03:00
obj . handleDomainRedirect = function ( req , res ) {
2019-06-07 20:20:54 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
if ( domain . redirects == null ) { res . sendStatus ( 404 ) ; return ; }
2019-12-27 09:53:01 +03:00
var urlArgs = '' , urlName = null , splitUrl = req . originalUrl . split ( '?' ) ;
2019-06-07 20:20:54 +03:00
if ( splitUrl . length > 1 ) { urlArgs = '?' + splitUrl [ 1 ] ; }
if ( ( splitUrl . length > 0 ) && ( splitUrl [ 0 ] . length > 1 ) ) { urlName = splitUrl [ 0 ] . substring ( 1 ) . toLowerCase ( ) ; }
2019-06-18 01:37:13 +03:00
if ( ( urlName == null ) || ( domain . redirects [ urlName ] == null ) || ( urlName [ 0 ] == '_' ) ) { res . sendStatus ( 404 ) ; return ; }
2019-06-18 03:17:23 +03:00
if ( domain . redirects [ urlName ] == '~showversion' ) {
// Show the current version
res . end ( 'MeshCentral v' + obj . parent . currentVer ) ;
} else {
// Perform redirection
res . redirect ( domain . redirects [ urlName ] + urlArgs + getQueryPortion ( req ) ) ;
}
2019-06-07 20:20:54 +03:00
}
2017-12-01 01:40:46 +03:00
// Take a "user/domain/userid/path/file" format and return the actual server disk file path if access is allowed
2018-05-24 07:43:44 +03:00
obj . getServerFilePath = function ( user , domain , path ) {
2017-12-01 01:40:46 +03:00
var splitpath = path . split ( '/' ) , serverpath = obj . path . join ( obj . filespath , 'domain' ) , filename = '' ;
if ( ( splitpath . length < 3 ) || ( splitpath [ 0 ] != 'user' && splitpath [ 0 ] != 'mesh' ) || ( splitpath [ 1 ] != domain . id ) ) return null ; // Basic validation
var objid = splitpath [ 0 ] + '/' + splitpath [ 1 ] + '/' + splitpath [ 2 ] ;
if ( splitpath [ 0 ] == 'user' && ( objid != user . _id ) ) return null ; // User validation, only self allowed
2019-12-28 02:18:43 +03:00
if ( splitpath [ 0 ] == 'mesh' ) { if ( ( obj . GetMeshRights ( user , objid ) & 32 ) == 0 ) { return null ; } } // Check mesh server file rights
2017-12-01 01:40:46 +03:00
if ( splitpath [ 1 ] != '' ) { serverpath += '-' + splitpath [ 1 ] ; } // Add the domain if needed
serverpath += ( '/' + splitpath [ 0 ] + '-' + splitpath [ 2 ] ) ;
for ( var i = 3 ; i < splitpath . length ; i ++ ) { if ( obj . common . IsFilenameValid ( splitpath [ i ] ) == true ) { serverpath += '/' + splitpath [ i ] ; filename = splitpath [ i ] ; } else { return null ; } } // Check that each folder is correct
2018-08-30 22:05:23 +03:00
return { fullpath : obj . path . resolve ( obj . filespath , serverpath ) , path : serverpath , name : filename , quota : obj . getQuota ( objid , domain ) } ;
} ;
2017-12-01 01:40:46 +03:00
// Return the maximum number of bytes allowed in the user account "My Files".
2018-05-24 07:43:44 +03:00
obj . getQuota = function ( objid , domain ) {
2017-12-01 01:40:46 +03:00
if ( objid == null ) return 0 ;
if ( objid . startsWith ( 'user/' ) ) {
var user = obj . users [ objid ] ;
if ( user == null ) return 0 ;
2018-09-01 01:23:42 +03:00
if ( user . siteadmin == 0xFFFFFFFF ) return null ; // Administrators have no user limit
2017-12-01 01:40:46 +03:00
if ( ( user . quota != null ) && ( typeof user . quota == 'number' ) ) { return user . quota ; }
2018-09-01 01:23:42 +03:00
if ( ( domain != null ) && ( domain . userquota != null ) && ( typeof domain . userquota == 'number' ) ) { return domain . userquota ; }
return null ; // By default, the user will have no limit
2017-12-01 01:40:46 +03:00
} else if ( objid . startsWith ( 'mesh/' ) ) {
var mesh = obj . meshes [ objid ] ;
if ( mesh == null ) return 0 ;
if ( ( mesh . quota != null ) && ( typeof mesh . quota == 'number' ) ) { return mesh . quota ; }
2018-09-01 01:23:42 +03:00
if ( ( domain != null ) && ( domain . meshquota != null ) && ( typeof domain . meshquota == 'number' ) ) { return domain . meshquota ; }
return null ; // By default, the mesh will have no limit
2017-12-01 01:40:46 +03:00
}
return 0 ;
2018-08-30 22:05:23 +03:00
} ;
2017-12-01 01:40:46 +03:00
2017-08-28 19:27:45 +03:00
// Download a file from the server
function handleDownloadFile ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2017-09-15 21:45:06 +03:00
if ( ( req . query . link == null ) || ( req . session == null ) || ( req . session . userid == null ) || ( domain == null ) || ( domain . userQuota == - 1 ) ) { res . sendStatus ( 404 ) ; return ; }
2019-01-05 23:04:18 +03:00
const user = obj . users [ req . session . userid ] ;
2017-09-15 21:45:06 +03:00
if ( user == null ) { res . sendStatus ( 404 ) ; return ; }
2019-01-05 23:04:18 +03:00
const file = obj . getServerFilePath ( user , domain , req . query . link ) ;
2017-08-28 19:27:45 +03:00
if ( file == null ) { res . sendStatus ( 404 ) ; return ; }
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , file . name , null , 'file.bin' ) ;
2020-07-07 01:04:22 +03:00
obj . fs . exists ( file . fullpath , function ( exists ) { if ( exists == true ) { res . sendFile ( file . fullpath ) ; } else { res . sendStatus ( 404 ) ; } } ) ;
2017-08-28 19:27:45 +03:00
}
2018-05-24 07:43:44 +03:00
2022-08-22 23:06:25 +03:00
// Download the MeshCommander web page
function handleMeshCommander ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
if ( ( req . session == null ) || ( req . session . userid == null ) ) { res . sendStatus ( 404 ) ; return ; }
// Find the correct MeshCommander language to send
const acceptableLanguages = obj . getLanguageCodes ( req ) ;
const commandLanguageTranslations = { 'en' : '' , 'de' : '-de' , 'es' : '-es' , 'fr' : '-fr' , 'it' : '-it' , 'ja' : '-ja' , 'ko' : '-ko' , 'nl' : '-nl' , 'pt' : '-pt' , 'ru' : '-ru' , 'zh-chs' : '-zh-chs' , 'zh-cht' : '-zh-chs' } ;
for ( var i in acceptableLanguages ) {
const meshCommanderLanguage = commandLanguageTranslations [ acceptableLanguages [ i ] ] ;
if ( meshCommanderLanguage != null ) {
try { res . sendFile ( obj . parent . path . join ( parent . webPublicPath , 'commander' + meshCommanderLanguage + '.htm' ) ) ; } catch ( ex ) { }
return ;
}
}
// Send out the default english MeshCommander
try { res . sendFile ( obj . parent . path . join ( parent . webPublicPath , 'commander.htm' ) ) ; } catch ( ex ) { }
}
2017-08-28 19:27:45 +03:00
// Upload a MeshCore.js file to the server
function handleUploadMeshCoreFile ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-10-01 00:50:28 +03:00
if ( domain . id !== '' ) { res . sendStatus ( 401 ) ; return ; }
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
2018-05-24 07:43:44 +03:00
2019-01-05 23:04:18 +03:00
const multiparty = require ( 'multiparty' ) ;
const form = new multiparty . Form ( ) ;
2017-08-28 19:27:45 +03:00
form . parse ( req , function ( err , fields , files ) {
2019-10-01 00:50:28 +03:00
// If an authentication cookie is embedded in the form, use that.
if ( ( fields != null ) && ( fields . auth != null ) && ( fields . auth . length == 1 ) && ( typeof fields . auth [ 0 ] == 'string' ) ) {
var loginCookie = obj . parent . decodeCookie ( fields . auth [ 0 ] , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2022-04-13 00:03:52 +03:00
if ( ( loginCookie != null ) && ( loginCookie . ip != null ) && ! checkCookieIp ( loginCookie . ip , req . clientIp ) ) { loginCookie = null ; } // Check cookie IP binding.
2019-10-01 00:50:28 +03:00
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { authUserid = loginCookie . userid ; } // Use cookie authentication
}
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
2021-02-05 00:44:40 +03:00
if ( ( fields == null ) || ( fields . attrib == null ) || ( fields . attrib . length != 1 ) ) { res . sendStatus ( 404 ) ; return ; }
2019-10-01 00:50:28 +03:00
// Get the user
const user = obj . users [ authUserid ] ;
2021-02-05 00:44:40 +03:00
if ( user == null ) { res . sendStatus ( 401 ) ; return ; } // Check this user exists
// Get the node and check node rights
const nodeid = fields . attrib [ 0 ] ;
obj . GetNodeWithRights ( domain , user , nodeid , function ( node , rights , visible ) {
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 ) {
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 ) { }
} ) ;
}
res . send ( '' ) ;
} ) ;
2017-08-28 19:27:45 +03:00
} ) ;
}
2021-03-23 04:45:07 +03:00
// Upload a MeshCore.js file to the server
2021-03-23 09:36:05 +03:00
function handleOneClickRecoveryFile ( req , res ) {
2021-03-23 04:45:07 +03:00
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
if ( domain . id !== '' ) { res . sendStatus ( 401 ) ; return ; }
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
const multiparty = require ( 'multiparty' ) ;
const form = new multiparty . Form ( ) ;
form . parse ( req , function ( err , fields , files ) {
// If an authentication cookie is embedded in the form, use that.
if ( ( fields != null ) && ( fields . auth != null ) && ( fields . auth . length == 1 ) && ( typeof fields . auth [ 0 ] == 'string' ) ) {
var loginCookie = obj . parent . decodeCookie ( fields . auth [ 0 ] , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2022-04-13 00:03:52 +03:00
if ( ( loginCookie != null ) && ( loginCookie . ip != null ) && ! checkCookieIp ( loginCookie . ip , req . clientIp ) ) { loginCookie = null ; } // Check cookie IP binding.
2021-03-23 04:45:07 +03:00
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { authUserid = loginCookie . userid ; } // Use cookie authentication
}
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
if ( ( fields == null ) || ( fields . attrib == null ) || ( fields . attrib . length != 1 ) ) { res . sendStatus ( 404 ) ; return ; }
// Get the user
const user = obj . users [ authUserid ] ;
if ( user == null ) { res . sendStatus ( 401 ) ; return ; } // Check this user exists
// Get the node and check node rights
const nodeid = fields . attrib [ 0 ] ;
obj . GetNodeWithRights ( domain , user , nodeid , function ( node , rights , visible ) {
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 ] ;
2021-03-23 05:00:50 +03:00
2021-03-23 09:36:05 +03:00
// 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 } ) ;
2021-03-23 05:00:50 +03:00
2021-03-24 00:48:57 +03:00
//try { obj.fs.unlinkSync(file.path); } catch (e) { } // TODO: Remove this file after 30 minutes.
2021-03-23 04:45:07 +03:00
}
res . send ( '' ) ;
} ) ;
} ) ;
}
2017-08-28 19:27:45 +03:00
// Upload a file to the server
function handleUploadFile ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-10-01 00:08:51 +03:00
if ( domain . userQuota == - 1 ) { res . sendStatus ( 401 ) ; return ; }
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
2019-01-05 23:04:18 +03:00
const multiparty = require ( 'multiparty' ) ;
const form = new multiparty . Form ( ) ;
2017-08-28 19:27:45 +03:00
form . parse ( req , function ( err , fields , files ) {
2019-10-01 00:08:51 +03:00
// If an authentication cookie is embedded in the form, use that.
if ( ( fields != null ) && ( fields . auth != null ) && ( fields . auth . length == 1 ) && ( typeof fields . auth [ 0 ] == 'string' ) ) {
var loginCookie = obj . parent . decodeCookie ( fields . auth [ 0 ] , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2022-04-13 00:03:52 +03:00
if ( ( loginCookie != null ) && ( loginCookie . ip != null ) && ! checkCookieIp ( loginCookie . ip , req . clientIp ) ) { loginCookie = null ; } // Check cookie IP binding.
2019-10-01 00:08:51 +03:00
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { authUserid = loginCookie . userid ; } // Use cookie authentication
}
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
// Get the user
const user = obj . users [ authUserid ] ;
if ( ( user == null ) || ( user . siteadmin & 8 ) == 0 ) { res . sendStatus ( 401 ) ; return ; } // Check if we have file rights
if ( ( fields == null ) || ( fields . link == null ) || ( fields . link . length != 1 ) ) { /*console.log('UploadFile, Invalid Fields:', fields, files);*/ console . log ( 'err4' ) ; res . sendStatus ( 404 ) ; return ; }
2020-06-22 00:31:00 +03:00
var xfile = null ;
try { xfile = obj . getServerFilePath ( user , domain , decodeURIComponent ( fields . link [ 0 ] ) ) ; } catch ( ex ) { }
2017-08-28 19:27:45 +03:00
if ( xfile == null ) { res . sendStatus ( 404 ) ; return ; }
// Get total bytes in the path
var totalsize = readTotalFileSize ( xfile . fullpath ) ;
2018-09-01 01:23:42 +03:00
if ( ( xfile . quota == null ) || ( totalsize < xfile . quota ) ) { // Check if the quota is not already broken
2019-02-20 02:38:27 +03:00
if ( fields . name != null ) {
2019-02-19 09:20:25 +03:00
2019-02-20 02:38:27 +03:00
// See if we need to create the folder
var domainx = 'domain' ;
if ( domain . id . length > 0 ) { domainx = 'domain-' + usersplit [ 1 ] ; }
2020-07-08 09:56:08 +03:00
try { obj . fs . mkdirSync ( obj . parent . filespath ) ; } catch ( ex ) { }
try { obj . fs . mkdirSync ( obj . parent . path . join ( obj . parent . filespath , domainx ) ) ; } catch ( ex ) { }
try { obj . fs . mkdirSync ( xfile . fullpath ) ; } catch ( ex ) { }
2019-02-19 09:20:25 +03:00
2017-08-28 19:27:45 +03:00
// Upload method where all the file data is within the fields.
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 ; }
2019-01-03 05:03:34 +03:00
var filedata = Buffer . from ( datas [ i ] . split ( ',' ) [ 1 ] , 'base64' ) ;
2018-09-01 01:23:42 +03:00
if ( ( xfile . quota == null ) || ( ( totalsize + filedata . length ) < xfile . quota ) ) { // Check if quota would not be broken if we add this file
2017-12-01 01:40:46 +03:00
// Create the user folder if needed
( function ( fullpath , filename , filedata ) {
obj . fs . mkdir ( xfile . fullpath , function ( ) {
// Write the file
obj . fs . writeFile ( obj . path . join ( xfile . fullpath , filename ) , filedata , function ( ) {
2018-08-30 22:05:23 +03:00
obj . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; // Fire an event causing this user to update this files
2017-12-01 01:40:46 +03:00
} ) ;
} ) ;
} ) ( xfile . fullpath , names [ i ] , filedata ) ;
2020-07-16 00:11:27 +03:00
} else {
// Send a notification
obj . parent . DispatchEvent ( [ user . _id ] , obj , { action : 'notify' , title : "Disk quota exceed" , value : names [ i ] , nolog : 1 , id : Math . random ( ) } ) ;
2017-08-28 19:27:45 +03:00
}
}
}
} else {
// More typical upload method, the file data is in a multipart mime post.
for ( var i in files . files ) {
2017-12-01 01:40:46 +03:00
var file = files . files [ i ] , fpath = obj . path . join ( xfile . fullpath , file . originalFilename ) ;
2018-09-01 01:23:42 +03:00
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
2019-02-20 02:38:27 +03:00
// See if we need to create the folder
var domainx = 'domain' ;
2019-08-23 02:39:50 +03:00
if ( domain . id . length > 0 ) { domainx = 'domain-' + domain . id ; }
2019-02-20 02:38:27 +03:00
try { obj . fs . mkdirSync ( obj . parent . filespath ) ; } catch ( e ) { }
try { obj . fs . mkdirSync ( obj . parent . path . join ( obj . parent . filespath , domainx ) ) ; } catch ( e ) { }
try { obj . fs . mkdirSync ( xfile . fullpath ) ; } catch ( e ) { }
2020-02-01 01:44:11 +03:00
// Rename the file
obj . fs . rename ( file . path , fpath , function ( err ) {
2020-02-02 23:37:27 +03:00
if ( err && ( err . code === 'EXDEV' ) ) {
2020-02-01 01:44:11 +03:00
// 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 . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; // Fire an event causing this user to update this files
} ) ;
} ) ;
} else {
obj . parent . DispatchEvent ( [ user . _id ] , obj , 'updatefiles' ) ; // Fire an event causing this user to update this files
}
2017-12-01 01:40:46 +03:00
} ) ;
2017-08-28 19:27:45 +03:00
} else {
2020-07-16 00:11:27 +03:00
// Send a notification
obj . parent . DispatchEvent ( [ user . _id ] , obj , { action : 'notify' , title : "Disk quota exceed" , value : file . originalFilename , nolog : 1 , id : Math . random ( ) } ) ;
2019-01-07 07:32:12 +03:00
try { obj . fs . unlink ( file . path , function ( err ) { } ) ; } catch ( e ) { }
2017-08-28 19:27:45 +03:00
}
}
}
2020-07-16 00:11:27 +03:00
} else {
// Send a notification
obj . parent . DispatchEvent ( [ user . _id ] , obj , { action : 'notify' , value : "Disk quota exceed" , nolog : 1 , id : Math . random ( ) } ) ;
2017-08-28 19:27:45 +03:00
}
res . send ( '' ) ;
} ) ;
}
2018-05-24 07:43:44 +03:00
2020-11-08 12:13:23 +03:00
// Upload a file to the server and then batch upload to many agents
function handleUploadFileBatch ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
if ( domain == null ) { return ; }
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
const multiparty = require ( 'multiparty' ) ;
const form = new multiparty . Form ( ) ;
form . parse ( req , function ( err , fields , files ) {
// If an authentication cookie is embedded in the form, use that.
if ( ( fields != null ) && ( fields . auth != null ) && ( fields . auth . length == 1 ) && ( typeof fields . auth [ 0 ] == 'string' ) ) {
var loginCookie = obj . parent . decodeCookie ( fields . auth [ 0 ] , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2022-04-13 00:03:52 +03:00
if ( ( loginCookie != null ) && ( loginCookie . ip != null ) && ! checkCookieIp ( loginCookie . ip , req . clientIp ) ) { loginCookie = null ; } // Check cookie IP binding.
2020-11-08 12:13:23 +03:00
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { authUserid = loginCookie . userid ; } // Use cookie authentication
}
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
// Get the user
const user = obj . users [ authUserid ] ;
2020-12-22 10:55:26 +03:00
if ( user == null ) { parent . debug ( 'web' , 'Batch upload error, invalid user.' ) ; res . sendStatus ( 401 ) ; return ; } // Check if user exists
2020-11-08 12:13:23 +03:00
// Get fields
if ( ( fields == null ) || ( fields . nodeIds == null ) || ( fields . nodeIds . length != 1 ) ) { res . sendStatus ( 404 ) ; return ; }
2021-06-15 00:19:02 +03:00
var cmd = { nodeids : fields . nodeIds [ 0 ] . split ( ',' ) , files : [ ] , user : user , domain : domain , overwrite : false , createFolder : false } ;
2020-11-08 12:13:23 +03:00
if ( ( fields . winpath != null ) && ( fields . winpath . length == 1 ) ) { cmd . windowsPath = fields . winpath [ 0 ] ; }
if ( ( fields . linuxpath != null ) && ( fields . linuxpath . length == 1 ) ) { cmd . linuxPath = fields . linuxpath [ 0 ] ; }
if ( ( fields . overwriteFiles != null ) && ( fields . overwriteFiles . length == 1 ) && ( fields . overwriteFiles [ 0 ] == 'on' ) ) { cmd . overwrite = true ; }
2020-11-10 02:28:46 +03:00
if ( ( fields . createFolder != null ) && ( fields . createFolder . length == 1 ) && ( fields . createFolder [ 0 ] == 'on' ) ) { cmd . createFolder = true ; }
2020-11-08 12:13:23 +03:00
2020-12-22 10:55:26 +03:00
// Check if we have at least one target path
2020-11-18 06:30:26 +03:00
if ( ( cmd . windowsPath == null ) && ( cmd . linuxPath == null ) ) {
parent . debug ( 'web' , 'Batch upload error, invalid fields: ' + JSON . stringify ( fields ) ) ;
res . send ( '' ) ;
return ;
}
2020-11-08 12:13:23 +03:00
// Get server temporary path
var serverpath = obj . path . join ( obj . filespath , 'tmp' )
try { obj . fs . mkdirSync ( obj . parent . filespath ) ; } catch ( ex ) { }
try { obj . fs . mkdirSync ( serverpath ) ; } catch ( ex ) { }
// 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 ) ;
cmd . files . push ( { name : file . originalFilename , target : ftarget } ) ;
// Rename the file
obj . fs . rename ( file . path , fpath , function ( err ) {
if ( err && ( err . code === 'EXDEV' ) ) {
// On some Linux, the rename will fail with a "EXDEV" error, do a copy+unlink instead.
2020-11-10 03:51:48 +03:00
obj . common . copyFile ( file . path , fpath , function ( err ) { obj . fs . unlink ( file . path , function ( err ) { } ) ; } ) ;
}
} ) ;
}
// Instruct one of more agents to download a URL to a given local drive location.
2020-11-10 23:51:34 +03:00
var tlsCertHash = null ;
2021-01-29 00:41:19 +03:00
if ( ( parent . args . ignoreagenthashcheck == null ) || ( parent . args . ignoreagenthashcheck === false ) ) { // TODO: If ignoreagenthashcheck is an array of IP addresses, not sure how to handle this.
2020-11-12 03:50:51 +03:00
tlsCertHash = obj . webCertificateFullHashs [ cmd . domain . id ] ;
2020-11-10 23:51:34 +03:00
if ( tlsCertHash != null ) { tlsCertHash = Buffer . from ( tlsCertHash , 'binary' ) . toString ( 'hex' ) ; }
}
2020-11-10 03:51:48 +03:00
for ( var i in cmd . nodeids ) {
obj . GetNodeWithRights ( cmd . domain , cmd . user , cmd . nodeids [ i ] , function ( node , rights , visible ) {
if ( ( node == null ) || ( ( rights & 8 ) == 0 ) || ( visible == false ) ) return ; // We don't have remote control rights to this device
2021-06-15 23:10:07 +03:00
var agentPath = ( ( ( node . agent . id > 0 ) && ( node . agent . id < 5 ) ) || ( node . agent . id == 34 ) ) ? cmd . windowsPath : cmd . linuxPath ;
2020-11-18 06:30:26 +03:00
if ( agentPath == null ) return ;
2020-11-10 03:51:48 +03:00
2021-06-15 23:10:07 +03:00
// Compute user consent
var consent = 0 ;
var mesh = obj . meshes [ node . meshid ] ;
if ( typeof domain . userconsentflags == 'number' ) { consent |= domain . userconsentflags ; } // Add server required consent flags
if ( ( mesh != null ) && ( typeof mesh . consent == 'number' ) ) { consent |= mesh . consent ; } // Add device group user consent
if ( typeof node . consent == 'number' ) { consent |= node . consent ; } // Add node user consent
if ( typeof user . consent == 'number' ) { consent |= user . consent ; } // Add user consent
// Check if we need to add consent flags because of a user group link
if ( ( mesh != null ) && ( user . links != null ) && ( user . links [ mesh . _id ] == null ) && ( user . links [ node . _id ] == null ) ) {
// This user does not have a direct link to the device group or device. Find all user groups the would cause the link.
for ( var i in user . links ) {
2021-11-19 07:05:39 +03:00
var ugrp = obj . userGroups [ i ] ;
2021-06-15 23:10:07 +03:00
if ( ( ugrp != null ) && ( ugrp . consent != null ) && ( ugrp . links != null ) && ( ( ugrp . links [ mesh . _id ] != null ) || ( ugrp . links [ node . _id ] != null ) ) ) {
consent |= ugrp . consent ; // Add user group consent flags
}
}
}
2020-11-10 03:51:48 +03:00
// Event that this operation is being performed.
var targets = obj . CreateNodeDispatchTargets ( node . meshid , node . _id , [ 'server-users' , cmd . user . _id ] ) ;
var msgid = 103 ; // "Batch upload of {0} file(s) to folder {1}"
var event = { etype : 'node' , userid : cmd . user . _id , username : cmd . user . name , nodeid : node . _id , action : 'batchupload' , msg : 'Performing batch upload of ' + cmd . files . length + ' file(s) to ' + agentPath , msgid : msgid , msgArgs : [ cmd . files . length , agentPath ] , domain : cmd . domain . id } ;
parent . DispatchEvent ( targets , obj , event ) ;
// Send the agent commands to perform the batch upload operation
for ( var f in cmd . files ) {
2020-11-18 06:30:26 +03:00
if ( cmd . files [ f ] . name != null ) {
2021-06-15 23:10:07 +03:00
const acmd = { action : 'wget' , userid : user . _id , username : user . name , realname : user . realname , remoteaddr : req . clientIp , consent : consent , rights : rights , overwrite : cmd . overwrite , createFolder : cmd . createFolder , urlpath : '/agentdownload.ashx?c=' + obj . parent . encodeCookie ( { a : 'tmpdl' , d : cmd . domain . id , nid : node . _id , f : cmd . files [ f ] . target } , obj . parent . loginCookieEncryptionKey ) , path : obj . path . join ( agentPath , cmd . files [ f ] . name ) , folder : agentPath , servertlshash : tlsCertHash } ;
2020-11-18 06:30:26 +03:00
var agent = obj . wsagents [ node . _id ] ;
if ( agent != null ) { try { agent . send ( JSON . stringify ( acmd ) ) ; } catch ( ex ) { } }
// TODO: Add support for peer servers.
2020-11-18 00:44:17 +03:00
}
2020-11-08 12:13:23 +03:00
}
} ) ;
}
res . send ( '' ) ;
} ) ;
}
2017-08-28 19:27:45 +03:00
// Subscribe to all events we are allowed to receive
obj . subscribe = function ( userid , target ) {
2019-01-05 23:04:18 +03:00
const user = obj . users [ userid ] ;
2022-12-10 23:02:33 +03:00
if ( user == null ) return ;
2022-02-01 20:11:11 +03:00
const subscriptions = [ userid , 'server-allusers' ] ;
2017-09-15 21:45:06 +03:00
if ( user . siteadmin != null ) {
2020-08-18 22:46:20 +03:00
// Allow full site administrators of users with all events rights to see all events.
if ( ( user . siteadmin == 0xFFFFFFFF ) || ( ( user . siteadmin & 2048 ) != 0 ) ) { subscriptions . push ( '*' ) ; }
else if ( ( user . siteadmin & 2 ) != 0 ) {
2019-04-17 03:32:18 +03:00
if ( ( user . groups == null ) || ( user . groups . length == 0 ) ) {
// Subscribe to all user changes
subscriptions . push ( 'server-users' ) ;
} else {
// Subscribe to user changes for some groups
for ( var i in user . groups ) { subscriptions . push ( 'server-users:' + i ) ; }
}
}
2017-08-28 19:27:45 +03:00
}
2018-07-14 05:18:43 +03:00
if ( user . links != null ) { for ( var i in user . links ) { subscriptions . push ( i ) ; } }
2017-08-28 19:27:45 +03:00
obj . parent . RemoveAllEventDispatch ( target ) ;
obj . parent . AddEventDispatch ( subscriptions , target ) ;
return subscriptions ;
2018-08-30 22:05:23 +03:00
} ;
2017-08-28 19:27:45 +03:00
// Handle a web socket relay request
2018-10-16 03:21:37 +03:00
function handleRelayWebSocket ( ws , req , domain , user , cookie ) {
if ( ! ( req . query . host ) ) { console . log ( 'ERR: No host target specified' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2019-08-23 01:31:39 +03:00
parent . debug ( 'web' , 'Websocket relay connected from ' + user . name + ' for ' + req . query . host + '.' ) ;
2019-10-04 22:18:56 +03:00
2018-10-14 02:04:04 +03:00
try { ws . _socket . setKeepAlive ( true , 240000 ) ; } catch ( ex ) { } // Set TCP keep alive
2017-09-15 21:45:06 +03:00
2017-08-28 19:27:45 +03:00
// Fetch information about the target
obj . db . Get ( req . query . host , function ( err , docs ) {
2018-10-16 03:21:37 +03:00
if ( docs . length == 0 ) { console . log ( 'ERR: Node not found' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2017-10-03 00:12:29 +03:00
var node = docs [ 0 ] ;
2018-10-16 03:21:37 +03:00
if ( ! node . intelamt ) { console . log ( 'ERR: Not AMT node' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; } // Disconnect websocket
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Check if this user has permission to manage this computer
2020-04-02 02:41:35 +03:00
if ( ( obj . GetNodeRights ( user , node . meshid , node . _id ) & MESHRIGHT _REMOTECONTROL ) == 0 ) { console . log ( 'ERR: Access denied (3)' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; }
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Check what connectivity is available for this node
2017-09-13 21:25:57 +03:00
var state = parent . GetConnectivityState ( req . query . host ) ;
2017-08-28 19:27:45 +03:00
var conn = 0 ;
2019-08-23 01:31:39 +03:00
if ( ! state || state . connectivity == 0 ) { parent . debug ( 'web' , 'ERR: No routing possible (1)' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; } else { conn = state . connectivity ; }
2017-09-15 21:45:06 +03:00
// Check what server needs to handle this connection
2019-08-15 21:47:40 +03:00
if ( ( obj . parent . multiServer != null ) && ( ( cookie == null ) || ( cookie . ps != 1 ) ) ) { // If a cookie is provided and is from a peer server, don't allow the connection to jump again to a different server
2017-09-15 21:45:06 +03:00
var server = obj . parent . GetRoutingServerId ( req . query . host , 2 ) ; // Check for Intel CIRA connection
if ( server != null ) {
if ( server . serverid != obj . parent . serverId ) {
// Do local Intel CIRA routing using a different server
2019-08-23 01:31:39 +03:00
parent . debug ( 'web' , 'Route Intel AMT CIRA connection to peer server: ' + server . serverid ) ;
2017-09-15 21:45:06 +03:00
obj . parent . multiServer . createPeerRelay ( ws , req , server . serverid , user ) ;
return ;
}
} else {
server = obj . parent . GetRoutingServerId ( req . query . host , 4 ) ; // Check for local Intel AMT connection
if ( ( server != null ) && ( server . serverid != obj . parent . serverId ) ) {
// Do local Intel AMT routing using a different server
2019-08-23 01:31:39 +03:00
parent . debug ( 'web' , 'Route Intel AMT direct connection to peer server: ' + server . serverid ) ;
2017-09-15 21:45:06 +03:00
obj . parent . multiServer . createPeerRelay ( ws , req , server . serverid , user ) ;
return ;
}
}
2017-08-28 19:27:45 +03:00
}
2017-09-15 21:45:06 +03:00
2019-08-08 00:25:23 +03:00
// Setup session recording if needed
2019-08-08 02:07:12 +03:00
if ( domain . sessionrecording == true || ( ( typeof domain . sessionrecording == 'object' ) && ( ( domain . sessionrecording . protocols == null ) || ( domain . sessionrecording . protocols . indexOf ( ( req . query . p == 2 ) ? 101 : 100 ) >= 0 ) ) ) ) { // TODO 100
2020-10-01 02:59:34 +03:00
// Check again if we need to do recording
var record = true ;
2021-08-26 22:42:09 +03:00
// Check user or device group recording
2021-08-27 08:04:17 +03:00
if ( ( typeof domain . sessionrecording == 'object' ) && ( ( domain . sessionrecording . onlyselectedusers === true ) || ( domain . sessionrecording . onlyselecteddevicegroups === true ) ) ) {
2021-08-26 22:42:09 +03:00
record = false ;
// Check device group recording
if ( domain . sessionrecording . onlyselecteddevicegroups === true ) {
var mesh = obj . meshes [ node . meshid ] ;
if ( ( mesh . flags != null ) && ( ( mesh . flags & 4 ) != 0 ) ) { record = true ; } // Record the session
}
// Check user recording
if ( domain . sessionrecording . onlyselectedusers === true ) {
if ( ( user . flags != null ) && ( ( user . flags & 2 ) != 0 ) ) { record = true ; } // Record the session
}
2019-08-08 00:25:23 +03:00
}
2020-10-01 02:59:34 +03:00
if ( record == true ) {
var now = new Date ( Date . now ( ) ) ;
2021-10-20 20:33:21 +03:00
var recFilename = 'relaysession' + ( ( domain . id == '' ) ? '' : '-' ) + domain . id + '-' + now . getUTCFullYear ( ) + '-' + obj . common . zeroPad ( now . getUTCMonth ( ) + 1 , 2 ) + '-' + obj . common . zeroPad ( now . getUTCDate ( ) , 2 ) + '-' + obj . common . zeroPad ( now . getUTCHours ( ) , 2 ) + '-' + obj . common . zeroPad ( now . getUTCMinutes ( ) , 2 ) + '-' + obj . common . zeroPad ( now . getUTCSeconds ( ) , 2 ) + '-' + getRandomPassword ( ) + '.mcrec'
2020-10-01 02:59:34 +03:00
var recFullFilename = null ;
if ( domain . sessionrecording . filepath ) {
try { obj . fs . mkdirSync ( domain . sessionrecording . filepath ) ; } catch ( e ) { }
recFullFilename = obj . path . join ( domain . sessionrecording . filepath , recFilename ) ;
} else {
try { obj . fs . mkdirSync ( parent . recordpath ) ; } catch ( e ) { }
recFullFilename = obj . path . join ( parent . recordpath , recFilename ) ;
}
var fd = obj . fs . openSync ( recFullFilename , 'w' ) ;
if ( fd != null ) {
// Write the recording file header
var firstBlock = JSON . stringify ( { magic : 'MeshCentralRelaySession' , ver : 1 , userid : user . _id , username : user . name , ipaddr : req . clientIp , nodeid : node . _id , intelamt : true , protocol : ( req . query . p == 2 ) ? 101 : 100 , time : new Date ( ) . toLocaleString ( ) } )
recordingEntry ( fd , 1 , 0 , firstBlock , function ( ) { } ) ;
ws . logfile = { fd : fd , lock : false } ;
if ( req . query . p == 2 ) { ws . send ( Buffer . from ( String . fromCharCode ( 0xF0 ) , 'binary' ) ) ; } // Intel AMT Redirection: Indicate the session is being recorded
}
2019-08-08 00:25:23 +03:00
}
}
2017-08-28 19:27:45 +03:00
// If Intel AMT CIRA connection is available, use it
2020-10-10 01:44:09 +03:00
var ciraconn = parent . mpsserver . GetConnectionToNode ( req . query . host , null , false ) ;
if ( ciraconn != null ) {
2019-08-23 01:31:39 +03:00
parent . debug ( 'web' , 'Opening relay CIRA channel connection to ' + req . query . host + '.' ) ;
2018-10-16 20:52:05 +03:00
2020-10-14 23:13:05 +03:00
// TODO: If the CIRA connection is a relay or LMS connection, we can't detect the TLS state like this.
2017-08-28 19:27:45 +03:00
// Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
var port = 16993 ;
//if (node.intelamt.tls == 0) port = 16992; // DEBUG: Allow TLS flag to set TLS mode within CIRA
if ( ciraconn . tag . boundPorts . indexOf ( 16992 ) >= 0 ) port = 16992 ; // RELEASE: Always use non-TLS mode if available within CIRA
if ( req . query . p == 2 ) port += 2 ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Setup a new CIRA channel
if ( ( port == 16993 ) || ( port == 16995 ) ) {
2020-10-14 23:39:24 +03:00
// Perform TLS
2017-08-28 19:27:45 +03:00
var ser = new SerialTunnel ( ) ;
2020-10-08 22:50:27 +03:00
var chnl = parent . mpsserver . SetupChannel ( ciraconn , port ) ;
2017-08-28 19:27:45 +03:00
2019-08-08 02:07:12 +03:00
// Let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
2017-08-28 19:27:45 +03:00
// Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
2020-10-14 23:13:05 +03:00
ser . forwardwrite = function ( data ) { if ( data . length > 0 ) { chnl . write ( data ) ; } } ; // TLS ---> CIRA
2017-08-28 19:27:45 +03:00
// When APF tunnel return something, update SerialTunnel buffer
2020-10-14 23:13:05 +03:00
chnl . onData = function ( ciraconn , data ) { if ( data . length > 0 ) { try { ser . updateBuffer ( data ) ; } catch ( ex ) { console . log ( ex ) ; } } } ; // CIRA ---> TLS
2017-08-28 19:27:45 +03:00
2018-10-14 02:04:04 +03:00
// Handle CIRA tunnel state change
2017-08-28 19:27:45 +03:00
chnl . onStateChange = function ( ciraconn , state ) {
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelay' , 'Relay TLS CIRA state change' , state ) ;
2017-08-28 19:27:45 +03:00
if ( state == 0 ) { try { ws . close ( ) ; } catch ( e ) { } }
2020-10-14 23:13:05 +03:00
if ( state == 2 ) {
// TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel an then wrapped through CIRA APF
const tlsoptions = { socket : ser , ciphers : 'RSA+AES:!aNULL:!MD5:!DSS' , secureOptions : constants . SSL _OP _NO _SSLv2 | constants . SSL _OP _NO _SSLv3 | constants . SSL _OP _NO _COMPRESSION | constants . SSL _OP _CIPHER _SERVER _PREFERENCE , rejectUnauthorized : false } ;
if ( req . query . tls1only == 1 ) { tlsoptions . secureProtocol = 'TLSv1_method' ; }
var tlsock = obj . tls . connect ( tlsoptions , function ( ) { parent . debug ( 'webrelay' , "CIRA Secure TLS Connection" ) ; ws . _socket . resume ( ) ; } ) ;
tlsock . chnl = chnl ;
tlsock . setEncoding ( 'binary' ) ;
tlsock . on ( 'error' , function ( err ) { parent . debug ( 'webrelay' , "CIRA TLS Connection Error" , err ) ; } ) ;
2022-09-07 17:40:36 +03:00
// Decrypted tunnel from TLS communication to be forwarded to websocket
2020-10-14 23:13:05 +03:00
tlsock . on ( 'data' , function ( data ) {
// AMT/TLS ---> WS
2020-10-14 23:39:24 +03:00
if ( ws . interceptor ) { data = ws . interceptor . processAmtData ( data ) ; } // Run data thru interceptor
2020-10-14 23:13:05 +03:00
try { ws . send ( data ) ; } catch ( ex ) { }
} ) ;
2017-08-28 19:27:45 +03:00
2020-10-14 23:13:05 +03:00
// If TLS is on, forward it through TLSSocket
ws . forwardclient = tlsock ;
ws . forwardclient . xtls = 1 ;
2017-08-28 19:27:45 +03:00
2020-10-14 23:13:05 +03:00
ws . forwardclient . onStateChange = function ( ciraconn , state ) {
parent . debug ( 'webrelay' , 'Relay CIRA state change' , state ) ;
if ( state == 0 ) { try { ws . close ( ) ; } catch ( e ) { } }
} ;
ws . forwardclient . onData = function ( ciraconn , data ) {
// Run data thru interceptor
2020-10-14 23:39:24 +03:00
if ( ws . interceptor ) { data = ws . interceptor . processAmtData ( data ) ; }
2020-10-14 23:13:05 +03:00
if ( data . length > 0 ) {
if ( ws . logfile == null ) {
2020-10-14 23:39:24 +03:00
try { ws . send ( data ) ; } catch ( e ) { }
2020-10-14 23:13:05 +03:00
} else {
// Log to recording file
recordingEntry ( ws . logfile . fd , 2 , 0 , data , function ( ) { try { ws . send ( data ) ; } catch ( ex ) { console . log ( ex ) ; } } ) ; // TODO: Add TLS support
}
}
} ;
2020-10-14 23:39:24 +03:00
// TODO: Flow control? (Dont' really need it with AMT, but would be nice)
ws . forwardclient . onSendOk = function ( ciraconn ) { } ;
2020-10-14 23:13:05 +03:00
}
} ;
2017-08-28 19:27:45 +03:00
} else {
// Without TLS
2020-10-08 22:50:27 +03:00
ws . forwardclient = parent . mpsserver . SetupChannel ( ciraconn , port ) ;
2017-08-28 19:27:45 +03:00
ws . forwardclient . xtls = 0 ;
2018-12-01 08:23:10 +03:00
ws . _socket . resume ( ) ;
2020-10-14 23:13:05 +03:00
ws . forwardclient . onStateChange = function ( ciraconn , state ) {
parent . debug ( 'webrelay' , 'Relay CIRA state change' , state ) ;
if ( state == 0 ) { try { ws . close ( ) ; } catch ( e ) { } }
} ;
ws . forwardclient . onData = function ( ciraconn , data ) {
//parent.debug('webrelaydata', 'Relay CIRA data to WS', data.length);
2022-09-07 17:40:36 +03:00
// Run data thru interceptor
2020-10-14 23:39:24 +03:00
if ( ws . interceptor ) { data = ws . interceptor . processAmtData ( data ) ; }
2020-10-14 23:13:05 +03:00
//console.log('AMT --> WS', Buffer.from(data, 'binary').toString('hex'));
if ( data . length > 0 ) {
if ( ws . logfile == null ) {
2020-10-14 23:39:24 +03:00
try { ws . send ( data ) ; } catch ( e ) { }
2020-10-14 23:13:05 +03:00
} else {
// Log to recording file
2020-10-14 23:39:24 +03:00
recordingEntry ( ws . logfile . fd , 2 , 0 , data , function ( ) { try { ws . send ( data ) ; } catch ( ex ) { console . log ( ex ) ; } } ) ;
2020-10-14 23:13:05 +03:00
}
}
} ;
2020-10-14 23:39:24 +03:00
// TODO: Flow control? (Dont' really need it with AMT, but would be nice)
ws . forwardclient . onSendOk = function ( ciraconn ) { } ;
2017-08-28 19:27:45 +03:00
}
2018-05-24 07:43:44 +03:00
2022-09-07 17:40:36 +03:00
// When data is received from the web socket, forward the data into the associated CIRA channel.
2017-08-28 19:27:45 +03:00
// If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
2020-10-14 23:13:05 +03:00
ws . on ( 'message' , function ( data ) {
//parent.debug('webrelaydata', 'Relay WS data to CIRA', data.length);
if ( typeof data == 'string' ) { data = Buffer . from ( data , 'binary' ) ; }
2017-08-28 19:27:45 +03:00
// WS ---> AMT/TLS
2020-10-14 23:39:24 +03:00
if ( ws . interceptor ) { data = ws . interceptor . processBrowserData ( data ) ; } // Run data thru interceptor
2019-08-08 02:07:12 +03:00
// Log to recording file
if ( ws . logfile == null ) {
// Forward data to the associated TCP connection.
2021-01-26 11:09:28 +03:00
try { ws . forwardclient . write ( data ) ; } catch ( ex ) { }
2019-08-08 02:07:12 +03:00
} else {
// Log to recording file
2020-10-14 23:13:05 +03:00
recordingEntry ( ws . logfile . fd , 2 , 2 , data , function ( ) { try { ws . forwardclient . write ( data ) ; } catch ( ex ) { } } ) ;
2019-08-08 02:07:12 +03:00
}
2017-08-28 19:27:45 +03:00
} ) ;
2019-01-03 05:34:50 +03:00
// If error, close the associated TCP connection.
ws . on ( 'error' , function ( err ) {
2020-05-22 07:25:11 +03:00
console . log ( 'CIRA server websocket error from ' + req . clientIp + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ;
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelay' , 'Websocket relay closed on error.' ) ;
2020-10-14 23:39:24 +03:00
// Websocket closed, close the CIRA channel and TLS session.
if ( ws . forwardclient ) {
if ( ws . forwardclient . close ) { ws . forwardclient . close ( ) ; } // NonTLS, close the CIRA channel
if ( ws . forwardclient . end ) { ws . forwardclient . end ( ) ; } // TLS, close the TLS session
if ( ws . forwardclient . chnl ) { ws . forwardclient . chnl . close ( ) ; } // TLS, close the CIRA channel
delete ws . forwardclient ;
}
2019-08-08 02:07:12 +03:00
// Close the recording file
2020-10-14 23:39:24 +03:00
if ( ws . logfile != null ) { recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd , ws ) { obj . fs . close ( fd ) ; delete ws . logfile ; } , ws ) ; }
2019-01-03 05:34:50 +03:00
} ) ;
2017-08-28 19:27:45 +03:00
// If the web socket is closed, close the associated TCP connection.
ws . on ( 'close' , function ( req ) {
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelay' , 'Websocket relay closed.' ) ;
2019-08-08 02:07:12 +03:00
2020-10-14 23:13:05 +03:00
// Websocket closed, close the CIRA channel and TLS session.
if ( ws . forwardclient ) {
if ( ws . forwardclient . close ) { ws . forwardclient . close ( ) ; } // NonTLS, close the CIRA channel
if ( ws . forwardclient . end ) { ws . forwardclient . end ( ) ; } // TLS, close the TLS session
if ( ws . forwardclient . chnl ) { ws . forwardclient . chnl . close ( ) ; } // TLS, close the CIRA channel
2020-10-14 23:39:24 +03:00
delete ws . forwardclient ;
2019-08-13 21:49:05 +03:00
}
2018-05-24 07:43:44 +03:00
2020-10-14 23:13:05 +03:00
// Close the recording file
2020-10-14 23:39:24 +03:00
if ( ws . logfile != null ) { recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd , ws ) { obj . fs . close ( fd ) ; delete ws . logfile ; } , ws ) ; }
2020-10-14 23:13:05 +03:00
} ) ;
2018-05-24 07:43:44 +03:00
2020-10-18 05:23:24 +03:00
// Note that here, req.query.p: 1 = WSMAN with server auth, 2 = REDIR with server auth, 3 = WSMAN without server auth, 4 = REDIR with server auth
2017-08-28 19:27:45 +03:00
// Fetch Intel AMT credentials & Setup interceptor
if ( req . query . p == 1 ) {
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelaydata' , 'INTERCEPTOR1' , { host : node . host , port : port , user : node . intelamt . user , pass : node . intelamt . pass } ) ;
2017-08-28 19:27:45 +03:00
ws . interceptor = obj . interceptor . CreateHttpInterceptor ( { host : node . host , port : port , user : node . intelamt . user , pass : node . intelamt . pass } ) ;
2018-02-05 22:56:29 +03:00
ws . interceptor . blockAmtStorage = true ;
2020-10-14 23:13:05 +03:00
} else if ( req . query . p == 2 ) {
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelaydata' , 'INTERCEPTOR2' , { user : node . intelamt . user , pass : node . intelamt . pass } ) ;
2017-08-28 19:27:45 +03:00
ws . interceptor = obj . interceptor . CreateRedirInterceptor ( { user : node . intelamt . user , pass : node . intelamt . pass } ) ;
2018-02-05 22:56:29 +03:00
ws . interceptor . blockAmtStorage = true ;
2017-08-28 19:27:45 +03:00
}
return ;
}
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// If Intel AMT direct connection is possible, option a direct socket
if ( ( conn & 4 ) != 0 ) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelay' , 'Opening relay TCP socket connection to ' + req . query . host + '.' ) ;
2017-09-15 21:45:06 +03:00
// When data is received from the web socket, forward the data into the associated TCP connection.
ws . on ( 'message' , function ( msg ) {
2020-10-14 23:39:24 +03:00
//parent.debug('webrelaydata', 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes');
if ( typeof msg == 'string' ) { msg = Buffer . from ( msg , 'binary' ) ; }
2017-10-15 09:22:19 +03:00
if ( ws . interceptor ) { msg = ws . interceptor . processBrowserData ( msg ) ; } // Run data thru interceptor
2019-08-08 02:07:12 +03:00
// Log to recording file
if ( ws . logfile == null ) {
// Forward data to the associated TCP connection.
2020-10-14 23:39:24 +03:00
try { ws . forwardclient . write ( msg ) ; } catch ( ex ) { }
2019-08-08 02:07:12 +03:00
} else {
// Log to recording file
recordingEntry ( ws . logfile . fd , 2 , 2 , msg , function ( ) { try { ws . forwardclient . write ( msg ) ; } catch ( ex ) { } } ) ;
}
2017-09-15 21:45:06 +03:00
} ) ;
2019-01-03 05:34:50 +03:00
// If error, close the associated TCP connection.
ws . on ( 'error' , function ( err ) {
2020-05-22 07:25:11 +03:00
console . log ( 'Error with relay web socket connection from ' + req . clientIp + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ;
parent . debug ( 'webrelay' , 'Error with relay web socket connection from ' + req . clientIp + '.' ) ;
2019-01-03 05:34:50 +03:00
if ( ws . forwardclient ) { try { ws . forwardclient . destroy ( ) ; } catch ( e ) { } }
2019-08-08 02:07:12 +03:00
// Close the recording file
2019-08-13 21:49:05 +03:00
if ( ws . logfile != null ) {
recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd ) {
obj . fs . close ( fd ) ;
ws . logfile = null ;
} ) ;
}
2019-01-03 05:34:50 +03:00
} ) ;
2017-09-15 21:45:06 +03:00
// If the web socket is closed, close the associated TCP connection.
2018-12-08 03:36:27 +03:00
ws . on ( 'close' , function ( ) {
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelay' , 'Closing relay web socket connection to ' + req . query . host + '.' ) ;
2017-09-15 21:45:06 +03:00
if ( ws . forwardclient ) { try { ws . forwardclient . destroy ( ) ; } catch ( e ) { } }
2019-08-08 02:07:12 +03:00
// Close the recording file
2019-08-13 21:49:05 +03:00
if ( ws . logfile != null ) {
recordingEntry ( ws . logfile . fd , 3 , 0 , 'MeshCentralMCREC' , function ( fd ) {
obj . fs . close ( fd ) ;
ws . logfile = null ;
} ) ;
}
2017-09-15 21:45:06 +03:00
} ) ;
2017-08-28 19:27:45 +03:00
// Compute target port
var port = 16992 ;
if ( node . intelamt . tls > 0 ) port = 16993 ; // This is a direct connection, use TLS when possible
2020-10-18 05:23:24 +03:00
if ( ( req . query . p == 2 ) || ( req . query . p == 4 ) ) port += 2 ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
if ( node . intelamt . tls == 0 ) {
// If this is TCP (without TLS) set a normal TCP socket
ws . forwardclient = new obj . net . Socket ( ) ;
ws . forwardclient . setEncoding ( 'binary' ) ;
ws . forwardclient . xstate = 0 ;
ws . forwardclient . forwardwsocket = ws ;
2018-12-01 08:23:10 +03:00
ws . _socket . resume ( ) ;
2017-08-28 19:27:45 +03:00
} else {
// If TLS is going to be used, setup a TLS socket
2022-11-16 00:12:12 +03:00
var tlsoptions = { ciphers : 'RSA+AES:!aNULL:!MD5:!DSS' , secureOptions : constants . SSL _OP _NO _SSLv2 | constants . SSL _OP _NO _SSLv3 | constants . SSL _OP _NO _COMPRESSION | constants . SSL _OP _CIPHER _SERVER _PREFERENCE | constants . SSL _OP _ALLOW _UNSAFE _LEGACY _RENEGOTIATION , rejectUnauthorized : false } ;
2020-03-31 05:29:46 +03:00
if ( req . query . tls1only == 1 ) { tlsoptions . secureProtocol = 'TLSv1_method' ; }
2017-09-30 00:26:51 +03:00
ws . forwardclient = obj . tls . connect ( port , node . host , tlsoptions , function ( ) {
2017-08-28 19:27:45 +03:00
// The TLS connection method is the same as TCP, but located a bit differently.
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelay' , 'TLS connected to ' + node . host + ':' + port + '.' ) ;
2017-08-28 19:27:45 +03:00
ws . forwardclient . xstate = 1 ;
2018-12-01 08:23:10 +03:00
ws . _socket . resume ( ) ;
2017-08-28 19:27:45 +03:00
} ) ;
ws . forwardclient . setEncoding ( 'binary' ) ;
ws . forwardclient . xstate = 0 ;
ws . forwardclient . forwardwsocket = ws ;
}
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// When we receive data on the TCP connection, forward it back into the web socket connection.
ws . forwardclient . on ( 'data' , function ( data ) {
2020-10-14 23:39:24 +03:00
if ( typeof data == 'string' ) { data = Buffer . from ( data , 'binary' ) ; }
2018-08-07 04:57:30 +03:00
if ( obj . parent . debugLevel >= 1 ) { // DEBUG
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelaydata' , 'TCP relay data from ' + node . host + ', ' + data . length + ' bytes.' ) ;
//if (obj.parent.debugLevel >= 4) { Debug(4, ' ' + Buffer.from(data, 'binary').toString('hex')); }
2018-08-07 04:57:30 +03:00
}
2017-08-28 19:27:45 +03:00
if ( ws . interceptor ) { data = ws . interceptor . processAmtData ( data ) ; } // Run data thru interceptor
2019-08-08 02:07:12 +03:00
if ( ws . logfile == null ) {
// No logging
2020-10-14 23:39:24 +03:00
try { ws . send ( data ) ; } catch ( e ) { }
2019-08-08 02:07:12 +03:00
} else {
// Log to recording file
recordingEntry ( ws . logfile . fd , 2 , 0 , data , function ( ) { try { ws . send ( data ) ; } catch ( e ) { } } ) ;
}
2017-08-28 19:27:45 +03:00
} ) ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// If the TCP connection closes, disconnect the associated web socket.
ws . forwardclient . on ( 'close' , function ( ) {
2020-03-31 05:29:46 +03:00
parent . debug ( 'webrelay' , 'TCP relay disconnected from ' + node . host + ':' + port + '.' ) ;
2017-08-28 19:27:45 +03:00
try { ws . close ( ) ; } catch ( e ) { }
} ) ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// If the TCP connection causes an error, disconnect the associated web socket.
ws . forwardclient . on ( 'error' , function ( err ) {
2020-03-31 05:29:46 +03:00
parent . debug ( 'webrelay' , 'TCP relay error from ' + node . host + ':' + port + ': ' + err ) ;
2017-08-28 19:27:45 +03:00
try { ws . close ( ) ; } catch ( e ) { }
} ) ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Fetch Intel AMT credentials & Setup interceptor
if ( req . query . p == 1 ) { ws . interceptor = obj . interceptor . CreateHttpInterceptor ( { host : node . host , port : port , user : node . intelamt . user , pass : node . intelamt . pass } ) ; }
else if ( req . query . p == 2 ) { ws . interceptor = obj . interceptor . CreateRedirInterceptor ( { user : node . intelamt . user , pass : node . intelamt . pass } ) ; }
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
if ( node . intelamt . tls == 0 ) {
2017-09-15 21:45:06 +03:00
// A TCP connection to Intel AMT just connected, start forwarding.
2017-08-28 19:27:45 +03:00
ws . forwardclient . connect ( port , node . host , function ( ) {
2019-08-23 01:31:39 +03:00
parent . debug ( 'webrelay' , 'TCP relay connected to ' + node . host + ':' + port + '.' ) ;
2017-08-28 19:27:45 +03:00
ws . forwardclient . xstate = 1 ;
2018-12-01 08:23:10 +03:00
ws . _socket . resume ( ) ;
2017-08-28 19:27:45 +03:00
} ) ;
}
return ;
}
} ) ;
2022-07-20 22:57:24 +03:00
}
2017-08-28 19:27:45 +03:00
2020-07-08 09:56:08 +03:00
// Setup agent to/from server file transfer handler
function handleAgentFileTransfer ( ws , req ) {
var domain = checkAgentIpAddress ( ws , req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'Got agent file transfer connection with bad domain or blocked IP address ' + req . clientIp + ', dropping.' ) ; ws . close ( ) ; return ; }
if ( req . query . c == null ) { parent . debug ( 'web' , 'Got agent file transfer connection without a cookie from ' + req . clientIp + ', dropping.' ) ; ws . close ( ) ; return ; }
var c = obj . parent . decodeCookie ( req . query . c , obj . parent . loginCookieEncryptionKey , 10 ) ; // 10 minute timeout
if ( ( c == null ) || ( c . a != 'aft' ) ) { parent . debug ( 'web' , 'Got agent file transfer connection with invalid cookie from ' + req . clientIp + ', dropping.' ) ; ws . close ( ) ; return ; }
ws . xcmd = c . b ; ws . xarg = c . c , ws . xfilelen = 0 ;
ws . send ( 'c' ) ; // Indicate connection of the tunnel. In this case, we are the termination point.
ws . send ( '5' ) ; // Indicate we want to perform file transfers (5 = Files).
if ( ws . xcmd == 'coredump' ) {
// Check the agent core dump folder if not already present.
2020-08-21 21:47:34 +03:00
var coreDumpPath = obj . path . join ( parent . datapath , '..' , 'meshcentral-coredumps' ) ;
2020-07-08 09:56:08 +03:00
if ( obj . fs . existsSync ( coreDumpPath ) == false ) { try { obj . fs . mkdirSync ( coreDumpPath ) ; } catch ( ex ) { } }
2020-08-21 21:47:34 +03:00
ws . xfilepath = obj . path . join ( parent . datapath , '..' , 'meshcentral-coredumps' , ws . xarg ) ;
2020-07-08 09:56:08 +03:00
ws . xid = 'coredump' ;
2020-12-01 00:20:31 +03:00
ws . send ( JSON . stringify ( { action : 'download' , sub : 'start' , ask : 'coredump' , id : 'coredump' } ) ) ; // Ask for a core dump file
2020-07-08 09:56:08 +03:00
}
// When data is received from the web socket, echo it back
ws . on ( 'message' , function ( data ) {
if ( typeof data == 'string' ) {
// Control message
var cmd = null ;
try { cmd = JSON . parse ( data ) ; } catch ( ex ) { }
if ( ( cmd == null ) || ( cmd . action != 'download' ) || ( cmd . sub == null ) ) return ;
switch ( cmd . sub ) {
case 'start' : {
// Perform an async file open
var callback = function onFileOpen ( err , fd ) {
onFileOpen . xws . xfile = fd ;
2021-03-01 23:38:13 +03:00
try { onFileOpen . xws . send ( JSON . stringify ( { action : 'download' , sub : 'startack' , id : onFileOpen . xws . xid , ack : 1 } ) ) ; } catch ( ex ) { } // Ask for a directory (test)
2020-07-08 09:56:08 +03:00
} ;
callback . xws = this ;
2020-10-28 02:40:12 +03:00
obj . fs . open ( this . xfilepath + '.part' , 'w' , callback ) ;
2020-07-08 09:56:08 +03:00
break ;
}
}
} else {
// Binary message
if ( data . length < 4 ) return ;
var flags = data . readInt32BE ( 0 ) ;
if ( ( data . length > 4 ) ) {
// Write the file
this . xfilelen += ( data . length - 4 ) ;
try {
var callback = function onFileDataWritten ( err , bytesWritten , buffer ) {
if ( onFileDataWritten . xflags & 1 ) {
// End of file
parent . debug ( 'web' , "Completed downloads of agent dumpfile, " + onFileDataWritten . xws . xfilelen + " bytes." ) ;
2020-10-28 02:40:12 +03:00
if ( onFileDataWritten . xws . xfile ) {
obj . fs . close ( onFileDataWritten . xws . xfile , function ( err ) { } ) ;
obj . fs . rename ( onFileDataWritten . xws . xfilepath + '.part' , onFileDataWritten . xws . xfilepath , function ( err ) { } ) ;
onFileDataWritten . xws . xfile = null ;
}
2021-03-01 23:38:13 +03:00
try { onFileDataWritten . xws . send ( JSON . stringify ( { action : 'markcoredump' } ) ) ; } catch ( ex ) { } // Ask to delete the core dump file
2020-07-08 09:56:08 +03:00
try { onFileDataWritten . xws . close ( ) ; } catch ( ex ) { }
} else {
// Send ack
2021-03-01 23:38:13 +03:00
try { onFileDataWritten . xws . send ( JSON . stringify ( { action : 'download' , sub : 'ack' , id : onFileDataWritten . xws . xid } ) ) ; } catch ( ex ) { } // Ask for a directory (test)
2020-07-08 09:56:08 +03:00
}
} ;
callback . xws = this ;
callback . xflags = flags ;
obj . fs . write ( this . xfile , data , 4 , data . length - 4 , callback ) ;
} catch ( ex ) { }
} else {
if ( flags & 1 ) {
// End of file
parent . debug ( 'web' , "Completed downloads of agent dumpfile, " + this . xfilelen + " bytes." ) ;
2020-10-28 02:40:12 +03:00
if ( this . xfile ) {
obj . fs . close ( this . xfile , function ( err ) { } ) ;
obj . fs . rename ( this . xfilepath + '.part' , this . xfilepath , function ( err ) { } ) ;
this . xfile = null ;
}
2020-07-08 09:56:08 +03:00
this . send ( JSON . stringify ( { action : 'markcoredump' } ) ) ; // Ask to delete the core dump file
try { this . close ( ) ; } catch ( ex ) { }
} else {
// Send ack
this . send ( JSON . stringify ( { action : 'download' , sub : 'ack' , id : this . xid } ) ) ; // Ask for a directory (test)
}
}
}
} ) ;
// If error, do nothing.
ws . on ( 'error' , function ( err ) { console . log ( 'Agent file transfer server error from ' + req . clientIp + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ; } ) ;
// If closed, do nothing
2020-10-28 02:40:12 +03:00
ws . on ( 'close' , function ( req ) {
if ( this . xfile ) {
obj . fs . close ( this . xfile , function ( err ) { } ) ;
obj . fs . unlink ( this . xfilepath + '.part' , function ( err ) { } ) ; // Remove a partial file
}
} ) ;
2020-07-08 09:56:08 +03:00
}
2017-08-28 19:27:45 +03:00
// Handle the web socket echo request, just echo back the data sent
function handleEchoWebSocket ( ws , req ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( ws , req ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2017-10-25 19:58:14 +03:00
ws . _socket . setKeepAlive ( true , 240000 ) ; // Set TCP keep alive
2017-09-27 22:43:20 +03:00
2017-08-28 19:27:45 +03:00
// When data is received from the web socket, echo it back
ws . on ( 'message' , function ( data ) {
2019-01-05 23:04:18 +03:00
if ( data . toString ( 'utf8' ) == 'close' ) {
2017-08-28 19:27:45 +03:00
try { ws . close ( ) ; } catch ( e ) { console . log ( e ) ; }
} else {
try { ws . send ( data ) ; } catch ( e ) { console . log ( e ) ; }
}
} ) ;
2019-01-03 05:34:50 +03:00
// If error, do nothing.
2020-05-22 07:25:11 +03:00
ws . on ( 'error' , function ( err ) { console . log ( 'Echo server error from ' + req . clientIp + ', ' + err . toString ( ) . split ( '\r' ) [ 0 ] + '.' ) ; } ) ;
2017-08-28 19:27:45 +03:00
// If closed, do nothing
ws . on ( 'close' , function ( req ) { } ) ;
}
2018-05-24 07:43:44 +03:00
2021-04-14 23:28:51 +03:00
// Handle the 2FA hold web socket
// Accept an hold a web socket connection until the 2FA response is received.
function handle2faHoldWebSocket ( ws , req ) {
const domain = checkUserIpAddress ( ws , req ) ;
if ( domain == null ) { return ; }
2021-04-15 00:06:31 +03:00
if ( ( typeof domain . passwordrequirements == 'object' ) && ( domain . passwordrequirements . push2factor == false ) ) { ws . close ( ) ; return ; } // Push 2FA is disabled
2021-04-14 23:28:51 +03:00
if ( typeof req . query . c !== 'string' ) { ws . close ( ) ; return ; }
const cookie = parent . decodeCookie ( req . query . c , null , 1 ) ;
if ( ( cookie == null ) || ( cookie . d != domain . id ) ) { ws . close ( ) ; return ; }
var user = obj . users [ cookie . u ] ;
if ( ( user == null ) || ( typeof user . otpdev != 'string' ) ) { ws . close ( ) ; return ; }
2021-04-15 00:06:31 +03:00
ws . _socket . setKeepAlive ( true , 240000 ) ; // Set TCP keep alive
2021-04-14 23:28:51 +03:00
// 2FA event subscription
obj . parent . AddEventDispatch ( [ '2fadev-' + cookie . s ] , ws ) ;
ws . cookie = cookie ;
ws . HandleEvent = function ( source , event , ids , id ) {
obj . parent . RemoveAllEventDispatch ( this ) ;
if ( ( event . approved === true ) && ( event . userid == this . cookie . u ) ) {
// Create a login cookie
const loginCookie = obj . parent . encodeCookie ( { a : 'pushAuth' , u : event . userid , d : event . domain } , obj . parent . loginCookieEncryptionKey ) ;
try { ws . send ( JSON . stringify ( { approved : true , token : loginCookie } ) ) ; } catch ( ex ) { }
} else {
// Reject the login
try { ws . send ( JSON . stringify ( { approved : false } ) ) ; } catch ( ex ) { }
}
}
// We do not accept any data on this connection.
ws . on ( 'message' , function ( data ) { this . close ( ) ; } ) ;
// If error, do nothing.
ws . on ( 'error' , function ( err ) { } ) ;
// If closed, unsubscribe
ws . on ( 'close' , function ( req ) { obj . parent . RemoveAllEventDispatch ( this ) ; } ) ;
// Perform push notification to device
try {
const deviceCookie = parent . encodeCookie ( { a : 'checkAuth' , c : cookie . c , u : cookie . u , n : cookie . n , s : cookie . s } ) ;
var code = Buffer . from ( cookie . c , 'base64' ) . toString ( ) ;
var payload = { notification : { title : ( domain . title ? domain . title : 'MeshCentral' ) , body : "Authentication - " + code } , data : { url : '2fa://auth?code=' + cookie . c + '&c=' + deviceCookie } } ;
var options = { priority : 'High' , timeToLive : 60 } ; // TTL: 1 minute
parent . firebase . sendToDevice ( user . otpdev , payload , options , function ( id , err , errdesc ) {
if ( err == null ) {
try { ws . send ( JSON . stringify ( { sent : true , code : code } ) ) ; } catch ( ex ) { }
} else {
try { ws . send ( JSON . stringify ( { sent : false } ) ) ; } catch ( ex ) { }
}
} ) ;
} catch ( ex ) { console . log ( ex ) ; }
}
2017-12-01 01:40:46 +03:00
// Get the total size of all files in a folder and all sub-folders. (TODO: try to make all async version)
2017-08-28 19:27:45 +03:00
function readTotalFileSize ( path ) {
2017-12-01 01:40:46 +03:00
var r = 0 , dir ;
try { dir = obj . fs . readdirSync ( path ) ; } catch ( e ) { return 0 ; }
2017-08-28 19:27:45 +03:00
for ( var i in dir ) {
2018-08-30 22:05:23 +03:00
var stat = obj . fs . statSync ( path + '/' + dir [ i ] ) ;
2017-08-28 19:27:45 +03:00
if ( ( stat . mode & 0x004000 ) == 0 ) { r += stat . size ; } else { r += readTotalFileSize ( path + '/' + dir [ i ] ) ; }
}
return r ;
}
2018-05-24 07:43:44 +03:00
2017-12-01 01:40:46 +03:00
// Delete a folder and all sub items. (TODO: try to make all async version)
2017-08-28 19:27:45 +03:00
function deleteFolderRec ( path ) {
if ( obj . fs . existsSync ( path ) == false ) return ;
2019-10-31 00:30:34 +03:00
try {
obj . fs . readdirSync ( path ) . forEach ( function ( file , index ) {
var pathx = path + '/' + file ;
if ( obj . fs . lstatSync ( pathx ) . isDirectory ( ) ) { deleteFolderRec ( pathx ) ; } else { obj . fs . unlinkSync ( pathx ) ; }
} ) ;
obj . fs . rmdirSync ( path ) ;
} catch ( ex ) { }
2018-08-30 22:05:23 +03:00
}
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Handle Intel AMT events
// To subscribe, add "http://server:port/amtevents.ashx" to Intel AMT subscriptions.
obj . handleAmtEventRequest = function ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = getDomain ( req ) ;
2017-08-28 19:27:45 +03:00
try {
2018-08-30 22:05:23 +03:00
if ( req . headers . authorization ) {
var authstr = req . headers . authorization ;
2019-12-27 09:53:01 +03:00
if ( authstr . substring ( 0 , 7 ) == 'Digest ' ) {
2017-08-28 19:27:45 +03:00
var auth = obj . common . parseNameValueList ( obj . common . quoteSplit ( authstr . substring ( 7 ) ) ) ;
2017-10-15 09:22:19 +03:00
if ( ( req . url === auth . uri ) && ( obj . httpAuthRealm === auth . realm ) && ( auth . opaque === obj . crypto . createHmac ( 'SHA384' , obj . httpAuthRandom ) . update ( auth . nonce ) . digest ( 'hex' ) ) ) {
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Read the data, we need to get the arg field
var eventData = '' ;
req . on ( 'data' , function ( chunk ) { eventData += chunk ; } ) ;
req . on ( 'end' , function ( ) {
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Completed event read, let get the argument that must contain the nodeid
var i = eventData . indexOf ( '<m:arg xmlns:m="http://x.com">' ) ;
if ( i > 0 ) {
2017-10-16 03:36:06 +03:00
var nodeid = eventData . substring ( i + 30 , i + 30 + 64 ) ;
2017-08-28 19:27:45 +03:00
if ( nodeid . length == 64 ) {
var nodekey = 'node/' + domain . id + '/' + nodeid ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// See if this node exists in the database
obj . db . Get ( nodekey , function ( err , nodes ) {
if ( nodes . length == 1 ) {
// Yes, the node exists, compute Intel AMT digest password
var node = nodes [ 0 ] ;
2019-12-27 09:53:01 +03:00
var amtpass = obj . crypto . createHash ( 'sha384' ) . update ( auth . username . toLowerCase ( ) + ':' + nodeid + ":" + obj . parent . dbconfig . amtWsEventSecret ) . digest ( 'base64' ) . substring ( 0 , 12 ) . split ( '/' ) . join ( 'x' ) . split ( '\\' ) . join ( 'x' ) ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Check the MD5 hash
2019-12-27 09:53:01 +03:00
if ( auth . response === obj . common . ComputeDigesthash ( auth . username , amtpass , auth . realm , 'POST' , auth . uri , auth . qop , auth . nonce , auth . nc , auth . cnonce ) ) {
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// This is an authenticated Intel AMT event, update the host address
2020-05-22 07:25:11 +03:00
var amthost = req . clientIp ;
2017-08-28 19:27:45 +03:00
if ( amthost . substring ( 0 , 7 ) === '::ffff:' ) { amthost = amthost . substring ( 7 ) ; }
if ( node . host != amthost ) {
// Get the mesh for this device
var mesh = obj . meshes [ node . meshid ] ;
if ( mesh ) {
// Update the database
var oldname = node . host ;
node . host = amthost ;
2020-03-31 03:47:04 +03:00
obj . db . Set ( obj . cleanDevice ( node ) ) ;
2017-08-28 19:27:45 +03:00
// Event the node change
2018-09-25 03:47:03 +03:00
var event = { etype : 'node' , action : 'changenode' , nodeid : node . _id , domain : domain . id , msg : 'Intel(R) AMT host change ' + node . name + ' from group ' + mesh . name + ': ' + oldname + ' to ' + amthost } ;
2019-10-11 21:16:36 +03:00
// Remove the Intel AMT password before eventing this.
event . node = node ;
if ( event . node . intelamt && event . node . intelamt . pass ) {
event . node = Object . assign ( { } , event . node ) ; // Shallow clone
event . node . intelamt = Object . assign ( { } , event . node . intelamt ) ; // Shallow clone
delete event . node . intelamt . pass ;
}
2019-05-29 03:25:23 +03:00
if ( obj . db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the node. Another event will come.
2017-08-28 19:27:45 +03:00
obj . parent . DispatchEvent ( [ '*' , node . meshid ] , obj , event ) ;
}
}
2018-05-24 07:43:44 +03:00
2021-08-20 23:05:34 +03:00
if ( parent . amtEventHandler ) { parent . amtEventHandler . handleAmtEvent ( eventData , nodeid , amthost ) ; }
2017-08-28 19:27:45 +03:00
//res.send('OK');
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
return ;
}
}
} ) ;
}
}
} ) ;
}
}
}
} catch ( e ) { console . log ( e ) ; }
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Send authentication response
2017-10-15 09:22:19 +03:00
obj . crypto . randomBytes ( 48 , function ( err , buf ) {
var nonce = buf . toString ( 'hex' ) , opaque = obj . crypto . createHmac ( 'SHA384' , obj . httpAuthRandom ) . update ( nonce ) . digest ( 'hex' ) ;
2017-08-28 19:27:45 +03:00
res . set ( { 'WWW-Authenticate' : 'Digest realm="' + obj . httpAuthRealm + '", qop="auth,auth-int", nonce="' + nonce + '", opaque="' + opaque + '"' } ) ;
res . sendStatus ( 401 ) ;
} ) ;
2018-08-30 22:05:23 +03:00
} ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Handle a server backup request
function handleBackupRequest ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2020-09-28 23:28:08 +03:00
if ( ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) ) { res . sendStatus ( 401 ) ; return ; }
2020-10-06 02:47:30 +03:00
if ( ( domain . myserver === false ) || ( ( domain . myserver != null ) && ( domain . myserver . backup !== true ) ) ) { res . sendStatus ( 401 ) ; return ; }
2020-09-28 23:28:08 +03:00
2017-08-28 19:27:45 +03:00
var user = obj . users [ req . session . userid ] ;
2019-05-25 01:11:00 +03:00
if ( ( user == null ) || ( ( user . siteadmin & 1 ) == 0 ) ) { res . sendStatus ( 401 ) ; return ; } // Check if we have server backup rights
2017-08-28 19:27:45 +03:00
// Require modules
2019-01-03 05:34:50 +03:00
const archive = require ( 'archiver' ) ( 'zip' , { level : 9 } ) ; // Sets the compression method to maximum.
2018-05-24 07:43:44 +03:00
2019-12-09 22:30:08 +03:00
// Good practice to catch this error explicitly
2017-08-28 19:27:45 +03:00
archive . on ( 'error' , function ( err ) { throw err ; } ) ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Set the archive name
2019-12-22 23:44:16 +03:00
res . attachment ( ( domain . title ? domain . title : 'MeshCentral' ) + '-Backup-' + new Date ( ) . toLocaleDateString ( ) . replace ( '/' , '-' ) . replace ( '/' , '-' ) + '.zip' ) ;
2017-08-28 19:27:45 +03:00
// Pipe archive data to the file
archive . pipe ( res ) ;
2018-05-24 07:43:44 +03:00
2020-09-28 20:34:44 +03:00
// Append files from a glob pattern
archive . directory ( obj . parent . datapath , false ) ;
2017-08-28 19:27:45 +03:00
// Finalize the archive (ie we are done appending files but streams have to finish yet)
archive . finalize ( ) ;
}
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Handle a server restore request
function handleRestoreRequest ( req , res ) {
2019-01-05 23:04:18 +03:00
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:30:23 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2020-10-06 02:47:30 +03:00
if ( ( domain . myserver === false ) || ( ( domain . myserver != null ) && ( domain . myserver . restore !== true ) ) ) { res . sendStatus ( 401 ) ; return ; }
2020-09-28 23:28:08 +03:00
2019-10-01 00:50:28 +03:00
var authUserid = null ;
if ( ( req . session != null ) && ( typeof req . session . userid == 'string' ) ) { authUserid = req . session . userid ; }
2019-01-05 23:04:18 +03:00
const multiparty = require ( 'multiparty' ) ;
const form = new multiparty . Form ( ) ;
2017-08-28 19:27:45 +03:00
form . parse ( req , function ( err , fields , files ) {
2019-10-01 00:50:28 +03:00
// If an authentication cookie is embedded in the form, use that.
if ( ( fields != null ) && ( fields . auth != null ) && ( fields . auth . length == 1 ) && ( typeof fields . auth [ 0 ] == 'string' ) ) {
var loginCookie = obj . parent . decodeCookie ( fields . auth [ 0 ] , obj . parent . loginCookieEncryptionKey , 60 ) ; // 60 minute timeout
2022-04-13 00:03:52 +03:00
if ( ( loginCookie != null ) && ( loginCookie . ip != null ) && ! checkCookieIp ( loginCookie . ip , req . clientIp ) ) { loginCookie = null ; } // Check cookie IP binding.
2019-10-01 00:50:28 +03:00
if ( ( loginCookie != null ) && ( domain . id == loginCookie . domainid ) ) { authUserid = loginCookie . userid ; } // Use cookie authentication
}
if ( authUserid == null ) { res . sendStatus ( 401 ) ; return ; }
// Get the user
const user = obj . users [ req . session . userid ] ;
if ( ( user == null ) || ( ( user . siteadmin & 4 ) == 0 ) ) { res . sendStatus ( 401 ) ; return ; } // Check if we have server restore rights
2020-09-28 20:34:44 +03:00
res . set ( 'Content-Type' , 'text/html' ) ;
res . end ( '<html><body>Server must be restarted, <a href="' + domain . url + '">click here to login</a>.</body></html>' ) ;
2017-08-28 19:27:45 +03:00
parent . Stop ( files . datafile [ 0 ] . path ) ;
} ) ;
}
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Handle a request to download a mesh agent
obj . handleMeshAgentRequest = function ( req , res ) {
2020-03-31 20:37:52 +03:00
var domain = getDomain ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { parent . debug ( 'web' , 'handleRootRequest: invalid domain.' ) ; try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2018-01-31 05:23:57 +03:00
// If required, check if this user has rights to do this
2019-07-11 00:27:38 +03:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) && ( req . session . userid == null ) ) { res . sendStatus ( 401 ) ; return ; }
2018-01-31 05:23:57 +03:00
2020-08-02 07:12:07 +03:00
if ( ( req . query . meshinstall != null ) && ( req . query . id != null ) ) {
2021-01-26 22:46:22 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; } // Check 3FA URL key
2020-08-02 07:12:07 +03:00
// Send meshagent with included self installer for a specific platform back
// Start by getting the .msh for this request
var meshsettings = getMshFromRequest ( req , res , domain ) ;
2021-01-26 22:46:22 +03:00
if ( meshsettings == null ) { try { res . sendStatus ( 401 ) ; } catch ( ex ) { } return ; }
2020-08-02 07:12:07 +03:00
// Get the interactive install script, this only works for non-Windows agents
var agentid = parseInt ( req . query . meshinstall ) ;
var argentInfo = obj . parent . meshAgentBinaries [ agentid ] ;
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ agentid ] ) { argentInfo = domain . meshAgentBinaries [ agentid ] ; }
2020-08-02 07:12:07 +03:00
var scriptInfo = obj . parent . meshAgentInstallScripts [ 6 ] ;
2021-01-26 22:46:22 +03:00
if ( ( argentInfo == null ) || ( scriptInfo == null ) || ( argentInfo . platform == 'win32' ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-08-02 07:12:07 +03:00
// Change the .msh file into JSON format and merge it into the install script
var tokens , msh = { } , meshsettingslines = meshsettings . split ( '\r' ) . join ( '' ) . split ( '\n' ) ;
for ( var i in meshsettingslines ) { tokens = meshsettingslines [ i ] . split ( '=' ) ; if ( tokens . length == 2 ) { msh [ tokens [ 0 ] ] = tokens [ 1 ] ; } }
var js = scriptInfo . data . replace ( 'var msh = {};' , 'var msh = ' + JSON . stringify ( msh ) + ';' ) ;
2020-12-20 04:21:42 +03:00
// Get the agent filename
var meshagentFilename = 'meshagent' ;
if ( ( domain . agentcustomization != null ) && ( typeof domain . agentcustomization . filename == 'string' ) ) { meshagentFilename = domain . agentcustomization . filename ; }
setContentDispositionHeader ( res , 'application/octet-stream' , meshagentFilename , null , 'meshagent' ) ;
2021-11-19 05:43:27 +03:00
if ( argentInfo . mtime != null ) { res . setHeader ( 'Last-Modified' , argentInfo . mtime . toUTCString ( ) ) ; }
2020-08-02 07:12:07 +03:00
res . statusCode = 200 ;
obj . parent . exeHandler . streamExeWithJavaScript ( { platform : argentInfo . platform , sourceFileName : argentInfo . path , destinationStream : res , js : Buffer . from ( js , 'utf8' ) , peinfo : argentInfo . pe } ) ;
} else if ( req . query . id != null ) {
2017-08-28 19:27:45 +03:00
// Send a specific mesh agent back
var argentInfo = obj . parent . meshAgentBinaries [ req . query . id ] ;
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ req . query . id ] ) { argentInfo = domain . meshAgentBinaries [ req . query . id ] ; }
2021-01-26 22:46:22 +03:00
if ( argentInfo == null ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-10-26 11:22:46 +03:00
// Download PDB debug files, only allowed for administrator or accounts with agent dump access
2020-10-26 21:13:27 +03:00
if ( req . query . pdb == 1 ) {
2021-01-26 22:46:22 +03:00
if ( ( req . session == null ) || ( req . session . userid == null ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-10-26 21:13:27 +03:00
var user = obj . users [ req . session . userid ] ;
2021-01-26 22:46:22 +03:00
if ( user == null ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-10-26 21:13:27 +03:00
if ( ( user != null ) && ( ( user . siteadmin == 0xFFFFFFFF ) || ( ( Array . isArray ( obj . parent . config . settings . agentcoredumpusers ) ) && ( obj . parent . config . settings . agentcoredumpusers . indexOf ( user . _id ) >= 0 ) ) ) ) {
2021-01-26 22:46:22 +03:00
if ( argentInfo . id == 3 ) {
setContentDispositionHeader ( res , 'application/octet-stream' , 'MeshService.pdb' , null , 'MeshService.pdb' ) ;
2021-11-19 05:43:27 +03:00
if ( argentInfo . mtime != null ) { res . setHeader ( 'Last-Modified' , argentInfo . mtime . toUTCString ( ) ) ; }
2021-01-26 22:46:22 +03:00
try { res . sendFile ( argentInfo . path . split ( 'MeshService-signed.exe' ) . join ( 'MeshService.pdb' ) ) ; } catch ( ex ) { }
return ;
}
if ( argentInfo . id == 4 ) {
setContentDispositionHeader ( res , 'application/octet-stream' , 'MeshService64.pdb' , null , 'MeshService64.pdb' ) ;
2021-11-19 05:43:27 +03:00
if ( argentInfo . mtime != null ) { res . setHeader ( 'Last-Modified' , argentInfo . mtime . toUTCString ( ) ) ; }
2021-01-26 22:46:22 +03:00
try { res . sendFile ( argentInfo . path . split ( 'MeshService64-signed.exe' ) . join ( 'MeshService64.pdb' ) ) ; } catch ( ex ) { }
return ;
}
2020-10-26 21:13:27 +03:00
}
2021-01-26 22:46:22 +03:00
try { res . sendStatus ( 404 ) ; } catch ( ex ) { }
return ;
2020-10-26 11:22:46 +03:00
}
2018-01-20 05:04:54 +03:00
if ( ( req . query . meshid == null ) || ( argentInfo . platform != 'win32' ) ) {
2020-12-20 04:21:42 +03:00
// Get the agent filename
var meshagentFilename = argentInfo . rname ;
if ( ( domain . agentcustomization != null ) && ( typeof domain . agentcustomization . filename == 'string' ) ) { meshagentFilename = domain . agentcustomization . filename ; }
2023-10-21 03:29:48 +03:00
if ( argentInfo . rname . endsWith ( '.apk' ) && ! meshagentFilename . endsWith ( '.apk' ) ) { meshagentFilename = meshagentFilename + '.apk' ; }
2021-11-19 05:43:27 +03:00
if ( argentInfo . mtime != null ) { res . setHeader ( 'Last-Modified' , argentInfo . mtime . toUTCString ( ) ) ; }
2021-08-18 01:32:58 +03:00
if ( req . query . zip == 1 ) { if ( argentInfo . zdata != null ) { setContentDispositionHeader ( res , 'application/octet-stream' , meshagentFilename + '.zip' , null , 'meshagent.zip' ) ; res . send ( argentInfo . zdata ) ; } else { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } } return ; } // Send compressed agent
2020-12-20 04:21:42 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , meshagentFilename , null , 'meshagent' ) ;
2021-01-26 22:46:22 +03:00
if ( argentInfo . data == null ) { res . sendFile ( argentInfo . path ) ; } else { res . send ( argentInfo . data ) ; }
return ;
2018-01-20 05:04:54 +03:00
} else {
2020-06-18 23:56:04 +03:00
// Check if the meshid is a time limited, encrypted cookie
var meshcookie = obj . parent . decodeCookie ( req . query . meshid , obj . parent . invitationLinkEncryptionKey ) ;
if ( ( meshcookie != null ) && ( meshcookie . m != null ) ) { req . query . meshid = meshcookie . m ; }
2018-01-20 05:04:54 +03:00
// We are going to embed the .msh file into the Windows executable (signed or not).
2018-08-22 01:08:15 +03:00
// First, fetch the mesh object to build the .msh file
var mesh = obj . meshes [ 'mesh/' + domain . id + '/' + req . query . meshid ] ;
2021-01-26 22:46:22 +03:00
if ( mesh == null ) { try { res . sendStatus ( 401 ) ; } catch ( ex ) { } return ; }
2018-08-22 01:08:15 +03:00
// If required, check if this user has rights to do this
2019-07-11 00:27:38 +03:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) ) {
2021-01-26 22:46:22 +03:00
if ( ( domain . id != mesh . domain ) || ( ( obj . GetMeshRights ( req . session . userid , mesh ) & 1 ) == 0 ) ) { try { res . sendStatus ( 401 ) ; } catch ( ex ) { } return ; }
2018-08-22 01:08:15 +03:00
}
2018-01-20 05:04:54 +03:00
2019-01-03 05:03:34 +03:00
var meshidhex = Buffer . from ( req . query . meshid . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
var serveridhex = Buffer . from ( obj . agentCertificateHashBase64 . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
2020-05-04 08:37:51 +03:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port if specified
2020-05-22 20:10:06 +03:00
if ( obj . args . agentport != null ) { httpsPort = obj . args . agentport ; } // If an agent only port is enabled, use that.
2020-05-04 08:37:51 +03:00
if ( obj . args . agentaliasport != null ) { httpsPort = obj . args . agentaliasport ; } // If an agent alias port is specified, use that.
2018-01-20 05:04:54 +03:00
2019-06-21 22:10:06 +03:00
// Prepare a mesh agent file name using the device group name.
var meshfilename = mesh . name
meshfilename = meshfilename . split ( '\\' ) . join ( '' ) . split ( '/' ) . join ( '' ) . split ( ':' ) . join ( '' ) . split ( '*' ) . join ( '' ) . split ( '?' ) . join ( '' ) . split ( '"' ) . join ( '' ) . split ( '<' ) . join ( '' ) . split ( '>' ) . join ( '' ) . split ( '|' ) . join ( '' ) . split ( ' ' ) . join ( '' ) . split ( '\'' ) . join ( '' ) ;
if ( argentInfo . rname . endsWith ( '.exe' ) ) { meshfilename = argentInfo . rname . substring ( 0 , argentInfo . rname . length - 4 ) + '-' + meshfilename + '.exe' ; } else { meshfilename = argentInfo . rname + '-' + meshfilename ; }
2020-12-20 04:21:42 +03:00
// Customize the mesh agent file name
if ( ( domain . agentcustomization != null ) && ( typeof domain . agentcustomization . filename == 'string' ) ) {
2021-06-08 23:31:46 +03:00
meshfilename = meshfilename . split ( 'meshagent' ) . join ( domain . agentcustomization . filename ) . split ( 'MeshAgent' ) . join ( domain . agentcustomization . filename ) ;
2020-12-20 04:21:42 +03:00
}
2020-05-10 23:45:10 +03:00
// Get the agent connection server name
2022-08-25 00:10:40 +03:00
var serverName = obj . getWebServerName ( domain , req ) ;
2020-05-10 23:45:10 +03:00
if ( typeof obj . args . agentaliasdns == 'string' ) { serverName = obj . args . agentaliasdns ; }
2018-08-22 01:08:15 +03:00
// Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
2019-12-27 09:53:01 +03:00
if ( xdomain != '' ) xdomain += '/' ;
2024-01-13 21:52:17 +03:00
var meshsettings = '' ;
2024-03-04 03:03:27 +03:00
if ( req . query . ac != '4' ) { // If MeshCentral Assistant Monitor Mode, DONT INCLUDE SERVER DETAILS!
2024-01-13 21:52:17 +03:00
meshsettings += '\r\nMeshName=' + mesh . name + '\r\nMeshType=' + mesh . mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n' ;
if ( obj . args . lanonly != true ) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n' ; } else {
meshsettings += 'MeshServer=local\r\n' ;
if ( ( obj . args . localdiscovery != null ) && ( typeof obj . args . localdiscovery . key == 'string' ) && ( obj . args . localdiscovery . key . length > 0 ) ) { meshsettings += 'DiscoveryKey=' + obj . args . localdiscovery . key + '\r\n' ; }
}
2024-09-27 07:09:34 +03:00
if ( ( req . query . tag != null ) && ( typeof req . query . tag == 'string' ) && ( obj . common . isAlphaNumeric ( req . query . tag ) == true ) ) { meshsettings += 'Tag=' + encodeURIComponent ( req . query . tag ) + '\r\n' ; }
2024-01-13 21:52:17 +03:00
if ( ( req . query . installflags != null ) && ( req . query . installflags != 0 ) && ( parseInt ( req . query . installflags ) == req . query . installflags ) ) { meshsettings += 'InstallFlags=' + parseInt ( req . query . installflags ) + '\r\n' ; }
2020-07-15 02:09:08 +03:00
}
2021-06-04 03:35:49 +03:00
if ( req . query . id == '10006' ) { // Assistant settings and customizations
if ( ( req . query . ac != null ) ) { meshsettings += 'AutoConnect=' + req . query . ac + '\r\n' ; } // Set MeshCentral Assistant flags if needed. 0x01 = Always Connected, 0x02 = Not System Tray
2022-01-31 21:21:13 +03:00
if ( obj . args . assistantconfig ) { for ( var i in obj . args . assistantconfig ) { meshsettings += obj . args . assistantconfig [ i ] + '\r\n' ; } }
if ( domain . assistantconfig ) { for ( var i in domain . assistantconfig ) { meshsettings += domain . assistantconfig [ i ] + '\r\n' ; } }
if ( ( domain . assistantnoproxy === true ) || ( obj . args . lanonly == true ) ) { meshsettings += 'ignoreProxyFile=1\r\n' ; }
2021-06-04 03:35:49 +03:00
if ( ( domain . assistantcustomization != null ) && ( typeof domain . assistantcustomization == 'object' ) ) {
if ( typeof domain . assistantcustomization . title == 'string' ) { meshsettings += 'Title=' + domain . assistantcustomization . title + '\r\n' ; }
if ( typeof domain . assistantcustomization . image == 'string' ) {
try { meshsettings += 'Image=' + Buffer . from ( obj . fs . readFileSync ( parent . getConfigFilePath ( domain . assistantcustomization . image ) ) , 'binary' ) . toString ( 'base64' ) + '\r\n' ; } catch ( ex ) { console . log ( ex ) ; }
}
2021-06-08 23:31:46 +03:00
if ( req . query . ac != '4' ) {
// Send with custom filename followed by device group name
if ( typeof domain . assistantcustomization . filename == 'string' ) { meshfilename = meshfilename . split ( 'MeshCentralAssistant' ) . join ( domain . assistantcustomization . filename ) ; }
} else {
// Send with custom filename, no device group name
if ( typeof domain . assistantcustomization . filename == 'string' ) { meshfilename = domain . assistantcustomization . filename + '.exe' ; } else { meshfilename = 'MeshCentralAssistant.exe' ; }
}
2021-06-04 06:18:38 +03:00
}
} else { // Add agent customization, not for Assistant
2022-01-31 21:21:13 +03:00
if ( obj . args . agentconfig ) { for ( var i in obj . args . agentconfig ) { meshsettings += obj . args . agentconfig [ i ] + '\r\n' ; } }
if ( domain . agentconfig ) { for ( var i in domain . agentconfig ) { meshsettings += domain . agentconfig [ i ] + '\r\n' ; } }
if ( ( domain . agentnoproxy === true ) || ( obj . args . lanonly == true ) ) { meshsettings += 'ignoreProxyFile=1\r\n' ; }
2021-06-04 06:18:38 +03:00
if ( domain . agentcustomization != null ) {
if ( domain . agentcustomization . displayname != null ) { meshsettings += 'displayName=' + domain . agentcustomization . displayname + '\r\n' ; }
if ( domain . agentcustomization . description != null ) { meshsettings += 'description=' + domain . agentcustomization . description + '\r\n' ; }
if ( domain . agentcustomization . companyname != null ) { meshsettings += 'companyName=' + domain . agentcustomization . companyname + '\r\n' ; }
if ( domain . agentcustomization . servicename != null ) { meshsettings += 'meshServiceName=' + domain . agentcustomization . servicename + '\r\n' ; }
if ( domain . agentcustomization . filename != null ) { meshsettings += 'fileName=' + domain . agentcustomization . filename + '\r\n' ; }
2022-01-19 00:34:10 +03:00
if ( domain . agentcustomization . image != null ) { meshsettings += 'image=' + domain . agentcustomization . image + '\r\n' ; }
2022-02-22 11:21:39 +03:00
if ( domain . agentcustomization . foregroundcolor != null ) { meshsettings += checkAgentColorString ( 'foreground=' , domain . agentcustomization . foregroundcolor ) ; }
if ( domain . agentcustomization . backgroundcolor != null ) { meshsettings += checkAgentColorString ( 'background=' , domain . agentcustomization . backgroundcolor ) ; }
2021-06-04 03:35:49 +03:00
}
2022-02-05 02:02:09 +03:00
if ( domain . agentTranslations != null ) { meshsettings += 'translation=' + domain . agentTranslations + '\r\n' ; } // Translation strings, not for MeshCentral Assistant
2021-06-04 03:35:49 +03:00
}
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , meshfilename , null , argentInfo . rname ) ;
2021-11-19 05:43:27 +03:00
if ( argentInfo . mtime != null ) { res . setHeader ( 'Last-Modified' , argentInfo . mtime . toUTCString ( ) ) ; }
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ req . query . id ] ) {
obj . parent . exeHandler . streamExeWithMeshPolicy ( { platform : 'win32' , sourceFileName : domain . meshAgentBinaries [ req . query . id ] . path , destinationStream : res , msh : meshsettings , peinfo : domain . meshAgentBinaries [ req . query . id ] . pe } ) ;
} else {
obj . parent . exeHandler . streamExeWithMeshPolicy ( { platform : 'win32' , sourceFileName : obj . parent . meshAgentBinaries [ req . query . id ] . path , destinationStream : res , msh : meshsettings , peinfo : obj . parent . meshAgentBinaries [ req . query . id ] . pe } ) ;
}
2021-01-26 22:46:22 +03:00
return ;
2018-01-20 05:04:54 +03:00
}
2017-09-15 21:45:06 +03:00
} else if ( req . query . script != null ) {
2021-01-26 22:46:22 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; } // Check 3FA URL key
2020-03-14 01:43:24 +03:00
2017-08-28 19:27:45 +03:00
// Send a specific mesh install script back
var scriptInfo = obj . parent . meshAgentInstallScripts [ req . query . script ] ;
2021-01-26 22:46:22 +03:00
if ( scriptInfo == null ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , scriptInfo . rname , null , 'script' ) ;
2019-07-11 21:43:45 +03:00
var data = scriptInfo . data ;
var cmdoptions = { wgetoptionshttp : '' , wgetoptionshttps : '' , curloptionshttp : '-L ' , curloptionshttps : '-L ' }
2019-10-06 00:24:40 +03:00
if ( obj . isTrustedCert ( domain ) != true ) {
2019-07-11 21:43:45 +03:00
cmdoptions . wgetoptionshttps += '--no-check-certificate ' ;
cmdoptions . curloptionshttps += '-k ' ;
}
if ( domain . agentnoproxy === true ) {
cmdoptions . wgetoptionshttp += '--no-proxy ' ;
cmdoptions . wgetoptionshttps += '--no-proxy ' ;
cmdoptions . curloptionshttp += '--noproxy \'*\' ' ;
cmdoptions . curloptionshttps += '--noproxy \'*\' ' ;
}
for ( var i in cmdoptions ) { data = data . split ( '{{{' + i + '}}}' ) . join ( cmdoptions [ i ] ) ; }
res . send ( data ) ;
2021-01-26 22:46:22 +03:00
return ;
2017-11-01 02:19:58 +03:00
} else if ( req . query . meshcmd != null ) {
2021-01-26 22:46:22 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; } // Check 3FA URL key
2020-03-14 01:43:24 +03:00
2017-11-01 02:19:58 +03:00
// Send meshcmd for a specific platform back
2018-03-09 04:58:22 +03:00
var agentid = parseInt ( req . query . meshcmd ) ;
2024-03-04 03:03:27 +03:00
2018-03-10 03:39:14 +03:00
// If the agentid is 3 or 4, check if we have a signed MeshCmd.exe
2023-04-14 23:36:34 +03:00
if ( ( agentid == 3 ) && ( obj . parent . meshAgentBinaries [ 11000 ] != null ) ) { // Signed Windows MeshCmd.exe x86-32
2022-08-31 11:36:23 +03:00
var stats = null , meshCmdPath = obj . parent . meshAgentBinaries [ 11000 ] . path ;
2018-08-30 22:05:23 +03:00
try { stats = obj . fs . statSync ( meshCmdPath ) ; } catch ( e ) { }
2020-10-23 23:49:38 +03:00
if ( ( stats != null ) ) {
2022-08-31 11:36:23 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , 'meshcmd.exe' , null , 'meshcmd' ) ;
2020-10-23 23:49:38 +03:00
res . sendFile ( meshCmdPath ) ; return ;
}
2023-04-14 23:36:34 +03:00
} else if ( ( agentid == 4 ) && ( obj . parent . meshAgentBinaries [ 11001 ] != null ) ) { // Signed Windows MeshCmd64.exe x86-64
2022-08-31 11:36:23 +03:00
var stats = null , meshCmd64Path = obj . parent . meshAgentBinaries [ 11001 ] . path ;
2018-08-30 22:05:23 +03:00
try { stats = obj . fs . statSync ( meshCmd64Path ) ; } catch ( e ) { }
2020-10-23 23:49:38 +03:00
if ( ( stats != null ) ) {
2022-08-31 11:36:23 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , 'meshcmd.exe' , null , 'meshcmd' ) ;
2020-10-23 23:49:38 +03:00
res . sendFile ( meshCmd64Path ) ; return ;
}
2023-04-14 23:36:34 +03:00
} else if ( ( agentid == 43 ) && ( obj . parent . meshAgentBinaries [ 11002 ] != null ) ) { // Signed Windows MeshCmd64.exe ARM-64
var stats = null , meshCmdAMR64Path = obj . parent . meshAgentBinaries [ 11002 ] . path ;
try { stats = obj . fs . statSync ( meshCmdAMR64Path ) ; } catch ( e ) { }
if ( ( stats != null ) ) {
setContentDispositionHeader ( res , 'application/octet-stream' , 'meshcmd-arm64.exe' , null , 'meshcmd' ) ;
res . sendFile ( meshCmdAMR64Path ) ; return ;
}
2018-03-10 03:39:14 +03:00
}
2021-05-21 01:18:21 +03:00
2018-03-10 03:39:14 +03:00
// No signed agents, we are going to merge a new MeshCmd.
2021-05-21 01:18:21 +03:00
if ( ( ( agentid == 3 ) || ( agentid == 4 ) ) && ( obj . parent . meshAgentBinaries [ agentid + 10000 ] != null ) ) { agentid += 10000 ; } // Avoid merging javascript to a signed mesh agent.
2018-03-09 04:58:22 +03:00
var argentInfo = obj . parent . meshAgentBinaries [ agentid ] ;
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ agentid ] ) { argentInfo = domain . meshAgentBinaries [ agentid ] ; }
2021-01-26 22:46:22 +03:00
if ( ( argentInfo == null ) || ( obj . parent . defaultMeshCmd == null ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , 'meshcmd' + ( ( req . query . meshcmd <= 4 ) ? '.exe' : '' ) , null , 'meshcmd' ) ;
2018-01-20 05:04:54 +03:00
res . statusCode = 200 ;
2021-05-21 01:18:21 +03:00
2018-03-09 04:58:22 +03:00
if ( argentInfo . signedMeshCmdPath != null ) {
2020-08-02 07:12:07 +03:00
// If we have a pre-signed MeshCmd, send that.
2018-03-09 04:58:22 +03:00
res . sendFile ( argentInfo . signedMeshCmdPath ) ;
} else {
// Merge JavaScript to a unsigned agent and send that.
2019-01-03 05:03:34 +03:00
obj . parent . exeHandler . streamExeWithJavaScript ( { platform : argentInfo . platform , sourceFileName : argentInfo . path , destinationStream : res , js : Buffer . from ( obj . parent . defaultMeshCmd , 'utf8' ) , peinfo : argentInfo . pe } ) ;
2018-03-09 04:58:22 +03:00
}
2021-05-21 01:18:21 +03:00
return ;
2017-11-01 02:19:58 +03:00
} else if ( req . query . meshaction != null ) {
2021-01-26 22:46:22 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; } // Check 3FA URL key
2017-11-01 02:19:58 +03:00
var user = obj . users [ req . session . userid ] ;
2020-11-03 08:41:58 +03:00
if ( user == null ) {
2020-11-04 05:44:43 +03:00
// Check if we have an authentication cookie
2020-11-03 08:41:58 +03:00
var c = obj . parent . decodeCookie ( req . query . auth , obj . parent . loginCookieEncryptionKey ) ;
2021-01-26 22:46:22 +03:00
if ( c == null ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-11-04 05:44:43 +03:00
// Download tools using a cookie
if ( c . download == req . query . meshaction ) {
if ( req . query . meshaction == 'winrouter' ) {
2022-05-16 23:48:15 +03:00
var p = null ;
if ( obj . meshToolsBinaries [ 'MeshCentralRouter' ] ) { p = obj . meshToolsBinaries [ 'MeshCentralRouter' ] . path ; }
if ( ( p == null ) || ( ! obj . fs . existsSync ( p ) ) ) { p = obj . path . join ( _ _dirname , 'agents' , 'MeshCentralRouter.exe' ) ; }
2020-11-04 05:44:43 +03:00
if ( obj . fs . existsSync ( p ) ) {
setContentDispositionHeader ( res , 'application/octet-stream' , 'MeshCentralRouter.exe' , null , 'MeshCentralRouter.exe' ) ;
2021-01-26 22:46:22 +03:00
try { res . sendFile ( p ) ; } catch ( ex ) { }
} else { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } }
return ;
2020-11-04 05:44:43 +03:00
} else if ( req . query . meshaction == 'winassistant' ) {
2022-05-16 23:48:15 +03:00
var p = null ;
if ( obj . meshToolsBinaries [ 'MeshCentralAssistant' ] ) { p = obj . meshToolsBinaries [ 'MeshCentralAssistant' ] . path ; }
if ( ( p == null ) || ( ! obj . fs . existsSync ( p ) ) ) { p = obj . path . join ( _ _dirname , 'agents' , 'MeshCentralAssistant.exe' ) ; }
2020-11-04 05:44:43 +03:00
if ( obj . fs . existsSync ( p ) ) {
setContentDispositionHeader ( res , 'application/octet-stream' , 'MeshCentralAssistant.exe' , null , 'MeshCentralAssistant.exe' ) ;
2021-01-26 22:46:22 +03:00
try { res . sendFile ( p ) ; } catch ( ex ) { }
} else { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } }
return ;
2020-12-26 22:58:25 +03:00
} else if ( req . query . meshaction == 'macrouter' ) {
2022-05-16 23:48:15 +03:00
var p = null ;
if ( obj . meshToolsBinaries [ 'MeshCentralRouterMacOS' ] ) { p = obj . meshToolsBinaries [ 'MeshCentralRouterMacOS' ] . path ; }
if ( ( p == null ) || ( ! obj . fs . existsSync ( p ) ) ) { p = obj . path . join ( _ _dirname , 'agents' , 'MeshCentralRouter.dmg' ) ; }
2020-12-26 22:58:25 +03:00
if ( obj . fs . existsSync ( p ) ) {
setContentDispositionHeader ( res , 'application/octet-stream' , 'MeshCentralRouter.dmg' , null , 'MeshCentralRouter.dmg' ) ;
2021-01-26 22:46:22 +03:00
try { res . sendFile ( p ) ; } catch ( ex ) { }
} else { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } }
return ;
2020-11-04 05:44:43 +03:00
}
return ;
}
// Check if the cookie authenticates a user
2021-01-26 22:46:22 +03:00
if ( c . userid == null ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-11-03 08:41:58 +03:00
user = obj . users [ c . userid ] ;
2021-01-26 22:46:22 +03:00
if ( user == null ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-11-03 08:41:58 +03:00
}
2017-12-16 00:43:04 +03:00
if ( ( req . query . meshaction == 'route' ) && ( req . query . nodeid != null ) ) {
2021-09-03 03:52:37 +03:00
var nodeIdSplit = req . query . nodeid . split ( '/' ) ;
if ( ( nodeIdSplit [ 0 ] != 'node' ) || ( nodeIdSplit [ 1 ] != domain . id ) ) { try { res . sendStatus ( 401 ) ; } catch ( ex ) { } return ; }
2017-12-16 00:43:04 +03:00
obj . db . Get ( req . query . nodeid , function ( err , nodes ) {
2021-09-03 03:52:37 +03:00
if ( ( err != null ) || ( nodes . length != 1 ) ) { try { res . sendStatus ( 401 ) ; } catch ( ex ) { } return ; }
2017-12-16 00:43:04 +03:00
var node = nodes [ 0 ] ;
2019-04-10 21:54:55 +03:00
2017-12-16 00:43:04 +03:00
// Create the meshaction.txt file for meshcmd.exe
var meshaction = {
action : req . query . meshaction ,
localPort : 1234 ,
remoteName : node . name ,
remoteNodeId : node . _id ,
2020-04-04 02:06:38 +03:00
remoteTarget : null ,
2017-12-16 00:43:04 +03:00
remotePort : 3389 ,
2020-04-07 06:25:52 +03:00
username : '' ,
2017-12-16 00:43:04 +03:00
password : '' ,
serverId : obj . agentCertificateHashHex . toUpperCase ( ) , // SHA384 of server HTTPS public key
2019-04-10 21:54:55 +03:00
serverHttpsHash : Buffer . from ( obj . webCertificateHashs [ domain . id ] , 'binary' ) . toString ( 'hex' ) . toUpperCase ( ) , // SHA384 of server HTTPS certificate
2017-12-16 00:43:04 +03:00
debugLevel : 0
2018-08-30 22:05:23 +03:00
} ;
2017-12-16 00:43:04 +03:00
if ( user != null ) { meshaction . username = user . name ; }
2020-09-18 21:40:08 +03:00
if ( req . query . key != null ) { meshaction . loginKey = req . query . key ; }
2018-03-09 04:58:22 +03:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2024-10-08 10:51:16 +03:00
if ( obj . args . lanonly != true ) { meshaction . serverUrl = 'wss://' + obj . getWebServerName ( domain , req ) + ':' + httpsPort + '/' + ( ( domain . id == '' ) ? '' : ( domain . id + '/' ) ) + 'meshrelay.ashx' ; }
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , 'meshaction.txt' , null , 'meshaction.txt' ) ;
2017-12-16 00:43:04 +03:00
res . send ( JSON . stringify ( meshaction , null , ' ' ) ) ;
2021-01-26 22:46:22 +03:00
return ;
2017-12-16 00:43:04 +03:00
} ) ;
2020-03-18 04:15:54 +03:00
} else if ( req . query . meshaction == 'generic' ) {
2017-11-01 02:19:58 +03:00
var meshaction = {
2020-04-04 02:06:38 +03:00
username : user . name ,
2017-11-01 02:19:58 +03:00
password : '' ,
serverId : obj . agentCertificateHashHex . toUpperCase ( ) , // SHA384 of server HTTPS public key
2019-04-10 21:54:55 +03:00
serverHttpsHash : Buffer . from ( obj . webCertificateHashs [ domain . id ] , 'binary' ) . toString ( 'hex' ) . toUpperCase ( ) , // SHA384 of server HTTPS certificate
2017-11-01 02:19:58 +03:00
debugLevel : 0
2018-08-30 22:05:23 +03:00
} ;
2017-11-01 02:19:58 +03:00
if ( user != null ) { meshaction . username = user . name ; }
2020-09-18 21:40:08 +03:00
if ( req . query . key != null ) { meshaction . loginKey = req . query . key ; }
2018-03-09 04:58:22 +03:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2022-08-25 00:10:40 +03:00
if ( obj . args . lanonly != true ) { meshaction . serverUrl = 'wss://' + obj . getWebServerName ( domain , req ) + ':' + httpsPort + '/' + ( ( domain . id == '' ) ? '' : ( '/' + domain . id ) ) + 'meshrelay.ashx' ; }
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , 'meshaction.txt' , null , 'meshaction.txt' ) ;
2017-11-01 02:19:58 +03:00
res . send ( JSON . stringify ( meshaction , null , ' ' ) ) ;
2021-01-26 22:46:22 +03:00
return ;
2019-04-24 00:15:48 +03:00
} else if ( req . query . meshaction == 'winrouter' ) {
2022-05-16 23:48:15 +03:00
var p = null ;
if ( parent . meshToolsBinaries [ 'MeshCentralRouter' ] ) { p = parent . meshToolsBinaries [ 'MeshCentralRouter' ] . path ; }
if ( ( p == null ) || ! obj . fs . existsSync ( p ) ) { p = obj . path . join ( _ _dirname , 'agents' , 'MeshCentralRouter.exe' ) ; }
2019-04-24 00:15:48 +03:00
if ( obj . fs . existsSync ( p ) ) {
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , 'MeshCentralRouter.exe' , null , 'MeshCentralRouter.exe' ) ;
2021-01-26 22:46:22 +03:00
try { res . sendFile ( p ) ; } catch ( ex ) { }
} else { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } }
return ;
2020-11-03 08:41:58 +03:00
} else if ( req . query . meshaction == 'winassistant' ) {
2022-05-16 23:48:15 +03:00
var p = null ;
if ( parent . meshToolsBinaries [ 'MeshCentralAssistant' ] ) { p = parent . meshToolsBinaries [ 'MeshCentralAssistant' ] . path ; }
if ( ( p == null ) || ! obj . fs . existsSync ( p ) ) { p = obj . path . join ( _ _dirname , 'agents' , 'MeshCentralAssistant.exe' ) ; }
2020-11-03 08:41:58 +03:00
if ( obj . fs . existsSync ( p ) ) {
setContentDispositionHeader ( res , 'application/octet-stream' , 'MeshCentralAssistant.exe' , null , 'MeshCentralAssistant.exe' ) ;
2021-01-26 22:46:22 +03:00
try { res . sendFile ( p ) ; } catch ( ex ) { }
} else { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } }
return ;
2020-12-26 22:58:25 +03:00
} else if ( req . query . meshaction == 'macrouter' ) {
2022-05-16 23:48:15 +03:00
var p = null ;
if ( parent . meshToolsBinaries [ 'MeshCentralRouterMacOS' ] ) { p = parent . meshToolsBinaries [ 'MeshCentralRouterMacOS' ] . path ; }
if ( ( p == null ) || ! obj . fs . existsSync ( p ) ) { p = obj . path . join ( _ _dirname , 'agents' , 'MeshCentralRouter.dmg' ) ; }
2020-12-26 22:58:25 +03:00
if ( obj . fs . existsSync ( p ) ) {
setContentDispositionHeader ( res , 'application/octet-stream' , 'MeshCentralRouter.dmg' , null , 'MeshCentralRouter.dmg' ) ;
2021-01-26 22:46:22 +03:00
try { res . sendFile ( p ) ; } catch ( ex ) { }
} else { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } }
return ;
2017-12-16 00:43:04 +03:00
} else {
2021-01-26 22:46:22 +03:00
try { res . sendStatus ( 401 ) ; } catch ( ex ) { }
return ;
2017-12-16 00:43:04 +03:00
}
2017-08-28 19:27:45 +03:00
} else {
2020-03-26 22:06:34 +03:00
domain = checkUserIpAddress ( req , res ) ; // Recheck the domain to apply user IP filtering.
if ( domain == null ) return ;
2021-01-26 22:46:22 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; } // Check 3FA URL key
if ( ( req . session == null ) || ( req . session . userid == null ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-09-18 01:39:33 +03:00
var user = null , coreDumpsAllowed = false ;
if ( typeof req . session . userid == 'string' ) { user = obj . users [ req . session . userid ] ; }
2021-01-26 22:46:22 +03:00
if ( user == null ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2020-09-18 01:39:33 +03:00
// Check if this user has access to agent core dumps
if ( ( obj . parent . config . settings . agentcoredump === true ) && ( ( user . siteadmin == 0xFFFFFFFF ) || ( ( Array . isArray ( obj . parent . config . settings . agentcoredumpusers ) ) && ( obj . parent . config . settings . agentcoredumpusers . indexOf ( user . _id ) >= 0 ) ) ) ) {
coreDumpsAllowed = true ;
if ( ( req . query . dldump != null ) && obj . common . IsFilenameValid ( req . query . dldump ) ) {
// Download a dump file
var dumpFile = obj . path . join ( parent . datapath , '..' , 'meshcentral-coredumps' , req . query . dldump ) ;
if ( obj . fs . existsSync ( dumpFile ) ) {
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , req . query . dldump , null , 'file.bin' ) ;
2020-09-18 01:39:33 +03:00
res . sendFile ( dumpFile ) ; return ;
} else {
2021-01-26 22:46:22 +03:00
try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ;
2020-09-18 01:39:33 +03:00
}
}
if ( ( req . query . deldump != null ) && obj . common . IsFilenameValid ( req . query . deldump ) ) {
// Delete a dump file
try { obj . fs . unlinkSync ( obj . path . join ( parent . datapath , '..' , 'meshcentral-coredumps' , req . query . deldump ) ) ; } catch ( ex ) { console . log ( ex ) ; }
}
if ( ( req . query . dumps != null ) || ( req . query . deldump != null ) ) {
// Send list of agent core dumps
var response = '<html><head><title>Mesh Agents Core Dumps</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body style=overflow:auto><table>' ;
response += '<tr style="background-color:lightgray"><th>ID</th><th>Upload Date</th><th>Description</th><th>Current</th><th>Dump</th><th>Size</th><th>Agent</th><th>Agent SHA384</th><th>NodeID</th><th></th></tr>' ;
var coreDumpPath = obj . path . join ( parent . datapath , '..' , 'meshcentral-coredumps' ) ;
if ( obj . fs . existsSync ( coreDumpPath ) ) {
var files = obj . fs . readdirSync ( coreDumpPath ) ;
var coredumps = [ ] ;
for ( var i in files ) {
var file = files [ i ] ;
if ( file . endsWith ( '.dmp' ) ) {
var fileSplit = file . substring ( 0 , file . length - 4 ) . split ( '-' ) ;
if ( fileSplit . length == 3 ) {
var agentid = parseInt ( fileSplit [ 0 ] ) ;
if ( ( isNaN ( agentid ) == false ) && ( obj . parent . meshAgentBinaries [ agentid ] != null ) ) {
var agentinfo = obj . parent . meshAgentBinaries [ agentid ] ;
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ agentid ] ) { argentInfo = domain . meshAgentBinaries [ agentid ] ; }
2020-09-18 01:39:33 +03:00
var filestats = obj . fs . statSync ( obj . path . join ( parent . datapath , '..' , 'meshcentral-coredumps' , file ) ) ;
coredumps . push ( {
fileSplit : fileSplit ,
agentinfo : agentinfo ,
filestats : filestats ,
currentAgent : agentinfo . hashhex . startsWith ( fileSplit [ 1 ] . toLowerCase ( ) ) ,
2024-09-27 07:09:34 +03:00
downloadUrl : req . originalUrl . split ( '?' ) [ 0 ] + '?dldump=' + file + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) ,
deleteUrl : req . originalUrl . split ( '?' ) [ 0 ] + '?deldump=' + file + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) ,
agentUrl : req . originalUrl . split ( '?' ) [ 0 ] + '?id=' + agentinfo . id + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) ,
2020-09-18 01:39:33 +03:00
time : new Date ( filestats . ctime )
} ) ;
}
}
}
}
coredumps . sort ( function ( a , b ) { if ( a . time > b . time ) return - 1 ; if ( a . time < b . time ) return 1 ; return 0 ; } ) ;
for ( var i in coredumps ) {
var d = coredumps [ i ] ;
response += '<tr><td>' + d . agentinfo . id + '</td><td>' + d . time . toDateString ( ) . split ( ' ' ) . join ( ' ' ) + '</td><td>' + d . agentinfo . desc . split ( ' ' ) . join ( ' ' ) + '</td>' ;
response += '<td style=text-align:center>' + d . currentAgent + '</td><td><a download href="' + d . downloadUrl + '">Download</a></td><td style=text-align:right>' + d . filestats . size + '</td>' ;
if ( d . currentAgent ) { response += '<td><a download href="' + d . agentUrl + '">Download</a></td>' ; } else { response += '<td></td>' ; }
response += '<td>' + d . fileSplit [ 1 ] . toLowerCase ( ) + '</td><td>' + d . fileSplit [ 2 ] + '</td><td><a href="' + d . deleteUrl + '">Delete</a></td></tr>' ;
}
}
2024-09-27 07:09:34 +03:00
response += '</table><a href="' + req . originalUrl . split ( '?' ) [ 0 ] + ( req . query . key ? ( '?key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">Mesh Agents</a></body></html>' ;
2020-09-18 01:39:33 +03:00
res . send ( response ) ;
return ;
}
}
2020-08-02 07:12:07 +03:00
2021-01-24 01:32:43 +03:00
if ( req . query . cores != null ) {
// Send list of agent cores
var response = '<html><head><title>Mesh Agents Cores</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body style=overflow:auto><table>' ;
response += '<tr style="background-color:lightgray"><th>Name</th><th>Size</th><th>Comp</th><th>Decompressed Hash SHA384</th></tr>' ;
for ( var i in parent . defaultMeshCores ) {
2024-09-27 07:09:34 +03:00
response += '<tr><td>' + i . split ( ' ' ) . join ( ' ' ) + '</td><td style="text-align:right"><a download href="/meshagents?dlcore=' + i + '">' + parent . defaultMeshCores [ i ] . length + ( req . query . key ? ( '?key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '</a></td><td style="text-align:right"><a download href="/meshagents?dlccore=' + i + ( req . query . key ? ( '?key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">' + parent . defaultMeshCoresDeflate [ i ] . length + '</a></td><td>' + Buffer . from ( parent . defaultMeshCoresHash [ i ] , 'binary' ) . toString ( 'hex' ) + '</td></tr>' ;
2021-01-24 01:32:43 +03:00
}
2024-09-27 07:09:34 +03:00
response += '</table><a href="' + req . originalUrl . split ( '?' ) [ 0 ] + ( req . query . key ? ( '?key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">Mesh Agents</a></body></html>' ;
2021-01-24 01:32:43 +03:00
res . send ( response ) ;
return ;
}
if ( req . query . dlcore != null ) {
// Download mesh core
var bin = parent . defaultMeshCores [ req . query . dlcore ] ;
2021-01-26 23:40:52 +03:00
if ( ( bin == null ) || ( bin . length < 5 ) ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2024-09-27 07:09:34 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , encodeURIComponent ( req . query . dlcore ) + '.js' , null , 'meshcore.js' ) ;
2022-01-26 03:46:43 +03:00
res . send ( bin . slice ( 4 ) ) ;
2021-01-24 01:32:43 +03:00
return ;
}
if ( req . query . dlccore != null ) {
// Download compressed mesh core
var bin = parent . defaultMeshCoresDeflate [ req . query . dlccore ] ;
2021-01-26 22:46:22 +03:00
if ( bin == null ) { try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
2021-01-24 01:32:43 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , req . query . dlccore + '.js.deflate' , null , 'meshcore.js.deflate' ) ;
res . send ( bin ) ;
return ;
}
2017-08-28 19:27:45 +03:00
// Send a list of available mesh agents
2020-09-18 01:39:33 +03:00
var response = '<html><head><title>Mesh Agents</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body style=overflow:auto><table>' ;
2017-12-13 03:04:54 +03:00
response += '<tr style="background-color:lightgray"><th>ID</th><th>Description</th><th>Link</th><th>Size</th><th>SHA384</th><th>MeshCmd</th></tr>' ;
2020-09-18 01:39:33 +03:00
var originalUrl = req . originalUrl . split ( '?' ) [ 0 ] ;
2017-08-28 19:27:45 +03:00
for ( var agentid in obj . parent . meshAgentBinaries ) {
2020-12-08 07:39:14 +03:00
if ( ( agentid >= 10000 ) && ( agentid != 10005 ) ) continue ;
2017-08-28 19:27:45 +03:00
var agentinfo = obj . parent . meshAgentBinaries [ agentid ] ;
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ agentid ] ) { argentInfo = domain . meshAgentBinaries [ agentid ] ; }
2020-09-18 01:39:33 +03:00
response += '<tr><td>' + agentinfo . id + '</td><td>' + agentinfo . desc . split ( ' ' ) . join ( ' ' ) + '</td>' ;
2024-09-27 07:09:34 +03:00
response += '<td><a download href="' + originalUrl + '?id=' + agentinfo . id + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">' + agentinfo . rname + '</a>' ;
2020-10-26 11:22:46 +03:00
if ( ( user . siteadmin == 0xFFFFFFFF ) || ( ( Array . isArray ( obj . parent . config . settings . agentcoredumpusers ) ) && ( obj . parent . config . settings . agentcoredumpusers . indexOf ( user . _id ) >= 0 ) ) ) {
2024-09-27 07:09:34 +03:00
if ( ( agentid == 3 ) || ( agentid == 4 ) ) { response += ', <a download href="' + originalUrl + '?id=' + agentinfo . id + '&pdb=1' + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">PDB</a>' ; }
2020-10-26 11:22:46 +03:00
}
2024-09-27 07:09:34 +03:00
if ( agentinfo . zdata != null ) { response += ', <a download href="' + originalUrl + '?id=' + agentinfo . id + '&zip=1' + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">ZIP</a>' ; }
2020-10-26 11:22:46 +03:00
response += '</td>' ;
2020-01-28 01:03:36 +03:00
response += '<td>' + agentinfo . size + '</td><td>' + agentinfo . hashhex + '</td>' ;
2024-09-27 07:09:34 +03:00
response += '<td><a download href="' + originalUrl + '?meshcmd=' + agentinfo . id + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">' + agentinfo . rname . replace ( 'agent' , 'cmd' ) + '</a></td></tr>' ;
2017-08-28 19:27:45 +03:00
}
2020-09-18 01:39:33 +03:00
response += '</table>' ;
2024-09-27 07:09:34 +03:00
response += '<a href="' + originalUrl + '?cores=1' + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">MeshCores</a> ' ;
if ( coreDumpsAllowed ) { response += '<a href="' + originalUrl + '?dumps=1' + ( req . query . key ? ( '&key=' + encodeURIComponent ( req . query . key ) ) : '' ) + '">MeshAgent Crash Dumps</a>' ; }
2020-09-18 01:39:33 +03:00
response += '</body></html>' ;
2017-08-28 19:27:45 +03:00
res . send ( response ) ;
2021-01-26 22:46:22 +03:00
return ;
2017-08-28 19:27:45 +03:00
}
2018-08-30 22:05:23 +03:00
} ;
2018-01-03 03:52:49 +03:00
// Get the web server hostname. This may change if using a domain with a DNS name.
2022-08-25 00:10:40 +03:00
obj . getWebServerName = function ( domain , req ) {
2018-01-03 03:52:49 +03:00
if ( domain . dns != null ) return domain . dns ;
2022-08-25 00:10:40 +03:00
if ( ( obj . certificates . CommonName == 'un-configured' ) && ( req != null ) && ( req . headers != null ) && ( typeof req . headers . host == 'string' ) ) { return req . headers . host . split ( ':' ) [ 0 ] ; }
2018-01-03 03:52:49 +03:00
return obj . certificates . CommonName ;
}
2024-02-17 22:22:38 +03:00
// Return true if this is an allowed HTTP request origin hostname.
obj . CheckWebServerOriginName = function ( domain , req ) {
if ( domain . allowedorigin === true ) return true ; // Ignore origin
if ( typeof req . headers . origin != 'string' ) return true ; // No origin in the header, this is a desktop app
const originUrl = require ( 'url' ) . parse ( req . headers . origin , true ) ;
if ( typeof originUrl . hostname != 'string' ) return false ; // Origin hostname is not valid
if ( Array . isArray ( domain . allowedorigin ) ) return ( domain . allowedorigin . indexOf ( originUrl . hostname ) >= 0 ) ; // Check if this is an allowed origin from an explicit list
if ( obj . isTrustedCert ( domain ) === false ) return true ; // This server does not have a trusted certificate.
if ( domain . dns != null ) return ( domain . dns == originUrl . hostname ) ; // Match the domain DNS
return ( obj . certificates . CommonName == originUrl . hostname ) ; // Match the default server name
}
2018-09-08 03:09:02 +03:00
// Create a OSX mesh agent installer
obj . handleMeshOsxAgentRequest = function ( req , res ) {
2020-03-26 22:06:34 +03:00
const domain = getDomain ( req , res ) ;
if ( domain == null ) { parent . debug ( 'web' , 'handleRootRequest: invalid domain.' ) ; try { res . sendStatus ( 404 ) ; } catch ( ex ) { } return ; }
if ( req . query . id == null ) { res . sendStatus ( 404 ) ; return ; }
2018-09-08 03:09:02 +03:00
// If required, check if this user has rights to do this
2019-07-11 00:27:38 +03:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) && ( req . session . userid == null ) ) { res . sendStatus ( 401 ) ; return ; }
2018-09-08 03:09:02 +03:00
// Send a specific mesh agent back
var argentInfo = obj . parent . meshAgentBinaries [ req . query . id ] ;
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ req . query . id ] ) { argentInfo = domain . meshAgentBinaries [ req . query . id ] ; }
2018-09-22 02:34:35 +03:00
if ( ( argentInfo == null ) || ( req . query . meshid == null ) ) { res . sendStatus ( 404 ) ; return ; }
2018-09-08 03:09:02 +03:00
2020-06-18 23:56:04 +03:00
// Check if the meshid is a time limited, encrypted cookie
var meshcookie = obj . parent . decodeCookie ( req . query . meshid , obj . parent . invitationLinkEncryptionKey ) ;
if ( ( meshcookie != null ) && ( meshcookie . m != null ) ) { req . query . meshid = meshcookie . m ; }
2018-09-08 03:09:02 +03:00
// We are going to embed the .msh file into the Windows executable (signed or not).
// First, fetch the mesh object to build the .msh file
var mesh = obj . meshes [ 'mesh/' + domain . id + '/' + req . query . meshid ] ;
if ( mesh == null ) { res . sendStatus ( 401 ) ; return ; }
// If required, check if this user has rights to do this
2019-07-11 00:27:38 +03:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) ) {
2019-12-28 02:18:43 +03:00
if ( ( domain . id != mesh . domain ) || ( ( obj . GetMeshRights ( req . session . userid , mesh ) & 1 ) == 0 ) ) { res . sendStatus ( 401 ) ; return ; }
2018-09-08 03:09:02 +03:00
}
2019-01-03 05:03:34 +03:00
var meshidhex = Buffer . from ( req . query . meshid . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
var serveridhex = Buffer . from ( obj . agentCertificateHashBase64 . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
2018-09-08 03:09:02 +03:00
2020-05-10 23:45:10 +03:00
// Get the agent connection server name
2022-08-25 00:10:40 +03:00
var serverName = obj . getWebServerName ( domain , req ) ;
2020-05-10 23:45:10 +03:00
if ( typeof obj . args . agentaliasdns == 'string' ) { serverName = obj . args . agentaliasdns ; }
2018-09-08 03:09:02 +03:00
// Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
2019-12-27 09:53:01 +03:00
if ( xdomain != '' ) xdomain += '/' ;
2020-11-12 04:55:54 +03:00
var meshsettings = '\r\nMeshName=' + mesh . name + '\r\nMeshType=' + mesh . mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n' ;
2019-03-02 04:13:11 +03:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2020-05-22 20:10:06 +03:00
if ( obj . args . agentport != null ) { httpsPort = obj . args . agentport ; } // If an agent only port is enabled, use that.
2020-05-04 08:37:51 +03:00
if ( obj . args . agentaliasport != null ) { httpsPort = obj . args . agentaliasport ; } // If an agent alias port is specified, use that.
2020-11-05 13:27:39 +03:00
if ( obj . args . lanonly != true ) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n' ; } else {
2020-07-15 02:09:08 +03:00
meshsettings += 'MeshServer=local\r\n' ;
2020-07-15 02:48:42 +03:00
if ( ( obj . args . localdiscovery != null ) && ( typeof obj . args . localdiscovery . key == 'string' ) && ( obj . args . localdiscovery . key . length > 0 ) ) { meshsettings += 'DiscoveryKey=' + obj . args . localdiscovery . key + '\r\n' ; }
2020-07-15 02:09:08 +03:00
}
2024-09-27 07:09:34 +03:00
if ( ( req . query . tag != null ) && ( typeof req . query . tag == 'string' ) && ( obj . common . isAlphaNumeric ( req . query . tag ) == true ) ) { meshsettings += 'Tag=' + encodeURIComponent ( req . query . tag ) + '\r\n' ; }
2020-07-06 22:46:38 +03:00
if ( ( req . query . installflags != null ) && ( req . query . installflags != 0 ) && ( parseInt ( req . query . installflags ) == req . query . installflags ) ) { meshsettings += 'InstallFlags=' + parseInt ( req . query . installflags ) + '\r\n' ; }
2019-12-27 09:53:01 +03:00
if ( ( domain . agentnoproxy === true ) || ( obj . args . lanonly == true ) ) { meshsettings += 'ignoreProxyFile=1\r\n' ; }
if ( obj . args . agentconfig ) { for ( var i in obj . args . agentconfig ) { meshsettings += obj . args . agentconfig [ i ] + '\r\n' ; } }
if ( domain . agentconfig ) { for ( var i in domain . agentconfig ) { meshsettings += domain . agentconfig [ i ] + '\r\n' ; } }
2020-11-12 03:28:56 +03:00
if ( domain . agentcustomization != null ) { // Add agent customization
if ( domain . agentcustomization . displayname != null ) { meshsettings += 'displayName=' + domain . agentcustomization . displayname + '\r\n' ; }
if ( domain . agentcustomization . description != null ) { meshsettings += 'description=' + domain . agentcustomization . description + '\r\n' ; }
if ( domain . agentcustomization . companyname != null ) { meshsettings += 'companyName=' + domain . agentcustomization . companyname + '\r\n' ; }
if ( domain . agentcustomization . servicename != null ) { meshsettings += 'meshServiceName=' + domain . agentcustomization . servicename + '\r\n' ; }
2020-12-23 11:53:10 +03:00
if ( domain . agentcustomization . filename != null ) { meshsettings += 'fileName=' + domain . agentcustomization . filename + '\r\n' ; }
2022-01-19 00:34:10 +03:00
if ( domain . agentcustomization . image != null ) { meshsettings += 'image=' + domain . agentcustomization . image + '\r\n' ; }
2022-02-22 11:21:39 +03:00
if ( domain . agentcustomization . foregroundcolor != null ) { meshsettings += checkAgentColorString ( 'foreground=' , domain . agentcustomization . foregroundcolor ) ; }
if ( domain . agentcustomization . backgroundcolor != null ) { meshsettings += checkAgentColorString ( 'background=' , domain . agentcustomization . backgroundcolor ) ; }
2020-11-12 03:28:56 +03:00
}
2022-02-05 02:02:09 +03:00
if ( domain . agentTranslations != null ) { meshsettings += 'translation=' + domain . agentTranslations + '\r\n' ; }
2018-09-08 03:09:02 +03:00
// Setup the response output
var archive = require ( 'archiver' ) ( 'zip' , { level : 5 } ) ; // Sets the compression method.
archive . on ( 'error' , function ( err ) { throw err ; } ) ;
2020-10-23 23:49:38 +03:00
2024-03-04 14:17:43 +03:00
// Customize the mesh agent file name
2024-03-04 19:34:41 +03:00
var meshfilename = 'MeshAgent-' + mesh . name + '.zip' ;
var meshexecutablename = 'meshagent' ;
2024-03-07 13:19:31 +03:00
var meshmpkgname = 'MeshAgent.mpkg' ;
2024-03-04 14:17:43 +03:00
if ( ( domain . agentcustomization != null ) && ( typeof domain . agentcustomization . filename == 'string' ) ) {
2024-03-04 19:34:41 +03:00
meshfilename = meshfilename . split ( 'MeshAgent' ) . join ( domain . agentcustomization . filename ) ;
meshexecutablename = meshexecutablename . split ( 'meshagent' ) . join ( domain . agentcustomization . filename ) ;
2024-03-07 13:19:31 +03:00
meshmpkgname = meshmpkgname . split ( 'MeshAgent' ) . join ( domain . agentcustomization . filename ) ;
2024-03-04 14:17:43 +03:00
}
2024-03-04 15:50:22 +03:00
// Customise the mesh agent display name
var meshdisplayname = 'Mesh Agent' ;
if ( ( domain . agentcustomization != null ) && ( typeof domain . agentcustomization . displayname == 'string' ) ) {
meshdisplayname = meshdisplayname . split ( 'Mesh Agent' ) . join ( domain . agentcustomization . displayname ) ;
}
2024-03-04 19:34:41 +03:00
// Customise the mesh agent service name
var meshservicename = 'meshagent' ;
if ( ( domain . agentcustomization != null ) && ( typeof domain . agentcustomization . servicename == 'string' ) ) {
meshservicename = meshservicename . split ( 'meshagent' ) . join ( domain . agentcustomization . servicename ) ;
}
// Customise the mesh agent company name
var meshcompanyname = 'meshagent' ;
if ( ( domain . agentcustomization != null ) && ( typeof domain . agentcustomization . companyname == 'string' ) ) {
meshcompanyname = meshcompanyname . split ( 'meshagent' ) . join ( domain . agentcustomization . companyname ) ;
}
2020-10-23 23:49:38 +03:00
// Set the agent download including the mesh name.
2024-03-04 14:17:43 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , meshfilename , null , 'MeshAgent.zip' ) ;
2018-09-08 03:09:02 +03:00
archive . pipe ( res ) ;
// Opens the "MeshAgentOSXPackager.zip"
2019-12-27 09:53:01 +03:00
var yauzl = require ( 'yauzl' ) ;
2018-09-08 03:09:02 +03:00
yauzl . open ( obj . path . join ( _ _dirname , 'agents' , 'MeshAgentOSXPackager.zip' ) , { lazyEntries : true } , function ( err , zipfile ) {
if ( err ) { res . sendStatus ( 500 ) ; return ; }
zipfile . readEntry ( ) ;
2019-12-27 09:53:01 +03:00
zipfile . on ( 'entry' , function ( entry ) {
2018-09-08 03:09:02 +03:00
if ( /\/$/ . test ( entry . fileName ) ) {
// Skip all folder entries
zipfile . readEntry ( ) ;
} else {
2018-11-15 02:44:28 +03:00
if ( entry . fileName == 'MeshAgent.mpkg/Contents/distribution.dist' ) {
2018-09-08 03:09:02 +03:00
// This is a special file entry, we need to fix it.
zipfile . openReadStream ( entry , function ( err , readStream ) {
2019-12-27 09:53:01 +03:00
readStream . on ( 'data' , function ( data ) { if ( readStream . xxdata ) { readStream . xxdata += data ; } else { readStream . xxdata = data ; } } ) ;
readStream . on ( 'end' , function ( ) {
2018-11-15 02:44:28 +03:00
var meshname = mesh . name . split ( ']' ) . join ( '' ) . split ( '[' ) . join ( '' ) ; // We can't have ']]' in the string since it will terminate the CDATA.
2024-03-04 15:50:22 +03:00
var welcomemsg = 'Welcome to the MeshCentral agent for MacOS\n\nThis installer will install the mesh agent for "' + meshname + '" and allow the administrator to remotely monitor and control this computer over the internet. For more information, go to https://meshcentral.com.\n\nThis software is provided under Apache 2.0 license.\n' ;
2018-09-08 03:09:02 +03:00
var installsize = Math . floor ( ( argentInfo . size + meshsettings . length ) / 1024 ) ;
2024-03-07 13:19:31 +03:00
archive . append ( readStream . xxdata . toString ( ) . split ( '###DISPLAYNAME###' ) . join ( meshdisplayname ) . split ( '###WELCOMEMSG###' ) . join ( welcomemsg ) . split ( '###INSTALLSIZE###' ) . join ( installsize ) , { name : entry . fileName . replace ( 'MeshAgent.mpkg' , meshmpkgname ) } ) ;
2018-09-08 03:09:02 +03:00
zipfile . readEntry ( ) ;
} ) ;
} ) ;
2024-03-10 10:46:01 +03:00
} else if ( entry . fileName == 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/meshagent_osx64_LaunchAgent.plist' ||
2024-03-04 19:34:41 +03:00
entry . fileName == 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/meshagent_osx64_LaunchDaemon.plist' ||
entry . fileName == 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/Info.plist' ||
entry . fileName == 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/Resources/postflight' ||
2024-03-10 10:46:01 +03:00
entry . fileName == 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/Resources/Postflight.sh' ||
2024-06-20 14:36:24 +03:00
entry . fileName == 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/Uninstall.command' ||
2024-03-04 19:34:41 +03:00
entry . fileName == 'MeshAgent.mpkg/Uninstall.command' ) {
// This is a special file entry, we need to fix it.
zipfile . openReadStream ( entry , function ( err , readStream ) {
readStream . on ( 'data' , function ( data ) { if ( readStream . xxdata ) { readStream . xxdata += data ; } else { readStream . xxdata = data ; } } ) ;
readStream . on ( 'end' , function ( ) {
2024-03-07 13:19:31 +03:00
var options = { name : entry . fileName . replace ( 'MeshAgent.mpkg' , meshmpkgname ) } ;
2024-03-04 19:34:41 +03:00
if ( entry . fileName . endsWith ( 'postflight' ) || entry . fileName . endsWith ( 'Uninstall.command' ) ) { options . mode = 493 ; }
archive . append ( readStream . xxdata . toString ( ) . split ( '###SERVICENAME###' ) . join ( meshservicename ) . split ( '###COMPANYNAME###' ) . join ( meshcompanyname ) . split ( '###EXECUTABLENAME###' ) . join ( meshexecutablename ) , options ) ;
zipfile . readEntry ( ) ;
} ) ;
} ) ;
2018-09-08 03:09:02 +03:00
} else {
// Normal file entry
zipfile . openReadStream ( entry , function ( err , readStream ) {
if ( err ) { throw err ; }
2024-03-07 13:19:31 +03:00
var options = { name : entry . fileName . replace ( 'MeshAgent.mpkg' , meshmpkgname ) } ;
2018-11-15 02:44:28 +03:00
if ( entry . fileName . endsWith ( 'postflight' ) || entry . fileName . endsWith ( 'Uninstall.command' ) ) { options . mode = 493 ; }
archive . append ( readStream , options ) ;
2018-09-08 03:09:02 +03:00
readStream . on ( 'end' , function ( ) { zipfile . readEntry ( ) ; } ) ;
} ) ;
}
}
} ) ;
2019-12-27 09:53:01 +03:00
zipfile . on ( 'end' , function ( ) {
2024-03-07 13:19:31 +03:00
archive . file ( argentInfo . path , { name : 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/meshagent_osx64.bin' . replace ( 'MeshAgent.mpkg' , meshmpkgname ) } ) ;
archive . append ( meshsettings , { name : 'MeshAgent.mpkg/Contents/Packages/internal.pkg/Contents/meshagent_osx64.msh' . replace ( 'MeshAgent.mpkg' , meshmpkgname ) } ) ;
2018-09-08 03:09:02 +03:00
archive . finalize ( ) ;
} ) ;
} ) ;
}
2020-08-02 07:12:07 +03:00
// Return a .msh file from a given request, id is the device group identifier or encrypted cookie with the identifier.
function getMshFromRequest ( req , res , domain ) {
2018-01-31 05:23:57 +03:00
// If required, check if this user has rights to do this
2020-08-02 07:12:07 +03:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) && ( req . session . userid == null ) ) { return null ; }
2018-01-31 05:23:57 +03:00
2020-06-18 23:56:04 +03:00
// Check if the meshid is a time limited, encrypted cookie
var meshcookie = obj . parent . decodeCookie ( req . query . id , obj . parent . invitationLinkEncryptionKey ) ;
if ( ( meshcookie != null ) && ( meshcookie . m != null ) ) { req . query . id = meshcookie . m ; }
2018-08-22 01:08:15 +03:00
// Fetch the mesh object
var mesh = obj . meshes [ 'mesh/' + domain . id + '/' + req . query . id ] ;
2020-08-02 07:12:07 +03:00
if ( mesh == null ) { return null ; }
2018-01-31 05:23:57 +03:00
2018-08-22 01:08:15 +03:00
// If needed, check if this user has rights to do this
2019-07-11 00:27:38 +03:00
if ( ( obj . parent . config . settings != null ) && ( ( obj . parent . config . settings . lockagentdownload == true ) || ( domain . lockagentdownload == true ) ) ) {
2020-08-02 07:12:07 +03:00
if ( ( domain . id != mesh . domain ) || ( ( obj . GetMeshRights ( req . session . userid , mesh ) & 1 ) == 0 ) ) { return null ; }
2018-08-22 01:08:15 +03:00
}
2017-10-16 03:36:06 +03:00
2019-01-03 05:03:34 +03:00
var meshidhex = Buffer . from ( req . query . id . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
var serveridhex = Buffer . from ( obj . agentCertificateHashBase64 . replace ( /\@/g , '+' ) . replace ( /\$/g , '/' ) , 'base64' ) . toString ( 'hex' ) . toUpperCase ( ) ;
2017-10-16 03:36:06 +03:00
2020-05-10 23:45:10 +03:00
// Get the agent connection server name
2022-08-25 00:10:40 +03:00
var serverName = obj . getWebServerName ( domain , req ) ;
2020-05-10 23:45:10 +03:00
if ( typeof obj . args . agentaliasdns == 'string' ) { serverName = obj . args . agentaliasdns ; }
2018-08-22 01:08:15 +03:00
// Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
2019-12-27 09:53:01 +03:00
if ( xdomain != '' ) xdomain += '/' ;
2020-11-12 04:55:54 +03:00
var meshsettings = '\r\nMeshName=' + mesh . name + '\r\nMeshType=' + mesh . mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n' ;
2018-08-22 01:08:15 +03:00
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
2020-05-22 20:10:06 +03:00
if ( obj . args . agentport != null ) { httpsPort = obj . args . agentport ; } // If an agent only port is enabled, use that.
2020-05-04 08:37:51 +03:00
if ( obj . args . agentaliasport != null ) { httpsPort = obj . args . agentaliasport ; } // If an agent alias port is specified, use that.
2020-11-05 13:27:39 +03:00
if ( obj . args . lanonly != true ) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n' ; } else {
2020-07-15 02:09:08 +03:00
meshsettings += 'MeshServer=local\r\n' ;
2020-07-15 02:48:42 +03:00
if ( ( obj . args . localdiscovery != null ) && ( typeof obj . args . localdiscovery . key == 'string' ) && ( obj . args . localdiscovery . key . length > 0 ) ) { meshsettings += 'DiscoveryKey=' + obj . args . localdiscovery . key + '\r\n' ; }
2020-07-15 02:09:08 +03:00
}
2024-09-27 07:09:34 +03:00
if ( ( req . query . tag != null ) && ( typeof req . query . tag == 'string' ) && ( obj . common . isAlphaNumeric ( req . query . tag ) == true ) ) { meshsettings += 'Tag=' + encodeURIComponent ( req . query . tag ) + '\r\n' ; }
2020-07-06 22:46:38 +03:00
if ( ( req . query . installflags != null ) && ( req . query . installflags != 0 ) && ( parseInt ( req . query . installflags ) == req . query . installflags ) ) { meshsettings += 'InstallFlags=' + parseInt ( req . query . installflags ) + '\r\n' ; }
2019-12-27 09:53:01 +03:00
if ( ( domain . agentnoproxy === true ) || ( obj . args . lanonly == true ) ) { meshsettings += 'ignoreProxyFile=1\r\n' ; }
if ( obj . args . agentconfig ) { for ( var i in obj . args . agentconfig ) { meshsettings += obj . args . agentconfig [ i ] + '\r\n' ; } }
if ( domain . agentconfig ) { for ( var i in domain . agentconfig ) { meshsettings += domain . agentconfig [ i ] + '\r\n' ; } }
2020-11-12 03:28:56 +03:00
if ( domain . agentcustomization != null ) { // Add agent customization
if ( domain . agentcustomization . displayname != null ) { meshsettings += 'displayName=' + domain . agentcustomization . displayname + '\r\n' ; }
if ( domain . agentcustomization . description != null ) { meshsettings += 'description=' + domain . agentcustomization . description + '\r\n' ; }
if ( domain . agentcustomization . companyname != null ) { meshsettings += 'companyName=' + domain . agentcustomization . companyname + '\r\n' ; }
if ( domain . agentcustomization . servicename != null ) { meshsettings += 'meshServiceName=' + domain . agentcustomization . servicename + '\r\n' ; }
2020-12-23 11:53:10 +03:00
if ( domain . agentcustomization . filename != null ) { meshsettings += 'fileName=' + domain . agentcustomization . filename + '\r\n' ; }
2022-01-19 00:34:10 +03:00
if ( domain . agentcustomization . image != null ) { meshsettings += 'image=' + domain . agentcustomization . image + '\r\n' ; }
2022-02-22 11:21:39 +03:00
if ( domain . agentcustomization . foregroundcolor != null ) { meshsettings += checkAgentColorString ( 'foreground=' , domain . agentcustomization . foregroundcolor ) ; }
if ( domain . agentcustomization . backgroundcolor != null ) { meshsettings += checkAgentColorString ( 'background=' , domain . agentcustomization . backgroundcolor ) ; }
2020-11-12 03:28:56 +03:00
}
2022-02-05 02:02:09 +03:00
if ( domain . agentTranslations != null ) { meshsettings += 'translation=' + domain . agentTranslations + '\r\n' ; }
2020-08-02 07:12:07 +03:00
return meshsettings ;
}
// Handle a request to download a mesh settings
obj . handleMeshSettingsRequest = function ( req , res ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { return ; }
//if ((domain.id !== '') || (!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
var meshsettings = getMshFromRequest ( req , res , domain ) ;
if ( meshsettings == null ) { res . sendStatus ( 401 ) ; return ; }
2017-08-28 19:27:45 +03:00
2020-12-20 04:21:42 +03:00
// Get the agent filename
var meshagentFilename = 'meshagent' ;
if ( ( domain . agentcustomization != null ) && ( typeof domain . agentcustomization . filename == 'string' ) ) { meshagentFilename = domain . agentcustomization . filename ; }
setContentDispositionHeader ( res , 'application/octet-stream' , meshagentFilename + '.msh' , null , 'meshagent.msh' ) ;
2018-08-22 01:08:15 +03:00
res . send ( meshsettings ) ;
2018-08-30 22:05:23 +03:00
} ;
2017-08-28 19:27:45 +03:00
2019-03-20 22:40:02 +03:00
// Handle a request for power events
obj . handleDevicePowerEvents = function ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2020-03-30 08:40:13 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { res . sendStatus ( 404 ) ; return ; } // Check 3FA URL key
2019-03-20 22:40:02 +03:00
if ( ( domain . id !== '' ) || ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) || ( req . query . id == null ) || ( typeof req . query . id != 'string' ) ) { res . sendStatus ( 401 ) ; return ; }
var x = req . query . id . split ( '/' ) ;
var user = obj . users [ req . session . userid ] ;
if ( ( x . length != 3 ) || ( x [ 0 ] != 'node' ) || ( x [ 1 ] != domain . id ) || ( user == null ) || ( user . links == null ) ) { res . sendStatus ( 401 ) ; return ; }
obj . db . Get ( req . query . id , function ( err , docs ) {
if ( docs . length != 1 ) {
res . sendStatus ( 401 ) ;
} else {
var node = docs [ 0 ] ;
// Check if we have right to this node
2020-03-30 08:40:13 +03:00
if ( obj . GetNodeRights ( user , node . meshid , node . _id ) == 0 ) { res . sendStatus ( 401 ) ; return ; }
2019-03-20 22:40:02 +03:00
2021-06-29 04:40:44 +03:00
// See how we will convert UTC time to local time
2021-06-17 00:23:58 +03:00
var localTimeOffset = 0 ;
2021-06-29 04:40:44 +03:00
var timeConversionSystem = 0 ;
if ( ( req . query . l != null ) && ( req . query . tz != null ) ) {
timeConversionSystem = 1 ;
} else if ( req . query . tf != null ) {
// Get local time offset (bad way)
timeConversionSystem = 2 ;
localTimeOffset = parseInt ( req . query . tf ) ;
if ( isNaN ( localTimeOffset ) ) { localTimeOffset = 0 ; }
}
2021-06-17 00:23:58 +03:00
2019-03-20 22:40:02 +03:00
// Get the list of power events and send them
2020-10-23 23:49:38 +03:00
setContentDispositionHeader ( res , 'application/octet-stream' , 'powerevents.csv' , null , 'powerevents.csv' ) ;
2019-03-20 22:40:02 +03:00
obj . db . getPowerTimeline ( node . _id , function ( err , docs ) {
2021-06-17 00:23:58 +03:00
var xevents = [ 'UTC Time, Local Time, State, Previous State' ] , prevState = 0 ;
2019-03-20 22:40:02 +03:00
for ( var i in docs ) {
if ( docs [ i ] . power != prevState ) {
2021-11-29 23:40:01 +03:00
var timedoc = docs [ i ] . time ;
if ( typeof timedoc == 'string' ) {
timedoc = new Date ( timedoc ) ;
}
2019-03-20 22:40:02 +03:00
prevState = docs [ i ] . power ;
2021-06-29 04:40:44 +03:00
var localTime = '' ;
if ( timeConversionSystem == 1 ) { // Good way
2021-11-29 23:40:01 +03:00
localTime = new Date ( timedoc . getTime ( ) ) . toLocaleString ( req . query . l , { timeZone : req . query . tz } )
2021-06-29 04:40:44 +03:00
} else if ( timeConversionSystem == 2 ) { // Bad way
2021-11-29 23:40:01 +03:00
localTime = new Date ( timedoc . getTime ( ) + ( localTimeOffset * 60000 ) ) . toISOString ( ) ;
2021-06-29 04:40:44 +03:00
localTime = localTime . substring ( 0 , localTime . length - 1 ) ;
}
2019-03-20 22:40:02 +03:00
if ( docs [ i ] . oldPower != null ) {
2021-11-29 23:40:01 +03:00
xevents . push ( '\"' + timedoc . toISOString ( ) + '\",\"' + localTime + '\",' + docs [ i ] . power + ',' + docs [ i ] . oldPower ) ;
2019-03-20 22:40:02 +03:00
} else {
2021-11-29 23:40:01 +03:00
xevents . push ( '\"' + timedoc . toISOString ( ) + '\",\"' + localTime + '\",' + docs [ i ] . power ) ;
2019-03-20 22:40:02 +03:00
}
}
}
res . send ( xevents . join ( '\r\n' ) ) ;
} ) ;
}
} ) ;
}
2019-11-26 01:12:43 +03:00
if ( parent . pluginHandler != null ) {
// Handle a plugin admin request
obj . handlePluginAdminReq = function ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:12:43 +03:00
if ( ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) ) { res . sendStatus ( 401 ) ; return ; }
var user = obj . users [ req . session . userid ] ;
if ( user == null ) { res . sendStatus ( 401 ) ; return ; }
parent . pluginHandler . handleAdminReq ( req , res , user , obj ) ;
}
obj . handlePluginAdminPostReq = function ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:12:43 +03:00
if ( ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) ) { res . sendStatus ( 401 ) ; return ; }
var user = obj . users [ req . session . userid ] ;
if ( user == null ) { res . sendStatus ( 401 ) ; return ; }
parent . pluginHandler . handleAdminPostReq ( req , res , user , obj ) ;
}
obj . handlePluginJS = function ( req , res ) {
const domain = checkUserIpAddress ( req , res ) ;
2020-03-26 22:06:34 +03:00
if ( domain == null ) { return ; }
2019-11-26 01:12:43 +03:00
if ( ( ! req . session ) || ( req . session == null ) || ( ! req . session . userid ) ) { res . sendStatus ( 401 ) ; return ; }
var user = obj . users [ req . session . userid ] ;
if ( user == null ) { res . sendStatus ( 401 ) ; return ; }
parent . pluginHandler . refreshJS ( req , res ) ;
}
2019-11-22 22:25:13 +03:00
}
2019-03-20 22:40:02 +03:00
2019-01-23 02:40:08 +03:00
// Starts the HTTPS server, this should be called after the user/mesh tables are loaded
function serverStart ( ) {
// Start the server, only after users and meshes are loaded from the database.
2020-11-05 13:27:39 +03:00
if ( obj . args . tlsoffload ) {
2019-01-23 02:40:08 +03:00
// Setup the HTTP server without TLS
2020-07-10 10:51:40 +03:00
obj . expressWs = require ( 'express-ws' ) ( obj . app , null , { wsOptions : { perMessageDeflate : ( args . wscompression === true ) } } ) ;
2017-08-28 19:27:45 +03:00
} else {
2022-11-03 08:36:03 +03:00
var ciphers = [
'TLS_AES_256_GCM_SHA384' ,
'TLS_AES_128_GCM_SHA256' ,
'TLS_AES_128_CCM_8_SHA256' ,
'TLS_AES_128_CCM_SHA256' ,
'TLS_CHACHA20_POLY1305_SHA256' ,
'ECDHE-RSA-AES256-GCM-SHA384' ,
'ECDHE-ECDSA-AES256-GCM-SHA384' ,
'ECDHE-RSA-AES128-GCM-SHA256' ,
'ECDHE-ECDSA-AES128-GCM-SHA256' ,
'DHE-RSA-AES128-GCM-SHA256' ,
'ECDHE-RSA-CHACHA20-POLY1305' , // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
'ECDHE-ARIA128-GCM-SHA256' ,
'ECDHE-ARIA256-GCM-SHA384' ,
'ECDHE-RSA-AES128-SHA256' , // SSLlabs considers this cipher suite weak, but it's needed for older browers.
'ECDHE-RSA-AES256-SHA384' , // SSLlabs considers this cipher suite weak, but it's needed for older browers.
'!aNULL' ,
'!eNULL' ,
'!EXPORT' ,
'!DES' ,
'!RC4' ,
'!MD5' ,
'!PSK' ,
'!SRP' ,
'!CAMELLIA'
] . join ( ':' ) ;
2024-03-10 10:45:10 +03:00
if ( obj . useNodeDefaultTLSCiphers ) {
ciphers = require ( "tls" ) . DEFAULT _CIPHERS ;
}
if ( obj . tlsCiphers ) {
ciphers = obj . tlsCiphers ;
if ( Array . isArray ( obj . tlsCiphers ) ) {
ciphers = obj . tlsCiphers . join ( ":" ) ;
}
}
2019-01-23 02:40:08 +03:00
// Setup the HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
2020-01-22 22:34:17 +03:00
//const tlsOptions = { cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!RSA:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 }; // This does not work with TLS 1.3
2022-11-03 08:36:03 +03:00
const tlsOptions = { cert : obj . certificates . web . cert , key : obj . certificates . web . key , ca : obj . certificates . web . ca , rejectUnauthorized : true , ciphers : ciphers , secureOptions : constants . SSL _OP _NO _SSLv2 | constants . SSL _OP _NO _SSLv3 | constants . SSL _OP _NO _COMPRESSION | constants . SSL _OP _CIPHER _SERVER _PREFERENCE | constants . SSL _OP _NO _TLSv1 | constants . SSL _OP _NO _TLSv1 _1 } ;
2019-01-23 02:40:08 +03:00
if ( obj . tlsSniCredentials != null ) { tlsOptions . SNICallback = TlsSniCallback ; } // We have multiple web server certificate used depending on the domain name
obj . tlsServer = require ( 'https' ) . createServer ( tlsOptions , obj . app ) ;
obj . tlsServer . on ( 'secureConnection' , function ( ) { /*console.log('tlsServer secureConnection');*/ } ) ;
2020-01-25 23:14:14 +03:00
obj . tlsServer . on ( 'error' , function ( err ) { console . log ( 'tlsServer error' , err ) ; } ) ;
//obj.tlsServer.on('tlsClientError', function (err) { console.log('tlsClientError', err); });
2019-01-23 02:40:08 +03:00
obj . tlsServer . on ( 'newSession' , function ( id , data , cb ) { if ( tlsSessionStoreCount > 1000 ) { tlsSessionStoreCount = 0 ; tlsSessionStore = { } ; } tlsSessionStore [ id . toString ( 'hex' ) ] = data ; tlsSessionStoreCount ++ ; cb ( ) ; } ) ;
obj . tlsServer . on ( 'resumeSession' , function ( id , cb ) { cb ( null , tlsSessionStore [ id . toString ( 'hex' ) ] || null ) ; } ) ;
2020-07-10 10:51:40 +03:00
obj . expressWs = require ( 'express-ws' ) ( obj . app , obj . tlsServer , { wsOptions : { perMessageDeflate : ( args . wscompression === true ) } } ) ;
2017-08-28 19:27:45 +03:00
}
2020-05-03 20:46:20 +03:00
// Start a second agent-only server if needed
if ( obj . args . agentport ) {
2020-05-22 22:54:22 +03:00
var agentPortTls = true ;
if ( obj . args . tlsoffload != null ) { agentPortTls = false ; }
if ( typeof obj . args . agentporttls == 'boolean' ) { agentPortTls = obj . args . agentporttls ; }
if ( obj . certificates . webdefault == null ) { agentPortTls = false ; }
if ( agentPortTls == false ) {
2020-05-03 20:46:20 +03:00
// Setup the HTTP server without TLS
2020-07-10 10:51:40 +03:00
obj . expressWsAlt = require ( 'express-ws' ) ( obj . agentapp , null , { wsOptions : { perMessageDeflate : ( args . wscompression === true ) } } ) ;
2020-05-03 20:46:20 +03:00
} else {
// Setup the agent HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
2020-05-22 22:54:22 +03:00
// If TLS is used on the agent port, we always use the default TLS certificate.
const tlsOptions = { cert : obj . certificates . webdefault . cert , key : obj . certificates . webdefault . key , ca : obj . certificates . webdefault . ca , rejectUnauthorized : true , ciphers : "HIGH:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256:TLS_CHACHA20_POLY1305_SHA256" , secureOptions : constants . SSL _OP _NO _SSLv2 | constants . SSL _OP _NO _SSLv3 | constants . SSL _OP _NO _COMPRESSION | constants . SSL _OP _CIPHER _SERVER _PREFERENCE | constants . SSL _OP _NO _TLSv1 | constants . SSL _OP _NO _TLSv1 _1 } ;
2020-05-03 20:46:20 +03:00
obj . tlsAltServer = require ( 'https' ) . createServer ( tlsOptions , obj . agentapp ) ;
obj . tlsAltServer . on ( 'secureConnection' , function ( ) { /*console.log('tlsAltServer secureConnection');*/ } ) ;
obj . tlsAltServer . on ( 'error' , function ( err ) { console . log ( 'tlsAltServer error' , err ) ; } ) ;
//obj.tlsAltServer.on('tlsClientError', function (err) { console.log('tlsClientError', err); });
obj . tlsAltServer . on ( 'newSession' , function ( id , data , cb ) { if ( tlsSessionStoreCount > 1000 ) { tlsSessionStoreCount = 0 ; tlsSessionStore = { } ; } tlsSessionStore [ id . toString ( 'hex' ) ] = data ; tlsSessionStoreCount ++ ; cb ( ) ; } ) ;
obj . tlsAltServer . on ( 'resumeSession' , function ( id , cb ) { cb ( null , tlsSessionStore [ id . toString ( 'hex' ) ] || null ) ; } ) ;
2020-07-10 10:51:40 +03:00
obj . expressWsAlt = require ( 'express-ws' ) ( obj . agentapp , obj . tlsAltServer , { wsOptions : { perMessageDeflate : ( args . wscompression === true ) } } ) ;
2020-05-03 20:46:20 +03:00
}
}
2019-01-23 02:40:08 +03:00
// Setup middleware
2024-09-24 21:09:14 +03:00
obj . app . engine ( 'handlebars' , obj . exphbs . engine ( { defaultLayout : false } ) ) ;
2019-01-23 02:40:08 +03:00
obj . app . set ( 'view engine' , 'handlebars' ) ;
2021-01-04 12:50:00 +03:00
if ( obj . args . trustedproxy ) {
// Reverse proxy should add the "X-Forwarded-*" headers
try {
obj . app . set ( 'trust proxy' , obj . args . trustedproxy ) ;
} catch ( ex ) {
// If there is an error, try to resolve the string
if ( ( obj . args . trustedproxy . length == 1 ) && ( typeof obj . args . trustedproxy [ 0 ] == 'string' ) ) {
2021-01-05 23:52:22 +03:00
require ( 'dns' ) . lookup ( obj . args . trustedproxy [ 0 ] , function ( err , address , family ) { if ( err == null ) { obj . app . set ( 'trust proxy' , address ) ; obj . args . trustedproxy = [ address ] ; } } ) ;
2021-01-04 12:50:00 +03:00
}
}
2022-07-20 22:57:24 +03:00
}
2021-01-04 12:50:00 +03:00
else if ( typeof obj . args . tlsoffload == 'object' ) {
// Reverse proxy should add the "X-Forwarded-*" headers
try {
obj . app . set ( 'trust proxy' , obj . args . tlsoffload ) ;
} catch ( ex ) {
// If there is an error, try to resolve the string
2021-01-08 10:35:11 +03:00
if ( ( Array . isArray ( obj . args . tlsoffload ) ) && ( obj . args . tlsoffload . length == 1 ) && ( typeof obj . args . tlsoffload [ 0 ] == 'string' ) ) {
2021-01-05 23:52:22 +03:00
require ( 'dns' ) . lookup ( obj . args . tlsoffload [ 0 ] , function ( err , address , family ) { if ( err == null ) { obj . app . set ( 'trust proxy' , address ) ; obj . args . tlsoffload = [ address ] ; } } ) ;
2021-01-04 12:50:00 +03:00
}
}
}
2022-07-09 04:00:15 +03:00
2022-07-11 21:11:03 +03:00
// Setup a keygrip instance with higher default security, default hash is SHA1, we want to bump that up with SHA384
// If multiple instances of this server are behind a load-balancer, this secret must be the same for all instances
// If args.sessionkey is a string, use it as a single key, but args.sessionkey can also be used as an array of keys.
const keygrip = require ( 'keygrip' ) ( ( typeof obj . args . sessionkey == 'string' ) ? [ obj . args . sessionkey ] : obj . args . sessionkey , 'sha384' , 'base64' ) ;
2022-07-09 04:00:15 +03:00
// Setup the cookie session
2022-07-11 21:11:03 +03:00
const sessionOptions = {
2019-01-23 02:40:08 +03:00
name : 'xid' , // Recommended security practice to not use the default cookie name
httpOnly : true ,
2022-07-11 21:11:03 +03:00
keys : keygrip ,
2022-04-10 03:12:52 +03:00
secure : ( obj . args . tlsoffload == null ) , // Use this cookie only over TLS (Check this: https://expressjs.com/en/guide/behind-proxies.html)
2022-07-07 16:57:48 +03:00
sameSite : ( obj . args . sessionsamesite ? obj . args . sessionsamesite : 'lax' )
2019-01-23 02:40:08 +03:00
}
2022-07-11 21:11:03 +03:00
if ( obj . args . sessiontime != null ) { sessionOptions . maxAge = ( obj . args . sessiontime * 60000 ) ; } // sessiontime is minutes
2022-07-11 21:19:04 +03:00
obj . app . use ( require ( 'cookie-session' ) ( sessionOptions ) ) ;
2024-03-04 03:03:27 +03:00
obj . app . use ( function ( request , response , next ) { // Patch for passport 0.6.0 - https://github.com/jaredhanson/passport/issues/904
2023-09-07 10:26:13 +03:00
if ( request . session && ! request . session . regenerate ) {
request . session . regenerate = function ( cb ) {
cb ( )
}
}
if ( request . session && ! request . session . save ) {
request . session . save = function ( cb ) {
cb ( )
}
}
next ( )
} ) ;
2019-01-23 02:40:08 +03:00
2022-07-04 04:17:57 +03:00
// Handle all incoming web sockets, see if some need to be handled as web relays
obj . app . ws ( '/*' , function ( ws , req , next ) {
2024-10-25 13:02:53 +03:00
// Global error catcher
ws . on ( 'error' , function ( err ) { parent . debug ( 'web' , 'GENERAL WSERR: ' + err ) ; console . log ( err ) ; } ) ;
2022-07-10 11:32:11 +03:00
if ( ( obj . webRelayRouter != null ) && ( obj . args . relaydns . indexOf ( req . hostname ) >= 0 ) ) { handleWebRelayWebSocket ( ws , req ) ; return ; }
2022-07-04 04:17:57 +03:00
return next ( ) ;
} ) ;
2019-01-23 02:40:08 +03:00
// Add HTTP security headers to all responses
2022-07-08 07:51:09 +03:00
obj . app . use ( async function ( req , res , next ) {
2022-01-07 02:05:45 +03:00
// Check if a session is destroyed
if ( typeof req . session . userid == 'string' ) {
if ( typeof req . session . x == 'string' ) {
if ( obj . destroyedSessions [ req . session . userid + '/' + req . session . x ] != null ) {
delete req . session . userid ;
delete req . session . ip ;
delete req . session . t ;
delete req . session . x ;
}
} else {
// Legacy session without a random, add one.
setSessionRandom ( req ) ;
}
}
2021-05-12 20:34:04 +03:00
// Remove legacy values from the session to keep the session as small as possible
2022-07-13 03:45:19 +03:00
delete req . session . u2f ;
2021-05-12 20:34:04 +03:00
delete req . session . domainid ;
delete req . session . nowInMinutes ;
delete req . session . tokenuserid ;
delete req . session . tokenusername ;
delete req . session . tokenpassword ;
delete req . session . tokenemail ;
delete req . session . tokensms ;
delete req . session . tokenpush ;
delete req . session . tusername ;
delete req . session . tpassword ;
2021-01-10 02:58:42 +03:00
// Useful for debugging reverse proxy issues
2021-01-10 15:53:30 +03:00
parent . debug ( 'httpheaders' , req . method , req . url , req . headers ) ;
2021-01-10 02:58:42 +03:00
2021-05-23 10:02:13 +03:00
// If this request came over HTTP, redirect to HTTPS
if ( req . headers [ 'x-forwarded-proto' ] == 'http' ) {
var host = req . headers . host ;
if ( typeof host == 'string' ) { host = host . split ( ':' ) [ 0 ] ; }
if ( ( host == null ) && ( obj . certificates != null ) ) { host = obj . certificates . CommonName ; if ( obj . certificates . CommonName . indexOf ( '.' ) == - 1 ) { host = req . headers . host ; } }
var httpsPort = ( ( obj . args . aliasport == null ) ? obj . args . port : obj . args . aliasport ) ; // Use HTTPS alias port is specified
res . redirect ( 'https://' + host + ':' + httpsPort + req . url ) ;
return ;
}
2021-05-05 21:37:26 +03:00
// Perform traffic accounting
if ( req . headers . upgrade == 'websocket' ) {
// We don't count traffic on WebSockets since it's counted by the handling modules.
obj . trafficStats . httpWebSocketCount ++ ;
} else {
// Normal HTTP traffic is counted
obj . trafficStats . httpRequestCount ++ ;
if ( typeof req . socket . xbytesRead != 'number' ) {
req . socket . xbytesRead = 0 ;
req . socket . xbytesWritten = 0 ;
req . socket . on ( 'close' , function ( ) {
// Perform final accounting
obj . trafficStats . httpIn += ( this . bytesRead - this . xbytesRead ) ;
obj . trafficStats . httpOut += ( this . bytesWritten - this . xbytesWritten ) ;
this . xbytesRead = this . bytesRead ;
this . xbytesWritten = this . bytesWritten ;
} ) ;
} else {
// Update counters
obj . trafficStats . httpIn += ( req . socket . bytesRead - req . socket . xbytesRead ) ;
obj . trafficStats . httpOut += ( req . socket . bytesWritten - req . socket . xbytesWritten ) ;
req . socket . xbytesRead = req . socket . bytesRead ;
req . socket . xbytesWritten = req . socket . bytesWritten ;
}
}
2021-05-05 12:24:23 +03:00
2020-05-22 07:25:11 +03:00
// Set the real IP address of the request
// If a trusted reverse-proxy is sending us the remote IP address, use it.
2020-12-01 00:20:31 +03:00
var ipex = '0.0.0.0' , xforwardedhost = req . headers . host ;
2020-11-29 06:30:07 +03:00
if ( typeof req . connection . remoteAddress == 'string' ) { ipex = ( req . connection . remoteAddress . startsWith ( '::ffff:' ) ) ? req . connection . remoteAddress . substring ( 7 ) : req . connection . remoteAddress ; }
2020-05-22 07:25:11 +03:00
if (
2021-01-05 23:52:22 +03:00
( obj . args . trustedproxy === true ) || ( obj . args . tlsoffload === true ) ||
2020-12-11 00:56:15 +03:00
( ( typeof obj . args . trustedproxy == 'object' ) && ( isIPMatch ( ipex , obj . args . trustedproxy ) ) ) ||
( ( typeof obj . args . tlsoffload == 'object' ) && ( isIPMatch ( ipex , obj . args . tlsoffload ) ) )
2020-05-22 07:25:11 +03:00
) {
2020-11-29 06:30:07 +03:00
// Get client IP
2020-05-22 07:25:11 +03:00
if ( req . headers [ 'cf-connecting-ip' ] ) { // Use CloudFlare IP address if present
req . clientIp = req . headers [ 'cf-connecting-ip' ] . split ( ',' ) [ 0 ] . trim ( ) ;
2020-06-08 19:45:03 +03:00
} else if ( req . headers [ 'x-forwarded-for' ] ) {
2020-05-22 07:25:11 +03:00
req . clientIp = req . headers [ 'x-forwarded-for' ] . split ( ',' ) [ 0 ] . trim ( ) ;
2020-06-08 19:45:03 +03:00
} else if ( req . headers [ 'x-real-ip' ] ) {
2020-05-22 07:25:11 +03:00
req . clientIp = req . headers [ 'x-real-ip' ] . split ( ',' ) [ 0 ] . trim ( ) ;
} else {
req . clientIp = ipex ;
}
2020-11-29 06:30:07 +03:00
2021-01-08 10:35:11 +03:00
// If there is a port number, remove it. This will only work for IPv4, but nice for people that have a bad reverse proxy config.
const clientIpSplit = req . clientIp . split ( ':' ) ;
if ( clientIpSplit . length == 2 ) { req . clientIp = clientIpSplit [ 0 ] ; }
2020-11-29 06:30:07 +03:00
// Get server host
2022-02-17 00:00:36 +03:00
if ( req . headers [ 'x-forwarded-host' ] ) { xforwardedhost = req . headers [ 'x-forwarded-host' ] . split ( ',' ) [ 0 ] ; } // If multiple hosts are specified with a comma, take the first one.
2020-05-22 07:25:11 +03:00
} else {
req . clientIp = ipex ;
}
2022-07-03 10:44:58 +03:00
// If this is a web relay connection, handle it here.
2022-07-10 11:32:11 +03:00
if ( ( obj . webRelayRouter != null ) && ( obj . args . relaydns . indexOf ( req . hostname ) >= 0 ) ) {
2022-07-10 20:50:57 +03:00
if ( [ 'GET' , 'POST' , 'PUT' , 'HEAD' , 'DELETE' , 'OPTIONS' ] . indexOf ( req . method ) >= 0 ) { return obj . webRelayRouter ( req , res ) ; } else { res . sendStatus ( 404 ) ; return ; }
2022-07-09 04:00:15 +03:00
}
2022-07-03 10:44:58 +03:00
2020-05-22 07:25:11 +03:00
// Get the domain for this request
const domain = req . xdomain = getDomain ( req ) ;
parent . debug ( 'webrequest' , '(' + req . clientIp + ') ' + req . url ) ;
2019-01-23 02:40:08 +03:00
2022-07-13 03:45:19 +03:00
// Skip the rest if this is an agent connection
2021-04-28 09:22:55 +03:00
if ( ( req . url . indexOf ( '/meshrelay.ashx/.websocket' ) >= 0 ) || ( req . url . indexOf ( '/agent.ashx/.websocket' ) >= 0 ) || ( req . url . indexOf ( '/localrelay.ashx/.websocket' ) >= 0 ) ) { next ( ) ; return ; }
2022-07-20 22:57:24 +03:00
2022-03-01 22:18:42 +03:00
// Setup security headers
const geourl = ( domain . geolocation ? ' *.openstreetmap.org' : '' ) ;
var selfurl = ' wss://' + req . headers . host ;
if ( ( xforwardedhost != null ) && ( xforwardedhost != req . headers . host ) ) { selfurl += ' wss://' + xforwardedhost ; }
const extraScriptSrc = ( parent . config . settings . extrascriptsrc != null ) ? ( ' ' + parent . config . settings . extrascriptsrc ) : '' ;
2022-06-25 02:53:31 +03:00
// If the web relay port is enabled, allow the web page to redirect to it
var extraFrameSrc = '' ;
if ( ( parent . webrelayserver != null ) && ( parent . webrelayserver . port != 0 ) ) {
extraFrameSrc = ' https://' + req . headers . host + ':' + parent . webrelayserver . port ;
if ( ( xforwardedhost != null ) && ( xforwardedhost != req . headers . host ) ) { extraFrameSrc += ' https://' + xforwardedhost + ':' + parent . webrelayserver . port ; }
}
2022-07-13 03:45:19 +03:00
// Finish setup security headers
2022-03-01 22:18:42 +03:00
const headers = {
'Referrer-Policy' : 'no-referrer' ,
'X-XSS-Protection' : '1; mode=block' ,
'X-Content-Type-Options' : 'nosniff' ,
2024-01-09 16:00:24 +03:00
'Content-Security-Policy' : "default-src 'none'; font-src 'self'; script-src 'self' 'unsafe-inline'" + extraScriptSrc + "; connect-src 'self'" + geourl + selfurl + "; img-src 'self' blob: data:" + geourl + " data:; style-src 'self' 'unsafe-inline'; frame-src 'self' blob: mcrouter:" + extraFrameSrc + "; media-src 'self'; form-action 'self'; manifest-src 'self'"
2022-03-01 22:18:42 +03:00
} ;
2022-05-17 02:20:50 +03:00
if ( req . headers [ 'user-agent' ] && ( req . headers [ 'user-agent' ] . indexOf ( 'Chrome' ) >= 0 ) ) { headers [ 'Permissions-Policy' ] = 'interest-cohort=()' ; } // Remove Google's FLoC Network, only send this if Chrome browser
2022-03-01 22:18:42 +03:00
if ( ( parent . config . settings . allowframing !== true ) && ( typeof parent . config . settings . allowframing !== 'string' ) ) { headers [ 'X-Frame-Options' ] = 'sameorigin' ; }
if ( ( parent . config . settings . stricttransportsecurity === true ) || ( ( parent . config . settings . stricttransportsecurity !== false ) && ( obj . isTrustedCert ( domain ) ) ) ) { if ( typeof parent . config . settings . stricttransportsecurity == 'string' ) { headers [ 'Strict-Transport-Security' ] = parent . config . settings . stricttransportsecurity ; } else { headers [ 'Strict-Transport-Security' ] = 'max-age=63072000' ; } }
// If this domain has configured headers, add them. If a header is set to null, remove it.
2019-09-13 01:12:40 +03:00
if ( ( domain != null ) && ( domain . httpheaders != null ) && ( typeof domain . httpheaders == 'object' ) ) {
2022-03-05 21:51:41 +03:00
for ( var i in domain . httpheaders ) { if ( domain . httpheaders [ i ] === null ) { delete headers [ i ] ; } else { headers [ i ] = domain . httpheaders [ i ] ; } }
2019-09-13 01:12:40 +03:00
}
2022-03-01 22:18:42 +03:00
res . set ( headers ) ;
2019-02-16 01:50:23 +03:00
2019-09-13 02:24:35 +03:00
// Check the session if bound to the external IP address
2022-04-13 00:03:52 +03:00
if ( ( req . session . ip != null ) && ( req . clientIp != null ) && ! checkCookieIp ( req . session . ip , req . clientIp ) ) { req . session = { } ; }
2019-09-13 02:24:35 +03:00
2020-06-12 22:00:58 +03:00
// Extend the session time by forcing a change to the session every minute.
2021-05-12 20:34:04 +03:00
if ( req . session . userid != null ) { req . session . t = Math . floor ( Date . now ( ) / 60e3 ) ; } else { delete req . session . t ; }
2020-06-12 22:00:58 +03:00
2022-07-08 07:51:09 +03:00
// Check CrowdSec Bounser if configured
if ( ( parent . crowdSecBounser != null ) && ( req . headers [ 'upgrade' ] != 'websocket' ) && ( req . session . userid == null ) ) { if ( ( await parent . crowdSecBounser . process ( domain , req , res , next ) ) == true ) { return ; } }
2021-01-26 22:46:22 +03:00
// Debugging code, this will stop the agent from crashing if two responses are made to the same request.
const render = res . render ;
const send = res . send ;
res . render = function renderWrapper ( ... args ) {
Error . captureStackTrace ( this ) ;
return render . apply ( this , args ) ;
} ;
res . send = function sendWrapper ( ... args ) {
try {
send . apply ( this , args ) ;
} catch ( err ) {
console . error ( ` Error in res.send | ${ err . code } | ${ err . message } | ${ res . stack } ` ) ;
try {
var errlogpath = null ;
if ( typeof parent . args . mesherrorlogpath == 'string' ) { errlogpath = parent . path . join ( parent . args . mesherrorlogpath , 'mesherrors.txt' ) ; } else { errlogpath = parent . getConfigFilePath ( 'mesherrors.txt' ) ; }
parent . fs . appendFileSync ( errlogpath , new Date ( ) . toLocaleString ( ) + ': ' + ` Error in res.send | ${ err . code } | ${ err . message } | ${ res . stack } ` + '\r\n' ) ;
2024-03-04 03:03:27 +03:00
} catch ( ex ) { parent . debug ( 'error' , 'Unable to write to mesherrors.txt.' ) ; }
2021-01-26 22:46:22 +03:00
}
} ;
2021-01-04 14:14:26 +03:00
// Continue processing the request
return next ( ) ;
2018-09-27 00:58:55 +03:00
} ) ;
2020-05-22 22:54:22 +03:00
if ( obj . agentapp ) {
// Add HTTP security headers to all responses
obj . agentapp . use ( function ( req , res , next ) {
// Set the real IP address of the request
// If a trusted reverse-proxy is sending us the remote IP address, use it.
2020-09-23 19:29:19 +03:00
var ipex = '0.0.0.0' ;
2020-11-29 06:30:07 +03:00
if ( typeof req . connection . remoteAddress == 'string' ) { ipex = ( req . connection . remoteAddress . startsWith ( '::ffff:' ) ) ? req . connection . remoteAddress . substring ( 7 ) : req . connection . remoteAddress ; }
2020-05-22 22:54:22 +03:00
if (
2021-08-31 17:35:35 +03:00
( obj . args . trustedproxy === true ) || ( obj . args . tlsoffload === true ) ||
( ( typeof obj . args . trustedproxy == 'object' ) && ( isIPMatch ( ipex , obj . args . trustedproxy ) ) ) ||
( ( typeof obj . args . tlsoffload == 'object' ) && ( isIPMatch ( ipex , obj . args . tlsoffload ) ) )
2020-05-22 22:54:22 +03:00
) {
if ( req . headers [ 'cf-connecting-ip' ] ) { // Use CloudFlare IP address if present
req . clientIp = req . headers [ 'cf-connecting-ip' ] . split ( ',' ) [ 0 ] . trim ( ) ;
2020-05-24 02:24:05 +03:00
} else if ( req . headers [ 'x-forwarded-for' ] ) {
2020-05-22 22:54:22 +03:00
req . clientIp = req . headers [ 'x-forwarded-for' ] . split ( ',' ) [ 0 ] . trim ( ) ;
2020-05-24 02:24:05 +03:00
} else if ( req . headers [ 'x-real-ip' ] ) {
2020-05-22 22:54:22 +03:00
req . clientIp = req . headers [ 'x-real-ip' ] . split ( ',' ) [ 0 ] . trim ( ) ;
} else {
req . clientIp = ipex ;
}
} else {
req . clientIp = ipex ;
}
// Get the domain for this request
const domain = req . xdomain = getDomain ( req ) ;
parent . debug ( 'webrequest' , '(' + req . clientIp + ') AgentPort: ' + req . url ) ;
res . removeHeader ( 'X-Powered-By' ) ;
return next ( ) ;
} ) ;
}
2024-03-04 03:03:27 +03:00
// Setup all sharing domains and check if auth strategies need setup
var setupSSO = false
2021-01-04 14:14:26 +03:00
for ( var i in parent . config . domains ) {
if ( ( parent . config . domains [ i ] . dns == null ) && ( parent . config . domains [ i ] . share != null ) ) { obj . app . use ( parent . config . domains [ i ] . url , obj . express . static ( parent . config . domains [ i ] . share ) ) ; }
2024-03-04 03:03:27 +03:00
if ( typeof parent . config . domains [ i ] . authstrategies == 'object' ) { setupSSO = true } ;
}
if ( setupSSO ) {
setupAllDomainAuthStrategies ( ) . then ( ( ) => finalizeWebserver ( ) ) ;
} else {
finalizeWebserver ( )
2021-01-04 14:14:26 +03:00
}
2022-08-18 00:14:56 +03:00
// Setup all domain auth strategy passport.js
2024-03-04 03:03:27 +03:00
async function setupAllDomainAuthStrategies ( ) {
for ( var i in parent . config . domains ) {
if ( parent . config . domains [ i ] . dns != null ) {
if ( typeof parent . config . domains [ '' ] . authstrategies != 'object' ) { parent . config . domains [ '' ] . authstrategies = { 'authStrategyFlags' : 0 } ; }
2024-04-11 19:43:08 +03:00
parent . config . domains [ '' ] . authstrategies . authStrategyFlags |= await setupDomainAuthStrategy ( parent . config . domains [ i ] ) ;
2024-03-04 03:03:27 +03:00
} else {
if ( typeof parent . config . domains [ i ] . authstrategies != 'object' ) { parent . config . domains [ i ] . authstrategies = { 'authStrategyFlags' : 0 } ; }
parent . config . domains [ i ] . authstrategies . authStrategyFlags |= await setupDomainAuthStrategy ( parent . config . domains [ i ] ) ;
2022-08-18 00:14:56 +03:00
}
}
}
2024-03-04 03:03:27 +03:00
function setupHTTPHandlers ( ) {
// Setup all HTTP handlers
if ( parent . pluginHandler != null ) {
parent . pluginHandler . callHook ( 'hook_setupHttpHandlers' , obj , parent ) ;
}
if ( parent . multiServer != null ) { obj . app . ws ( '/meshserver.ashx' , function ( ws , req ) { parent . multiServer . CreatePeerInServer ( parent . multiServer , ws , req , obj . args . tlsoffload == null ) ; } ) ; }
for ( var i in parent . config . domains ) {
if ( ( parent . config . domains [ i ] . dns != null ) || ( parent . config . domains [ i ] . share != null ) ) { continue ; } // This is a subdomain with a DNS name, no added HTTP bindings needed.
var domain = parent . config . domains [ i ] ;
var url = domain . url ;
if ( typeof domain . rootredirect == 'string' ) {
// Root page redirects the user to a different URL
obj . app . get ( url , handleRootRedirect ) ;
} else {
// Present the login page as the root page
obj . app . get ( url , handleRootRequest ) ;
obj . app . post ( url , obj . bodyParser . urlencoded ( { extended : false } ) , handleRootPostRequest ) ;
}
obj . app . get ( url + 'refresh.ashx' , function ( req , res ) { res . sendStatus ( 200 ) ; } ) ;
if ( ( domain . myserver !== false ) && ( ( domain . myserver == null ) || ( domain . myserver . backup === true ) ) ) { obj . app . get ( url + 'backup.zip' , handleBackupRequest ) ; }
if ( ( domain . myserver !== false ) && ( ( domain . myserver == null ) || ( domain . myserver . restore === true ) ) ) { obj . app . post ( url + 'restoreserver.ashx' , obj . bodyParser . urlencoded ( { extended : false } ) , handleRestoreRequest ) ; }
obj . app . get ( url + 'terms' , handleTermsRequest ) ;
obj . app . get ( url + 'xterm' , handleXTermRequest ) ;
obj . app . get ( url + 'login' , handleRootRequest ) ;
obj . app . post ( url + 'login' , obj . bodyParser . urlencoded ( { extended : false } ) , handleRootPostRequest ) ;
obj . app . post ( url + 'tokenlogin' , obj . bodyParser . urlencoded ( { extended : false } ) , handleLoginRequest ) ;
obj . app . get ( url + 'logout' , handleLogoutRequest ) ;
obj . app . get ( url + 'MeshServerRootCert.cer' , handleRootCertRequest ) ;
2024-09-24 21:09:14 +03:00
obj . app . get ( url + 'manifest.json' , handleManifestRequest ) ;
2024-03-04 03:03:27 +03:00
obj . app . post ( url + 'changepassword' , obj . bodyParser . urlencoded ( { extended : false } ) , handlePasswordChangeRequest ) ;
obj . app . post ( url + 'deleteaccount' , obj . bodyParser . urlencoded ( { extended : false } ) , handleDeleteAccountRequest ) ;
obj . app . post ( url + 'createaccount' , obj . bodyParser . urlencoded ( { extended : false } ) , handleCreateAccountRequest ) ;
obj . app . post ( url + 'resetpassword' , obj . bodyParser . urlencoded ( { extended : false } ) , handleResetPasswordRequest ) ;
obj . app . post ( url + 'resetaccount' , obj . bodyParser . urlencoded ( { extended : false } ) , handleResetAccountRequest ) ;
obj . app . get ( url + 'checkmail' , handleCheckMailRequest ) ;
obj . app . get ( url + 'agentinvite' , handleAgentInviteRequest ) ;
obj . app . get ( url + 'userimage.ashx' , handleUserImageRequest ) ;
obj . app . post ( url + 'amtevents.ashx' , obj . bodyParser . urlencoded ( { extended : false } ) , obj . handleAmtEventRequest ) ;
obj . app . get ( url + 'meshagents' , obj . handleMeshAgentRequest ) ;
obj . app . get ( url + 'messenger' , handleMessengerRequest ) ;
obj . app . get ( url + 'messenger.png' , handleMessengerImageRequest ) ;
obj . app . get ( url + 'meshosxagent' , obj . handleMeshOsxAgentRequest ) ;
obj . app . get ( url + 'meshsettings' , obj . handleMeshSettingsRequest ) ;
obj . app . get ( url + 'devicepowerevents.ashx' , obj . handleDevicePowerEvents ) ;
obj . app . get ( url + 'downloadfile.ashx' , handleDownloadFile ) ;
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 + '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 ) ;
obj . app . ws ( url + 'echo.ashx' , handleEchoWebSocket ) ;
obj . app . ws ( url + '2fahold.ashx' , handle2faHoldWebSocket ) ;
obj . app . ws ( url + 'apf.ashx' , function ( ws , req ) { obj . parent . mpsserver . onWebSocketConnection ( ws , req ) ; } )
obj . app . get ( url + 'webrelay.ashx' , function ( req , res ) { res . send ( 'Websocket connection expected' ) ; } ) ;
obj . app . get ( url + 'health.ashx' , function ( req , res ) { res . send ( 'ok' ) ; } ) ; // TODO: Perform more server checking.
obj . app . ws ( url + 'webrelay.ashx' , function ( ws , req ) { PerformWSSessionAuth ( ws , req , false , handleRelayWebSocket ) ; } ) ;
obj . app . ws ( url + 'webider.ashx' , function ( ws , req ) { PerformWSSessionAuth ( ws , req , false , function ( ws1 , req1 , domain , user , cookie , authData ) { obj . meshIderHandler . CreateAmtIderSession ( obj , obj . db , ws1 , req1 , obj . args , domain , user ) ; } ) ; } ) ;
obj . app . ws ( url + 'control.ashx' , function ( ws , req ) {
getWebsocketArgs ( ws , req , function ( ws , req ) {
const domain = getDomain ( req ) ;
if ( obj . CheckWebServerOriginName ( domain , req ) == false ) {
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'invalidorigin' , msg : 'invalidorigin' } ) ) ; } catch ( ex ) { }
try { ws . close ( ) ; } catch ( ex ) { }
return ;
}
2024-08-16 18:02:21 +03:00
if ( ( domain . loginkey != null ) && ( domain . loginkey . indexOf ( req . query . key ) == - 1 ) ) { // Check 3FA URL key
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'nokey' } ) ) ; } catch ( ex ) { }
try { ws . close ( ) ; } catch ( ex ) { }
return ;
}
2024-03-04 03:03:27 +03:00
PerformWSSessionAuth ( ws , req , true , function ( ws1 , req1 , domain , user , cookie , authData ) {
if ( user == null ) { // User is not authenticated, perform inner server authentication
if ( req . headers [ 'x-meshauth' ] === '*' ) {
PerformWSSessionInnerAuth ( ws , req , domain , function ( ws1 , req1 , domain , user ) { obj . meshUserHandler . CreateMeshUser ( obj , obj . db , ws1 , req1 , obj . args , domain , user , authData ) ; } ) ; // User is authenticated
} else {
2024-08-16 18:02:21 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth' } ) ) ; } catch ( ex ) { }
2024-03-04 03:03:27 +03:00
try { ws . close ( ) ; } catch ( ex ) { } // user is not authenticated and inner authentication was not requested, disconnect now.
}
2021-11-04 04:52:11 +03:00
} else {
2024-03-04 03:03:27 +03:00
obj . meshUserHandler . CreateMeshUser ( obj , obj . db , ws1 , req1 , obj . args , domain , user , authData ) ; // User is authenticated
2021-11-04 04:52:11 +03:00
}
2024-03-04 03:03:27 +03:00
} ) ;
2021-11-04 04:52:11 +03:00
} ) ;
2021-04-03 03:20:36 +03:00
} ) ;
2024-03-04 03:03:27 +03:00
obj . app . ws ( url + 'devicefile.ashx' , function ( ws , req ) { obj . meshDeviceFileHandler . CreateMeshDeviceFile ( obj , ws , null , req , domain ) ; } ) ;
obj . app . get ( url + 'devicefile.ashx' , handleDeviceFile ) ;
obj . app . get ( url + 'agentdownload.ashx' , handleAgentDownloadFile ) ;
obj . app . get ( url + 'logo.png' , handleLogoRequest ) ;
obj . app . get ( url + 'loginlogo.png' , handleLoginLogoRequest ) ;
2024-09-24 21:09:14 +03:00
obj . app . get ( url + 'pwalogo.png' , handlePWALogoRequest ) ;
2024-03-04 03:03:27 +03:00
obj . app . post ( url + 'translations' , obj . bodyParser . urlencoded ( { extended : false } ) , handleTranslationsRequest ) ;
obj . app . get ( url + 'welcome.jpg' , handleWelcomeImageRequest ) ;
obj . app . get ( url + 'welcome.png' , handleWelcomeImageRequest ) ;
obj . app . get ( url + 'recordings.ashx' , handleGetRecordings ) ;
obj . app . ws ( url + 'recordings.ashx' , handleGetRecordingsWebSocket ) ;
obj . app . get ( url + 'player.htm' , handlePlayerRequest ) ;
obj . app . get ( url + 'player' , handlePlayerRequest ) ;
obj . app . get ( url + 'sharing' , handleSharingRequest ) ;
obj . app . ws ( url + 'agenttransfer.ashx' , handleAgentFileTransfer ) ; // Setup agent to/from server file transfer handler
obj . app . ws ( url + 'meshrelay.ashx' , function ( ws , req ) {
2022-03-05 02:56:19 +03:00
PerformWSSessionAuth ( ws , req , true , function ( ws1 , req1 , domain , user , cookie , authData ) {
2024-03-04 03:03:27 +03:00
if ( ( ( parent . config . settings . desktopmultiplex === true ) || ( domain . desktopmultiplex === true ) ) && ( req . query . p == 2 ) ) {
obj . meshDesktopMultiplexHandler . CreateMeshRelay ( obj , ws1 , req1 , domain , user , cookie ) ; // Desktop multiplexor 1-to-n
2021-04-28 09:22:55 +03:00
} else {
2024-03-04 03:03:27 +03:00
obj . meshRelayHandler . CreateMeshRelay ( obj , ws1 , req1 , domain , user , cookie ) ; // Normal relay 1-to-1
2021-04-28 09:22:55 +03:00
}
} ) ;
} ) ;
2024-03-04 03:03:27 +03:00
if ( obj . args . wanonly != true ) { // If the server is not in WAN mode, allow server relayed connections.
obj . app . ws ( url + 'localrelay.ashx' , function ( ws , req ) {
PerformWSSessionAuth ( ws , req , true , function ( ws1 , req1 , domain , user , cookie , authData ) {
if ( ( user == null ) || ( cookie == null ) ) {
try { ws1 . close ( ) ; } catch ( ex ) { }
} else {
obj . meshRelayHandler . CreateLocalRelay ( obj , ws1 , req1 , domain , user , cookie ) ; // Local relay
}
} ) ;
2021-05-09 04:09:49 +03:00
} ) ;
2024-03-04 03:03:27 +03:00
}
2024-04-11 20:51:54 +03:00
obj . app . get ( url + 'invite' , handleInviteRequest ) ;
obj . app . post ( url + 'invite' , obj . bodyParser . urlencoded ( { extended : false } ) , handleInviteRequest ) ;
2024-03-04 03:03:27 +03:00
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 ) ;
obj . app . get ( url + 'pluginHandler.js' , obj . handlePluginJS ) ;
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// New account CAPTCHA request
if ( ( domain . newaccountscaptcha != null ) && ( domain . newaccountscaptcha !== false ) ) {
obj . app . get ( url + 'newAccountCaptcha.ashx' , handleNewAccountCaptchaRequest ) ;
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Check CrowdSec Bounser if configured
if ( parent . crowdSecBounser != null ) {
obj . app . get ( url + 'captcha.ashx' , handleCaptchaGetRequest ) ;
obj . app . post ( url + 'captcha.ashx' , obj . bodyParser . urlencoded ( { extended : false } ) , handleCaptchaPostRequest ) ;
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup IP-KVM relay if supported
if ( domain . ipkvm ) {
obj . app . ws ( url + 'ipkvm.ashx/*' , function ( ws , req ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'ipkvm: failed domain checks.' ) ; try { ws . close ( ) ; } catch ( ex ) { } return ; }
parent . ipKvmManager . handleIpKvmWebSocket ( domain , ws , req ) ;
2021-05-25 01:26:54 +03:00
} ) ;
2024-03-04 03:03:27 +03:00
obj . app . get ( url + 'ipkvm.ashx/*' , function ( req , res , next ) {
const domain = getDomain ( req ) ;
if ( domain == null ) return ;
parent . ipKvmManager . handleIpKvmGet ( domain , req , res , next ) ;
2020-05-25 08:05:18 +03:00
} ) ;
2020-05-14 06:49:58 +03:00
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup RDP unless indicated as disabled
if ( domain . mstsc !== false ) {
obj . app . get ( url + 'mstsc.html' , function ( req , res ) { handleMSTSCRequest ( req , res , 'mstsc' ) ; } ) ;
obj . app . ws ( url + 'mstscrelay.ashx' , function ( ws , req ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'mstsc: failed checks.' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; }
// If no user is logged in and we have a default user, set it now.
if ( ( req . session . userid == null ) && ( typeof obj . args . user == 'string' ) && ( obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) ) { req . session . userid = 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ; }
try { require ( './apprelays.js' ) . CreateMstscRelay ( obj , obj . db , ws , req , obj . args , domain ) ; } catch ( ex ) { console . log ( ex ) ; }
2020-05-25 08:05:18 +03:00
} ) ;
2024-03-04 03:03:27 +03:00
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup SSH if needed
if ( domain . ssh === true ) {
obj . app . get ( url + 'ssh.html' , function ( req , res ) { handleMSTSCRequest ( req , res , 'ssh' ) ; } ) ;
obj . app . ws ( url + 'sshrelay.ashx' , function ( ws , req ) {
const domain = getDomain ( req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'ssh: failed checks.' ) ; try { ws . close ( ) ; } catch ( e ) { } return ; }
// If no user is logged in and we have a default user, set it now.
if ( ( req . session . userid == null ) && ( typeof obj . args . user == 'string' ) && ( obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) ) { req . session . userid = 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ; }
try { require ( './apprelays.js' ) . CreateSshRelay ( obj , obj . db , ws , req , obj . args , domain ) ; } catch ( ex ) { console . log ( ex ) ; }
2020-05-25 08:05:18 +03:00
} ) ;
2024-03-04 03:03:27 +03:00
obj . app . ws ( url + 'sshterminalrelay.ashx' , function ( ws , req ) {
PerformWSSessionAuth ( ws , req , true , function ( ws1 , req1 , domain , user , cookie , authData ) {
require ( './apprelays.js' ) . CreateSshTerminalRelay ( obj , obj . db , ws1 , req1 , domain , user , cookie , obj . args ) ;
} ) ;
} ) ;
obj . app . ws ( url + 'sshfilesrelay.ashx' , function ( ws , req ) {
PerformWSSessionAuth ( ws , req , true , function ( ws1 , req1 , domain , user , cookie , authData ) {
require ( './apprelays.js' ) . CreateSshFilesRelay ( obj , obj . db , ws1 , req1 , domain , user , cookie , obj . args ) ;
} ) ;
2020-05-14 11:41:03 +03:00
} ) ;
2020-05-14 06:49:58 +03:00
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup firebase push only server
if ( ( obj . parent . firebase != null ) && ( obj . parent . config . firebase ) ) {
if ( obj . parent . config . firebase . pushrelayserver ) { parent . debug ( 'email' , 'Firebase-pushrelay-handler' ) ; obj . app . post ( url + 'firebaserelay.aspx' , obj . bodyParser . urlencoded ( { extended : false } ) , handleFirebasePushOnlyRelayRequest ) ; }
if ( obj . parent . config . firebase . relayserver ) { parent . debug ( 'email' , 'Firebase-relay-handler' ) ; obj . app . ws ( url + 'firebaserelay.aspx' , handleFirebaseRelayRequest ) ; }
}
// Setup auth strategies using passport if needed
if ( typeof domain . authstrategies == 'object' ) {
2024-03-10 10:44:18 +03:00
parent . authLog ( 'setupHTTPHandlers' , ` Setting up authentication strategies login and callback URLs for ${ domain . id == '' ? 'root' : '"' + domain . id + '"' } domain. ` ) ;
2024-03-04 03:03:27 +03:00
// Twitter
if ( ( domain . authstrategies . authStrategyFlags & domainAuthStrategyConsts . twitter ) != 0 ) {
obj . app . get ( url + 'auth-twitter' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
domain . passport . authenticate ( 'twitter-' + domain . id ) ( req , res , function ( err ) { console . log ( 'c1' , err , req . session ) ; next ( ) ; } ) ;
} ) ;
obj . app . get ( url + 'auth-twitter-callback' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
if ( ( Object . keys ( req . session ) . length == 0 ) && ( req . query . nmr == null ) ) {
// This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
var url = req . url ;
if ( url . indexOf ( '?' ) >= 0 ) { url += '&nmr=1' ; } else { url += '?nmr=1' ; } // Add this to the URL to prevent redirect loop.
res . set ( 'Content-Type' , 'text/html' ) ;
2024-09-27 07:09:34 +03:00
res . end ( '<html><head><meta http-equiv="refresh" content=0;url="' + encodeURIComponent ( url ) + '"></head><body></body></html>' ) ;
2024-03-04 03:03:27 +03:00
} else {
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'twitter-' + domain . id , { failureRedirect : domain . url } ) ( req , res , function ( err ) { if ( err != null ) { console . log ( err ) ; } next ( ) ; } ) ;
2024-03-04 03:03:27 +03:00
}
} , handleStrategyLogin ) ;
}
2020-05-20 05:02:27 +03:00
2024-03-04 03:03:27 +03:00
// Google
if ( ( domain . authstrategies . authStrategyFlags & domainAuthStrategyConsts . google ) != 0 ) {
obj . app . get ( url + 'auth-google' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
domain . passport . authenticate ( 'google-' + domain . id , { scope : [ 'profile' , 'email' ] } ) ( req , res , next ) ;
} ) ;
obj . app . get ( url + 'auth-google-callback' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'google-' + domain . id , { failureRedirect : domain . url } ) ( req , res , function ( err ) { if ( err != null ) { console . log ( err ) ; } next ( ) ; } ) ;
2024-03-04 03:03:27 +03:00
} , handleStrategyLogin ) ;
}
// GitHub
if ( ( domain . authstrategies . authStrategyFlags & domainAuthStrategyConsts . github ) != 0 ) {
obj . app . get ( url + 'auth-github' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
domain . passport . authenticate ( 'github-' + domain . id , { scope : [ 'user:email' ] } ) ( req , res , next ) ;
} ) ;
obj . app . get ( url + 'auth-github-callback' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'github-' + domain . id , { failureRedirect : domain . url } ) ( req , res , next ) ;
2024-03-04 03:03:27 +03:00
} , handleStrategyLogin ) ;
}
// Azure
if ( ( domain . authstrategies . authStrategyFlags & domainAuthStrategyConsts . azure ) != 0 ) {
obj . app . get ( url + 'auth-azure' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
domain . passport . authenticate ( 'azure-' + domain . id , { state : obj . parent . encodeCookie ( { 'p' : 'azure' } , obj . parent . loginCookieEncryptionKey ) } ) ( req , res , next ) ;
} ) ;
obj . app . get ( url + 'auth-azure-callback' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
if ( ( Object . keys ( req . session ) . length == 0 ) && ( req . query . nmr == null ) ) {
// This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
var url = req . url ;
if ( url . indexOf ( '?' ) >= 0 ) { url += '&nmr=1' ; } else { url += '?nmr=1' ; } // Add this to the URL to prevent redirect loop.
res . set ( 'Content-Type' , 'text/html' ) ;
2024-09-27 07:09:34 +03:00
res . end ( '<html><head><meta http-equiv="refresh" content=0;url="' + encodeURIComponent ( url ) + '"></head><body></body></html>' ) ;
2024-03-04 03:03:27 +03:00
} else {
if ( req . query . state != null ) {
var c = obj . parent . decodeCookie ( req . query . state , obj . parent . loginCookieEncryptionKey , 10 ) ; // 10 minute timeout
2024-08-30 18:31:54 +03:00
if ( ( c != null ) && ( c . p == 'azure' ) ) { domain . passport . authenticate ( 'azure-' + domain . id , { failureRedirect : domain . url } ) ( req , res , next ) ; return ; }
2024-03-04 03:03:27 +03:00
}
next ( ) ;
2020-05-24 10:04:49 +03:00
}
2024-03-04 03:03:27 +03:00
} , handleStrategyLogin ) ;
}
// Setup OpenID Connect URLs
if ( ( domain . authstrategies . authStrategyFlags & domainAuthStrategyConsts . oidc ) != 0 ) {
2024-03-10 10:44:18 +03:00
let authURL = url + 'auth-oidc'
parent . authLog ( 'setupHTTPHandlers' , ` OIDC: Authorization URL: ${ authURL } ` ) ;
obj . app . get ( authURL , function ( req , res , next ) {
2024-03-04 03:03:27 +03:00
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( ` oidc- ${ domain . id } ` , { failureRedirect : domain . url , failureFlash : true } ) ( req , res , next ) ;
2024-03-04 03:03:27 +03:00
} ) ;
2024-06-04 12:26:29 +03:00
let redirectPath ;
2024-03-04 03:03:27 +03:00
if ( typeof domain . authstrategies . oidc . client . redirect _uri == 'string' ) {
2024-06-04 12:26:29 +03:00
redirectPath = ( new URL ( domain . authstrategies . oidc . client . redirect _uri ) ) . pathname ;
2024-03-04 03:03:27 +03:00
} else if ( Array . isArray ( domain . authstrategies . oidc . client . redirect _uris ) ) {
2024-06-04 12:26:29 +03:00
redirectPath = ( new URL ( domain . authstrategies . oidc . client . redirect _uris [ 0 ] ) ) . pathname ;
2024-03-04 03:03:27 +03:00
} else {
2024-06-04 12:26:29 +03:00
redirectPath = url + 'auth-oidc-callback' ;
2020-05-24 10:04:49 +03:00
}
2024-03-10 10:44:18 +03:00
parent . authLog ( 'setupHTTPHandlers' , ` OIDC: Callback URL: ${ redirectPath } ` ) ;
2024-03-04 03:03:27 +03:00
obj . app . get ( redirectPath , obj . bodyParser . urlencoded ( { extended : false } ) , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-06-01 22:31:25 +03:00
if ( req . session && req . session . userid ) { next ( ) ; return ; } // already logged in so dont authenticate just carry on
if ( req . session && req . session [ 'oidc-' + domain . id ] ) { // we have a request to login so do authenticate
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( ` oidc- ${ domain . id } ` , { failureRedirect : domain . url , failureFlash : true } ) ( req , res , next ) ;
2024-06-01 22:31:25 +03:00
} else { // no idea so carry on
next ( ) ; return ;
}
2024-03-04 03:03:27 +03:00
} , handleStrategyLogin ) ;
}
2020-05-24 10:04:49 +03:00
2024-03-04 03:03:27 +03:00
// Generic SAML
if ( ( domain . authstrategies . authStrategyFlags & domainAuthStrategyConsts . saml ) != 0 ) {
obj . app . get ( url + 'auth-saml' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'saml-' + domain . id , { failureRedirect : domain . url , failureFlash : true } ) ( req , res , next ) ;
2024-03-04 03:03:27 +03:00
} ) ;
obj . app . post ( url + 'auth-saml-callback' , obj . bodyParser . urlencoded ( { extended : false } ) , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'saml-' + domain . id , { failureRedirect : domain . url , failureFlash : true } ) ( req , res , next ) ;
2024-03-04 03:03:27 +03:00
} , handleStrategyLogin ) ;
}
2020-05-14 06:49:58 +03:00
2024-03-04 03:03:27 +03:00
// Intel SAML
if ( ( domain . authstrategies . authStrategyFlags & domainAuthStrategyConsts . intelSaml ) != 0 ) {
obj . app . get ( url + 'auth-intel' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'isaml-' + domain . id , { failureRedirect : domain . url , failureFlash : true } ) ( req , res , next ) ;
2024-03-04 03:03:27 +03:00
} ) ;
obj . app . post ( url + 'auth-intel-callback' , obj . bodyParser . urlencoded ( { extended : false } ) , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'isaml-' + domain . id , { failureRedirect : domain . url , failureFlash : true } ) ( req , res , next ) ;
2024-03-04 03:03:27 +03:00
} , handleStrategyLogin ) ;
}
2019-06-07 20:20:54 +03:00
2024-03-04 03:03:27 +03:00
// JumpCloud SAML
if ( ( domain . authstrategies . authStrategyFlags & domainAuthStrategyConsts . jumpCloudSaml ) != 0 ) {
obj . app . get ( url + 'auth-jumpcloud' , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'jumpcloud-' + domain . id , { failureRedirect : domain . url , failureFlash : true } ) ( req , res , next ) ;
2024-03-04 03:03:27 +03:00
} ) ;
obj . app . post ( url + 'auth-jumpcloud-callback' , obj . bodyParser . urlencoded ( { extended : false } ) , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . passport == null ) { next ( ) ; return ; }
2024-08-30 18:31:54 +03:00
domain . passport . authenticate ( 'jumpcloud-' + domain . id , { failureRedirect : domain . url , failureFlash : true } ) ( req , res , next ) ;
2024-03-04 03:03:27 +03:00
} , handleStrategyLogin ) ;
}
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Server redirects
if ( parent . config . domains [ i ] . redirects ) { for ( var j in parent . config . domains [ i ] . redirects ) { if ( j [ 0 ] != '_' ) { obj . app . get ( url + j , obj . handleDomainRedirect ) ; } } }
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Server picture
obj . app . get ( url + 'serverpic.ashx' , function ( req , res ) {
2019-02-03 01:54:36 +03:00
// Check if we have "server.jpg" in the data folder, if so, use that.
2024-03-04 03:03:27 +03:00
if ( ( parent . configurationFiles != null ) && ( parent . configurationFiles [ 'server.png' ] != null ) ) {
res . set ( { 'Content-Type' : 'image/png' } ) ;
res . send ( parent . configurationFiles [ 'server.png' ] ) ;
2019-02-03 01:54:36 +03:00
} else {
2024-03-04 03:03:27 +03:00
// Check if we have "server.jpg" in the data folder, if so, use that.
var p = obj . path . join ( obj . parent . datapath , 'server.png' ) ;
if ( obj . fs . existsSync ( p ) ) {
// Use the data folder server picture
try { res . sendFile ( p ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
2019-09-15 22:47:59 +03:00
} else {
2024-03-04 03:03:27 +03:00
var domain = getDomain ( req ) ;
if ( ( domain != null ) && ( domain . webpublicpath != null ) && ( obj . fs . existsSync ( obj . path . join ( domain . webpublicpath , 'images/server-256.png' ) ) ) ) {
// Use the domain server picture
try { res . sendFile ( obj . path . join ( domain . webpublicpath , 'images/server-256.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else if ( parent . webPublicOverridePath && obj . fs . existsSync ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/server-256.png' ) ) ) {
// Use the override server picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicOverridePath , 'images/server-256.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
} else {
// Use the default server picture
try { res . sendFile ( obj . path . join ( obj . parent . webPublicPath , 'images/server-256.png' ) ) ; } catch ( ex ) { res . sendStatus ( 404 ) ; }
}
2019-09-15 22:47:59 +03:00
}
2019-02-03 01:54:36 +03:00
}
2019-10-04 22:18:56 +03:00
} ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Receive mesh agent connections
obj . app . ws ( url + 'agent.ashx' , function ( ws , req ) {
2020-05-03 20:46:20 +03:00
var domain = checkAgentIpAddress ( ws , req ) ;
2020-05-22 07:25:11 +03:00
if ( domain == null ) { parent . debug ( 'web' , 'Got agent connection with bad domain or blocked IP address ' + req . clientIp + ', holding.' ) ; return ; }
2022-01-18 17:21:09 +03:00
if ( domain . agentkey && ( ( req . query . key == null ) || ( domain . agentkey . indexOf ( req . query . key ) == - 1 ) ) ) { return ; } // If agent key is required and not provided or not valid, just hold the websocket and do nothing.
2024-03-04 03:03:27 +03:00
//console.log('Agent connect: ' + req.clientIp);
2024-08-05 08:31:17 +03:00
try { obj . meshAgentHandler . CreateMeshAgent ( obj , obj . db , ws , req , obj . args , domain ) ; } catch ( ex ) { console . log ( ex ) ; }
2020-05-03 20:46:20 +03:00
} ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup MQTT broker over websocket
if ( obj . parent . mqttbroker != null ) {
obj . app . ws ( url + 'mqtt.ashx' , function ( ws , req ) {
var domain = checkAgentIpAddress ( ws , req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'Got agent connection with bad domain or blocked IP address ' + req . clientIp + ', holding.' ) ; return ; }
var serialtunnel = SerialTunnel ( ) ;
serialtunnel . xtransport = 'ws' ;
serialtunnel . xdomain = domain ;
serialtunnel . xip = req . clientIp ;
ws . on ( 'message' , function ( b ) { serialtunnel . updateBuffer ( Buffer . from ( b , 'binary' ) ) } ) ;
serialtunnel . forwardwrite = function ( b ) { ws . send ( b , 'binary' ) }
ws . on ( 'close' , function ( ) { serialtunnel . emit ( 'end' ) ; } ) ;
obj . parent . mqttbroker . handle ( serialtunnel ) ; // Pass socket wrapper to MQTT broker
2020-05-03 20:46:20 +03:00
} ) ;
2024-03-04 03:03:27 +03:00
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup any .well-known folders
var p = obj . parent . path . join ( obj . parent . datapath , '.well-known' + ( ( parent . config . domains [ i ] . id == '' ) ? '' : ( '-' + parent . config . domains [ i ] . id ) ) ) ;
if ( obj . parent . fs . existsSync ( p ) ) { obj . app . use ( url + '.well-known' , obj . express . static ( p ) ) ; }
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup the alternative agent-only port
if ( obj . agentapp ) {
// Receive mesh agent connections on alternate port
obj . agentapp . ws ( url + 'agent.ashx' , function ( ws , req ) {
var domain = checkAgentIpAddress ( ws , req ) ;
if ( domain == null ) { parent . debug ( 'web' , 'Got agent connection with bad domain or blocked IP address ' + req . clientIp + ', holding.' ) ; return ; }
if ( domain . agentkey && ( ( req . query . key == null ) || ( domain . agentkey . indexOf ( req . query . key ) == - 1 ) ) ) { return ; } // If agent key is required and not provided or not valid, just hold the websocket and do nothing.
try { obj . meshAgentHandler . CreateMeshAgent ( obj , obj . db , ws , req , obj . args , domain ) ; } catch ( e ) { console . log ( e ) ; }
} ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup mesh relay on alternative agent-only port
obj . agentapp . ws ( url + 'meshrelay.ashx' , function ( ws , req ) {
PerformWSSessionAuth ( ws , req , true , function ( ws1 , req1 , domain , user , cookie , authData ) {
if ( ( ( parent . config . settings . desktopmultiplex === true ) || ( domain . desktopmultiplex === true ) ) && ( req . query . p == 2 ) ) {
obj . meshDesktopMultiplexHandler . CreateMeshRelay ( obj , ws1 , req1 , domain , user , cookie ) ; // Desktop multiplexor 1-to-n
} else {
obj . meshRelayHandler . CreateMeshRelay ( obj , ws1 , req1 , domain , user , cookie ) ; // Normal relay 1-to-1
2022-07-10 23:08:28 +03:00
}
2024-03-04 03:03:27 +03:00
} ) ;
} ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Allows agents to transfer files
obj . agentapp . ws ( url + 'devicefile.ashx' , function ( ws , req ) { obj . meshDeviceFileHandler . CreateMeshDeviceFile ( obj , ws , null , req , domain ) ; } ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup agent to/from server file transfer handler
obj . agentapp . ws ( url + 'agenttransfer.ashx' , handleAgentFileTransfer ) ; // Setup agent to/from server file transfer handler
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup agent downloads for meshcore updates
obj . agentapp . get ( url + 'meshagents' , obj . handleMeshAgentRequest ) ;
2024-08-30 15:17:14 +03:00
// Setup agent file downloads
obj . agentapp . get ( url + 'agentdownload.ashx' , handleAgentDownloadFile ) ;
2024-03-04 03:03:27 +03:00
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup web relay on this web server if needed
// We set this up when a DNS name is used as a web relay instead of a port
if ( obj . args . relaydns != null ) {
obj . webRelayRouter = require ( 'express' ) . Router ( ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// This is the magic URL that will setup the relay session
obj . webRelayRouter . get ( '/control-redirect.ashx' , function ( req , res , next ) {
if ( obj . args . relaydns . indexOf ( req . hostname ) == - 1 ) { res . sendStatus ( 404 ) ; return ; }
if ( ( req . session . userid == null ) && obj . args . user && obj . users [ 'user//' + obj . args . user . toLowerCase ( ) ] ) { req . session . userid = 'user//' + obj . args . user . toLowerCase ( ) ; } // Use a default user if needed
res . set ( { 'Cache-Control' : 'no-store' } ) ;
parent . debug ( 'web' , 'webRelaySetup' ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Decode the relay cookie
if ( req . query . c == null ) { res . sendStatus ( 404 ) ; return ; }
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Decode and check if this relay cookie is valid
var userid , domainid , domain , nodeid , addr , port , appid , webSessionId , expire , publicid ;
const urlCookie = obj . parent . decodeCookie ( req . query . c , parent . loginCookieEncryptionKey , 32 ) ; // Allow cookies up to 32 minutes old. The web page will renew this cookie every 30 minutes.
if ( urlCookie == null ) { res . sendStatus ( 404 ) ; return ; }
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Decode the incoming cookie
if ( ( urlCookie . ruserid != null ) && ( urlCookie . x != null ) ) {
if ( parent . webserver . destroyedSessions [ urlCookie . ruserid + '/' + urlCookie . x ] != null ) { res . sendStatus ( 404 ) ; return ; }
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// This is a standard user, figure out what our web relay will be.
if ( req . session . x != urlCookie . x ) { req . session . x = urlCookie . x ; } // Set the sessionid if missing
if ( req . session . userid != urlCookie . ruserid ) { req . session . userid = urlCookie . ruserid ; } // Set the session userid if missing
if ( req . session . z ) { delete req . session . z ; } // Clear the web relay guest session
userid = req . session . userid ;
domainid = userid . split ( '/' ) [ 1 ] ;
domain = parent . config . domains [ domainid ] ;
nodeid = ( ( req . query . relayid != null ) ? req . query . relayid : req . query . n ) ;
addr = ( req . query . addr != null ) ? req . query . addr : '127.0.0.1' ;
port = parseInt ( req . query . p ) ;
appid = parseInt ( req . query . appid ) ;
webSessionId = req . session . userid + '/' + req . session . x ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Check that all the required arguments are present
if ( ( req . session . userid == null ) || ( req . session . x == null ) || ( req . query . n == null ) || ( req . query . p == null ) || ( parent . webserver . destroyedSessions [ webSessionId ] != null ) || ( ( req . query . appid != 1 ) && ( req . query . appid != 2 ) ) ) { res . redirect ( '/' ) ; return ; }
} else if ( urlCookie . r == 8 ) {
// This is a guest user, figure out what our web relay will be.
userid = urlCookie . userid ;
domainid = userid . split ( '/' ) [ 1 ] ;
domain = parent . config . domains [ domainid ] ;
nodeid = urlCookie . nid ;
addr = ( urlCookie . addr != null ) ? urlCookie . addr : '127.0.0.1' ;
port = urlCookie . port ;
appid = ( urlCookie . p == 16 ) ? 2 : 1 ; // appid: 1 = HTTP, 2 = HTTPS
webSessionId = userid + '/' + urlCookie . pid ;
publicid = urlCookie . pid ;
if ( req . session . x ) { delete req . session . x ; } // Clear the web relay sessionid
if ( req . session . userid ) { delete req . session . userid ; } // Clear the web relay userid
if ( req . session . z != webSessionId ) { req . session . z = webSessionId ; } // Set the web relay guest session
expire = urlCookie . expire ;
if ( ( expire != null ) && ( expire <= Date . now ( ) ) ) { parent . debug ( 'webrelay' , 'expired link' ) ; res . sendStatus ( 404 ) ; return ; }
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// No session identifier was setup, exit now
if ( webSessionId == null ) { res . sendStatus ( 404 ) ; return ; }
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Check that we have an exact session on any of the relay DNS names
var xrelaySessionId , xrelaySession , freeRelayHost , oldestRelayTime , oldestRelayHost ;
for ( var hostIndex in obj . args . relaydns ) {
const host = obj . args . relaydns [ hostIndex ] ;
xrelaySessionId = webSessionId + '/' + host ;
xrelaySession = webRelaySessions [ xrelaySessionId ] ;
if ( xrelaySession == null ) {
// We found an unused hostname, save this as it could be useful.
if ( freeRelayHost == null ) { freeRelayHost = host ; }
2022-07-10 23:08:28 +03:00
} else {
2024-03-04 03:03:27 +03:00
// Check if we already have a relay session that matches exactly what we want
if ( ( xrelaySession . domain . id == domain . id ) && ( xrelaySession . userid == userid ) && ( xrelaySession . nodeid == nodeid ) && ( xrelaySession . addr == addr ) && ( xrelaySession . port == port ) && ( xrelaySession . appid == appid ) ) {
// We found an exact match, we are all setup already, redirect to root of that DNS name
if ( host == req . hostname ) {
// Request was made on the same host, redirect to root.
res . redirect ( '/' ) ;
} else {
// Request was made to a different host
const httpport = ( ( args . aliasport != null ) ? args . aliasport : args . port ) ;
res . redirect ( 'https://' + host + ( ( httpport != 443 ) ? ( ':' + httpport ) : '' ) + '/' ) ;
}
return ;
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Keep a record of the oldest web relay session, this could be useful.
if ( oldestRelayHost == null ) {
// Oldest host not set yet, set it
2022-07-10 21:32:59 +03:00
oldestRelayHost = host ;
oldestRelayTime = xrelaySession . lastOperation ;
2024-03-04 03:03:27 +03:00
} else {
// Check if this host is older then oldest so far
if ( oldestRelayTime > xrelaySession . lastOperation ) {
oldestRelayHost = host ;
oldestRelayTime = xrelaySession . lastOperation ;
}
2022-07-10 21:32:59 +03:00
}
}
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Check that the user has rights to access this device
parent . webserver . GetNodeWithRights ( domain , userid , nodeid , function ( node , rights , visible ) {
// If there is no remote control or relay rights, reject this web relay
if ( ( rights & 0x00200008 ) == 0 ) { res . sendStatus ( 404 ) ; return ; } // MESHRIGHT_REMOTECONTROL or MESHRIGHT_RELAY
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Check if there is a free relay DNS name we can use
var selectedHost = null ;
if ( freeRelayHost != null ) {
// There is a free one, use it.
selectedHost = freeRelayHost ;
} else {
// No free ones, close the oldest one
selectedHost = oldestRelayHost ;
2022-08-26 06:11:47 +03:00
}
2024-03-04 03:03:27 +03:00
xrelaySessionId = webSessionId + '/' + selectedHost ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
if ( selectedHost == req . hostname ) {
// If this web relay session id is not free, close it now
xrelaySession = webRelaySessions [ xrelaySessionId ] ;
if ( xrelaySession != null ) { xrelaySession . close ( ) ; delete webRelaySessions [ xrelaySessionId ] ; }
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Create a web relay session
const relaySession = require ( './apprelays.js' ) . CreateWebRelaySession ( obj , db , req , args , domain , userid , nodeid , addr , port , appid , xrelaySessionId , expire , node . mtype ) ;
relaySession . xpublicid = publicid ;
relaySession . onclose = function ( sessionId ) {
// Remove the relay session
delete webRelaySessions [ sessionId ] ;
// If there are not more relay sessions, clear the cleanup timer
if ( ( Object . keys ( webRelaySessions ) . length == 0 ) && ( obj . cleanupTimer != null ) ) { clearInterval ( webRelayCleanupTimer ) ; obj . cleanupTimer = null ; }
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Set the multi-tunnel session
webRelaySessions [ xrelaySessionId ] = relaySession ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Setup the cleanup timer if needed
if ( obj . cleanupTimer == null ) { webRelayCleanupTimer = setInterval ( checkWebRelaySessionsTimeout , 10000 ) ; }
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Redirect to root.
res . redirect ( '/' ) ;
2022-08-26 06:11:47 +03:00
} else {
2024-03-04 03:03:27 +03:00
if ( req . query . noredirect != null ) {
// No redirects allowed, fail here. This is important to make sure there is no redirect cascades
res . sendStatus ( 404 ) ;
} else {
// Request was made to a different host, redirect using the full URL so an HTTP cookie can be created on the other DNS name.
const httpport = ( ( args . aliasport != null ) ? args . aliasport : args . port ) ;
res . redirect ( 'https://' + selectedHost + ( ( httpport != 443 ) ? ( ':' + httpport ) : '' ) + req . url + '&noredirect=1' ) ;
}
2022-08-26 06:11:47 +03:00
}
2024-03-04 03:03:27 +03:00
} ) ;
2022-08-26 06:11:47 +03:00
} ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Handle all incoming requests as web relays
obj . webRelayRouter . get ( '/*' , function ( req , res ) { try { handleWebRelayRequest ( req , res ) ; } catch ( ex ) { console . log ( ex ) ; } } )
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Handle all incoming requests as web relays
obj . webRelayRouter . post ( '/*' , function ( req , res ) { try { handleWebRelayRequest ( req , res ) ; } catch ( ex ) { console . log ( ex ) ; } } )
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Handle all incoming requests as web relays
obj . webRelayRouter . put ( '/*' , function ( req , res ) { try { handleWebRelayRequest ( req , res ) ; } catch ( ex ) { console . log ( ex ) ; } } )
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Handle all incoming requests as web relays
obj . webRelayRouter . delete ( '/*' , function ( req , res ) { try { handleWebRelayRequest ( req , res ) ; } catch ( ex ) { console . log ( ex ) ; } } )
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Handle all incoming requests as web relays
obj . webRelayRouter . options ( '/*' , function ( req , res ) { try { handleWebRelayRequest ( req , res ) ; } catch ( ex ) { console . log ( ex ) ; } } )
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Handle all incoming requests as web relays
obj . webRelayRouter . head ( '/*' , function ( req , res ) { try { handleWebRelayRequest ( req , res ) ; } catch ( ex ) { console . log ( ex ) ; } } )
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Indicates to ExpressJS that the override public folder should be used to serve static files.
2024-04-12 12:43:06 +03:00
obj . app . use ( url , function ( req , res , next ) {
var domain = getDomain ( req ) ;
if ( domain . webpublicpath != null ) { // Use domain public path
obj . express . static ( domain . webpublicpath ) ( req , res , next ) ;
} else if ( obj . parent . webPublicOverridePath != null ) { // Use override path
obj . express . static ( obj . parent . webPublicOverridePath ) ( req , res , next ) ;
} else { // carry on and use default public path
next ( ) ;
}
} ) ;
2024-03-04 03:03:27 +03:00
// Indicates to ExpressJS that the default public folder should be used to serve static files.
obj . app . use ( url , obj . express . static ( obj . parent . webPublicPath ) ) ;
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Start regular disconnection list flush every 2 minutes.
obj . wsagentsDisconnectionsTimer = setInterval ( function ( ) { obj . wsagentsDisconnections = { } ; } , 120000 ) ;
}
}
function finalizeWebserver ( ) {
// Setup all HTTP handlers
setupHTTPHandlers ( )
// Handle 404 error
if ( obj . args . nice404 !== false ) {
obj . app . use ( function ( req , res , next ) {
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
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
2024-10-19 21:28:23 +03:00
res . status ( 404 ) . render ( getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'error4042' : 'error404' , req , domain ) , getRenderArgs ( { cspNonce : cspNonce } , req , domain ) ) ;
2022-07-04 04:17:57 +03:00
} ) ;
2020-03-25 23:21:14 +03:00
}
2019-08-25 21:08:32 +03:00
2024-03-04 03:03:27 +03:00
// Start server on a free port.
CheckListenPort ( obj . args . port , obj . args . portbind , StartWebServer ) ;
2019-01-23 06:34:55 +03:00
2024-03-04 03:03:27 +03:00
// Start on a second agent-only alternative port if needed.
if ( obj . args . agentport ) { CheckListenPort ( obj . args . agentport , obj . args . agentportbind , StartAltWebServer ) ; }
2019-01-23 02:40:08 +03:00
2024-03-04 03:03:27 +03:00
// We are done starting the web server.
if ( doneFunc ) doneFunc ( ) ;
2019-08-23 02:39:50 +03:00
}
2017-08-28 19:27:45 +03:00
}
2024-04-11 20:51:54 +03:00
function nice404 ( req , res ) {
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
2024-04-12 12:43:06 +03:00
if ( obj . args . nice404 == false ) { res . sendStatus ( 404 ) ; return ; }
2024-04-11 20:51:54 +03:00
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
2024-10-19 21:28:23 +03:00
res . status ( 404 ) . render ( getRenderPage ( ( domain . sitestyle == 2 || domain . sitestyle == 3 ) ? 'error4042' : 'error404' , req , domain ) , getRenderArgs ( { cspNonce : cspNonce } , req , domain ) ) ;
2024-04-11 20:51:54 +03:00
}
2022-08-18 00:14:56 +03:00
// Auth strategy flags
const domainAuthStrategyConsts = {
twitter : 1 ,
google : 2 ,
github : 3 ,
2024-03-04 03:34:01 +03:00
reddit : 8 , // Deprecated
2022-08-18 00:14:56 +03:00
azure : 16 ,
2022-09-04 22:18:53 +03:00
oidc : 32 ,
2022-08-18 00:14:56 +03:00
saml : 64 ,
intelSaml : 128 ,
jumpCloudSaml : 256
}
// Setup auth strategies for a domain
2024-03-04 03:03:27 +03:00
async function setupDomainAuthStrategy ( domain ) {
// Return binary flags representing all auth strategies that have been setup
let authStrategyFlags = 0 ;
2022-08-18 00:14:56 +03:00
// Setup auth strategies using passport if needed
if ( typeof domain . authstrategies != 'object' ) return authStrategyFlags ;
2024-03-04 03:03:27 +03:00
const url = domain . url
2022-08-18 00:14:56 +03:00
const passport = domain . passport = require ( 'passport' ) ;
passport . serializeUser ( function ( user , done ) { done ( null , user . sid ) ; } ) ;
passport . deserializeUser ( function ( sid , done ) { done ( null , { sid : sid } ) ; } ) ;
obj . app . use ( passport . initialize ( ) ) ;
2024-06-11 22:06:19 +03:00
obj . app . use ( require ( 'connect-flash' ) ( ) ) ;
2022-08-18 00:14:56 +03:00
// Twitter
if ( ( typeof domain . authstrategies . twitter == 'object' ) && ( typeof domain . authstrategies . twitter . clientid == 'string' ) && ( typeof domain . authstrategies . twitter . clientsecret == 'string' ) ) {
const TwitterStrategy = require ( 'passport-twitter' ) ;
2024-03-04 03:03:27 +03:00
let options = { consumerKey : domain . authstrategies . twitter . clientid , consumerSecret : domain . authstrategies . twitter . clientsecret } ;
2022-08-18 00:14:56 +03:00
if ( typeof domain . authstrategies . twitter . callbackurl == 'string' ) { options . callbackURL = domain . authstrategies . twitter . callbackurl ; } else { options . callbackURL = url + 'auth-twitter-callback' ; }
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Adding Twitter SSO with options: ' + JSON . stringify ( options ) ) ;
2022-08-18 00:14:56 +03:00
passport . use ( 'twitter-' + domain . id , new TwitterStrategy ( options ,
function ( token , tokenSecret , profile , cb ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Twitter profile: ' + JSON . stringify ( profile ) ) ;
2022-08-18 00:14:56 +03:00
var user = { sid : '~twitter:' + profile . id , name : profile . displayName , strategy : 'twitter' } ;
if ( ( typeof profile . emails == 'object' ) && ( profile . emails [ 0 ] != null ) && ( typeof profile . emails [ 0 ] . value == 'string' ) ) { user . email = profile . emails [ 0 ] . value ; }
return cb ( null , user ) ;
}
) ) ;
authStrategyFlags |= domainAuthStrategyConsts . twitter ;
}
// Google
if ( ( typeof domain . authstrategies . google == 'object' ) && ( typeof domain . authstrategies . google . clientid == 'string' ) && ( typeof domain . authstrategies . google . clientsecret == 'string' ) ) {
const GoogleStrategy = require ( 'passport-google-oauth20' ) ;
2024-03-04 03:03:27 +03:00
let options = { clientID : domain . authstrategies . google . clientid , clientSecret : domain . authstrategies . google . clientsecret } ;
2022-08-18 00:14:56 +03:00
if ( typeof domain . authstrategies . google . callbackurl == 'string' ) { options . callbackURL = domain . authstrategies . google . callbackurl ; } else { options . callbackURL = url + 'auth-google-callback' ; }
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Adding Google SSO with options: ' + JSON . stringify ( options ) ) ;
2022-08-18 00:14:56 +03:00
passport . use ( 'google-' + domain . id , new GoogleStrategy ( options ,
function ( token , tokenSecret , profile , cb ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Google profile: ' + JSON . stringify ( profile ) ) ;
2022-08-18 00:14:56 +03:00
var user = { sid : '~google:' + profile . id , name : profile . displayName , strategy : 'google' } ;
if ( ( typeof profile . emails == 'object' ) && ( profile . emails [ 0 ] != null ) && ( typeof profile . emails [ 0 ] . value == 'string' ) && ( profile . emails [ 0 ] . verified == true ) ) { user . email = profile . emails [ 0 ] . value ; }
return cb ( null , user ) ;
}
) ) ;
authStrategyFlags |= domainAuthStrategyConsts . google ;
}
// Github
if ( ( typeof domain . authstrategies . github == 'object' ) && ( typeof domain . authstrategies . github . clientid == 'string' ) && ( typeof domain . authstrategies . github . clientsecret == 'string' ) ) {
const GitHubStrategy = require ( 'passport-github2' ) ;
2024-03-04 03:03:27 +03:00
let options = { clientID : domain . authstrategies . github . clientid , clientSecret : domain . authstrategies . github . clientsecret } ;
2022-08-18 00:14:56 +03:00
if ( typeof domain . authstrategies . github . callbackurl == 'string' ) { options . callbackURL = domain . authstrategies . github . callbackurl ; } else { options . callbackURL = url + 'auth-github-callback' ; }
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Adding Github SSO with options: ' + JSON . stringify ( options ) ) ;
2022-08-18 00:14:56 +03:00
passport . use ( 'github-' + domain . id , new GitHubStrategy ( options ,
function ( token , tokenSecret , profile , cb ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Github profile: ' + JSON . stringify ( profile ) ) ;
2022-08-18 00:14:56 +03:00
var user = { sid : '~github:' + profile . id , name : profile . displayName , strategy : 'github' } ;
if ( ( typeof profile . emails == 'object' ) && ( profile . emails [ 0 ] != null ) && ( typeof profile . emails [ 0 ] . value == 'string' ) ) { user . email = profile . emails [ 0 ] . value ; }
return cb ( null , user ) ;
}
) ) ;
authStrategyFlags |= domainAuthStrategyConsts . github ;
}
// Azure
if ( ( typeof domain . authstrategies . azure == 'object' ) && ( typeof domain . authstrategies . azure . clientid == 'string' ) && ( typeof domain . authstrategies . azure . clientsecret == 'string' ) ) {
const AzureOAuth2Strategy = require ( 'passport-azure-oauth2' ) ;
2024-03-04 03:03:27 +03:00
let options = { clientID : domain . authstrategies . azure . clientid , clientSecret : domain . authstrategies . azure . clientsecret , tenant : domain . authstrategies . azure . tenantid } ;
2022-08-18 00:14:56 +03:00
if ( typeof domain . authstrategies . azure . callbackurl == 'string' ) { options . callbackURL = domain . authstrategies . azure . callbackurl ; } else { options . callbackURL = url + 'auth-azure-callback' ; }
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Adding Azure SSO with options: ' + JSON . stringify ( options ) ) ;
2022-08-18 00:14:56 +03:00
passport . use ( 'azure-' + domain . id , new AzureOAuth2Strategy ( options ,
function ( accessToken , refreshtoken , params , profile , done ) {
var userex = null ;
try { userex = require ( 'jwt-simple' ) . decode ( params . id _token , '' , true ) ; } catch ( ex ) { }
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Azure profile: ' + JSON . stringify ( userex ) ) ;
2022-08-18 00:14:56 +03:00
var user = null ;
if ( userex != null ) {
var user = { sid : '~azure:' + userex . unique _name , name : userex . name , strategy : 'azure' } ;
if ( typeof userex . email == 'string' ) { user . email = userex . email ; }
}
return done ( null , user ) ;
}
) ) ;
authStrategyFlags |= domainAuthStrategyConsts . azure ;
}
// Generic SAML
if ( typeof domain . authstrategies . saml == 'object' ) {
if ( ( typeof domain . authstrategies . saml . cert != 'string' ) || ( typeof domain . authstrategies . saml . idpurl != 'string' ) ) {
2024-03-04 03:03:27 +03:00
parent . debug ( 'error' , 'Missing SAML configuration.' ) ;
2022-08-18 00:14:56 +03:00
} else {
const certPath = obj . common . joinPath ( obj . parent . datapath , domain . authstrategies . saml . cert ) ;
var cert = obj . fs . readFileSync ( certPath ) ;
if ( cert == null ) {
2024-03-04 03:03:27 +03:00
parent . debug ( 'error' , 'Unable to read SAML IdP certificate: ' + domain . authstrategies . saml . cert ) ;
2022-08-18 00:14:56 +03:00
} else {
var options = { entryPoint : domain . authstrategies . saml . idpurl , issuer : 'meshcentral' } ;
if ( typeof domain . authstrategies . saml . callbackurl == 'string' ) { options . callbackUrl = domain . authstrategies . saml . callbackurl ; } else { options . callbackUrl = url + 'auth-saml-callback' ; }
if ( domain . authstrategies . saml . disablerequestedauthncontext != null ) { options . disableRequestedAuthnContext = domain . authstrategies . saml . disablerequestedauthncontext ; }
if ( typeof domain . authstrategies . saml . entityid == 'string' ) { options . issuer = domain . authstrategies . saml . entityid ; }
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Adding SAML SSO with options: ' + JSON . stringify ( options ) ) ;
2022-08-18 00:14:56 +03:00
options . cert = cert . toString ( ) . split ( '-----BEGIN CERTIFICATE-----' ) . join ( '' ) . split ( '-----END CERTIFICATE-----' ) . join ( '' ) ;
const SamlStrategy = require ( 'passport-saml' ) . Strategy ;
passport . use ( 'saml-' + domain . id , new SamlStrategy ( options ,
function ( profile , done ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'SAML profile: ' + JSON . stringify ( profile ) ) ;
2022-08-18 00:14:56 +03:00
if ( typeof profile . nameID != 'string' ) { return done ( ) ; }
var user = { sid : '~saml:' + profile . nameID , name : profile . nameID , strategy : 'saml' } ;
2022-11-06 09:50:24 +03:00
if ( typeof profile . displayname == 'string' ) {
user . name = profile . displayname ;
} else if ( ( typeof profile . firstname == 'string' ) && ( typeof profile . lastname == 'string' ) ) {
user . name = profile . firstname + ' ' + profile . lastname ;
}
2022-08-18 00:14:56 +03:00
if ( typeof profile . email == 'string' ) { user . email = profile . email ; }
return done ( null , user ) ;
}
) ) ;
2024-03-04 03:03:27 +03:00
authStrategyFlags |= domainAuthStrategyConsts . saml
2022-08-18 00:14:56 +03:00
}
}
}
// Intel SAML
if ( typeof domain . authstrategies . intel == 'object' ) {
if ( ( typeof domain . authstrategies . intel . cert != 'string' ) || ( typeof domain . authstrategies . intel . idpurl != 'string' ) ) {
2024-03-04 03:03:27 +03:00
parent . debug ( 'error' , 'Missing Intel SAML configuration.' ) ;
2022-08-18 00:14:56 +03:00
} else {
var cert = obj . fs . readFileSync ( obj . common . joinPath ( obj . parent . datapath , domain . authstrategies . intel . cert ) ) ;
if ( cert == null ) {
2024-03-04 03:03:27 +03:00
parent . debug ( 'error' , 'Unable to read Intel SAML IdP certificate: ' + domain . authstrategies . intel . cert ) ;
2022-08-18 00:14:56 +03:00
} else {
var options = { entryPoint : domain . authstrategies . intel . idpurl , issuer : 'meshcentral' } ;
if ( typeof domain . authstrategies . intel . callbackurl == 'string' ) { options . callbackUrl = domain . authstrategies . intel . callbackurl ; } else { options . callbackUrl = url + 'auth-intel-callback' ; }
if ( domain . authstrategies . intel . disablerequestedauthncontext != null ) { options . disableRequestedAuthnContext = domain . authstrategies . intel . disablerequestedauthncontext ; }
if ( typeof domain . authstrategies . intel . entityid == 'string' ) { options . issuer = domain . authstrategies . intel . entityid ; }
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Adding Intel SSO with options: ' + JSON . stringify ( options ) ) ;
2022-08-18 00:14:56 +03:00
options . cert = cert . toString ( ) . split ( '-----BEGIN CERTIFICATE-----' ) . join ( '' ) . split ( '-----END CERTIFICATE-----' ) . join ( '' ) ;
const SamlStrategy = require ( 'passport-saml' ) . Strategy ;
passport . use ( 'isaml-' + domain . id , new SamlStrategy ( options ,
function ( profile , done ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Intel profile: ' + JSON . stringify ( profile ) ) ;
2022-08-18 00:14:56 +03:00
if ( typeof profile . nameID != 'string' ) { return done ( ) ; }
var user = { sid : '~intel:' + profile . nameID , name : profile . nameID , strategy : 'intel' } ;
if ( ( typeof profile . firstname == 'string' ) && ( typeof profile . lastname == 'string' ) ) { user . name = profile . firstname + ' ' + profile . lastname ; }
else if ( ( typeof profile . FirstName == 'string' ) && ( typeof profile . LastName == 'string' ) ) { user . name = profile . FirstName + ' ' + profile . LastName ; }
if ( typeof profile . email == 'string' ) { user . email = profile . email ; }
else if ( typeof profile . EmailAddress == 'string' ) { user . email = profile . EmailAddress ; }
return done ( null , user ) ;
}
) ) ;
2024-03-04 03:03:27 +03:00
authStrategyFlags |= domainAuthStrategyConsts . intelSaml
2022-08-18 00:14:56 +03:00
}
}
}
// JumpCloud SAML
if ( typeof domain . authstrategies . jumpcloud == 'object' ) {
if ( ( typeof domain . authstrategies . jumpcloud . cert != 'string' ) || ( typeof domain . authstrategies . jumpcloud . idpurl != 'string' ) ) {
2024-03-04 03:03:27 +03:00
parent . debug ( 'error' , 'Missing JumpCloud SAML configuration.' ) ;
2022-08-18 00:14:56 +03:00
} else {
var cert = obj . fs . readFileSync ( obj . common . joinPath ( obj . parent . datapath , domain . authstrategies . jumpcloud . cert ) ) ;
if ( cert == null ) {
2024-03-04 03:03:27 +03:00
parent . debug ( 'error' , 'Unable to read JumpCloud IdP certificate: ' + domain . authstrategies . jumpcloud . cert ) ;
2022-08-18 00:14:56 +03:00
} else {
var options = { entryPoint : domain . authstrategies . jumpcloud . idpurl , issuer : 'meshcentral' } ;
if ( typeof domain . authstrategies . jumpcloud . callbackurl == 'string' ) { options . callbackUrl = domain . authstrategies . jumpcloud . callbackurl ; } else { options . callbackUrl = url + 'auth-jumpcloud-callback' ; }
if ( typeof domain . authstrategies . jumpcloud . entityid == 'string' ) { options . issuer = domain . authstrategies . jumpcloud . entityid ; }
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'Adding JumpCloud SSO with options: ' + JSON . stringify ( options ) ) ;
2022-08-18 00:14:56 +03:00
options . cert = cert . toString ( ) . split ( '-----BEGIN CERTIFICATE-----' ) . join ( '' ) . split ( '-----END CERTIFICATE-----' ) . join ( '' ) ;
const SamlStrategy = require ( 'passport-saml' ) . Strategy ;
passport . use ( 'jumpcloud-' + domain . id , new SamlStrategy ( options ,
function ( profile , done ) {
2024-03-04 03:03:27 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , 'JumpCloud profile: ' + JSON . stringify ( profile ) ) ;
2022-08-18 00:14:56 +03:00
if ( typeof profile . nameID != 'string' ) { return done ( ) ; }
var user = { sid : '~jumpcloud:' + profile . nameID , name : profile . nameID , strategy : 'jumpcloud' } ;
if ( ( typeof profile . firstname == 'string' ) && ( typeof profile . lastname == 'string' ) ) { user . name = profile . firstname + ' ' + profile . lastname ; }
if ( typeof profile . email == 'string' ) { user . email = profile . email ; }
return done ( null , user ) ;
}
) ) ;
2024-03-04 03:03:27 +03:00
authStrategyFlags |= domainAuthStrategyConsts . jumpCloudSaml
2022-08-18 00:14:56 +03:00
}
}
}
2024-03-04 03:03:27 +03:00
// Setup OpenID Connect Authentication Strategy
if ( obj . common . validateObject ( domain . authstrategies . oidc ) ) {
2024-03-10 10:44:18 +03:00
parent . authLog ( 'setupDomainAuthStrategy' , ` OIDC: Setting up strategy for domain: ${ domain . id == null ? 'default' : domain . id } ` ) ;
2024-03-04 03:03:27 +03:00
// Ensure required objects exist
let initStrategy = domain . authstrategies . oidc
if ( typeof initStrategy . issuer == 'string' ) { initStrategy . issuer = { 'issuer' : initStrategy . issuer } }
let strategy = migrateOldConfigs ( Object . assign ( { 'client' : { } , 'issuer' : { } , 'options' : { } , 'custom' : { } , 'obj' : { 'openidClient' : require ( 'openid-client' ) } } , initStrategy ) )
let preset = obj . common . validateString ( strategy . custom . preset ) ? strategy . custom . preset : null
if ( ! preset ) {
if ( typeof strategy . custom . tenant _id == 'string' ) { strategy . custom . preset = preset = 'azure' }
if ( strategy . custom . customer _id || strategy . custom . identitysource || strategy . client . client _id . split ( '.' ) [ 2 ] == 'googleusercontent' ) { strategy . custom . preset = preset = 'google' }
}
2024-03-10 10:46:01 +03:00
2024-03-04 03:03:27 +03:00
// Check issuer url
let presetIssuer
if ( preset == 'azure' ) { presetIssuer = 'https://login.microsoftonline.com/' + strategy . custom . tenant _id + '/v2.0' ; }
if ( preset == 'google' ) { presetIssuer = 'https://accounts.google.com' ; }
if ( ! obj . common . validateString ( strategy . issuer . issuer ) ) {
if ( ! preset ) {
let error = new Error ( 'OIDC: Missing issuer URI.' ) ;
parent . authLog ( 'error' , ` ${ error . message } STRATEGY: ${ JSON . stringify ( strategy ) } ` ) ;
throw error ;
} else {
strategy . issuer . issuer = presetIssuer
parent . authLog ( 'setupDomainAuthStrategy' , ` OIDC: PRESET: ${ preset . toUpperCase ( ) } : Using preset issuer: ${ presetIssuer } ` ) ;
}
} else if ( ( typeof strategy . issuer . issuer == 'string' ) && ( typeof strategy . custom . preset == 'string' ) ) {
let error = new Error ( ` OIDC: PRESET: ${ strategy . custom . preset . toUpperCase ( ) } : PRESET OVERRIDDEN: CONFIG ISSUER: ${ strategy . issuer . issuer } PRESET ISSUER: ${ presetIssuer } ` ) ;
parent . authLog ( 'setupDomainAuthStrategy' , error . message ) ;
console . warn ( error )
}
// Setup Strategy Options
strategy . custom . scope = obj . common . convertStrArray ( strategy . custom . scope , ' ' )
if ( strategy . custom . scope . length > 1 ) {
strategy . options = Object . assign ( strategy . options , { 'params' : { 'scope' : strategy . custom . scope } } )
} else {
strategy . options = Object . assign ( strategy . options , { 'params' : { 'scope' : [ 'openid' , 'profile' , 'email' ] } } )
}
if ( typeof strategy . groups == 'object' ) {
let groupScope = strategy . groups . scope || null
if ( groupScope == null ) {
if ( preset == 'azure' ) { groupScope = 'Group.Read.All' }
if ( preset == 'google' ) { groupScope = 'https://www.googleapis.com/auth/cloud-identity.groups.readonly' }
if ( typeof preset != 'string' ) { groupScope = 'groups' }
}
strategy . options . params . scope . push ( groupScope )
}
strategy . options . params . scope = strategy . options . params . scope . join ( ' ' )
// Discover additional information if available, use endpoints from config if present
let issuer
try {
parent . authLog ( 'setupDomainAuthStrategy' , ` OIDC: Discovering Issuer Endpoints: ${ strategy . issuer . issuer } ` ) ;
issuer = await strategy . obj . openidClient . Issuer . discover ( strategy . issuer . issuer ) ;
} catch ( err ) {
let error = new Error ( 'OIDC: Discovery failed.' , { cause : err } ) ;
parent . authLog ( 'setupDomainAuthStrategy' , ` ERROR: ${ JSON . stringify ( error ) } ISSUER_URI: ${ strategy . issuer . issuer } ` ) ;
throw error
}
if ( Object . keys ( strategy . issuer ) . length > 1 ) {
parent . authLog ( 'setupDomainAuthStrategy' , ` OIDC: Adding Issuer Metadata: ${ JSON . stringify ( strategy . issuer ) } ` ) ;
issuer = new strategy . obj . openidClient . Issuer ( Object . assign ( issuer ? . metadata , strategy . issuer ) ) ;
}
2024-06-04 12:26:29 +03:00
strategy . issuer = issuer ? . metadata ;
strategy . obj . issuer = issuer ;
var httpport = ( ( args . aliasport != null ) ? args . aliasport : args . port ) ;
var origin = 'https://' + ( domain . dns ? domain . dns : parent . certificates . CommonName ) ;
if ( httpport != 443 ) { origin += ':' + httpport ; }
2024-03-04 03:03:27 +03:00
// Make sure redirect_uri and post_logout_redirect_uri exist before continuing
if ( ! strategy . client . redirect _uri ) {
2024-06-04 12:26:29 +03:00
strategy . client . redirect _uri = origin + url + 'auth-oidc-callback' ;
2024-03-04 03:03:27 +03:00
}
if ( ! strategy . client . post _logout _redirect _uri ) {
2024-06-04 12:26:29 +03:00
strategy . client . post _logout _redirect _uri = origin + url + 'login' ;
2024-03-04 03:03:27 +03:00
}
// Create client and overwrite in options
let client = new issuer . Client ( strategy . client )
2024-06-01 22:31:25 +03:00
strategy . options = Object . assign ( strategy . options , { 'client' : client , sessionKey : 'oidc-' + domain . id } ) ;
2024-03-04 03:03:27 +03:00
strategy . client = client . metadata
strategy . obj . client = client
// Setup strategy and save configs for later
passport . use ( 'oidc-' + domain . id , new strategy . obj . openidClient . Strategy ( strategy . options , oidcCallback ) ) ;
2024-03-10 10:44:18 +03:00
parent . config . domains [ domain . id ] . authstrategies . oidc = strategy ;
2024-03-04 03:03:27 +03:00
parent . debug ( 'verbose' , 'OIDC: Saved Configuration: ' + JSON . stringify ( strategy ) ) ;
if ( preset ) { parent . authLog ( 'setupDomainAuthStrategy' , 'OIDC: ' + preset . toUpperCase ( ) + ': Setup Complete' ) ; }
else { parent . authLog ( 'setupDomainAuthStrategy' , 'OIDC: Setup Complete' ) ; }
authStrategyFlags |= domainAuthStrategyConsts . oidc
function migrateOldConfigs ( strategy ) {
let oldConfigs = {
'client' : {
'clientid' : 'client_id' ,
'clientsecret' : 'client_secret' ,
'callbackurl' : 'redirect_uri'
} ,
'issuer' : {
'authorizationurl' : 'authorization_endpoint' ,
'tokenurl' : 'token_endpoint' ,
'userinfourl' : 'userinfo_endpoint'
} ,
'custom' : {
'tenantid' : 'tenant_id' ,
'customerid' : 'customer_id'
}
}
for ( var type in oldConfigs ) {
for ( const [ key , value ] of Object . entries ( oldConfigs [ type ] ) ) {
if ( Object . hasOwn ( strategy , key ) ) {
if ( strategy [ type ] [ value ] && obj . common . validateString ( strategy [ type ] [ value ] ) ) {
let error = new Error ( 'OIDC: OLD CONFIG: Config conflict, new config overrides old config' ) ;
parent . authLog ( 'migrateOldConfigs' , ` ${ JSON . stringify ( error ) } OLD CONFIG: ${ key } : ${ strategy [ key ] } NEW CONFIG: ${ value } : ${ strategy [ type ] [ value ] } ` ) ;
} else {
parent . authLog ( 'migrateOldConfigs' , ` OIDC: OLD CONFIG: Moving old config to new location. strategy. ${ key } => strategy. ${ type } . ${ value } ` ) ;
strategy [ type ] [ value ] = strategy [ key ] ;
}
delete strategy [ key ]
}
}
}
if ( typeof strategy . scope == 'string' ) {
if ( ! strategy . custom . scope ) {
strategy . custom . scope = strategy . scope ;
strategy . options . params = { 'scope' : strategy . scope } ;
parent . authLog ( 'migrateOldConfigs' , ` OIDC: OLD CONFIG: Moving old config to new location. strategy.scope => strategy.custom.scope ` ) ;
} else {
let error = new Error ( 'OIDC: OLD CONFIG: Config conflict, using new config values.' ) ;
parent . authLog ( 'migrateOldConfigs' , ` ${ error . message } OLD CONFIG: strategy.scope: ${ strategy . scope } NEW CONFIG: strategy.custom.scope: ${ strategy . custom . scope } ` ) ;
parent . debug ( 'warning' , error . message )
}
delete strategy . scope
}
2024-05-21 21:04:43 +03:00
if ( strategy . groups && strategy . groups . sync && strategy . groups . sync . enabled && strategy . groups . sync . enabled === true ) {
if ( strategy . groups . sync . filter ) {
delete strategy . groups . sync . enabled ;
} else {
strategy . groups . sync = true ;
}
parent . authLog ( 'migrateOldConfigs' , ` OIDC: OLD CONFIG: Moving old config to new location. strategy.groups.sync.enabled => strategy.groups.sync ` ) ;
}
2024-03-04 03:03:27 +03:00
return strategy
}
// Callback function must be able to grab info from API's using the access token, would prefer to use the token here.
function oidcCallback ( tokenset , profile , verified ) {
// Initialize user object
let user = { 'strategy' : 'oidc' }
2024-05-17 22:01:12 +03:00
let claims = obj . common . validateObject ( strategy . custom . claims ) ? strategy . custom . claims : null ;
user . sid = obj . common . validateString ( profile . sub ) ? '~oidc:' + profile . sub : null ;
user . name = obj . common . validateString ( profile . name ) ? profile . name : null ;
user . email = obj . common . validateString ( profile . email ) ? profile . email : null ;
2024-03-04 03:03:27 +03:00
if ( claims != null ) {
2024-05-17 22:01:12 +03:00
user . sid = obj . common . validateString ( profile [ claims . uuid ] ) ? '~oidc:' + profile [ claims . uuid ] : user . sid ;
user . name = obj . common . validateString ( profile [ claims . name ] ) ? profile [ claims . name ] : user . name ;
user . email = obj . common . validateString ( profile [ claims . email ] ) ? profile [ claims . email ] : user . email ;
}
user . emailVerified = profile . email _verified ? profile . email _verified : obj . common . validateEmail ( user . email ) ;
user . groups = obj . common . validateStrArray ( profile . groups , 1 ) ? profile . groups : null ;
user . preset = obj . common . validateString ( strategy . custom . preset ) ? strategy . custom . preset : null ;
if ( strategy . groups && obj . common . validateString ( strategy . groups . claim ) ) {
2024-03-04 03:03:27 +03:00
user . groups = obj . common . validateStrArray ( profile [ strategy . groups . claim ] , 1 ) ? profile [ strategy . groups . claim ] : null
}
// Setup end session enpoint if not already configured this requires an auth token
try {
if ( ! strategy . issuer . end _session _endpoint ) {
strategy . issuer . end _session _endpoint = strategy . obj . client . endSessionUrl ( { 'id_token_hint' : tokenset } )
2024-03-10 10:44:18 +03:00
parent . authLog ( 'oidcCallback' , ` OIDC: Discovered end_session_endpoint: ${ strategy . issuer . end _session _endpoint } ` ) ;
2024-03-04 03:03:27 +03:00
}
} catch ( err ) {
let error = new Error ( 'OIDC: Discovering end_session_endpoint failed. Using Default.' , { cause : err } ) ;
strategy . issuer . end _session _endpoint = strategy . issuer . issuer + '/logout' ;
parent . debug ( 'error' , ` ${ error . message } end_session_endpoint: ${ strategy . issuer . end _session _endpoint } post_logout_redirect_uri: ${ strategy . client . post _logout _redirect _uri } TOKENSET: ${ JSON . stringify ( tokenset ) } ` ) ;
parent . authLog ( 'oidcCallback' , error . message ) ;
}
// Setup presets and groups, get groups from API if needed then return
if ( strategy . groups && typeof user . preset == 'string' ) {
getGroups ( user . preset , tokenset ) . then ( ( groups ) => {
user = Object . assign ( user , { 'groups' : groups } ) ;
return verified ( null , user ) ;
} ) . catch ( ( err ) => {
let error = new Error ( 'OIDC: GROUPS: No groups found due to error:' , { cause : err } ) ;
parent . debug ( 'error' , ` ${ JSON . stringify ( error ) } ` ) ;
parent . authLog ( 'oidcCallback' , error . message ) ;
user . groups = [ ] ;
return verified ( null , user ) ;
} ) ;
} else {
return verified ( null , user ) ;
}
async function getGroups ( preset , tokenset ) {
let url = '' ;
if ( preset == 'azure' ) { url = strategy . groups . recursive == true ? 'https://graph.microsoft.com/v1.0/me/transitiveMemberOf' : 'https://graph.microsoft.com/v1.0/me/memberOf' ; }
if ( preset == 'google' ) { url = strategy . custom . customer _id ? 'https://cloudidentity.googleapis.com/v1/groups?parent=customers/' + strategy . custom . customer _id : strategy . custom . identitysource ? 'https://cloudidentity.googleapis.com/v1/groups?parent=identitysources/' + strategy . custom . identitysource : null ; }
return new Promise ( ( resolve , reject ) => {
const options = {
'headers' : { authorization : 'Bearer ' + tokenset . access _token }
}
const req = require ( 'https' ) . get ( url , options , ( res ) => {
let data = [ ]
res . on ( 'data' , ( chunk ) => {
data . push ( chunk ) ;
} ) ;
res . on ( 'end' , ( ) => {
if ( res . statusCode < 200 || res . statusCode >= 300 ) {
let error = new Error ( 'OIDC: GROUPS: Bad response code from API, statusCode: ' + res . statusCode ) ;
parent . authLog ( 'getGroups' , ` ERROR: ${ error . message } URL: ${ url } OPTIONS: ${ JSON . stringify ( options ) } ` ) ;
console . error ( error ) ;
reject ( error ) ;
}
if ( data . length == 0 ) {
let error = new Error ( 'OIDC: GROUPS: Getting groups from API failed, request returned no data in response.' ) ;
parent . authLog ( 'getGroups' , ` ERROR: ${ error . message } URL: ${ url } OPTIONS: ${ JSON . stringify ( options ) } ` ) ;
console . error ( error ) ;
reject ( error ) ;
}
try {
if ( Buffer . isBuffer ( data [ 0 ] ) ) {
data = Buffer . concat ( data ) ;
data = data . toString ( ) ;
} else { // else if (typeof data[0] == 'string')
data = data . join ( ) ;
}
} catch ( err ) {
let error = new Error ( 'OIDC: GROUPS: Getting groups from API failed. Error joining response data.' , { cause : err } ) ;
parent . authLog ( 'getGroups' , ` ERROR: ${ error . message } URL: ${ url } OPTIONS: ${ JSON . stringify ( options ) } ` ) ;
console . error ( error ) ;
reject ( error ) ;
}
if ( preset == 'azure' ) {
data = JSON . parse ( data ) ;
if ( data . error ) {
let error = new Error ( 'OIDC: GROUPS: Getting groups from API failed. Error joining response data.' , { cause : data . error } ) ;
parent . authLog ( 'getGroups' , ` ERROR: ${ error . message } URL: ${ url } OPTIONS: ${ JSON . stringify ( options ) } ` ) ;
console . error ( error ) ;
reject ( error ) ;
}
data = data . value ;
}
if ( preset == 'google' ) {
data = data . split ( '\n' ) ;
data = data . join ( '' ) ;
data = JSON . parse ( data ) ;
data = data . groups ;
}
let groups = [ ]
for ( var i in data ) {
if ( typeof data [ i ] . displayName == 'string' ) {
groups . push ( data [ i ] . displayName ) ;
}
}
if ( groups . length == 0 ) {
let warn = new Error ( 'OIDC: GROUPS: No groups returned from API.' ) ;
parent . authLog ( 'getGroups' , ` WARN: ${ warn . message } DATA: ${ data } ` ) ;
console . warn ( warn ) ;
resolve ( groups ) ;
} else {
resolve ( groups ) ;
}
} ) ;
} ) ;
req . on ( 'error' , ( err ) => {
let error = new Error ( 'OIDC: GROUPS: Request error.' , { cause : err } ) ;
parent . authLog ( 'getGroups' , ` ERROR: ${ error . message } URL: ${ url } OPTIONS: ${ JSON . stringify ( options ) } ` ) ;
console . error ( error ) ;
reject ( error ) ;
} ) ;
req . end ( ) ;
} ) ;
}
}
}
2022-08-18 00:14:56 +03:00
return authStrategyFlags ;
}
2022-07-04 04:17:57 +03:00
// Handle an incoming request as a web relay
function handleWebRelayRequest ( req , res ) {
2022-08-26 06:11:47 +03:00
var webRelaySessionId = null ;
if ( ( req . session . userid != null ) && ( req . session . x != null ) ) { webRelaySessionId = req . session . userid + '/' + req . session . x ; }
else if ( req . session . z != null ) { webRelaySessionId = req . session . z ; }
if ( ( webRelaySessionId != null ) && ( obj . destroyedSessions [ webRelaySessionId ] == null ) ) {
var relaySession = webRelaySessions [ webRelaySessionId + '/' + req . hostname ] ;
2022-07-04 04:17:57 +03:00
if ( relaySession != null ) {
// The web relay session is valid, use it
relaySession . handleRequest ( req , res ) ;
} else {
2022-09-07 17:40:36 +03:00
// No web relay session with this relay identifier, close the HTTP request.
2022-07-04 04:17:57 +03:00
res . sendStatus ( 404 ) ;
}
} else {
// The user is not logged in or does not have a relay identifier, close the HTTP request.
res . sendStatus ( 404 ) ;
}
}
// Handle an incoming websocket connection as a web relay
function handleWebRelayWebSocket ( ws , req ) {
2022-08-26 06:11:47 +03:00
var webRelaySessionId = null ;
if ( ( req . session . userid != null ) && ( req . session . x != null ) ) { webRelaySessionId = req . session . userid + '/' + req . session . x ; }
else if ( req . session . z != null ) { webRelaySessionId = req . session . z ; }
if ( ( webRelaySessionId != null ) && ( obj . destroyedSessions [ webRelaySessionId ] == null ) ) {
var relaySession = webRelaySessions [ webRelaySessionId + '/' + req . hostname ] ;
2022-07-04 04:17:57 +03:00
if ( relaySession != null ) {
// The multi-tunnel session is valid, use it
relaySession . handleWebSocket ( ws , req ) ;
} else {
// No multi-tunnel session with this relay identifier, close the websocket.
ws . close ( ) ;
}
} else {
// The user is not logged in or does not have a relay identifier, close the websocket.
ws . close ( ) ;
}
}
2021-04-03 03:20:36 +03:00
// Perform server inner authentication
// This is a type of server authentication where the client will open the socket regardless of the TLS certificate and request that the server
// sign a client nonce with the server agent cert and return the response. Only after that will the client send the client authentication username
// and password or authentication cookie.
function PerformWSSessionInnerAuth ( ws , req , domain , func ) {
// When data is received from the web socket
ws . on ( 'message' , function ( data ) {
var command ;
try { command = JSON . parse ( data . toString ( 'utf8' ) ) ; } catch ( e ) { return ; }
if ( obj . common . validateString ( command . action , 3 , 32 ) == false ) return ; // Action must be a string between 3 and 32 chars
switch ( command . action ) {
case 'serverAuth' : { // This command is used to perform server "inner" authentication.
2021-05-26 08:30:33 +03:00
// Check the client nonce and TLS hash
if ( ( obj . common . validateString ( command . cnonce , 1 , 256 ) == false ) || ( obj . common . validateString ( command . tlshash , 1 , 512 ) == false ) ) {
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'badargs' } ) ) ; } catch ( ex ) { }
try { ws . close ( ) ; } catch ( ex ) { }
2022-07-20 22:57:24 +03:00
break ;
2021-05-26 08:30:33 +03:00
}
2021-04-03 03:20:36 +03:00
// Check that the TLS hash is an acceptable one.
var h = Buffer . from ( command . tlshash , 'hex' ) . toString ( 'binary' ) ;
2021-05-26 08:30:33 +03:00
if ( ( obj . webCertificateHashs [ domain . id ] != h ) && ( obj . webCertificateFullHashs [ domain . id ] != h ) && ( obj . defaultWebCertificateHash != h ) && ( obj . defaultWebCertificateFullHash != h ) ) {
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'badtlscert' } ) ) ; } catch ( ex ) { }
try { ws . close ( ) ; } catch ( ex ) { }
return ;
}
2021-04-03 03:20:36 +03:00
// TLS hash check is a success, sign the request.
// Perform the hash signature using the server agent certificate
var nonce = obj . crypto . randomBytes ( 48 ) ;
var signData = Buffer . from ( command . cnonce , 'base64' ) . toString ( 'binary' ) + h + nonce . toString ( 'binary' ) ; // Client Nonce + TLS Hash + Server Nonce
parent . certificateOperations . acceleratorPerformSignature ( 0 , signData , null , function ( tag , signature ) {
// Send back our certificate + nonce + signature
ws . send ( JSON . stringify ( { 'action' : 'serverAuth' , 'cert' : Buffer . from ( obj . agentCertificateAsn1 , 'binary' ) . toString ( 'base64' ) , 'nonce' : nonce . toString ( 'base64' ) , 'signature' : Buffer . from ( signature , 'binary' ) . toString ( 'base64' ) } ) ) ;
} ) ;
break ;
}
case 'userAuth' : { // This command is used to perform user authentication.
// Check username and password authentication
if ( ( typeof command . username == 'string' ) && ( typeof command . password == 'string' ) ) {
2021-04-16 22:42:54 +03:00
obj . authenticate ( Buffer . from ( command . username , 'base64' ) . toString ( ) , Buffer . from ( command . password , 'base64' ) . toString ( ) , domain , function ( err , userid , passhint , loginOptions ) {
2021-05-26 08:30:33 +03:00
if ( ( err != null ) || ( userid == null ) ) {
// Invalid authentication
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2c' } ) ) ; } catch ( ex ) { }
try { ws . close ( ) ; } catch ( ex ) { }
} else {
var user = obj . users [ userid ] ;
if ( ( err == null ) && ( user ) ) {
// Check if a 2nd factor is needed
2022-02-04 18:29:18 +03:00
const emailcheck = ( ( domain . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
2021-05-26 08:30:33 +03:00
// See if we support two-factor trusted cookies
var twoFactorCookieDays = 30 ;
if ( typeof domain . twofactorcookiedurationdays == 'number' ) { twoFactorCookieDays = domain . twofactorcookiedurationdays ; }
2022-03-05 02:56:19 +03:00
// Check if two factor can be skipped
const twoFactorSkip = checkUserOneTimePasswordSkip ( domain , user , req , loginOptions ) ;
if ( ( twoFactorSkip == null ) && ( checkUserOneTimePasswordRequired ( domain , user , req , loginOptions ) == true ) ) {
2021-05-26 08:30:33 +03:00
// Figure out if email 2FA is allowed
var email2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( domain . mailserver != null ) && ( user . otpekey != null ) ) ;
var sms2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) && ( parent . smsserver != null ) && ( user . phone != null ) ) ;
2022-10-22 22:59:12 +03:00
var msg2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( user . msghandle != null ) ) ;
2021-05-26 08:30:33 +03:00
//var push2fa = ((parent.firebase != null) && (user.otpdev != null));
if ( ( typeof command . token != 'string' ) || ( command . token == '**email**' ) || ( command . token == '**sms**' ) /* || (command.token == '**push**')*/ ) {
if ( ( command . token == '**email**' ) && ( email2fa == true ) ) {
// Cause a token to be sent to the user's registered email
user . otpekey = { k : obj . common . zeroPad ( getRandomEightDigitInteger ( ) , 8 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA email to: ' + user . email ) ;
domain . mailserver . sendAccountLoginMail ( domain , user . email , user . otpekey . k , obj . getLanguageCodes ( req ) , req . query . key ) ;
// Ask for a login token & confirm email was sent
2022-10-22 22:59:12 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , email2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2021-05-26 08:30:33 +03:00
} else if ( ( command . token == '**sms**' ) && ( sms2fa == true ) ) {
// Cause a token to be sent to the user's phone number
user . otpsms = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA SMS to: ' + user . phone ) ;
parent . smsserver . sendToken ( domain , user . phone , user . otpsms . k , obj . getLanguageCodes ( req ) ) ;
// Ask for a login token & confirm sms was sent
2022-10-22 22:59:12 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , sms2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else if ( ( command . token == '**msg**' ) && ( msg2fa == true ) ) {
// Cause a token to be sent to the user's messenger account
user . otpmsg = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA message to: ' + user . phone ) ;
parent . msgserver . sendToken ( domain , user . msghandle , user . otpmsg . k , obj . getLanguageCodes ( req ) ) ;
// Ask for a login token & confirm sms was sent
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , msg2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2021-05-26 08:30:33 +03:00
/ *
} else if ( ( command . token == '**push**' ) && ( push2fa == true ) ) {
// Cause push notification to device
const code = Buffer . from ( obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) ) . toString ( 'base64' ) ;
const authCookie = parent . encodeCookie ( { a : 'checkAuth' , c : code , u : user . _id , n : user . otpdev } ) ;
var payload = { notification : { title : "MeshCentral" , body : user . name + " authentication" } , data : { url : '2fa://auth?code=' + code + '&c=' + authCookie } } ;
var options = { priority : 'High' , timeToLive : 60 } ; // TTL: 1 minute
parent . firebase . sendToDevice ( user . otpdev , payload , options , function ( id , err , errdesc ) {
if ( err == null ) { parent . debug ( 'email' , 'Successfully auth check send push message to device' ) ; } else { parent . debug ( 'email' , 'Failed auth check push message to device, error: ' + errdesc ) ; }
} ) ;
* /
2021-04-03 03:20:36 +03:00
} else {
2021-05-26 08:30:33 +03:00
// Ask for a login token
parent . debug ( 'web' , 'Asking for login token' ) ;
2022-10-24 17:13:20 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( ex ) { console . log ( ex ) ; }
2021-05-26 08:30:33 +03:00
}
} else {
2022-03-05 02:56:19 +03:00
checkUserOneTimePassword ( req , domain , user , command . token , null , function ( result , authData ) {
2021-05-26 08:30:33 +03:00
if ( result == false ) {
// Failed, ask for a login token again
parent . debug ( 'web' , 'Invalid login token, asking again' ) ;
2022-10-24 17:13:20 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2021-04-03 03:20:36 +03:00
} else {
2021-05-26 08:30:33 +03:00
// We are authenticated with 2nd factor.
// Check email verification
2021-11-25 21:09:11 +03:00
if ( emailcheck && ( user . email != null ) && ( ! ( user . _id . split ( '/' ) [ 2 ] . startsWith ( '~' ) ) ) && ( user . emailVerified !== true ) ) {
2021-05-26 08:30:33 +03:00
parent . debug ( 'web' , 'Invalid login, asking for email validation' ) ;
2022-10-24 17:13:20 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
2021-05-26 08:30:33 +03:00
} else {
// We are authenticated
ws . _socket . pause ( ) ;
ws . removeAllListeners ( [ 'message' , 'close' , 'error' ] ) ;
2022-03-05 02:56:19 +03:00
func ( ws , req , domain , user , authData ) ;
2021-05-26 08:30:33 +03:00
}
2021-04-03 03:20:36 +03:00
}
2021-05-26 08:30:33 +03:00
} ) ;
}
2021-04-03 03:20:36 +03:00
} else {
2021-05-26 08:30:33 +03:00
// Check email verification
2021-11-25 21:09:11 +03:00
if ( emailcheck && ( user . email != null ) && ( ! ( user . _id . split ( '/' ) [ 2 ] . startsWith ( '~' ) ) ) && ( user . emailVerified !== true ) ) {
2021-05-26 08:30:33 +03:00
parent . debug ( 'web' , 'Invalid login, asking for email validation' ) ;
var email2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( domain . mailserver != null ) && ( user . otpekey != null ) ) ;
var sms2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) && ( parent . smsserver != null ) && ( user . phone != null ) ) ;
2022-10-22 22:59:12 +03:00
var msg2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( user . msghandle != null ) ) ;
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
2021-05-26 08:30:33 +03:00
} else {
// We are authenticated
ws . _socket . pause ( ) ;
ws . removeAllListeners ( [ 'message' , 'close' , 'error' ] ) ;
2022-03-05 02:56:19 +03:00
func ( ws , req , domain , user , twoFactorSkip ) ;
2021-05-26 08:30:33 +03:00
}
2021-04-03 03:20:36 +03:00
}
}
}
} ) ;
} else {
// Invalid authentication
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2c' } ) ) ; } catch ( ex ) { }
try { ws . close ( ) ; } catch ( ex ) { }
}
break ;
}
}
} ) ;
// If error, do nothing
ws . on ( 'error' , function ( err ) { try { ws . close ( ) ; } catch ( e ) { console . log ( e ) ; } } ) ;
// If the web socket is closed
ws . on ( 'close' , function ( req ) { try { ws . close ( ) ; } catch ( e ) { console . log ( e ) ; } } ) ;
// Resume the socket to perform inner authentication
try { ws . _socket . resume ( ) ; } catch ( ex ) { }
}
2018-10-14 02:04:04 +03:00
// Authenticates a session and forwards
2018-10-16 03:51:03 +03:00
function PerformWSSessionAuth ( ws , req , noAuthOk , func ) {
2021-04-17 04:55:03 +03:00
// Check if the session expired
if ( ( req . session != null ) && ( typeof req . session . expire == 'number' ) && ( req . session . expire <= Date . now ( ) ) ) {
parent . debug ( 'web' , 'WSERROR: Session expired.' ) ; try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'expired' , msg : 'expired-1' } ) ) ; ws . close ( ) ; } catch ( e ) { } return ;
}
2019-11-19 04:31:42 +03:00
// Check if this is a banned ip address
2020-05-17 00:07:53 +03:00
if ( obj . checkAllowLogin ( req ) == false ) { parent . debug ( 'web' , 'WSERROR: Banned connection.' ) ; try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'banned' , msg : 'banned-1' } ) ) ; ws . close ( ) ; } catch ( e ) { } return ; }
2018-10-14 02:04:04 +03:00
try {
2018-10-16 20:52:05 +03:00
// Hold this websocket until we are ready.
2018-12-01 08:23:10 +03:00
ws . _socket . pause ( ) ;
2018-10-16 20:52:05 +03:00
2018-10-16 03:21:37 +03:00
// Check IP filtering and domain
2019-01-05 01:35:01 +03:00
var domain = null ;
2020-03-26 22:06:34 +03:00
if ( noAuthOk == true ) {
domain = getDomain ( req ) ;
2020-05-17 00:07:53 +03:00
if ( domain == null ) { parent . debug ( 'web' , 'WSERROR: Got no domain, no auth ok.' ) ; try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-1' } ) ) ; ws . close ( ) ; return ; } catch ( e ) { } return ; }
2020-03-26 22:06:34 +03:00
} else {
// If authentication is required, enforce IP address filtering.
domain = checkUserIpAddress ( ws , req ) ;
2020-05-17 00:07:53 +03:00
if ( domain == null ) { parent . debug ( 'web' , 'WSERROR: Got no domain, user auth required.' ) ; return ; }
2020-03-26 22:06:34 +03:00
}
2018-10-16 03:21:37 +03:00
2021-04-03 03:26:17 +03:00
// Check if inner authentication is requested
if ( req . headers [ 'x-meshauth' ] === '*' ) { func ( ws , req , domain , null ) ; return ; }
2022-02-04 18:29:18 +03:00
const emailcheck = ( ( domain . mailserver != null ) && ( obj . parent . certificates . CommonName != null ) && ( obj . parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) && ( obj . args . lanonly != true ) && ( domain . auth != 'sspi' ) && ( domain . auth != 'ldap' ) )
2020-04-21 11:50:27 +03:00
2018-10-16 03:21:37 +03:00
// A web socket session can be authenticated in many ways (Default user, session, user/pass and cookie). Check authentication here.
if ( ( req . query . user != null ) && ( req . query . pass != null ) ) {
// A user/pass is provided in URL arguments
2021-04-16 22:42:54 +03:00
obj . authenticate ( req . query . user , req . query . pass , domain , function ( err , userid , passhint , loginOptions ) {
2021-06-23 05:18:20 +03:00
var user = obj . users [ userid ] ;
2021-06-04 22:46:41 +03:00
// Check if user as the "notools" site right. If so, deny this connection as tools are not allowed to connect.
2021-06-15 03:48:55 +03:00
if ( ( user != null ) && ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & SITERIGHT _NOMESHCMD ) ) {
2021-06-04 22:46:41 +03:00
// No tools allowed, close the websocket connection
parent . debug ( 'web' , 'ERR: Websocket no tools allowed' ) ;
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'notools' , msg : 'notools' } ) ) ; ws . close ( ) ; } catch ( e ) { }
return ;
}
2020-09-17 02:16:58 +03:00
// See if we support two-factor trusted cookies
var twoFactorCookieDays = 30 ;
if ( typeof domain . twofactorcookiedurationdays == 'number' ) { twoFactorCookieDays = domain . twofactorcookiedurationdays ; }
2019-03-21 21:45:08 +03:00
if ( ( err == null ) && ( user ) ) {
// Check if a 2nd factor is needed
2021-04-16 22:42:54 +03:00
if ( checkUserOneTimePasswordRequired ( domain , user , req , loginOptions ) == true ) {
2020-03-22 08:52:23 +03:00
// Figure out if email 2FA is allowed
2021-02-10 22:28:21 +03:00
var email2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( domain . mailserver != null ) && ( user . otpekey != null ) ) ;
2020-04-23 01:29:26 +03:00
var sms2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) && ( parent . smsserver != null ) && ( user . phone != null ) ) ;
2022-10-22 22:59:12 +03:00
var msg2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( user . msghandle != null ) ) ;
2021-04-14 09:23:09 +03:00
//var push2fa = ((parent.firebase != null) && (user.otpdev != null));
if ( ( typeof req . query . token != 'string' ) || ( req . query . token == '**email**' ) || ( req . query . token == '**sms**' ) /* || (req.query.token == '**push**')*/ ) {
2020-03-22 08:52:23 +03:00
if ( ( req . query . token == '**email**' ) && ( email2fa == true ) ) {
// Cause a token to be sent to the user's registered email
user . otpekey = { k : obj . common . zeroPad ( getRandomEightDigitInteger ( ) , 8 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA email to: ' + user . email ) ;
2021-02-10 22:28:21 +03:00
domain . mailserver . sendAccountLoginMail ( domain , user . email , user . otpekey . k , obj . getLanguageCodes ( req ) , req . query . key ) ;
2020-03-22 08:52:23 +03:00
// Ask for a login token & confirm email was sent
2022-10-22 22:59:12 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , email2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2020-04-23 01:29:26 +03:00
} else if ( ( req . query . token == '**sms**' ) && ( sms2fa == true ) ) {
// Cause a token to be sent to the user's phone number
user . otpsms = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA SMS to: ' + user . phone ) ;
parent . smsserver . sendToken ( domain , user . phone , user . otpsms . k , obj . getLanguageCodes ( req ) ) ;
// Ask for a login token & confirm sms was sent
2022-10-22 22:59:12 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , sms2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else if ( ( req . query . token == '**msg**' ) && ( msg2fa == true ) ) {
// Cause a token to be sent to the user's messenger account
user . otpmsg = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA message to: ' + user . msghandle ) ;
parent . msgserver . sendToken ( domain , user . msghandle , user . otpmsg . k , obj . getLanguageCodes ( req ) ) ;
// Ask for a login token & confirm message was sent
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , msg2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2021-04-14 09:23:09 +03:00
/ *
} else if ( ( command . token == '**push**' ) && ( push2fa == true ) ) {
// Cause push notification to device
const code = Buffer . from ( obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) ) . toString ( 'base64' ) ;
const authCookie = parent . encodeCookie ( { a : 'checkAuth' , c : code , u : user . _id , n : user . otpdev } ) ;
var payload = { notification : { title : "MeshCentral" , body : user . name + " authentication" } , data : { url : '2fa://auth?code=' + code + '&c=' + authCookie } } ;
var options = { priority : 'High' , timeToLive : 60 } ; // TTL: 1 minute
parent . firebase . sendToDevice ( user . otpdev , payload , options , function ( id , err , errdesc ) {
if ( err == null ) { parent . debug ( 'email' , 'Successfully auth check send push message to device' ) ; } else { parent . debug ( 'email' , 'Failed auth check push message to device, error: ' + errdesc ) ; }
} ) ;
* /
2020-03-22 08:52:23 +03:00
} else {
// Ask for a login token
2020-05-27 10:52:31 +03:00
parent . debug ( 'web' , 'Asking for login token' ) ;
2022-10-24 17:13:20 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2020-03-22 08:52:23 +03:00
}
2019-03-21 21:45:08 +03:00
} else {
2022-03-05 02:56:19 +03:00
checkUserOneTimePassword ( req , domain , user , req . query . token , null , function ( result , authData ) {
2019-03-21 21:45:08 +03:00
if ( result == false ) {
2020-03-22 08:52:23 +03:00
// Failed, ask for a login token again
2020-05-27 10:52:31 +03:00
parent . debug ( 'web' , 'Invalid login token, asking again' ) ;
2022-10-24 17:13:20 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2019-03-21 21:45:08 +03:00
} else {
// We are authenticated with 2nd factor.
2020-04-21 11:50:27 +03:00
// Check email verification
2021-11-25 21:09:11 +03:00
if ( emailcheck && ( user . email != null ) && ( ! ( user . _id . split ( '/' ) [ 2 ] . startsWith ( '~' ) ) ) && ( user . emailVerified !== true ) ) {
2020-05-27 10:52:31 +03:00
parent . debug ( 'web' , 'Invalid login, asking for email validation' ) ;
2022-10-24 17:13:20 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
2020-04-21 11:50:27 +03:00
} else {
2022-03-05 02:56:19 +03:00
func ( ws , req , domain , user , null , authData ) ;
2020-04-21 11:50:27 +03:00
}
2019-03-21 21:45:08 +03:00
}
} ) ;
}
} else {
2020-04-21 11:50:27 +03:00
// Check email verification
2021-11-25 21:09:11 +03:00
if ( emailcheck && ( user . email != null ) && ( ! ( user . _id . split ( '/' ) [ 2 ] . startsWith ( '~' ) ) ) && ( user . emailVerified !== true ) ) {
2020-05-27 10:52:31 +03:00
parent . debug ( 'web' , 'Invalid login, asking for email validation' ) ;
2021-02-10 22:28:21 +03:00
var email2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( domain . mailserver != null ) && ( user . otpekey != null ) ) ;
2020-12-22 10:25:30 +03:00
var sms2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) && ( parent . smsserver != null ) && ( user . phone != null ) ) ;
2022-10-22 22:59:12 +03:00
var msg2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( user . msghandle != null ) ) ;
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
2020-04-21 11:50:27 +03:00
} else {
// We are authenticated
func ( ws , req , domain , user ) ;
}
2019-03-21 21:45:08 +03:00
}
2018-10-14 02:04:04 +03:00
} else {
2018-10-16 20:52:05 +03:00
// Failed to authenticate, see if a default user is active
if ( obj . args . user && obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) {
// A default user is active
func ( ws , req , domain , obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) ;
} else {
// If not authenticated, close the websocket connection
2019-08-23 01:31:39 +03:00
parent . debug ( 'web' , 'ERR: Websocket bad user/pass auth' ) ;
2020-05-22 07:25:11 +03:00
//obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + obj.args.user.toLowerCase()], obj, { action: 'authfail', userid: 'user/' + domain.id + '/' + obj.args.user.toLowerCase(), username: obj.args.user, domain: domain.id, msg: 'Invalid user login attempt from ' + req.clientIp });
2019-11-19 04:31:42 +03:00
//obj.setbadLogin(req);
2021-01-08 10:35:11 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2a' } ) ) ; ws . close ( ) ; } catch ( e ) { }
2018-10-16 20:52:05 +03:00
}
2018-10-14 02:04:04 +03:00
}
2018-10-16 03:21:37 +03:00
} ) ;
return ;
2022-05-20 00:41:32 +03:00
}
if ( ( req . query . auth != null ) && ( req . query . auth != '' ) ) {
2018-10-16 03:21:37 +03:00
// This is a encrypted cookie authentication
2020-09-17 02:16:58 +03:00
var cookie = obj . parent . decodeCookie ( req . query . auth , obj . parent . loginCookieEncryptionKey , 60 ) ; // Cookie with 1 hour timeout
if ( ( cookie == null ) && ( obj . parent . multiServer != null ) ) { cookie = obj . parent . decodeCookie ( req . query . auth , obj . parent . serverKey , 60 ) ; } // Try the server key
2022-04-13 00:03:52 +03:00
if ( ( cookie != null ) && ( cookie . ip != null ) && ! checkCookieIp ( cookie . ip , req . clientIp ) ) { // If the cookie if binded to an IP address, check here.
2020-05-22 07:25:11 +03:00
parent . debug ( 'web' , 'ERR: Invalid cookie IP address, got \"' + cookie . ip + '\", expected \"' + cleanRemoteAddr ( req . clientIp ) + '\".' ) ;
2020-01-26 21:05:27 +03:00
cookie = null ;
}
2021-09-10 23:04:10 +03:00
if ( ( cookie != null ) && ( cookie . userid != null ) && ( obj . users [ cookie . userid ] ) && ( cookie . domainid == domain . id ) && ( cookie . userid . split ( '/' ) [ 1 ] == domain . id ) ) {
// Valid cookie, we are authenticated. Cookie of format { userid: 'user//name', domain: '' }
2018-10-16 03:21:37 +03:00
func ( ws , req , domain , obj . users [ cookie . userid ] , cookie ) ;
2022-05-20 00:41:32 +03:00
return ;
2021-09-10 23:04:10 +03:00
} else if ( ( cookie != null ) && ( cookie . a === 3 ) && ( typeof cookie . u == 'string' ) && ( obj . users [ cookie . u ] ) && ( cookie . u . split ( '/' ) [ 1 ] == domain . id ) ) {
// Valid cookie, we are authenticated. Cookie of format { u: 'user//name', a: 3 }
func ( ws , req , domain , obj . users [ cookie . u ] , cookie ) ;
2022-05-20 00:41:32 +03:00
return ;
2021-11-11 00:21:30 +03:00
} else if ( ( cookie != null ) && ( cookie . nouser === 1 ) ) {
// This is a valid cookie, but no user. This is used for agent self-sharing.
func ( ws , req , domain , null , cookie ) ;
2022-05-20 00:41:32 +03:00
return ;
} / * else {
2019-01-29 02:47:54 +03:00
// This is a bad cookie, keep going anyway, maybe we have a active session that will save us.
2020-01-26 21:05:27 +03:00
if ( ( cookie != null ) && ( cookie . domainid != domain . id ) ) { parent . debug ( 'web' , 'ERR: Invalid domain, got \"' + cookie . domainid + '\", expected \"' + domain . id + '\".' ) ; }
parent . debug ( 'web' , 'ERR: Websocket bad cookie auth (Cookie:' + ( cookie != null ) + '): ' + req . query . auth ) ;
2021-01-08 10:35:11 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2b' } ) ) ; ws . close ( ) ; } catch ( e ) { }
2022-05-20 00:41:32 +03:00
return ;
2018-10-14 02:04:04 +03:00
}
2022-05-20 00:41:32 +03:00
* /
}
if ( req . headers [ 'x-meshauth' ] != null ) {
2019-03-21 21:45:08 +03:00
// This is authentication using a custom HTTP header
var s = req . headers [ 'x-meshauth' ] . split ( ',' ) ;
for ( var i in s ) { s [ i ] = Buffer . from ( s [ i ] , 'base64' ) . toString ( ) ; }
2021-01-08 10:35:11 +03:00
if ( ( s . length < 2 ) || ( s . length > 3 ) ) { try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2c' } ) ) ; ws . close ( ) ; } catch ( e ) { } return ; }
2021-04-16 22:42:54 +03:00
obj . authenticate ( s [ 0 ] , s [ 1 ] , domain , function ( err , userid , passhint , loginOptions ) {
2019-03-21 21:45:08 +03:00
var user = obj . users [ userid ] ;
if ( ( err == null ) && ( user ) ) {
2021-06-04 22:46:41 +03:00
// Check if user as the "notools" site right. If so, deny this connection as tools are not allowed to connect.
if ( ( user . siteadmin != 0xFFFFFFFF ) && ( user . siteadmin & SITERIGHT _NOMESHCMD ) ) {
// No tools allowed, close the websocket connection
parent . debug ( 'web' , 'ERR: Websocket no tools allowed' ) ;
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'notools' , msg : 'notools' } ) ) ; ws . close ( ) ; } catch ( e ) { }
return ;
}
2019-03-21 21:45:08 +03:00
// Check if a 2nd factor is needed
2021-04-16 22:42:54 +03:00
if ( checkUserOneTimePasswordRequired ( domain , user , req , loginOptions ) == true ) {
2020-09-17 02:16:58 +03:00
// See if we support two-factor trusted cookies
var twoFactorCookieDays = 30 ;
if ( typeof domain . twofactorcookiedurationdays == 'number' ) { twoFactorCookieDays = domain . twofactorcookiedurationdays ; }
2020-03-22 08:52:23 +03:00
// Figure out if email 2FA is allowed
2021-02-10 22:28:21 +03:00
var email2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . email2factor != false ) ) && ( domain . mailserver != null ) && ( user . otpekey != null ) ) ;
2020-04-23 01:29:26 +03:00
var sms2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . sms2factor != false ) ) && ( parent . smsserver != null ) && ( user . phone != null ) ) ;
2022-10-22 22:59:12 +03:00
var msg2fa = ( ( ( typeof domain . passwordrequirements != 'object' ) || ( domain . passwordrequirements . msg2factor != false ) ) && ( parent . msgserver != null ) && ( parent . msgserver . providers != 0 ) && ( user . msghandle != null ) ) ;
2019-03-21 21:45:08 +03:00
if ( s . length != 3 ) {
2022-10-24 17:13:20 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , sms2fa : sms2fa , msg2fa : msg2fa , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2019-03-21 21:45:08 +03:00
} else {
2022-03-05 02:56:19 +03:00
checkUserOneTimePassword ( req , domain , user , s [ 2 ] , null , function ( result , authData ) {
2019-03-21 21:45:08 +03:00
if ( result == false ) {
2020-03-22 08:52:23 +03:00
if ( ( s [ 2 ] == '**email**' ) && ( email2fa == true ) ) {
// Cause a token to be sent to the user's registered email
user . otpekey = { k : obj . common . zeroPad ( getRandomEightDigitInteger ( ) , 8 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA email to: ' + user . email ) ;
2021-02-10 22:28:21 +03:00
domain . mailserver . sendAccountLoginMail ( domain , user . email , user . otpekey . k , obj . getLanguageCodes ( req ) , req . query . key ) ;
2020-03-22 08:52:23 +03:00
// Ask for a login token & confirm email was sent
2020-09-17 02:16:58 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , email2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2020-04-23 01:29:26 +03:00
} else if ( ( s [ 2 ] == '**sms**' ) && ( sms2fa == true ) ) {
// Cause a token to be sent to the user's phone number
user . otpsms = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA SMS to: ' + user . phone ) ;
parent . smsserver . sendToken ( domain , user . phone , user . otpsms . k , obj . getLanguageCodes ( req ) ) ;
// Ask for a login token & confirm sms was sent
2020-09-17 02:16:58 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , sms2fa : sms2fa , sms2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2022-10-22 22:59:12 +03:00
} else if ( ( s [ 2 ] == '**msg**' ) && ( msg2fa == true ) ) {
// Cause a token to be sent to the user's phone number
user . otpmsg = { k : obj . common . zeroPad ( getRandomSixDigitInteger ( ) , 6 ) , d : Date . now ( ) } ;
obj . db . SetUser ( user ) ;
parent . debug ( 'web' , 'Sending 2FA message to: ' + user . msghandle ) ;
parent . msgserver . sendToken ( domain , user . msghandle , user . otpmsg . k , obj . getLanguageCodes ( req ) ) ;
// Ask for a login token & confirm sms was sent
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , msg2fa : msg2fa , msg2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2020-03-22 08:52:23 +03:00
} else {
// Ask for a login token
2020-09-29 20:52:03 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'tokenrequired' , email2fa : email2fa , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2020-03-22 08:52:23 +03:00
}
2019-03-21 21:45:08 +03:00
} else {
// We are authenticated with 2nd factor.
2020-04-21 11:50:27 +03:00
// Check email verification
2021-11-25 21:09:11 +03:00
if ( emailcheck && ( user . email != null ) && ( ! ( user . _id . split ( '/' ) [ 2 ] . startsWith ( '~' ) ) ) && ( user . emailVerified !== true ) ) {
parent . debug ( 'web' , 'Invalid login, asking for email validation' ) ;
2020-09-17 02:16:58 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , email2fasent : true , twoFactorCookieDays : twoFactorCookieDays } ) ) ; ws . close ( ) ; } catch ( e ) { }
2020-04-21 11:50:27 +03:00
} else {
2022-03-05 02:56:19 +03:00
func ( ws , req , domain , user , null , authData ) ;
2020-04-21 11:50:27 +03:00
}
2019-03-21 21:45:08 +03:00
}
} ) ;
}
} else {
// We are authenticated
2020-04-21 11:50:27 +03:00
// Check email verification
2021-11-25 21:09:11 +03:00
if ( emailcheck && ( user . email != null ) && ( ! ( user . _id . split ( '/' ) [ 2 ] . startsWith ( '~' ) ) ) && ( user . emailVerified !== true ) ) {
parent . debug ( 'web' , 'Invalid login, asking for email validation' ) ;
2020-04-21 11:50:27 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'emailvalidation' , msg : 'emailvalidationrequired' , email2fa : email2fa , email2fasent : true } ) ) ; ws . close ( ) ; } catch ( e ) { }
} else {
func ( ws , req , domain , user ) ;
2020-05-27 10:52:31 +03:00
}
}
2019-03-21 21:45:08 +03:00
} else {
// Failed to authenticate, see if a default user is active
if ( obj . args . user && obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) {
// A default user is active
func ( ws , req , domain , obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) ;
} else {
// If not authenticated, close the websocket connection
2019-08-23 01:31:39 +03:00
parent . debug ( 'web' , 'ERR: Websocket bad user/pass auth' ) ;
2021-01-08 10:35:11 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-2d' } ) ) ; ws . close ( ) ; } catch ( e ) { }
2019-03-21 21:45:08 +03:00
}
}
} ) ;
return ;
2019-01-29 02:47:54 +03:00
}
2019-03-21 21:45:08 +03:00
2019-01-29 02:47:54 +03:00
if ( obj . args . user && obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) {
2018-10-16 03:21:37 +03:00
// A default user is active
func ( ws , req , domain , obj . users [ 'user/' + domain . id + '/' + obj . args . user . toLowerCase ( ) ] ) ;
return ;
2022-05-20 00:41:32 +03:00
}
if ( req . session && ( req . session . userid != null ) && ( req . session . userid . split ( '/' ) [ 1 ] == domain . id ) && ( obj . users [ req . session . userid ] ) ) {
2018-10-16 03:21:37 +03:00
// This user is logged in using the ExpressJS session
2018-10-16 20:52:05 +03:00
func ( ws , req , domain , obj . users [ req . session . userid ] ) ;
2018-10-16 03:21:37 +03:00
return ;
2018-10-14 02:04:04 +03:00
}
2018-10-16 03:51:03 +03:00
if ( noAuthOk != true ) {
// If not authenticated, close the websocket connection
2019-08-23 01:31:39 +03:00
parent . debug ( 'web' , 'ERR: Websocket no auth' ) ;
2019-01-29 02:47:54 +03:00
try { ws . send ( JSON . stringify ( { action : 'close' , cause : 'noauth' , msg : 'noauth-4' } ) ) ; ws . close ( ) ; } catch ( e ) { }
2018-10-16 03:51:03 +03:00
} else {
// Continue this session without user authentication,
// this is expected if the agent is connecting for a tunnel.
func ( ws , req , domain , null ) ;
}
2018-10-14 02:04:04 +03:00
} catch ( e ) { console . log ( e ) ; }
}
2017-08-28 19:27:45 +03:00
// Find a free port starting with the specified one and going up.
2020-06-05 09:27:48 +03:00
function CheckListenPort ( port , addr , func ) {
2017-08-28 19:27:45 +03:00
var s = obj . net . createServer ( function ( socket ) { } ) ;
2020-06-05 09:27:48 +03:00
obj . tcpServer = s . listen ( port , addr , function ( ) { s . close ( function ( ) { if ( func ) { func ( port , addr ) ; } } ) ; } ) . on ( 'error' , function ( err ) {
2019-01-03 05:34:50 +03:00
if ( args . exactports ) { console . error ( 'ERROR: MeshCentral HTTPS server port ' + port + ' not available.' ) ; process . exit ( ) ; }
2020-06-05 09:27:48 +03:00
else { if ( port < 65535 ) { CheckListenPort ( port + 1 , addr , func ) ; } else { if ( func ) { func ( 0 ) ; } } }
2017-08-28 19:27:45 +03:00
} ) ;
}
// Start the ExpressJS web server
2020-06-05 09:27:48 +03:00
function StartWebServer ( port , addr ) {
2019-12-11 05:17:25 +03:00
if ( ( port < 1 ) || ( port > 65535 ) ) return ;
2017-08-28 19:27:45 +03:00
obj . args . port = port ;
if ( obj . tlsServer != null ) {
if ( obj . args . lanonly == true ) {
2022-07-05 06:20:25 +03:00
obj . tcpServer = obj . tlsServer . listen ( port , addr , function ( ) { console . log ( 'MeshCentral HTTPS server running on port ' + port + ( ( typeof args . aliasport == 'number' ) ? ( ', alias port ' + args . aliasport ) : '' ) + '.' ) ; } ) ;
2017-08-28 19:27:45 +03:00
} else {
2022-07-05 06:20:25 +03:00
obj . tcpServer = obj . tlsServer . listen ( port , addr , function ( ) {
console . log ( 'MeshCentral HTTPS server running on ' + certificates . CommonName + ':' + port + ( ( typeof args . aliasport == 'number' ) ? ( ', alias port ' + args . aliasport ) : '' ) + '.' ) ;
2022-07-10 11:32:11 +03:00
if ( args . relaydns != null ) { console . log ( 'MeshCentral HTTPS relay server running on ' + args . relaydns [ 0 ] + ':' + port + ( ( typeof args . aliasport == 'number' ) ? ( ', alias port ' + args . aliasport ) : '' ) + '.' ) ; }
2022-07-05 06:20:25 +03:00
} ) ;
2018-03-14 22:10:13 +03:00
obj . parent . updateServerState ( 'servername' , certificates . CommonName ) ;
2017-08-28 19:27:45 +03:00
}
2024-03-04 03:03:27 +03:00
obj . parent . debug ( 'https' , 'Server listening on ' + ( ( addr != null ) ? addr : '0.0.0.0' ) + ' port ' + port + '.' ) ;
2018-03-14 22:10:13 +03:00
obj . parent . updateServerState ( 'https-port' , port ) ;
if ( args . aliasport != null ) { obj . parent . updateServerState ( 'https-aliasport' , args . aliasport ) ; }
2017-08-28 19:27:45 +03:00
} else {
2022-07-05 06:20:25 +03:00
obj . tcpServer = obj . app . listen ( port , addr , function ( ) {
console . log ( 'MeshCentral HTTP server running on port ' + port + ( ( typeof args . aliasport == 'number' ) ? ( ', alias port ' + args . aliasport ) : '' ) + '.' ) ;
2022-07-10 11:32:11 +03:00
if ( args . relaydns != null ) { console . log ( 'MeshCentral HTTP relay server running on ' + args . relaydns [ 0 ] + ':' + port + ( ( typeof args . aliasport == 'number' ) ? ( ', alias port ' + args . aliasport ) : '' ) + '.' ) ; }
2022-07-05 06:20:25 +03:00
} ) ;
2018-03-14 22:10:13 +03:00
obj . parent . updateServerState ( 'http-port' , port ) ;
if ( args . aliasport != null ) { obj . parent . updateServerState ( 'http-aliasport' , args . aliasport ) ; }
2017-08-28 19:27:45 +03:00
}
2019-12-11 05:17:25 +03:00
// Check if there is a permissions problem with the ports.
if ( require ( 'os' ) . platform ( ) != 'win32' ) {
var expectedPort = obj . parent . config . settings . port ? obj . parent . config . settings . port : 443 ;
if ( ( expectedPort != port ) && ( port >= 1024 ) && ( port < 1034 ) ) {
console . log ( '' ) ;
console . log ( 'WARNING: MeshCentral is running without permissions to use ports below 1025.' ) ;
console . log ( ' Use setcap to grant access to lower ports, or read installation guide.' ) ;
console . log ( '' ) ;
2020-06-24 00:10:35 +03:00
console . log ( ' sudo setcap \'cap_net_bind_service=+ep\' `which node` \r\n' ) ;
2019-12-11 05:17:25 +03:00
obj . parent . addServerWarning ( 'Server running without permissions to use ports below 1025.' , false ) ;
}
}
2017-08-28 19:27:45 +03:00
}
2020-05-03 20:46:20 +03:00
// Start the ExpressJS web server on agent-only alternative port
2020-06-05 09:27:48 +03:00
function StartAltWebServer ( port , addr ) {
2020-05-03 20:46:20 +03:00
if ( ( port < 1 ) || ( port > 65535 ) ) return ;
2020-05-22 22:54:22 +03:00
var agentAliasPort = null ;
2023-11-07 03:10:12 +03:00
var agentAliasDns = null ;
2020-05-22 22:54:22 +03:00
if ( args . agentaliasport != null ) { agentAliasPort = args . agentaliasport ; }
2023-11-07 03:10:12 +03:00
if ( args . agentaliasdns != null ) { agentAliasDns = args . agentaliasdns ; }
2020-05-03 20:46:20 +03:00
if ( obj . tlsAltServer != null ) {
if ( obj . args . lanonly == true ) {
2020-06-05 09:27:48 +03:00
obj . tcpAltServer = obj . tlsAltServer . listen ( port , addr , function ( ) { console . log ( 'MeshCentral HTTPS agent-only server running on port ' + port + ( ( agentAliasPort != null ) ? ( ', alias port ' + agentAliasPort ) : '' ) + '.' ) ; } ) ;
2020-05-03 20:46:20 +03:00
} else {
2023-11-07 03:10:12 +03:00
obj . tcpAltServer = obj . tlsAltServer . listen ( port , addr , function ( ) { console . log ( 'MeshCentral HTTPS agent-only server running on ' + ( ( agentAliasDns != null ) ? agentAliasDns : certificates . CommonName ) + ':' + port + ( ( agentAliasPort != null ) ? ( ', alias port ' + agentAliasPort ) : '' ) + '.' ) ; } ) ;
2020-05-03 20:46:20 +03:00
}
2024-03-04 03:03:27 +03:00
obj . parent . debug ( 'https' , 'Server listening on 0.0.0.0 port ' + port + '.' ) ;
2020-05-03 20:46:20 +03:00
obj . parent . updateServerState ( 'https-agent-port' , port ) ;
} else {
2020-06-05 09:27:48 +03:00
obj . tcpAltServer = obj . agentapp . listen ( port , addr , function ( ) { console . log ( 'MeshCentral HTTP agent-only server running on port ' + port + ( ( agentAliasPort != null ) ? ( ', alias port ' + agentAliasPort ) : '' ) + '.' ) ; } ) ;
2020-05-03 20:46:20 +03:00
obj . parent . updateServerState ( 'http-agent-port' , port ) ;
}
}
2017-08-28 19:27:45 +03:00
// Force mesh agent disconnection
2018-05-24 07:43:44 +03:00
obj . forceMeshAgentDisconnect = function ( user , domain , nodeid , disconnectMode ) {
2017-09-15 21:45:06 +03:00
if ( nodeid == null ) return ;
2017-08-28 19:27:45 +03:00
var splitnode = nodeid . split ( '/' ) ;
if ( ( splitnode . length != 3 ) || ( splitnode [ 1 ] != domain . id ) ) return ; // Check that nodeid is valid and part of our domain
var agent = obj . wsagents [ nodeid ] ;
2017-09-15 21:45:06 +03:00
if ( agent == null ) return ;
2017-08-28 19:27:45 +03:00
// Check we have agent rights
2019-12-28 02:18:43 +03:00
if ( ( ( obj . GetMeshRights ( user , agent . dbMeshKey ) & MESHRIGHT _AGENTCONSOLE ) != 0 ) || ( user . siteadmin == 0xFFFFFFFF ) ) { agent . close ( disconnectMode ) ; }
2018-08-30 22:05:23 +03:00
} ;
2017-08-28 19:27:45 +03:00
// Send the core module to the mesh agent
2019-01-22 01:05:50 +03:00
obj . sendMeshAgentCore = function ( user , domain , nodeid , coretype , coredata ) {
2017-09-15 21:45:06 +03:00
if ( nodeid == null ) return ;
2019-01-22 01:05:50 +03:00
const splitnode = nodeid . split ( '/' ) ;
2017-08-28 19:27:45 +03:00
if ( ( splitnode . length != 3 ) || ( splitnode [ 1 ] != domain . id ) ) return ; // Check that nodeid is valid and part of our domain
2019-01-22 01:05:50 +03:00
// TODO: This command only works if the agent is connected on the same server. Will not work with multi server peering.
const agent = obj . wsagents [ nodeid ] ;
2017-09-15 21:45:06 +03:00
if ( agent == null ) return ;
2017-08-28 19:27:45 +03:00
// Check we have agent rights
2019-12-28 02:18:43 +03:00
if ( ( ( obj . GetMeshRights ( user , agent . dbMeshKey ) & MESHRIGHT _AGENTCONSOLE ) != 0 ) || ( user . siteadmin == 0xFFFFFFFF ) ) {
2019-01-22 01:05:50 +03:00
if ( coretype == 'clear' ) {
2017-08-28 19:27:45 +03:00
// Clear the mesh agent core
2019-01-22 01:05:50 +03:00
agent . agentCoreCheck = 1000 ; // Tell the agent object we are using a custom core.
2017-08-28 19:27:45 +03:00
agent . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) ) ;
2019-01-22 01:05:50 +03:00
} else if ( coretype == 'default' ) {
// Reset to default code
2017-08-28 19:27:45 +03:00
agent . agentCoreCheck = 0 ; // Tell the agent object we are using a default code
agent . send ( obj . common . ShortToStr ( 11 ) + obj . common . ShortToStr ( 0 ) ) ; // Command 11, ask for mesh core hash.
2019-01-22 01:05:50 +03:00
} else if ( coretype == 'recovery' ) {
// Reset to recovery core
agent . agentCoreCheck = 1001 ; // Tell the agent object we are using the recovery core.
agent . send ( obj . common . ShortToStr ( 11 ) + obj . common . ShortToStr ( 0 ) ) ; // Command 11, ask for mesh core hash.
2021-01-25 05:43:15 +03:00
} else if ( coretype == 'tiny' ) {
// Reset to tiny core
agent . agentCoreCheck = 1011 ; // Tell the agent object we are using the tiny core.
agent . send ( obj . common . ShortToStr ( 11 ) + obj . common . ShortToStr ( 0 ) ) ; // Command 11, ask for mesh core hash.
2019-01-22 01:05:50 +03:00
} else if ( coretype == 'custom' ) {
agent . agentCoreCheck = 1000 ; // Tell the agent object we are using a custom core.
2022-01-14 01:34:39 +03:00
var buf = Buffer . from ( coredata , 'utf8' ) ;
const hash = obj . crypto . createHash ( 'sha384' ) . update ( buf ) . digest ( ) . toString ( 'binary' ) ; // Perform a SHA384 hash on the core module
agent . sendBinary ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) + hash + buf . toString ( 'binary' ) ) ; // Send the code module to the agent
2017-08-28 19:27:45 +03:00
}
}
2018-08-30 22:05:23 +03:00
} ;
2018-05-24 07:43:44 +03:00
2017-08-28 19:27:45 +03:00
// Get the server path of a user or mesh object
function getServerRootFilePath ( obj ) {
2017-09-15 21:45:06 +03:00
if ( ( typeof obj != 'object' ) || ( obj . domain == null ) || ( obj . _id == null ) ) return null ;
2017-08-28 19:27:45 +03:00
var domainname = 'domain' , splitname = obj . _id . split ( '/' ) ;
if ( splitname . length != 3 ) return null ;
if ( obj . domain !== '' ) domainname = 'domain-' + obj . domain ;
return obj . path . join ( obj . filespath , domainname + "/" + splitname [ 0 ] + "-" + splitname [ 2 ] ) ;
}
2018-05-24 07:43:44 +03:00
2017-12-13 03:04:54 +03:00
// Return true is the input string looks like an email address
function checkEmail ( str ) {
var x = str . split ( '@' ) ;
var ok = ( ( x . length == 2 ) && ( x [ 0 ] . length > 0 ) && ( x [ 1 ] . split ( '.' ) . length > 1 ) && ( x [ 1 ] . length > 2 ) ) ;
if ( ok == true ) { var y = x [ 1 ] . split ( '.' ) ; for ( var i in y ) { if ( y [ i ] . length == 0 ) { ok = false ; } } }
return ok ;
}
2018-05-24 07:43:44 +03:00
/ *
obj . wssessions = { } ; // UserId --> Array Of Sessions
obj . wssessions2 = { } ; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)
obj . wsPeerSessions = { } ; // ServerId --> Array Of "UserId + SessionRnd"
obj . wsPeerSessions2 = { } ; // "UserId + SessionRnd" --> ServerId
obj . wsPeerSessions3 = { } ; // ServerId --> UserId --> [ SessionId ]
* /
2017-09-21 00:44:22 +03:00
// Count sessions and event any changes
obj . recountSessions = function ( changedSessionId ) {
2018-08-30 22:05:23 +03:00
var userid , oldcount , newcount , x , serverid ;
2017-09-21 00:44:22 +03:00
if ( changedSessionId == null ) {
// Recount all sessions
// Calculate the session count for all userid's
var newSessionsCount = { } ;
2018-08-30 22:05:23 +03:00
for ( userid in obj . wssessions ) { newSessionsCount [ userid ] = obj . wssessions [ userid ] . length ; }
for ( serverid in obj . wsPeerSessions3 ) {
for ( userid in obj . wsPeerSessions3 [ serverid ] ) {
x = obj . wsPeerSessions3 [ serverid ] [ userid ] . length ;
if ( newSessionsCount [ userid ] == null ) { newSessionsCount [ userid ] = x ; } else { newSessionsCount [ userid ] += x ; }
2017-09-21 00:44:22 +03:00
}
}
// See what session counts have changed, event any changes
2018-08-30 22:05:23 +03:00
for ( userid in newSessionsCount ) {
newcount = newSessionsCount [ userid ] ;
oldcount = obj . sessionsCount [ userid ] ;
2017-09-21 00:44:22 +03:00
if ( oldcount == null ) { oldcount = 0 ; } else { delete obj . sessionsCount [ userid ] ; }
if ( newcount != oldcount ) {
2018-08-30 22:05:23 +03:00
x = userid . split ( '/' ) ;
2019-08-13 00:58:06 +03:00
var u = obj . users [ userid ] ;
2019-05-04 22:55:46 +03:00
if ( u ) {
var targets = [ '*' , 'server-users' ] ;
if ( u . groups ) { for ( var i in u . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
obj . parent . DispatchEvent ( targets , obj , { action : 'wssessioncount' , userid : userid , username : x [ 2 ] , count : newcount , domain : x [ 1 ] , nolog : 1 , nopeers : 1 } ) ;
2019-05-04 22:55:46 +03:00
}
2017-09-21 00:44:22 +03:00
}
}
// If there are any counts left in the old counts, event to zero
2018-08-30 22:05:23 +03:00
for ( userid in obj . sessionsCount ) {
oldcount = obj . sessionsCount [ userid ] ;
2017-09-21 00:44:22 +03:00
if ( ( oldcount != null ) && ( oldcount != 0 ) ) {
2018-08-30 22:05:23 +03:00
x = userid . split ( '/' ) ;
2019-08-13 00:58:06 +03:00
var u = obj . users [ userid ] ;
2019-05-04 22:55:46 +03:00
if ( u ) {
var targets = [ '*' , 'server-users' ] ;
if ( u . groups ) { for ( var i in u . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
obj . parent . DispatchEvent ( [ '*' ] , obj , { action : 'wssessioncount' , userid : userid , username : x [ 2 ] , count : 0 , domain : x [ 1 ] , nolog : 1 , nopeers : 1 } )
2019-05-04 22:55:46 +03:00
}
2017-09-21 00:44:22 +03:00
}
}
// Set the new session counts
obj . sessionsCount = newSessionsCount ;
} else {
// Figure out the userid
2018-08-30 22:05:23 +03:00
userid = changedSessionId . split ( '/' ) . slice ( 0 , 3 ) . join ( '/' ) ;
2017-09-21 00:44:22 +03:00
// Recount only changedSessionId
2018-08-30 22:05:23 +03:00
newcount = 0 ;
2017-09-21 00:44:22 +03:00
if ( obj . wssessions [ userid ] != null ) { newcount = obj . wssessions [ userid ] . length ; }
2018-08-30 22:05:23 +03:00
for ( serverid in obj . wsPeerSessions3 ) { if ( obj . wsPeerSessions3 [ serverid ] [ userid ] != null ) { newcount += obj . wsPeerSessions3 [ serverid ] [ userid ] . length ; } }
oldcount = obj . sessionsCount [ userid ] ;
2017-09-21 00:44:22 +03:00
if ( oldcount == null ) { oldcount = 0 ; }
// If the count changed, update and event
if ( newcount != oldcount ) {
2018-08-30 22:05:23 +03:00
x = userid . split ( '/' ) ;
2019-08-13 00:58:06 +03:00
var u = obj . users [ userid ] ;
2019-05-04 22:55:46 +03:00
if ( u ) {
var targets = [ '*' , 'server-users' ] ;
if ( u . groups ) { for ( var i in u . groups ) { targets . push ( 'server-users:' + i ) ; } }
2019-07-30 02:35:48 +03:00
obj . parent . DispatchEvent ( targets , obj , { action : 'wssessioncount' , userid : userid , username : x [ 2 ] , count : newcount , domain : x [ 1 ] , nolog : 1 , nopeers : 1 } ) ;
2019-05-04 22:55:46 +03:00
obj . sessionsCount [ userid ] = newcount ;
}
2017-09-21 00:44:22 +03:00
}
}
2018-08-30 22:05:23 +03:00
} ;
2017-09-21 00:44:22 +03:00
2024-03-04 03:03:27 +03:00
/* Access Control Functions */
2019-12-28 02:18:43 +03:00
2021-08-28 00:42:28 +03:00
// Remove user rights
function removeUserRights ( rights , user ) {
if ( user . removeRights == null ) return rights ;
var add = 0 , substract = 0 ;
2021-08-28 19:51:03 +03:00
if ( ( user . removeRights & 0x00000008 ) != 0 ) { substract += 0x00000008 ; } // No Remote Control
2021-08-28 00:42:28 +03:00
if ( ( user . removeRights & 0x00010000 ) != 0 ) { add += 0x00010000 ; } // No Desktop
if ( ( user . removeRights & 0x00000100 ) != 0 ) { add += 0x00000100 ; } // Desktop View Only
if ( ( user . removeRights & 0x00000200 ) != 0 ) { add += 0x00000200 ; } // No Terminal
if ( ( user . removeRights & 0x00000400 ) != 0 ) { add += 0x00000400 ; } // No Files
if ( ( user . removeRights & 0x00000010 ) != 0 ) { substract += 0x00000010 ; } // No Console
2021-08-28 19:51:03 +03:00
if ( ( user . removeRights & 0x00008000 ) != 0 ) { substract += 0x00008000 ; } // No Uninstall
if ( ( user . removeRights & 0x00020000 ) != 0 ) { substract += 0x00020000 ; } // No Remote Command
if ( ( user . removeRights & 0x00000040 ) != 0 ) { substract += 0x00000040 ; } // No Wake
if ( ( user . removeRights & 0x00040000 ) != 0 ) { substract += 0x00040000 ; } // No Reset/Off
2021-08-28 00:42:28 +03:00
if ( rights != 0xFFFFFFFF ) {
// If not administrator, add and subsctract restrictions
rights |= add ;
rights &= ( 0xFFFFFFFF - substract ) ;
} else {
// If administrator for a device group, start with permissions and add and subsctract restrictions
rights = 1 + 2 + 4 + 8 + 32 + 64 + 128 + 16384 + 32768 + 131072 + 262144 + 524288 + 1048576 ;
rights |= add ;
rights &= ( 0xFFFFFFFF - substract ) ;
}
return rights ;
}
2022-03-30 17:14:14 +03:00
// Return the node and rights for a array of nodeids
obj . GetNodesWithRights = function ( domain , user , nodeids , func ) {
var rc = nodeids . length , r = { } ;
for ( var i in nodeids ) {
obj . GetNodeWithRights ( domain , user , nodeids [ i ] , function ( node , rights , visible ) {
if ( ( node != null ) && ( visible == true ) ) { r [ node . _id ] = { node : node , rights : rights } ; if ( -- rc == 0 ) { func ( r ) ; } }
} ) ;
}
}
2019-12-28 02:18:43 +03:00
// Return the node and rights for a given nodeid
obj . GetNodeWithRights = function ( domain , user , nodeid , func ) {
// Perform user pre-validation
if ( ( user == null ) || ( nodeid == null ) ) { func ( null , 0 , false ) ; return ; } // Invalid user
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
2020-09-10 02:17:00 +03:00
if ( user == null ) { func ( null , 0 , false ) ; return ; } // No rights
2019-12-28 02:18:43 +03:00
// Perform node pre-validation
if ( obj . common . validateString ( nodeid , 0 , 128 ) == false ) { func ( null , 0 , false ) ; return ; } // Invalid nodeid
const snode = nodeid . split ( '/' ) ;
if ( ( snode . length != 3 ) || ( snode [ 0 ] != 'node' ) ) { func ( null , 0 , false ) ; return ; } // Invalid nodeid
if ( ( domain != null ) && ( snode [ 1 ] != domain . id ) ) { func ( null , 0 , false ) ; return ; } // Invalid domain
// Check that we have permissions for this node.
db . Get ( nodeid , function ( err , nodes ) {
if ( ( nodes == null ) || ( nodes . length != 1 ) ) { func ( null , 0 , false ) ; return ; } // No such nodeid
2020-01-04 23:44:12 +03:00
2020-05-06 21:47:23 +03:00
// This is a super user that can see all device groups for a given domain
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) && ( nodes [ 0 ] . domain == user . domain ) ) {
2021-08-28 00:42:28 +03:00
func ( nodes [ 0 ] , removeUserRights ( 0xFFFFFFFF , user ) , true ) ; return ;
2020-05-06 21:47:23 +03:00
}
2020-09-10 02:17:00 +03:00
// If no links, stop here.
if ( user . links == null ) { func ( null , 0 , false ) ; return ; }
2020-03-18 03:17:04 +03:00
// Check device link
var rights = 0 , visible = false , r = user . links [ nodeid ] ;
2020-01-04 23:44:12 +03:00
if ( r != null ) {
2021-08-28 00:42:28 +03:00
if ( r . rights == 0xFFFFFFFF ) { func ( nodes [ 0 ] , removeUserRights ( 0xFFFFFFFF , user ) , true ) ; return ; } // User has full rights thru a device link, stop here.
2020-03-18 03:17:04 +03:00
rights |= r . rights ;
visible = true ;
}
// Check device group link
r = user . links [ nodes [ 0 ] . meshid ] ;
if ( r != null ) {
2021-08-28 00:42:28 +03:00
if ( r . rights == 0xFFFFFFFF ) { func ( nodes [ 0 ] , removeUserRights ( 0xFFFFFFFF , user ) , true ) ; return ; } // User has full rights thru a device group link, stop here.
2020-03-18 03:17:04 +03:00
rights |= r . rights ;
2020-01-04 23:44:12 +03:00
visible = true ;
}
// Check user group links
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp/' ) ) {
const g = obj . userGroups [ i ] ;
if ( g && ( g . links != null ) ) {
r = g . links [ nodes [ 0 ] . meshid ] ;
if ( r != null ) {
2021-08-28 00:42:28 +03:00
if ( r . rights == 0xFFFFFFFF ) { func ( nodes [ 0 ] , removeUserRights ( 0xFFFFFFFF , user ) , true ) ; return ; } // User has full rights thru a user group link, stop here.
2020-01-04 23:44:12 +03:00
rights |= r . rights ; // TODO: Deal with reverse rights
visible = true ;
}
2020-04-13 20:50:57 +03:00
r = g . links [ nodeid ] ;
if ( r != null ) {
2021-08-28 00:42:28 +03:00
if ( r . rights == 0xFFFFFFFF ) { func ( nodes [ 0 ] , removeUserRights ( 0xFFFFFFFF , user ) , true ) ; return ; } // User has full rights thru a user group direct link, stop here.
2020-04-13 20:50:57 +03:00
rights |= r . rights ; // TODO: Deal with reverse rights
visible = true ;
}
2020-01-04 23:44:12 +03:00
}
}
}
2021-08-28 00:42:28 +03:00
// Remove any user rights
rights = removeUserRights ( rights , user ) ;
2020-01-04 23:44:12 +03:00
// Return the rights we found
func ( nodes [ 0 ] , rights , visible ) ;
2019-12-28 02:18:43 +03:00
} ) ;
}
2019-12-27 09:53:01 +03:00
// Returns a list of all meshes that this user has some rights too
obj . GetAllMeshWithRights = function ( user , rights ) {
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
2020-09-10 02:17:00 +03:00
if ( user == null ) { return [ ] ; }
2020-02-18 00:01:13 +03:00
2019-12-27 09:53:01 +03:00
var r = [ ] ;
2020-02-18 00:01:13 +03:00
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) ) {
// This is a super user that can see all device groups for a given domain
var meshStartStr = 'mesh/' + user . domain + '/' ;
for ( var i in obj . meshes ) { if ( ( obj . meshes [ i ] . _id . startsWith ( meshStartStr ) ) && ( obj . meshes [ i ] . deleted == null ) ) { r . push ( obj . meshes [ i ] ) ; } }
return r ;
}
2020-09-10 02:17:00 +03:00
if ( user . links == null ) { return [ ] ; }
2020-01-04 23:44:12 +03:00
for ( var i in user . links ) {
if ( i . startsWith ( 'mesh/' ) ) {
// Grant access to a device group thru a direct link
const m = obj . meshes [ i ] ;
2021-11-25 20:26:25 +03:00
if ( ( m ) && ( r . indexOf ( m ) == - 1 ) && ( m . deleted == null ) && ( ( rights == null ) || ( ( user . links [ i ] . rights & rights ) != 0 ) ) ) { r . push ( m ) ; }
2020-01-04 23:44:12 +03:00
} else if ( i . startsWith ( 'ugrp/' ) ) {
// Grant access to a device group thru a user group
const g = obj . userGroups [ i ] ;
2021-11-25 20:26:25 +03:00
for ( var j in g . links ) {
if ( j . startsWith ( 'mesh/' ) && ( ( rights == null ) || ( ( g . links [ j ] . rights != null ) && ( g . links [ j ] . rights & rights ) != 0 ) ) ) {
const m = obj . meshes [ j ] ;
if ( ( m ) && ( m . deleted == null ) && ( r . indexOf ( m ) == - 1 ) ) { r . push ( m ) ; }
2020-01-04 23:44:12 +03:00
}
}
}
}
2019-12-27 09:53:01 +03:00
return r ;
}
// Returns a list of all mesh id's that this user has some rights too
obj . GetAllMeshIdWithRights = function ( user , rights ) {
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
2020-09-10 02:17:00 +03:00
if ( user == null ) { return [ ] ; }
2019-12-27 09:53:01 +03:00
var r = [ ] ;
2020-02-18 00:01:13 +03:00
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) ) {
// This is a super user that can see all device groups for a given domain
var meshStartStr = 'mesh/' + user . domain + '/' ;
for ( var i in obj . meshes ) { if ( ( obj . meshes [ i ] . _id . startsWith ( meshStartStr ) ) && ( obj . meshes [ i ] . deleted == null ) ) { r . push ( obj . meshes [ i ] . _id ) ; } }
return r ;
}
2020-09-10 02:17:00 +03:00
if ( user . links == null ) { return [ ] ; }
2020-01-04 23:44:12 +03:00
for ( var i in user . links ) {
if ( i . startsWith ( 'mesh/' ) ) {
// Grant access to a device group thru a direct link
const m = obj . meshes [ i ] ;
2021-01-25 11:22:01 +03:00
if ( ( m ) && ( m . deleted == null ) && ( ( rights == null ) || ( ( user . links [ i ] . rights & rights ) != 0 ) ) ) {
2020-01-04 23:44:12 +03:00
if ( r . indexOf ( m . _id ) == - 1 ) { r . push ( m . _id ) ; }
}
} else if ( i . startsWith ( 'ugrp/' ) ) {
// Grant access to a device group thru a user group
const g = obj . userGroups [ i ] ;
2021-01-25 11:22:01 +03:00
if ( g && ( g . links != null ) && ( ( rights == null ) || ( ( user . links [ i ] . rights & rights ) != 0 ) ) ) {
2020-01-04 23:44:12 +03:00
for ( var j in g . links ) {
if ( j . startsWith ( 'mesh/' ) ) {
const m = obj . meshes [ j ] ;
if ( ( m ) && ( m . deleted == null ) ) {
if ( r . indexOf ( m . _id ) == - 1 ) { r . push ( m . _id ) ; }
}
}
}
}
}
}
2019-12-27 09:53:01 +03:00
return r ;
}
2020-03-18 03:17:04 +03:00
// Get the rights of a user on a given device group
2019-12-27 09:53:01 +03:00
obj . GetMeshRights = function ( user , mesh ) {
if ( ( user == null ) || ( mesh == null ) ) { return 0 ; }
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
2020-09-10 02:17:00 +03:00
if ( user == null ) { return 0 ; }
2020-01-04 23:44:12 +03:00
var r , meshid ;
if ( typeof mesh == 'string' ) {
meshid = mesh ;
} else if ( ( typeof mesh == 'object' ) && ( typeof mesh . _id == 'string' ) ) {
meshid = mesh . _id ;
} else return 0 ;
2020-02-18 00:01:13 +03:00
// Check if this is a super user that can see all device groups for a given domain
2021-08-28 00:42:28 +03:00
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) && ( meshid . startsWith ( 'mesh/' + user . domain + '/' ) ) ) { return removeUserRights ( 0xFFFFFFFF , user ) ; }
2020-02-18 00:01:13 +03:00
2020-01-04 23:44:12 +03:00
// Check direct user to device group permissions
2020-09-10 02:17:00 +03:00
if ( user . links == null ) return 0 ;
2020-01-05 00:19:32 +03:00
var rights = 0 ;
2020-01-04 23:44:12 +03:00
r = user . links [ meshid ] ;
2020-01-05 00:19:32 +03:00
if ( r != null ) {
var rights = r . rights ;
2021-08-28 00:42:28 +03:00
if ( rights == 0xFFFFFFFF ) { return removeUserRights ( rights , user ) ; } // If the user has full access thru direct link, stop here.
2020-01-05 00:19:32 +03:00
}
2020-01-04 23:44:12 +03:00
// Check if we are part of any user groups that would give this user more access.
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp' ) ) {
2020-01-05 00:19:32 +03:00
const g = obj . userGroups [ i ] ;
2020-01-04 23:44:12 +03:00
if ( g ) {
r = g . links [ meshid ] ;
if ( r != null ) {
if ( r . rights == 0xFFFFFFFF ) {
2021-08-28 00:42:28 +03:00
return removeUserRights ( r . rights , user ) ; // If the user hash full access thru a user group link, stop here.
2020-01-04 23:44:12 +03:00
} else {
rights |= r . rights ; // Add to existing rights (TODO: Deal with reverse rights)
}
}
}
}
}
2021-08-28 00:42:28 +03:00
return removeUserRights ( rights , user ) ;
2019-12-27 09:53:01 +03:00
}
// Returns true if the user can view the given device group
obj . IsMeshViewable = function ( user , mesh ) {
if ( ( user == null ) || ( mesh == null ) ) { return false ; }
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
2020-09-10 02:17:00 +03:00
if ( user == null ) { return false ; }
2020-01-04 23:44:12 +03:00
var meshid ;
if ( typeof mesh == 'string' ) {
meshid = mesh ;
} else if ( ( typeof mesh == 'object' ) && ( typeof mesh . _id == 'string' ) ) {
meshid = mesh . _id ;
} else return false ;
2020-02-18 00:01:13 +03:00
// Check if this is a super user that can see all device groups for a given domain
if ( ( user . siteadmin == 0xFFFFFFFF ) && ( parent . config . settings . managealldevicegroups . indexOf ( user . _id ) >= 0 ) && ( meshid . startsWith ( 'mesh/' + user . domain + '/' ) ) ) { return true ; }
2020-01-04 23:44:12 +03:00
// Check direct user to device group permissions
2020-09-10 02:17:00 +03:00
if ( user . links == null ) { return false ; }
2020-01-04 23:44:12 +03:00
if ( user . links [ meshid ] != null ) { return true ; } // If the user has a direct link, stop here.
// Check if we are part of any user groups that would give this user visibility to this device group.
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp' ) ) {
2020-01-05 00:19:32 +03:00
const g = obj . userGroups [ i ] ;
2020-01-04 23:44:12 +03:00
if ( g && ( g . links [ meshid ] != null ) ) { return true ; } // If the user has a user group link, stop here.
}
}
return false ;
2019-12-27 09:53:01 +03:00
}
2022-01-10 00:19:49 +03:00
var GetNodeRightsCache = { } ;
var GetNodeRightsCacheCount = 0 ;
2020-03-30 08:40:13 +03:00
// Return the user rights for a given node
obj . GetNodeRights = function ( user , mesh , nodeid ) {
if ( ( user == null ) || ( mesh == null ) || ( nodeid == null ) ) { return 0 ; }
if ( typeof user == 'string' ) { user = obj . users [ user ] ; }
2020-10-28 02:40:12 +03:00
if ( user == null ) { return 0 ; }
2022-01-10 00:19:49 +03:00
var meshid ;
if ( typeof mesh == 'string' ) { meshid = mesh ; } else if ( ( typeof mesh == 'object' ) && ( typeof mesh . _id == 'string' ) ) { meshid = mesh . _id ; } else return 0 ;
// Check if we have this in the cache
const cacheid = user . _id + '/' + meshid + '/' + nodeid ;
const cache = GetNodeRightsCache [ cacheid ] ;
if ( cache != null ) { if ( cache . t > Date . now ( ) ) { return cache . o ; } else { GetNodeRightsCacheCount -- ; } } // Cache hit, or we need to update the cache
if ( GetNodeRightsCacheCount > 2000 ) { GetNodeRightsCache = { } ; GetNodeRightsCacheCount = 0 ; } // From time to time, flush the cache
2020-03-30 08:40:13 +03:00
var r = obj . GetMeshRights ( user , mesh ) ;
2022-01-10 00:19:49 +03:00
if ( r == 0xFFFFFFFF ) {
const out = removeUserRights ( r , user ) ;
GetNodeRightsCache [ cacheid ] = { t : Date . now ( ) + 10000 , o : out } ;
GetNodeRightsCacheCount ++ ;
return out ;
}
2020-03-30 08:40:13 +03:00
// Check direct device rights using device data
if ( ( user . links != null ) && ( user . links [ nodeid ] != null ) ) { r |= user . links [ nodeid ] . rights ; } // TODO: Deal with reverse permissions
2022-01-10 00:19:49 +03:00
if ( r == 0xFFFFFFFF ) {
const out = removeUserRights ( r , user ) ;
GetNodeRightsCache [ cacheid ] = { t : Date . now ( ) + 10000 , o : out } ;
GetNodeRightsCacheCount ++ ;
return out ;
}
2020-04-13 20:50:57 +03:00
// Check direct device rights thru a user group
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp' ) ) {
const g = obj . userGroups [ i ] ;
if ( g && ( g . links [ nodeid ] != null ) ) { r |= g . links [ nodeid ] . rights ; }
}
}
2022-01-10 00:19:49 +03:00
const out = removeUserRights ( r , user ) ;
GetNodeRightsCache [ cacheid ] = { t : Date . now ( ) + 10000 , o : out } ;
GetNodeRightsCacheCount ++ ;
return out ;
2020-03-30 08:40:13 +03:00
}
2020-01-05 00:46:55 +03:00
// Returns a list of displatch targets for a given mesh
// We have to target the meshid and all user groups for this mesh, plus any added targets
obj . CreateMeshDispatchTargets = function ( mesh , addedTargets ) {
var targets = ( addedTargets != null ) ? addedTargets : [ ] ;
if ( targets . indexOf ( '*' ) == - 1 ) { targets . push ( '*' ) ; }
if ( typeof mesh == 'string' ) { mesh = obj . meshes [ mesh ] ; }
2020-04-13 21:24:56 +03:00
if ( mesh != null ) { targets . push ( mesh . _id ) ; for ( var i in mesh . links ) { if ( i . startsWith ( 'ugrp/' ) ) { targets . push ( i ) ; } } }
return targets ;
}
// Returns a list of displatch targets for a given mesh
// We have to target the meshid and all user groups for this mesh, plus any added targets
obj . CreateNodeDispatchTargets = function ( mesh , nodeid , addedTargets ) {
var targets = ( addedTargets != null ) ? addedTargets : [ ] ;
targets . push ( nodeid ) ;
if ( targets . indexOf ( '*' ) == - 1 ) { targets . push ( '*' ) ; }
if ( typeof mesh == 'string' ) { mesh = obj . meshes [ mesh ] ; }
if ( mesh != null ) { targets . push ( mesh . _id ) ; for ( var i in mesh . links ) { if ( i . startsWith ( 'ugrp/' ) ) { targets . push ( i ) ; } } }
for ( var i in obj . userGroups ) { const g = obj . userGroups [ i ] ; if ( ( g != null ) && ( g . links != null ) && ( g . links [ nodeid ] != null ) ) { targets . push ( i ) ; } }
2020-01-05 00:46:55 +03:00
return targets ;
}
2019-02-06 06:07:12 +03:00
// Clone a safe version of a user object, remove everything that is secret.
2019-10-03 03:19:33 +03:00
obj . CloneSafeUser = function ( user ) {
2019-02-06 06:07:12 +03:00
if ( typeof user != 'object' ) { return user ; }
2019-10-11 21:16:36 +03:00
var user2 = Object . assign ( { } , user ) ; // Shallow clone
2019-02-06 06:07:12 +03:00
delete user2 . hash ;
delete user2 . passhint ;
delete user2 . salt ;
delete user2 . type ;
delete user2 . domain ;
delete user2 . subscriptions ;
delete user2 . passtype ;
2020-04-23 01:29:26 +03:00
delete user2 . otpsms ;
2022-10-22 22:59:12 +03:00
delete user2 . otpmsg ;
2020-03-15 01:03:50 +03:00
if ( ( typeof user2 . otpekey == 'object' ) && ( user2 . otpekey != null ) ) { user2 . otpekey = 1 ; } // Indicates that email 2FA is enabled.
2019-10-03 03:19:33 +03:00
if ( ( typeof user2 . otpsecret == 'string' ) && ( user2 . otpsecret != null ) ) { user2 . otpsecret = 1 ; } // Indicates a time secret is present.
if ( ( typeof user2 . otpkeys == 'object' ) && ( user2 . otpkeys != null ) ) { user2 . otpkeys = 0 ; if ( user . otpkeys != null ) { for ( var i = 0 ; i < user . otpkeys . keys . length ; i ++ ) { if ( user . otpkeys . keys [ i ] . u == true ) { user2 . otpkeys = 1 ; } } } } // Indicates the number of one time backup codes that are active.
if ( ( typeof user2 . otphkeys == 'object' ) && ( user2 . otphkeys != null ) ) { user2 . otphkeys = user2 . otphkeys . length ; } // Indicates the number of hardware keys setup
2021-04-14 05:59:10 +03:00
if ( ( typeof user2 . otpdev == 'string' ) && ( user2 . otpdev != null ) ) { user2 . otpdev = 1 ; } // Indicates device for 2FA push notification
2021-02-09 23:24:12 +03:00
if ( ( typeof user2 . webpush == 'object' ) && ( user2 . webpush != null ) ) { user2 . webpush = user2 . webpush . length ; } // Indicates the number of web push sessions we have
2019-02-06 06:07:12 +03:00
return user2 ;
}
2019-10-11 21:16:36 +03:00
// Clone a safe version of a node object, remove everything that is secret.
obj . CloneSafeNode = function ( node ) {
if ( typeof node != 'object' ) { return node ; }
var r = node ;
2021-08-13 08:52:42 +03:00
if ( ( r . pmt != null ) || ( r . ssh != null ) || ( r . rdp != null ) || ( ( r . intelamt != null ) && ( ( r . intelamt . pass != null ) || ( r . intelamt . mpspass != null ) ) ) ) {
2019-10-11 21:16:36 +03:00
r = Object . assign ( { } , r ) ; // Shallow clone
2021-01-31 03:15:06 +03:00
if ( r . pmt != null ) { r . pmt = 1 ; }
2022-05-18 02:09:35 +03:00
if ( r . ssh != null ) {
var n = { } ;
for ( var i in r . ssh ) {
if ( i . startsWith ( 'user/' ) ) {
if ( r . ssh [ i ] . p ) { n [ i ] = 1 ; } // Username and password
else if ( r . ssh [ i ] . k && r . ssh [ i ] . kp ) { n [ i ] = 2 ; } // Username, key and password
else if ( r . ssh [ i ] . k ) { n [ i ] = 3 ; } // Username and key. No password.
}
}
r . ssh = n ;
2022-05-11 03:44:34 +03:00
}
2022-05-18 02:09:35 +03:00
if ( r . rdp != null ) { var n = { } ; for ( var i in r . rdp ) { if ( i . startsWith ( 'user/' ) ) { n [ i ] = 1 ; } } r . rdp = n ; }
2021-01-31 03:15:06 +03:00
if ( ( r . intelamt != null ) && ( ( r . intelamt . pass != null ) || ( r . intelamt . mpspass != null ) ) ) {
r . intelamt = Object . assign ( { } , r . intelamt ) ; // Shallow clone
if ( r . intelamt . pass != null ) { r . intelamt . pass = 1 ; } ; // Remove the Intel AMT administrator password from the node
if ( r . intelamt . mpspass != null ) { r . intelamt . mpspass = 1 ; } ; // Remove the Intel AMT MPS password from the node
}
2019-10-11 21:16:36 +03:00
}
return r ;
}
// Clone a safe version of a mesh object, remove everything that is secret.
obj . CloneSafeMesh = function ( mesh ) {
if ( typeof mesh != 'object' ) { return mesh ; }
var r = mesh ;
2021-12-03 05:34:18 +03:00
if ( ( ( r . amt != null ) && ( r . amt . password != null ) ) || ( ( r . kvm != null ) && ( r . kvm . pass != null ) ) ) {
2019-10-11 21:16:36 +03:00
r = Object . assign ( { } , r ) ; // Shallow clone
2021-12-03 05:34:18 +03:00
if ( ( r . amt != null ) && ( r . amt . password != null ) ) {
r . amt = Object . assign ( { } , r . amt ) ; // Shallow clone
if ( ( r . amt . password != null ) && ( r . amt . password != '' ) ) { r . amt . password = 1 ; } // Remove the Intel AMT password from the policy
}
if ( ( r . kvm != null ) && ( r . kvm . pass != null ) ) {
r . kvm = Object . assign ( { } , r . kvm ) ; // Shallow clone
if ( ( r . kvm . pass != null ) && ( r . kvm . pass != '' ) ) { r . kvm . pass = 1 ; } // Remove the IP KVM device password
}
2019-10-11 21:16:36 +03:00
}
return r ;
}
2019-10-26 00:41:14 +03:00
// Filter the user web site and only output state that we need to keep
2024-07-16 14:06:06 +03:00
const acceptableUserWebStateStrings = [ 'webPageStackMenu' , 'notifications' , 'deviceView' , 'nightMode' , 'webPageFullScreen' , 'search' , 'showRealNames' , 'sort' , 'deskAspectRatio' , 'viewsize' , 'DeskControl' , 'uiMode' , 'footerBar' , 'loctag' ] ;
2024-06-23 23:00:30 +03:00
const acceptableUserWebStateDesktopStrings = [ 'encoding' , 'showfocus' , 'showmouse' , 'showcad' , 'limitFrameRate' , 'noMouseRotate' , 'quality' , 'scaling' , 'agentencoding' ]
2019-10-26 00:41:14 +03:00
obj . filterUserWebState = function ( state ) {
if ( typeof state == 'string' ) { try { state = JSON . parse ( state ) ; } catch ( ex ) { return null ; } }
2020-06-22 00:27:10 +03:00
if ( ( state == null ) || ( typeof state != 'object' ) ) { return null ; }
2019-10-26 00:41:14 +03:00
var out = { } ;
for ( var i in acceptableUserWebStateStrings ) {
var n = acceptableUserWebStateStrings [ i ] ;
2020-07-29 01:27:28 +03:00
if ( ( state [ n ] != null ) && ( ( typeof state [ n ] == 'number' ) || ( typeof state [ n ] == 'boolean' ) || ( ( typeof state [ n ] == 'string' ) && ( state [ n ] . length < 64 ) ) ) ) { out [ n ] = state [ n ] ; }
2019-10-26 00:41:14 +03:00
}
2020-07-29 01:27:28 +03:00
if ( ( typeof state . stars == 'string' ) && ( state . stars . length < 2048 ) ) { out . stars = state . stars ; }
2019-10-26 00:41:14 +03:00
if ( typeof state . desktopsettings == 'string' ) { try { state . desktopsettings = JSON . parse ( state . desktopsettings ) ; } catch ( ex ) { delete state . desktopsettings ; } }
if ( state . desktopsettings != null ) {
out . desktopsettings = { } ;
for ( var i in acceptableUserWebStateDesktopStrings ) {
var n = acceptableUserWebStateDesktopStrings [ i ] ;
if ( ( state . desktopsettings [ n ] != null ) && ( ( typeof state . desktopsettings [ n ] == 'number' ) || ( typeof state . desktopsettings [ n ] == 'boolean' ) || ( ( typeof state . desktopsettings [ n ] == 'string' ) && ( state . desktopsettings [ n ] . length < 32 ) ) ) ) { out . desktopsettings [ n ] = state . desktopsettings [ n ] ; }
}
out . desktopsettings = JSON . stringify ( out . desktopsettings ) ;
}
2021-01-08 10:35:11 +03:00
if ( ( typeof state . deskKeyShortcuts == 'string' ) && ( state . deskKeyShortcuts . length < 2048 ) ) { out . deskKeyShortcuts = state . deskKeyShortcuts ; }
2021-11-12 04:09:12 +03:00
if ( ( typeof state . deskStrings == 'string' ) && ( state . deskStrings . length < 10000 ) ) { out . deskStrings = state . deskStrings ; }
2022-08-19 23:42:44 +03:00
if ( ( typeof state . runopt == 'string' ) && ( state . runopt . length < 30000 ) ) { out . runopt = state . runopt ; }
2019-10-26 00:41:14 +03:00
return JSON . stringify ( out ) ;
}
2019-08-25 21:08:32 +03:00
// Return the correct render page given mobile, minify and override path.
2020-03-25 23:21:14 +03:00
function getRenderPage ( pagename , req , domain ) {
2020-05-02 09:53:32 +03:00
var mobile = isMobileBrowser ( req ) , minify = ( domain . minify == true ) , p ;
2020-05-27 02:36:17 +03:00
if ( req . query . mobile == '1' ) { mobile = true ; } else if ( req . query . mobile == '0' ) { mobile = false ; }
2020-05-02 09:53:32 +03:00
if ( req . query . minify == '1' ) { minify = true ; } else if ( req . query . minify == '0' ) { minify = false ; }
2022-02-04 05:50:04 +03:00
if ( ( domain != null ) && ( domain . mobilesite === false ) ) { mobile = false ; }
2019-08-25 21:08:32 +03:00
if ( mobile ) {
2020-03-25 23:21:14 +03:00
if ( ( domain != null ) && ( domain . webviewspath != null ) ) { // If the domain has a web views path, use that first
if ( minify ) {
p = obj . path . join ( domain . webviewspath , pagename + '-mobile-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Minify + Override document
}
p = obj . path . join ( domain . webviewspath , pagename + '-mobile' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Override document
}
2019-08-25 21:08:32 +03:00
if ( obj . parent . webViewsOverridePath != null ) {
if ( minify ) {
p = obj . path . join ( obj . parent . webViewsOverridePath , pagename + '-mobile-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Minify + Override document
}
2019-09-21 02:24:37 +03:00
p = obj . path . join ( obj . parent . webViewsOverridePath , pagename + '-mobile' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Override document
2019-08-25 21:08:32 +03:00
}
if ( minify ) {
p = obj . path . join ( obj . parent . webViewsPath , pagename + '-mobile-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile + Minify document
}
2019-09-21 02:24:37 +03:00
p = obj . path . join ( obj . parent . webViewsPath , pagename + '-mobile' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Mobile document
2019-08-25 21:08:32 +03:00
}
2020-03-25 23:21:14 +03:00
if ( ( domain != null ) && ( domain . webviewspath != null ) ) { // If the domain has a web views path, use that first
if ( minify ) {
p = obj . path . join ( domain . webviewspath , pagename + '-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Minify + Override document
}
p = obj . path . join ( domain . webviewspath , pagename ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Override document
}
2019-08-25 21:08:32 +03:00
if ( obj . parent . webViewsOverridePath != null ) {
if ( minify ) {
p = obj . path . join ( obj . parent . webViewsOverridePath , pagename + '-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Minify + Override document
}
2019-09-21 02:24:37 +03:00
p = obj . path . join ( obj . parent . webViewsOverridePath , pagename ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Override document
2019-08-25 21:08:32 +03:00
}
if ( minify ) {
p = obj . path . join ( obj . parent . webViewsPath , pagename + '-min' ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Minify document
}
2019-09-21 02:24:37 +03:00
p = obj . path . join ( obj . parent . webViewsPath , pagename ) ;
if ( obj . fs . existsSync ( p + '.handlebars' ) ) { return p ; } // Default document
return null ;
2019-08-25 21:08:32 +03:00
}
2019-12-16 22:21:07 +03:00
// Return the correct render page arguments.
2020-09-08 21:44:28 +03:00
function getRenderArgs ( xargs , req , domain , page ) {
2020-05-03 09:49:59 +03:00
var minify = ( domain . minify == true ) ;
if ( req . query . minify == '1' ) { minify = true ; } else if ( req . query . minify == '0' ) { minify = false ; }
xargs . min = minify ? '-min' : '' ;
2019-12-18 23:00:08 +03:00
xargs . titlehtml = domain . titlehtml ;
xargs . title = ( domain . title != null ) ? domain . title : 'MeshCentral' ;
2020-09-08 21:44:28 +03:00
if (
( ( page == 'login2' ) && ( domain . loginpicture == null ) && ( domain . titlehtml == null ) ) ||
( ( page != 'login2' ) && ( domain . titlepicture == null ) && ( domain . titlehtml == null ) )
) {
2019-12-18 23:00:08 +03:00
if ( domain . title == null ) {
xargs . title1 = 'MeshCentral' ;
2020-03-30 08:40:13 +03:00
xargs . title2 = '' ;
2019-12-18 23:00:08 +03:00
} else {
xargs . title1 = domain . title ;
xargs . title2 = domain . title2 ? domain . title2 : '' ;
}
} else {
xargs . title1 = domain . title1 ? domain . title1 : '' ;
2020-01-30 01:38:41 +03:00
xargs . title2 = ( domain . title1 && domain . title2 ) ? domain . title2 : '' ;
2019-12-18 23:00:08 +03:00
}
2020-09-08 22:24:07 +03:00
xargs . extitle = encodeURIComponent ( xargs . title ) . split ( '\'' ) . join ( '\\\'' ) ;
2019-12-18 23:00:08 +03:00
xargs . domainurl = domain . url ;
2024-03-24 21:03:33 +03:00
xargs . autocomplete = ( domain . autocomplete === false ) ? 'autocomplete=off x' : 'autocomplete' ; // This option allows autocomplete to be turned off on the login page.
2019-12-21 01:02:49 +03:00
if ( typeof domain . hide == 'number' ) { xargs . hide = domain . hide ; }
2022-06-08 20:47:23 +03:00
2022-06-09 19:58:02 +03:00
// To mitigate any possible BREACH attack, we generate a random 0 to 255 bytes length string here.
xargs . randomlength = ( args . webpagelengthrandomization !== false ) ? parent . crypto . randomBytes ( parent . crypto . randomBytes ( 1 ) [ 0 ] ) . toString ( 'base64' ) : '' ;
2022-06-08 20:47:23 +03:00
2019-12-18 23:00:08 +03:00
return xargs ;
2019-12-16 22:21:07 +03:00
}
2019-10-10 01:56:27 +03:00
// Route a command from a agent. domainid, nodeid and meshid are the values of the source agent.
obj . routeAgentCommand = function ( command , domainid , nodeid , meshid ) {
// Route a message.
// If this command has a sessionid, that is the target.
if ( command . sessionid != null ) {
if ( typeof command . sessionid != 'string' ) return ;
var splitsessionid = command . sessionid . split ( '/' ) ;
// Check that we are in the same domain and the user has rights over this node.
2019-12-28 02:18:43 +03:00
if ( ( splitsessionid . length == 4 ) && ( splitsessionid [ 0 ] == 'user' ) && ( splitsessionid [ 1 ] == domainid ) ) {
2019-10-10 01:56:27 +03:00
// Check if this user has rights to get this message
2020-03-30 08:40:13 +03:00
if ( obj . GetNodeRights ( splitsessionid [ 0 ] + '/' + splitsessionid [ 1 ] + '/' + splitsessionid [ 2 ] , meshid , nodeid ) == 0 ) return ; // TODO: Check if this is ok
2019-10-10 01:56:27 +03:00
// See if the session is connected. If so, go ahead and send this message to the target node
var ws = obj . wssessions2 [ command . sessionid ] ;
if ( ws != null ) {
2020-03-30 08:40:13 +03:00
command . nodeid = nodeid ; // Set the nodeid, required for responses.
delete command . sessionid ; // Remove the sessionid, since we are sending to that sessionid, so it's implyed.
2019-10-10 01:56:27 +03:00
try { ws . send ( JSON . stringify ( command ) ) ; } catch ( ex ) { }
} else if ( parent . multiServer != null ) {
// See if we can send this to a peer server
var serverid = obj . wsPeerSessions2 [ command . sessionid ] ;
if ( serverid != null ) {
command . fromNodeid = nodeid ;
parent . multiServer . DispatchMessageSingleServer ( command , serverid ) ;
}
}
}
} else if ( command . userid != null ) { // If this command has a userid, that is the target.
if ( typeof command . userid != 'string' ) return ;
var splituserid = command . userid . split ( '/' ) ;
// Check that we are in the same domain and the user has rights over this node.
if ( ( splituserid [ 0 ] == 'user' ) && ( splituserid [ 1 ] == domainid ) ) {
// Check if this user has rights to get this message
2020-03-30 08:40:13 +03:00
if ( obj . GetNodeRights ( command . userid , meshid , nodeid ) == 0 ) return ; // TODO: Check if this is ok
2019-10-10 01:56:27 +03:00
// See if the session is connected
var sessions = obj . wssessions [ command . userid ] ;
// Go ahead and send this message to the target node
if ( sessions != null ) {
command . nodeid = nodeid ; // Set the nodeid, required for responses.
2020-03-30 08:40:13 +03:00
delete command . userid ; // Remove the userid, since we are sending to that userid, so it's implyed.
2019-10-10 01:56:27 +03:00
for ( i in sessions ) { sessions [ i ] . send ( JSON . stringify ( command ) ) ; }
}
if ( parent . multiServer != null ) {
// TODO: Add multi-server support
}
}
2020-01-08 00:56:26 +03:00
} else { // Route this command to all users with MESHRIGHT_AGENTCONSOLE rights to this device group
2019-10-10 01:56:27 +03:00
command . nodeid = nodeid ;
var cmdstr = JSON . stringify ( command ) ;
2019-12-28 02:18:43 +03:00
2020-01-08 00:56:26 +03:00
// Find all connected user sessions with access to this device
2019-12-28 02:18:43 +03:00
for ( var userid in obj . wssessions ) {
var xsessions = obj . wssessions [ userid ] ;
2020-03-30 08:40:13 +03:00
if ( obj . GetNodeRights ( userid , meshid , nodeid ) != 0 ) {
2020-01-08 00:56:26 +03:00
// Send the message to all sessions for this user on this server
for ( i in xsessions ) { try { xsessions [ i ] . send ( cmdstr ) ; } catch ( e ) { } }
}
2019-10-10 01:56:27 +03:00
}
// Send the message to all users of other servers
if ( parent . multiServer != null ) {
delete command . nodeid ;
command . fromNodeid = nodeid ;
command . meshid = meshid ;
parent . multiServer . DispatchMessage ( command ) ;
}
}
}
2020-04-05 05:29:20 +03:00
// Returns a list of acceptable languages in order
2020-05-27 10:52:31 +03:00
obj . getLanguageCodes = function ( req ) {
2020-04-05 05:29:20 +03:00
// If a user set a localization, use that
if ( ( req . query . lang == null ) && ( req . session != null ) && ( req . session . userid ) ) {
var user = obj . users [ req . session . userid ] ;
if ( ( user != null ) && ( user . lang != null ) ) { req . query . lang = user . lang ; }
} ;
2019-10-22 03:21:49 +03:00
2020-04-05 05:29:20 +03:00
// Get a list of acceptable languages in order
var acceptLanguages = [ ] ;
if ( req . query . lang != null ) {
acceptLanguages . push ( req . query . lang . toLowerCase ( ) ) ;
} else {
if ( req . headers [ 'accept-language' ] != null ) {
var acceptLanguageSplit = req . headers [ 'accept-language' ] . split ( ';' ) ;
for ( var i in acceptLanguageSplit ) {
var acceptLanguageSplitEx = acceptLanguageSplit [ i ] . split ( ',' ) ;
for ( var j in acceptLanguageSplitEx ) { if ( acceptLanguageSplitEx [ j ] . startsWith ( 'q=' ) == false ) { acceptLanguages . push ( acceptLanguageSplitEx [ j ] . toLowerCase ( ) ) ; } }
2019-10-22 01:59:20 +03:00
}
}
2020-04-05 05:29:20 +03:00
}
return acceptLanguages ;
}
// Render a page using the proper language
2022-01-23 11:34:47 +03:00
function render ( req , res , filename , args , user ) {
2020-04-05 05:29:20 +03:00
if ( obj . renderPages != null ) {
// Get the list of acceptable languages in order
var acceptLanguages = obj . getLanguageCodes ( req ) ;
2024-06-17 11:48:21 +03:00
var domain = getDomain ( req ) ;
2019-10-22 01:59:20 +03:00
// Take a look at the options we have for this file
2024-06-17 11:48:21 +03:00
var fileOptions = obj . renderPages [ domain . id ] [ obj . path . basename ( filename ) ] ;
2019-10-22 03:21:49 +03:00
if ( fileOptions != null ) {
for ( var i in acceptLanguages ) {
2022-01-23 11:34:47 +03:00
if ( ( acceptLanguages [ i ] == 'en' ) || ( acceptLanguages [ i ] . startsWith ( 'en-' ) ) ) {
// English requested
args . lang = 'en' ;
2022-01-23 22:03:30 +03:00
if ( user && user . llang ) { delete user . llang ; obj . db . SetUser ( user ) ; } // Clear user 'last language' used if needed. Since English is the default, remove "last language".
2022-01-23 11:34:47 +03:00
break ;
}
2022-03-25 00:14:33 +03:00
// See if a language (like "fr-ca") or short-language (like "fr") matches an available translation file.
var foundLanguage = null ;
if ( fileOptions [ acceptLanguages [ i ] ] != null ) { foundLanguage = acceptLanguages [ i ] ; } else {
const ptr = acceptLanguages [ i ] . indexOf ( '-' ) ;
if ( ptr >= 0 ) {
const shortAcceptedLanguage = acceptLanguages [ i ] . substring ( 0 , ptr ) ;
if ( fileOptions [ shortAcceptedLanguage ] != null ) { foundLanguage = shortAcceptedLanguage ; }
}
}
// If a language is found, render it.
if ( foundLanguage != null ) {
2019-12-19 01:57:29 +03:00
// Found a match. If the file no longer exists, default to English.
2022-03-25 00:14:33 +03:00
obj . fs . exists ( fileOptions [ foundLanguage ] + '.handlebars' , function ( exists ) {
if ( exists ) { args . lang = foundLanguage ; res . render ( fileOptions [ foundLanguage ] , args ) ; } else { args . lang = 'en' ; res . render ( filename , args ) ; }
2019-12-21 01:02:49 +03:00
} ) ;
2022-03-25 00:14:33 +03:00
if ( user && ( user . llang != foundLanguage ) ) { user . llang = foundLanguage ; obj . db . SetUser ( user ) ; } // Set user 'last language' used if needed.
2019-12-19 01:57:29 +03:00
return ;
2020-02-01 01:44:11 +03:00
}
2019-10-22 03:21:49 +03:00
}
2019-10-22 01:59:20 +03:00
}
}
2022-01-23 11:34:47 +03:00
// No matches found, render the default English page.
2019-10-22 01:59:20 +03:00
res . render ( filename , args ) ;
}
// Get the list of pages with different languages that can be rendered
function getRenderList ( ) {
2020-01-28 04:52:20 +03:00
// Fetch default rendeing pages
2019-10-23 03:19:17 +03:00
var translateFolder = null ;
if ( obj . fs . existsSync ( 'views/translations' ) ) { translateFolder = 'views/translations' ; }
if ( obj . fs . existsSync ( obj . path . join ( _ _dirname , 'views' , 'translations' ) ) ) { translateFolder = obj . path . join ( _ _dirname , 'views' , 'translations' ) ; }
if ( translateFolder != null ) {
2019-10-22 01:59:20 +03:00
obj . renderPages = { } ;
2019-10-22 21:59:19 +03:00
obj . renderLanguages = [ 'en' ] ;
2024-06-17 11:48:21 +03:00
for ( var i in parent . config . domains ) {
if ( obj . fs . existsSync ( 'views/translations' ) ) { translateFolder = 'views/translations' ; }
if ( obj . fs . existsSync ( obj . path . join ( _ _dirname , 'views' , 'translations' ) ) ) { translateFolder = obj . path . join ( _ _dirname , 'views' , 'translations' ) ; }
2020-01-28 04:52:20 +03:00
var files = obj . fs . readdirSync ( translateFolder ) ;
2024-06-17 11:48:21 +03:00
var domain = parent . config . domains [ i ] . id ;
obj . renderPages [ domain ] = { } ;
2020-01-28 04:52:20 +03:00
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.handlebars' ) ) {
name = name . substring ( 0 , name . length - 11 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
2024-06-17 11:48:21 +03:00
if ( obj . renderPages [ domain ] [ xname [ 0 ] ] == null ) { obj . renderPages [ domain ] [ xname [ 0 ] ] = { } ; }
obj . renderPages [ domain ] [ xname [ 0 ] ] [ xname [ 1 ] ] = obj . path . join ( translateFolder , name ) ;
2020-01-28 04:52:20 +03:00
if ( obj . renderLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . renderLanguages . push ( xname [ 1 ] ) ; }
}
}
}
2024-06-17 11:48:21 +03:00
// See if there are any custom rending pages that will override the default ones
if ( ( obj . parent . webViewsOverridePath != null ) && ( obj . fs . existsSync ( obj . path . join ( obj . parent . webViewsOverridePath , 'translations' ) ) ) ) {
translateFolder = obj . path . join ( obj . parent . webViewsOverridePath , 'translations' ) ;
var files = obj . fs . readdirSync ( translateFolder ) ;
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.handlebars' ) ) {
name = name . substring ( 0 , name . length - 11 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
if ( obj . renderPages [ domain ] [ xname [ 0 ] ] == null ) { obj . renderPages [ domain ] [ xname [ 0 ] ] = { } ; }
obj . renderPages [ domain ] [ xname [ 0 ] ] [ xname [ 1 ] ] = obj . path . join ( translateFolder , name ) ;
if ( obj . renderLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . renderLanguages . push ( xname [ 1 ] ) ; }
}
}
}
}
// See if there is a custom meshcentral-web-domain folder as that will override the default ones
if ( obj . fs . existsSync ( obj . path . join ( _ _dirname , '..' , 'meshcentral-web-' + domain , 'views' , 'translations' ) ) ) {
translateFolder = obj . path . join ( _ _dirname , '..' , 'meshcentral-web-' + domain , 'views' , 'translations' ) ;
var files = obj . fs . readdirSync ( translateFolder ) ;
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.handlebars' ) ) {
name = name . substring ( 0 , name . length - 11 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
if ( obj . renderPages [ domain ] [ xname [ 0 ] ] == null ) { obj . renderPages [ domain ] [ xname [ 0 ] ] = { } ; }
obj . renderPages [ domain ] [ xname [ 0 ] ] [ xname [ 1 ] ] = obj . path . join ( translateFolder , name ) ;
if ( obj . renderLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . renderLanguages . push ( xname [ 1 ] ) ; }
}
}
}
}
2020-01-28 04:52:20 +03:00
}
2019-10-22 01:59:20 +03:00
}
}
2020-04-05 05:29:20 +03:00
// Get the list of pages with different languages that can be rendered
function getEmailLanguageList ( ) {
// Fetch default rendeing pages
var translateFolder = null ;
if ( obj . fs . existsSync ( 'emails/translations' ) ) { translateFolder = 'emails/translations' ; }
if ( obj . fs . existsSync ( obj . path . join ( _ _dirname , 'emails' , 'translations' ) ) ) { translateFolder = obj . path . join ( _ _dirname , 'emails' , 'translations' ) ; }
if ( translateFolder != null ) {
obj . emailLanguages = [ 'en' ] ;
var files = obj . fs . readdirSync ( translateFolder ) ;
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.html' ) ) {
name = name . substring ( 0 , name . length - 5 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
if ( obj . emailLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . emailLanguages . push ( xname [ 1 ] ) ; }
}
}
}
// See if there are any custom rending pages that will override the default ones
if ( ( obj . parent . webEmailsOverridePath != null ) && ( obj . fs . existsSync ( obj . path . join ( obj . parent . webEmailsOverridePath , 'translations' ) ) ) ) {
translateFolder = obj . path . join ( obj . parent . webEmailsOverridePath , 'translations' ) ;
var files = obj . fs . readdirSync ( translateFolder ) ;
for ( var i in files ) {
var name = files [ i ] ;
if ( name . endsWith ( '.html' ) ) {
name = name . substring ( 0 , name . length - 5 ) ;
var xname = name . split ( '_' ) ;
if ( xname . length == 2 ) {
if ( obj . emailLanguages . indexOf ( xname [ 1 ] ) == - 1 ) { obj . emailLanguages . push ( xname [ 1 ] ) ; }
}
}
}
}
}
}
2021-02-10 02:31:50 +03:00
// Perform a web push to a user
// If any of the push fail, remove the subscription from the user's webpush subscription list.
obj . performWebPush = function ( domain , user , payload , options ) {
if ( ( parent . webpush == null ) || ( Array . isArray ( user . webpush ) == false ) || ( user . webpush . length == 0 ) ) return ;
var completionFunc = function pushCompletionFunc ( sub , fail ) {
pushCompletionFunc . failCount += fail ;
if ( -- pushCompletionFunc . pushCount == 0 ) {
if ( pushCompletionFunc . failCount > 0 ) {
var user = pushCompletionFunc . user , newwebpush = [ ] ;
for ( var i in user . webpush ) { if ( user . webpush [ i ] . fail == null ) { newwebpush . push ( user . webpush [ i ] ) ; } }
user . webpush = newwebpush ;
// Update the database
obj . db . SetUser ( user ) ;
// Event the change
var message = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , domain : domain . id , nolog : 1 } ;
if ( db . changeStream ) { message . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
parent . DispatchEvent ( targets , obj , message ) ;
}
}
}
completionFunc . pushCount = user . webpush . length ;
completionFunc . user = user ;
completionFunc . domain = domain ;
completionFunc . failCount = 0 ;
for ( var i in user . webpush ) {
var errorFunc = function pushErrorFunc ( error ) { pushErrorFunc . sub . fail = 1 ; pushErrorFunc . call ( pushErrorFunc . sub , 1 ) ; }
errorFunc . sub = user . webpush [ i ] ;
errorFunc . call = completionFunc ;
var successFunc = function pushSuccessFunc ( value ) { pushSuccessFunc . call ( pushSuccessFunc . sub , 0 ) ; }
successFunc . sub = user . webpush [ i ] ;
successFunc . call = completionFunc ;
parent . webpush . sendNotification ( user . webpush [ i ] , JSON . stringify ( payload ) , options ) . then ( successFunc , errorFunc ) ;
}
}
2024-03-04 03:03:27 +03:00
// Ensure exclusivity of a push messaging token for Android device
2021-02-23 03:54:30 +03:00
obj . removePmtFromAllOtherNodes = function ( node ) {
if ( typeof node . pmt != 'string' ) return ;
db . Get ( 'pmt_' + node . pmt , function ( err , docs ) {
if ( ( err == null ) && ( docs . length == 1 ) ) {
var oldNodeId = docs [ 0 ] . nodeid ;
db . Get ( oldNodeId , function ( nerr , ndocs ) {
if ( ( nerr == null ) && ( ndocs . length == 1 ) ) {
var oldNode = ndocs [ 0 ] ;
if ( oldNode . pmt == node . pmt ) {
// Remove the push messaging token and save the node.
delete oldNode . pmt ;
db . Set ( oldNode ) ;
// Event the node change
var event = { etype : 'node' , action : 'changenode' , nodeid : oldNode . _id , domain : oldNode . domain , node : obj . CloneSafeNode ( oldNode ) }
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
parent . DispatchEvent ( [ '*' , oldNode . meshid , oldNode . _id ] , obj , event ) ;
2022-07-20 22:57:24 +03:00
}
2021-02-23 03:54:30 +03:00
}
} ) ;
}
db . Set ( { _id : 'pmt_' + node . pmt , type : 'pmt' , domain : node . domain , time : Date . now ( ) , nodeid : node . _id } )
} ) ;
}
2018-05-22 02:36:51 +03:00
// Return true if a mobile browser is detected.
2018-05-24 07:43:44 +03:00
// This code comes from "http://detectmobilebrowsers.com/" and was modified, This is free and unencumbered software released into the public domain. For more information, please refer to the http://unlicense.org/
2018-05-30 02:57:08 +03:00
function isMobileBrowser ( req ) {
//var ua = req.headers['user-agent'].toLowerCase();
//return (/(android|bb\d+|meego).+mobile|mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(ua) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(ua.substr(0, 4)));
2018-08-30 03:40:30 +03:00
if ( typeof req . headers [ 'user-agent' ] != 'string' ) return false ;
2018-05-30 02:57:08 +03:00
return ( req . headers [ 'user-agent' ] . toLowerCase ( ) . indexOf ( 'mobile' ) >= 0 ) ;
2018-05-22 02:36:51 +03:00
}
2021-02-26 11:58:38 +03:00
// Return decoded user agent information
2024-03-04 03:03:27 +03:00
obj . getUserAgentInfo = function ( req ) {
2021-02-26 11:58:38 +03:00
var browser = 'Unknown' , os = 'Unknown' ;
try {
2022-09-02 08:06:08 +03:00
const ua = obj . uaparser ( ( typeof req == 'string' ) ? req : req . headers [ 'user-agent' ] ) ;
2021-02-26 11:58:38 +03:00
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 } }
return ua ;
} catch ( ex ) { return { browserStr : browser , osStr : os } }
}
2019-06-12 20:23:26 +03:00
// Return the query string portion of the URL, the ? and anything after.
function getQueryPortion ( req ) { var s = req . url . indexOf ( '?' ) ; if ( s == - 1 ) { if ( req . body && req . body . urlargs ) { return req . body . urlargs ; } return '' ; } return req . url . substring ( s ) ; }
2019-06-22 03:18:48 +03:00
// Generate a random Intel AMT password
function checkAmtPassword ( p ) { return ( p . length > 7 ) && ( /\d/ . test ( p ) ) && ( /[a-z]/ . test ( p ) ) && ( /[A-Z]/ . test ( p ) ) && ( /\W/ . test ( p ) ) ; }
function getRandomAmtPassword ( ) { var p ; do { p = Buffer . from ( obj . crypto . randomBytes ( 9 ) , 'binary' ) . toString ( 'base64' ) . split ( '/' ) . join ( '@' ) ; } while ( checkAmtPassword ( p ) == false ) ; return p ; }
2019-08-08 02:07:12 +03:00
function getRandomPassword ( ) { return Buffer . from ( obj . crypto . randomBytes ( 9 ) , 'binary' ) . toString ( 'base64' ) . split ( '/' ) . join ( '@' ) ; }
2019-11-16 01:36:41 +03:00
function getRandomLowerCase ( len ) { var r = '' , random = obj . crypto . randomBytes ( len ) ; for ( var i = 0 ; i < len ; i ++ ) { r += String . fromCharCode ( 97 + ( random [ i ] % 26 ) ) ; } return r ; }
2019-06-22 03:18:48 +03:00
2020-03-14 06:39:21 +03:00
// Generate a 8 digit integer with even random probability for each value.
function getRandomEightDigitInteger ( ) { var bigInt ; do { bigInt = parent . crypto . randomBytes ( 4 ) . readUInt32BE ( 0 ) ; } while ( bigInt >= 4200000000 ) ; return bigInt % 100000000 ; }
2020-04-23 01:29:26 +03:00
function getRandomSixDigitInteger ( ) { var bigInt ; do { bigInt = parent . crypto . randomBytes ( 4 ) . readUInt32BE ( 0 ) ; } while ( bigInt >= 4200000000 ) ; return bigInt % 1000000 ; }
2020-03-14 06:39:21 +03:00
2019-08-08 00:25:23 +03:00
// Clean a IPv6 address that encodes a IPv4 address
2019-09-20 20:41:31 +03:00
function cleanRemoteAddr ( addr ) { if ( typeof addr != 'string' ) { return null ; } if ( addr . indexOf ( '::ffff:' ) == 0 ) { return addr . substring ( 7 ) ; } else { return addr ; } }
2019-08-08 00:25:23 +03:00
2020-10-23 23:49:38 +03:00
// Set the content disposition header for a HTTP response.
// Because the filename can't have any special characters in it, we need to be extra careful.
function setContentDispositionHeader ( res , type , name , size , altname ) {
2021-02-04 23:27:14 +03:00
var name = require ( 'path' ) . basename ( name ) . split ( '\\' ) . join ( '' ) . split ( '/' ) . join ( '' ) . split ( ':' ) . join ( '' ) . split ( '*' ) . join ( '' ) . split ( '?' ) . join ( '' ) . split ( '"' ) . join ( '' ) . split ( '<' ) . join ( '' ) . split ( '>' ) . join ( '' ) . split ( '|' ) . join ( '' ) . split ( '\'' ) . join ( '' ) ;
2020-10-23 23:49:38 +03:00
try {
2021-02-05 06:00:22 +03:00
var x = { 'Cache-Control' : 'no-store' , 'Content-Type' : type , 'Content-Disposition' : 'attachment; filename="' + encodeURIComponent ( name ) + '"' } ;
2020-10-23 23:49:38 +03:00
if ( typeof size == 'number' ) { x [ 'Content-Length' ] = size ; }
res . set ( x ) ;
} catch ( ex ) {
var x = { 'Cache-Control' : 'no-store' , 'Content-Type' : type , 'Content-Disposition' : 'attachment; filename="' + altname + '"' } ;
if ( typeof size == 'number' ) { x [ 'Content-Length' ] = size ; }
res . set ( x ) ;
}
}
2019-08-08 00:25:23 +03:00
// Record a new entry in a recording log
2019-08-13 21:49:05 +03:00
function recordingEntry ( fd , type , flags , data , func , tag ) {
2019-08-08 00:25:23 +03:00
try {
if ( typeof data == 'string' ) {
// String write
var blockData = Buffer . from ( data ) , header = Buffer . alloc ( 16 ) ; // Header: Type (2) + Flags (2) + Size(4) + Time(8)
header . writeInt16BE ( type , 0 ) ; // Type (1 = Header, 2 = Network Data)
header . writeInt16BE ( flags , 2 ) ; // Flags (1 = Binary, 2 = User)
header . writeInt32BE ( blockData . length , 4 ) ; // Size
header . writeIntBE ( new Date ( ) , 10 , 6 ) ; // Time
var block = Buffer . concat ( [ header , blockData ] ) ;
2019-08-13 21:49:05 +03:00
obj . fs . write ( fd , block , 0 , block . length , function ( ) { func ( fd , tag ) ; } ) ;
2019-08-08 00:25:23 +03:00
} else {
// Binary write
var header = Buffer . alloc ( 16 ) ; // Header: Type (2) + Flags (2) + Size(4) + Time(8)
header . writeInt16BE ( type , 0 ) ; // Type (1 = Header, 2 = Network Data)
header . writeInt16BE ( flags | 1 , 2 ) ; // Flags (1 = Binary, 2 = User)
header . writeInt32BE ( data . length , 4 ) ; // Size
header . writeIntBE ( new Date ( ) , 10 , 6 ) ; // Time
var block = Buffer . concat ( [ header , data ] ) ;
2019-08-13 21:49:05 +03:00
obj . fs . write ( fd , block , 0 , block . length , function ( ) { func ( fd , tag ) ; } ) ;
2019-08-08 00:25:23 +03:00
}
2019-08-13 21:49:05 +03:00
} catch ( ex ) { console . log ( ex ) ; func ( fd , tag ) ; }
2019-08-08 00:25:23 +03:00
}
2020-12-11 00:56:15 +03:00
// Perform a IP match against a list
function isIPMatch ( ip , matchList ) {
const ipcheck = require ( 'ipcheck' ) ;
for ( var i in matchList ) { if ( ipcheck . match ( ip , matchList [ i ] ) == true ) return true ; }
return false ;
}
2019-11-19 04:31:42 +03:00
// This is the invalid login throttling code
obj . badLoginTable = { } ;
obj . badLoginTableLastClean = 0 ;
if ( parent . config . settings == null ) { parent . config . settings = { } ; }
2020-02-18 21:57:39 +03:00
if ( parent . config . settings . maxinvalidlogin !== false ) {
if ( typeof parent . config . settings . maxinvalidlogin != 'object' ) { parent . config . settings . maxinvalidlogin = { time : 10 , count : 10 } ; }
if ( typeof parent . config . settings . maxinvalidlogin . time != 'number' ) { parent . config . settings . maxinvalidlogin . time = 10 ; }
if ( typeof parent . config . settings . maxinvalidlogin . count != 'number' ) { parent . config . settings . maxinvalidlogin . count = 10 ; }
if ( ( typeof parent . config . settings . maxinvalidlogin . coolofftime != 'number' ) || ( parent . config . settings . maxinvalidlogin . coolofftime < 1 ) ) { parent . config . settings . maxinvalidlogin . coolofftime = null ; }
}
2019-11-19 04:31:42 +03:00
obj . setbadLogin = function ( ip ) { // Set an IP address that just did a bad login request
2020-02-18 21:57:39 +03:00
if ( parent . config . settings . maxinvalidlogin === false ) return ;
2020-05-22 07:25:11 +03:00
if ( typeof ip == 'object' ) { ip = ip . clientIp ; }
2021-10-14 03:15:26 +03:00
if ( parent . config . settings . maxinvalidlogin != null ) {
if ( typeof parent . config . settings . maxinvalidlogin . exclude == 'string' ) {
const excludeSplit = parent . config . settings . maxinvalidlogin . exclude . split ( ',' ) ;
for ( var i in excludeSplit ) { if ( require ( 'ipcheck' ) . match ( ip , excludeSplit [ i ] ) ) return ; }
} else if ( Array . isArray ( parent . config . settings . maxinvalidlogin . exclude ) ) {
for ( var i in parent . config . settings . maxinvalidlogin . exclude ) { if ( require ( 'ipcheck' ) . match ( ip , parent . config . settings . maxinvalidlogin . exclude [ i ] ) ) return ; }
}
}
2020-01-11 03:25:02 +03:00
var splitip = ip . split ( '.' ) ;
if ( splitip . length == 4 ) { ip = ( splitip [ 0 ] + '.' + splitip [ 1 ] + '.' + splitip [ 2 ] + '.*' ) ; }
2019-11-19 04:31:42 +03:00
if ( ++ obj . badLoginTableLastClean > 100 ) { obj . cleanBadLoginTable ( ) ; }
2019-11-20 00:33:52 +03:00
if ( typeof obj . badLoginTable [ ip ] == 'number' ) { if ( obj . badLoginTable [ ip ] < Date . now ( ) ) { delete obj . badLoginTable [ ip ] ; } else { return ; } } // Check cooloff period
2019-11-19 04:31:42 +03:00
if ( obj . badLoginTable [ ip ] == null ) { obj . badLoginTable [ ip ] = [ Date . now ( ) ] ; } else { obj . badLoginTable [ ip ] . push ( Date . now ( ) ) ; }
2019-11-20 00:33:52 +03:00
if ( ( obj . badLoginTable [ ip ] . length >= parent . config . settings . maxinvalidlogin . count ) && ( parent . config . settings . maxinvalidlogin . coolofftime != null ) ) {
obj . badLoginTable [ ip ] = Date . now ( ) + ( parent . config . settings . maxinvalidlogin . coolofftime * 60000 ) ; // Move to cooloff period
}
2019-11-19 04:31:42 +03:00
}
obj . checkAllowLogin = function ( ip ) { // Check if an IP address is allowed to login
2020-02-18 21:57:39 +03:00
if ( parent . config . settings . maxinvalidlogin === false ) return true ;
2020-05-22 07:25:11 +03:00
if ( typeof ip == 'object' ) { ip = ip . clientIp ; }
2020-01-11 03:25:02 +03:00
var splitip = ip . split ( '.' ) ;
if ( splitip . length == 4 ) { ip = ( splitip [ 0 ] + '.' + splitip [ 1 ] + '.' + splitip [ 2 ] + '.*' ) ; } // If this is IPv4, keep only the 3 first
2019-11-19 04:31:42 +03:00
var cutoffTime = Date . now ( ) - ( parent . config . settings . maxinvalidlogin . time * 60000 ) ; // Time in minutes
var ipTable = obj . badLoginTable [ ip ] ;
if ( ipTable == null ) return true ;
2019-11-20 00:33:52 +03:00
if ( typeof ipTable == 'number' ) { if ( obj . badLoginTable [ ip ] < Date . now ( ) ) { delete obj . badLoginTable [ ip ] ; } else { return false ; } } // Check cooloff period
2019-11-19 04:31:42 +03:00
while ( ( ipTable . length > 0 ) && ( ipTable [ 0 ] < cutoffTime ) ) { ipTable . shift ( ) ; }
if ( ipTable . length == 0 ) { delete obj . badLoginTable [ ip ] ; return true ; }
return ( ipTable . length < parent . config . settings . maxinvalidlogin . count ) ; // No more than x bad logins in x minutes
}
obj . cleanBadLoginTable = function ( ) { // Clean up the IP address login blockage table, we do this occasionaly.
2020-02-18 21:57:39 +03:00
if ( parent . config . settings . maxinvalidlogin === false ) return ;
2019-11-19 04:31:42 +03:00
var cutoffTime = Date . now ( ) - ( parent . config . settings . maxinvalidlogin . time * 60000 ) ; // Time in minutes
2019-11-20 00:33:52 +03:00
for ( var ip in obj . badLoginTable ) {
2019-11-19 04:31:42 +03:00
var ipTable = obj . badLoginTable [ ip ] ;
2019-11-20 00:33:52 +03:00
if ( typeof ipTable == 'number' ) {
if ( obj . badLoginTable [ ip ] < Date . now ( ) ) { delete obj . badLoginTable [ ip ] ; } // Check cooloff period
} else {
while ( ( ipTable . length > 0 ) && ( ipTable [ 0 ] < cutoffTime ) ) { ipTable . shift ( ) ; }
if ( ipTable . length == 0 ) { delete obj . badLoginTable [ ip ] ; }
}
2019-11-19 04:31:42 +03:00
}
obj . badLoginTableLastClean = 0 ;
}
2022-01-04 00:12:10 +03:00
// This is the invalid 2FA throttling code
obj . bad2faTable = { } ;
obj . bad2faTableLastClean = 0 ;
if ( parent . config . settings == null ) { parent . config . settings = { } ; }
if ( parent . config . settings . maxinvalid2fa !== false ) {
if ( typeof parent . config . settings . maxinvalid2fa != 'object' ) { parent . config . settings . maxinvalid2fa = { time : 10 , count : 10 } ; }
if ( typeof parent . config . settings . maxinvalid2fa . time != 'number' ) { parent . config . settings . maxinvalid2fa . time = 10 ; }
if ( typeof parent . config . settings . maxinvalid2fa . count != 'number' ) { parent . config . settings . maxinvalid2fa . count = 10 ; }
if ( ( typeof parent . config . settings . maxinvalid2fa . coolofftime != 'number' ) || ( parent . config . settings . maxinvalid2fa . coolofftime < 1 ) ) { parent . config . settings . maxinvalid2fa . coolofftime = null ; }
}
obj . setbad2Fa = function ( ip ) { // Set an IP address that just did a bad 2FA request
if ( parent . config . settings . maxinvalid2fa === false ) return ;
if ( typeof ip == 'object' ) { ip = ip . clientIp ; }
if ( parent . config . settings . maxinvalid2fa != null ) {
if ( typeof parent . config . settings . maxinvalid2fa . exclude == 'string' ) {
const excludeSplit = parent . config . settings . maxinvalid2fa . exclude . split ( ',' ) ;
for ( var i in excludeSplit ) { if ( require ( 'ipcheck' ) . match ( ip , excludeSplit [ i ] ) ) return ; }
} else if ( Array . isArray ( parent . config . settings . maxinvalid2fa . exclude ) ) {
for ( var i in parent . config . settings . maxinvalid2fa . exclude ) { if ( require ( 'ipcheck' ) . match ( ip , parent . config . settings . maxinvalid2fa . exclude [ i ] ) ) return ; }
}
}
var splitip = ip . split ( '.' ) ;
if ( splitip . length == 4 ) { ip = ( splitip [ 0 ] + '.' + splitip [ 1 ] + '.' + splitip [ 2 ] + '.*' ) ; }
if ( ++ obj . bad2faTableLastClean > 100 ) { obj . cleanBad2faTable ( ) ; }
if ( typeof obj . bad2faTable [ ip ] == 'number' ) { if ( obj . bad2faTable [ ip ] < Date . now ( ) ) { delete obj . bad2faTable [ ip ] ; } else { return ; } } // Check cooloff period
if ( obj . bad2faTable [ ip ] == null ) { obj . bad2faTable [ ip ] = [ Date . now ( ) ] ; } else { obj . bad2faTable [ ip ] . push ( Date . now ( ) ) ; }
if ( ( obj . bad2faTable [ ip ] . length >= parent . config . settings . maxinvalid2fa . count ) && ( parent . config . settings . maxinvalid2fa . coolofftime != null ) ) {
obj . bad2faTable [ ip ] = Date . now ( ) + ( parent . config . settings . maxinvalid2fa . coolofftime * 60000 ) ; // Move to cooloff period
}
}
obj . checkAllow2Fa = function ( ip ) { // Check if an IP address is allowed to perform 2FA
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
var cutoffTime = Date . now ( ) - ( parent . config . settings . maxinvalid2fa . time * 60000 ) ; // Time in minutes
var ipTable = obj . bad2faTable [ ip ] ;
if ( ipTable == null ) return true ;
if ( typeof ipTable == 'number' ) { if ( obj . bad2faTable [ ip ] < Date . now ( ) ) { delete obj . bad2faTable [ ip ] ; } else { return false ; } } // Check cooloff period
while ( ( ipTable . length > 0 ) && ( ipTable [ 0 ] < cutoffTime ) ) { ipTable . shift ( ) ; }
if ( ipTable . length == 0 ) { delete obj . bad2faTable [ ip ] ; return true ; }
return ( ipTable . length < parent . config . settings . maxinvalid2fa . count ) ; // No more than x bad 2FAs in x minutes
}
obj . cleanBad2faTable = function ( ) { // Clean up the IP address 2FA blockage table, we do this occasionaly.
if ( parent . config . settings . maxinvalid2fa === false ) return ;
var cutoffTime = Date . now ( ) - ( parent . config . settings . maxinvalid2fa . time * 60000 ) ; // Time in minutes
for ( var ip in obj . bad2faTable ) {
var ipTable = obj . bad2faTable [ ip ] ;
if ( typeof ipTable == 'number' ) {
if ( obj . bad2faTable [ ip ] < Date . now ( ) ) { delete obj . bad2faTable [ ip ] ; } // Check cooloff period
} else {
while ( ( ipTable . length > 0 ) && ( ipTable [ 0 ] < cutoffTime ) ) { ipTable . shift ( ) ; }
if ( ipTable . length == 0 ) { delete obj . bad2faTable [ ip ] ; }
}
}
obj . bad2faTableLastClean = 0 ;
}
2021-11-04 04:52:11 +03:00
// Hold a websocket until additional arguments are provided within the socket.
// This is a generic function that can be used for any websocket to avoid passing arguments in the URL.
function getWebsocketArgs ( ws , req , func ) {
if ( req . query . moreargs != '1' ) {
// No more arguments needed, pass the websocket thru
func ( ws , req ) ;
} else {
// More arguments are needed
delete req . query . moreargs ;
const xfunc = function getWebsocketArgsEx ( msg ) {
var command = null ;
try { command = JSON . parse ( msg . toString ( 'utf8' ) ) ; } catch ( e ) { return ; }
if ( ( command != null ) && ( command . action === 'urlargs' ) && ( typeof command . args == 'object' ) ) {
for ( var i in command . args ) { getWebsocketArgsEx . req . query [ i ] = command . args [ i ] ; }
ws . removeEventListener ( 'message' , getWebsocketArgsEx ) ;
getWebsocketArgsEx . func ( getWebsocketArgsEx . ws , getWebsocketArgsEx . req ) ;
}
}
xfunc . ws = ws ;
xfunc . req = req ;
xfunc . func = func ;
ws . on ( 'message' , xfunc ) ;
}
}
2022-01-07 02:05:45 +03:00
// Set a random value to this session. Only works if the session has a userid.
// This random value along with the userid is used to destroy the session when logging out.
function setSessionRandom ( req ) {
if ( ( req . session == null ) || ( req . session . userid == null ) || ( req . session . x != null ) ) return ;
var x = obj . crypto . randomBytes ( 6 ) . toString ( 'base64' ) ;
while ( obj . destroyedSessions [ req . session . userid + '/' + x ] != null ) { x = obj . crypto . randomBytes ( 6 ) . toString ( 'base64' ) ; }
req . session . x = x ;
}
// Remove all destroyed sessions after 2 hours, these sessions would have timed out anyway.
function clearDestroyedSessions ( ) {
var toRemove = [ ] , t = Date . now ( ) - ( 2 * 60 * 60 * 1000 ) ;
for ( var i in obj . destroyedSessions ) { if ( obj . destroyedSessions [ i ] < t ) { toRemove . push ( i ) ; } }
for ( var i in toRemove ) { delete obj . destroyedSessions [ toRemove [ i ] ] ; }
}
2022-02-17 07:51:36 +03:00
// Check and/or convert the agent color value into a correct string or return empty string.
function checkAgentColorString ( header , value ) {
if ( ( typeof header !== 'string' ) || ( typeof value !== 'string' ) ) return '' ;
if ( value . startsWith ( '#' ) && ( value . length == 7 ) ) {
// Convert color in hex format
value = parseInt ( value . substring ( 1 , 3 ) , 16 ) + ',' + parseInt ( value . substring ( 3 , 5 ) , 16 ) + ',' + parseInt ( value . substring ( 5 , 7 ) , 16 ) ;
} else {
// Check color in decimal format
const valueSplit = value . split ( ',' ) ;
if ( valueSplit . length != 3 ) return '' ;
const r = parseInt ( valueSplit [ 0 ] ) , g = parseInt ( valueSplit [ 1 ] ) , b = parseInt ( valueSplit [ 2 ] ) ;
if ( isNaN ( r ) || ( r < 0 ) || ( r > 255 ) || isNaN ( g ) || ( g < 0 ) || ( g > 255 ) || isNaN ( b ) || ( b < 0 ) || ( b > 255 ) ) return '' ;
value = r + ',' + g + ',' + b ;
}
return header + value + '\r\n' ;
}
2022-07-03 10:44:58 +03:00
// Check that everything is cleaned up
function checkWebRelaySessionsTimeout ( ) {
for ( var i in webRelaySessions ) { webRelaySessions [ i ] . checkTimeout ( ) ; }
}
2022-04-13 00:03:52 +03:00
2022-07-29 01:12:28 +03:00
// Return true if this is a private IP address
function isPrivateAddress ( ip _addr ) {
// If this is a loopback address, return true
if ( ( ip _addr == '127.0.0.1' ) || ( ip _addr == '::1' ) ) return true ;
// Check IPv4 private addresses
const ipcheck = require ( 'ipcheck' ) ;
const IPv4PrivateRanges = [ '0.0.0.0/8' , '10.0.0.0/8' , '100.64.0.0/10' , '127.0.0.0/8' , '169.254.0.0/16' , '172.16.0.0/12' , '192.0.0.0/24' , '192.0.0.0/29' , '192.0.0.8/32' , '192.0.0.9/32' , '192.0.0.10/32' , '192.0.0.170/32' , '192.0.0.171/32' , '192.0.2.0/24' , '192.31.196.0/24' , '192.52.193.0/24' , '192.88.99.0/24' , '192.168.0.0/16' , '192.175.48.0/24' , '198.18.0.0/15' , '198.51.100.0/24' , '203.0.113.0/24' , '240.0.0.0/4' , '255.255.255.255/32' ]
for ( var i in IPv4PrivateRanges ) { if ( ipcheck . match ( ip _addr , IPv4PrivateRanges [ i ] ) ) return true ; }
// Check IPv6 private addresses
return /^::$/ . test ( ip _addr ) ||
/^::1$/ . test ( ip _addr ) ||
/^::f{4}:([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/ . test ( ip _addr ) ||
/^::f{4}:0.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/ . test ( ip _addr ) ||
/^64:ff9b::([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/ . test ( ip _addr ) ||
/^100::([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/ . test ( ip _addr ) ||
/^2001::([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/ . test ( ip _addr ) ||
/^2001:2[0-9a-fA-F]:([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/ . test ( ip _addr ) ||
/^2001:db8:([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/ . test ( ip _addr ) ||
/^2002:([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4}):?([0-9a-fA-F]{0,4})$/ . test ( ip _addr ) ||
/^f[c-d]([0-9a-fA-F]{2,2}):/i . test ( ip _addr ) ||
/^fe[8-9a-bA-B][0-9a-fA-F]:/i . test ( ip _addr ) ||
/^ff([0-9a-fA-F]{2,2}):/i . test ( ip _addr )
}
2022-04-10 03:12:52 +03:00
// Check that a cookie IP is within the correct range depending on the active policy
function checkCookieIp ( cookieip , ip ) {
if ( obj . args . cookieipcheck == 'none' ) return true ; // 'none' - No IP address checking
if ( obj . args . cookieipcheck == 'strict' ) return ( cookieip == ip ) ; // 'strict' - Strict IP address checking, this can cause issues with HTTP proxies or load-balancers.
2022-07-29 01:12:28 +03:00
if ( require ( 'ipcheck' ) . match ( cookieip , ip + '/24' ) ) return true ; // 'lax' - IP address need to be in the some range
return ( isPrivateAddress ( cookieip ) && isPrivateAddress ( ip ) ) ; // 'lax' - If both IP addresses are private or loopback, accept it. This is needed because sometimes browsers will resolve IP addresses oddly on private networks.
2022-04-10 03:12:52 +03:00
}
2022-07-20 10:50:32 +03:00
// Takes a formating string like "this {{{a}}} is an {{{b}}} example" and fills the a and b with input o.a and o.b
function assembleStringFromObject ( format , o ) {
var r = '' , i = format . indexOf ( '{{{' ) ;
if ( i > 0 ) { r = format . substring ( 0 , i ) ; format = format . substring ( i ) ; }
const cmd = format . split ( '{{{' ) ;
for ( var j in cmd ) { if ( j == 0 ) continue ; i = cmd [ j ] . indexOf ( '}}}' ) ; r += o [ cmd [ j ] . substring ( 0 , i ) ] + cmd [ j ] . substring ( i + 3 ) ; }
return r ;
}
2022-08-22 07:19:34 +03:00
// Sync an account with an external user group.
// Return true if the user was changed
function syncExternalUserGroups ( domain , user , userMemberships , userMembershipType ) {
var userChanged = false ;
if ( user . links == null ) { user . links = { } ; }
2022-09-06 10:30:07 +03:00
2022-08-22 07:19:34 +03:00
// Create a user of memberships for this user that type
var existingUserMemberships = { } ;
for ( var i in user . links ) {
if ( i . startsWith ( 'ugrp/' ) && ( obj . userGroups [ i ] != null ) && ( obj . userGroups [ i ] . membershipType == userMembershipType ) ) { existingUserMemberships [ i ] = obj . userGroups [ i ] ; }
}
// Go thru the list user memberships and create and add to any user groups as needed
for ( var i in userMemberships ) {
const membership = userMemberships [ i ] ;
var ugrpid = 'ugrp/' + domain . id + '/' + obj . crypto . createHash ( 'sha384' ) . update ( membership ) . digest ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ;
var ugrp = obj . userGroups [ ugrpid ] ;
if ( ugrp == null ) {
// This user group does not exist, create it
ugrp = { type : 'ugrp' , _id : ugrpid , name : membership , domain : domain . id , membershipType : userMembershipType , links : { } } ;
// Save the new group
db . Set ( ugrp ) ;
if ( db . changeStream == false ) { obj . userGroups [ ugrpid ] = ugrp ; }
// Event the user group creation
var event = { etype : 'ugrp' , ugrpid : ugrpid , name : ugrp . name , action : 'createusergroup' , links : ugrp . links , msgid : 69 , msgArgv : [ ugrp . name ] , msg : 'User group created: ' + ugrp . name , ugrpdomain : domain . id } ;
parent . DispatchEvent ( [ '*' , ugrpid , user . _id ] , obj , event ) ; // Even if DB change stream is active, this event must be acted upon.
// Log in the auth log
2024-03-04 03:03:27 +03:00
parent . authLog ( 'https' , userMembershipType . toUpperCase ( ) + ': Created user group ' + ugrp . name ) ;
2022-08-22 07:19:34 +03:00
}
if ( existingUserMemberships [ ugrpid ] == null ) {
// This user is not part of the user group, add it.
if ( user . links == null ) { user . links = { } ; }
user . links [ ugrp . _id ] = { rights : 1 } ;
userChanged = true ;
db . SetUser ( user ) ;
parent . DispatchEvent ( [ user . _id ] , obj , 'resubscribe' ) ;
// Notify user change
var targets = [ '*' , 'server-users' , user . _id ] ;
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msgid : 67 , msgArgs : [ user . name ] , msg : 'User group membership changed: ' + user . name , domain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . DispatchEvent ( targets , obj , event ) ;
// Add a user to the user group
ugrp . links [ user . _id ] = { userid : user . _id , name : user . name , rights : 1 } ;
db . Set ( ugrp ) ;
// Notify user group change
var event = { etype : 'ugrp' , userid : user . _id , username : user . name , ugrpid : ugrp . _id , name : ugrp . name , desc : ugrp . desc , action : 'usergroupchange' , links : ugrp . links , msgid : 71 , msgArgs : [ user . name , ugrp . name ] , msg : 'Added user(s) ' + user . name + ' to user group ' + ugrp . name , addUserDomain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
parent . DispatchEvent ( [ '*' , ugrp . _id , user . _id ] , obj , event ) ;
2022-09-06 10:30:07 +03:00
// Log in the auth log
2024-03-04 03:03:27 +03:00
parent . authLog ( 'https' , userMembershipType . toUpperCase ( ) + ': Adding ' + user . name + ' to user group ' + userMemberships [ i ] + '.' ) ;
2022-08-22 07:19:34 +03:00
} else {
// User is already part of this user group
delete existingUserMemberships [ ugrpid ] ;
}
}
// Remove the user from any memberships they don't belong to anymore
for ( var ugrpid in existingUserMemberships ) {
var ugrp = obj . userGroups [ ugrpid ] ;
2024-03-04 03:03:27 +03:00
parent . authLog ( 'https' , userMembershipType . toUpperCase ( ) + ': Removing ' + user . name + ' from user group ' + ugrp . name + '.' ) ;
2022-08-22 07:19:34 +03:00
if ( ( user . links != null ) && ( user . links [ ugrpid ] != null ) ) {
delete user . links [ ugrpid ] ;
// Notify user change
var targets = [ '*' , 'server-users' , user . _id , user . _id ] ;
var event = { etype : 'user' , userid : user . _id , username : user . name , account : obj . CloneSafeUser ( user ) , action : 'accountchange' , msgid : 67 , msgArgs : [ user . name ] , msg : 'User group membership changed: ' + user . name , domain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . DispatchEvent ( targets , obj , event ) ;
db . SetUser ( user ) ;
parent . DispatchEvent ( [ user . _id ] , obj , 'resubscribe' ) ;
}
if ( ugrp != null ) {
// Remove the user from the group
if ( ( ugrp . links != null ) && ( ugrp . links [ user . _id ] != null ) ) {
delete ugrp . links [ user . _id ] ;
db . Set ( ugrp ) ;
// Notify user group change
var event = { etype : 'ugrp' , userid : user . _id , username : user . name , ugrpid : ugrp . _id , name : ugrp . name , desc : ugrp . desc , action : 'usergroupchange' , links : ugrp . links , msgid : 72 , msgArgs : [ user . name , ugrp . name ] , msg : 'Removed user ' + user . name + ' from user group ' + ugrp . name , domain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
parent . DispatchEvent ( [ '*' , ugrp . _id , user . _id ] , obj , event ) ;
}
}
}
return userChanged ;
}
2017-08-28 19:27:45 +03:00
return obj ;
2022-03-15 03:33:43 +03:00
} ;