| 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 | $db->exec("SET time_zone='+00:00';"); // Execute directly on PDO object, set session to UTC to make our gmdate() values match correctly. |
| 35 | if (strlen(@$_COOKIE['sessionkey'])) { |
| 36 | // Fetch the session - or at least try to. |
| 37 | $result = $db->prepare('SELECT * FROM `auth_sessions` WHERE `sesskey` = :sesskey AND `time_expire` > :expire;'); |
| 38 | $result->execute(array(':sesskey' => $_COOKIE['sessionkey'], ':expire' => gmdate('Y-m-d H:i:s'))); |
| 39 | $row = $result->fetch(PDO::FETCH_ASSOC); |
| 40 | if ($row) { |
| 41 | $session = $row; |
| 42 | |
| 43 | if (strlen(@$_POST['email'])) { |
| 44 | if (!preg_match('/^[^@]+@[^@]+\.[^@]+$/', $_POST['email'])) { |
| 45 | $errors[] = _('The email address is invalid.'); |
| 46 | } |
| 47 | else { |
| 48 | $result = $db->prepare('SELECT `id`, `pwdhash`, `email`, `status`, `verify_hash` FROM `auth_users` WHERE `email` = :email;'); |
| 49 | $result->execute(array(':email' => $_POST['email'])); |
| 50 | $user = $result->fetch(PDO::FETCH_ASSOC); |
| 51 | if ($user['id']) { |
| 52 | // existing user, check password |
| 53 | if (($user['status'] == 'ok') && password_verify(@$_POST['pwd'], $user['pwdhash'])) { |
| 54 | // Check if a newer hashing algorithm is available |
| 55 | // or the cost has changed |
| 56 | if (password_needs_rehash($user['pwdhash'], PASSWORD_DEFAULT, $pwd_options)) { |
| 57 | // If so, create a new hash, and replace the old one |
| 58 | $newHash = password_hash($_POST['pwd'], PASSWORD_DEFAULT, $pwd_options); |
| 59 | $result = $db->prepare('UPDATE `auth_users` SET `pwdhash` = :pwdhash WHERE `id` = :userid;'); |
| 60 | $result->execute(array(':pwdhash' => $newHash, ':userid' => $user['id'])); |
| 61 | } |
| 62 | |
| 63 | // Log user in - update session key for that, see https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines#Login |
| 64 | $sesskey = bin2hex(openssl_random_pseudo_bytes(512/8)); // Get 512 bits of randomness (128 byte hex string). |
| 65 | setcookie('sessionkey', $sesskey, 0, "", "", !$running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost. |
| 66 | $result = $db->prepare('UPDATE `auth_sessions` SET `sesskey` = :sesskey, `user` = :userid, `time_expire` = :expire WHERE `id` = :sessid;'); |
| 67 | $result->execute(array(':sesskey' => $sesskey, ':userid' => $user['id'], ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day')), ':sessid' => $session['id'])); |
| 68 | } |
| 69 | else { |
| 70 | $errors[] = _('This password is invalid or your email is not verified yet. Did you type them correctly?'); |
| 71 | } |
| 72 | } |
| 73 | else { |
| 74 | // new user, check password, then create user and send verification |
| 75 | $new_password = strval(@$_POST['pwd']); |
| 76 | if ($new_password != trim($new_password)) { |
| 77 | $errors[] = _('Password must not start or end with a whitespace character like a space.'); |
| 78 | } |
| 79 | if (strlen($new_password) < 8) { $errors[] = sprintf(_('Password too short (min. %s characters).'), 8); } |
| 80 | if (strlen($new_password) > 70) { $errors[] = sprintf(_('Password too long (max. %s characters).'), 70); } |
| 81 | if (strtolower($new_password) == strtolower($_POST['email'])) { |
| 82 | $errors[] = _('The passwort can not be equal to your email.'); |
| 83 | } |
| 84 | if ((strlen($new_password) < 15) && (preg_match('/^[a-zA-Z]*$/', $new_password))) { |
| 85 | $errors[] = sprintf(_('Your password must use letters other than normal characters or contain least 15 characters.'), 15); |
| 86 | } |
| 87 | if (strlen(count_chars($new_password, 3)) < 5) { |
| 88 | $errors[] = sprintf(_('Password does have to contain at least %s different characters.'), 5); |
| 89 | } |
| 90 | if (!count($errors)) { |
| 91 | // Put user into the DB |
| 92 | $newHash = password_hash($_POST['pwd'], PASSWORD_DEFAULT, $pwd_options); |
| 93 | $vhash = bin2hex(openssl_random_pseudo_bytes(512/8)); // Get 512 bits of randomness (128 byte hex string). |
| 94 | $result = $db->prepare('INSERT INTO `auth_users` (`email`, `pwdhash`, `status`, `verify_hash`) VALUES (:email, :pwdhash, \'unverified\', :vhash);'); |
| 95 | $result->execute(array(':email' => $_POST['email'], ':pwdhash' => $newHash, ':vhash' => $vhash)); |
| 96 | $user = array('id' => $db->lastInsertId(), |
| 97 | 'email' => $_POST['email'], |
| 98 | 'pwdhash' => $newHash, |
| 99 | 'status' => 'unverified', |
| 100 | 'verify_hash' => $vhash); |
| 101 | // Send email for verification and show message to point to it. |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | if (is_null($session)) { |
| 109 | // Create new session and set cookie. |
| 110 | $sesskey = bin2hex(openssl_random_pseudo_bytes(512/8)); // Get 512 bits of randomness (128 byte hex string). |
| 111 | setcookie('sessionkey', $sesskey, 0, "", "", !$running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost. |
| 112 | $result = $db->prepare('INSERT INTO `auth_sessions` (`sesskey`, `time_expire`) VALUES (:sesskey, :expire);'); |
| 113 | $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s', strtotime('+5 minutes')))); |
| 114 | // After insert, actually fetch the session row from the DB so we have all values. |
| 115 | $result = $db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;'); |
| 116 | $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s'))); |
| 117 | $row = $result->fetch(PDO::FETCH_ASSOC); |
| 118 | if ($row) { |
| 119 | $session = $row; |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | if (!count($errors)) { |
| 125 | |
| 126 | if ($session['logged_in']) { |
| 127 | $div = $body->appendElement('div', $user['email']); |
| 128 | $div->setAttribute('class', 'loginheader'); |
| 129 | $div = $body->appendElement('div'); |
| 130 | $div->setAttribute('class', 'loginlinks'); |
| 131 | $link = $div->appendLink('?logout', _('Log out')); |
| 132 | $link->setAttribute('title', _('Log out user of the system')); |
| 133 | } |
| 134 | else { // not logged in |
| 135 | $form = $body->appendForm('#', 'POST', 'loginform'); |
| 136 | $form->setAttribute('id', 'loginform'); |
| 137 | $form->setAttribute('class', 'loginarea hidden'); |
| 138 | $ulist = $form->appendElement('ul'); |
| 139 | $ulist->setAttribute('class', 'flat login'); |
| 140 | $litem = $ulist->appendElement('li'); |
| 141 | $inptxt = $litem->appendInputEmail('email', 30, 20, 'login_email', (intval($user['id'])?$user['email']:'')); |
| 142 | $inptxt->setAttribute('autocomplete', 'email'); |
| 143 | $inptxt->setAttribute('required', ''); |
| 144 | $inptxt->setAttribute('placeholder', _('Email')); |
| 145 | $inptxt->setAttribute('class', 'login'); |
| 146 | $litem = $ulist->appendElement('li'); |
| 147 | $inptxt = $litem->appendInputPassword('pwd', 20, 20, 'login_pwd', ''); |
| 148 | $inptxt->setAttribute('placeholder', _('Password')); |
| 149 | $inptxt->setAttribute('class', 'login'); |
| 150 | $litem = $ulist->appendElement('li'); |
| 151 | $cbox = $litem->appendInputCheckbox('remember', 'login_remember', 'true', false); |
| 152 | $cbox->setAttribute('class', 'logincheck'); |
| 153 | $label = $litem->appendLabel('login_remember', _('Remember me')); |
| 154 | $label->setAttribute('id', 'rememprompt'); |
| 155 | $label->setAttribute('class', 'loginprompt'); |
| 156 | $litem = $ulist->appendElement('li'); |
| 157 | $submit = $litem->appendInputSubmit(_('Log in')); |
| 158 | $submit->setAttribute('class', 'loginbutton'); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | if (count($errors)) { |
| 163 | $body->appendElement('p', ((count($errors) <= 1) |
| 164 | ?_('The following error was detected') |
| 165 | :_('The following errors were detected')).':'); |
| 166 | $list = $body->appendElement('ul'); |
| 167 | $list->setAttribute('class', 'flat warn'); |
| 168 | foreach ($errors as $msg) { |
| 169 | $item = $list->appendElement('li', $msg); |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | // Send HTML to client. |
| 174 | print($document->saveHTML()); |
| 175 | ?> |