From 03ca19f59498a886154639e8f8f2ac7346794a36 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Wed, 4 Dec 2019 09:37:49 +0100 Subject: [PATCH 1/6] !!! FEATURE: Refactor login form to Fusion --- .../Controller/AuthenticationController.php | 33 ++++++++- Resources/Private/Fusion/Backend/Root.fusion | 71 +++++++++++++++++++ .../Private/Fusion/NodeTypes/LoginForm.fusion | 24 ++++--- .../Templates/Authentication/Index.html | 39 ---------- composer.json | 6 +- 5 files changed, 120 insertions(+), 53 deletions(-) create mode 100644 Resources/Private/Fusion/Backend/Root.fusion delete mode 100644 Resources/Private/Templates/Authentication/Index.html diff --git a/Classes/Controller/AuthenticationController.php b/Classes/Controller/AuthenticationController.php index 45fb305..3ebb61c 100644 --- a/Classes/Controller/AuthenticationController.php +++ b/Classes/Controller/AuthenticationController.php @@ -18,14 +18,23 @@ use Neos\Flow\I18n\Translator; use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\Exception\UnsupportedRequestTypeException; +use Neos\Flow\Mvc\FlashMessage\FlashMessageService; +use Neos\Flow\Mvc\View\ViewInterface; use Neos\Flow\Security\Authentication\Controller\AbstractAuthenticationController; use Neos\Flow\Security\Exception\AuthenticationRequiredException; +use Neos\Fusion\View\FusionView; /** * Controller for displaying a login/logout form and authenticating/logging out "frontend users" */ class AuthenticationController extends AbstractAuthenticationController { + + /** + * @var FusionView + */ + protected $defaultViewObjectName = FusionView::class; + /** * @var Translator * @Flow\Inject @@ -44,12 +53,21 @@ class AuthenticationController extends AbstractAuthenticationController */ protected $translationSourceName; + /** + * @Flow\Inject + * @var FlashMessageService + */ + protected $flashMessageService; + /** * @return void */ public function indexAction(): void { - $this->view->assign('account', $this->securityContext->getAccount()); + $this->view->assignMultiple([ + 'account' => $this->securityContext->getAccount(), + 'flashMessages' => $this->flashMessageService->getFlashMessageContainerForRequest($this->request)->getMessagesAndFlush(), + ]); } /** @@ -117,4 +135,17 @@ protected function getErrorFlashMessage() { return false; } + + /** + * Sets the Fusion path pattern on the view. + * + * @param ViewInterface $view + * @return void + */ + protected function initializeView(ViewInterface $view) + { + parent::initializeView($view); + /** @var FusionView $view */ + $view->setFusionPathPattern('resource://Flowpack.Neos.FrontendLogin/Private/Fusion/Backend'); + } } diff --git a/Resources/Private/Fusion/Backend/Root.fusion b/Resources/Private/Fusion/Backend/Root.fusion new file mode 100644 index 0000000..0bc17ce --- /dev/null +++ b/Resources/Private/Fusion/Backend/Root.fusion @@ -0,0 +1,71 @@ +include: resource://Neos.Fusion/Private/Fusion/Root.fusion +include: resource://Neos.Fusion.Form/Private/Fusion/Root.fusion + +Flowpack.Neos.FrontendLogin.AuthenticationController.index = Neos.Fusion:Component { + account = ${account} + flashMessages = ${flashMessages} + + settings = ${Configuration.setting('Flowpack.Neos.FrontendLogin')} + + renderer = afx` +
+
+

+ {I18n.id('authentication.backendmessage').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} +

+
+

+ {I18n.id('authentication.form.loggedIn').value('You are logged in as').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()}  + "{props.account.accountIdentifier}" +

+ + + {I18n.id('authentication.form.logout').value('Logout').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} + + +
+
+
    + +
  • {flashMessage.message}
  • +
    +
