1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-12 17:54:44 +02:00

Update ProcessLogin to support login by email address for the admin. To use, you must enable the "unique" flag on your "email" field (Setup > Fields > email > Advanced), and then you can enable login-by-email in the ProcessLogin module settings.

This commit is contained in:
Ryan Cramer
2020-02-14 15:15:28 -05:00
parent eedad3a742
commit af6a68e06d
4 changed files with 145 additions and 23 deletions

View File

@@ -1063,6 +1063,8 @@ class ProcessForgotPassword extends Process implements ConfigurableModule {
$f->description = $f->description =
$this->_('When checked, user will be asked for their email address to reset their password, rather than their username.') . ' ' . $this->_('When checked, user will be asked for their email address to reset their password, rather than their username.') . ' ' .
$this->_('If the email address is used by more than one account, resetting passwords is not possible for those accounts.'); $this->_('If the email address is used by more than one account, resetting passwords is not possible for those accounts.');
$f->notes =
$this->_('Note: the ProcessLogin module will set this automatically at runtime when configured to use email login.');
$f->columnWidth = 50; $f->columnWidth = 50;
if($this->askEmail) $f->attr('checked', 'checked'); if($this->askEmail) $f->attr('checked', 'checked');
$form->add($f); $form->add($f);

View File

@@ -8,10 +8,12 @@
* For more details about how Process modules work, please see: * For more details about how Process modules work, please see:
* /wire/core/Process.php * /wire/core/Process.php
* *
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer * ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com * https://processwire.com
* *
* @property bool $allowForgot Whether the ProcessForgotPassword module is installed. * @property bool $allowForgot Whether the ProcessForgotPassword module is installed.
* @property bool $allowEmail Whether or not email login is allowed.
* @property string $emailField Field name used for email login (when enabled).
* @property array $tfaRecRoleIDs Role IDs where admin prompts/recommends them to enable TFA. * @property array $tfaRecRoleIDs Role IDs where admin prompts/recommends them to enable TFA.
* *
* @method void beforeLogin() #pw-hooker * @method void beforeLogin() #pw-hooker
@@ -23,10 +25,12 @@
* @method string renderLoginForm() #pw-hooker * @method string renderLoginForm() #pw-hooker
* @method InputfieldForm buildLoginForm() #pw-hooker * @method InputfieldForm buildLoginForm() #pw-hooker
* @method void login($name, $pass) #pw-hooker * @method void login($name, $pass) #pw-hooker
* @method void loginFailed($name) #pw-hooker * @method void loginFailed($name, $message = '') #pw-hooker
* @method void loginSuccess(User $user) #pw-hooker * @method void loginSuccess(User $user) #pw-hooker
* @method array getBeforeLoginVars() #pw-hooker * @method array getBeforeLoginVars() #pw-hooker
* *
* @todo add option to select roles allowed to login here (when template is admin)
*
* *
*/ */
@@ -106,12 +110,22 @@ class ProcessLogin extends Process implements ConfigurableModule {
*/ */
protected $tfaLoginSuccess = false; protected $tfaLoginSuccess = false;
/**
* Cached value from useEmailLogin method
*
* @var bool|null
*
*/
protected $useEmailLogin = null;
/** /**
* Construct * Construct
* *
*/ */
public function __construct() { public function __construct() {
$this->set('tfaRecRoleIDs', array()); $this->set('tfaRecRoleIDs', array());
$this->set('allowEmail', false);
$this->set('emailField', 'email');
parent::__construct(); parent::__construct();
} }
@@ -122,12 +136,40 @@ class ProcessLogin extends Process implements ConfigurableModule {
public function init() { public function init() {
$this->id = isset($_GET['id']) ? (int) $_GET['id'] : ''; // id no longer used as anything but a toggle (on/off) $this->id = isset($_GET['id']) ? (int) $_GET['id'] : ''; // id no longer used as anything but a toggle (on/off)
$this->allowForgot = $this->modules->isInstalled('ProcessForgotPassword'); $this->set('allowForgot', $this->modules->isInstalled('ProcessForgotPassword'));
$this->isAdmin = $this->wire('page')->template == 'admin'; $this->isAdmin = $this->wire('page')->template == 'admin';
$this->useEmailLogin = $this->useEmailLogin();
return parent::init(); return parent::init();
} }
/**
* Use login by email?
*
* @return bool
* @since 3.0.151
*
*/
public function useEmailLogin() {
if(is_bool($this->useEmailLogin)) return $this->useEmailLogin;
if(!$this->allowEmail) return false;
if(!$this->emailField) return false;
/** @var Field $field */
$field = $this->fields->get($this->emailField);
if(!$field) return false;
if(!$field->type instanceof FieldtypeEmail) return false;
if(!$field->hasFlag(Field::flagUnique)) return false;
/** @var Template $template */
$template = $this->templates->get($this->config->userTemplateID);
if(!$template || !$template->hasField($field)) return false;
return true;
}
/** /**
* Set URL to redirect to after login success * Set URL to redirect to after login success
* *
@@ -227,6 +269,7 @@ class ProcessLogin extends Process implements ConfigurableModule {
} else if($input->get('forgot') && $this->allowForgot) { } else if($input->get('forgot') && $this->allowForgot) {
/** @var ProcessForgotPassword $process */ /** @var ProcessForgotPassword $process */
$process = $this->modules->get("ProcessForgotPassword"); $process = $this->modules->get("ProcessForgotPassword");
if($this->useEmailLogin()) $process->askEmail = true;
return $process->execute(); return $process->execute();
} }
@@ -240,7 +283,8 @@ class ProcessLogin extends Process implements ConfigurableModule {
return $this->renderLoginForm(); return $this->renderLoginForm();
} }
$name = $this->wire('sanitizer')->pageName($this->nameField->attr('value')); // at this point login form has been submitted
$name = $this->getLoginName();
$pass = substr($this->passField->attr('value'), 0, 128); $pass = substr($this->passField->attr('value'), 0, 128);
if(!$name || !$pass) return $this->renderLoginForm(); if(!$name || !$pass) return $this->renderLoginForm();
@@ -259,6 +303,54 @@ class ProcessLogin extends Process implements ConfigurableModule {
return $this->renderLoginForm(); return $this->renderLoginForm();
} }
/**
* Get login username (whether email or name used)
*
* @return string|bool
* @since 3.0.151
*
*/
protected function getLoginName() {
$value = $this->nameField->attr('value');
if(!strlen($value)) return false;
if(!$this->useEmailLogin()) {
return $this->sanitizer->pageName($value);
}
// at this point we are dealing with an email login
$value = strtolower($this->sanitizer->email($value));
if(empty($value)) return false;
$error = $this->_('Login is not supported for that email address.');
$items = $this->users->find("include=all, $this->emailField=" . $this->sanitizer->selectorValue($value));
if(!$items->count()) {
// fail: no matches
$this->loginFailed($value);
return false;
} else if($items->count() > 1) {
// fail: more than one match
if($this->config->debug) $error .= ' (not unique)';
$this->loginFailed($value, $error);
return false;
}
// success: single match
$user = $items->first();
if($user->status > Page::statusHidden) {
// hidden, unpublished, trash
if($this->config->debug) $error .= ' (inactive)';
$this->loginFailed($value, $error);
return false;
}
return $user->name;
}
/** /**
* Perform login and redirect on success * Perform login and redirect on success
* *
@@ -415,10 +507,16 @@ class ProcessLogin extends Process implements ConfigurableModule {
*/ */
protected function ___buildLoginForm() { protected function ___buildLoginForm() {
if($this->useEmailLogin()) {
$this->nameField = $this->modules->get('InputfieldEmail');
$this->nameField->set('label', $this->_('Email')); // Login form: email field label
} else {
$this->nameField = $this->modules->get('InputfieldText'); $this->nameField = $this->modules->get('InputfieldText');
$this->nameField->set('label', $this->_('Username')); // Login form: username field label $this->nameField->set('label', $this->_('Username')); // Login form: username field label
}
$this->nameField->attr('id+name', 'login_name'); $this->nameField->attr('id+name', 'login_name');
$this->nameField->attr('class', $this->className() . 'Name'); $this->nameField->attr('class', $this->className() . 'Name');
$this->nameField->addClass('InputfieldFocusFirst');
$this->nameField->collapsed = Inputfield::collapsedNever; $this->nameField->collapsed = Inputfield::collapsedNever;
$this->passField = $this->modules->get('InputfieldText'); $this->passField = $this->modules->get('InputfieldText');
@@ -611,11 +709,13 @@ class ProcessLogin extends Process implements ConfigurableModule {
/** /**
* Hook called on login fail * Hook called on login fail
* *
* @param $name * @param string $name
* @param string $message Specify only to override default error message (since 3.0.151)
* *
*/ */
protected function ___loginFailed($name) { protected function ___loginFailed($name, $message = '') {
$this->error("$name - " . $this->_("Login failed")); if(empty($message)) $message = "$name - " . $this->_('Login failed');
$this->error($message);
} }
/** /**
@@ -673,6 +773,23 @@ class ProcessLogin extends Process implements ConfigurableModule {
/** @var Modules $modules */ /** @var Modules $modules */
$modules = $this->wire('modules'); $modules = $this->wire('modules');
/** @var InputfieldRadios $f */
$f = $modules->get('InputfieldRadios');
$f->attr('name', 'allowEmail');
$f->label = $this->_('Login type');
$f->addOption(0, $this->_('User name'));
$f->addOption(1, $this->_('Email address'));
$f->icon = 'sign-in';
$f->val((int) $this->allowEmail);
$emailField = $this->fields->get($this->emailField); /** @var Field $field */
if($emailField && !$emailField->hasFlag(Field::flagUnique)) {
$f->notes = sprintf(
$this->_('To use email login, you must [enable the “unique” setting](%s) for your email field.'),
$emailField->editUrl('flagUnique')
);
}
$inputfields->add($f);
/** @var InputfieldFieldset $fieldset */ /** @var InputfieldFieldset $fieldset */
$fieldset = $modules->get('InputfieldFieldset'); $fieldset = $modules->get('InputfieldFieldset');
$fieldset->label = $this->_('Two-factor authentication'); $fieldset->label = $this->_('Two-factor authentication');

View File

@@ -1967,9 +1967,12 @@ function InputfieldStates($target) {
}); });
// Make the first field in any form have focus, if it is a text field that is blank // Make the first field in any form have focus, if it is a text field that is blank
// $('#content .InputfieldForm:not(.InputfieldNoFocus):not(.InputfieldFormNoFocus)') var $focusInputs = $('input.InputfieldFocusFirst'); // input elements only
$('#content .InputfieldFormFocusFirst:not(.InputfieldFormNoFocus)') if(!$focusInputs.length) {
.find('input[type=text]:enabled:first:not(.hasDatepicker):not(.InputfieldNoFocus)').each(function() { $focusInputs = $('#content .InputfieldFormFocusFirst:not(.InputfieldFormNoFocus)')
.find('input[type=text]:enabled:first:not(.hasDatepicker):not(.InputfieldNoFocus)');
}
if($focusInputs.length) $focusInputs.each(function() {
var $t = $(this); var $t = $(this);
// jump to first input, if it happens to be blank // jump to first input, if it happens to be blank
if($t.val()) return; if($t.val()) return;

File diff suppressed because one or more lines are too long