Handle passwords in-transit Base64 encoded to allow arbitrary characters / escape sequences (fixes #2892)

This commit is contained in:
Bernd Bestel 2026-03-31 21:19:25 +02:00
parent 763676c936
commit d4bf5d075a
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
7 changed files with 56 additions and 18 deletions

View File

@ -117,7 +117,9 @@ else
$authMiddlewareClass = GROCY_AUTH_CLASS; $authMiddlewareClass = GROCY_AUTH_CLASS;
$app->add(new $authMiddlewareClass($container, $app->getResponseFactory())); $app->add(new $authMiddlewareClass($container, $app->getResponseFactory()));
// Add default middleware // Add default middleware
$app->addBodyParsingMiddleware();
$app->addRoutingMiddleware(); $app->addRoutingMiddleware();
$errorMiddleware = $app->addErrorMiddleware(true, false, false); $errorMiddleware = $app->addErrorMiddleware(true, false, false);
$errorMiddleware->setDefaultErrorHandler( $errorMiddleware->setDefaultErrorHandler(

View File

@ -52,7 +52,7 @@
### General ### General
- xxx - Fixed that it wasn't possible to log in using passwords containing special escape sequences (e.g. `<<`)
### API ### API

View File

@ -22,7 +22,15 @@ class LoginController extends BaseController
public function ProcessLogin(Request $request, Response $response, array $args) public function ProcessLogin(Request $request, Response $response, array $args)
{ {
$authMiddlewareClass = GROCY_AUTH_CLASS; $authMiddlewareClass = GROCY_AUTH_CLASS;
if ($authMiddlewareClass::ProcessLogin($request->getParsedBody()))
$postParams = $request->getParsedBody();
if (isset($postParams['password_base64']))
{
$postParams['password'] = base64_decode($postParams['password_base64']);
}
unset($postParams['password_base64']);
if ($authMiddlewareClass::ProcessLogin($postParams))
{ {
return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/')); return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/'));
} }

View File

@ -43,6 +43,12 @@ class UsersApiController extends BaseApiController
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
} }
if (isset($requestBody['password_base64']))
{
$requestBody['password'] = base64_decode($requestBody['password_base64']);
}
unset($requestBody['password_base64']);
$this->getUsersService()->CreateUser($requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password'], $requestBody['picture_file_name']); $this->getUsersService()->CreateUser($requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password'], $requestBody['picture_file_name']);
return $this->EmptyApiResponse($response); return $this->EmptyApiResponse($response);
} }
@ -81,6 +87,12 @@ class UsersApiController extends BaseApiController
try try
{ {
if (isset($requestBody['password_base64']))
{
$requestBody['password'] = base64_decode($requestBody['password_base64']);
}
unset($requestBody['password_base64']);
$this->getUsersService()->EditUser($args['userId'], $requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password'], $requestBody['picture_file_name']); $this->getUsersService()->EditUser($args['userId'], $requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password'], $requestBody['picture_file_name']);
return $this->EmptyApiResponse($response); return $this->EmptyApiResponse($response);
} }

View File

@ -1,4 +1,4 @@
setTimeout(function() setTimeout(function ()
{ {
$('#username').focus(); $('#username').focus();
}, Grocy.FormFocusDelay); }, Grocy.FormFocusDelay);
@ -8,3 +8,11 @@ if (GetUriParam('invalid') === 'true')
$('#login-error').text(__t('Invalid credentials, please try again')); $('#login-error').text(__t('Invalid credentials, please try again'));
$('#login-error').removeClass('d-none'); $('#login-error').removeClass('d-none');
} }
$("#login-button").on("click", function (e)
{
e.preventDefault();
$("#password_base64").val(btoa($("#password_input").val()));
$("#login-form").trigger("submit");
});

View File

