Commit | Line | Data |
---|---|---|
133aecbe RK |
1 | <?php |
2 | /* This Source Code Form is subject to the terms of the Mozilla Public | |
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, | |
4 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
5 | ||
6 | // Include the common auth system files (including the OAuth2 Server object). | |
7 | require_once(__DIR__.'/authsystem.inc.php'); | |
8 | ||
d26d08a1 RK |
9 | $errors = array(); |
10 | ||
133aecbe RK |
11 | // Start HTML document as a DOM object. |
12 | extract(ExtendedDocument::initHTML5()); // sets $document, $html, $head, $title, $body | |
13 | $document->formatOutput = true; // we want a nice output | |
14 | ||
15 | $style = $head->appendElement('link'); | |
16 | $style->setAttribute('rel', 'stylesheet'); | |
17 | $style->setAttribute('href', 'authsystem.css'); | |
d26d08a1 | 18 | $head->appendJSFile('authsystem.js'); |
133aecbe RK |
19 | $title->appendText('KaiRo.at Authentication Server'); |
20 | $h1 = $body->appendElement('h1', 'KaiRo.at Authentication Server'); | |
21 | ||
d26d08a1 RK |
22 | $running_on_localhost = preg_match('/^((.+\.)?localhost|127\.0\.0\.\d+)$/', $_SERVER['SERVER_NAME']); |
23 | if (($_SERVER['SERVER_PORT'] != 443) && !$running_on_localhost) { | |
24 | $errors[] = _('You are not accessing this site on a secure connection, so authentication doesn\'t work.'); | |
133aecbe | 25 | } |
d26d08a1 RK |
26 | |
27 | $para = $body->appendElement('p', _('This login system does not work without JavaScript. Please activate JavaScript for this site to log in.')); | |
28 | $para->setAttribute('id', 'jswarning'); | |
29 | $para->setAttribute('class', 'warn'); | |
30 | ||
31 | if (!count($errors)) { | |
32 | $session = null; | |
33 | $user = array('id' => 0, 'email' => ''); | |
b19743bc | 34 | $pagetype = 'default'; |
d26d08a1 RK |
35 | $db->exec("SET time_zone='+00:00';"); // Execute directly on PDO object, set session to UTC to make our gmdate() values match correctly. |
36 | if (strlen(@$_COOKIE['sessionkey'])) { | |
37 | // Fetch the session - or at least try to. | |
38 | $result = $db->prepare('SELECT * FROM `auth_sessions` WHERE `sesskey` = :sesskey AND `time_expire` > :expire;'); | |
39 | $result->execute(array(':sesskey' => $_COOKIE['sessionkey'], ':expire' => gmdate('Y-m-d H:i:s'))); | |
40 | $row = $result->fetch(PDO::FETCH_ASSOC); | |
41 | if ($row) { | |
42 | $session = $row; | |
43 | ||
b19743bc RK |
44 | if (array_key_exists('logout', $_GET)) { |
45 | $result = $db->prepare('UPDATE `auth_sessions` SET `logged_in` = FALSE WHERE `id` = :sessid;'); | |
46 | if (!$result->execute(array(':sessid' => $session['id']))) { | |
47 | // XXXlog: Unexpected logout failure! | |
48 | $errors[] = _('The email address is invalid.'); | |
49 | } | |
50 | $session['logged_in'] = 0; | |
51 | } | |
52 | elseif (array_key_exists('email', $_POST)) { | |
d26d08a1 RK |
53 | if (!preg_match('/^[^@]+@[^@]+\.[^@]+$/', $_POST['email'])) { |
54 | $errors[] = _('The email address is invalid.'); | |
55 | } | |
89975cb9 | 56 | elseif (verifyTimeCode(@$_POST['tcode'], $session)) { |
d26d08a1 RK |
57 | $result = $db->prepare('SELECT `id`, `pwdhash`, `email`, `status`, `verify_hash` FROM `auth_users` WHERE `email` = :email;'); |
58 | $result->execute(array(':email' => $_POST['email'])); | |
59 | $user = $result->fetch(PDO::FETCH_ASSOC); | |
b19743bc | 60 | if ($user['id'] && array_key_exists('pwd', $_POST)) { |
d26d08a1 RK |
61 | // existing user, check password |
62 | if (($user['status'] == 'ok') && password_verify(@$_POST['pwd'], $user['pwdhash'])) { | |
63 | // Check if a newer hashing algorithm is available | |
64 | // or the cost has changed | |
65 | if (password_needs_rehash($user['pwdhash'], PASSWORD_DEFAULT, $pwd_options)) { | |
66 | // If so, create a new hash, and replace the old one | |
67 | $newHash = password_hash($_POST['pwd'], PASSWORD_DEFAULT, $pwd_options); | |
68 | $result = $db->prepare('UPDATE `auth_users` SET `pwdhash` = :pwdhash WHERE `id` = :userid;'); | |
b19743bc RK |
69 | if (!$result->execute(array(':pwdhash' => $newHash, ':userid' => $user['id']))) { |
70 | // XXXlog: Failed to update user hash! | |
71 | } | |
d26d08a1 RK |
72 | } |
73 | ||
74 | // Log user in - update session key for that, see https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines#Login | |
89975cb9 | 75 | $sesskey = createSessionKey(); |
d26d08a1 | 76 | setcookie('sessionkey', $sesskey, 0, "", "", !$running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost. |
b19743bc RK |
77 | // If the session has a user set, create a new one - otherwise take existing session entry. |
78 | if (intval($session['user'])) { | |
79 | $result = $db->prepare('INSERT INTO `auth_sessions` (`sesskey`, `time_expire`, `user`, `logged_in`) VALUES (:sesskey, :expire, :userid, TRUE);'); | |
80 | $result->execute(array(':sesskey' => $sesskey, ':userid' => $user['id'], ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day')))); | |
81 | // After insert, actually fetch the session row from the DB so we have all values. | |
82 | $result = $db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;'); | |
83 | $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s'))); | |
84 | $row = $result->fetch(PDO::FETCH_ASSOC); | |
85 | if ($row) { | |
86 | $session = $row; | |
87 | } | |
88 | else { | |
89975cb9 | 89 | // XXXlog: Unexpected failure to create session! |
b19743bc RK |
90 | $errors[] = _('The session system is not working. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); |
91 | } | |
92 | } | |
93 | else { | |
94 | $result = $db->prepare('UPDATE `auth_sessions` SET `sesskey` = :sesskey, `user` = :userid, `logged_in` = TRUE, `time_expire` = :expire WHERE `id` = :sessid;'); | |
95 | if (!$result->execute(array(':sesskey' => $sesskey, ':userid' => $user['id'], ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day')), ':sessid' => $session['id']))) { | |
96 | // XXXlog: Unexpected login failure! | |
97 | $errors[] = _('Login failed unexpectedly. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); | |
98 | } | |
99 | } | |
89975cb9 RK |
100 | // If a verify_hash if set on a verified user, a password reset had been requested. As a login works right now, cancel that reset request by deleting the hash. |
101 | if (strlen(@$user['verify_hash'])) { | |
102 | $result = $db->prepare('UPDATE `auth_users` SET `verify_hash` = \'\' WHERE `id` = :userid;'); | |
103 | if (!$result->execute(array(':userid' => $user['id']))) { | |
104 | // XXXlog: verify_hash could not be emptied! | |
105 | } | |
106 | else { | |
107 | $user['verify_hash'] = ''; | |
108 | } | |
109 | } | |
d26d08a1 RK |
110 | } |
111 | else { | |
112 | $errors[] = _('This password is invalid or your email is not verified yet. Did you type them correctly?'); | |
113 | } | |
114 | } | |
115 | else { | |
b19743bc RK |
116 | // new user: check password, create user and send verification; existing users: re-send verification or send password change instructions |
117 | if (array_key_exists('pwd', $_POST)) { | |
e876642c | 118 | $errors += checkPasswordConstraints(strval($_POST['pwd']), $_POST['email']); |
d26d08a1 RK |
119 | } |
120 | if (!count($errors)) { | |
121 | // Put user into the DB | |
b19743bc RK |
122 | if (!$user['id']) { |
123 | $newHash = password_hash($_POST['pwd'], PASSWORD_DEFAULT, $pwd_options); | |
89975cb9 | 124 | $vcode = createVerificationCode(); |
b19743bc RK |
125 | $result = $db->prepare('INSERT INTO `auth_users` (`email`, `pwdhash`, `status`, `verify_hash`) VALUES (:email, :pwdhash, \'unverified\', :vcode);'); |
126 | if (!$result->execute(array(':email' => $_POST['email'], ':pwdhash' => $newHash, ':vcode' => $vcode))) { | |
127 | // XXXlog: User insertion failure! | |
128 | $errors[] = _('Could not add user. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); | |
129 | } | |
130 | $user = array('id' => $db->lastInsertId(), | |
131 | 'email' => $_POST['email'], | |
132 | 'pwdhash' => $newHash, | |
133 | 'status' => 'unverified', | |
134 | 'verify_hash' => $vcode); | |
135 | } | |
136 | if ($user['status'] == 'unverified') { | |
137 | // Send email for verification and show message to point to it. | |
138 | $mail = new email(); | |
139 | $mail->setCharset('utf-8'); | |
140 | $mail->addHeader('X-KAIRO-AUTH', 'email_verification'); | |
141 | $mail->addRecipient($user['email']); | |
142 | $mail->setSender('noreply@auth.kairo.at', _('KaiRo.at Authentication Service')); | |
143 | $mail->setSubject('Email Verification for KaiRo.at Authentication'); | |
144 | $mail->addMailText(_('Welcome!')."\n\n"); | |
145 | $mail->addMailText(sprintf(_('This email address, %s, has been used for registration on "%s".'), | |
146 | $user['email'], _('KaiRo.at Authentication Service'))."\n\n"); | |
147 | $mail->addMailText(_('Please confirm that registration by clicking the following link (or calling it up in your browser):')."\n"); | |
148 | $mail->addMailText(($running_on_localhost?'http':'https').'://'.$_SERVER['SERVER_NAME'].strstr($_SERVER['REQUEST_URI'], '?', true) | |
149 | .'?email='.rawurlencode($user['email']).'&verification_code='.rawurlencode($user['verify_hash'])."\n\n"); | |
150 | $mail->addMailText(_('With this confirmation, you accept that we handle your data for the purpose of logging you into other websites when you request that.')."\n"); | |
151 | $mail->addMailText(_('Those websites will get to know your email address but not your password, which we store securely.')."\n"); | |
152 | $mail->addMailText(_('If you do not call this confirmation link within 72 hours, your data will be deleted from our database.')."\n\n"); | |
153 | $mail->addMailText(sprintf(_('The %s team'), 'KaiRo.at')); | |
154 | //$mail->setDebugAddress("robert@localhost"); | |
155 | $mailsent = $mail->send(); | |
156 | if ($mailsent) { | |
157 | $pagetype = 'verification_sent'; | |
158 | } | |
159 | else { | |
160 | $errors[] = _('The confirmation email could not be sent to you. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); | |
161 | } | |
162 | } | |
163 | else { | |
89975cb9 RK |
164 | // Password reset requested with "Password forgotten?" function. |
165 | $vcode = createVerificationCode(); | |
166 | $result = $db->prepare('UPDATE `auth_users` SET `verify_hash` = :vcode WHERE `id` = :userid;'); | |
167 | if (!$result->execute(array(':vcode' => $vcode, ':userid' => $user['id']))) { | |
168 | // XXXlog: User insertion failure! | |
169 | $errors[] = _('Could not initiate reset request. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); | |
170 | } | |
171 | else { | |
172 | $resetcode = $vcode.dechex($user['id'] + $session['id']).'_'.createTimeCode($session, null, 60); | |
173 | // Send email with instructions for resetting the password. | |
174 | $mail = new email(); | |
175 | $mail->setCharset('utf-8'); | |
176 | $mail->addHeader('X-KAIRO-AUTH', 'password_reset'); | |
177 | $mail->addRecipient($user['email']); | |
178 | $mail->setSender('noreply@auth.kairo.at', _('KaiRo.at Authentication Service')); | |
179 | $mail->setSubject('How to reset your password for KaiRo.at Authentication'); | |
180 | $mail->addMailText(_('Hi,')."\n\n"); | |
181 | $mail->addMailText(sprintf(_('A request for setting a new password for this email address, %s, has been submitted on "%s".'), | |
182 | $user['email'], _('KaiRo.at Authentication Service'))."\n\n"); | |
183 | $mail->addMailText(_('You can set a new password by clicking the following link (or calling it up in your browser):')."\n"); | |
184 | $mail->addMailText(($running_on_localhost?'http':'https').'://'.$_SERVER['SERVER_NAME'].strstr($_SERVER['REQUEST_URI'], '?', true) | |
185 | .'?email='.rawurlencode($user['email']).'&reset_code='.rawurlencode($resetcode)."\n\n"); | |
186 | $mail->addMailText(_('If you do not call this confirmation link within 1 hour, this link expires and the existing password is being kept in place.')."\n\n"); | |
187 | $mail->addMailText(sprintf(_('The %s team'), 'KaiRo.at')); | |
188 | //$mail->setDebugAddress("robert@localhost"); | |
189 | $mailsent = $mail->send(); | |
190 | if ($mailsent) { | |
191 | $pagetype = 'resetmail_sent'; | |
192 | } | |
193 | else { | |
194 | $errors[] = _('The email with password reset instructions could not be sent to you. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); | |
195 | } | |
196 | } | |
b19743bc | 197 | } |
d26d08a1 RK |
198 | } |
199 | } | |
200 | } | |
89975cb9 RK |
201 | else { |
202 | $errors[] = _('The form you used was not valid. Possibly it has expired and you need to initiate the action again.'); | |
203 | } | |
d26d08a1 | 204 | } |
b19743bc RK |
205 | elseif (array_key_exists('reset', $_GET)) { |
206 | if ($session['logged_in']) { | |
e876642c RK |
207 | $result = $db->prepare('SELECT `id`,`email` FROM `auth_users` WHERE `id` = :userid;'); |
208 | $result->execute(array(':userid' => $session['user'])); | |
209 | $user = $result->fetch(PDO::FETCH_ASSOC); | |
210 | if (!$user['id']) { | |
89975cb9 | 211 | // XXXlog: Unexpected failure to fetch user data! |
e876642c | 212 | } |
b19743bc RK |
213 | $pagetype = 'resetpwd'; |
214 | } | |
215 | else { | |
216 | // Display form for entering email. | |
217 | $pagetype = 'resetstart'; | |
218 | } | |
219 | } | |
220 | elseif (array_key_exists('verification_code', $_GET)) { | |
221 | $result = $db->prepare('SELECT `id`,`email` FROM `auth_users` WHERE `email` = :email AND `status` = \'unverified\' AND `verify_hash` = :vcode;'); | |
222 | $result->execute(array(':email' => @$_GET['email'], ':vcode' => $_GET['verification_code'])); | |
223 | $user = $result->fetch(PDO::FETCH_ASSOC); | |
224 | if ($user['id']) { | |
225 | $result = $db->prepare('UPDATE `auth_users` SET `verify_hash` = \'\', `status` = \'ok\' WHERE `id` = :userid;'); | |
226 | if (!$result->execute(array(':userid' => $user['id']))) { | |
89975cb9 | 227 | // XXXlog: Unexpected failure to save verification! |
b19743bc RK |
228 | $errors[] = _('Could not save confirmation. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); |
229 | } | |
230 | $pagetype = 'verification_done'; | |
231 | } | |
232 | else { | |
233 | $errors[] = _('The confirmation link you called is not valid. Possibly it has expired and you need to try registering again.'); | |
234 | } | |
235 | } | |
89975cb9 RK |
236 | elseif (array_key_exists('reset_code', $_GET)) { |
237 | $reset_fail = true; | |
238 | $result = $db->prepare('SELECT `id`,`email`,`verify_hash` FROM `auth_users` WHERE `email` = :email'); | |
239 | $result->execute(array(':email' => @$_GET['email'])); | |
240 | $user = $result->fetch(PDO::FETCH_ASSOC); | |
241 | if ($user['id']) { | |
242 | // Deconstruct reset code and verify it. | |
243 | if (preg_match('/^([0-9a-f]{'.strlen($user['verify_hash']).'})([0-9a-f]+)_(\d+\.\d+)$/', $_GET['reset_code'], $regs)) { | |
244 | $tcode_sessid = hexdec($regs[2]) - $user['id']; | |
245 | $result = $db->prepare('SELECT `id`,`sesskey` FROM `auth_sessions` WHERE `id` = :sessid;'); | |
246 | $result->execute(array(':sessid' => $tcode_sessid)); | |
247 | $row = $result->fetch(PDO::FETCH_ASSOC); | |
248 | if ($row) { | |
249 | $tcode_session = $row; | |
250 | if (($regs[1] == $user['verify_hash']) && | |
251 | verifyTimeCode($regs[3], $session, 60)) { | |
252 | // Set a new verify_hash for the actual password reset. | |
253 | $user['verify_hash'] = createVerificationCode(); | |
254 | $result = $db->prepare('UPDATE `auth_users` SET `verify_hash` = :vcode WHERE `id` = :userid;'); | |
255 | if (!$result->execute(array(':vcode' => $user['verify_hash'], ':userid' => $user['id']))) { | |
256 | // XXXlog: Unexpected failure to reset verify_hash! | |
257 | } | |
258 | $result = $db->prepare('UPDATE `auth_sessions` SET `user` = :userid WHERE `id` = :sessid;'); | |
259 | if (!$result->execute(array(':userid' => $user['id'], ':sessid' => $session['id']))) { | |
260 | // XXXlog: Unexpected failure to update session! | |
261 | } | |
262 | $pagetype = 'resetpwd'; | |
263 | $reset_fail = false; | |
264 | } | |
265 | } | |
266 | } | |
267 | } | |
268 | if ($reset_fail) { | |
269 | $errors[] = _('The password reset link you called is not valid. Possibly it has expired and you need to call the "Password forgotten?" function again.'); | |
270 | } | |
271 | } | |
b19743bc | 272 | elseif (intval($session['user'])) { |
89975cb9 | 273 | $result = $db->prepare('SELECT `id`,`email`,`verify_hash` FROM `auth_users` WHERE `id` = :userid;'); |
b19743bc RK |
274 | $result->execute(array(':userid' => $session['user'])); |
275 | $user = $result->fetch(PDO::FETCH_ASSOC); | |
276 | if (!$user['id']) { | |
89975cb9 | 277 | // XXXlog: Unexpected failure to fetch user data! |
b19743bc | 278 | } |
e876642c RK |
279 | // Password reset requested. |
280 | if (array_key_exists('pwd', $_POST) && array_key_exists('reset', $_POST) && array_key_exists('tcode', $_POST)) { | |
89975cb9 RK |
281 | // If not logged in, a password reset needs to have the proper vcode set. |
282 | if (!$session['logged_in'] && (!strlen(@$_POST['vcode']) || ($_POST['vcode'] != $user['verify_hash']))) { | |
283 | $errors[] = _('Password reset failed. The reset form you used was not valid. Possibly it has expired and you need to initiate the password reset again.'); | |
284 | } | |
285 | // If not logged in, a password reset also needs to have the proper email set. | |
286 | if (!$session['logged_in'] && !count($errors) && (@$_POST['email_hidden'] != $user['email'])) { | |
287 | $errors[] = _('Password reset failed. The reset form you used was not valid. Possibly it has expired and you need to initiate the password reset again.'); | |
288 | } | |
289 | // Check validity of time code. | |
290 | if (!count($errors) && !verifyTimeCode($_POST['tcode'], $session)) { | |
291 | $errors[] = _('Password reset failed. The reset form you used was not valid. Possibly it has expired and you need to initiate the password reset again.'); | |
292 | } | |
e876642c RK |
293 | $errors += checkPasswordConstraints(strval($_POST['pwd']), $user['email']); |
294 | if (!count($errors)) { | |
295 | $newHash = password_hash($_POST['pwd'], PASSWORD_DEFAULT, $pwd_options); | |
89975cb9 | 296 | $result = $db->prepare('UPDATE `auth_users` SET `pwdhash` = :pwdhash, `verify_hash` = \'\' WHERE `id` = :userid;'); |
e876642c RK |
297 | if (!$result->execute(array(':pwdhash' => $newHash, ':userid' => $session['user']))) { |
298 | // XXXlog: Password reset failure! | |
299 | $errors[] = _('Password reset failed. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); | |
300 | } | |
301 | else { | |
89975cb9 | 302 | $pagetype = 'reset_done'; |
e876642c RK |
303 | } |
304 | } | |
305 | } | |
b19743bc | 306 | } |
d26d08a1 RK |
307 | } |
308 | } | |
309 | if (is_null($session)) { | |
310 | // Create new session and set cookie. | |
89975cb9 | 311 | $sesskey = createSessionKey(); |
d26d08a1 RK |
312 | setcookie('sessionkey', $sesskey, 0, "", "", !$running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost. |
313 | $result = $db->prepare('INSERT INTO `auth_sessions` (`sesskey`, `time_expire`) VALUES (:sesskey, :expire);'); | |
314 | $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s', strtotime('+5 minutes')))); | |
315 | // After insert, actually fetch the session row from the DB so we have all values. | |
316 | $result = $db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;'); | |
317 | $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s'))); | |
318 | $row = $result->fetch(PDO::FETCH_ASSOC); | |
319 | if ($row) { | |
320 | $session = $row; | |
321 | } | |
b19743bc | 322 | else { |
89975cb9 | 323 | // XXXlog: Unexpected failure to create session! |
b19743bc RK |
324 | $errors[] = _('The session system is not working. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.'); |
325 | } | |
d26d08a1 RK |
326 | } |
327 | } | |
328 | ||
329 | if (!count($errors)) { | |
b19743bc RK |
330 | if ($pagetype == 'verification_sent') { |
331 | $para = $body->appendElement('p', sprintf(_('An email for confirmation has been sent to %s. Please follow the link provided there to complete the process.'), $user['email'])); | |
332 | $para->setAttribute('class', 'verifyinfo pending'); | |
333 | } | |
89975cb9 RK |
334 | elseif ($pagetype == 'resetmail_sent') { |
335 | $para = $body->appendElement('p', | |
336 | _('An email has been sent to the requested account with further information. If you do not receive an email then please confirm you have entered the same email address used during account registration.')); | |
337 | $para->setAttribute('class', 'resetinfo pending'); | |
338 | } | |
b19743bc RK |
339 | elseif ($pagetype == 'resetstart') { |
340 | $para = $body->appendElement('p', _('If you forgot your password or didn\'t receive the registration confirmation, please enter your email here.')); | |
341 | $para->setAttribute('class', ''); | |
342 | $form = $body->appendForm('?reset', 'POST', 'resetform'); | |
343 | $form->setAttribute('id', 'loginform'); | |
344 | $form->setAttribute('class', 'loginarea hidden'); | |
345 | $ulist = $form->appendElement('ul'); | |
346 | $ulist->setAttribute('class', 'flat login'); | |
347 | $litem = $ulist->appendElement('li'); | |
348 | $inptxt = $litem->appendInputEmail('email', 30, 20, 'login_email'); | |
349 | $inptxt->setAttribute('autocomplete', 'email'); | |
350 | $inptxt->setAttribute('required', ''); | |
351 | $inptxt->setAttribute('placeholder', _('Email')); | |
352 | $litem = $ulist->appendElement('li'); | |
e876642c | 353 | $litem->appendInputHidden('tcode', createTimeCode($session)); |
b19743bc RK |
354 | $submit = $litem->appendInputSubmit(_('Send instructions to email')); |
355 | } | |
356 | elseif ($pagetype == 'resetpwd') { | |
89975cb9 | 357 | $para = $body->appendElement('p', sprintf(_('You can set a new password for %s here.'), $user['email'])); |
b19743bc | 358 | $para->setAttribute('class', ''); |
e876642c | 359 | $form = $body->appendForm('?', 'POST', 'newpwdform'); |
b19743bc RK |
360 | $form->setAttribute('id', 'loginform'); |
361 | $form->setAttribute('class', 'loginarea hidden'); | |
362 | $ulist = $form->appendElement('ul'); | |
363 | $ulist->setAttribute('class', 'flat login'); | |
364 | $litem = $ulist->appendElement('li'); | |
e876642c RK |
365 | $litem->setAttribute('class', 'donotshow'); |
366 | $inptxt = $litem->appendInputEmail('email_hidden', 30, 20, 'login_email', $user['email']); | |
367 | $inptxt->setAttribute('autocomplete', 'email'); | |
368 | $inptxt->setAttribute('placeholder', _('Email')); | |
369 | $litem = $ulist->appendElement('li'); | |
b19743bc RK |
370 | $inptxt = $litem->appendInputPassword('pwd', 20, 20, 'login_pwd', ''); |
371 | $inptxt->setAttribute('required', ''); | |
372 | $inptxt->setAttribute('placeholder', _('Password')); | |
373 | $inptxt->setAttribute('class', 'login'); | |
374 | $litem = $ulist->appendElement('li'); | |
e876642c RK |
375 | $litem->appendInputHidden('reset', ''); |
376 | $litem->appendInputHidden('tcode', createTimeCode($session)); | |
89975cb9 RK |
377 | if (!$session['logged_in'] && strlen(@$user['verify_hash'])) { |
378 | $litem->appendInputHidden('vcode', $user['verify_hash']); | |
379 | } | |
b19743bc RK |
380 | $submit = $litem->appendInputSubmit(_('Save password')); |
381 | } | |
382 | elseif ($session['logged_in']) { | |
e876642c RK |
383 | if ($pagetype == 'reset_done') { |
384 | $para = $body->appendElement('p', _('Your password has successfully been reset.')); | |
385 | $para->setAttribute('class', 'resetinfo done'); | |
386 | } | |
d26d08a1 RK |
387 | $div = $body->appendElement('div', $user['email']); |
388 | $div->setAttribute('class', 'loginheader'); | |
389 | $div = $body->appendElement('div'); | |
390 | $div->setAttribute('class', 'loginlinks'); | |
b19743bc RK |
391 | $ulist = $div->appendElement('ul'); |
392 | $ulist->setAttribute('class', 'flat'); | |
393 | $litem = $ulist->appendElement('li'); | |
394 | $link = $litem->appendLink('?logout', _('Log out')); | |
395 | $litem = $ulist->appendElement('li'); | |
396 | $litem->appendLink('?reset', _('Set new password')); | |
d26d08a1 RK |
397 | } |
398 | else { // not logged in | |
b19743bc RK |
399 | if ($pagetype == 'verification_done') { |
400 | $para = $body->appendElement('p', _('Hooray! Your email was successfully confirmed! You can log in now.')); | |
401 | $para->setAttribute('class', 'verifyinfo done'); | |
402 | } | |
e876642c RK |
403 | elseif ($pagetype == 'reset_done') { |
404 | $para = $body->appendElement('p', _('Your password has successfully been reset. You can log in now with the new password.')); | |
405 | $para->setAttribute('class', 'resetinfo done'); | |
406 | } | |
b19743bc | 407 | $form = $body->appendForm('?', 'POST', 'loginform'); |
d26d08a1 RK |
408 | $form->setAttribute('id', 'loginform'); |
409 | $form->setAttribute('class', 'loginarea hidden'); | |
410 | $ulist = $form->appendElement('ul'); | |
411 | $ulist->setAttribute('class', 'flat login'); | |
412 | $litem = $ulist->appendElement('li'); | |
413 | $inptxt = $litem->appendInputEmail('email', 30, 20, 'login_email', (intval($user['id'])?$user['email']:'')); | |
414 | $inptxt->setAttribute('autocomplete', 'email'); | |
415 | $inptxt->setAttribute('required', ''); | |
416 | $inptxt->setAttribute('placeholder', _('Email')); | |
417 | $inptxt->setAttribute('class', 'login'); | |
418 | $litem = $ulist->appendElement('li'); | |
419 | $inptxt = $litem->appendInputPassword('pwd', 20, 20, 'login_pwd', ''); | |
b19743bc | 420 | $inptxt->setAttribute('required', ''); |
d26d08a1 RK |
421 | $inptxt->setAttribute('placeholder', _('Password')); |
422 | $inptxt->setAttribute('class', 'login'); | |
423 | $litem = $ulist->appendElement('li'); | |
b19743bc RK |
424 | $litem->appendLink('?reset', _('Forgot password?')); |
425 | $litem = $ulist->appendElement('li'); | |
d26d08a1 RK |
426 | $cbox = $litem->appendInputCheckbox('remember', 'login_remember', 'true', false); |
427 | $cbox->setAttribute('class', 'logincheck'); | |
428 | $label = $litem->appendLabel('login_remember', _('Remember me')); | |
429 | $label->setAttribute('id', 'rememprompt'); | |
430 | $label->setAttribute('class', 'loginprompt'); | |
431 | $litem = $ulist->appendElement('li'); | |
e876642c RK |
432 | $litem->appendInputHidden('tcode', createTimeCode($session)); |
433 | $submit = $litem->appendInputSubmit(_('Log in / Register')); | |
d26d08a1 RK |
434 | $submit->setAttribute('class', 'loginbutton'); |
435 | } | |
436 | } | |
437 | ||
438 | if (count($errors)) { | |
439 | $body->appendElement('p', ((count($errors) <= 1) | |
440 | ?_('The following error was detected') | |
441 | :_('The following errors were detected')).':'); | |
442 | $list = $body->appendElement('ul'); | |
443 | $list->setAttribute('class', 'flat warn'); | |
444 | foreach ($errors as $msg) { | |
445 | $item = $list->appendElement('li', $msg); | |
446 | } | |
b19743bc | 447 | $body->appendButton(_('Back'), 'history.back();'); |
133aecbe RK |
448 | } |
449 | ||
450 | // Send HTML to client. | |
451 | print($document->saveHTML()); | |
e876642c RK |
452 | |
453 | // ********** helper functions ********** | |
454 | ||
455 | function checkPasswordConstraints($new_password, $user_email) { | |
456 | $errors = array(); | |
457 | if ($new_password != trim($new_password)) { | |
458 | $errors[] = _('Password must not start or end with a whitespace character like a space.'); | |
459 | } | |
460 | if (strlen($new_password) < 8) { $errors[] = sprintf(_('Password too short (min. %s characters).'), 8); } | |
461 | if (strlen($new_password) > 70) { $errors[] = sprintf(_('Password too long (max. %s characters).'), 70); } | |
462 | if ((strtolower($new_password) == strtolower($user_email)) || | |
463 | in_array(strtolower($new_password), preg_split("/[@\.]+/", strtolower($user_email)))) { | |
464 | $errors[] = _('The passwort can not be equal to your email or any part of it.'); | |
465 | } | |
466 | if ((strlen($new_password) < 15) && (preg_match('/^[a-zA-Z]+$/', $new_password))) { | |
467 | $errors[] = sprintf(_('Your password must use characters other than normal letters or contain least %s characters.'), 15); | |
468 | } | |
469 | if (preg_match('/^\d+$/', $new_password)) { | |
470 | $errors[] = sprintf(_('Your password cannot consist only of numbers.'), 15); | |
471 | } | |
472 | if (strlen(count_chars($new_password, 3)) < 5) { | |
473 | $errors[] = sprintf(_('Password does have to contain at least %s different characters.'), 5); | |
474 | } | |
475 | return $errors; | |
476 | } | |
477 | ||
89975cb9 RK |
478 | function createSessionKey() { |
479 | return bin2hex(openssl_random_pseudo_bytes(512 / 8)); // Get 512 bits of randomness (128 byte hex string). | |
480 | } | |
481 | ||
482 | function createVerificationCode() { | |
483 | return bin2hex(openssl_random_pseudo_bytes(512 / 8)); // Get 512 bits of randomness (128 byte hex string). | |
484 | } | |
485 | ||
486 | function createTimeCode($session, $offset = null, $validity_minutes = 10) { | |
e876642c | 487 | // Matches TOTP algorithms, see https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm |
89975cb9 RK |
488 | $valid_seconds = intval($validity_minutes) * 60; |
489 | if ($valid_seconds < 60) { $valid_seconds = 60; } | |
490 | $code_digits = 8; | |
e876642c | 491 | $time = time(); |
89975cb9 | 492 | $rest = is_null($offset)?($time % $valid_seconds):intval($offset); // T0, will be sent as part of code to make it valid for the full duration. |
e876642c | 493 | $counter = floor(($time - $rest) / $valid_seconds); |
89975cb9 | 494 | $hmac = mhash(MHASH_SHA1, $counter, $session['id'].$session['sesskey']); |
e876642c RK |
495 | $offset = hexdec(substr(bin2hex(substr($hmac, -1)), -1)); // Get the last 4 bits as a number. |
496 | $totp = hexdec(bin2hex(substr($hmac, $offset, 4))) & 0x7FFFFFFF; // Take 4 bytes at the offset, discard highest bit. | |
497 | $totp_value = sprintf('%0'.$code_digits.'d', substr($totp, -$code_digits)); | |
498 | return $rest.'.'.$totp_value; | |
499 | } | |
500 | ||
89975cb9 RK |
501 | function verifyTimeCode($timecode_to_verify, $session, $validity_minutes = 10) { |
502 | if (preg_match('/^(\d+)\.\d+$/', $timecode_to_verify, $regs)) { | |
503 | return ($timecode_to_verify === createTimeCode($session, $regs[1], $validity_minutes)); | |
504 | } | |
505 | return false; | |
506 | } | |
507 | ||
133aecbe | 508 | ?> |