Skip to content

Commit

Permalink
[BUGFIX] Provide request-token in backend login refresh dialog
Browse files Browse the repository at this point in the history
The backend login refresh dialog is now passing a request-token
(which is required in `AbstractUserAuthentication`), which is
fetched via AJAX before actually submitting the login credentials
as well via AJAX.

Resolves: #101209
Releases: main, 12.4
Change-Id: I0d3d8a927b7f02791d990eefb2faf98be8aa2efb
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79727
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
  • Loading branch information
ohader committed Jul 5, 2023
1 parent fb9b3e3 commit 74421f6
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 16 deletions.
46 changes: 31 additions & 15 deletions Build/Sources/TypeScript/backend/login-refresh.ts
Expand Up @@ -24,10 +24,16 @@ enum MarkupIdentifiers {

interface LoginRefreshOptions {
intervalTime?: number;
requestTokenUrl?: string;
loginFramesetUrl?: string;
logoutUrl?: string;
}

interface RequestTokenResponseData {
headerName?: string;
requestToken?: string;
}

/**
* Module: @typo3/backend/login-refresh
* @exports @typo3/backend/login-refresh
Expand All @@ -45,6 +51,7 @@ class LoginRefresh {
private $timeoutModal: JQuery = null;
private $backendLockedModal: JQuery = null;
private $loginForm: JQuery = null;
private requestTokenUrl: string = '';
private loginFramesetUrl: string = '';
private logoutUrl: string = '';

Expand Down Expand Up @@ -354,9 +361,15 @@ class LoginRefresh {
*
* @param {JQueryEventObject} event
*/
protected submitForm = (event: JQueryEventObject): void => {
protected submitForm = async (event: JQueryEventObject): Promise<void> => {
event.preventDefault();

const tokenResponse = await new AjaxRequest(this.requestTokenUrl).post({});
const tokenData: RequestTokenResponseData = await tokenResponse.resolve('application/json');

if (!tokenData.headerName || !tokenData.requestToken) {
return;
}
const $form = this.$loginForm.find('form');
const $passwordField = $form.find('input[name=p_field]');
const $useridentField = $form.find('input[name=userident]');
Expand All @@ -373,23 +386,23 @@ class LoginRefresh {
$passwordField.val('');
}

const postData: any = {
login_status: 'login',
};
const postData: Record<string, string> = { login_status: 'login' };
for (const field of $form.serializeArray()) {
postData[field.name] = field.value;
}
new AjaxRequest($form.attr('action')).post(postData).then(async (response: AjaxResponse): Promise<void> => {
const data = await response.resolve();
if (data.login.success) {
// User is logged in
this.hideLoginForm();
} else {
Notification.error(TYPO3.lang['mess.refresh_login_failed'], TYPO3.lang['mess.refresh_login_failed_message']);
$passwordField.focus();
}
});
};
const headers = new Headers();
headers.set(tokenData.headerName, tokenData.requestToken);

const response = await new AjaxRequest($form.attr('action')).post(postData, { headers });
const data = await response.resolve();
if (data.login.success) {
// User is logged in
this.hideLoginForm();
} else {
Notification.error(TYPO3.lang['mess.refresh_login_failed'], TYPO3.lang['mess.refresh_login_failed_message']);
$passwordField.focus();
}
}

/**
* Registers the (shown|hidden).bs.modal events.
Expand Down Expand Up @@ -456,6 +469,9 @@ class LoginRefresh {
if (options.logoutUrl !== undefined) {
this.setLogoutUrl(options.logoutUrl);
}
if (options.requestTokenUrl !== undefined) {
this.requestTokenUrl = options.requestTokenUrl;
}
}
}

Expand Down
Expand Up @@ -94,6 +94,7 @@ public function mainAction(ServerRequestInterface $request): ResponseInterface
JavaScriptModuleInstruction::create('@typo3/backend/login-refresh.js')
->invoke('initialize', [
'intervalTime' => MathUtility::forceIntegerInRange((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['sessionTimeout'] - 60, 60),
'requestTokenUrl' => (string)$this->uriBuilder->buildUriFromRoute('login_request_token'),
'loginFramesetUrl' => (string)$this->uriBuilder->buildUriFromRoute('login_frameset'),
'logoutUrl' => (string)$this->uriBuilder->buildUriFromRoute('logout'),
])
Expand Down
13 changes: 13 additions & 0 deletions typo3/sysext/backend/Classes/Controller/LoginController.php
Expand Up @@ -38,6 +38,7 @@
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Http\PropagateResponseException;
use TYPO3\CMS\Core\Http\RedirectResponse;
Expand Down Expand Up @@ -144,6 +145,18 @@ public function refreshAction(ServerRequestInterface $request): ResponseInterfac
return $this->appendLoginProviderCookie($request, $request->getAttribute('normalizedParams'), $response);
}

/**
* Returns a new request-token value, which is signed by a new nonce value (the nonce is sent
* as cookie automatically in `RequestTokenMiddleware` since it is created via the `NoncePool`).
*/
public function requestTokenAction(ServerRequestInterface $request): ResponseInterface
{
return new JsonResponse([
'headerName' => RequestToken::HEADER_NAME,
'requestToken' => $this->provideRequestTokenJwt(),
]);
}

/**
* @todo: Ugly. This can be used by login providers, they receive an instance of $this.
* Unused in core, though. It should vanish when login providers receive love.
Expand Down
Expand Up @@ -58,6 +58,7 @@ class BackendUserAuthenticator extends \TYPO3\CMS\Core\Middleware\BackendUserAut
'/login/password-reset/initiate-reset',
'/login/password-reset/validate',
'/login/password-reset/finish',
'/login/request-token',
'/install/server-response-check/host',
'/ajax/login',
'/ajax/logout',
Expand Down
8 changes: 8 additions & 0 deletions typo3/sysext/backend/Configuration/Backend/Routes.php
Expand Up @@ -83,6 +83,14 @@
'target' => Controller\LoginController::class . '::refreshAction',
],

// Fetch RequestToken via AJAX
'login_request_token' => [
'path' => '/login/request-token',
'access' => 'public',
'methods' => ['POST'],
'target' => Controller\LoginController::class . '::requestTokenAction',
],

// Authentication endpoint for Multi-factor authentication
'auth_mfa' => [
'path' => '/auth/mfa',
Expand Down

0 comments on commit 74421f6

Please sign in to comment.