+ + +
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + {I18n.id('authentication.form.submit').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} + +
+
+
+
+
+
+ ` +} diff --git a/Resources/Private/Fusion/NodeTypes/LoginForm.fusion b/Resources/Private/Fusion/NodeTypes/LoginForm.fusion index 8932f06..1602cc6 100644 --- a/Resources/Private/Fusion/NodeTypes/LoginForm.fusion +++ b/Resources/Private/Fusion/NodeTypes/LoginForm.fusion @@ -1,16 +1,18 @@ ## # "LoginForm" element, extending "Plugin" # -prototype(Flowpack.Neos.FrontendLogin:LoginForm) < prototype(Neos.Neos:Plugin) { - package = 'Flowpack.Neos.FrontendLogin' - controller = 'Authentication' - action = 'index' +prototype(Flowpack.Neos.FrontendLogin:LoginForm) < prototype(Neos.Neos:ContentComponent) { + renderer = Neos.Neos:Plugin { + package = 'Flowpack.Neos.FrontendLogin' + controller = 'Authentication' + action = 'index' - redirectAfterLoginUri = Neos.Neos:NodeUri { - node = ${q(node).property('redirectAfterLogin')} - } + redirectAfterLoginUri = Neos.Neos:NodeUri { + node = ${q(node).property('redirectAfterLogin')} + } - redirectAfterLogoutUri = Neos.Neos:NodeUri { - node = ${q(node).property('redirectAfterLogout')} - } -} \ No newline at end of file + redirectAfterLogoutUri = Neos.Neos:NodeUri { + node = ${q(node).property('redirectAfterLogout')} + } + } +} diff --git a/Resources/Private/Templates/Authentication/Index.html b/Resources/Private/Templates/Authentication/Index.html deleted file mode 100644 index 76c373d..0000000 --- a/Resources/Private/Templates/Authentication/Index.html +++ /dev/null @@ -1,39 +0,0 @@ -{namespace neos=Neos\Neos\ViewHelpers} -
-
- - -

-
- - - -

You're logged in as "{account.accountIdentifier}"

- - - -
- - - - -
- - -
-
- - -
-
-
- -
-
-
-
-
-
-
-
-
diff --git a/composer.json b/composer.json index 7d7b2d4..3703a93 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,9 @@ "description": "Neos plugin demonstrating a simple frontend login", "license": "MIT", "require": { - "neos/neos": "^5.0 || dev-master" + "neos/neos": "^5.0 || dev-master", + "neos/fusion-form": "^1.0 || dev-master" + "neos/fusion-afx": "^1.4 || dev-master" }, "autoload": { "psr-4": { @@ -13,7 +15,7 @@ }, "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "5.0.x-dev" }, "applied-flow-migrations": [ "TYPO3.FLOW3-201201261636", From 0d53ebc2840f7473f0d32059237e5fb52ea295f5 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 5 Dec 2019 17:50:05 +0100 Subject: [PATCH 2/6] BUGFIX: Fix composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3703a93..7f04455 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "MIT", "require": { "neos/neos": "^5.0 || dev-master", - "neos/fusion-form": "^1.0 || dev-master" + "neos/fusion-form": "^1.0 || dev-master", "neos/fusion-afx": "^1.4 || dev-master" }, "autoload": { From 02a5c0a5d43bf12d54614bea83a90282c9bb63bc Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 5 Dec 2019 18:27:12 +0100 Subject: [PATCH 3/6] TASK: Refactor the login-form to a pluginless approach --- .../Controller/AuthenticationController.php | 59 +--------- Configuration/Routes.yaml | 21 ++++ Configuration/Settings.yaml | 4 + Resources/Private/Fusion/Backend/Root.fusion | 71 ------------ .../Private/Fusion/NodeTypes/LoginForm.fusion | 108 ++++++++++++++++-- 5 files changed, 127 insertions(+), 136 deletions(-) create mode 100644 Configuration/Routes.yaml delete mode 100644 Resources/Private/Fusion/Backend/Root.fusion diff --git a/Classes/Controller/AuthenticationController.php b/Classes/Controller/AuthenticationController.php index 3ebb61c..7ba4ad2 100644 --- a/Classes/Controller/AuthenticationController.php +++ b/Classes/Controller/AuthenticationController.php @@ -30,11 +30,6 @@ class AuthenticationController extends AbstractAuthenticationController { - /** - * @var FusionView - */ - protected $defaultViewObjectName = FusionView::class; - /** * @var Translator * @Flow\Inject @@ -59,17 +54,6 @@ class AuthenticationController extends AbstractAuthenticationController */ protected $flashMessageService; - /** - * @return void - */ - public function indexAction(): void - { - $this->view->assignMultiple([ - 'account' => $this->securityContext->getAccount(), - 'flashMessages' => $this->flashMessageService->getFlashMessageContainerForRequest($this->request)->getMessagesAndFlush(), - ]); - } - /** * return void */ @@ -78,12 +62,7 @@ public function logoutAction() parent::logoutAction(); $uri = $this->request->getInternalArgument('__redirectAfterLogoutUri'); - - if (empty($uri)) { - $this->redirect('index'); - } else { - $this->redirectToUri($uri); - } + $this->redirectToUri($uri); } /** @@ -94,12 +73,7 @@ public function logoutAction() protected function onAuthenticationSuccess(ActionRequest $originalRequest = null) { $uri = $this->request->getInternalArgument('__redirectAfterLoginUri'); - - if (empty($uri)) { - $this->redirect('index'); - } else { - $this->redirectToUri($uri); - } + $this->redirectToUri($uri); } /** @@ -110,20 +84,8 @@ protected function onAuthenticationSuccess(ActionRequest $originalRequest = null */ protected function onAuthenticationFailure(AuthenticationRequiredException $exception = null) { - $title = $this->getTranslationById('authentication.failure.title'); - $message = $this->getTranslationById('authentication.failure.message'); - $this->addFlashMessage($message, $title, Error\Message::SEVERITY_ERROR, [], $exception === null ? 1496914553 : $exception->getCode()); - } - - /** - * Get translation by label id for configured source name and package key - * - * @param string $labelId Key to use for finding translation - * @return string Translated message or NULL on failure - */ - protected function getTranslationById($labelId): string - { - return $this->translator->translateById($labelId, [], null, null, $this->translationSourceName, $this->translationPackageKey); + $uri = $this->request->getInternalArgument('__redirectAfterFailureUri'); + $this->redirectToUri($uri); } /** @@ -135,17 +97,4 @@ protected function getErrorFlashMessage() { return false; } - - /** - * Sets the Fusion path pattern on the view. - * - * @param ViewInterface $view - * @return void - */ - protected function initializeView(ViewInterface $view) - { - parent::initializeView($view); - /** @var FusionView $view */ - $view->setFusionPathPattern('resource://Flowpack.Neos.FrontendLogin/Private/Fusion/Backend'); - } } diff --git a/Configuration/Routes.yaml b/Configuration/Routes.yaml new file mode 100644 index 0000000..78f8447 --- /dev/null +++ b/Configuration/Routes.yaml @@ -0,0 +1,21 @@ + + +- + name: 'Frontend Authenticate' + uriPattern: 'frontend/login(.{@format})' + defaults: + '@package': 'Flowpack.Neos.FrontendLogin' + '@controller': 'Authentication' + '@action': 'authenticate' + '@format': 'html' + httpMethods: ['POST'] + +- + name: 'Frontend Logout' + uriPattern: 'frontend/logout(.{@format})' + defaults: + '@package': 'Flowpack.Neos.FrontendLogin' + '@controller': 'Authentication' + '@action': 'logout' + '@format': 'html' + httpMethods: ['POST'] diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 10c126c..5238cc3 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -14,6 +14,10 @@ Neos: pattern: Flowpack\Neos\FrontendLogin\Security\NeosRequestPattern patternOptions: matchFrontend: true + mvc: + routes: + Flowpack.Neos.FrontendLogin: + position: 'before Neos.Neos' Neos: userInterface: translation: diff --git a/Resources/Private/Fusion/Backend/Root.fusion b/Resources/Private/Fusion/Backend/Root.fusion deleted file mode 100644 index 0bc17ce..0000000 --- a/Resources/Private/Fusion/Backend/Root.fusion +++ /dev/null @@ -1,71 +0,0 @@ -include: resource://Neos.Fusion/Private/Fusion/Root.fusion -include: resource://Neos.Fusion.Form/Private/Fusion/Root.fusion - -Flowpack.Neos.FrontendLogin.AuthenticationController.index = Neos.Fusion:Component { - account = ${account} - flashMessages = ${flashMessages} - - settings = ${Configuration.setting('Flowpack.Neos.FrontendLogin')} - - renderer = afx` -
-
-

