$session['logged_in'] = 0;
}
if (intval($session['user'])) {
- $result = $db->prepare('SELECT `id`,`email`,`verify_hash` FROM `auth_users` WHERE `id` = :userid;');
+ $result = $db->prepare('SELECT `id`,`email`,`verify_hash`,`group_id` FROM `auth_users` WHERE `id` = :userid;');
$result->execute(array(':userid' => $session['user']));
$user = $result->fetch(PDO::FETCH_ASSOC);
if (!$user['id']) {
$form = $body->appendForm('', 'POST', 'loginauthform');
$form->setAttribute('id', 'loginauthform');
$form->setAttribute('class', 'loginarea');
- $form->appendInputRadio('user_email', 'uemail_'.md5($user['email']), $user['email'], $user['email'] == $user['email']);
- $form->appendLabel('uemail_'.md5($user['email']), $user['email']);
+ $ulist = $form->appendElement('ul');
+ $ulist->setAttribute('class', 'flat emaillist');
+ $emails = $utils->getGroupedEmails($user['group_id']);
+ if (!count($emails)) { $emails = array($user['email']); }
+ foreach ($emails as $email) {
+ $litem = $ulist->appendElement('li');
+ $litem->appendInputRadio('user_email', 'uemail_'.md5($email), $email, $email == $user['email']);
+ $litem->appendLabel('uemail_'.md5($email), $email);
+ }
$para = $form->appendElement('p');
$para->setAttribute('class', 'small otheremaillinks');
$link = $para->appendLink('#', _('Add another email address'));
$para->setAttribute('class', 'small');
$link = $para->appendLink('#', _('Cancel'));
$link->setAttribute('id', 'cancelauth'); // Makes the JS put the right functionality onto the link.
+ $utils->setRedirect($session, $_SERVER['REQUEST_URI']);
}
else {
+ // Switch to different user if we selected a different email within the group.
+ if (strlen(@$_POST['user_email']) && ($_POST['user_email'] != $user['email'])) {
+ $result = $db->prepare('SELECT `id`, `pwdhash`, `email`, `status`, `verify_hash`,`group_id` FROM `auth_users` WHERE `group_id` = :groupid AND `email` = :email;');
+ $result->execute(array(':groupid' => $user['group_id'], ':email' => $_POST['user_email']));
+ $newuser = $result->fetch(PDO::FETCH_ASSOC);
+ if ($newuser) {
+ $user = $newuser;
+ $session = $utils->getLoginSession($user['id'], $session);
+ }
+ }
// Handle authorize request, forwarding code in GET parameters if the user has authorized your client.
$is_authorized = (@$_POST['authorized'] === 'yes');
$server->handleAuthorizeRequest($request, $response, $is_authorized, $user['id']);
exit("SUCCESS! Authorization Code: $code");
}
*/
+ $utils->resetRedirect($session);
$response->send();
exit();
}
$para = $body->appendElement('p', _('You need to log in or register to continue.'));
$para->setAttribute('class', 'logininfo');
$utils->appendLoginForm($body, $session, $user);
- // Save the request in the session so we can get back to fulfilling it.
- $result = $db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
- $saved_request = str_replace('&logout=1', '', $_SERVER['REQUEST_URI']); // Make sure to strip a logout to not get into a loop.
- if (!$result->execute(array(':redir' => $saved_request, ':sessid' => $session['id']))) {
- $utils->log('redir_save_failure', 'session: '.$session['id'].', redirect: '.$saved_request);
- }
+ $utils->setRedirect($session, str_replace('&logout=1', '', $_SERVER['REQUEST_URI'])); // Make sure to strip a logout to not get into a loop.
}
}
margin: 0.5rem 1rem 0;
}
+.loginheader > .groupmails {
+ font-weight: normal;
+ margin: 5px 0; /* IE8 and older do not support rem */
+ margin: 0.5rem 0;
+}
+
.loginlinks {
margin: 5px 10px 0; /* IE8 and older do not support rem */
margin: 0.5rem 1rem 0;
.resetinfo,
.verifyinfo,
+.addemailinfo,
.newpwdinfo,
.signinwelcome {
margin: 5px 10px 0; /* IE8 and older do not support rem */
.otheremaillinks > a:link, .otheremaillinks > a:visited { color: #BBBBBB; }
.otheremaillinks > a:hover, .otheremaillinks > a:active { color: #808080; }
-#addanotheremail { /* HACK - not implemented yet */
- background-color: transparent !important;
- color: transparent !important;
-}
-
.small {
font-size: 0.75em;
}
`pwdhash` VARCHAR(255) NOT NULL ,
`status` ENUM('unverified','ok') NOT NULL DEFAULT 'unverified' ,
`verify_hash` VARCHAR(150) NULL DEFAULT NULL ,
+ `group_id` MEDIUMINT UNSIGNED DEFAULT '0' ,
PRIMARY KEY (`id`),
UNIQUE (`email`)
);
var addAnotherEmail = document.getElementById("addanotheremail");
if (addAnotherEmail) {
addAnotherEmail.onclick = function() {
- // Not implemented yet.
+ location.href = "./?addemail";
}
}
var isNotMe = document.getElementById("isnotme");
// function initSession()
// Initialize a session. Returns an associative array of all the DB fields of the session.
//
+ // function getLoginSession($user)
+ // Return an associative array of a session with the given user logged in (new if user changed compared to given previous session, otherwise updated variant of that previous session).
+ //
+ // function setRedirect($session, $redirect)
+ // Set a redirect on the session for performing later. Returns true if a redirect was saved, otherwise false.
+ //
+ // function doRedirectIfSet($session)
+ // If the session has a redirect set, perform it. Returns true if a redirect was performed, otherwise false.
+ //
+ // function resetRedirect($session)
+ // If the session has a redirect set, remove it. Returns true if a redirect was removed, otherwise false.
+ //
// function getDomainBaseURL()
// Get the base URL of the current domain, e.g. 'https://example.com'.
//
// function pwdNeedsRehash($user)
// Return true if the pwdhash field of the user uses an outdated standard and needs to be rehashed.
//
- // function appendLoginForm($dom_element, $session, $user)
- // append a login form for the given session to the given DOM element, possibly prefilling the email from the given user info array.
+ // function getGroupedEmails($group_id, [$exclude_email])
+ // Return all emails grouped in the specified group ID, optionally exclude a specific email (e.g. because you only want non-current entries)
+ //
+ // function appendLoginForm($dom_element, $session, $user, [$addfields])
+ // Append a login form for the given session to the given DOM element, possibly prefilling the email from the given user info array.
+ // The optional $addfields parameter is an array of name=>value pairs of hidden fields to add to the form.
function __construct($settings, $db) {
// *** constructor ***
return $session;
}
+ function getLoginSession($userid, $prev_session) {
+ $session = $prev_session;
+ $sesskey = $this->createSessionKey();
+ setcookie('sessionkey', $sesskey, 0, "", "", !$this->running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost.
+ // If the previous session has a user set, create a new one - otherwise take existing session entry.
+ if (intval($session['user'])) {
+ $result = $this->db->prepare('INSERT INTO `auth_sessions` (`sesskey`, `time_expire`, `user`, `logged_in`) VALUES (:sesskey, :expire, :userid, TRUE);');
+ $result->execute(array(':sesskey' => $sesskey, ':userid' => $userid, ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day'))));
+ // After insert, actually fetch the session row from the DB so we have all values.
+ $result = $this->db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;');
+ $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s')));
+ $row = $result->fetch(PDO::FETCH_ASSOC);
+ if ($row) {
+ $session = $row;
+ }
+ else {
+ $utils->log('create_session_failure', 'at login, prev session: '.$session['id'].', new user: '.$userid);
+ $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.');
+ }
+ }
+ else {
+ $result = $this->db->prepare('UPDATE `auth_sessions` SET `sesskey` = :sesskey, `user` = :userid, `logged_in` = TRUE, `time_expire` = :expire WHERE `id` = :sessid;');
+ if (!$result->execute(array(':sesskey' => $sesskey, ':userid' => $userid, ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day')), ':sessid' => $session['id']))) {
+ $utils->log('login_failure', 'session: '.$session['id'].', user: '.$userid);
+ $errors[] = _('Login failed unexpectedly. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.');
+ }
+ else {
+ // After update, actually fetch the session row from the DB so we have all values.
+ $result = $this->db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;');
+ $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s')));
+ $row = $result->fetch(PDO::FETCH_ASSOC);
+ if ($row) {
+ $session = $row;
+ }
+ }
+ }
+ return $session;
+ }
+
+ function setRedirect($session, $redirect) {
+ $success = false;
+ // Save the request in the session so we can get back to fulfilling it if one of the links is clicked.
+ $result = $this->db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
+ if (!$result->execute(array(':redir' => $redirect, ':sessid' => $session['id']))) {
+ $this->log('redir_save_failure', 'session: '.$session['id'].', redirect: '.$redirect);
+ }
+ else {
+ $success = true;
+ }
+ return $success;
+ }
+
+ function doRedirectIfSet($session) {
+ $success = false;
+ // If the session has a redirect set, make sure it's performed.
+ if (strlen(@$session['saved_redirect'])) {
+ // Remove redirect.
+ $result = $this->db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
+ if (!$result->execute(array(':redir' => '', ':sessid' => $session['id']))) {
+ $this->log('redir_save_failure', 'session: '.$session['id'].', redirect: (empty)');
+ }
+ else {
+ $success = true;
+ }
+ header('Location: '.$this->getDomainBaseURL().$session['saved_redirect']);
+ }
+ return $success;
+ }
+
+ function resetRedirect($session) {
+ $success = false;
+ // If the session has a redirect set, remove it.
+ if (strlen(@$session['saved_redirect'])) {
+ $result = $this->db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
+ if (!$result->execute(array(':redir' => '', ':sessid' => $session['id']))) {
+ $this->log('redir_save_failure', 'session: '.$session['id'].', redirect: (empty)');
+ }
+ else {
+ $success = true;
+ }
+ }
+ return $success;
+ }
+
function getDomainBaseURL() {
return ($this->running_on_localhost?'http':'https').'://'.$_SERVER['SERVER_NAME'];
}
}
}
- function appendLoginForm($dom_element, $session, $user) {
+ function getGroupedEmails($group_id, $exclude_email = '') {
+ $emails = array();
+ if (intval($group_id)) {
+ $result = $this->db->prepare('SELECT `email` FROM `auth_users` WHERE `group_id` = :groupid AND `status` = \'ok\' AND `email` != :excludemail ORDER BY `email` ASC;');
+ $result->execute(array(':groupid' => $group_id, ':excludemail' => $exclude_email));
+ foreach ($result->fetchAll(PDO::FETCH_ASSOC) as $row) {
+ $emails[] = $row['email'];
+ }
+ }
+ return $emails;
+ }
+
+ function appendLoginForm($dom_element, $session, $user, $addfields = array()) {
$form = $dom_element->appendForm('./', 'POST', 'loginform');
$form->setAttribute('id', 'loginform');
$form->setAttribute('class', 'loginarea hidden');
$label->setAttribute('class', 'loginprompt');
$litem = $ulist->appendElement('li');
$litem->appendInputHidden('tcode', $this->createTimeCode($session));
+ foreach ($addfields as $fname => $fvalue) {
+ $litem->appendInputHidden($fname, $fvalue);
+ }
$submit = $litem->appendInputSubmit(_('Log in / Register'));
$submit->setAttribute('class', 'loginbutton');
}
$errors[] = _('The email address is invalid.');
}
elseif ($utils->verifyTimeCode(@$_POST['tcode'], $session)) {
- $result = $db->prepare('SELECT `id`, `pwdhash`, `email`, `status`, `verify_hash` FROM `auth_users` WHERE `email` = :email;');
+ $result = $db->prepare('SELECT `id`, `pwdhash`, `email`, `status`, `verify_hash`,`group_id` FROM `auth_users` WHERE `email` = :email;');
$result->execute(array(':email' => $_POST['email']));
$user = $result->fetch(PDO::FETCH_ASSOC);
+ // If we need to add the email to a group, note here which user's group we should be added to - otherwise, set to 0.
+ $addgroup = (array_key_exists('grouptoexisting', $_POST) && intval($session['user']) && ($session['user'] != @$user['id'])) ? $session['user'] : 0;
if ($user['id'] && array_key_exists('pwd', $_POST)) {
// existing user, check password
if (($user['status'] == 'ok') && $utils->pwdVerify(@$_POST['pwd'], $user)) {
// Log user in - update session key for that, see https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines#Login
$utils->log('login', 'user: '.$user['id']);
- $sesskey = $utils->createSessionKey();
- setcookie('sessionkey', $sesskey, 0, "", "", !$utils->running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost.
- // If the session has a redirect set, make sure it's performed.
- if (strlen(@$session['saved_redirect'])) {
- header('Location: '.$utils->getDomainBaseURL().$session['saved_redirect']);
- // Remove redirect.
- $result = $db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
- if (!$result->execute(array(':redir' => '', ':sessid' => $session['id']))) {
- $utils->log('redir_save_failure', 'session: '.$session['id'].', redirect: (empty)');
- }
- }
- // If the session has a user set, create a new one - otherwise take existing session entry.
- if (intval($session['user'])) {
- $result = $db->prepare('INSERT INTO `auth_sessions` (`sesskey`, `time_expire`, `user`, `logged_in`) VALUES (:sesskey, :expire, :userid, TRUE);');
- $result->execute(array(':sesskey' => $sesskey, ':userid' => $user['id'], ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day'))));
- // After insert, actually fetch the session row from the DB so we have all values.
- $result = $db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;');
- $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s')));
- $row = $result->fetch(PDO::FETCH_ASSOC);
- if ($row) {
- $session = $row;
- }
- else {
- $utils->log('create_session_failure', 'at login, prev session: '.$session['id'].', new user: '.$user['id']);
- $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.');
- }
- }
- else {
- $result = $db->prepare('UPDATE `auth_sessions` SET `sesskey` = :sesskey, `user` = :userid, `logged_in` = TRUE, `time_expire` = :expire WHERE `id` = :sessid;');
- if (!$result->execute(array(':sesskey' => $sesskey, ':userid' => $user['id'], ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day')), ':sessid' => $session['id']))) {
- $utils->log('login_failure', 'session: '.$session['id'].', user: '.$user['id']);
- $errors[] = _('Login failed unexpectedly. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.');
- }
- else {
- // After update, actually fetch the session row from the DB so we have all values.
- $result = $db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;');
- $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s')));
- $row = $result->fetch(PDO::FETCH_ASSOC);
- if ($row) {
- $session = $row;
- }
- }
- }
+ $prev_session = $session;
+ $session = $utils->getLoginSession($user['id'], $session);
// 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.
if (strlen(@$user['verify_hash'])) {
$result = $db->prepare('UPDATE `auth_users` SET `verify_hash` = \'\' WHERE `id` = :userid;');
$user['verify_hash'] = '';
}
}
+ $utils->doRedirectIfSet($prev_session);
}
else {
$errors[] = _('This password is invalid or your email is not verified yet. Did you type them correctly?');
$vcode = $utils->createVerificationCode();
$result = $db->prepare('INSERT INTO `auth_users` (`email`, `pwdhash`, `status`, `verify_hash`) VALUES (:email, :pwdhash, \'unverified\', :vcode);');
if (!$result->execute(array(':email' => $_POST['email'], ':pwdhash' => $newHash, ':vcode' => $vcode))) {
- $utils->log('user_insert_failure', 'email: '.$_POST['email']);
+ $utils->log('user_insert_failure', 'email: '.$_POST['email'].' - '.$result->errorInfo()[2]);
$errors[] = _('Could not add user. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.');
}
$user = array('id' => $db->lastInsertId(),
}
}
}
+ if (!count($errors) && ($addgroup > 0)) {
+ // We should add the login email to the group of that existing user.
+ $result = $db->prepare('SELECT `group_id` FROM `auth_users` WHERE `id` = :userid;');
+ $result->execute(array(':userid' => $addgroup));
+ $grpuser = $result->fetch(PDO::FETCH_ASSOC);
+ if (!intval($grpuser['group_id'])) {
+ // If that user doesn't have a group, put him into a group with his own user ID.
+ $result = $db->prepare('UPDATE `auth_users` SET `group_id` = :groupid WHERE `id` = :userid;');
+ if (!$result->execute(array(':groupid' => $addgroup, ':userid' => $addgroup))) {
+ $utils->log('group_save_failure', 'user: '.$addgroup);
+ }
+ else {
+ $utils->log('new grouping', 'user: '.$addgroup.', group: '.$addgroup);
+ }
+ }
+ // Save grouping for the new or logged-in user.
+ $result = $db->prepare('UPDATE `auth_users` SET `group_id` = :groupid WHERE `id` = :userid;');
+ if (!$result->execute(array(':groupid' => $addgroup, ':userid' => $user['id']))) {
+ $utils->log('group_save_failure', 'user: '.$user['id']);
+ }
+ else {
+ $utils->log('new grouping', 'user: '.$user['id'].', group: '.$addgroup);
+ $user['group_id'] = $addgroup;
+ }
+ }
}
else {
$errors[] = _('The form you used was not valid. Possibly it has expired and you need to initiate the action again, or you have disabled cookies for this site.');
}
}
elseif (intval($session['user'])) {
- $result = $db->prepare('SELECT `id`,`email`,`verify_hash` FROM `auth_users` WHERE `id` = :userid;');
+ $result = $db->prepare('SELECT `id`,`email`,`verify_hash`,`group_id` FROM `auth_users` WHERE `id` = :userid;');
$result->execute(array(':userid' => $session['user']));
$user = $result->fetch(PDO::FETCH_ASSOC);
if (!$user['id']) {
}
}
}
+ else {
+ $utils->doRedirectIfSet($session);
+ }
}
}
$para->setAttribute('class', 'toplink');
$link = $para->appendLink('./', _('Back to top'));
}
- elseif ($session['logged_in']) {
+ elseif ($session['logged_in'] && (!array_key_exists('addemail', $_GET))) {
if ($pagetype == 'reset_done') {
$para = $body->appendElement('p', _('Your password has successfully been reset.'));
$para->setAttribute('class', 'resetinfo done');
}
$div = $body->appendElement('div', $user['email']);
$div->setAttribute('class', 'loginheader');
+ $groupmails = $utils->getGroupedEmails($user['group_id'], $user['email']);
+ if (count($groupmails)) {
+ $para = $div->appendElement('p', _('Grouped with: ').implode(', ', $groupmails));
+ $para->setAttribute('class', 'small groupmails');
+ }
$div = $body->appendElement('div');
$div->setAttribute('class', 'loginlinks');
$ulist = $div->appendElement('ul');
$ulist->setAttribute('class', 'flat');
$litem = $ulist->appendElement('li');
$link = $litem->appendLink('./?logout', _('Log out'));
+ $litem = $ulist->appendElement('li');
+ $link = $litem->appendLink('./?addemail', _('Add another email address'));
if (in_array($user['email'], $utils->client_reg_email_whitelist)) {
$litem = $ulist->appendElement('li');
$link = $litem->appendLink('./?clients', _('Manage OAuth2 clients'));
$litem->appendLink('./?reset', _('Set new password'));
}
else { // not logged in
+ $addfields = array();
if ($pagetype == 'verification_done') {
$para = $body->appendElement('p', _('Hooray! Your email was successfully confirmed! You can log in now.'));
$para->setAttribute('class', 'verifyinfo done');
$para = $body->appendElement('p', _('Your password has successfully been reset. You can log in now with the new password.'));
$para->setAttribute('class', 'resetinfo done');
}
- $utils->appendLoginForm($body, $session, $user);
+ elseif (array_key_exists('addemail', $_GET)) {
+ $para = $body->appendElement('p', sprintf(_('Add another email grouped with %s by either logging in with it or specifying the email and a new password to use.'), $user['email']));
+ $para->setAttribute('class', 'addemailinfo');
+ $addfields['grouptoexisting'] = '1';
+ }
+ $utils->appendLoginForm($body, $session, $user, $addfields);
}
}