mirror of
https://github.com/processwire/processwire.git
synced 2025-08-11 09:14:58 +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:
@@ -1063,6 +1063,8 @@ class ProcessForgotPassword extends Process implements ConfigurableModule {
|
||||
$f->description =
|
||||
$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.');
|
||||
$f->notes =
|
||||
$this->_('Note: the ProcessLogin module will set this automatically at runtime when configured to use email login.');
|
||||
$f->columnWidth = 50;
|
||||
if($this->askEmail) $f->attr('checked', 'checked');
|
||||
$form->add($f);
|
||||
|
@@ -8,10 +8,12 @@
|
||||
* For more details about how Process modules work, please see:
|
||||
* /wire/core/Process.php
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @method void beforeLogin() #pw-hooker
|
||||
@@ -23,10 +25,12 @@
|
||||
* @method string renderLoginForm() #pw-hooker
|
||||
* @method InputfieldForm buildLoginForm() #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 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;
|
||||
|
||||
/**
|
||||
* Cached value from useEmailLogin method
|
||||
*
|
||||
* @var bool|null
|
||||
*
|
||||
*/
|
||||
protected $useEmailLogin = null;
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->set('tfaRecRoleIDs', array());
|
||||
$this->set('allowEmail', false);
|
||||
$this->set('emailField', 'email');
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -122,12 +136,40 @@ class ProcessLogin extends Process implements ConfigurableModule {
|
||||
public function init() {
|
||||
|
||||
$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->useEmailLogin = $this->useEmailLogin();
|
||||
|
||||
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
|
||||
*
|
||||
@@ -227,6 +269,7 @@ class ProcessLogin extends Process implements ConfigurableModule {
|
||||
} else if($input->get('forgot') && $this->allowForgot) {
|
||||
/** @var ProcessForgotPassword $process */
|
||||
$process = $this->modules->get("ProcessForgotPassword");
|
||||
if($this->useEmailLogin()) $process->askEmail = true;
|
||||
return $process->execute();
|
||||
}
|
||||
|
||||
@@ -239,8 +282,9 @@ class ProcessLogin extends Process implements ConfigurableModule {
|
||||
$this->beforeLogin();
|
||||
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);
|
||||
|
||||
if(!$name || !$pass) return $this->renderLoginForm();
|
||||
@@ -259,6 +303,54 @@ class ProcessLogin extends Process implements ConfigurableModule {
|
||||
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
|
||||
*
|
||||
@@ -415,10 +507,16 @@ class ProcessLogin extends Process implements ConfigurableModule {
|
||||
*/
|
||||
protected function ___buildLoginForm() {
|
||||
|
||||
$this->nameField = $this->modules->get('InputfieldText');
|
||||
$this->nameField->set('label', $this->_('Username')); // Login form: username field label
|
||||
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->set('label', $this->_('Username')); // Login form: username field label
|
||||
}
|
||||
$this->nameField->attr('id+name', 'login_name');
|
||||
$this->nameField->attr('class', $this->className() . 'Name');
|
||||
$this->nameField->addClass('InputfieldFocusFirst');
|
||||
$this->nameField->collapsed = Inputfield::collapsedNever;
|
||||
|
||||
$this->passField = $this->modules->get('InputfieldText');
|
||||
@@ -611,11 +709,13 @@ class ProcessLogin extends Process implements ConfigurableModule {
|
||||
/**
|
||||
* 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) {
|
||||
$this->error("$name - " . $this->_("Login failed"));
|
||||
protected function ___loginFailed($name, $message = '') {
|
||||
if(empty($message)) $message = "$name - " . $this->_('Login failed');
|
||||
$this->error($message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -673,6 +773,23 @@ class ProcessLogin extends Process implements ConfigurableModule {
|
||||
/** @var Modules $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 */
|
||||
$fieldset = $modules->get('InputfieldFieldset');
|
||||
$fieldset->label = $this->_('Two-factor authentication');
|
||||
|
@@ -1967,18 +1967,21 @@ function InputfieldStates($target) {
|
||||
});
|
||||
|
||||
// Make the first field in any form have focus, if it is a text field that is blank
|
||||
// $('#content .InputfieldForm:not(.InputfieldNoFocus):not(.InputfieldFormNoFocus)')
|
||||
$('#content .InputfieldFormFocusFirst:not(.InputfieldFormNoFocus)')
|
||||
.find('input[type=text]:enabled:first:not(.hasDatepicker):not(.InputfieldNoFocus)').each(function() {
|
||||
var $t = $(this);
|
||||
// jump to first input, if it happens to be blank
|
||||
if($t.val()) return;
|
||||
// avoid jumping to inputs that fall "below the fold"
|
||||
if($t.offset().top < $(window).height()) {
|
||||
window.setTimeout(function () {
|
||||
if($t.is(":visible")) $t.focus();
|
||||
}, 250);
|
||||
}
|
||||
var $focusInputs = $('input.InputfieldFocusFirst'); // input elements only
|
||||
if(!$focusInputs.length) {
|
||||
$focusInputs = $('#content .InputfieldFormFocusFirst:not(.InputfieldFormNoFocus)')
|
||||
.find('input[type=text]:enabled:first:not(.hasDatepicker):not(.InputfieldNoFocus)');
|
||||
}
|
||||
if($focusInputs.length) $focusInputs.each(function() {
|
||||
var $t = $(this);
|
||||
// jump to first input, if it happens to be blank
|
||||
if($t.val()) return;
|
||||
// avoid jumping to inputs that fall "below the fold"
|
||||
if($t.offset().top < $(window).height()) {
|
||||
window.setTimeout(function () {
|
||||
if($t.is(":visible")) $t.focus();
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
// confirm changed forms that user navigates away from before submitting
|
||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user