- {I18n.id('authentication.backendmessage').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} -

-
-

- {I18n.id('authentication.form.loggedIn').value('You are logged in as').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()}  - "{props.account.accountIdentifier}" -

- - - {I18n.id('authentication.form.logout').value('Logout').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} - - -
-
-
    - -
  • {flashMessage.message}
  • -
    -
- - -
- -
- -
-
-
- -
- -
-
-
-
- - {I18n.id('authentication.form.submit').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} - -
-
-
-
-
-
- ` -} diff --git a/Resources/Private/Fusion/NodeTypes/LoginForm.fusion b/Resources/Private/Fusion/NodeTypes/LoginForm.fusion index 1602cc6..2fe31d7 100644 --- a/Resources/Private/Fusion/NodeTypes/LoginForm.fusion +++ b/Resources/Private/Fusion/NodeTypes/LoginForm.fusion @@ -1,18 +1,106 @@ ## -# "LoginForm" element, extending "Plugin" +# "LoginForm" element, extending "ContentComponent" # prototype(Flowpack.Neos.FrontendLogin:LoginForm) < prototype(Neos.Neos:ContentComponent) { - renderer = Neos.Neos:Plugin { - package = 'Flowpack.Neos.FrontendLogin' - controller = 'Authentication' - action = 'index' - - redirectAfterLoginUri = Neos.Neos:NodeUri { - node = ${q(node).property('redirectAfterLogin')} + renderer = Neos.Fusion:Case { + loggedIn { + condition = ${Security.hasRole('Flowpack.Neos.FrontendLogin:User')} + renderer = Flowpack.Neos.FrontendLogin:LoginForm.Logout { + accountIdentifier = ${Security.getAccount().accountIdentifier} + redirectAfterLogoutUri = Neos.Neos:NodeUri { + node = ${q(node).property('redirectAfterLogout')} + } + } } - redirectAfterLogoutUri = Neos.Neos:NodeUri { - node = ${q(node).property('redirectAfterLogout')} + default { + condition = true + renderer = Flowpack.Neos.FrontendLogin:LoginForm.Login { + redirectAfterLoginUri = Neos.Neos:NodeUri { + node = ${q(node).property('redirectAfterLogin')} + } + redirectAfterFailureUri = Neos.Neos:NodeUri { + node = ${documentNode} + } + } } } } + +prototype(Flowpack.Neos.FrontendLogin:LoginForm.Login) < prototype(Neos.Fusion:Component) { + + redirectAfterLoginUri = null + redirectAfterFailureUri = null + + settings = ${Configuration.setting('Flowpack.Neos.FrontendLogin')} + + renderer = afx` +
    + +
  • {flashMessage.message}
  • +
    +
