536d73971095cafa7edbf2dc531a02258af9733d
[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   $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 ?>