Added login password hint support

Ylian Saint-Hilaire committed Sep 8, 2017 at 11:37 UTC 6347eb7e4a15d3a543295f1cd3aabb5d0ceae488
3 files changed +112 -16
views/default.handlebars
+4 -2
@@ -3605,9 +3605,11 @@
3605 x += "<td align=right>Password:</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() /> <b><span id=dxPassWarn></span></b></td>";
3606 x += "</tr><tr>";
3607 x += "<td align=right>Password:</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() /></td>";
3608 + x += "</tr><tr>";
3609 + x += "<td align=right>Password Hint:</td><td><input id=apasswordhint name=apasswordhint maxlength=250 type=text autocomplete=off /></td>";
3610 x += '</tr></table><br /><div style="padding:10px;margin-bottom:4px">';
3609 - x += '<input id="account_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">';
3610 - x += '<input id="account_dlgOkButton" type="submit" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">';
3611 + x += '<input id=account_dlgCancelButton type=button value="Cancel" style="float:right;width:80px;margin-left:5px" onclick=dialogclose(0)>';
3612 + x += '<input id=account_dlgOkButton type=submit value="OK" style="float:right;width:80px" onclick="dialogclose(1)">';
3613 x += '</div><br /></form>';
3614 setDialogMode(2, "Change Password", 0, null, x);
3615 account_validateDeleteAccount();
views/login.handlebars
+87 -8
@@ -41,15 +41,15 @@
41 </div>
42 <table>
43 <tr>
44 - <td align="right" width="100">Username:</td>
44 + <td align=right width=100>Username:</td>
45 <td><input id=username type=text name=username onchange=validateLogin() onkeyup=validateLogin() /></td>
46 </tr>
47 <tr>
48 - <td align="right">Password:</td>
48 + <td align=right>Password:</td>
49 <td><input id=password type=password name=password autocomplete=off onchange=validateLogin() onkeyup=validateLogin() /></td>
50 </tr>
51 <tr>
52 - <td></td>
52 + <td><div id=showPassHintLink style=display:none><a onclick=showPassHint() style="cursor:pointer">Show Hint</a></div></td>
53 <td align=right><input id=loginButton type=submit value="Log In" disabled="disabled" /></td>
54 </tr>
55 </table>
@@ -59,7 +59,7 @@
59 </form>
60 </div>
61 <div id=createpanel style="background-color: #979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
62 - <form action="createaccount" method="post">
62 + <form action=createaccount method=post>
63 <div id=message2>
64 {{{message}}}
65 </div>
@@ -69,19 +69,23 @@
69 <table>
70 <tr>
71 <td align=right width=100>Username:</td>
72 - <td><input id="ausername" type="text" name=username onchange=validateCreate() onkeyup=validateCreate() /></td>
72 + <td><input id=ausername type=text name=username onchange=validateCreate() onkeyup=validateCreate() /></td>
73 </tr>
74 <tr>
75 <td align=right width=100>Email:</td>
76 - <td><input id="aemail" type="text" name=email onchange=validateCreate() onkeyup=validateCreate() /></td>
76 + <td><input id=aemail type=text name=email onchange=validateCreate() onkeyup=validateCreate() /></td>
77 </tr>
78 <tr>
79 <td align=right>Password:</td>
80 - <td><input id="apassword1" type=password name=password1 autocomplete="off" onchange=validateCreate() onkeyup=validateCreate() /></td>
80 + <td><input id=apassword1 type=password name=password1 autocomplete=off onchange=validateCreate() onkeyup=validateCreate() /></td>
81 </tr>
82 <tr>
83 <td align=right>Password:</td>
84 - <td><input id="apassword2" type=password name=password2 autocomplete="off" onchange=validateCreate() onkeyup=validateCreate() /></td>
84 + <td><input id=apassword2 type=password name=password2 autocomplete=off onchange=validateCreate() onkeyup=validateCreate() /></td>
85 + </tr>
86 + <tr>
87 + <td align=right>Password Hint:</td>
88 + <td><input id=apasswordhint type=text name=apasswordhint autocomplete=off maxlength=250 /></td>
89 </tr>
90 <tr>
91 <td colspan=2>
@@ -111,12 +115,40 @@
115 </div>
116 </div>
117 </div>
118 + <div id=dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:180px;width:400px;display:none">
119 + <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
120 + <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
121 + <div id=id_dialogtitle style=padding:5px></div>
122 + <div style=width:100%;margin:6px></div>
123 + </div>
124 + <div style="margin-right:16px;margin-left:8px">
125 + <div id=dialog1 style="margin:auto;text-align:center;margin:3px">
126 + <div id=id_dialogMessage style="padding:10px"></div>
127 + </div>
128 + <div id=dialog2 style="margin:auto;margin:3px">
129 + <div id=id_dialogOptions></div>
130 + </div>
131 + </div>
132 + <div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
133 + <input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
134 + <input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
135 + </div>
136 + </div>
137 <script>
138 + var passhint = "{{{passhint}}}";
139 +
140 function startup() {
141 + window.onresize = center;
142 + center();
143 validateLogin();
144 validateCreate();
145 if ('{{loginmode}}' != '') { go({{loginmode}}); } else { go(1); }
146 QV('newAccountDiv', '{{{newAccount}}}' != '0' );
147 + if ((passhint != null) && (passhint.length > 0)) { QV("showPassHintLink", true); }
148 + }
149 +
150 + function showPassHint() {
151 + messagebox("Password Hint", passhint);
152 }
153
154 function xgo(x) {
@@ -126,6 +158,7 @@
158 }
159
160 function go(x) {
161 + QV("showPassHintLink", false);
162 QV('loginpanel', x == 1);
163 QV('createpanel', x == 2);
164 }
@@ -133,9 +166,11 @@
166 function validateLogin() {
167 var ok = (Q('username').value.length > 0 && Q('password').value.length > 0);
168 QE('loginButton', ok);
169 + QV("showPassHintLink", false);
170 }
171
172 function validateCreate() {
173 + QV("showPassHintLink", false);
174 var ok = (Q('ausername').value.length > 0 && Q('aemail').value.length > 0 && Q('apassword1').value.length > 0 && Q('apassword2').value == Q('apassword1').value);
175 QE('createButton', ok);
176 if (Q('apassword1').value == '') {
@@ -156,6 +191,50 @@
191 for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
192 return parseInt(r + (varCount - 1) * 10);
193 }
194 +
195 +
196 + //
197 + // POPUP DIALOG
198 + //
199 +
200 + // undefined = Hidden, 1 = Generic Message
201 + var xxdialogMode;
202 + var xxdialogFunc;
203 + var xxdialogButtons;
204 + var xxdialogTag;
205 + var xxcurrentView = 0;
206 +
207 + // Display a dialog box
208 + // Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 2 only)
209 + function setDialogMode(x, y, b, f, c, tag) {
210 + xxdialogMode = x;
211 + xxdialogFunc = f;
212 + xxdialogButtons = b;
213 + xxdialogTag = tag;
214 + QE('idx_dlgOkButton', true);
215 + QV('idx_dlgOkButton', b & 1);
216 + QV('idx_dlgCancelButton', b & 2);
217 + QV('id_dialogclose', (b & 2) || (b & 8));
218 + QV('idx_dlgButtonBar', b & 7);
219 + if (y) QH('id_dialogtitle', y);
220 + for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
221 + QV('dialog', x);
222 + if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
223 + }
224 +
225 + function dialogclose(x) {
226 + var f = xxdialogFunc;
227 + var b = xxdialogButtons;
228 + var t = xxdialogTag;
229 + setDialogMode();
230 + if (((b & 8) || x) && f) f(x, t);
231 + }
232 +
233 + function center() { QS('dialog').left = ((((getDocWidth() - 400) / 2)) + "px"); }
234 + function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
235 + function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
236 + function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }
237 +
238 </script>
239 </body>
240 </html>
\ No newline at end of file
webserver.js
+21 -6
@@ -82,6 +82,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
82 obj.crypto.randomBytes(16, function (err, buf) { obj.httpAuthRealm = buf.toString('hex'); });
83 obj.crypto.randomBytes(32, function (err, buf) { obj.relayRandom = buf; });
84
85 + function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
86 + function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
87 +
88 if (obj.args.notls) {
89 // Setup the HTTP server without TLS
90 obj.expressWs = require('express-ws')(obj.app);
@@ -108,12 +111,15 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
111 if (req.session != undefined) {
112 var err = req.session.error;
113 var msg = req.session.success;
114 + var passhint = req.session.passhint;
115 delete req.session.error;
116 delete req.session.success;
117 + delete req.session.passhint;
118 }
119 res.locals.message = '';
120 if (err) res.locals.message = '<p class="msg error">' + err + '</p>';
121 if (msg) res.locals.message = '<p class="msg success">' + msg + '</p>';
122 + if (passhint) res.locals.passhint = EscapeHtml(passhint);
123 next();
124 });
125
@@ -146,7 +152,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
152 obj.hash(pass, user.salt, function (err, hash) {
153 if (err) return fn(err);
154 if (hash == user.hash) return fn(null, user._id);
149 - fn(new Error('invalid password'));
155 + fn(new Error('invalid password'), null, user.passhint);
156 });
157 }
158 }
@@ -187,7 +193,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
193
194 function handleLoginRequest(req, res) {
195 var domain = getDomain(req);
190 - obj.authenticate(req.body.username, req.body.password, domain, function (err, userid) {
196 + obj.authenticate(req.body.username, req.body.password, domain, function (err, userid, passhint) {
197 if (userid) {
198 var user = obj.users[userid];
199
@@ -203,9 +209,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
209 req.session.userid = userid;
210 req.session.domainid = domain.id;
211 req.session.currentNode = '';
206 - if (req.body.viewmode) {
207 - req.session.viewmode = req.body.viewmode;
208 - }
212 + if (req.session.passhint) { delete req.session.passhint; }
213 + if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; }
214 if (req.body.host) {
215 obj.db.GetAllType('node', function (err, docs) {
216 for (var i = 0; i < docs.length; i++) {
@@ -227,6 +232,11 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
232 } else {
233 delete req.session.loginmode;
234 req.session.error = '<b style=color:#8C001A>Login failed, check username and password.</b>';
235 + if ((passhint != null) && (passhint.length > 0)) {
236 + req.session.passhint = passhint;
237 + } else {
238 + if (req.session.passhint) { delete req.session.passhint; }
239 + }
240 res.redirect(domain.url);
241 }
242 });
@@ -245,7 +255,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
255 req.session.loginmode = 2;
256 req.session.error = '<b style=color:#8C001A>Username already exists.</b>';
257 } else {
248 - var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Date.now(), login: Date.now(), domain: domain.id };
258 + var hint = req.body.apasswordhint;
259 + if (hint.length > 250) hint = hint.substring(0, 250);
260 + var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Date.now(), login: Date.now(), domain: domain.id, passhint: hint };
261 var usercount = 0;
262 for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
263 if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newAccounts == 2) { domain.newAccounts = 0; } } // If this is the first user, give the account site admin.
@@ -295,10 +307,13 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
307 // Update the password
308 obj.hash(req.body.apassword1, function (err, salt, hash) {
309 if (err) throw err;
310 + var hint = req.body.apasswordhint;
311 + if (hint.length > 250) hint = hint.substring(0, 250);
312 var user = obj.users[req.session.userid];
313 user.salt = salt;
314 user.hash = hash;
315 user.passchange = Date.now();
316 + user.passhint = req.body.apasswordhint;
317 obj.db.SetUser(user);
318 req.session.viewmode = 2;
319 res.redirect(domain.url);