@ -24,7 +24,7 @@
}); });
} }
$('#save-user-button').on('click', function(e) $('#save-user-button').on('click', function (e)
{ {
e.preventDefault(); e.preventDefault();
@ -46,11 +46,16 @@ $('#save-user-button').on('click', function(e)
jsonData.picture_file_name = RandomString() + CleanFileName($("#user-picture")[0].files[0].name); jsonData.picture_file_name = RandomString() + CleanFileName($("#user-picture")[0].files[0].name);
} }
jsonData.password_base64 = btoa(jsonData.password);
delete jsonData.password;
delete jsonData.password_confirm;
delete jsonData.change_password;
if (Grocy.EditMode === 'create') if (Grocy.EditMode === 'create')
{ {
Grocy.Api.Post('users', jsonData, Grocy.Api.Post('users', jsonData,
(result) => SaveUserPicture(result, jsonData), (result) => SaveUserPicture(result, jsonData),
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy("user-form"); Grocy.FrontendHelpers.EndUiBusy("user-form");
console.error(xhr); console.error(xhr);
@ -64,11 +69,11 @@ $('#save-user-button').on('click', function(e)
jsonData.picture_file_name = null; jsonData.picture_file_name = null;
Grocy.Api.DeleteFile(Grocy.UserPictureFileName, 'userpictures', Grocy.Api.DeleteFile(Grocy.UserPictureFileName, 'userpictures',
function(result) function (result)
{ {
// Nothing to do // Nothing to do
}, },
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy("user-form"); Grocy.FrontendHelpers.EndUiBusy("user-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response); Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
@ -78,7 +83,7 @@ $('#save-user-button').on('click', function(e)
Grocy.Api.Put('users/' + Grocy.EditObjectId, jsonData, Grocy.Api.Put('users/' + Grocy.EditObjectId, jsonData,
(result) => SaveUserPicture(result, jsonData), (result) => SaveUserPicture(result, jsonData),
function(xhr) function (xhr)
{ {
Grocy.FrontendHelpers.EndUiBusy("user-form"); Grocy.FrontendHelpers.EndUiBusy("user-form");
console.error(xhr); console.error(xhr);
@ -87,7 +92,7 @@ $('#save-user-button').on('click', function(e)
} }
}); });
$('#user-form input').keyup(function(event) $('#user-form input').keyup(function (event)
{ {
var element = document.getElementById("password_confirm"); var element = document.getElementById("password_confirm");
if ($("#password").val() !== $("#password_confirm").val()) if ($("#password").val() !== $("#password_confirm").val())
@ -102,7 +107,7 @@ $('#user-form input').keyup(function(event)
Grocy.FrontendHelpers.ValidateForm('user-form'); Grocy.FrontendHelpers.ValidateForm('user-form');
}); });
$('#user-form input').keydown(function(event) $('#user-form input').keydown(function (event)
{ {
if (event.keyCode === 13) // Enter if (event.keyCode === 13) // Enter
{ {
@ -119,7 +124,7 @@ $('#user-form input').keydown(function(event)
} }
}); });
$("#user-picture").on("change", function(e) $("#user-picture").on("change", function (e)
{ {
$("#user-picture-label").removeClass("d-none"); $("#user-picture-label").removeClass("d-none");
$("#user-picture-label-none").addClass("d-none"); $("#user-picture-label-none").addClass("d-none");
@ -129,7 +134,7 @@ $("#user-picture").on("change", function(e)
}); });
Grocy.DeleteUserPictureOnSave = false; Grocy.DeleteUserPictureOnSave = false;
$("#delete-current-user-picture-button").on("click", function(e) $("#delete-current-user-picture-button").on("click", function (e)
{ {
Grocy.DeleteUserPictureOnSave = true; Grocy.DeleteUserPictureOnSave = true;
$("#current-user-picture").addClass("d-none"); $("#current-user-picture").addClass("d-none");
@ -138,12 +143,12 @@ $("#delete-current-user-picture-button").on("click", function(e)
$("#user-picture-label-none").removeClass("d-none"); $("#user-picture-label-none").removeClass("d-none");
}); });
$("#change_password").click(function() $("#change_password").click(function ()
{ {
$("#password").attr("disabled", !this.checked); $("#password").attr("disabled", !this.checked);
$("#password_confirm").attr("disabled", !this.checked); $("#password_confirm").attr("disabled", !this.checked);
setTimeout(function() setTimeout(function ()
{ {
$("#password").focus(); $("#password").focus();
}, Grocy.FormFocusDelay); }, Grocy.FormFocusDelay);
@ -155,7 +160,7 @@ if (GetUriParam("changepw") === "true")
} }
else else
{ {
setTimeout(function() setTimeout(function ()
{ {
$('#username').focus(); $('#username').focus();
}, Grocy.FormFocusDelay); }, Grocy.FormFocusDelay);

View File

@ -23,13 +23,16 @@
name="username"> name="username">
</div> </div>
<input type="hidden"
id="password_base64"
name="password_base64">
<div class="form-group"> <div class="form-group">
<label for="password">{{ $__t('Password') }}</label> <label for="password_input">{{ $__t('Password') }}</label>
<input type="password" <input type="password"
class="form-control" class="form-control"
required required
id="password" id="password_input">
name="password">
<div id="login-error" <div id="login-error"
class="form-text text-danger d-none"></div> class="form-text text-danger d-none"></div>
</div> </div>