[TikiWiki-commits] [Git][tikiwiki/tiki][30.x] [ENH] User validation: enhance login flow with ticket validation and failure recording options
"Espoir Baraka \(@esbarakabigega\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a856513949c4_3818c89c906a1@gitlab-sidekiq-low-urgency-cpu-bound-v2-7d6dcbfbf4-kp29f.mail> |
Espoir Baraka pushed to branch 30.x at Tiki Wiki CMS Groupware / Tiki
Commits:
ecc88c25 by Espoir Baraka at 2026-08-19T10:04:35+02:00
[ENH] User validation: enhance login flow with ticket validation and failure recording options
---
* [ENH] User validation: enhance login flow with ticket validation and failure recording options
---
* [ENH] User validation: enhance login flow with ticket validation and failure recording options
- Added a new parameter to `validate_user` and `validate_user_tiki` methods to control failure recording.
- Updated `actionValidateUser` to include CSRF protection and rate-limiting for login attempts.
- Modified the login box template to send a security ticket with user credentials.
(cherry picked from commit a82fe4f5a01fb46a382a4f214660215bb318e7bb)
See merge request tikiwiki/tiki!10958
(cherry picked from commit a574dd138b29f7b87a3b25001d24be0087c5bbc9)
See merge request tikiwiki/tiki!10976
- - - - -
3 changed files:
- lib/core/Services/User/Controller.php
- lib/userslib.php
- templates/modules/mod-login_box.tpl
Changes:
=====================================
lib/core/Services/User/Controller.php
=====================================
@@ -4,6 +4,9 @@
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+
+use Tiki\BruteForce\BruteForce;
+
class Services_User_Controller
{
/**
@@ -1426,13 +1429,79 @@ class Services_User_Controller
return true;
}
+ /**
+ * Pre-check credentials for interactive login flows (e.g. 2FA step).
+ *
+ * Keep endpoint pre-auth but hardened: POST + CSRF (single-use ticket),
+ * and the same brute-force policy as tiki-login.
+ *
+ * @param JitFilter $input
+ * @return array{valid: bool, ticket: string|false}
+ * @throws Services_Exception
+ */
public function actionValidateUser($input)
{
+ global $prefs;
+
+ $access = TikiLib::lib('access');
+ $tikilib = TikiLib::lib('tiki');
+
+ if (! $access->requestIsPost()) {
+ throw new Services_Exception(tra('Method not allowed'), 405);
+ }
+
+ if (empty($_POST['ticket'])) {
+ throw new Services_Exception(tra('Missing security ticket'), 401);
+ }
+
+ $access->checkCsrf(false, true, 'hostTicket', true, '', 'services');
+ $access->setTicket();
+
$username = $input->username->text();
$password = $input->password->text();
+ $bruteForceProperties = ['ip' => $tikilib->get_ip_address()];
+
+ if ($username === '' || $password === '') {
+ return [
+ 'valid' => false,
+ 'ticket' => $access->getTicket(),
+ ];
+ }
+
+ if (($prefs['bruteforce_protection'] ?? 'n') === 'y') {
+ $bruteForce = new BruteForce();
+ if (! $bruteForce->isOperationAllowed('login', $bruteForceProperties, false)) {
+ $waitTime = $bruteForce->getWaitTime('login', $bruteForceProperties);
+ if ($waitTime > 60) {
+ $message = sprintf(
+ tra('Too many login attempts. Please try again in %d minutes and %d seconds.'),
+ floor($waitTime / 60),
+ $waitTime % 60
+ );
+ } else {
+ $message = sprintf(tra('Too many login attempts. Please try again in %d seconds.'), $waitTime);
+ }
+ throw new Services_Exception($message, 429);
+ }
+ }
+
$userlib = TikiLib::lib('user');
- $ret = $userlib->validate_user($username, $password);
- return $ret[0];
+ $ret = $userlib->validate_user($username, $password, false, null, false);
+ $valid = (bool) $ret[0];
+
+ if (($prefs['bruteforce_protection'] ?? 'n') === 'y') {
+ $bruteForce = $bruteForce ?? new BruteForce();
+ if ($valid) {
+ $bruteForce->success('login', $bruteForceProperties);
+ } else {
+ $bruteForce->attempt('login', $bruteForceProperties);
+ }
+ }
+
+ return [
+ 'valid' => $valid,
+ 'ticket' => $access->getTicket(),
+ ];
}
public function action_save_column_prefs($input)
=====================================
lib/userslib.php
=====================================
@@ -428,8 +428,9 @@ class UsersLib extends TikiLib
* @param twoFactorCode: ???
* @param validate_phase: If true, user followed the link from validation email after creation of account
* @param for_login: If true, we are validating user for login purpose. If false, we are only checking that user is valid (for check of current password for example)
+ * @param record_failure: If false, unsuccessful attempts are not recorded (no lockout / waiting side effects)
*/
- public function validate_user($user, $pass, $validate_phase = false, $twoFactorCode = null, $for_login = true)
+ public function validate_user($user, $pass, $validate_phase = false, $twoFactorCode = null, $for_login = true, $record_failure = true)
{
global $prefs;
@@ -485,7 +486,7 @@ class UsersLib extends TikiLib
// first attempt a login via the standard Tiki system
//
if (! ($auth_shib || $auth_cas || $auth_saml) || $isAdminGroupMember) { //redflo: does this mean, that users in cas and shib are not replicated to tiki tables? Does this work well?
- list($result, $user) = $this->validate_user_tiki($user, $pass, $validate_phase);
+ list($result, $user) = $this->validate_user_tiki($user, $pass, $validate_phase, $record_failure);
} else {
$result = null;
}
@@ -544,6 +545,9 @@ class UsersLib extends TikiLib
if ($noSpecialAuthEnabled || $adminCanSkip || $ldapUserHasTikiLoginAccess) {
// if the user verified ok, log them in
if ($userTiki) {//user validated in tiki, update lastlogin and be done
+ if (! $for_login) {
+ return [true, $user, $result];
+ }
if ($auth_ldap) {
return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result, 'tiki'];
}
@@ -1951,7 +1955,7 @@ class UsersLib extends TikiLib
* @param user: username
* @param pass: password
*/
- public function validate_user_tiki($user, $pass, $validate_phase = false)
+ public function validate_user_tiki($user, $pass, $validate_phase = false, $record_failure = true)
{
global $prefs;
@@ -2018,7 +2022,9 @@ class UsersLib extends TikiLib
}
return [USER_VALID, $user];
} else {
- $this->handleUnsuccessfulLogin($user);
+ if ($record_failure) {
+ $this->handleUnsuccessfulLogin($user);
+ }
return [PASSWORD_INCORRECT, $user]; // if the password was incorrect, dont give the md5's a spin
}
}
@@ -2036,7 +2042,9 @@ class UsersLib extends TikiLib
return [USER_VALID, $user];
}
- $this->handleUnsuccessfulLogin($user);
+ if ($record_failure) {
+ $this->handleUnsuccessfulLogin($user);
+ }
return [PASSWORD_INCORRECT, $user];
}
=====================================
templates/modules/mod-login_box.tpl
=====================================
@@ -108,12 +108,21 @@ $(document).ready(function () {
function validateUserCredentials(username, password) {
return new Promise((resolve, reject) => {
+ const $form = $("#loginbox-{{$module_logo_instance}}");
+ const ticket = $form.find("input[name=ticket]").val() || "";
$.ajax({
url: $.service("user", "ValidateUser"),
type: 'POST',
- data: { username: username, password: password },
+ data: { username: username, password: password, ticket: ticket },
success: function (res) {
- resolve(res);
+ if (res && typeof res === 'object') {
+ if (res.ticket) {
+ $form.find("input[name=ticket]").val(res.ticket);
+ }
+ resolve(!!res.valid);
+ } else {
+ resolve(!!res);
+ }
},
error: function (req, status, error) {
displayFeedback("error", error);
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/ecc88c25a6b13b51e5dd51ae1b07d57cc012115c
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/ecc88c25a6b13b51e5dd51ae1b07d57cc012115c
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs