More work on OAuth support.

Ylian Saint-Hilaire committed May 14, 2020 at 15:09 UTC 87b4fc8811465551eea1b85b396b9be136f994c6
4 files changed +83 -16
public/styles/style.css
+15
@@ -803,6 +803,21 @@ NoMeshesPanel img {
803 background-color:#44F;
804 }
805
806 +.userAuthStrategyLogo {
807 + text-align:center;
808 + position:absolute;
809 + right:20px;
810 + top:140px;
811 + width:64px;
812 + height:64px;
813 + color:#FFF;
814 + padding:2px;
815 + background-color:#FFF;
816 + border-radius:20px;
817 + box-shadow: 2px 2px 10px black;
818 + z-index:1;
819 +}
820 +
821 #xdevices {
822 max-height: calc(100vh - 242px);
823 overflow-y: auto;
sample-config.json
+23
@@ -157,6 +157,29 @@
157 "_maxRecordingSizeMegabytes": 3,
158 "__protocols__": "Is an array: 1 = Terminal, 2 = Desktop, 5 = Files, 100 = Intel AMT WSMAN, 101 = Intel AMT Redirection",
159 "protocols": [ 1, 2, 101 ]
160 + },
161 + "_authStrategies": {
162 + "__comment__" : "This section is used to allow users to login using other accounts. You will need to get an API key from the services and register callback URL's",
163 + "twitter": {
164 + "__callbackurl": "https://server/auth-twitter-callback",
165 + "apikey": "xxxxxxxxxxxxxxxxxxxxxxx",
166 + "apisecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
167 + },
168 + "google": {
169 + "__callbackurl": "https://server/auth-google-callback",
170 + "clientid": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
171 + "clientsecret": "xxxxxxxxxxxxxxxxxxxxxxx"
172 + },
173 + "github": {
174 + "__callbackurl": "https://server/auth-github-callback",
175 + "clientid": "xxxxxxxxxxxxxxxxxxxxxxx",
176 + "clientsecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
177 + },
178 + "reddit": {
179 + "__callbackurl": "https://server/auth-reddit-callback",
180 + "clientid": "xxxxxxxxxxxxxxxxxxxxxxx",
181 + "clientsecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
182 + }
183 }
184 },
185 "_customer1": {
views/default.handlebars
+19 -9
@@ -885,7 +885,8 @@
885 <div id=p30html></div>
886 </td>
887 <td style=width:20px></td>
888 - <td style=width:200px valign=top>
888 + <td style=width:200px;position:relative valign=top>
889 + <img id="p30userAuthServiceLogo" loading="lazy" style="display:none" class=userAuthStrategyLogo src=images/login/reddit64.png width=64 height=64>
890 <picture id=MainUserImage style=border-width:0px;height:200px;width:200px;float:right>
891 <source type="image/webp" width=200 height=200 srcset="images/webp/user-256.webp" />
892 <img alt="" width=200 height=200 src=images/user-256.png />
@@ -9928,9 +9929,9 @@
9929 if ((users == null) || ((features & 4) != 0)) { QH('p3users', ''); return; }
9930
9931 // Sort the list of user id's
9931 - var sortedUserIds = [], maxUsers = 100, hiddenUsers = 0;
9932 - for (var i in users) { sortedUserIds.push(i); }
9933 - sortedUserIds.sort();
9932 + var sortedUsers = [], maxUsers = 100, hiddenUsers = 0;
9933 + for (var i in users) { sortedUsers.push(users[i]); }
9934 + sortedUsers.sort(nameSort);
9935
9936 // Get search
9937 var userSearch = Q('UserSearchInput').value.toLowerCase();
@@ -9949,8 +9950,8 @@
9950 for (var i=0;i<elements.length;i++) { if (elements[i].checked) { checkedUserids.push(elements[i].value); } }
9951
9952 // Online users
9952 - for (var i in sortedUserIds) {
9953 - var user = users[sortedUserIds[i]], sessions = null;
9953 + for (var i in sortedUsers) {
9954 + var user = sortedUsers[i], sessions = null;
9955 if (wssessions != null) { sessions = wssessions[user._id]; }
9956 if ((sessions != null) &&
9957 ((userSearch != null) && ((userSearch == '') || (user.name.toLowerCase().indexOf(userSearch) >= 0)) ||
@@ -9967,8 +9968,8 @@
9968 }
9969 addHeader = true;
9970 // Offline users
9970 - for (var i in sortedUserIds) {
9971 - var user = users[sortedUserIds[i]], sessions = null;
9971 + for (var i in sortedUsers) {
9972 + var user = sortedUsers[i], sessions = null;
9973 if (wssessions != null) { sessions = wssessions[user._id]; }
9974 if ((sessions == null) &&
9975 ((userSearch != null) && ((userSearch == '') || (user.name.toLowerCase().indexOf(userSearch) >= 0)) ||
@@ -10859,6 +10860,15 @@
10860 Q('MainUserImage').classList.remove('gray');
10861 if (activeSessions == 0) { Q('MainUserImage').classList.add('gray'); }
10862
10863 + // Add user auth strategy
10864 + var shortuserid = user._id.split('/')[2];
10865 + if (shortuserid.startsWith('~twitter:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/twitter64.png';; }
10866 + else if (shortuserid.startsWith('~google:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/google64.png';; }
10867 + else if (shortuserid.startsWith('~github:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/github64.png';; }
10868 + else if (shortuserid.startsWith('~reddit:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/reddit64.png';; }
10869 + else if (shortuserid.startsWith('~intel:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/intel64.png';; }
10870 + else { QV('p30userAuthServiceLogo', false); }
10871 +
10872 // Server permissions
10873 var msg = [], premsg = '';
10874 if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { premsg = '<img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" /> '; msg.push("Locked account"); }
@@ -11979,7 +11989,7 @@
11989 if ((xxcurrentView == 11) && (desktop != null) && (desktop.m.recordedData != null)) { deskRecordSession(); }
11990
11991 // If we are trying to go to "My Users" and we are not a user manager, move to recordings
11982 - if ((x == 4) && (users == null) || ((features & 4) != 0)) { x = 52; }
11992 + if (((x == 4) && ((userinfo.siteadmin & 2) == 0)) || ((features & 4) != 0)) { x = 52; }
11993
11994 // Edit this line when adding a new screen
11995 for (var i = 0; i < 53; i++) { QV('p' + i, i == x); }
webserver.js
+26 -7
@@ -4043,6 +4043,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4043 passport.serializeUser(function (user, done) { done(null, user.id); });
4044 passport.deserializeUser(function (id, done) { done(null, { id: id }); });
4045 obj.app.use(passport.initialize());
4046 + //obj.app.use(passport.session());
4047
4048 // Twitter
4049 if ((typeof domain.authstrategies.twitter == 'object') && (typeof domain.authstrategies.twitter.apikey == 'string') && (typeof domain.authstrategies.twitter.apisecret == 'string')) {
@@ -4055,7 +4056,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4056 }
4057 ));
4058 obj.app.get(url + 'auth-twitter', domain.passport.authenticate('twitter'));
4058 - obj.app.get(url + 'auth-twitter-callback', domain.passport.authenticate('twitter', { failureRedirect: '/' }), handleStrategyLogin);
4059 + obj.app.get(url + 'auth-twitter-callback', function (req, res, next) {
4060 + if ((Object.keys(req.session).length == 0) && (req.query.nmr == null)) {
4061 + // This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
4062 + var url = req.url;
4063 + if (url.indexOf('?') >= 0) { url += '&nmr=1'; } else { url += '?nmr=1'; } // Add this to the URL to prevent redirect loop.
4064 + res.set('Content-Type', 'text/html');
4065 + res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
4066 + } else {
4067 + domain.passport.authenticate('twitter', { failureRedirect: '/' })(req, res, next);
4068 + }
4069 + }, handleStrategyLogin);
4070 }
4071
4072 // Google
@@ -4096,19 +4107,27 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4107 return cb(null, user);
4108 }
4109 ));
4099 - obj.app.get(url + 'auth-reddit', function(req, res, next) {
4100 - domain.passport.authenticate('reddit', { state: 'rcookie', duration: 'permanent' })(req, res, next); // TODO: Replace 'rcookie' with a time-limited cookie
4110 + obj.app.get(url + 'auth-reddit', function (req, res, next) {
4111 + req.session.rstate = obj.crypto.randomBytes(32).toString('hex');
4112 + domain.passport.authenticate('reddit', { state: req.session.rstate, duration: 'permanent' })(req, res, next); // TODO: Replace 'rcookie' with a time-limited cookie
4113 });
4102 - obj.app.get(url + 'auth-reddit-callback',
4103 - function(req, res, next) {
4104 - if (req.query.state == 'rcookie') {
4114 + obj.app.get(url + 'auth-reddit-callback', function (req, res, next) {
4115 + if ((Object.keys(req.session).length == 0) && (req.query.nmr == null)) {
4116 + // This is an empty session likely due to the 302 redirection, redirect again (this is a bit of a hack).
4117 + var url = req.url;
4118 + if (url.indexOf('?') >= 0) { url += '&nmr=1'; } else { url += '?nmr=1'; } // Add this to the URL to prevent redirect loop.
4119 + res.set('Content-Type', 'text/html');
4120 + res.end('<html><head><meta http-equiv="refresh" content=0;url="' + url + '"></head><body></body></html>');
4121 + } else {
4122 + if (req.query.state == req.session.rstate) {
4123 delete req.session.rstate;
4124 domain.passport.authenticate('reddit', { failureRedirect: '/' })(req, res, next);
4125 } else {
4126 delete req.session.rstate;
4127 next(new Error(403));
4128 }
4111 - }, handleStrategyLogin);
4129 + }
4130 + }, handleStrategyLogin);
4131 }
4132 }
4133