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

@ -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

@ -46,6 +46,11 @@ $('#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,

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>