+ + + + + +
+ + +
+
+ + +
+
+ + {I18n.id('authentication.form.submit').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} + +
+
+ ` +} + +prototype(Flowpack.Neos.FrontendLogin:LoginForm.Logout) < prototype(Neos.Fusion:Component) { + + redirectAfterLogoutUri = null + accountIdentifier = null + + settings = ${Configuration.setting('Flowpack.Neos.FrontendLogin')} + + renderer = afx` +

+ {I18n.id('authentication.form.loggedIn').value('You are logged in as').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()}  + {props.accountIdentifier} +

+ + + + + {I18n.id('authentication.form.logout').value('Logout').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} + + + ` +} From 3b055b5b27a0743f341f3c3d4157ddfe01b63b34 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Fri, 6 Dec 2019 08:48:40 +0100 Subject: [PATCH 4/6] TASK: Add dynamic cache segment to cache the login form until an fe-user is logged in --- .../Private/Fusion/NodeTypes/LoginForm.fusion | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Resources/Private/Fusion/NodeTypes/LoginForm.fusion b/Resources/Private/Fusion/NodeTypes/LoginForm.fusion index 2fe31d7..e812f8a 100644 --- a/Resources/Private/Fusion/NodeTypes/LoginForm.fusion +++ b/Resources/Private/Fusion/NodeTypes/LoginForm.fusion @@ -25,6 +25,21 @@ prototype(Flowpack.Neos.FrontendLogin:LoginForm) < prototype(Neos.Neos:ContentCo } } } + + @cache { + mode = 'dynamic' + entryIdentifier { + node = ${node} + } + entryDiscriminator = ${Security.hasRole('Flowpack.Neos.FrontendLogin:User') ? false : 'no-fe-user'} + context { + 1 = 'node' + 2 = 'documentNode' + } + entryTags { + 1 = ${Neos.Caching.nodeTag(node)} + } + } } prototype(Flowpack.Neos.FrontendLogin:LoginForm.Login) < prototype(Neos.Fusion:Component) { From 4d48498251bab5f508185b379cee474e5c415038 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Fri, 6 Dec 2019 09:52:06 +0100 Subject: [PATCH 5/6] TASK: Add failure message via validation results --- .../Controller/AuthenticationController.php | 23 ++++++++++++++++--- .../Private/Fusion/NodeTypes/LoginForm.fusion | 22 ++++++++---------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Classes/Controller/AuthenticationController.php b/Classes/Controller/AuthenticationController.php index 7ba4ad2..3119c9d 100644 --- a/Classes/Controller/AuthenticationController.php +++ b/Classes/Controller/AuthenticationController.php @@ -77,15 +77,32 @@ protected function onAuthenticationSuccess(ActionRequest $originalRequest = null } /** - * Create translated FlashMessage and add it to flashMessageContainer + * Create add a validation error and send the request back to the referrer * * @param AuthenticationRequiredException $exception * @return void */ protected function onAuthenticationFailure(AuthenticationRequiredException $exception = null) { - $uri = $this->request->getInternalArgument('__redirectAfterFailureUri'); - $this->redirectToUri($uri); + $referringRequest = $this->request->getReferringRequest(); + if ($referringRequest === null) { + return; + } + + $validationResults = new Error\Result(); + $validationResults->addError(new Error\Error('authenticationFailure')); + + $packageKey = $referringRequest->getControllerPackageKey(); + $subpackageKey = $referringRequest->getControllerSubpackageKey(); + if ($subpackageKey !== null) { + $packageKey .= '\\' . $subpackageKey; + } + + $argumentsForNextController = $referringRequest->getArguments(); + $argumentsForNextController['__submittedArguments'] = []; + $argumentsForNextController['__submittedArgumentValidationResults'] = $validationResults; + + $this->forward($referringRequest->getControllerActionName(), $referringRequest->getControllerName(), $packageKey , $argumentsForNextController); } /** diff --git a/Resources/Private/Fusion/NodeTypes/LoginForm.fusion b/Resources/Private/Fusion/NodeTypes/LoginForm.fusion index e812f8a..0574823 100644 --- a/Resources/Private/Fusion/NodeTypes/LoginForm.fusion +++ b/Resources/Private/Fusion/NodeTypes/LoginForm.fusion @@ -19,9 +19,6 @@ prototype(Flowpack.Neos.FrontendLogin:LoginForm) < prototype(Neos.Neos:ContentCo redirectAfterLoginUri = Neos.Neos:NodeUri { node = ${q(node).property('redirectAfterLogin')} } - redirectAfterFailureUri = Neos.Neos:NodeUri { - node = ${documentNode} - } } } } @@ -31,7 +28,7 @@ prototype(Flowpack.Neos.FrontendLogin:LoginForm) < prototype(Neos.Neos:ContentCo entryIdentifier { node = ${node} } - entryDiscriminator = ${Security.hasRole('Flowpack.Neos.FrontendLogin:User') ? false : 'no-fe-user'} + entryDiscriminator = ${(Security.hasRole('Flowpack.Neos.FrontendLogin:User') || request.internalArguments.__submittedArgumentValidationResults.errors) ? false : 'no-fe-user'} context { 1 = 'node' 2 = 'documentNode' @@ -45,17 +42,10 @@ prototype(Flowpack.Neos.FrontendLogin:LoginForm) < prototype(Neos.Neos:ContentCo prototype(Flowpack.Neos.FrontendLogin:LoginForm.Login) < prototype(Neos.Fusion:Component) { redirectAfterLoginUri = null - redirectAfterFailureUri = null settings = ${Configuration.setting('Flowpack.Neos.FrontendLogin')} renderer = afx` -
    - -
  • {flashMessage.message}
  • -
    -
- - + +
+

+ {I18n.id('authentication.failure.title').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} +

+

+ {I18n.id('authentication.failure.message').package(props.settings.translation.packageKey).source(props.settings.translation.sourceName).translate()} +

+