457
458
// Save this LDAP user to file if needed
459
if (typeof domain.ldapsaveusertofile == 'string') {
460
- obj.fs.appendFile(domain.ldapsaveusertofile, JSON.stringify(xxuser, null, 2) + '\r\n\r\n', function (err) { });
460
+ obj.fs.appendFile(domain.ldapsaveusertofile, JSON.stringify(xxuser) + '\r\n\r\n', function (err) { });
461
}
462
463
// Work on getting the userid for this LDAP user
489
if (Array.isArray(userMemberships) == false) { userMemberships = []; }
490
491
// See if the user is required to be part of an LDAP user group in order to log into this server.
492
- if (typeof domain.ldapuserrequiredgroupmembership == 'string') { domain.ldapuserrequiredgroupmembership = [ domain.ldapuserrequiredgroupmembership ]; }
492
+ if (typeof domain.ldapuserrequiredgroupmembership == 'string') { domain.ldapuserrequiredgroupmembership = [domain.ldapuserrequiredgroupmembership]; }
493
if (Array.isArray(domain.ldapuserrequiredgroupmembership)) {
494
// Look for a matching LDAP user group
495
var userMembershipMatch = false;
496
for (var i in domain.ldapuserrequiredgroupmembership) { if (userMemberships.indexOf(domain.ldapuserrequiredgroupmembership[i]) >= 0) { userMembershipMatch = true; } }
497
- if (userMembershipMatch === false) { parent.debug('authlog', '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
497
+ 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
498
}
499
500
// Check if user is in an site administrator group
501
var siteAdminGroup = null;
502
- if (typeof domain.ldapsiteadmingroups == 'string') { domain.ldapsiteadmingroups = [ domain.ldapsiteadmingroups ]; }
502
+ if (typeof domain.ldapsiteadmingroups == 'string') { domain.ldapsiteadmingroups = [domain.ldapsiteadmingroups]; }
503
if (Array.isArray(domain.ldapsiteadmingroups)) {
504
siteAdminGroup = false;
505
for (var i in domain.ldapsiteadmingroups) {
559
}
560
561
// Display user information extracted from LDAP data
562
- parent.debug('authlog', 'LDAP user login, id: ' + shortname + ', username: ' + username + ', email: ' + email + ', realname: ' + realname + ', phone: ' + phonenumber + ', image: ' + (userimage != null));
562
+ parent.authLog('ldapHandler', 'LDAP user login, id: ' + shortname + ', username: ' + username + ', email: ' + email + ', realname: ' + realname + ', phone: ' + phonenumber + ', image: ' + (userimage != null));
563
564
// If there is a testing userid, use that
565
if (ldapHandlerFunc.ldapShortName) {
619
620
// See if the user is a member of the site admin group.
621
if (typeof siteAdminGroup === 'string') {
622
- parent.debug('authlog', `LDAP: Granting site admin privilages to new user "${user.name}" found in admin group: ${siteAdminGroup}`);
622
+ parent.authLog('ldapHandler', `LDAP: Granting site admin privilages to new user "${user.name}" found in admin group: ${siteAdminGroup}`);
623
user.siteadmin = 0xFFFFFFFF;
624
}
625
662
663
// See if the user is a member of the site admin group.
664
if ((typeof siteAdminGroup === 'string') && (user.siteadmin !== 0xFFFFFFFF)) {
665
- parent.debug('authlog', `LDAP: Granting site admin privilages to user "${user.name}" found in administrator group: ${siteAdminGroup}`);
665
+ parent.authLog('ldapHandler', `LDAP: Granting site admin privilages to user "${user.name}" found in administrator group: ${siteAdminGroup}`);
666
user.siteadmin = 0xFFFFFFFF;
667
userChanged = true;
668
} else if ((siteAdminGroup === false) && (user.siteadmin === 0xFFFFFFFF)) {
669
- parent.debug('authlog', `LDAP: Revoking site admin privilages from user "${user.name}" since they are not found in any administrator groups.`);
669
+ parent.authLog('ldapHandler', `LDAP: Revoking site admin privilages from user "${user.name}" since they are not found in any administrator groups.`);
670
delete user.siteadmin;
671
userChanged = true;
672
}
836
parent.debug('web', 'handleLogoutRequest: success.');
837
838
// If this user was logged in using an authentication strategy and there is a logout URL, use it.
839
- if ((userid != null) && (domain.authstrategies != null)) {
840
- const u = userid.split('/')[2];
841
- if (u.startsWith('~twitter:') && (domain.authstrategies.twitter != null) && (typeof domain.authstrategies.twitter.logouturl == 'string')) { res.redirect(domain.authstrategies.twitter.logouturl); return; }
842
- if (u.startsWith('~google:') && (domain.authstrategies.google != null) && (typeof domain.authstrategies.google.logouturl == 'string')) { res.redirect(domain.authstrategies.google.logouturl); return; }
843
- if (u.startsWith('~github:') && (domain.authstrategies.github != null) && (typeof domain.authstrategies.github.logouturl == 'string')) { res.redirect(domain.authstrategies.github.logouturl); return; }
844
- if (u.startsWith('~reddit:') && (domain.authstrategies.reddit != null) && (typeof domain.authstrategies.reddit.logouturl == 'string')) { res.redirect(domain.authstrategies.reddit.logouturl); return; }
845
- if (u.startsWith('~azure:') && (domain.authstrategies.azure != null) && (typeof domain.authstrategies.azure.logouturl == 'string')) { res.redirect(domain.authstrategies.azure.logouturl); return; }
846
- if (u.startsWith('~oidc:') && (domain.authstrategies.oidc != null) && (typeof domain.authstrategies.oidc.logouturl == 'string')) { res.redirect(domain.authstrategies.oidc.logouturl); return; }
847
- if (u.startsWith('~jumpcloud:') && (domain.authstrategies.jumpcloud != null) && (typeof domain.authstrategies.jumpcloud.logouturl == 'string')) { res.redirect(domain.authstrategies.jumpcloud.logouturl); return; }
848
- if (u.startsWith('~saml:') && (domain.authstrategies.saml != null) && (typeof domain.authstrategies.saml.logouturl == 'string')) { res.redirect(domain.authstrategies.saml.logouturl); return; }
849
- if (u.startsWith('~intel:') && (domain.authstrategies.intel != null) && (typeof domain.authstrategies.intel.logouturl == 'string')) { res.redirect(domain.authstrategies.intel.logouturl); return; }
839
+ if ((userid != null) && (domain.authstrategies?.authStrategyFlags != null)) {
840
+ let logouturl = null;
841
+ let userStrategy = ((userid.split('/')[2]).split(':')[0]).substring(1);
842
+ // Setup logout url for oidc
843
+ if (userStrategy == 'oidc' && domain.authstrategies.oidc != null) {
844
+ if (typeof domain.authstrategies.oidc.logouturl == 'string') {
845
+ logouturl = domain.authstrategies.oidc.logouturl;
846
+ } else if (typeof domain.authstrategies.oidc.issuer.end_session_endpoint == 'string' && typeof domain.authstrategies.oidc.client.post_logout_redirect_uri == 'string') {
847
+ logouturl = domain.authstrategies.oidc.issuer.end_session_endpoint + '?post_logout_redirect_uri=' + domain.authstrategies.oidc.client.post_logout_redirect_uri;
848
+ } else if (typeof domain.authstrategies.oidc.issuer.end_session_endpoint == 'string') {
849
+ logouturl = domain.authstrategies.oidc.issuer.end_session_endpoint;
850
+ }
851
+ // Log out all other strategies
852
+ } else if ((domain.authstrategies[userStrategy] != null) && (typeof domain.authstrategies[userStrategy].logouturl == 'string')) { logouturl = domain.authstrategies[userStrategy].logouturl; }
853
+ // If custom logout was setup, use it
854
+ if (logouturl != null) {
855
+ parent.authLog('handleLogoutRequest', userStrategy.toUpperCase() + ': LOGOUT: ' + logouturl);
856
+ res.redirect(logouturl);
857
+ return;
858
+ }
859
}
860
861
// This is the default logout redirect to the login page
2008
// Send a notification
2009
obj.parent.DispatchEvent([user._id], obj, { action: 'notify', title: 'Email verified', value: user.email, nolog: 1, id: Math.random() });
2010
2002
- // Send to authlog
2011
+ // Send to authLog
2012
obj.parent.authLog('https', 'Verified email address ' + user.email + ' for user ' + user.name, { useragent: req.headers['user-agent'] });
2013
}
2014
});
2044
render(req, res, getRenderPage((domain.sitestyle == 2) ? '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));
2045
parent.debug('web', 'handleCheckMailRequest: send temporary password.');
2046
2038
- // Send to authlog
2047
+ // Send to authLog
2048
obj.parent.authLog('https', 'Performed account reset for user ' + user.name);
2049
}, 0);
2050
});
2584
const domain = checkUserIpAddress(req, res);
2585
if (domain == null) { return; }
2586
if ((req.user != null) && (req.user.sid != null) && (req.user.strategy != null)) {
2578
- const authStrategy = req.user.strategy
2579
- parent.debug('authlog', `${authStrategy.toUpperCase()}: Verified user: ${JSON.stringify(req.user, null, 4)}` + JSON.stringify(req.user));
2580
-
2581
- // Check if any group related options exist
2582
- var userMemberships = [];
2583
- var siteAdminGroup = null;
2584
- if (typeof domain.authstrategies[authStrategy].groups === 'object') {
2585
- if (Array.isArray(req.user.groups)) { userMemberships = req.user.groups; }
2586
- else if (typeof req.user.groups == 'string') { userMemberships = [req.user.groups]; }
2587
- parent.debug('authlog', `${authStrategy.toUpperCase()}: Member Of: ${userMemberships.join(', ')}`);
2588
-
2589
- // See if the user is required to be part of a specific group in order to log into this server.
2590
- if (typeof domain.authstrategies[authStrategy].groups.required == 'string') { domain.authstrategies[authStrategy].groups.required = [domain.authstrategies[authStrategy].groups.required]; }
2591
- if (Array.isArray(domain.authstrategies[authStrategy].groups.required)) {
2592
- var userMembershipMatch = false;
2593
- for (var i in domain.authstrategies[authStrategy].groups.required) {
2594
- if (userMemberships.indexOf(domain.authstrategies[authStrategy].groups.required[i]) >= 0) {
2595
- userMembershipMatch = true;
2596
- parent.debug('authlog', `${authStrategy.toUpperCase()}: ${req.user.name} is member of required group: ${domain.authstrategies[authStrategy].groups.required[i]}`);
2587
+ const strategy = domain.authstrategies[req.user.strategy];
2588
+ const groups = { 'enabled': typeof strategy.groups == 'object' }
2589
+ parent.authLog(req.user.strategy.toUpperCase(), `User Authorized: ${JSON.stringify(req.user)}`);
2590
+ if (groups.enabled) { // Groups only available for OIDC strategy currently
2591
+ groups.userMemberships = obj.common.convertStrArray(req.user.groups)
2592
+ groups.syncEnabled = (strategy.groups.sync === true || strategy.groups.sync?.filter) ? true : false
2593
+ groups.syncMemberships = []
2594
+ groups.siteAdminEnabled = strategy.groups.siteadmin ? true : false
2595
+ groups.grantAdmin = false
2596
+ groups.revokeAdmin = strategy.groups.revokeAdmin ? strategy.groups.revokeAdmin : true
2597
+ groups.requiredGroups = obj.common.convertStrArray(strategy.groups.required)
2598
+ groups.siteAdmin = obj.common.convertStrArray(strategy.groups.siteadmin)
2599
+ groups.syncFilter = obj.common.convertStrArray(strategy.groups.sync?.filter)
2600
+
2601
+ // Fancy Logs
2602
+ let groupMessage = ''
2603
+ if (groups.userMemberships.length == 1) { groupMessage = ` Found membership: "${groups.userMemberships[0]}"` }
2604
+ else { groupMessage = ` Found ${groups.userMemberships.length} memberships: ["${groups.userMemberships.join('", "')}"]` }
2605
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}"` + groupMessage);
2606
+
2607
+ // Check user membership in required groups
2608
+ if (groups.requiredGroups != null) {
2609
+ let match = false
2610
+ for (var i in groups.requiredGroups) {
2611
+ if (groups.userMemberships.indexOf(groups.requiredGroups[i]) != -1) {
2612
+ match = true;
2613
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}" Membership to required group found: "${groups.requiredGroups[i]}"`);
2614
}
2615
}
2599
- if (userMembershipMatch === false) {
2600
- parent.debug('authlog', `${authStrategy}: User login denied. User not found in required group.`);
2616
+ if (match === false) {
2617
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}" Login denied. No memberhip to required group.`);
2618
req.session.loginmode = 1;
2602
- req.session.messageid = 100; // Unable to create account.
2619
+ req.session.messageid = 111; // Access Denied.
2620
res.redirect(domain.url + getQueryPortion(req));
2621
return;
2622
}
2623
}
2624
2608
- // Check if user is in an administrator group
2609
- if (typeof domain.authstrategies[authStrategy].groups.siteadmin == 'string') { domain.authstrategies[authStrategy].groups.siteadmin = [ domain.authstrategies[authStrategy].groups.siteadmin ]; }
2610
- if (Array.isArray(domain.authstrategies[authStrategy].groups.siteadmin)) {
2611
- siteAdminGroup = false;
2612
- for (var i in domain.authstrategies[authStrategy].groups.siteadmin) {
2613
- if (userMemberships.indexOf(domain.authstrategies[authStrategy].groups.siteadmin[i]) >= 0) { siteAdminGroup = domain.authstrategies[authStrategy].groups.siteadmin[i]; }
2625
+ // Check user membership in admin groups
2626
+ if (groups.siteAdminEnabled === true) {
2627
+ groups.grantAdmin = false;
2628
+ for (var i in strategy.groups.siteadmin) {
2629
+ if (groups.userMemberships.indexOf(strategy.groups.siteadmin[i]) >= 0) {
2630
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}" User membership found in site admin group: "${strategy.groups.siteadmin[i]}"`);
2631
+ groups.siteAdmin = strategy.groups.siteadmin[i];
2632
+ groups.grantAdmin = true;
2633
+ break;
2634
+ }
2635
}
2636
}
2637
2617
- // See if we need to sync user-memberships (IdP) with user-groups (meshcentral)
2618
- if (domain.authstrategies[authStrategy].groups.sync === true) { domain.authstrategies[authStrategy].groups.sync = { enabled: true }; }
2619
- if (typeof domain.authstrategies[authStrategy].groups.sync.filter == 'string' || Array.isArray(domain.authstrategies[authStrategy].groups.sync.filter)) {
2620
- if (typeof domain.authstrategies[authStrategy].groups.sync.filter == 'string') { domain.authstrategies[authStrategy].groups.sync.filter = [ domain.authstrategies[authStrategy].groups.sync.filter ]; }
2621
- const filteredMemberships = [];
2622
- for (var i in userMemberships) {
2623
- for (var j in domain.authstrategies[authStrategy].groups.sync.filter) {
2624
- if (userMemberships[i].indexOf(domain.authstrategies[authStrategy].groups.sync.filter[j]) >= 0) { filteredMemberships.push(userMemberships[i]); }
2625
- }
2638
+ // Check if we need to sync user-memberships (IdP) with user-groups (meshcentral)
2639
+ if (groups.syncEnabled === true) {
2640
+ for (var i in groups.syncFilter) {
2641
+ if (groups.userMemberships.indexOf(groups.syncFilter[i]) >= 0) { groups.syncMemberships.push(groups.syncFilter[i]); }
2642
}
2627
- if (filteredMemberships.length > 0) {
2628
- parent.debug('authlog', `${authStrategy.toUpperCase()}: Filtered user memberships from config: ${filteredMemberships.join(', ')}`);
2629
- } else {
2630
- parent.debug('authlog', `${authStrategy.toUpperCase()}: No groups found with filter: ${domain.authstrategies[authStrategy].groups.sync.filter.join(', ')}`);
2643
+ if (groups.syncMemberships.length > 0) {
2644
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}" Filtered user memberships from config to sync: ${groups.syncMemberships.join(', ')}`);
2645
+ } else {
2646
+ groups.syncMemberships = null;
2647
+ groups.syncEnabled = false
2648
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}" No sync memberships found after filter: ${strategy.groups.sync.filter.join(', ')}`);
2649
}
2632
- userMemberships = filteredMemberships;
2650
}
2651
}
2652
2660
if (domain.newaccounts === true) { newAccountAllowed = true; }
2661
if (obj.common.validateStrArray(domain.newaccountrealms)) { newAccountRealms = domain.newaccountrealms; }
2662
2646
- if ((domain.authstrategies != null) && (domain.authstrategies[authStrategy] != null)) {
2647
- if (domain.authstrategies[authStrategy].newaccounts === true) { newAccountAllowed = true; }
2648
- if (obj.common.validateStrArray(domain.authstrategies[authStrategy].newaccountrealms)) { newAccountRealms = domain.authstrategies[req.user.strategy].newaccountrealms; }
2663
+ if (domain.authstrategies[req.user.strategy]) {
2664
+ if (domain.authstrategies[req.user.strategy].newaccounts === true) { newAccountAllowed = true; }
2665
+ if (obj.common.validateStrArray(domain.authstrategies[req.user.strategy].newaccountrealms)) { newAccountRealms = domain.authstrategies[req.user.strategy].newaccountrealms; }
2666
}
2667
2668
if (newAccountAllowed === true) {
2669
// Create the user
2653
- parent.debug('authlog', `${authStrategy.toUpperCase()}: Creating new login user: "${userid}"`);
2670
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: USER: "${req.user.sid}" Creating new login user: "${userid}"`);
2671
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 };
2655
- if (req.user.email != null) { user.email = req.user.email; user.emailVerified = true; }
2672
+ if (req.user.email != null) { user.email = req.user.email; user.emailVerified = req.user.email_verified ? req.user.email_verified : true; }
2673
if (domain.newaccountsrights) { user.siteadmin = domain.newaccountsrights; } // New accounts automatically assigned server rights.
2657
- if (domain.authstrategies[authStrategy].newaccountsrights) { user.siteadmin = obj.common.meshServerRightsArrayToNumber(domain.authstrategies[req.user.strategy].newaccountsrights); } // If there are specific SSO server rights, use these instead.
2674
+ 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.
2675
if (newAccountRealms) { user.groups = newAccountRealms; } // New accounts automatically part of some groups (Realms).
2676
obj.users[userid] = user;
2677
2678
// Auto-join any user groups
2679
var newaccountsusergroups = null;
2680
if (typeof domain.newaccountsusergroups == 'object') { newaccountsusergroups = domain.newaccountsusergroups; }
2664
- if (typeof domain.authstrategies[authStrategy].newaccountsusergroups == 'object') { newaccountsusergroups = domain.authstrategies[req.user.strategy].newaccountsusergroups; }
2681
+ if (typeof domain.authstrategies[req.user.strategy].newaccountsusergroups == 'object') { newaccountsusergroups = domain.authstrategies[req.user.strategy].newaccountsusergroups; }
2682
if (newaccountsusergroups) {
2683
for (var i in newaccountsusergroups) {
2684
var ugrpid = newaccountsusergroups[i];
2701
}
2702
}
2703
2687
- if (typeof domain.authstrategies[authStrategy].groups == 'object') {
2704
+ if (groups.enabled === true) {
2705
// Sync the user groups if enabled
2689
- if ((typeof domain.authstrategies[authStrategy].groups.sync == 'object') && (domain.authstrategies[authStrategy].groups.sync.enabled === true)) { syncExternalUserGroups(domain, user, userMemberships, authStrategy) }
2690
-
2706
+ if (groups.syncEnabled === true) {
2707
+ // Set groupType to the preset name if it exists, otherwise use the strategy name
2708
+ const groupType = domain.authstrategies[req.user.strategy].custom?.preset ? domain.authstrategies[req.user.strategy].custom.preset : req.user.strategy;
2709
+ syncExternalUserGroups(domain, user, groups.syncMemberships, groupType);
2710
+ }
2711
// See if the user is a member of the site admin group.
2692
- if (typeof siteAdminGroup === 'string') {
2693
- parent.debug('authlog', `${authStrategy.toUpperCase()}: Granting site admin privilages to new user "${user.name}" found in admin group: ${siteAdminGroup}`);
2712
+ if (groups.grantAdmin === true) {
2713
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}" Granting site admin privilages`);
2714
user.siteadmin = 0xFFFFFFFF;
2715
}
2716
}
2735
obj.parent.DispatchEvent(targets, obj, loginEvent);
2736
} else {
2737
// New users not allowed
2718
- parent.debug('authlog', `${authStrategy.toUpperCase()}: Can\'t create new user, account creation is not allowed`);
2738
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: LOGIN FAILED: USER: "${req.user.sid}" New accounts are not allowed`);
2739
req.session.loginmode = 1;
2740
req.session.messageid = 100; // Unable to create account.
2741
res.redirect(domain.url + getQueryPortion(req));
2746
var userChanged = false;
2747
if ((req.user.name != null) && (req.user.name != user.name)) { user.name = req.user.name; userChanged = true; }
2748
if ((req.user.email != null) && (req.user.email != user.email)) { user.email = req.user.email; user.emailVerified = true; userChanged = true; }
2729
-
2730
- if (typeof domain.authstrategies[authStrategy].groups == 'object') {
2731
- // Sync the user groups if enabled
2732
- if ((typeof domain.authstrategies[authStrategy].groups.sync == 'object') && (domain.authstrategies[authStrategy].groups.sync.enabled === true)) { syncExternalUserGroups(domain, user, userMemberships, authStrategy) }
2749
2750
+ if (groups.enabled === true) {
2751
+ // Sync the user groups if enabled
2752
+ if (groups.syncEnabled === true) {
2753
+ syncExternalUserGroups(domain, user, groups.syncMemberships, req.user.strategy)
2754
+ }
2755
// See if the user is a member of the site admin group.
2735
- if ((typeof domain.authstrategies[authStrategy].groups.siteadmin !== 'undefined') && (domain.authstrategies[authStrategy].groups.siteadmin !== null)) {
2736
- if ((typeof siteAdminGroup === 'string') && (user.siteadmin !== 0xFFFFFFFF)) {
2737
- parent.debug('authlog', `${authStrategy.toUpperCase()}: Granting site admin privilages to user "${user.name}" found in administrator group: ${siteAdminGroup}`);
2738
- user.siteadmin = 0xFFFFFFFF;
2739
- userChanged = true;
2740
- } else if ((siteAdminGroup === false) && (user.siteadmin === 0xFFFFFFFF)) {
2741
- parent.debug('authlog', `${authStrategy.toUpperCase()}: Revoking site admin privilages from user "${user.name}" since they are not found in any administrator groups.`);
2756
+ if (groups.siteAdminEnabled === true) {
2757
+ if (groups.grantAdmin === true) {
2758
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}" Granting site admin privilages`);
2759
+ if (user.siteadmin !== 0xFFFFFFFF) { user.siteadmin = 0xFFFFFFFF; userChanged = true; }
2760
+ } else if ((groups.revokeAdmin === true) && (user.siteadmin === 0xFFFFFFFF)) {
2761
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: GROUPS: USER: "${req.user.sid}" Revoking site admin privilages.`);
2762
delete user.siteadmin;
2763
userChanged = true;
2764
}
2767
2768
// Update db record for user if there are changes detected
2769
if (userChanged) {
2770
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: CHANGED: USER: "${req.user.sid}" Updating user database entry`);
2771
obj.db.SetUser(user);
2772
2773
// Event user change
2785
const ua = obj.getUserAgentInfo(req);
2786
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' };
2787
obj.parent.DispatchEvent(targets, obj, loginEvent);
2767
- parent.debug('authlog', `${authStrategy.toUpperCase()}: User Logged In: Name: ${user.name} ID: ${user._id}`);
2788
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: LOGIN SUCCESS: USER: "${req.user.sid}"`);
2789
}
2769
- } else { parent.debug('warn', 'handleStrategyLogin: FAILED - No user'); }
2790
+ } else {
2791
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: LOGIN FAILED: USER: "${req.user.sid}" REQUEST CONTAINS NO USER OR SID`);
2792
+ }
2793
+
2794
+ parent.authLog('handleStrategyLogin', `${req.user.strategy.toUpperCase()}: User Authenticated: ${JSON.stringify(user)}`);
2795
//res.redirect(domain.url); // This does not handle cookie correctly.
2796
res.set('Content-Type', 'text/html');
2797
res.end('<html><head><meta http-equiv="refresh" content=0;url="' + domain.url + '"></head><body></body></html>');
3310
if (typeof domain.authstrategies.github == 'object') { authStrategies.push('github'); }
3311
if (typeof domain.authstrategies.reddit == 'object') { authStrategies.push('reddit'); }
3312
if (typeof domain.authstrategies.azure == 'object') { authStrategies.push('azure'); }
3288
- if (typeof domain.authstrategies.oidc == 'object') { authStrategies.push('oidc'); }
3313
+ if (typeof domain.authstrategies.oidc == 'object') {
3314
+ if (obj.common.validateObject(domain.authstrategies.oidc.custom) && obj.common.validateString(domain.authstrategies.oidc.custom.preset)) {
3315
+ authStrategies.push('oidc-' + domain.authstrategies.oidc.custom.preset);
3316
+ } else {
3317
+ authStrategies.push('oidc');
3318
+ }
3319
+ }
3320
if (typeof domain.authstrategies.intel == 'object') { authStrategies.push('intel'); }
3321
if (typeof domain.authstrategies.jumpcloud == 'object') { authStrategies.push('jumpcloud'); }
3322
if (typeof domain.authstrategies.saml == 'object') { authStrategies.push('saml'); }
5382
var xdomain = (domain.dns == null) ? domain.id : '';
5383
if (xdomain != '') xdomain += '/';
5384
var meshsettings = '';
5354
- if (req.query.ac != '4'){ // If MeshCentral Assistant Monitor Mode, DONT INCLUDE SERVER DETAILS!
5385
+ if (req.query.ac != '4') { // If MeshCentral Assistant Monitor Mode, DONT INCLUDE SERVER DETAILS!
5386
meshsettings += '\r\nMeshName=' + mesh.name + '\r\nMeshType=' + mesh.mtype + '\r\nMeshID=0x' + meshidhex + '\r\nServerID=' + serveridhex + '\r\n';
5387
if (obj.args.lanonly != true) { meshsettings += 'MeshServer=wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'agent.ashx\r\n'; } else {
5388
meshsettings += 'MeshServer=local\r\n';
5461
5462
// Send meshcmd for a specific platform back
5463
var agentid = parseInt(req.query.meshcmd);
5433
-
5464
+
5465
// If the agentid is 3 or 4, check if we have a signed MeshCmd.exe
5466
if ((agentid == 3) && (obj.parent.meshAgentBinaries[11000] != null)) { // Signed Windows MeshCmd.exe x86-32
5467
var stats = null, meshCmdPath = obj.parent.meshAgentBinaries[11000].path;
6169
}
6170
if (obj.args.sessiontime != null) { sessionOptions.maxAge = (obj.args.sessiontime * 60000); } // sessiontime is minutes
6171
obj.app.use(require('cookie-session')(sessionOptions));
6141
- obj.app.use(function(request, response, next) { // Patch for passport 0.6.0 - https://github.com/jaredhanson/passport/issues/904
6172
+ obj.app.use(function (request, response, next) { // Patch for passport 0.6.0 - https://github.com/jaredhanson/passport/issues/904
6173
if (request.session && !request.session.regenerate) {
6174
request.session.regenerate = function (cb) {
6175
cb()
6355
var errlogpath = null;
6356
if (typeof parent.args.mesherrorlogpath == 'string') { errlogpath = parent.path.join(parent.args.mesherrorlogpath, 'mesherrors.txt'); } else { errlogpath = parent.getConfigFilePath('mesherrors.txt'); }
6357
parent.fs.appendFileSync(errlogpath, new Date().toLocaleString() + ': ' + `Error in res.send | ${err.code} | ${err.message} | ${res.stack}` + '\r\n');
6327
- } catch (ex) { console.log('ERROR: Unable to write to mesherrors.txt.'); }
6358
+ } catch (ex) { parent.debug('error', 'Unable to write to mesherrors.txt.'); }
6359
}
6360
};
6361
6396
});
6397
}
6398
6368
- // Setup all sharing domains
6399
+ // Setup all sharing domains and check if auth strategies need setup
6400
+ var setupSSO = false
6401
for (var i in parent.config.domains) {
6402
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)); }
6403
+ if (typeof parent.config.domains[i].authstrategies == 'object') { setupSSO = true };
6404
+ }
6405
+
6406
+ if (setupSSO) {
6407
+ setupAllDomainAuthStrategies().then(() => finalizeWebserver());
6408
+ } else {
6409
+ finalizeWebserver()
6410
}
6411
6412
// Setup all domain auth strategy passport.js
6374
- for (var i in parent.config.domains) {
6375
- if (typeof parent.config.domains[i].authstrategies == 'object') {
6376
- parent.config.domains[i].authstrategies.authStrategyFlags = 0;
6377
- const authStrategyFlags = setupDomainAuthStrategy(parent.config.domains[i]);
6378
- if (authStrategyFlags > 0) {
6379
- if (parent.config.domains[i].dns != null) {
6380
- if (typeof parent.config.domains[''].authstrategies != 'object') { parent.config.domains[''].authstrategies = { authStrategyFlags: 0 }; }
6381
- parent.config.domains[''].authstrategies.authStrategyFlags |= authStrategyFlags;
6382
- } else {
6383
- if (typeof parent.config.domains[i].authstrategies != 'object') { parent.config.domains[i].authstrategies = { authStrategyFlags: 0 }; }
6384
- parent.config.domains[i].authstrategies.authStrategyFlags |= authStrategyFlags;
6385
- }
6413
+ async function setupAllDomainAuthStrategies() {
6414
+ for (var i in parent.config.domains) {
6415
+ if (parent.config.domains[i].dns != null) {
6416
+ if (typeof parent.config.domains[''].authstrategies != 'object') { parent.config.domains[''].authstrategies = { 'authStrategyFlags': 0 }; }
6417
+ parent.config.domains[''].authstrategies.authStrategyFlags |= await setupDomainAuthStrategy(parent.config.domains['']);
6418
+ } else {
6419
+ if (typeof parent.config.domains[i].authstrategies != 'object') { parent.config.domains[i].authstrategies = { 'authStrategyFlags': 0 }; }
6420
+ parent.config.domains[i].authstrategies.authStrategyFlags |= await setupDomainAuthStrategy(parent.config.domains[i]);
6421
}
6422
}
6423
}
6389
-
6390
- // Setup all HTTP handlers
6391
- if (parent.pluginHandler != null) {
6392
- parent.pluginHandler.callHook('hook_setupHttpHandlers', obj, parent);
6393
- }
6394
- if (parent.multiServer != null) { obj.app.ws('/meshserver.ashx', function (ws, req) { parent.multiServer.CreatePeerInServer(parent.multiServer, ws, req, obj.args.tlsoffload == null); }); }
6395
- for (var i in parent.config.domains) {
6396
- 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.
6397
- var domain = parent.config.domains[i];
6398
- var url = domain.url;
6399
- if (typeof domain.rootredirect == 'string') {
6400
- // Root page redirects the user to a different URL
6401
- obj.app.get(url, handleRootRedirect);
6402
- } else {
6403
- // Present the login page as the root page
6404
- obj.app.get(url, handleRootRequest);
6405
- obj.app.post(url, obj.bodyParser.urlencoded({ extended: false }), handleRootPostRequest);
6406
- }
6407
- obj.app.get(url + 'refresh.ashx', function (req, res) { res.sendStatus(200); });
6408
- if ((domain.myserver !== false) && ((domain.myserver == null) || (domain.myserver.backup === true))) { obj.app.get(url + 'backup.zip', handleBackupRequest); }
6409
- if ((domain.myserver !== false) && ((domain.myserver == null) || (domain.myserver.restore === true))) { obj.app.post(url + 'restoreserver.ashx', obj.bodyParser.urlencoded({ extended: false }), handleRestoreRequest); }
6410
- obj.app.get(url + 'terms', handleTermsRequest);
6411
- obj.app.get(url + 'xterm', handleXTermRequest);
6412
- obj.app.get(url + 'login', handleRootRequest);
6413
- obj.app.post(url + 'login', obj.bodyParser.urlencoded({ extended: false }), handleRootPostRequest);
6414
- obj.app.post(url + 'tokenlogin', obj.bodyParser.urlencoded({ extended: false }), handleLoginRequest);
6415
- obj.app.get(url + 'logout', handleLogoutRequest);
6416
- obj.app.get(url + 'MeshServerRootCert.cer', handleRootCertRequest);
6417
- obj.app.post(url + 'changepassword', obj.bodyParser.urlencoded({ extended: false }), handlePasswordChangeRequest);
6418
- obj.app.post(url + 'deleteaccount', obj.bodyParser.urlencoded({ extended: false }), handleDeleteAccountRequest);
6419
- obj.app.post(url + 'createaccount', obj.bodyParser.urlencoded({ extended: false }), handleCreateAccountRequest);
6420
- obj.app.post(url + 'resetpassword', obj.bodyParser.urlencoded({ extended: false }), handleResetPasswordRequest);
6421
- obj.app.post(url + 'resetaccount', obj.bodyParser.urlencoded({ extended: false }), handleResetAccountRequest);
6422
- obj.app.get(url + 'checkmail', handleCheckMailRequest);
6423
- obj.app.get(url + 'agentinvite', handleAgentInviteRequest);
6424
- obj.app.get(url + 'userimage.ashx', handleUserImageRequest);
6425
- obj.app.post(url + 'amtevents.ashx', obj.bodyParser.urlencoded({ extended: false }), obj.handleAmtEventRequest);
6426
- obj.app.get(url + 'meshagents', obj.handleMeshAgentRequest);
6427
- obj.app.get(url + 'messenger', handleMessengerRequest);
6428
- obj.app.get(url + 'messenger.png', handleMessengerImageRequest);
6429
- obj.app.get(url + 'meshosxagent', obj.handleMeshOsxAgentRequest);
6430
- obj.app.get(url + 'meshsettings', obj.handleMeshSettingsRequest);
6431
- obj.app.get(url + 'devicepowerevents.ashx', obj.handleDevicePowerEvents);
6432
- obj.app.get(url + 'downloadfile.ashx', handleDownloadFile);
6433
- obj.app.get(url + 'commander.ashx', handleMeshCommander);
6434
- obj.app.post(url + 'uploadfile.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadFile);
6435
- obj.app.post(url + 'uploadfilebatch.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadFileBatch);
6436
- obj.app.post(url + 'uploadmeshcorefile.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadMeshCoreFile);
6437
- obj.app.post(url + 'oneclickrecovery.ashx', obj.bodyParser.urlencoded({ extended: false }), handleOneClickRecoveryFile);
6438
- obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
6439
- obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
6440
- obj.app.ws(url + '2fahold.ashx', handle2faHoldWebSocket);
6441
- obj.app.ws(url + 'apf.ashx', function (ws, req) { obj.parent.mpsserver.onWebSocketConnection(ws, req); })
6442
- obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
6443
- obj.app.get(url + 'health.ashx', function (req, res) { res.send('ok'); }); // TODO: Perform more server checking.
6444
- obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); });
6445
- 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); }); });
6446
- obj.app.ws(url + 'control.ashx', function (ws, req) {
6447
- getWebsocketArgs(ws, req, function (ws, req) {
6448
- const domain = getDomain(req);
6449
- if (obj.CheckWebServerOriginName(domain, req) == false) {
6450
- try { ws.send(JSON.stringify({ action: 'close', cause: 'invalidorigin', msg: 'invalidorigin' })); } catch (ex) { }
6451
- try { ws.close(); } catch (ex) { }
6452
- return;
6453
- }
6454
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { ws.close(); return; } // Check 3FA URL key
6455
- PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6456
- if (user == null) { // User is not authenticated, perform inner server authentication
6457
- if (req.headers['x-meshauth'] === '*') {
6458
- 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
6424
+ function setupHTTPHandlers() {
6425
+ // Setup all HTTP handlers
6426
+ if (parent.pluginHandler != null) {
6427
+ parent.pluginHandler.callHook('hook_setupHttpHandlers', obj, parent);
6428
+ }
6429
+ if (parent.multiServer != null) { obj.app.ws('/meshserver.ashx', function (ws, req) { parent.multiServer.CreatePeerInServer(parent.multiServer, ws, req, obj.args.tlsoffload == null); }); }
6430
+ for (var i in parent.config.domains) {
6431
+ 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.
6432
+ var domain = parent.config.domains[i];
6433
+ var url = domain.url;
6434
+ if (typeof domain.rootredirect == 'string') {
6435
+ // Root page redirects the user to a different URL
6436
+ obj.app.get(url, handleRootRedirect);
6437
+ } else {
6438
+ // Present the login page as the root page
6439
+ obj.app.get(url, handleRootRequest);
6440
+ obj.app.post(url, obj.bodyParser.urlencoded({ extended: false }), handleRootPostRequest);
6441
+ }
6442
+ obj.app.get(url + 'refresh.ashx', function (req, res) { res.sendStatus(200); });
6443
+ if ((domain.myserver !== false) && ((domain.myserver == null) || (domain.myserver.backup === true))) { obj.app.get(url + 'backup.zip', handleBackupRequest); }
6444
+ if ((domain.myserver !== false) && ((domain.myserver == null) || (domain.myserver.restore === true))) { obj.app.post(url + 'restoreserver.ashx', obj.bodyParser.urlencoded({ extended: false }), handleRestoreRequest); }
6445
+ obj.app.get(url + 'terms', handleTermsRequest);
6446
+ obj.app.get(url + 'xterm', handleXTermRequest);
6447
+ obj.app.get(url + 'login', handleRootRequest);
6448
+ obj.app.post(url + 'login', obj.bodyParser.urlencoded({ extended: false }), handleRootPostRequest);
6449
+ obj.app.post(url + 'tokenlogin', obj.bodyParser.urlencoded({ extended: false }), handleLoginRequest);
6450
+ obj.app.get(url + 'logout', handleLogoutRequest);
6451
+ obj.app.get(url + 'MeshServerRootCert.cer', handleRootCertRequest);
6452
+ obj.app.post(url + 'changepassword', obj.bodyParser.urlencoded({ extended: false }), handlePasswordChangeRequest);
6453
+ obj.app.post(url + 'deleteaccount', obj.bodyParser.urlencoded({ extended: false }), handleDeleteAccountRequest);
6454
+ obj.app.post(url + 'createaccount', obj.bodyParser.urlencoded({ extended: false }), handleCreateAccountRequest);
6455
+ obj.app.post(url + 'resetpassword', obj.bodyParser.urlencoded({ extended: false }), handleResetPasswordRequest);
6456
+ obj.app.post(url + 'resetaccount', obj.bodyParser.urlencoded({ extended: false }), handleResetAccountRequest);
6457
+ obj.app.get(url + 'checkmail', handleCheckMailRequest);
6458
+ obj.app.get(url + 'agentinvite', handleAgentInviteRequest);
6459
+ obj.app.get(url + 'userimage.ashx', handleUserImageRequest);
6460
+ obj.app.post(url + 'amtevents.ashx', obj.bodyParser.urlencoded({ extended: false }), obj.handleAmtEventRequest);
6461
+ obj.app.get(url + 'meshagents', obj.handleMeshAgentRequest);
6462
+ obj.app.get(url + 'messenger', handleMessengerRequest);
6463
+ obj.app.get(url + 'messenger.png', handleMessengerImageRequest);
6464
+ obj.app.get(url + 'meshosxagent', obj.handleMeshOsxAgentRequest);
6465
+ obj.app.get(url + 'meshsettings', obj.handleMeshSettingsRequest);
6466
+ obj.app.get(url + 'devicepowerevents.ashx', obj.handleDevicePowerEvents);
6467
+ obj.app.get(url + 'downloadfile.ashx', handleDownloadFile);
6468
+ obj.app.get(url + 'commander.ashx', handleMeshCommander);
6469
+ obj.app.post(url + 'uploadfile.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadFile);
6470
+ obj.app.post(url + 'uploadfilebatch.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadFileBatch);
6471
+ obj.app.post(url + 'uploadmeshcorefile.ashx', obj.bodyParser.urlencoded({ extended: false }), handleUploadMeshCoreFile);
6472
+ obj.app.post(url + 'oneclickrecovery.ashx', obj.bodyParser.urlencoded({ extended: false }), handleOneClickRecoveryFile);
6473
+ obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
6474
+ obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
6475
+ obj.app.ws(url + '2fahold.ashx', handle2faHoldWebSocket);
6476
+ obj.app.ws(url + 'apf.ashx', function (ws, req) { obj.parent.mpsserver.onWebSocketConnection(ws, req); })
6477
+ obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
6478
+ obj.app.get(url + 'health.ashx', function (req, res) { res.send('ok'); }); // TODO: Perform more server checking.
6479
+ obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); });
6480
+ 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); }); });
6481
+ obj.app.ws(url + 'control.ashx', function (ws, req) {
6482
+ getWebsocketArgs(ws, req, function (ws, req) {
6483
+ const domain = getDomain(req);
6484
+ if (obj.CheckWebServerOriginName(domain, req) == false) {
6485
+ try { ws.send(JSON.stringify({ action: 'close', cause: 'invalidorigin', msg: 'invalidorigin' })); } catch (ex) { }
6486
+ try { ws.close(); } catch (ex) { }
6487
+ return;
6488
+ }
6489
+ if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { ws.close(); return; } // Check 3FA URL key
6490
+ PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6491
+ if (user == null) { // User is not authenticated, perform inner server authentication
6492
+ if (req.headers['x-meshauth'] === '*') {
6493
+ 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
6494
+ } else {
6495
+ try { ws.close(); } catch (ex) { } // user is not authenticated and inner authentication was not requested, disconnect now.
6496
+ }
6497
} else {
6460
- try { ws.close(); } catch (ex) { } // user is not authenticated and inner authentication was not requested, disconnect now.
6498
+ obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user, authData); // User is authenticated
6499
}
6462
- } else {
6463
- obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user, authData); // User is authenticated
6464
- }
6500
+ });
6501
});
6502
});
6467
- });
6468
- obj.app.ws(url + 'devicefile.ashx', function (ws, req) { obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, ws, null, req, domain); });
6469
- obj.app.get(url + 'devicefile.ashx', handleDeviceFile);
6470
- obj.app.get(url + 'agentdownload.ashx', handleAgentDownloadFile);
6471
- obj.app.get(url + 'logo.png', handleLogoRequest);
6472
- obj.app.get(url + 'loginlogo.png', handleLoginLogoRequest);
6473
- obj.app.post(url + 'translations', obj.bodyParser.urlencoded({ extended: false }), handleTranslationsRequest);
6474
- obj.app.get(url + 'welcome.jpg', handleWelcomeImageRequest);
6475
- obj.app.get(url + 'welcome.png', handleWelcomeImageRequest);
6476
- obj.app.get(url + 'recordings.ashx', handleGetRecordings);
6477
- obj.app.ws(url + 'recordings.ashx', handleGetRecordingsWebSocket);
6478
- obj.app.get(url + 'player.htm', handlePlayerRequest);
6479
- obj.app.get(url + 'player', handlePlayerRequest);
6480
- obj.app.get(url + 'sharing', handleSharingRequest);
6481
- obj.app.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler
6482
- obj.app.ws(url + 'meshrelay.ashx', function (ws, req) {
6483
- PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6484
- if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) {
6485
- obj.meshDesktopMultiplexHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Desktop multiplexor 1-to-n
6486
- } else {
6487
- obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Normal relay 1-to-1
6488
- }
6489
- });
6490
- });
6491
- if (obj.args.wanonly != true) { // If the server is not in WAN mode, allow server relayed connections.
6492
- obj.app.ws(url + 'localrelay.ashx', function (ws, req) {
6503
+ obj.app.ws(url + 'devicefile.ashx', function (ws, req) { obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, ws, null, req, domain); });
6504
+ obj.app.get(url + 'devicefile.ashx', handleDeviceFile);
6505
+ obj.app.get(url + 'agentdownload.ashx', handleAgentDownloadFile);
6506
+ obj.app.get(url + 'logo.png', handleLogoRequest);
6507
+ obj.app.get(url + 'loginlogo.png', handleLoginLogoRequest);
6508
+ obj.app.post(url + 'translations', obj.bodyParser.urlencoded({ extended: false }), handleTranslationsRequest);
6509
+ obj.app.get(url + 'welcome.jpg', handleWelcomeImageRequest);
6510
+ obj.app.get(url + 'welcome.png', handleWelcomeImageRequest);
6511
+ obj.app.get(url + 'recordings.ashx', handleGetRecordings);
6512
+ obj.app.ws(url + 'recordings.ashx', handleGetRecordingsWebSocket);
6513
+ obj.app.get(url + 'player.htm', handlePlayerRequest);
6514
+ obj.app.get(url + 'player', handlePlayerRequest);
6515
+ obj.app.get(url + 'sharing', handleSharingRequest);
6516
+ obj.app.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler
6517
+ obj.app.ws(url + 'meshrelay.ashx', function (ws, req) {
6518
PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6494
- if ((user == null) || (cookie == null)) {
6495
- try { ws1.close(); } catch (ex) { }
6519
+ if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) {
6520
+ obj.meshDesktopMultiplexHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Desktop multiplexor 1-to-n
6521
} else {
6497
- obj.meshRelayHandler.CreateLocalRelay(obj, ws1, req1, domain, user, cookie); // Local relay
6522
+ obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Normal relay 1-to-1
6523
}
6524
});
6525
});
6501
- }
6502
- if (domain.agentinvitecodes == true) {
6503
- obj.app.get(url + 'invite', handleInviteRequest);
6504
- obj.app.post(url + 'invite', obj.bodyParser.urlencoded({ extended: false }), handleInviteRequest);
6505
- }
6506
- if (parent.pluginHandler != null) {
6507
- obj.app.get(url + 'pluginadmin.ashx', obj.handlePluginAdminReq);
6508
- obj.app.post(url + 'pluginadmin.ashx', obj.bodyParser.urlencoded({ extended: false }), obj.handlePluginAdminPostReq);
6509
- obj.app.get(url + 'pluginHandler.js', obj.handlePluginJS);
6510
- }
6511
-
6512
- // New account CAPTCHA request
6513
- if ((domain.newaccountscaptcha != null) && (domain.newaccountscaptcha !== false)) {
6514
- obj.app.get(url + 'newAccountCaptcha.ashx', handleNewAccountCaptchaRequest);
6515
- }
6516
-
6517
- // Check CrowdSec Bounser if configured
6518
- if (parent.crowdSecBounser != null) {
6519
- obj.app.get(url + 'captcha.ashx', handleCaptchaGetRequest);
6520
- obj.app.post(url + 'captcha.ashx', obj.bodyParser.urlencoded({ extended: false }), handleCaptchaPostRequest);
6521
- }
6522
-
6523
- // Setup IP-KVM relay if supported
6524
- if (domain.ipkvm) {
6525
- obj.app.ws(url + 'ipkvm.ashx/*', function (ws, req) {
6526
- const domain = getDomain(req);
6527
- if (domain == null) { parent.debug('web', 'ipkvm: failed domain checks.'); try { ws.close(); } catch (ex) { } return; }
6528
- parent.ipKvmManager.handleIpKvmWebSocket(domain, ws, req);
6529
- });
6530
- obj.app.get(url + 'ipkvm.ashx/*', function (req, res, next) {
6531
- const domain = getDomain(req);
6532
- if (domain == null) return;
6533
- parent.ipKvmManager.handleIpKvmGet(domain, req, res, next);
6534
- });
6535
- }
6536
-
6537
- // Setup RDP unless indicated as disabled
6538
- if (domain.mstsc !== false) {
6539
- obj.app.get(url + 'mstsc.html', function (req, res) { handleMSTSCRequest(req, res, 'mstsc'); });
6540
- obj.app.ws(url + 'mstscrelay.ashx', function (ws, req) {
6541
- const domain = getDomain(req);
6542
- if (domain == null) { parent.debug('web', 'mstsc: failed checks.'); try { ws.close(); } catch (e) { } return; }
6543
- // If no user is logged in and we have a default user, set it now.
6544
- 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(); }
6545
- try { require('./apprelays.js').CreateMstscRelay(obj, obj.db, ws, req, obj.args, domain); } catch (ex) { console.log(ex); }
6546
- });
6547
- }
6548
-
6549
- // Setup SSH if needed
6550
- if (domain.ssh === true) {
6551
- obj.app.get(url + 'ssh.html', function (req, res) { handleMSTSCRequest(req, res, 'ssh'); });
6552
- obj.app.ws(url + 'sshrelay.ashx', function (ws, req) {
6553
- const domain = getDomain(req);
6554
- if (domain == null) { parent.debug('web', 'ssh: failed checks.'); try { ws.close(); } catch (e) { } return; }
6555
- // If no user is logged in and we have a default user, set it now.
6556
- 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(); }
6557
- try { require('./apprelays.js').CreateSshRelay(obj, obj.db, ws, req, obj.args, domain); } catch (ex) { console.log(ex); }
6558
- });
6559
- obj.app.ws(url + 'sshterminalrelay.ashx', function (ws, req) {
6560
- PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6561
- require('./apprelays.js').CreateSshTerminalRelay(obj, obj.db, ws1, req1, domain, user, cookie, obj.args);
6526
+ if (obj.args.wanonly != true) { // If the server is not in WAN mode, allow server relayed connections.
6527
+ obj.app.ws(url + 'localrelay.ashx', function (ws, req) {
6528
+ PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6529
+ if ((user == null) || (cookie == null)) {
6530
+ try { ws1.close(); } catch (ex) { }
6531
+ } else {
6532
+ obj.meshRelayHandler.CreateLocalRelay(obj, ws1, req1, domain, user, cookie); // Local relay
6533
+ }
6534
+ });
6535
});
6563
- });
6564
- obj.app.ws(url + 'sshfilesrelay.ashx', function (ws, req) {
6565
- PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6566
- require('./apprelays.js').CreateSshFilesRelay(obj, obj.db, ws1, req1, domain, user, cookie, obj.args);
6536
+ }
6537
+ if (domain.agentinvitecodes == true) {
6538
+ obj.app.get(url + 'invite', handleInviteRequest);
6539
+ obj.app.post(url + 'invite', obj.bodyParser.urlencoded({ extended: false }), handleInviteRequest);
6540
+ }
6541
+ if (parent.pluginHandler != null) {
6542
+ obj.app.get(url + 'pluginadmin.ashx', obj.handlePluginAdminReq);
6543
+ obj.app.post(url + 'pluginadmin.ashx', obj.bodyParser.urlencoded({ extended: false }), obj.handlePluginAdminPostReq);
6544
+ obj.app.get(url + 'pluginHandler.js', obj.handlePluginJS);
6545
+ }
6546
+
6547
+ // New account CAPTCHA request
6548
+ if ((domain.newaccountscaptcha != null) && (domain.newaccountscaptcha !== false)) {
6549
+ obj.app.get(url + 'newAccountCaptcha.ashx', handleNewAccountCaptchaRequest);
6550
+ }
6551
+
6552
+ // Check CrowdSec Bounser if configured
6553
+ if (parent.crowdSecBounser != null) {
6554
+ obj.app.get(url + 'captcha.ashx', handleCaptchaGetRequest);
6555
+ obj.app.post(url + 'captcha.ashx', obj.bodyParser.urlencoded({ extended: false }), handleCaptchaPostRequest);
6556
+ }
6557
+
6558
+ // Setup IP-KVM relay if supported
6559
+ if (domain.ipkvm) {
6560
+ obj.app.ws(url + 'ipkvm.ashx/*', function (ws, req) {
6561
+ const domain = getDomain(req);
6562
+ if (domain == null) { parent.debug('web', 'ipkvm: failed domain checks.'); try { ws.close(); } catch (ex) { } return; }
6563
+ parent.ipKvmManager.handleIpKvmWebSocket(domain, ws, req);
6564
});
6568
- });
6569
- }
6570
-
6571
- // Setup firebase push only server
6572
- if ((obj.parent.firebase != null) && (obj.parent.config.firebase)) {
6573
- if (obj.parent.config.firebase.pushrelayserver) { parent.debug('email', 'Firebase-pushrelay-handler'); obj.app.post(url + 'firebaserelay.aspx', obj.bodyParser.urlencoded({ extended: false }), handleFirebasePushOnlyRelayRequest); }
6574
- if (obj.parent.config.firebase.relayserver) { parent.debug('email', 'Firebase-relay-handler'); obj.app.ws(url + 'firebaserelay.aspx', handleFirebaseRelayRequest); }
6575
- }
6576
-
6577
- // Setup auth strategies using passport if needed
6578
- if (typeof domain.authstrategies == 'object') {
6579
- // Twitter
6580
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.twitter) != 0) {
6581
- obj.app.get(url + 'auth-twitter', function (req, res, next) {
6582
- var domain = getDomain(req);
6583
- if (domain.passport == null) { next(); return; }
6584
- domain.passport.authenticate('twitter-' + domain.id)(req, res, function (err) { console.log('c1', err, req.session); next(); });
6565
+ obj.app.get(url + 'ipkvm.ashx/*', function (req, res, next) {
6566
+ const domain = getDomain(req);
6567
+ if (domain == null) return;
6568
+ parent.ipKvmManager.handleIpKvmGet(domain, req, res, next);
6569
});
6586
- obj.app.get(url + 'auth-twitter-callback', function (req, res, next) {
6587
- var domain = getDomain(req);
6588
- if (domain.passport == null) { next(); return; }
6589
- if ((Object.keys(req.session).length == 0) && (req.query.nmr == null)) {
6590
- // This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
6591
- var url = req.url;
6592
- if (url.indexOf('?') >= 0) { url += '&nmr=1'; } else { url += '?nmr=1'; } // Add this to the URL to prevent redirect loop.
6593
- res.set('Content-Type', 'text/html');
6594
- res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
6595
- } else {
6596
- domain.passport.authenticate('twitter-' + domain.id, { failureRedirect: '/' })(req, res, function (err) { if (err != null) { console.log(err); } next(); });
6597
- }
6598
- }, handleStrategyLogin);
6570
}
6600
-
6601
- // Google
6602
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.google) != 0) {
6603
- obj.app.get(url + 'auth-google', function (req, res, next) {
6604
- var domain = getDomain(req);
6605
- if (domain.passport == null) { next(); return; }
6606
- domain.passport.authenticate('google-' + domain.id, { scope: ['profile', 'email'] })(req, res, next);
6571
+
6572
+ // Setup RDP unless indicated as disabled
6573
+ if (domain.mstsc !== false) {
6574
+ obj.app.get(url + 'mstsc.html', function (req, res) { handleMSTSCRequest(req, res, 'mstsc'); });
6575
+ obj.app.ws(url + 'mstscrelay.ashx', function (ws, req) {
6576
+ const domain = getDomain(req);
6577
+ if (domain == null) { parent.debug('web', 'mstsc: failed checks.'); try { ws.close(); } catch (e) { } return; }
6578
+ // If no user is logged in and we have a default user, set it now.
6579
+ 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(); }
6580
+ try { require('./apprelays.js').CreateMstscRelay(obj, obj.db, ws, req, obj.args, domain); } catch (ex) { console.log(ex); }
6581
});
6608
- obj.app.get(url + 'auth-google-callback', function (req, res, next) {
6609
- var domain = getDomain(req);
6610
- if (domain.passport == null) { next(); return; }
6611
- domain.passport.authenticate('google-' + domain.id, { failureRedirect: '/' })(req, res, function (err) { if (err != null) { console.log(err); } next(); });
6612
- }, handleStrategyLogin);
6613
- }
6614
-
6615
- // GitHub
6616
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.github) != 0) {
6617
- obj.app.get(url + 'auth-github', function (req, res, next) {
6618
- var domain = getDomain(req);
6619
- if (domain.passport == null) { next(); return; }
6620
- domain.passport.authenticate('github-' + domain.id, { scope: ['user:email'] })(req, res, next);
6582
+ }
6583
+
6584
+ // Setup SSH if needed
6585
+ if (domain.ssh === true) {
6586
+ obj.app.get(url + 'ssh.html', function (req, res) { handleMSTSCRequest(req, res, 'ssh'); });
6587
+ obj.app.ws(url + 'sshrelay.ashx', function (ws, req) {
6588
+ const domain = getDomain(req);
6589
+ if (domain == null) { parent.debug('web', 'ssh: failed checks.'); try { ws.close(); } catch (e) { } return; }
6590
+ // If no user is logged in and we have a default user, set it now.
6591
+ 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(); }
6592
+ try { require('./apprelays.js').CreateSshRelay(obj, obj.db, ws, req, obj.args, domain); } catch (ex) { console.log(ex); }
6593
});
6622
- obj.app.get(url + 'auth-github-callback', function (req, res, next) {
6623
- var domain = getDomain(req);
6624
- if (domain.passport == null) { next(); return; }
6625
- domain.passport.authenticate('github-' + domain.id, { failureRedirect: '/' })(req, res, next);
6626
- }, handleStrategyLogin);
6627
- }
6628
-
6629
- // Reddit
6630
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.reddit) != 0) {
6631
- obj.app.get(url + 'auth-reddit', function (req, res, next) {
6632
- var domain = getDomain(req);
6633
- if (domain.passport == null) { next(); return; }
6634
- domain.passport.authenticate('reddit-' + domain.id, { state: obj.parent.encodeCookie({ 'p': 'reddit' }, obj.parent.loginCookieEncryptionKey), duration: 'permanent' })(req, res, next);
6594
+ obj.app.ws(url + 'sshterminalrelay.ashx', function (ws, req) {
6595
+ PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6596
+ require('./apprelays.js').CreateSshTerminalRelay(obj, obj.db, ws1, req1, domain, user, cookie, obj.args);
6597
+ });
6598
+ });
6599
+ obj.app.ws(url + 'sshfilesrelay.ashx', function (ws, req) {
6600
+ PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6601
+ require('./apprelays.js').CreateSshFilesRelay(obj, obj.db, ws1, req1, domain, user, cookie, obj.args);
6602
+ });
6603
});
6636
- obj.app.get(url + 'auth-reddit-callback', function (req, res, next) {
6637
- var domain = getDomain(req);
6638
- if (domain.passport == null) { next(); return; }
6639
- if ((Object.keys(req.session).length == 0) && (req.query.nmr == null)) {
6640
- // This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
6641
- var url = req.url;
6642
- if (url.indexOf('?') >= 0) { url += '&nmr=1'; } else { url += '?nmr=1'; } // Add this to the URL to prevent redirect loop.
6643
- res.set('Content-Type', 'text/html');
6644
- res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
6645
- } else {
6646
- if (req.query.state != null) {
6647
- var c = obj.parent.decodeCookie(req.query.state, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout
6648
- if ((c != null) && (c.p == 'reddit')) { domain.passport.authenticate('reddit-' + domain.id, { failureRedirect: '/' })(req, res, next); return; }
6649
- }
6650
- next();
6651
- }
6652
- }, handleStrategyLogin);
6604
}
6605
+
6606
+ // Setup firebase push only server
6607
+ if ((obj.parent.firebase != null) && (obj.parent.config.firebase)) {
6608
+ if (obj.parent.config.firebase.pushrelayserver) { parent.debug('email', 'Firebase-pushrelay-handler'); obj.app.post(url + 'firebaserelay.aspx', obj.bodyParser.urlencoded({ extended: false }), handleFirebasePushOnlyRelayRequest); }
6609
+ if (obj.parent.config.firebase.relayserver) { parent.debug('email', 'Firebase-relay-handler'); obj.app.ws(url + 'firebaserelay.aspx', handleFirebaseRelayRequest); }
6610
+ }
6611
+
6612
+ // Setup auth strategies using passport if needed
6613
+ if (typeof domain.authstrategies == 'object') {
6614
+ // Twitter
6615
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.twitter) != 0) {
6616
+ obj.app.get(url + 'auth-twitter', function (req, res, next) {
6617
+ var domain = getDomain(req);
6618
+ if (domain.passport == null) { next(); return; }
6619
+ domain.passport.authenticate('twitter-' + domain.id)(req, res, function (err) { console.log('c1', err, req.session); next(); });
6620
+ });
6621
+ obj.app.get(url + 'auth-twitter-callback', function (req, res, next) {
6622
+ var domain = getDomain(req);
6623
+ if (domain.passport == null) { next(); return; }
6624
+ if ((Object.keys(req.session).length == 0) && (req.query.nmr == null)) {
6625
+ // This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
6626
+ var url = req.url;
6627
+ if (url.indexOf('?') >= 0) { url += '&nmr=1'; } else { url += '?nmr=1'; } // Add this to the URL to prevent redirect loop.
6628
+ res.set('Content-Type', 'text/html');
6629
+ res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
6630
+ } else {
6631
+ domain.passport.authenticate('twitter-' + domain.id, { failureRedirect: '/' })(req, res, function (err) { if (err != null) { console.log(err); } next(); });
6632
+ }
6633
+ }, handleStrategyLogin);
6634
+ }
6635
6655
- // Azure
6656
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.azure) != 0) {
6657
- obj.app.get(url + 'auth-azure', function (req, res, next) {
6658
- var domain = getDomain(req);
6659
- if (domain.passport == null) { next(); return; }
6660
- domain.passport.authenticate('azure-' + domain.id, { state: obj.parent.encodeCookie({ 'p': 'azure' }, obj.parent.loginCookieEncryptionKey) })(req, res, next);
6661
- });
6662
- obj.app.get(url + 'auth-azure-callback', function (req, res, next) {
6663
- var domain = getDomain(req);
6664
- if (domain.passport == null) { next(); return; }
6665
- if ((Object.keys(req.session).length == 0) && (req.query.nmr == null)) {
6666
- // This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
6667
- var url = req.url;
6668
- if (url.indexOf('?') >= 0) { url += '&nmr=1'; } else { url += '?nmr=1'; } // Add this to the URL to prevent redirect loop.
6669
- res.set('Content-Type', 'text/html');
6670
- res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
6671
- } else {
6672
- if (req.query.state != null) {
6673
- var c = obj.parent.decodeCookie(req.query.state, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout
6674
- if ((c != null) && (c.p == 'azure')) { domain.passport.authenticate('azure-' + domain.id, { failureRedirect: '/' })(req, res, next); return; }
6636
+ // Google
6637
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.google) != 0) {
6638
+ obj.app.get(url + 'auth-google', function (req, res, next) {
6639
+ var domain = getDomain(req);
6640
+ if (domain.passport == null) { next(); return; }
6641
+ domain.passport.authenticate('google-' + domain.id, { scope: ['profile', 'email'] })(req, res, next);
6642
+ });
6643
+ obj.app.get(url + 'auth-google-callback', function (req, res, next) {
6644
+ var domain = getDomain(req);
6645
+ if (domain.passport == null) { next(); return; }
6646
+ domain.passport.authenticate('google-' + domain.id, { failureRedirect: '/' })(req, res, function (err) { if (err != null) { console.log(err); } next(); });
6647
+ }, handleStrategyLogin);
6648
+ }
6649
+
6650
+ // GitHub
6651
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.github) != 0) {
6652
+ obj.app.get(url + 'auth-github', function (req, res, next) {
6653
+ var domain = getDomain(req);
6654
+ if (domain.passport == null) { next(); return; }
6655
+ domain.passport.authenticate('github-' + domain.id, { scope: ['user:email'] })(req, res, next);
6656
+ });
6657
+ obj.app.get(url + 'auth-github-callback', function (req, res, next) {
6658
+ var domain = getDomain(req);
6659
+ if (domain.passport == null) { next(); return; }
6660
+ domain.passport.authenticate('github-' + domain.id, { failureRedirect: '/' })(req, res, next);
6661
+ }, handleStrategyLogin);
6662
+ }
6663
+
6664
+ // Reddit
6665
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.reddit) != 0) {
6666
+ obj.app.get(url + 'auth-reddit', function (req, res, next) {
6667
+ var domain = getDomain(req);
6668
+ if (domain.passport == null) { next(); return; }
6669
+ domain.passport.authenticate('reddit-' + domain.id, { state: obj.parent.encodeCookie({ 'p': 'reddit' }, obj.parent.loginCookieEncryptionKey), duration: 'permanent' })(req, res, next);
6670
+ });
6671
+ obj.app.get(url + 'auth-reddit-callback', function (req, res, next) {
6672
+ var domain = getDomain(req);
6673
+ if (domain.passport == null) { next(); return; }
6674
+ if ((Object.keys(req.session).length == 0) && (req.query.nmr == null)) {
6675
+ // This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
6676
+ var url = req.url;
6677
+ if (url.indexOf('?') >= 0) { url += '&nmr=1'; } else { url += '?nmr=1'; } // Add this to the URL to prevent redirect loop.
6678
+ res.set('Content-Type', 'text/html');
6679
+ res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
6680
+ } else {
6681
+ if (req.query.state != null) {
6682
+ var c = obj.parent.decodeCookie(req.query.state, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout
6683
+ if ((c != null) && (c.p == 'reddit')) { domain.passport.authenticate('reddit-' + domain.id, { failureRedirect: '/' })(req, res, next); return; }
6684
+ }
6685
+ next();
6686
+ }
6687
+ }, handleStrategyLogin);
6688
+ }
6689
+
6690
+ // Azure
6691
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.azure) != 0) {
6692
+ obj.app.get(url + 'auth-azure', function (req, res, next) {
6693
+ var domain = getDomain(req);
6694
+ if (domain.passport == null) { next(); return; }
6695
+ domain.passport.authenticate('azure-' + domain.id, { state: obj.parent.encodeCookie({ 'p': 'azure' }, obj.parent.loginCookieEncryptionKey) })(req, res, next);
6696
+ });
6697
+ obj.app.get(url + 'auth-azure-callback', function (req, res, next) {
6698
+ var domain = getDomain(req);
6699
+ if (domain.passport == null) { next(); return; }
6700
+ if ((Object.keys(req.session).length == 0) && (req.query.nmr == null)) {
6701
+ // This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
6702
+ var url = req.url;
6703
+ if (url.indexOf('?') >= 0) { url += '&nmr=1'; } else { url += '?nmr=1'; } // Add this to the URL to prevent redirect loop.
6704
+ res.set('Content-Type', 'text/html');
6705
+ res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
6706
+ } else {
6707
+ if (req.query.state != null) {
6708
+ var c = obj.parent.decodeCookie(req.query.state, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout
6709
+ if ((c != null) && (c.p == 'azure')) { domain.passport.authenticate('azure-' + domain.id, { failureRedirect: '/' })(req, res, next); return; }
6710
+ }
6711
+ next();
6712
}
6676
- next();
6713
+ }, handleStrategyLogin);
6714
+ }
6715
+
6716
+ // Setup OpenID Connect URLs
6717
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.oidc) != 0) {
6718
+
6719
+ obj.app.get(url + 'auth-oidc', function (req, res, next) {
6720
+ var domain = getDomain(req);
6721
+ if (domain.passport == null) { next(); return; }
6722
+ domain.passport.authenticate(`oidc-${domain.id}`, { failureRedirect: '/', failureFlash: true })(req, res, next);
6723
+ });
6724
+ let redirectPath
6725
+ if (typeof domain.authstrategies.oidc.client.redirect_uri == 'string') {
6726
+ redirectPath = (new URL(domain.authstrategies.oidc.client.redirect_uri)).pathname
6727
+ } else if (Array.isArray(domain.authstrategies.oidc.client.redirect_uris)) {
6728
+ redirectPath = (new URL(domain.authstrategies.oidc.client.redirect_uris[0])).pathname
6729
+ } else {
6730
+ redirectPath = url + 'auth-oidc-callback'
6731
}
6678
- }, handleStrategyLogin);
6679
- }
6732
+ obj.app.get(redirectPath, obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
6733
+ var domain = getDomain(req);
6734
+ if (domain.passport == null) { next(); return; }
6735
+ domain.passport.authenticate(`oidc-${domain.id}`, { failureRedirect: '/', failureFlash: true })(req, res, next);
6736
+ }, handleStrategyLogin);
6737
+ }
6738
6681
- // Generic OpenID Connect
6682
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.oidc) != 0) {
6683
- var flash = require('connect-flash');
6684
- obj.app.use(flash());
6685
- obj.app.get(url + 'auth-oidc', function (req, res, next) {
6686
- var domain = getDomain(req);
6687
- if (domain.passport == null) { next(); return; }
6688
- domain.passport.authenticate('oidc-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6689
- });
6690
- obj.app.get(url + 'oidc-callback', function (req, res, next) {
6691
- var domain = getDomain(req);
6692
- if (domain.passport == null) { next(); return; }
6693
- domain.passport.authenticate('oidc-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6694
- }, handleStrategyLogin);
6695
- }
6696
-
6697
- // Generic SAML
6698
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.saml) != 0) {
6699
- obj.app.get(url + 'auth-saml', function (req, res, next) {
6700
- var domain = getDomain(req);
6701
- if (domain.passport == null) { next(); return; }
6702
- domain.passport.authenticate('saml-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6703
- });
6704
- obj.app.post(url + 'auth-saml-callback', obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
6705
- var domain = getDomain(req);
6706
- if (domain.passport == null) { next(); return; }
6707
- domain.passport.authenticate('saml-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6708
- }, handleStrategyLogin);
6709
- }
6710
-
6711
- // Intel SAML
6712
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.intelSaml) != 0) {
6713
- obj.app.get(url + 'auth-intel', function (req, res, next) {
6714
- var domain = getDomain(req);
6715
- if (domain.passport == null) { next(); return; }
6716
- domain.passport.authenticate('isaml-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6717
- });
6718
- obj.app.post(url + 'auth-intel-callback', obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
6719
- var domain = getDomain(req);
6720
- if (domain.passport == null) { next(); return; }
6721
- domain.passport.authenticate('isaml-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6722
- }, handleStrategyLogin);
6723
- }
6724
-
6725
- // JumpCloud SAML
6726
- if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.jumpCloudSaml) != 0) {
6727
- obj.app.get(url + 'auth-jumpcloud', function (req, res, next) {
6728
- var domain = getDomain(req);
6729
- if (domain.passport == null) { next(); return; }
6730
- domain.passport.authenticate('jumpcloud-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6731
- });
6732
- obj.app.post(url + 'auth-jumpcloud-callback', obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
6733
- var domain = getDomain(req);
6734
- if (domain.passport == null) { next(); return; }
6735
- domain.passport.authenticate('jumpcloud-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6736
- }, handleStrategyLogin);
6737
- }
6738
- }
6739
+ // Generic SAML
6740
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.saml) != 0) {
6741
+ obj.app.get(url + 'auth-saml', function (req, res, next) {
6742
+ var domain = getDomain(req);
6743
+ if (domain.passport == null) { next(); return; }
6744
+ domain.passport.authenticate('saml-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6745
+ });
6746
+ obj.app.post(url + 'auth-saml-callback', obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
6747
+ var domain = getDomain(req);
6748
+ if (domain.passport == null) { next(); return; }
6749
+ domain.passport.authenticate('saml-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6750
+ }, handleStrategyLogin);
6751
+ }
6752
6740
- // Server redirects
6741
- 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); } } }
6753
+ // Intel SAML
6754
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.intelSaml) != 0) {
6755
+ obj.app.get(url + 'auth-intel', function (req, res, next) {
6756
+ var domain = getDomain(req);
6757
+ if (domain.passport == null) { next(); return; }
6758
+ domain.passport.authenticate('isaml-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6759
+ });
6760
+ obj.app.post(url + 'auth-intel-callback', obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
6761
+ var domain = getDomain(req);
6762
+ if (domain.passport == null) { next(); return; }
6763
+ domain.passport.authenticate('isaml-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6764
+ }, handleStrategyLogin);
6765
+ }
6766
6743
- // Server picture
6744
- obj.app.get(url + 'serverpic.ashx', function (req, res) {
6745
- // Check if we have "server.jpg" in the data folder, if so, use that.
6746
- if ((parent.configurationFiles != null) && (parent.configurationFiles['server.png'] != null)) {
6747
- res.set({ 'Content-Type': 'image/png' });
6748
- res.send(parent.configurationFiles['server.png']);
6749
- } else {
6767
+ // JumpCloud SAML
6768
+ if ((domain.authstrategies.authStrategyFlags & domainAuthStrategyConsts.jumpCloudSaml) != 0) {
6769
+ obj.app.get(url + 'auth-jumpcloud', function (req, res, next) {
6770
+ var domain = getDomain(req);
6771
+ if (domain.passport == null) { next(); return; }
6772
+ domain.passport.authenticate('jumpcloud-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6773
+ });
6774
+ obj.app.post(url + 'auth-jumpcloud-callback', obj.bodyParser.urlencoded({ extended: false }), function (req, res, next) {
6775
+ var domain = getDomain(req);
6776
+ if (domain.passport == null) { next(); return; }
6777
+ domain.passport.authenticate('jumpcloud-' + domain.id, { failureRedirect: '/', failureFlash: true })(req, res, next);
6778
+ }, handleStrategyLogin);
6779
+ }
6780
+ }
6781
+
6782
+ // Server redirects
6783
+ 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); } } }
6784
+
6785
+ // Server picture
6786
+ obj.app.get(url + 'serverpic.ashx', function (req, res) {
6787
// Check if we have "server.jpg" in the data folder, if so, use that.
6751
- var p = obj.path.join(obj.parent.datapath, 'server.png');
6752
- if (obj.fs.existsSync(p)) {
6753
- // Use the data folder server picture
6754
- try { res.sendFile(p); } catch (ex) { res.sendStatus(404); }
6788
+ if ((parent.configurationFiles != null) && (parent.configurationFiles['server.png'] != null)) {
6789
+ res.set({ 'Content-Type': 'image/png' });
6790
+ res.send(parent.configurationFiles['server.png']);
6791
} else {
6756
- var domain = getDomain(req);
6757
- if ((domain != null) && (domain.webpublicpath != null) && (obj.fs.existsSync(obj.path.join(domain.webpublicpath, 'images/server-256.png')))) {
6758
- // Use the domain server picture
6759
- try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/server-256.png')); } catch (ex) { res.sendStatus(404); }
6760
- } else if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/server-256.png'))) {
6761
- // Use the override server picture
6762
- try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, 'images/server-256.png')); } catch (ex) { res.sendStatus(404); }
6792
+ // Check if we have "server.jpg" in the data folder, if so, use that.
6793
+ var p = obj.path.join(obj.parent.datapath, 'server.png');
6794
+ if (obj.fs.existsSync(p)) {
6795
+ // Use the data folder server picture
6796
+ try { res.sendFile(p); } catch (ex) { res.sendStatus(404); }
6797
} else {
6764
- // Use the default server picture
6765
- try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/server-256.png')); } catch (ex) { res.sendStatus(404); }
6798
+ var domain = getDomain(req);
6799
+ if ((domain != null) && (domain.webpublicpath != null) && (obj.fs.existsSync(obj.path.join(domain.webpublicpath, 'images/server-256.png')))) {
6800
+ // Use the domain server picture
6801
+ try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/server-256.png')); } catch (ex) { res.sendStatus(404); }
6802
+ } else if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/server-256.png'))) {
6803
+ // Use the override server picture
6804
+ try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, 'images/server-256.png')); } catch (ex) { res.sendStatus(404); }
6805
+ } else {
6806
+ // Use the default server picture
6807
+ try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/server-256.png')); } catch (ex) { res.sendStatus(404); }
6808
+ }
6809
}
6810
}
6768
- }
6769
- });
6770
-
6771
- // Receive mesh agent connections
6772
- obj.app.ws(url + 'agent.ashx', function (ws, req) {
6773
- var domain = checkAgentIpAddress(ws, req);
6774
- if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + req.clientIp + ', holding.'); return; }
6775
- 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.
6776
- //console.log('Agent connect: ' + req.clientIp);
6777
- try { obj.meshAgentHandler.CreateMeshAgent(obj, obj.db, ws, req, obj.args, domain); } catch (e) { console.log(e); }
6778
- });
6779
-
6780
- // Setup MQTT broker over websocket
6781
- if (obj.parent.mqttbroker != null) {
6782
- obj.app.ws(url + 'mqtt.ashx', function (ws, req) {
6783
- var domain = checkAgentIpAddress(ws, req);
6784
- if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + req.clientIp + ', holding.'); return; }
6785
- var serialtunnel = SerialTunnel();
6786
- serialtunnel.xtransport = 'ws';
6787
- serialtunnel.xdomain = domain;
6788
- serialtunnel.xip = req.clientIp;
6789
- ws.on('message', function (b) { serialtunnel.updateBuffer(Buffer.from(b, 'binary')) });
6790
- serialtunnel.forwardwrite = function (b) { ws.send(b, 'binary') }
6791
- ws.on('close', function () { serialtunnel.emit('end'); });
6792
- obj.parent.mqttbroker.handle(serialtunnel); // Pass socket wrapper to MQTT broker
6811
});
6794
- }
6795
-
6796
- // Setup any .well-known folders
6797
- var p = obj.parent.path.join(obj.parent.datapath, '.well-known' + ((parent.config.domains[i].id == '') ? '' : ('-' + parent.config.domains[i].id)));
6798
- if (obj.parent.fs.existsSync(p)) { obj.app.use(url + '.well-known', obj.express.static(p)); }
6799
-
6800
- // Setup the alternative agent-only port
6801
- if (obj.agentapp) {
6802
- // Receive mesh agent connections on alternate port
6803
- obj.agentapp.ws(url + 'agent.ashx', function (ws, req) {
6812
+
6813
+ // Receive mesh agent connections
6814
+ obj.app.ws(url + 'agent.ashx', function (ws, req) {
6815
var domain = checkAgentIpAddress(ws, req);
6816
if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + req.clientIp + ', holding.'); return; }
6817
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.
6818
+ //console.log('Agent connect: ' + req.clientIp);
6819
try { obj.meshAgentHandler.CreateMeshAgent(obj, obj.db, ws, req, obj.args, domain); } catch (e) { console.log(e); }
6820
});
6809
-
6810
- // Setup mesh relay on alternative agent-only port
6811
- obj.agentapp.ws(url + 'meshrelay.ashx', function (ws, req) {
6812
- PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6813
- if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) {
6814
- obj.meshDesktopMultiplexHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Desktop multiplexor 1-to-n
6815
- } else {
6816
- obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Normal relay 1-to-1
6817
- }
6821
+
6822
+ // Setup MQTT broker over websocket
6823
+ if (obj.parent.mqttbroker != null) {
6824
+ obj.app.ws(url + 'mqtt.ashx', function (ws, req) {
6825
+ var domain = checkAgentIpAddress(ws, req);
6826
+ if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + req.clientIp + ', holding.'); return; }
6827
+ var serialtunnel = SerialTunnel();
6828
+ serialtunnel.xtransport = 'ws';
6829
+ serialtunnel.xdomain = domain;
6830
+ serialtunnel.xip = req.clientIp;
6831
+ ws.on('message', function (b) { serialtunnel.updateBuffer(Buffer.from(b, 'binary')) });
6832
+ serialtunnel.forwardwrite = function (b) { ws.send(b, 'binary') }
6833
+ ws.on('close', function () { serialtunnel.emit('end'); });
6834
+ obj.parent.mqttbroker.handle(serialtunnel); // Pass socket wrapper to MQTT broker
6835
});
6819
- });
6820
-
6821
- // Allows agents to transfer files
6822
- obj.agentapp.ws(url + 'devicefile.ashx', function (ws, req) { obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, ws, null, req, domain); });
6823
-
6824
- // Setup agent to/from server file transfer handler
6825
- obj.agentapp.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler
6826
-
6827
- // Setup agent downloads for meshcore updates
6828
- obj.agentapp.get(url + 'meshagents', obj.handleMeshAgentRequest);
6829
- }
6830
-
6831
- // Setup web relay on this web server if needed
6832
- // We set this up when a DNS name is used as a web relay instead of a port
6833
- if (obj.args.relaydns != null) {
6834
- obj.webRelayRouter = require('express').Router();
6835
-
6836
- // This is the magic URL that will setup the relay session
6837
- obj.webRelayRouter.get('/control-redirect.ashx', function (req, res, next) {
6838
- if (obj.args.relaydns.indexOf(req.hostname) == -1) { res.sendStatus(404); return; }
6839
- 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
6840
- res.set({ 'Cache-Control': 'no-store' });
6841
- parent.debug('web', 'webRelaySetup');
6842
-
6843
- // Decode the relay cookie
6844
- if (req.query.c == null) { res.sendStatus(404); return; }
6845
-
6846
- // Decode and check if this relay cookie is valid
6847
- var userid, domainid, domain, nodeid, addr, port, appid, webSessionId, expire, publicid;
6848
- 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.
6849
- if (urlCookie == null) { res.sendStatus(404); return; }
6850
-
6851
- // Decode the incoming cookie
6852
- if ((urlCookie.ruserid != null) && (urlCookie.x != null)) {
6853
- if (parent.webserver.destroyedSessions[urlCookie.ruserid + '/' + urlCookie.x] != null) { res.sendStatus(404); return; }
6854
-
6855
- // This is a standard user, figure out what our web relay will be.
6856
- if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing
6857
- if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing
6858
- if (req.session.z) { delete req.session.z; } // Clear the web relay guest session
6859
- userid = req.session.userid;
6860
- domainid = userid.split('/')[1];
6861
- domain = parent.config.domains[domainid];
6862
- nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n);
6863
- addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1';
6864
- port = parseInt(req.query.p);
6865
- appid = parseInt(req.query.appid);
6866
- webSessionId = req.session.userid + '/' + req.session.x;
6867
-
6868
- // Check that all the required arguments are present
6869
- 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; }
6870
- } else if (urlCookie.r == 8) {
6871
- // This is a guest user, figure out what our web relay will be.
6872
- userid = urlCookie.userid;
6873
- domainid = userid.split('/')[1];
6874
- domain = parent.config.domains[domainid];
6875
- nodeid = urlCookie.nid;
6876
- addr = (urlCookie.addr != null) ? urlCookie.addr : '127.0.0.1';
6877
- port = urlCookie.port;
6878
- appid = (urlCookie.p == 16) ? 2 : 1; // appid: 1 = HTTP, 2 = HTTPS
6879
- webSessionId = userid + '/' + urlCookie.pid;
6880
- publicid = urlCookie.pid;
6881
- if (req.session.x) { delete req.session.x; } // Clear the web relay sessionid
6882
- if (req.session.userid) { delete req.session.userid; } // Clear the web relay userid
6883
- if (req.session.z != webSessionId) { req.session.z = webSessionId; } // Set the web relay guest session
6884
- expire = urlCookie.expire;
6885
- if ((expire != null) && (expire <= Date.now())) { parent.debug('webrelay', 'expired link'); res.sendStatus(404); return; }
6886
- }
6887
-
6888
- // No session identifier was setup, exit now
6889
- if (webSessionId == null) { res.sendStatus(404); return; }
6890
-
6891
- // Check that we have an exact session on any of the relay DNS names
6892
- var xrelaySessionId, xrelaySession, freeRelayHost, oldestRelayTime, oldestRelayHost;
6893
- for (var hostIndex in obj.args.relaydns) {
6894
- const host = obj.args.relaydns[hostIndex];
6895
- xrelaySessionId = webSessionId + '/' + host;
6896
- xrelaySession = webRelaySessions[xrelaySessionId];
6897
- if (xrelaySession == null) {
6898
- // We found an unused hostname, save this as it could be useful.
6899
- if (freeRelayHost == null) { freeRelayHost = host; }
6900
- } else {
6901
- // Check if we already have a relay session that matches exactly what we want
6902
- if ((xrelaySession.domain.id == domain.id) && (xrelaySession.userid == userid) && (xrelaySession.nodeid == nodeid) && (xrelaySession.addr == addr) && (xrelaySession.port == port) && (xrelaySession.appid == appid)) {
6903
- // We found an exact match, we are all setup already, redirect to root of that DNS name
6904
- if (host == req.hostname) {
6905
- // Request was made on the same host, redirect to root.
6906
- res.redirect('/');
6907
- } else {
6908
- // Request was made to a different host
6909
- const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
6910
- res.redirect('https://' + host + ((httpport != 443) ? (':' + httpport) : '') + '/');
6911
- }
6912
- return;
6836
+ }
6837
+
6838
+ // Setup any .well-known folders
6839
+ var p = obj.parent.path.join(obj.parent.datapath, '.well-known' + ((parent.config.domains[i].id == '') ? '' : ('-' + parent.config.domains[i].id)));
6840
+ if (obj.parent.fs.existsSync(p)) { obj.app.use(url + '.well-known', obj.express.static(p)); }
6841
+
6842
+ // Setup the alternative agent-only port
6843
+ if (obj.agentapp) {
6844
+ // Receive mesh agent connections on alternate port
6845
+ obj.agentapp.ws(url + 'agent.ashx', function (ws, req) {
6846
+ var domain = checkAgentIpAddress(ws, req);
6847
+ if (domain == null) { parent.debug('web', 'Got agent connection with bad domain or blocked IP address ' + req.clientIp + ', holding.'); return; }
6848
+ 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.
6849
+ try { obj.meshAgentHandler.CreateMeshAgent(obj, obj.db, ws, req, obj.args, domain); } catch (e) { console.log(e); }
6850
+ });
6851
+
6852
+ // Setup mesh relay on alternative agent-only port
6853
+ obj.agentapp.ws(url + 'meshrelay.ashx', function (ws, req) {
6854
+ PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
6855
+ if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) {
6856
+ obj.meshDesktopMultiplexHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Desktop multiplexor 1-to-n
6857
+ } else {
6858
+ obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Normal relay 1-to-1
6859
}
6914
-
6915
- // Keep a record of the oldest web relay session, this could be useful.
6916
- if (oldestRelayHost == null) {
6917
- // Oldest host not set yet, set it
6918
- oldestRelayHost = host;
6919
- oldestRelayTime = xrelaySession.lastOperation;
6860
+ });
6861
+ });
6862
+
6863
+ // Allows agents to transfer files
6864
+ obj.agentapp.ws(url + 'devicefile.ashx', function (ws, req) { obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, ws, null, req, domain); });
6865
+
6866
+ // Setup agent to/from server file transfer handler
6867
+ obj.agentapp.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler
6868
+
6869
+ // Setup agent downloads for meshcore updates
6870
+ obj.agentapp.get(url + 'meshagents', obj.handleMeshAgentRequest);
6871
+ }
6872
+
6873
+ // Setup web relay on this web server if needed
6874
+ // We set this up when a DNS name is used as a web relay instead of a port
6875
+ if (obj.args.relaydns != null) {
6876
+ obj.webRelayRouter = require('express').Router();
6877
+
6878
+ // This is the magic URL that will setup the relay session
6879
+ obj.webRelayRouter.get('/control-redirect.ashx', function (req, res, next) {
6880
+ if (obj.args.relaydns.indexOf(req.hostname) == -1) { res.sendStatus(404); return; }
6881
+ 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
6882
+ res.set({ 'Cache-Control': 'no-store' });
6883
+ parent.debug('web', 'webRelaySetup');
6884
+
6885
+ // Decode the relay cookie
6886
+ if (req.query.c == null) { res.sendStatus(404); return; }
6887
+
6888
+ // Decode and check if this relay cookie is valid
6889
+ var userid, domainid, domain, nodeid, addr, port, appid, webSessionId, expire, publicid;
6890
+ 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.
6891
+ if (urlCookie == null) { res.sendStatus(404); return; }
6892
+
6893
+ // Decode the incoming cookie
6894
+ if ((urlCookie.ruserid != null) && (urlCookie.x != null)) {
6895
+ if (parent.webserver.destroyedSessions[urlCookie.ruserid + '/' + urlCookie.x] != null) { res.sendStatus(404); return; }
6896
+
6897
+ // This is a standard user, figure out what our web relay will be.
6898
+ if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing
6899
+ if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing
6900
+ if (req.session.z) { delete req.session.z; } // Clear the web relay guest session
6901
+ userid = req.session.userid;
6902
+ domainid = userid.split('/')[1];
6903
+ domain = parent.config.domains[domainid];
6904
+ nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n);
6905
+ addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1';
6906
+ port = parseInt(req.query.p);
6907
+ appid = parseInt(req.query.appid);
6908
+ webSessionId = req.session.userid + '/' + req.session.x;
6909
+
6910
+ // Check that all the required arguments are present
6911
+ 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; }
6912
+ } else if (urlCookie.r == 8) {
6913
+ // This is a guest user, figure out what our web relay will be.
6914
+ userid = urlCookie.userid;
6915
+ domainid = userid.split('/')[1];
6916
+ domain = parent.config.domains[domainid];
6917
+ nodeid = urlCookie.nid;
6918
+ addr = (urlCookie.addr != null) ? urlCookie.addr : '127.0.0.1';
6919
+ port = urlCookie.port;
6920
+ appid = (urlCookie.p == 16) ? 2 : 1; // appid: 1 = HTTP, 2 = HTTPS
6921
+ webSessionId = userid + '/' + urlCookie.pid;
6922
+ publicid = urlCookie.pid;
6923
+ if (req.session.x) { delete req.session.x; } // Clear the web relay sessionid
6924
+ if (req.session.userid) { delete req.session.userid; } // Clear the web relay userid
6925
+ if (req.session.z != webSessionId) { req.session.z = webSessionId; } // Set the web relay guest session
6926
+ expire = urlCookie.expire;
6927
+ if ((expire != null) && (expire <= Date.now())) { parent.debug('webrelay', 'expired link'); res.sendStatus(404); return; }
6928
+ }
6929
+
6930
+ // No session identifier was setup, exit now
6931
+ if (webSessionId == null) { res.sendStatus(404); return; }
6932
+
6933
+ // Check that we have an exact session on any of the relay DNS names
6934
+ var xrelaySessionId, xrelaySession, freeRelayHost, oldestRelayTime, oldestRelayHost;
6935
+ for (var hostIndex in obj.args.relaydns) {
6936
+ const host = obj.args.relaydns[hostIndex];
6937
+ xrelaySessionId = webSessionId + '/' + host;
6938
+ xrelaySession = webRelaySessions[xrelaySessionId];
6939
+ if (xrelaySession == null) {
6940
+ // We found an unused hostname, save this as it could be useful.
6941
+ if (freeRelayHost == null) { freeRelayHost = host; }
6942
} else {
6921
- // Check if this host is older then oldest so far
6922
- if (oldestRelayTime > xrelaySession.lastOperation) {
6943
+ // Check if we already have a relay session that matches exactly what we want
6944
+ if ((xrelaySession.domain.id == domain.id) && (xrelaySession.userid == userid) && (xrelaySession.nodeid == nodeid) && (xrelaySession.addr == addr) && (xrelaySession.port == port) && (xrelaySession.appid == appid)) {
6945
+ // We found an exact match, we are all setup already, redirect to root of that DNS name
6946
+ if (host == req.hostname) {
6947
+ // Request was made on the same host, redirect to root.
6948
+ res.redirect('/');
6949
+ } else {
6950
+ // Request was made to a different host
6951
+ const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
6952
+ res.redirect('https://' + host + ((httpport != 443) ? (':' + httpport) : '') + '/');
6953
+ }
6954
+ return;
6955
+ }
6956
+
6957
+ // Keep a record of the oldest web relay session, this could be useful.
6958
+ if (oldestRelayHost == null) {
6959
+ // Oldest host not set yet, set it
6960
oldestRelayHost = host;
6961
oldestRelayTime = xrelaySession.lastOperation;
6962
+ } else {
6963
+ // Check if this host is older then oldest so far
6964
+ if (oldestRelayTime > xrelaySession.lastOperation) {
6965
+ oldestRelayHost = host;
6966
+ oldestRelayTime = xrelaySession.lastOperation;
6967
+ }
6968
}
6969
}
6970
}
6928
- }
6929
-
6930
- // Check that the user has rights to access this device
6931
- parent.webserver.GetNodeWithRights(domain, userid, nodeid, function (node, rights, visible) {
6932
- // If there is no remote control or relay rights, reject this web relay
6933
- if ((rights & 0x00200008) == 0) { res.sendStatus(404); return; } // MESHRIGHT_REMOTECONTROL or MESHRIGHT_RELAY
6934
-
6935
- // Check if there is a free relay DNS name we can use
6936
- var selectedHost = null;
6937
- if (freeRelayHost != null) {
6938
- // There is a free one, use it.
6939
- selectedHost = freeRelayHost;
6940
- } else {
6941
- // No free ones, close the oldest one
6942
- selectedHost = oldestRelayHost;
6943
- }
6944
- xrelaySessionId = webSessionId + '/' + selectedHost;
6945
-
6946
- if (selectedHost == req.hostname) {
6947
- // If this web relay session id is not free, close it now
6948
- xrelaySession = webRelaySessions[xrelaySessionId];
6949
- if (xrelaySession != null) { xrelaySession.close(); delete webRelaySessions[xrelaySessionId]; }
6950
-
6951
- // Create a web relay session
6952
- const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySessionId, expire, node.mtype);
6953
- relaySession.xpublicid = publicid;
6954
- relaySession.onclose = function (sessionId) {
6955
- // Remove the relay session
6956
- delete webRelaySessions[sessionId];
6957
- // If there are not more relay sessions, clear the cleanup timer
6958
- if ((Object.keys(webRelaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(webRelayCleanupTimer); obj.cleanupTimer = null; }
6971
+
6972
+ // Check that the user has rights to access this device
6973
+ parent.webserver.GetNodeWithRights(domain, userid, nodeid, function (node, rights, visible) {
6974
+ // If there is no remote control or relay rights, reject this web relay
6975
+ if ((rights & 0x00200008) == 0) { res.sendStatus(404); return; } // MESHRIGHT_REMOTECONTROL or MESHRIGHT_RELAY
6976
+
6977
+ // Check if there is a free relay DNS name we can use
6978
+ var selectedHost = null;
6979
+ if (freeRelayHost != null) {
6980
+ // There is a free one, use it.
6981
+ selectedHost = freeRelayHost;
6982
+ } else {
6983
+ // No free ones, close the oldest one
6984
+ selectedHost = oldestRelayHost;
6985
}
6960
-
6961
- // Set the multi-tunnel session
6962
- webRelaySessions[xrelaySessionId] = relaySession;
6963
-
6964
- // Setup the cleanup timer if needed
6965
- if (obj.cleanupTimer == null) { webRelayCleanupTimer = setInterval(checkWebRelaySessionsTimeout, 10000); }
6966
-
6967
- // Redirect to root.
6968
- res.redirect('/');
6969
- } else {
6970
- if (req.query.noredirect != null) {
6971
- // No redirects allowed, fail here. This is important to make sure there is no redirect cascades
6972
- res.sendStatus(404);
6986
+ xrelaySessionId = webSessionId + '/' + selectedHost;
6987
+
6988
+ if (selectedHost == req.hostname) {
6989
+ // If this web relay session id is not free, close it now
6990
+ xrelaySession = webRelaySessions[xrelaySessionId];
6991
+ if (xrelaySession != null) { xrelaySession.close(); delete webRelaySessions[xrelaySessionId]; }
6992
+
6993
+ // Create a web relay session
6994
+ const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySessionId, expire, node.mtype);
6995
+ relaySession.xpublicid = publicid;
6996
+ relaySession.onclose = function (sessionId) {
6997
+ // Remove the relay session
6998
+ delete webRelaySessions[sessionId];
6999
+ // If there are not more relay sessions, clear the cleanup timer
7000
+ if ((Object.keys(webRelaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(webRelayCleanupTimer); obj.cleanupTimer = null; }
7001
+ }
7002
+
7003
+ // Set the multi-tunnel session
7004
+ webRelaySessions[xrelaySessionId] = relaySession;
7005
+
7006
+ // Setup the cleanup timer if needed
7007
+ if (obj.cleanupTimer == null) { webRelayCleanupTimer = setInterval(checkWebRelaySessionsTimeout, 10000); }
7008
+
7009
+ // Redirect to root.
7010
+ res.redirect('/');
7011
} else {
6974
- // Request was made to a different host, redirect using the full URL so an HTTP cookie can be created on the other DNS name.
6975
- const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
6976
- res.redirect('https://' + selectedHost + ((httpport != 443) ? (':' + httpport) : '') + req.url + '&noredirect=1');
7012
+ if (req.query.noredirect != null) {
7013
+ // No redirects allowed, fail here. This is important to make sure there is no redirect cascades
7014
+ res.sendStatus(404);
7015
+ } else {
7016
+ // Request was made to a different host, redirect using the full URL so an HTTP cookie can be created on the other DNS name.
7017
+ const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
7018
+ res.redirect('https://' + selectedHost + ((httpport != 443) ? (':' + httpport) : '') + req.url + '&noredirect=1');
7019
+ }
7020
}
6978
- }
7021
+ });
7022
});
7023
+
7024
+ // Handle all incoming requests as web relays
7025
+ obj.webRelayRouter.get('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
7026
+
7027
+ // Handle all incoming requests as web relays
7028
+ obj.webRelayRouter.post('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
7029
+
7030
+ // Handle all incoming requests as web relays
7031
+ obj.webRelayRouter.put('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
7032
+
7033
+ // Handle all incoming requests as web relays
7034
+ obj.webRelayRouter.delete('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
7035
+
7036
+ // Handle all incoming requests as web relays
7037
+ obj.webRelayRouter.options('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
7038
+
7039
+ // Handle all incoming requests as web relays
7040
+ obj.webRelayRouter.head('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
7041
+ }
7042
+
7043
+ // Indicates to ExpressJS that the override public folder should be used to serve static files.
7044
+ if (parent.config.domains[i].webpublicpath != null) {
7045
+ // Use domain public path
7046
+ obj.app.use(url, obj.express.static(parent.config.domains[i].webpublicpath));
7047
+ } else if (obj.parent.webPublicOverridePath != null) {
7048
+ // Use override path
7049
+ obj.app.use(url, obj.express.static(obj.parent.webPublicOverridePath));
7050
+ }
7051
+
7052
+ // Indicates to ExpressJS that the default public folder should be used to serve static files.
7053
+ obj.app.use(url, obj.express.static(obj.parent.webPublicPath));
7054
+
7055
+ // Start regular disconnection list flush every 2 minutes.
7056
+ obj.wsagentsDisconnectionsTimer = setInterval(function () { obj.wsagentsDisconnections = {}; }, 120000);
7057
+ }
7058
+ }
7059
+ function finalizeWebserver() {
7060
+ // Setup all HTTP handlers
7061
+ setupHTTPHandlers()
7062
+
7063
+ // Handle 404 error
7064
+ if (obj.args.nice404 !== false) {
7065
+ obj.app.use(function (req, res, next) {
7066
+ parent.debug('web', '404 Error ' + req.url);
7067
+ var domain = getDomain(req);
7068
+ if ((domain == null) || (domain.auth == 'sspi')) { res.sendStatus(404); return; }
7069
+ if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL
7070
+ const cspNonce = obj.crypto.randomBytes(15).toString('base64');
7071
+ 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
7072
+ res.status(404).render(getRenderPage((domain.sitestyle == 2) ? 'error4042' : 'error404', req, domain), getRenderArgs({ cspNonce: cspNonce }, req, domain));
7073
});
6981
-
6982
- // Handle all incoming requests as web relays
6983
- obj.webRelayRouter.get('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
6984
-
6985
- // Handle all incoming requests as web relays
6986
- obj.webRelayRouter.post('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
6987
-
6988
- // Handle all incoming requests as web relays
6989
- obj.webRelayRouter.put('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
6990
-
6991
- // Handle all incoming requests as web relays
6992
- obj.webRelayRouter.delete('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
6993
-
6994
- // Handle all incoming requests as web relays
6995
- obj.webRelayRouter.options('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
6996
-
6997
- // Handle all incoming requests as web relays
6998
- obj.webRelayRouter.head('/*', function (req, res) { try { handleWebRelayRequest(req, res); } catch (ex) { console.log(ex); } })
6999
- }
7000
-
7001
- // Indicates to ExpressJS that the override public folder should be used to serve static files.
7002
- if (parent.config.domains[i].webpublicpath != null) {
7003
- // Use domain public path
7004
- obj.app.use(url, obj.express.static(parent.config.domains[i].webpublicpath));
7005
- } else if (obj.parent.webPublicOverridePath != null) {
7006
- // Use override path
7007
- obj.app.use(url, obj.express.static(obj.parent.webPublicOverridePath));
7074
}
7075
7010
- // Indicates to ExpressJS that the default public folder should be used to serve static files.
7011
- obj.app.use(url, obj.express.static(obj.parent.webPublicPath));
7076
+ // Start server on a free port.
7077
+ CheckListenPort(obj.args.port, obj.args.portbind, StartWebServer);
7078
7013
- // Start regular disconnection list flush every 2 minutes.
7014
- obj.wsagentsDisconnectionsTimer = setInterval(function () { obj.wsagentsDisconnections = {}; }, 120000);
7015
- }
7079
+ // Start on a second agent-only alternative port if needed.
7080
+ if (obj.args.agentport) { CheckListenPort(obj.args.agentport, obj.args.agentportbind, StartAltWebServer); }
7081
7017
- // Handle 404 error
7018
- if (obj.args.nice404 !== false) {
7019
- obj.app.use(function (req, res, next) {
7020
- parent.debug('web', '404 Error ' + req.url);
7021
- var domain = getDomain(req);
7022
- if ((domain == null) || (domain.auth == 'sspi')) { res.sendStatus(404); return; }
7023
- if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL
7024
- const cspNonce = obj.crypto.randomBytes(15).toString('base64');
7025
- 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
7026
- res.status(404).render(getRenderPage((domain.sitestyle == 2) ? 'error4042' : 'error404', req, domain), getRenderArgs({ cspNonce: cspNonce }, req, domain));
7027
- });
7082
+ // We are done starting the web server.
7083
+ if (doneFunc) doneFunc();
7084
}
7029
-
7030
- // Start server on a free port.
7031
- CheckListenPort(obj.args.port, obj.args.portbind, StartWebServer);
7032
-
7033
- // Start on a second agent-only alternative port if needed.
7034
- if (obj.args.agentport) { CheckListenPort(obj.args.agentport, obj.args.agentportbind, StartAltWebServer); }
7035
-
7036
- // We are done starting the web server.
7037
- if (doneFunc) doneFunc();
7085
}
7086
7087
// Auth strategy flags
7098
}
7099
7100
// Setup auth strategies for a domain
7054
- function setupDomainAuthStrategy(domain) {
7055
- // Return the auth strategies that have been setup
7056
- var authStrategyFlags = 0;
7101
+ async function setupDomainAuthStrategy(domain) {
7102
+ // Return binary flags representing all auth strategies that have been setup
7103
+ let authStrategyFlags = 0;
7104
7105
// Setup auth strategies using passport if needed
7106
if (typeof domain.authstrategies != 'object') return authStrategyFlags;
7107
7061
- const url = domain.url;
7108
+ const url = domain.url
7109
const passport = domain.passport = require('passport');
7110
passport.serializeUser(function (user, done) { done(null, user.sid); });
7111
passport.deserializeUser(function (sid, done) { done(null, { sid: sid }); });
7114
// Twitter
7115
if ((typeof domain.authstrategies.twitter == 'object') && (typeof domain.authstrategies.twitter.clientid == 'string') && (typeof domain.authstrategies.twitter.clientsecret == 'string')) {
7116
const TwitterStrategy = require('passport-twitter');
7070
- var options = { consumerKey: domain.authstrategies.twitter.clientid, consumerSecret: domain.authstrategies.twitter.clientsecret };
7117
+ let options = { consumerKey: domain.authstrategies.twitter.clientid, consumerSecret: domain.authstrategies.twitter.clientsecret };
7118
if (typeof domain.authstrategies.twitter.callbackurl == 'string') { options.callbackURL = domain.authstrategies.twitter.callbackurl; } else { options.callbackURL = url + 'auth-twitter-callback'; }
7072
- parent.debug('authlog', 'Adding Twitter SSO with options: ' + JSON.stringify(options));
7119
+ parent.authLog('setupDomainAuthStrategy', 'Adding Twitter SSO with options: ' + JSON.stringify(options));
7120
passport.use('twitter-' + domain.id, new TwitterStrategy(options,
7121
function (token, tokenSecret, profile, cb) {
7075
- parent.debug('authlog', 'Twitter profile: ' + JSON.stringify(profile));
7122
+ parent.authLog('setupDomainAuthStrategy', 'Twitter profile: ' + JSON.stringify(profile));
7123
var user = { sid: '~twitter:' + profile.id, name: profile.displayName, strategy: 'twitter' };
7124
if ((typeof profile.emails == 'object') && (profile.emails[0] != null) && (typeof profile.emails[0].value == 'string')) { user.email = profile.emails[0].value; }
7125
return cb(null, user);
7131
// Google
7132
if ((typeof domain.authstrategies.google == 'object') && (typeof domain.authstrategies.google.clientid == 'string') && (typeof domain.authstrategies.google.clientsecret == 'string')) {
7133
const GoogleStrategy = require('passport-google-oauth20');
7087
- var options = { clientID: domain.authstrategies.google.clientid, clientSecret: domain.authstrategies.google.clientsecret };
7134
+ let options = { clientID: domain.authstrategies.google.clientid, clientSecret: domain.authstrategies.google.clientsecret };
7135
if (typeof domain.authstrategies.google.callbackurl == 'string') { options.callbackURL = domain.authstrategies.google.callbackurl; } else { options.callbackURL = url + 'auth-google-callback'; }
7089
- parent.debug('authlog', 'Adding Google SSO with options: ' + JSON.stringify(options));
7136
+ parent.authLog('setupDomainAuthStrategy', 'Adding Google SSO with options: ' + JSON.stringify(options));
7137
passport.use('google-' + domain.id, new GoogleStrategy(options,
7138
function (token, tokenSecret, profile, cb) {
7092
- parent.debug('authlog', 'Google profile: ' + JSON.stringify(profile));
7139
+ parent.authLog('setupDomainAuthStrategy', 'Google profile: ' + JSON.stringify(profile));
7140
var user = { sid: '~google:' + profile.id, name: profile.displayName, strategy: 'google' };
7141
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; }
7142
return cb(null, user);
7148
// Github
7149
if ((typeof domain.authstrategies.github == 'object') && (typeof domain.authstrategies.github.clientid == 'string') && (typeof domain.authstrategies.github.clientsecret == 'string')) {
7150
const GitHubStrategy = require('passport-github2');
7104
- var options = { clientID: domain.authstrategies.github.clientid, clientSecret: domain.authstrategies.github.clientsecret };
7151
+ let options = { clientID: domain.authstrategies.github.clientid, clientSecret: domain.authstrategies.github.clientsecret };
7152
if (typeof domain.authstrategies.github.callbackurl == 'string') { options.callbackURL = domain.authstrategies.github.callbackurl; } else { options.callbackURL = url + 'auth-github-callback'; }
7106
- parent.debug('authlog', 'Adding Github SSO with options: ' + JSON.stringify(options));
7153
+ parent.authLog('setupDomainAuthStrategy', 'Adding Github SSO with options: ' + JSON.stringify(options));
7154
passport.use('github-' + domain.id, new GitHubStrategy(options,
7155
function (token, tokenSecret, profile, cb) {
7109
- parent.debug('authlog', 'Github profile: ' + JSON.stringify(profile));
7156
+ parent.authLog('setupDomainAuthStrategy', 'Github profile: ' + JSON.stringify(profile));
7157
var user = { sid: '~github:' + profile.id, name: profile.displayName, strategy: 'github' };
7158
if ((typeof profile.emails == 'object') && (profile.emails[0] != null) && (typeof profile.emails[0].value == 'string')) { user.email = profile.emails[0].value; }
7159
return cb(null, user);
7165
// Reddit
7166
if ((typeof domain.authstrategies.reddit == 'object') && (typeof domain.authstrategies.reddit.clientid == 'string') && (typeof domain.authstrategies.reddit.clientsecret == 'string')) {
7167
const RedditStrategy = require('passport-reddit');
7121
- var options = { clientID: domain.authstrategies.reddit.clientid, clientSecret: domain.authstrategies.reddit.clientsecret };
7168
+ let options = { clientID: domain.authstrategies.reddit.clientid, clientSecret: domain.authstrategies.reddit.clientsecret };
7169
if (typeof domain.authstrategies.reddit.callbackurl == 'string') { options.callbackURL = domain.authstrategies.reddit.callbackurl; } else { options.callbackURL = url + 'auth-reddit-callback'; }
7123
- parent.debug('authlog', 'Adding Reddit SSO with options: ' + JSON.stringify(options));
7170
+ parent.authLog('setupDomainAuthStrategy', 'Adding Reddit SSO with options: ' + JSON.stringify(options));
7171
passport.use('reddit-' + domain.id, new RedditStrategy.Strategy(options,
7172
function (token, tokenSecret, profile, cb) {
7126
- parent.debug('authlog', 'Reddit profile: ' + JSON.stringify(profile));
7173
+ parent.authLog('setupDomainAuthStrategy', 'Reddit profile: ' + JSON.stringify(profile));
7174
var user = { sid: '~reddit:' + profile.id, name: profile.name, strategy: 'reddit' };
7175
if ((typeof profile.emails == 'object') && (profile.emails[0] != null) && (typeof profile.emails[0].value == 'string')) { user.email = profile.emails[0].value; }
7176
return cb(null, user);
7182
// Azure
7183
if ((typeof domain.authstrategies.azure == 'object') && (typeof domain.authstrategies.azure.clientid == 'string') && (typeof domain.authstrategies.azure.clientsecret == 'string')) {
7184
const AzureOAuth2Strategy = require('passport-azure-oauth2');
7138
- var options = { clientID: domain.authstrategies.azure.clientid, clientSecret: domain.authstrategies.azure.clientsecret, tenant: domain.authstrategies.azure.tenantid };
7185
+ let options = { clientID: domain.authstrategies.azure.clientid, clientSecret: domain.authstrategies.azure.clientsecret, tenant: domain.authstrategies.azure.tenantid };
7186
if (typeof domain.authstrategies.azure.callbackurl == 'string') { options.callbackURL = domain.authstrategies.azure.callbackurl; } else { options.callbackURL = url + 'auth-azure-callback'; }
7140
- parent.debug('authlog', 'Adding Azure SSO with options: ' + JSON.stringify(options));
7187
+ parent.authLog('setupDomainAuthStrategy', 'Adding Azure SSO with options: ' + JSON.stringify(options));
7188
passport.use('azure-' + domain.id, new AzureOAuth2Strategy(options,
7189
function (accessToken, refreshtoken, params, profile, done) {
7190
var userex = null;
7191
try { userex = require('jwt-simple').decode(params.id_token, '', true); } catch (ex) { }
7145
- parent.debug('authlog', 'Azure profile: ' + JSON.stringify(userex));
7192
+ parent.authLog('setupDomainAuthStrategy', 'Azure profile: ' + JSON.stringify(userex));
7193
var user = null;
7194
if (userex != null) {
7195
var user = { sid: '~azure:' + userex.unique_name, name: userex.name, strategy: 'azure' };
7201
authStrategyFlags |= domainAuthStrategyConsts.azure;
7202
}
7203
7157
- // Generic OpenID Connect
7158
- if ((typeof domain.authstrategies.oidc == 'object') && (typeof domain.authstrategies.oidc.clientid == 'string') && (typeof domain.authstrategies.oidc.clientsecret == 'string') && (typeof domain.authstrategies.oidc.issuer == 'string')) {
7159
- const OIDCStrategy = require('@mstrhakr/passport-openidconnect');
7160
- const options = {
7161
- issuer: domain.authstrategies.oidc.issuer,
7162
- clientID: domain.authstrategies.oidc.clientid,
7163
- clientSecret: domain.authstrategies.oidc.clientsecret,
7164
- scope: ['profile', 'email'],
7165
- };
7166
- if (typeof domain.authstrategies.oidc.authorizationurl == 'string') {options.authorizationURL = domain.authstrategies.oidc.authorizationurl; }
7167
- if (typeof domain.authstrategies.oidc.tokenurl == 'string') { options.tokenURL = domain.authstrategies.oidc.tokenurl; }
7168
- if (typeof domain.authstrategies.oidc.userinfourl == 'string') { options.userInfoURL = domain.authstrategies.oidc.userinfourl; }
7169
- if (typeof domain.authstrategies.oidc.callbackurl == 'string') { options.callbackURL = domain.authstrategies.oidc.callbackurl; }
7170
-
7171
- const discoverOptions = async function(options){
7172
- if ((typeof domain.authstrategies.oidc.authorizationurl != 'string') || (typeof domain.authstrategies.oidc.tokenurl != 'string') || (typeof domain.authstrategies.oidc.userinfourl != 'string')) {
7173
- const Issuer = require('openid-client').Issuer;
7174
- parent.debug('authlog', `OIDC: Attempting to discover well known endpoints for ${options.issuer}`);
7175
- var issuer = await Issuer.discover(options.issuer)
7176
- if (typeof domain.authstrategies.oidc.authorizationurl == 'string') { options.authorizationURL = domain.authstrategies.oidc.authorizationurl; } else { options.authorizationURL = issuer.metadata.authorization_endpoint; }
7177
- if (typeof domain.authstrategies.oidc.tokenurl == 'string') { options.tokenURL = domain.authstrategies.oidc.tokenurl; } else { options.tokenURL = issuer.metadata.token_endpoint; }
7178
- if (typeof domain.authstrategies.oidc.userinfourl == 'string') { options.userInfoURL = domain.authstrategies.oidc.userinfourl; } else { options.userInfoURL = issuer.metadata.userinfo_endpoint; }
7179
- if (typeof domain.authstrategies.oidc.callbackurl == 'string') { options.callbackURL = domain.authstrategies.oidc.callbackurl; } else { options.callbackURL = url + 'oidc-callback'; }
7180
- parent.debug('authlog', 'OIDC: Discovered: ' + JSON.stringify(options, null, 4));
7181
- }
7182
- return options;
7183
- }
7184
- if (typeof domain.authstrategies.oidc.groups == 'object') { options.scope.push('groups') }
7185
- discoverOptions(options).then(function(options) {
7186
- passport.use('oidc-' + domain.id, new OIDCStrategy.Strategy(options,
7187
- function verify(issuer, profile, verified) {
7188
- parent.debug('authlog', `OIDC: Connecting to ${issuer} with the following options ` + JSON.stringify(options, null, 4));
7189
- var user = { sid: '~oidc:' + profile.id, name: profile.displayName, strategy: 'oidc' };
7190
- if (typeof profile.emails == 'object') { if (typeof profile.emails[0].value == 'string') { user.email = profile.emails[0].value; } else { user.email = profile.emails[0].value[0]; } } else if (typeof profile.emails == 'string') { user.email = profile.emails; }
7191
- if (options.scope.indexOf('groups') >= 0) { if ( Array.isArray(profile.groups[0].value) ) { user.groups = profile.groups[0].value; } else { user.groups = [profile.groups[0].value]; } }
7192
- parent.debug('authlog', `oidc: Configured:\nUser: ${JSON.stringify(user, null, 4)}\nFROM\nProfile: ${JSON.stringify(profile, null, 4)}`);
7193
- return verified(null, user);
7194
- }
7195
- ))
7196
- });
7197
- authStrategyFlags |= domainAuthStrategyConsts.oidc;
7198
- }
7199
-
7204
// Generic SAML
7205
if (typeof domain.authstrategies.saml == 'object') {
7206
if ((typeof domain.authstrategies.saml.cert != 'string') || (typeof domain.authstrategies.saml.idpurl != 'string')) {
7203
- console.log('ERROR: Missing SAML configuration.');
7207
+ parent.debug('error', 'Missing SAML configuration.');
7208
} else {
7209
const certPath = obj.common.joinPath(obj.parent.datapath, domain.authstrategies.saml.cert);
7210
var cert = obj.fs.readFileSync(certPath);
7211
if (cert == null) {
7208
- console.log('ERROR: Unable to read SAML IdP certificate: ' + domain.authstrategies.saml.cert);
7212
+ parent.debug('error', 'Unable to read SAML IdP certificate: ' + domain.authstrategies.saml.cert);
7213
} else {
7214
var options = { entryPoint: domain.authstrategies.saml.idpurl, issuer: 'meshcentral' };
7215
if (typeof domain.authstrategies.saml.callbackurl == 'string') { options.callbackUrl = domain.authstrategies.saml.callbackurl; } else { options.callbackUrl = url + 'auth-saml-callback'; }
7216
if (domain.authstrategies.saml.disablerequestedauthncontext != null) { options.disableRequestedAuthnContext = domain.authstrategies.saml.disablerequestedauthncontext; }
7217
if (typeof domain.authstrategies.saml.entityid == 'string') { options.issuer = domain.authstrategies.saml.entityid; }
7214
- parent.debug('authlog', 'Adding SAML SSO with options: ' + JSON.stringify(options, null, 4));
7218
+ parent.authLog('setupDomainAuthStrategy', 'Adding SAML SSO with options: ' + JSON.stringify(options));
7219
options.cert = cert.toString().split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join('');
7220
const SamlStrategy = require('passport-saml').Strategy;
7221
passport.use('saml-' + domain.id, new SamlStrategy(options,
7222
function (profile, done) {
7219
- parent.debug('authlog', 'SAML profile: ' + JSON.stringify(profile, null, 4));
7223
+ parent.authLog('setupDomainAuthStrategy', 'SAML profile: ' + JSON.stringify(profile));
7224
if (typeof profile.nameID != 'string') { return done(); }
7225
var user = { sid: '~saml:' + profile.nameID, name: profile.nameID, strategy: 'saml' };
7226
if (typeof profile.displayname == 'string') {
7232
return done(null, user);
7233
}
7234
));
7231
- authStrategyFlags |= domainAuthStrategyConsts.saml;
7235
+ authStrategyFlags |= domainAuthStrategyConsts.saml
7236
}
7237
}
7238
}
7240
// Intel SAML
7241
if (typeof domain.authstrategies.intel == 'object') {
7242
if ((typeof domain.authstrategies.intel.cert != 'string') || (typeof domain.authstrategies.intel.idpurl != 'string')) {
7239
- console.log('ERROR: Missing Intel SAML configuration.');
7243
+ parent.debug('error', 'Missing Intel SAML configuration.');
7244
} else {
7245
var cert = obj.fs.readFileSync(obj.common.joinPath(obj.parent.datapath, domain.authstrategies.intel.cert));
7246
if (cert == null) {
7243
- console.log('ERROR: Unable to read Intel SAML IdP certificate: ' + domain.authstrategies.intel.cert);
7247
+ parent.debug('error', 'Unable to read Intel SAML IdP certificate: ' + domain.authstrategies.intel.cert);
7248
} else {
7249
var options = { entryPoint: domain.authstrategies.intel.idpurl, issuer: 'meshcentral' };
7250
if (typeof domain.authstrategies.intel.callbackurl == 'string') { options.callbackUrl = domain.authstrategies.intel.callbackurl; } else { options.callbackUrl = url + 'auth-intel-callback'; }
7251
if (domain.authstrategies.intel.disablerequestedauthncontext != null) { options.disableRequestedAuthnContext = domain.authstrategies.intel.disablerequestedauthncontext; }
7252
if (typeof domain.authstrategies.intel.entityid == 'string') { options.issuer = domain.authstrategies.intel.entityid; }
7249
- parent.debug('authlog', 'Adding Intel SSO with options: ' + JSON.stringify(options, null, 4));
7253
+ parent.authLog('setupDomainAuthStrategy', 'Adding Intel SSO with options: ' + JSON.stringify(options));
7254
options.cert = cert.toString().split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join('');
7255
const SamlStrategy = require('passport-saml').Strategy;
7256
passport.use('isaml-' + domain.id, new SamlStrategy(options,
7257
function (profile, done) {
7254
- parent.debug('authlog', 'Intel profile: ' + JSON.stringify(profile, null, 4));
7258
+ parent.authLog('setupDomainAuthStrategy', 'Intel profile: ' + JSON.stringify(profile));
7259
if (typeof profile.nameID != 'string') { return done(); }
7260
var user = { sid: '~intel:' + profile.nameID, name: profile.nameID, strategy: 'intel' };
7261
if ((typeof profile.firstname == 'string') && (typeof profile.lastname == 'string')) { user.name = profile.firstname + ' ' + profile.lastname; }
7265
return done(null, user);
7266
}
7267
));
7264
- authStrategyFlags |= domainAuthStrategyConsts.intelSaml;
7268
+ authStrategyFlags |= domainAuthStrategyConsts.intelSaml
7269
}
7270
}
7271
}
7273
// JumpCloud SAML
7274
if (typeof domain.authstrategies.jumpcloud == 'object') {
7275
if ((typeof domain.authstrategies.jumpcloud.cert != 'string') || (typeof domain.authstrategies.jumpcloud.idpurl != 'string')) {
7272
- console.log('ERROR: Missing JumpCloud SAML configuration.');
7276
+ parent.debug('error', 'Missing JumpCloud SAML configuration.');
7277
} else {
7278
var cert = obj.fs.readFileSync(obj.common.joinPath(obj.parent.datapath, domain.authstrategies.jumpcloud.cert));
7279
if (cert == null) {
7276
- console.log('ERROR: Unable to read JumpCloud IdP certificate: ' + domain.authstrategies.jumpcloud.cert);
7280
+ parent.debug('error', 'Unable to read JumpCloud IdP certificate: ' + domain.authstrategies.jumpcloud.cert);
7281
} else {
7282
var options = { entryPoint: domain.authstrategies.jumpcloud.idpurl, issuer: 'meshcentral' };
7283
if (typeof domain.authstrategies.jumpcloud.callbackurl == 'string') { options.callbackUrl = domain.authstrategies.jumpcloud.callbackurl; } else { options.callbackUrl = url + 'auth-jumpcloud-callback'; }
7284
if (typeof domain.authstrategies.jumpcloud.entityid == 'string') { options.issuer = domain.authstrategies.jumpcloud.entityid; }
7281
- parent.debug('authlog', 'Adding JumpCloud SSO with options: ' + JSON.stringify(options, null, 4));
7285
+ parent.authLog('setupDomainAuthStrategy', 'Adding JumpCloud SSO with options: ' + JSON.stringify(options));
7286
options.cert = cert.toString().split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join('');
7287
const SamlStrategy = require('passport-saml').Strategy;
7288
passport.use('jumpcloud-' + domain.id, new SamlStrategy(options,
7289
function (profile, done) {
7286
- parent.debug('authlog', 'JumpCloud profile: ' + JSON.stringify(profile, null, 4));
7290
+ parent.authLog('setupDomainAuthStrategy', 'JumpCloud profile: ' + JSON.stringify(profile));
7291
if (typeof profile.nameID != 'string') { return done(); }
7292
var user = { sid: '~jumpcloud:' + profile.nameID, name: profile.nameID, strategy: 'jumpcloud' };
7293
if ((typeof profile.firstname == 'string') && (typeof profile.lastname == 'string')) { user.name = profile.firstname + ' ' + profile.lastname; }
7295
return done(null, user);
7296
}
7297
));
7294
- authStrategyFlags |= domainAuthStrategyConsts.jumpCloudSaml;
7298
+ authStrategyFlags |= domainAuthStrategyConsts.jumpCloudSaml
7299
}
7300
}
7301
}
7302
7303
+ // Setup OpenID Connect Authentication Strategy
7304
+ if (obj.common.validateObject(domain.authstrategies.oidc)) {
7305
+ parent.authLog('setupDomainAuthStrategy', `OIDC: Setting up strategy for domain: ${domain.id}`);
7306
+ // Ensure required objects exist
7307
+ let initStrategy = domain.authstrategies.oidc
7308
+ if (typeof initStrategy.issuer == 'string') { initStrategy.issuer = { 'issuer': initStrategy.issuer } }
7309
+ let strategy = migrateOldConfigs(Object.assign({ 'client': {}, 'issuer': {}, 'options': {}, 'custom': {}, 'obj': { 'openidClient': require('openid-client') } }, initStrategy))
7310
+ let preset = obj.common.validateString(strategy.custom.preset) ? strategy.custom.preset : null
7311
+ if (!preset) {
7312
+ if (typeof strategy.custom.tenant_id == 'string') { strategy.custom.preset = preset = 'azure' }
7313
+ if (strategy.custom.customer_id || strategy.custom.identitysource || strategy.client.client_id.split('.')[2] == 'googleusercontent') { strategy.custom.preset = preset = 'google' }
7314
+ }
7315
+
7316
+ // Check issuer url
7317
+ let presetIssuer
7318
+ if (preset == 'azure') { presetIssuer = 'https://login.microsoftonline.com/' + strategy.custom.tenant_id + '/v2.0'; }
7319
+ if (preset == 'google') { presetIssuer = 'https://accounts.google.com'; }
7320
+ if (!obj.common.validateString(strategy.issuer.issuer)) {
7321
+ if (!preset) {
7322
+ let error = new Error('OIDC: Missing issuer URI.');
7323
+ parent.authLog('error', `${error.message} STRATEGY: ${JSON.stringify(strategy)}`);
7324
+ throw error;
7325
+ } else {
7326
+ strategy.issuer.issuer = presetIssuer
7327
+ parent.authLog('setupDomainAuthStrategy', `OIDC: PRESET: ${preset.toUpperCase()}: Using preset issuer: ${presetIssuer}`);
7328
+ }
7329
+ } else if ((typeof strategy.issuer.issuer == 'string') && (typeof strategy.custom.preset == 'string')) {
7330
+ let error = new Error(`OIDC: PRESET: ${strategy.custom.preset.toUpperCase()}: PRESET OVERRIDDEN: CONFIG ISSUER: ${strategy.issuer.issuer} PRESET ISSUER: ${presetIssuer}`);
7331
+ parent.authLog('setupDomainAuthStrategy', error.message);
7332
+ console.warn(error)
7333
+ }
7334
+
7335
+ // Setup Strategy Options
7336
+ strategy.custom.scope = obj.common.convertStrArray(strategy.custom.scope, ' ')
7337
+ if (strategy.custom.scope.length > 1) {
7338
+ strategy.options = Object.assign(strategy.options, { 'params': { 'scope': strategy.custom.scope } })
7339
+ } else {
7340
+ strategy.options = Object.assign(strategy.options, { 'params': { 'scope': ['openid', 'profile', 'email'] } })
7341
+ }
7342
+ if (typeof strategy.groups == 'object') {
7343
+ let groupScope = strategy.groups.scope || null
7344
+ if (groupScope == null) {
7345
+ if (preset == 'azure') { groupScope = 'Group.Read.All' }
7346
+ if (preset == 'google') { groupScope = 'https://www.googleapis.com/auth/cloud-identity.groups.readonly' }
7347
+ if (typeof preset != 'string') { groupScope = 'groups' }
7348
+ }
7349
+ strategy.options.params.scope.push(groupScope)
7350
+ }
7351
+ strategy.options.params.scope = strategy.options.params.scope.join(' ')
7352
+
7353
+ // Discover additional information if available, use endpoints from config if present
7354
+ let issuer
7355
+ try {
7356
+ parent.authLog('setupDomainAuthStrategy', `OIDC: Discovering Issuer Endpoints: ${strategy.issuer.issuer}`);
7357
+ issuer = await strategy.obj.openidClient.Issuer.discover(strategy.issuer.issuer);
7358
+ } catch (err) {
7359
+ let error = new Error('OIDC: Discovery failed.', { cause: err });
7360
+ parent.authLog('setupDomainAuthStrategy', `ERROR: ${JSON.stringify(error)} ISSUER_URI: ${strategy.issuer.issuer}`);
7361
+ throw error
7362
+ }
7363
+ if (Object.keys(strategy.issuer).length > 1) {
7364
+ parent.authLog('setupDomainAuthStrategy', `OIDC: Adding Issuer Metadata: ${JSON.stringify(strategy.issuer)}`);
7365
+ issuer = new strategy.obj.openidClient.Issuer(Object.assign(issuer?.metadata, strategy.issuer));
7366
+ }
7367
+ strategy.issuer = issuer?.metadata
7368
+ strategy.obj.issuer = issuer
7369
+
7370
+ // Make sure redirect_uri and post_logout_redirect_uri exist before continuing
7371
+ if (!strategy.client.redirect_uri) {
7372
+ strategy.client.redirect_uri = 'https://' + parent.config.settings.cert + url + 'auth-oidc-callback';
7373
+ }
7374
+ if (!strategy.client.post_logout_redirect_uri) {
7375
+ strategy.client.post_logout_redirect_uri = 'https://' + parent.config.settings.cert + url + 'login';
7376
+ }
7377
+
7378
+ // Create client and overwrite in options
7379
+ let client = new issuer.Client(strategy.client)
7380
+ strategy.options = Object.assign(strategy.options, { 'client': client });
7381
+ strategy.client = client.metadata
7382
+ strategy.obj.client = client
7383
+
7384
+ // Setup strategy and save configs for later
7385
+ passport.use('oidc-' + domain.id, new strategy.obj.openidClient.Strategy(strategy.options, oidcCallback));
7386
+ if (domain.dns == null) {
7387
+ parent.config.domains[''].authstrategies.oidc = strategy;
7388
+ } else if (typeof parent.config.domains[domain.id].authstrategies.oidc == 'object') {
7389
+ parent.config.domains[domain.id].authstrategies.oidc = strategy;
7390
+ }
7391
+ parent.debug('verbose', 'OIDC: Saved Configuration: ' + JSON.stringify(strategy));
7392
+ if (preset) { parent.authLog('setupDomainAuthStrategy', 'OIDC: ' + preset.toUpperCase() + ': Setup Complete'); }
7393
+ else { parent.authLog('setupDomainAuthStrategy', 'OIDC: Setup Complete'); }
7394
+
7395
+ authStrategyFlags |= domainAuthStrategyConsts.oidc
7396
+
7397
+ function migrateOldConfigs(strategy) {
7398
+ let oldConfigs = {
7399
+ 'client': {
7400
+ 'clientid': 'client_id',
7401
+ 'clientsecret': 'client_secret',
7402
+ 'callbackurl': 'redirect_uri'
7403
+ },
7404
+ 'issuer': {
7405
+ 'authorizationurl': 'authorization_endpoint',
7406
+ 'tokenurl': 'token_endpoint',
7407
+ 'userinfourl': 'userinfo_endpoint'
7408
+ },
7409
+ 'custom': {
7410
+ 'tenantid': 'tenant_id',
7411
+ 'customerid': 'customer_id'
7412
+ }
7413
+ }
7414
+ for (var type in oldConfigs) {
7415
+ for (const [key, value] of Object.entries(oldConfigs[type])) {
7416
+ if (Object.hasOwn(strategy, key)) {
7417
+ if (strategy[type][value] && obj.common.validateString(strategy[type][value])) {
7418
+ let error = new Error('OIDC: OLD CONFIG: Config conflict, new config overrides old config');
7419
+ parent.authLog('migrateOldConfigs', `${JSON.stringify(error)} OLD CONFIG: ${key}: ${strategy[key]} NEW CONFIG: ${value}:${strategy[type][value]}`);
7420
+ } else {
7421
+ parent.authLog('migrateOldConfigs', `OIDC: OLD CONFIG: Moving old config to new location. strategy.${key} => strategy.${type}.${value}`);
7422
+ strategy[type][value] = strategy[key];
7423
+ }
7424
+ delete strategy[key]
7425
+ }
7426
+ }
7427
+ }
7428
+ if (typeof strategy.scope == 'string') {
7429
+ if (!strategy.custom.scope) {
7430
+ strategy.custom.scope = strategy.scope;
7431
+ strategy.options.params = { 'scope': strategy.scope };
7432
+ parent.authLog('migrateOldConfigs', `OIDC: OLD CONFIG: Moving old config to new location. strategy.scope => strategy.custom.scope`);
7433
+ } else {
7434
+ let error = new Error('OIDC: OLD CONFIG: Config conflict, using new config values.');
7435
+ parent.authLog('migrateOldConfigs', `${error.message} OLD CONFIG: strategy.scope: ${strategy.scope} NEW CONFIG: strategy.custom.scope:${strategy.custom.scope}`);
7436
+ parent.debug('warning', error.message)
7437
+ }
7438
+ delete strategy.scope
7439
+ }
7440
+ return strategy
7441
+ }
7442
+
7443
+ // Callback function must be able to grab info from API's using the access token, would prefer to use the token here.
7444
+ function oidcCallback(tokenset, profile, verified) {
7445
+ // Initialize user object
7446
+ let user = { 'strategy': 'oidc' }
7447
+ let claims = obj.common.validateObject(strategy.custom.claims) ? strategy.custom.claims : null
7448
+ user.sid = obj.common.validateString(profile.sub) ? '~oidc:' + profile.sub : null
7449
+ user.name = obj.common.validateString(profile.name) ? profile.name : null
7450
+ user.email = obj.common.validateString(profile.email) ? profile.email : null
7451
+ if (claims != null) {
7452
+ user.sid = obj.common.validateString(profile[claims.uuid]) ? '~oidc:' + profile[claims.uuid] : user.sid
7453
+ user.name = obj.common.validateString(profile[claims.name]) ? profile[claims.name] : user.name
7454
+ user.email = obj.common.validateString(profile[claims.email]) ? profile[claims.email] : user.email
7455
+ }
7456
+ user.emailVerified = profile.email_verified ? profile.email_verified : obj.common.validateEmail(user.email),
7457
+ user.groups = obj.common.validateStrArray(profile.groups, 1) ? profile.groups : null
7458
+ user.preset = obj.common.validateString(strategy.custom.preset) ? strategy.custom.preset : null
7459
+ if (obj.common.validateString(strategy.groups.claim)) {
7460
+ user.groups = obj.common.validateStrArray(profile[strategy.groups.claim], 1) ? profile[strategy.groups.claim] : null
7461
+ }
7462
+
7463
+ // Setup end session enpoint if not already configured this requires an auth token
7464
+ try {
7465
+ if (!strategy.issuer.end_session_endpoint) {
7466
+ strategy.issuer.end_session_endpoint = strategy.obj.client.endSessionUrl({ 'id_token_hint': tokenset })
7467
+ }
7468
+ } catch (err) {
7469
+ let error = new Error('OIDC: Discovering end_session_endpoint failed. Using Default.', { cause: err });
7470
+ strategy.issuer.end_session_endpoint = strategy.issuer.issuer + '/logout';
7471
+ 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)}`);
7472
+ parent.authLog('oidcCallback', error.message);
7473
+ }
7474
+
7475
+ // Setup presets and groups, get groups from API if needed then return
7476
+ if (strategy.groups && typeof user.preset == 'string') {
7477
+ getGroups(user.preset, tokenset).then((groups) => {
7478
+ user = Object.assign(user, { 'groups': groups });
7479
+ return verified(null, user);
7480
+ }).catch((err) => {
7481
+ let error = new Error('OIDC: GROUPS: No groups found due to error:', { cause: err });
7482
+ parent.debug('error', `${JSON.stringify(error)}`);
7483
+ parent.authLog('oidcCallback', error.message);
7484
+ user.groups = [];
7485
+ return verified(null, user);
7486
+ });
7487
+ } else {
7488
+ return verified(null, user);
7489
+ }
7490
+
7491
+ async function getGroups(preset, tokenset) {
7492
+ let url = '';
7493
+ if (preset == 'azure') { url = strategy.groups.recursive == true ? 'https://graph.microsoft.com/v1.0/me/transitiveMemberOf' : 'https://graph.microsoft.com/v1.0/me/memberOf'; }
7494
+ 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; }
7495
+ return new Promise((resolve, reject) => {
7496
+ const options = {
7497
+ 'headers': { authorization: 'Bearer ' + tokenset.access_token }
7498
+ }
7499
+ const req = require('https').get(url, options, (res) => {
7500
+ let data = []
7501
+ res.on('data', (chunk) => {
7502
+ data.push(chunk);
7503
+ });
7504
+ res.on('end', () => {
7505
+ if (res.statusCode < 200 || res.statusCode >= 300) {
7506
+ let error = new Error('OIDC: GROUPS: Bad response code from API, statusCode: ' + res.statusCode);
7507
+ parent.authLog('getGroups', `ERROR: ${error.message} URL: ${url} OPTIONS: ${JSON.stringify(options)}`);
7508
+ console.error(error);
7509
+ reject(error);
7510
+ }
7511
+ if (data.length == 0) {
7512
+ let error = new Error('OIDC: GROUPS: Getting groups from API failed, request returned no data in response.');
7513
+ parent.authLog('getGroups', `ERROR: ${error.message} URL: ${url} OPTIONS: ${JSON.stringify(options)}`);
7514
+ console.error(error);
7515
+ reject(error);
7516
+ }
7517
+ try {
7518
+ if (Buffer.isBuffer(data[0])) {
7519
+ data = Buffer.concat(data);
7520
+ data = data.toString();
7521
+ } else { // else if (typeof data[0] == 'string')
7522
+ data = data.join();
7523
+ }
7524
+ } catch (err) {
7525
+ let error = new Error('OIDC: GROUPS: Getting groups from API failed. Error joining response data.', { cause: err });
7526
+ parent.authLog('getGroups', `ERROR: ${error.message} URL: ${url} OPTIONS: ${JSON.stringify(options)}`);
7527
+ console.error(error);
7528
+ reject(error);
7529
+ }
7530
+ if (preset == 'azure') {
7531
+ data = JSON.parse(data);
7532
+ if (data.error) {
7533
+ let error = new Error('OIDC: GROUPS: Getting groups from API failed. Error joining response data.', { cause: data.error });
7534
+ parent.authLog('getGroups', `ERROR: ${error.message} URL: ${url} OPTIONS: ${JSON.stringify(options)}`);
7535
+ console.error(error);
7536
+ reject(error);
7537
+ }
7538
+ data = data.value;
7539
+ }
7540
+ if (preset == 'google') {
7541
+ data = data.split('\n');
7542
+ data = data.join('');
7543
+ data = JSON.parse(data);
7544
+ data = data.groups;
7545
+ }
7546
+ let groups = []
7547
+ for (var i in data) {
7548
+ if (typeof data[i].displayName == 'string') {
7549
+ groups.push(data[i].displayName);
7550
+ }
7551
+ }
7552
+ if (groups.length == 0) {
7553
+ let warn = new Error('OIDC: GROUPS: No groups returned from API.');
7554
+ parent.authLog('getGroups', `WARN: ${warn.message} DATA: ${data}`);
7555
+ console.warn(warn);
7556
+ resolve(groups);
7557
+ } else {
7558
+ resolve(groups);
7559
+ }
7560
+ });
7561
+ });
7562
+ req.on('error', (err) => {
7563
+ let error = new Error('OIDC: GROUPS: Request error.', { cause: err });
7564
+ parent.authLog('getGroups', `ERROR: ${error.message} URL: ${url} OPTIONS: ${JSON.stringify(options)}`);
7565
+ console.error(error);
7566
+ reject(error);
7567
+ });
7568
+ req.end();
7569
+ });
7570
+ }
7571
+ }
7572
+ }
7573
return authStrategyFlags;
7574
}
7575
8097
});
8098
obj.parent.updateServerState('servername', certificates.CommonName);
8099
}
7826
- obj.parent.authLog('https', 'Server listening on ' + ((addr != null) ? addr : '0.0.0.0') + ' port ' + port + '.');
8100
+ obj.parent.debug('https', 'Server listening on ' + ((addr != null) ? addr : '0.0.0.0') + ' port ' + port + '.');
8101
obj.parent.updateServerState('https-port', port);
8102
if (args.aliasport != null) { obj.parent.updateServerState('https-aliasport', args.aliasport); }
8103
} else {
8136
} else {
8137
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) : '') + '.'); });
8138
}
7865
- obj.parent.authLog('https', 'Server listening on 0.0.0.0 port ' + port + '.');
8139
+ obj.parent.debug('https', 'Server listening on 0.0.0.0 port ' + port + '.');
8140
obj.parent.updateServerState('https-agent-port', port);
8141
} else {
8142
obj.tcpAltServer = obj.agentapp.listen(port, addr, function () { console.log('MeshCentral HTTP agent-only server running on port ' + port + ((agentAliasPort != null) ? (', alias port ' + agentAliasPort) : '') + '.'); });
8291
}
8292
};
8293
8020
- //
8021
- // Access Control Functions
8022
- //
8294
+ /* Access Control Functions */
8295
8296
// Remove user rights
8297
function removeUserRights(rights, user) {
9065
9066
}
9067
8796
- // Insure exclusivity of a push messaging token for Android device
9068
+ // Ensure exclusivity of a push messaging token for Android device
9069
obj.removePmtFromAllOtherNodes = function (node) {
9070
if (typeof node.pmt != 'string') return;
9071
db.Get('pmt_' + node.pmt, function (err, docs) {
9101
}
9102
9103
// Return decoded user agent information
8832
- obj.getUserAgentInfo = function(req) {
9104
+ obj.getUserAgentInfo = function (req) {
9105
var browser = 'Unknown', os = 'Unknown';
9106
try {
9107
const ua = obj.uaparser((typeof req == 'string') ? req : req.headers['user-agent']);
9426
parent.DispatchEvent(['*', ugrpid, user._id], obj, event); // Even if DB change stream is active, this event must be acted upon.
9427
9428
// Log in the auth log
9157
- parent.authLog('https', 'Created ' + userMembershipType + ' user group ' + ugrp.name);
9429
+ parent.authLog('https', userMembershipType.toUpperCase() + ': Created user group ' + ugrp.name);
9430
}
9431
9432
if (existingUserMemberships[ugrpid] == null) {
9453
parent.DispatchEvent(['*', ugrp._id, user._id], obj, event);
9454
9455
// Log in the auth log
9184
- parent.authLog('https', 'Adding ' + user.name + ' to ' + userMembershipType + ' user group ' + userMemberships[i] + '.');
9456
+ parent.authLog('https', userMembershipType.toUpperCase() + ': Adding ' + user.name + ' to user group ' + userMemberships[i] + '.');
9457
} else {
9458
// User is already part of this user group
9459
delete existingUserMemberships[ugrpid];
9463
// Remove the user from any memberships they don't belong to anymore
9464
for (var ugrpid in existingUserMemberships) {
9465
var ugrp = obj.userGroups[ugrpid];
9194
- parent.authLog('https', 'Removing ' + user.name + ' from ' + userMembershipType + ' user group ' + ugrp.name + '.');
9466
+ parent.authLog('https', userMembershipType.toUpperCase() + ': Removing ' + user.name + ' from user group ' + ugrp.name + '.');
9467
if ((user.links != null) && (user.links[ugrpid] != null)) {
9468
delete user.links[ugrpid];
9469