2a560e8928dd54abb9096aa223d12112e690e97e
[authserver.git] / index.php
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
9 $errors = array();
10
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');
18 $head->appendJSFile('authsystem.js');
19 $title->appendText('KaiRo.at Authentication Server');
20 $h1 = $body->appendElement('h1', 'KaiRo.at Authentication Server');
21
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.');
25 }
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' => '');
34   $pagetype = 'default';
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
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)) {
53         if (!preg_match('/^[^@]+@[^@]+\.[^@]+$/', $_POST['email'])) {
54           $errors[] = _('The email address is invalid.');
55         }
56         elseif (AuthUtils::verifyTimeCode(@$_POST['tcode'], $session)) {
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);
60           if ($user['id'] && array_key_exists('pwd', $_POST)) {
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;');
69                 if (!$result->execute(array(':pwdhash' => $newHash, ':userid' => $user['id']))) {
70                   // XXXlog: Failed to update user hash!
71                 }
72               }
73
74               // Log user in - update session key for that, see https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines#Login
75               $sesskey = AuthUtils::createSessionKey();
76               setcookie('sessionkey', $sesskey, 0, "", "", !$running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost.
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 {
89                   // XXXlog: Unexpected failure to create session!
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               }
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               }
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 {
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)) {
118               $errors += AuthUtils::checkPasswordConstraints(strval($_POST['pwd']), $_POST['email']);
119             }
120             if (!count($errors)) {
121               // Put user into the DB
122               if (!$user['id']) {
123                 $newHash = password_hash($_POST['pwd'], PASSWORD_DEFAULT, $pwd_options);
124                 $vcode = AuthUtils::createVerificationCode();
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 {
164                 // Password reset requested with "Password forgotten?" function.
165                 $vcode = AuthUtils::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']).'_'.AuthUtils::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                 }
197               }
198             }
199           }
200         }
201         else {
202           $errors[] = _('The form you used was not valid. Possibly it has expired and you need to initiate the action again.');
203         }
204       }
205       elseif (array_key_exists('reset', $_GET)) {
206         if ($session['logged_in']) {
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']) {
211             // XXXlog: Unexpected failure to fetch user data!
212           }
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']))) {
227             // XXXlog: Unexpected failure to save verification!
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       }
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                   AuthUtils::verifyTimeCode($regs[3], $session, 60)) {
252                 // Set a new verify_hash for the actual password reset.
253                 $user['verify_hash'] = AuthUtils::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       }
272       elseif (intval($session['user'])) {
273         $result = $db->prepare('SELECT `id`,`email`,`verify_hash` FROM `auth_users` WHERE `id` = :userid;');
274         $result->execute(array(':userid' => $session['user']));
275         $user = $result->fetch(PDO::FETCH_ASSOC);
276         if (!$user['id']) {
277           // XXXlog: Unexpected failure to fetch user data!
278         }
279         // Password reset requested.
280         if (array_key_exists('pwd', $_POST) && array_key_exists('reset', $_POST) && array_key_exists('tcode', $_POST)) {
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) && !AuthUtils::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           }
293           $errors += AuthUtils::checkPasswordConstraints(strval($_POST['pwd']), $user['email']);
294           if (!count($errors)) {
295             $newHash = password_hash($_POST['pwd'], PASSWORD_DEFAULT, $pwd_options);
296             $result = $db->prepare('UPDATE `auth_users` SET `pwdhash` = :pwdhash, `verify_hash` = \'\' WHERE `id` = :userid;');
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 {
302               $pagetype = 'reset_done';
303             }
304           }
305         }
306       }
307     }
308   }
309   if (is_null($session)) {
310     // Create new session and set cookie.
311     $sesskey = AuthUtils::createSessionKey();
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     }
322     else {
323       // XXXlog: Unexpected failure to create session!
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     }
326   }
327 }
328
329 if (!count($errors)) {
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   }
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   }
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');
353     $litem->appendInputHidden('tcode', AuthUtils::createTimeCode($session));
354     $submit = $litem->appendInputSubmit(_('Send instructions to email'));
355   }
356   elseif ($pagetype == 'resetpwd') {
357     $para = $body->appendElement('p', sprintf(_('You can set a new password for %s here.'), $user['email']));
358     $para->setAttribute('class', '');
359     $form = $body->appendForm('?', 'POST', 'newpwdform');
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');
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');
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');
375     $litem->appendInputHidden('reset', '');
376     $litem->appendInputHidden('tcode', AuthUtils::createTimeCode($session));
377     if (!$session['logged_in'] && strlen(@$user['verify_hash'])) {
378       $litem->appendInputHidden('vcode', $user['verify_hash']);
379     }
380     $submit = $litem->appendInputSubmit(_('Save password'));
381   }
382   elseif ($session['logged_in']) {
383     if ($pagetype == 'reset_done') {
384       $para = $body->appendElement('p', _('Your password has successfully been reset.'));
385       $para->setAttribute('class', 'resetinfo done');
386     }
387     $div = $body->appendElement('div', $user['email']);
388     $div->setAttribute('class', 'loginheader');
389     $div = $body->appendElement('div');
390     $div->setAttribute('class', 'loginlinks');
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'));
397   }
398   else { // not logged in
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     }
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     }
407     $form = $body->appendForm('?', 'POST', 'loginform');
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', '');
420     $inptxt->setAttribute('required', '');
421     $inptxt->setAttribute('placeholder', _('Password'));
422     $inptxt->setAttribute('class', 'login');
423     $litem = $ulist->appendElement('li');
424     $litem->appendLink('?reset', _('Forgot password?'));
425     $litem = $ulist->appendElement('li');
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');
432     $litem->appendInputHidden('tcode', AuthUtils::createTimeCode($session));
433     $submit = $litem->appendInputSubmit(_('Log in / Register'));
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   }
447   $body->appendButton(_('Back'), 'history.back();');
448 }
449
450 // Send HTML to client.
451 print($document->saveHTML());
452 ?>