├── .gitignore ├── Resources └── Private │ ├── Fusion │ ├── Root.fusion │ └── NodeTypes │ │ ├── Registration.fusion │ │ ├── Profile.fusion │ │ └── LoginForm.fusion │ ├── Layouts │ ├── Neos │ │ └── Default.html │ └── Default.html │ ├── EmailTemplates │ ├── ActivationToken.txt │ ├── ResetPasswordToken.txt │ ├── ActivationToken.html │ └── ResetPasswordToken.html │ ├── Templates │ ├── Login │ │ ├── Authenticate.html │ │ └── Login.html │ ├── Registration │ │ ├── Register.html │ │ ├── ActivateAccount.html │ │ └── Index.html │ ├── ResetPassword │ │ ├── RequestToken.html │ │ ├── UpdatePassword.html │ │ ├── Index.html │ │ └── InsertNewPassword.html │ └── Profile │ │ └── Index.html │ ├── Translations │ ├── en │ │ ├── NodeTypes │ │ │ ├── Registration.xlf │ │ │ ├── LoginForm.xlf │ │ │ └── Profile.xlf │ │ ├── Partials │ │ │ └── Profile │ │ │ │ ├── PersonalForm.xlf │ │ │ │ ├── PasswordForm.xlf │ │ │ │ └── AccountData.xlf │ │ ├── Templates │ │ │ └── Partials │ │ │ │ └── Profile.xlf │ │ └── Main.xlf │ ├── de │ │ ├── NodeTypes │ │ │ ├── Registration.xlf │ │ │ ├── LoginForm.xlf │ │ │ └── Profile.xlf │ │ ├── Partials │ │ │ └── Profile │ │ │ │ ├── PersonalForm.xlf │ │ │ │ ├── PasswordForm.xlf │ │ │ │ └── AccountData.xlf │ │ ├── Templates │ │ │ └── Partials │ │ │ │ └── Profile.xlf │ │ └── Main.xlf │ └── fr │ │ ├── NodeTypes │ │ ├── Registration.xlf │ │ ├── LoginForm.xlf │ │ └── Profile.xlf │ │ ├── Partials │ │ └── Profile │ │ │ ├── PersonalForm.xlf │ │ │ ├── PasswordForm.xlf │ │ │ └── AccountData.xlf │ │ ├── Templates │ │ └── Partials │ │ │ └── Profile.xlf │ │ └── Main.xlf │ └── Partials │ ├── LogoutForm.html │ ├── FormErrors.html │ └── Profile │ ├── AccountData.html │ ├── PasswordForm.html │ └── PersonalForm.html ├── .editorconfig ├── Configuration ├── Views.yaml ├── NodeTypes.Registration.yaml ├── Objects.yaml ├── NodeTypes.Profile.yaml ├── NodeTypes.LoginForm.yaml ├── Policy.yaml ├── Routes.yaml └── Settings.yaml ├── Classes ├── Domain │ ├── Service │ │ ├── UserCreationServiceInterface.php │ │ ├── RegistrationFlowValidationServiceInterface.php │ │ ├── RedirectTargetServiceInterface.php │ │ ├── Flow │ │ │ ├── FlowUserCreationService.php │ │ │ └── FlowRedirectTargetService.php │ │ └── Neos │ │ │ ├── NeosUserCreationService.php │ │ │ └── NeosRedirectTargetService.php │ ├── Repository │ │ ├── UserRepository.php │ │ ├── ResetPasswordFlowRepository.php │ │ └── RegistrationFlowRepository.php │ ├── Validator │ │ ├── RegistrationFlowValidator.php │ │ └── CustomPasswordDtoValidator.php │ └── Model │ │ ├── ResetPasswordFlow.php │ │ ├── PasswordDto.php │ │ ├── User.php │ │ └── RegistrationFlow.php ├── Security │ └── NeosRequestPattern.php ├── ViewHelpers │ └── IfAuthenticatedViewHelper.php ├── Controller │ ├── ProfileController.php │ ├── RegistrationController.php │ ├── ResetPasswordController.php │ └── LoginController.php └── Command │ └── SandstormUserCommandController.php ├── LICENSE ├── composer.json ├── Migrations └── Mysql │ ├── Version20161201174312.php │ ├── Version20160526150442.php │ ├── Version20160530132725.php │ ├── Version20161101091328.php │ ├── Version20160526150536.php │ ├── Version20160524125855.php │ ├── Version20160607125833.php │ └── Version20160519150828.php ├── Tests └── Unit │ └── Domain │ └── PasswordDtoTest.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /Resources/Private/Fusion/Root.fusion: -------------------------------------------------------------------------------- 1 | include: NodeTypes/* -------------------------------------------------------------------------------- /Resources/Private/Layouts/Neos/Default.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 4 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.{yaml,html}] 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /Configuration/Views.yaml: -------------------------------------------------------------------------------- 1 | - 2 | requestFilter: 'mainRequest.isPackage("Neos.Neos") && isPackage("Sandstorm.UserManagement")' 3 | options: 4 | layoutRootPaths: ['resource://Sandstorm.UserManagement/Private/Layouts/Neos'] 5 | -------------------------------------------------------------------------------- /Resources/Private/Fusion/NodeTypes/Registration.fusion: -------------------------------------------------------------------------------- 1 | prototype(Sandstorm.UserManagement:Registration) < prototype(Neos.Neos:Plugin) { 2 | package = 'Sandstorm.UserManagement' 3 | controller = 'Registration' 4 | action = 'index' 5 | node = ${node} 6 | } 7 | -------------------------------------------------------------------------------- /Resources/Private/EmailTemplates/ActivationToken.txt: -------------------------------------------------------------------------------- 1 | Dear User, 2 | 3 | you signed up for an account at our service. To confirm your account, 4 | please click the link below or copy it into your browser: 5 | 6 | {activationLink} 7 | 8 | Thank you for your interest in our service! 9 | -------------------------------------------------------------------------------- /Resources/Private/EmailTemplates/ResetPasswordToken.txt: -------------------------------------------------------------------------------- 1 | Dear User, 2 | 3 | you want to reset your password for our service. To continue, 4 | please click the link below or copy it into your browser: 5 | 6 | {resetPasswordLink} 7 | 8 | If you did not want to reset your password, please ignore this mail. 9 | -------------------------------------------------------------------------------- /Configuration/NodeTypes.Registration.yaml: -------------------------------------------------------------------------------- 1 | ## 2 | # A simple "Login form" plugin that demonstrates "Frontend authorization" 3 | # 4 | 'Sandstorm.UserManagement:Registration': 5 | superTypes: 6 | 'Neos.Neos:Plugin': TRUE 7 | ui: 8 | label: i18n 9 | icon: 'icon-key' 10 | inspector: 11 | groups: 12 | pluginSettings: 13 | label: i18n 14 | -------------------------------------------------------------------------------- /Resources/Private/EmailTemplates/ActivationToken.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dear User,
4 | 5 | you signed up for an account at our service. To confirm your account, 6 | please click the link below. 7 |

8 | Click here to activate your account 9 |

10 | Thank you for your interest in our service! 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/Private/EmailTemplates/ResetPasswordToken.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dear User,
4 | 5 | you want to reset your password for our service. To continue, 6 | please click the link below: 7 |

8 | Click here to reset your password 9 |

10 | If you did not want to reset your password, please ignore this mail. 11 | 12 | 13 | -------------------------------------------------------------------------------- /Configuration/Objects.yaml: -------------------------------------------------------------------------------- 1 | # Use the Flow services as default and switch to Neos as necessary 2 | Sandstorm\UserManagement\Domain\Service\RedirectTargetServiceInterface: 3 | className: 'Sandstorm\UserManagement\Domain\Service\Flow\FlowRedirectTargetService' 4 | Sandstorm\UserManagement\Domain\Service\UserCreationServiceInterface: 5 | className: 'Sandstorm\UserManagement\Domain\Service\Flow\FlowUserCreationService' 6 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Login/Authenticate.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Login 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Resources/Private/Fusion/NodeTypes/Profile.fusion: -------------------------------------------------------------------------------- 1 | prototype(Sandstorm.UserManagement:Profile) < prototype(Neos.Neos:Plugin) { 2 | package = 'Sandstorm.UserManagement' 3 | controller = 'Profile' 4 | action = 'index' 5 | showPersonalInformation = ${q(node).property('showPersonalInformation')} 6 | showAccountInformation = ${q(node).property('showAccountInformation')} 7 | enableNewPassword = ${q(node).property('enableNewPassword')} 8 | } 9 | -------------------------------------------------------------------------------- /Resources/Private/Fusion/NodeTypes/LoginForm.fusion: -------------------------------------------------------------------------------- 1 | ## 2 | # "LoginForm" element, extending "Plugin" 3 | # 4 | prototype(Sandstorm.UserManagement:LoginForm) < prototype(Neos.Neos:Plugin) { 5 | package = 'Sandstorm.UserManagement' 6 | controller = 'Login' 7 | action = 'login' 8 | 9 | redirectAfterLogin = ${q(node).property('redirectAfterLogin')} 10 | redirectAfterLogout = ${q(node).property('redirectAfterLogout')} 11 | node = ${node} 12 | } 13 | -------------------------------------------------------------------------------- /Resources/Private/Translations/en/NodeTypes/Registration.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Registration Form 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Registration/Register.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Registration 9 | 10 | 11 |
12 |

13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /Resources/Private/Translations/de/NodeTypes/Registration.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Registration Form 7 | Registrierungsformular 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/Private/Translations/fr/NodeTypes/Registration.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Registration Form 7 | Formulaire d'inscription 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/Private/Layouts/Default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <f:render section="Title"/> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Resources/Private/Partials/LogoutForm.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 |

9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /Resources/Private/Templates/ResetPassword/RequestToken.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Passwort vergessen 9 | 10 | 11 | 12 |
13 |

14 | Eine Email wurde an {resetPasswordFlow.email} versendet, 15 | falls dieser Account existiert. Klicken Sie auf 16 | den enthaltenen Link, um Ihr Passwort zurückzusetzen. 17 |

18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /Resources/Private/Partials/FormErrors.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 |
{error}
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /Classes/Domain/Service/UserCreationServiceInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | First name 7 | 8 | 9 | Last name 10 | 11 | 12 | Change personal data 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Classes/Domain/Service/RegistrationFlowValidationServiceInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | New Password 7 | 8 | 9 | Repeat new password 10 | 11 | 12 | Change password 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/UserRepository.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Profile Settings 7 | 8 | 9 | User 10 | 11 | 12 | Account 13 | 14 | 15 | Password 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/Private/Translations/en/Partials/Profile/AccountData.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E-mail address: 7 | 8 | 9 | Role(s): 10 | 11 | 12 | Created on: 13 | 14 | 15 | Y-m-d 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Configuration/NodeTypes.LoginForm.yaml: -------------------------------------------------------------------------------- 1 | ## 2 | # A simple "Login form" plugin that demonstrates "Frontend authorization" 3 | # 4 | 'Sandstorm.UserManagement:LoginForm': 5 | superTypes: 6 | 'Neos.Neos:Plugin': TRUE 7 | ui: 8 | label: i18n 9 | icon: 'icon-key' 10 | inspector: 11 | groups: 12 | pluginSettings: 13 | label: i18n 14 | 15 | properties: 16 | redirectAfterLogin: 17 | type: reference 18 | ui: 19 | label: i18n 20 | reloadIfChanged: false 21 | inspector: 22 | group: pluginSettings 23 | editorOptions: 24 | nodeTypes: 25 | - 'Neos.Neos:Document' 26 | redirectAfterLogout: 27 | type: reference 28 | ui: 29 | label: i18n 30 | reloadIfChanged: false 31 | inspector: 32 | group: pluginSettings 33 | editorOptions: 34 | nodeTypes: 35 | - 'Neos.Neos:Document' 36 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/ResetPasswordFlowRepository.php: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Passwort vergessen 9 | 10 | 11 | 12 | 13 |
14 |

Das Passwort wurde erfolgreich aktualisiert.

15 |
16 | Zum Login 17 |
18 | 19 | 20 |
21 |

Es gab einen Fehler beim zurücksetzen Ihres Passworts.

22 |
23 | Nochmals versuchen 24 |
25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /Resources/Private/Translations/en/NodeTypes/LoginForm.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login Form 7 | 8 | 9 | Login Settings 10 | 11 | 12 | Redirect after Login 13 | 14 | 15 | Redirect after Logout 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/Private/Translations/de/Partials/Profile/PersonalForm.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | First name 7 | Vorname 8 | 9 | 10 | Last name 11 | Nachname 12 | 13 | 14 | Change personal data 15 | Persönliche Daten ändern 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/Private/Translations/fr/Partials/Profile/PersonalForm.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | First name 7 | Prénom 8 | 9 | 10 | Last name 11 | Nom de famille 12 | 13 | 14 | Change personal data 15 | Modifier les informations personnelles 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/RegistrationFlowRepository.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | New Password 7 | Neues Passwort 8 | 9 | 10 | Repeat new password 11 | Neues Passwort wiederholen 12 | 13 | 14 | Change password 15 | Passwort ändern 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/Private/Translations/fr/Partials/Profile/PasswordForm.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | New Password 7 | Nouveau mot de passe 8 | 9 | 10 | Repeat new password 11 | Répéter le nouveau mot de passe 12 | 13 | 14 | Change password 15 | Modifier le mot de passe 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/Private/Translations/de/Templates/Partials/Profile.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Profile Settings 7 | Profileinstellungen 8 | 9 | 10 | User 11 | Benutzer 12 | 13 | 14 | Account 15 | Konto 16 | 17 | 18 | Password 19 | Passwort 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Resources/Private/Translations/fr/Templates/Partials/Profile.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Profile Settings 7 | Réglages du profil 8 | 9 | 10 | User 11 | Utilisateur 12 | 13 | 14 | Account 15 | Compte 16 | 17 | 18 | Password 19 | Mot de passe 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 sandstorm|media 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Resources/Private/Translations/fr/Partials/Profile/AccountData.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E-mail address: 7 | Adresse email: 8 | 9 | 10 | Role(s): 11 | Rôle(s): 12 | 13 | 14 | Created on: 15 | Créé le: 16 | 17 | 18 | Y-m-d 19 | d.m.Y 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Resources/Private/Translations/de/Partials/Profile/AccountData.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | E-mail address: 7 | E-Mail Adresse: 8 | 9 | 10 | Role(s): 11 | Rolle(n): 12 | 13 | 14 | Created on: 15 | Erstellt am: 16 | 17 | 18 | Y-m-d 19 | d.m.Y 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Resources/Private/Translations/en/NodeTypes/Profile.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User Profile 7 | 8 | 9 | User Profile Settings 10 | 11 | 12 | Show Personal Information 13 | 14 | 15 | Show Account Information 16 | 17 | 18 | Enable Setting of a new Password 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Resources/Private/Translations/de/NodeTypes/LoginForm.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login Form 7 | Login-Formular 8 | 9 | 10 | Login Settings 11 | Login-Einstellungen 12 | 13 | 14 | Redirect after Login 15 | Weiterleitung nach Login 16 | 17 | 18 | Redirect after Logout 19 | Weiterleitung nach Logout 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Resources/Private/Partials/Profile/AccountData.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 23 | 24 |
{account.accountIdentifier}
15 | {role.name} 16 |
21 | {account.creationDate.date} 22 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandstorm/usermanagement", 3 | "description": "Neos and Flow package for user management, login/logout, password reset and user activation", 4 | "type": "neos-package", 5 | "keywords": ["user", "login", "logout", "registration", "frontend login", "forgot password", "account", "Neos", "Flow"], 6 | "homepage": "https://github.com/sandstorm/UserManagement", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Bastian Heist", 11 | "homepage": "https://www.sandstorm.de", 12 | "role": "Developer" 13 | }, 14 | { 15 | "name": "Sebastian Kurfuerst", 16 | "homepage": "https://www.sandstorm.de", 17 | "role": "Developer" 18 | } 19 | ], 20 | "support": { 21 | "issues": "https://github.com/sandstorm/UserManagement/issues", 22 | "source": "https://github.com/sandstorm/UserManagement" 23 | }, 24 | "require": { 25 | "neos/flow": "^6.0 || ^7.0 || ^8.0 || dev-master", 26 | "sandstorm/templatemailer": "^2.0.2" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Sandstorm\\UserManagement\\": "Classes" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Resources/Private/Translations/fr/NodeTypes/LoginForm.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login Form 7 | Formulaire de connexion 8 | 9 | 10 | Login Settings 11 | Réglages de connexion 12 | 13 | 14 | Redirect after Login 15 | Redirection après connexion 16 | 17 | 18 | Redirect after Logout 19 | Redirection après déconnexion 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Resources/Private/Partials/Profile/PasswordForm.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 |
8 | 11 | 12 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /Resources/Private/Templates/ResetPassword/Index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Passwort vergessen 9 | 10 | 11 | 12 |

Bitte geben Sie Ihre Email-Adresse ein. Wir schicken Ihnen dann einen Link, mit dem Sie Ihr Passwort zurücksetzen können.

13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | {propertyName}: 21 |
22 |
23 | {error} 24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /Classes/Security/NeosRequestPattern.php: -------------------------------------------------------------------------------- 1 | options = $options; 25 | } 26 | 27 | /** 28 | * Matches a \Neos\Flow\Mvc\ActionRequest against its set pattern rules 29 | * 30 | * @param ActionRequest $request The request that should be matched 31 | * @return boolean TRUE if the pattern matched, FALSE otherwise 32 | */ 33 | public function matchRequest(ActionRequest $request) 34 | { 35 | $shouldMatchBackend = ($this->options['area'] === self::AREA_FRONTEND) ? false : true; 36 | 37 | $requestPath = $request->getHttpRequest()->getUri()->getPath(); 38 | $requestPathMatchesBackend = substr($requestPath, 0, 5) === '/neos' || substr($requestPath, 0, 6) === '/setup' || strpos($requestPath, '@') !== false; 39 | 40 | return $shouldMatchBackend === $requestPathMatchesBackend; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Login/Login.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | Login 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 | 30 | -------------------------------------------------------------------------------- /Resources/Private/Translations/de/NodeTypes/Profile.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User Profile 7 | Benutzerprofil 8 | 9 | 10 | User Profile Settings 11 | Benutzerprofileinstellungen 12 | 13 | 14 | Show Personal Information 15 | Pesönliche Informationen anzeigen 16 | 17 | 18 | Show Account Information 19 | Kontoinformazionen anzeigen 20 | 21 | 22 | Enable Setting of a new Password 23 | Setzen eines neuen Passwortes aktivieren 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Resources/Private/Translations/fr/NodeTypes/Profile.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User Profile 7 | Profil utilisateur 8 | 9 | 10 | User Profile Settings 11 | Réglage du profil utilisateur 12 | 13 | 14 | Show Personal Information 15 | Afficher les informations personnelles 16 | 17 | 18 | Show Account Information 19 | Afficher les informations du compte 20 | 21 | 22 | Enable Setting of a new Password 23 | Afficher la création d'un nouveau mot de passe 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Classes/Domain/Service/RedirectTargetServiceInterface.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 28 | $this->addSql("ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow CHANGE attributes attributes LONGTEXT NOT NULL COMMENT '(DC2Type:flow_json_array)'"); 29 | } 30 | 31 | /** 32 | * @param Schema $schema 33 | * @return void 34 | */ 35 | public function down(Schema $schema): void 36 | { 37 | // this down() migration is autogenerated, please modify it to your needs 38 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 39 | $this->addSql("ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow CHANGE attributes attributes LONGTEXT NOT NULL COMMENT '(DC2Type:json_array)'"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Configuration/Policy.yaml: -------------------------------------------------------------------------------- 1 | privilegeTargets: 2 | 3 | # We allow access to "everybody" to the Registration process, as well as login and also logout, because otherwise a user 4 | # that has no roles yet cannot logout anymore. 5 | 6 | 'Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilege': 7 | 'Sandstorm.UserManagement:Login': 8 | matcher: 'method(Sandstorm\UserManagement\Controller\(Login|ResetPassword)Controller->(?!initialize).*Action())' 9 | 'Sandstorm.UserManagement:Logout': 10 | matcher: 'method(Neos\Flow\Security\Authentication\Controller\AbstractAuthenticationController->logoutAction())' 11 | 'Sandstorm.UserManagement:Registration': 12 | matcher: 'method(Sandstorm\UserManagement\Controller\RegistrationController->(?!initialize).*Action())' 13 | 'Sandstorm.UserManagement:Profile': 14 | matcher: 'method(Sandstorm\UserManagement\Controller\ProfileController->(?!initialize).*Action())' 15 | 16 | roles: 17 | 'Neos.Flow:Everybody': 18 | privileges: 19 | - 20 | privilegeTarget: 'Sandstorm.UserManagement:Login' 21 | permission: GRANT 22 | - 23 | privilegeTarget: 'Sandstorm.UserManagement:Registration' 24 | permission: GRANT 25 | - 26 | privilegeTarget: 'Sandstorm.UserManagement:Logout' 27 | permission: GRANT 28 | 'Neos.Neos:FrontendUser': 29 | privileges: 30 | - 31 | privilegeTarget: 'Sandstorm.UserManagement:Profile' 32 | permission: GRANT 33 | 'Neos.Neos:Editor': 34 | privileges: 35 | - 36 | privilegeTarget: 'Sandstorm.UserManagement:Profile' 37 | permission: GRANT 38 | 39 | -------------------------------------------------------------------------------- /Migrations/Mysql/Version20160526150442.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 29 | 30 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow DROP newactivationtokenrequested'); 31 | } 32 | 33 | /** 34 | * @param Schema $schema 35 | * @return void 36 | */ 37 | public function down(Schema $schema): void 38 | { 39 | // this down() migration is autogenerated, please modify it to your needs 40 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 41 | 42 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow ADD newactivationtokenrequested TINYINT(1) DEFAULT NULL'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Migrations/Mysql/Version20160530132725.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 29 | 30 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow ADD firstname VARCHAR(255) DEFAULT NULL, ADD lastname VARCHAR(255) DEFAULT NULL'); 31 | } 32 | 33 | /** 34 | * @param Schema $schema 35 | * @return void 36 | */ 37 | public function down(Schema $schema): void 38 | { 39 | // this down() migration is autogenerated, please modify it to your needs 40 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 41 | 42 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow DROP firstname, lastname'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Migrations/Mysql/Version20161101091328.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 29 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow DROP firstname, DROP lastname'); 30 | } 31 | 32 | /** 33 | * @param Schema $schema 34 | * @return void 35 | */ 36 | public function down(Schema $schema): void 37 | { 38 | // this down() migration is autogenerated, please modify it to your needs 39 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 40 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow ADD firstname VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci, ADD lastname VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Resources/Private/Templates/ResetPassword/InsertNewPassword.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Passwort-Änderung 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |

Entschuldigung, dieser Link zur Passwort-Änderung ist nicht mehr gültig!

33 |
34 | 35 | Neuen Passwortlink abrufen 36 |
37 | 38 |
39 |

Entschuldigung, dieser Link zur Passwort-Änderung ist nicht gültig!

40 |
41 | 42 | Neuen Passwortlink abrufen 43 |
44 | 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /Resources/Private/Partials/Profile/PersonalForm.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 |
8 | 11 | 16 | 19 | 24 | 25 | 26 | 27 | 30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /Migrations/Mysql/Version20160526150536.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 29 | 30 | $this->addSql('CREATE TABLE sandstorm_usermanagement_domain_model_resetpasswordflow (persistence_object_identifier VARCHAR(40) NOT NULL, email VARCHAR(255) NOT NULL, resetpasswordtoken VARCHAR(255) DEFAULT NULL, resetpasswordtokenvaliduntil DATETIME DEFAULT NULL, PRIMARY KEY(persistence_object_identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 31 | } 32 | 33 | /** 34 | * @param Schema $schema 35 | * @return void 36 | */ 37 | public function down(Schema $schema): void 38 | { 39 | // this down() migration is autogenerated, please modify it to your needs 40 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 41 | 42 | $this->addSql('DROP TABLE sandstorm_usermanagement_domain_model_resetpasswordflow'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Registration/ActivateAccount.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Activate Account 9 | 10 | 11 | VARIABLES: 12 | 13 | - tokenNotFound: if set, the auth token was not found. 14 | - tokenTimeout: if set, the token had a timeout. 15 | - success: all worked. 16 | 17 | 18 | 19 | 20 |
21 |

22 | 23 |

24 |
25 |
26 | 27 | 28 |
29 |

30 | 31 |

32 |
33 |
34 | 35 | 36 |
37 |

38 |

39 |
40 | 42 | 43 |
44 | 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /Migrations/Mysql/Version20160524125855.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 29 | 30 | $this->addSql('CREATE TABLE sandstorm_usermanagement_domain_model_registrationflow (persistence_object_identifier VARCHAR(40) NOT NULL, email VARCHAR(255) NOT NULL, encryptedpassword VARCHAR(255) NOT NULL, attributes LONGTEXT NOT NULL COMMENT \'(DC2Type:json_array)\', activationtoken VARCHAR(255) DEFAULT NULL, activationtokenvaliduntil DATETIME DEFAULT NULL, newactivationtokenrequested TINYINT(1) DEFAULT NULL, PRIMARY KEY(persistence_object_identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 31 | } 32 | 33 | /** 34 | * @param Schema $schema 35 | * @return void 36 | */ 37 | public function down(Schema $schema): void 38 | { 39 | // this down() migration is autogenerated, please modify it to your needs 40 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 41 | 42 | $this->addSql('DROP TABLE sandstorm_usermanagement_domain_model_registrationflow'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/IfAuthenticatedViewHelper.php: -------------------------------------------------------------------------------- 1 | registerArgument('authenticationProviderName', 'string', 'Use a different Authentication Provider than the default one', false,'Sandstorm.UserManagement:Login'); 22 | } 23 | 24 | /** 25 | * Renders child if any account is currently authenticated, otherwise renders child. 26 | * 27 | * @param string $authenticationProviderName 28 | * @return string the rendered string 29 | * @api 30 | */ 31 | public function render() 32 | { 33 | $authenticationProviderName = $this->arguments['authenticationProviderName']; 34 | if (static::evaluateCondition($this->arguments, $this->renderingContext)) { 35 | return $this->renderThenChild(); 36 | } 37 | 38 | return $this->renderElseChild(); 39 | } 40 | 41 | /** 42 | * @param array $arguments 43 | * @param RenderingContextInterface $renderingContext 44 | * @return bool 45 | */ 46 | protected static function evaluateCondition($arguments, RenderingContextInterface $renderingContext) 47 | { 48 | $objectManager = $renderingContext->getObjectManager(); 49 | /** @var Context $securityContext */ 50 | $securityContext = $objectManager->get(Context::class); 51 | $activeTokens = $securityContext->getAuthenticationTokens(); 52 | 53 | 54 | /** @var $token TokenInterface */ 55 | foreach ($activeTokens as $token) { 56 | if ($token->getAuthenticationProviderName() === $arguments['authenticationProviderName'] && $token->isAuthenticated()) { 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Migrations/Mysql/Version20160607125833.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 29 | 30 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow CHANGE firstname firstname VARCHAR(255) NOT NULL, CHANGE lastname lastname VARCHAR(255) NOT NULL'); 31 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_user DROP companyname, DROP resetpasswordtoken, DROP activationtoken, DROP activationtokenvaliduntil, DROP resetpasswordtokenvaliduntil, DROP newactivationtokenrequested'); 32 | } 33 | 34 | /** 35 | * @param Schema $schema 36 | * @return void 37 | */ 38 | public function down(Schema $schema): void 39 | { 40 | // this down() migration is autogenerated, please modify it to your needs 41 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".'); 42 | 43 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_registrationflow CHANGE firstname firstname VARCHAR(255) DEFAULT NULL COLLATE utf8_unicode_ci, CHANGE lastname lastname VARCHAR(255) DEFAULT NULL COLLATE utf8_unicode_ci'); 44 | $this->addSql('ALTER TABLE sandstorm_usermanagement_domain_model_user ADD companyname VARCHAR(255) DEFAULT NULL COLLATE utf8_unicode_ci, ADD resetpasswordtoken VARCHAR(255) DEFAULT NULL COLLATE utf8_unicode_ci, ADD activationtoken VARCHAR(255) DEFAULT NULL COLLATE utf8_unicode_ci, ADD activationtokenvaliduntil DATETIME DEFAULT NULL, ADD resetpasswordtokenvaliduntil DATETIME DEFAULT NULL, ADD newactivationtokenrequested TINYINT(1) DEFAULT NULL'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Classes/Domain/Validator/RegistrationFlowValidator.php: -------------------------------------------------------------------------------- 1 | accountRepository->findOneByAccountIdentifier($value->getEmail()); 49 | 50 | if ($existingAccount) { 51 | $message = $this->translator->translateById('validations.registrationflow.email', [$value->getEmail()], null, null, 'Main', 'Sandstorm.UserManagement'); 52 | $this->getResult()->forProperty('email')->addError(new Error($message, 1336499566)); 53 | } 54 | 55 | // If a custom validation service is registered, call its validate method to allow custom validations during registration 56 | if ($this->objectManager->isRegistered(RegistrationFlowValidationServiceInterface::class)) { 57 | $instance = $this->objectManager->get(RegistrationFlowValidationServiceInterface::class); 58 | $instance->validateRegistrationFlow($value, $this); 59 | } 60 | 61 | } 62 | 63 | /** 64 | * The custom validation service might need to access the result directly, so it is exposed here 65 | * 66 | * @return Result 67 | */ 68 | public function getResult() 69 | { 70 | return parent::getResult(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Profile/Index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | Profile Settings 11 | 12 | 13 |

14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |

26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 |

39 | 40 |
41 | 42 |

43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /Classes/Domain/Service/Flow/FlowUserCreationService.php: -------------------------------------------------------------------------------- 1 | setAccountIdentifier($registrationFlow->getEmail()); 50 | $account->setCredentialsSource($registrationFlow->getEncryptedPassword()); 51 | $account->setAuthenticationProviderName('Sandstorm.UserManagement:Login'); 52 | 53 | // Assign pre-configured roles 54 | foreach ($this->rolesForNewUsers as $roleString) { 55 | $account->addRole(new Role($roleString)); 56 | } 57 | 58 | // Create the user 59 | $user = new User(); 60 | $user->setAccount($account); 61 | $user->setEmail($registrationFlow->getEmail()); 62 | if (array_key_exists('salutation', $registrationFlow->getAttributes())) { 63 | $user->setGender($registrationFlow->getAttributes()['salutation']); 64 | } 65 | if (array_key_exists('firstName', $registrationFlow->getAttributes())) { 66 | $user->setFirstName($registrationFlow->getAttributes()['firstName']); 67 | } 68 | if (array_key_exists('lastName', $registrationFlow->getAttributes())) { 69 | $user->setLastName($registrationFlow->getAttributes()['lastName']); 70 | } 71 | 72 | // Persist user 73 | $this->userRepository->add($user); 74 | $this->persistenceManager->allowObject($user); 75 | $this->persistenceManager->allowObject($account); 76 | 77 | // Return the user so the controller can directly use it 78 | return $user; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Registration/Index.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | Registration 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 23 | 24 | 29 | 30 | 34 | 35 | 40 | 41 | 46 | 47 | 52 | 53 | 54 |
55 |
56 |
57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /Configuration/Routes.yaml: -------------------------------------------------------------------------------- 1 | # # 2 | # Default routes configuration for the User Management package # 3 | # # 4 | 5 | ## Login/Logout ---------------------------------------------------------- 6 | 7 | - 8 | name: 'Login Screen' 9 | uriPattern: 'login' 10 | defaults: 11 | '@package': 'Sandstorm.UserManagement' 12 | '@controller': 'Login' 13 | '@action': 'login' 14 | '@format': 'html' 15 | 16 | - 17 | name: 'Login: Authenticate' 18 | uriPattern: 'login/authenticate' 19 | defaults: 20 | '@package': 'Sandstorm.UserManagement' 21 | '@controller': 'Login' 22 | '@action': 'authenticate' 23 | '@format': 'html' 24 | 25 | - 26 | name: 'Logout' 27 | uriPattern: 'logout' 28 | defaults: 29 | '@package': 'Sandstorm.UserManagement' 30 | '@controller': 'Login' 31 | '@action': 'logout' 32 | '@format': 'html' 33 | 34 | ## Registration & account activation --------------------------------------- 35 | 36 | - 37 | name: 'Account: Registration: show form' 38 | uriPattern: 'account/signup/index' 39 | defaults: 40 | '@package': 'Sandstorm.UserManagement' 41 | '@controller': 'Registration' 42 | '@action': 'index' 43 | '@format': 'html' 44 | 45 | - 46 | name: 'Account: submit signup form' 47 | uriPattern: 'account/signup/submit' 48 | defaults: 49 | '@package': 'Sandstorm.UserManagement' 50 | '@controller': 'Registration' 51 | '@action': 'register' 52 | '@format': 'html' 53 | 54 | - 55 | name: 'Account: activate' 56 | uriPattern: 'account/activate/{token}' 57 | defaults: 58 | '@package': 'Sandstorm.UserManagement' 59 | '@controller': 'Registration' 60 | '@action': 'activateAccount' 61 | '@format': 'html' 62 | 63 | ## Reset password ----------------------------------------------------------- 64 | 65 | - 66 | name: 'User: send new password link' 67 | uriPattern: 'account/forgotpassword' 68 | defaults: 69 | '@package': 'Sandstorm.UserManagement' 70 | '@controller': 'ResetPassword' 71 | '@action': 'index' 72 | '@format': 'html' 73 | 74 | - 75 | name: 'User: request new password token' 76 | uriPattern: 'account/requestpasswordtoken' 77 | defaults: 78 | '@package': 'Sandstorm.UserManagement' 79 | '@controller': 'ResetPassword' 80 | '@action': 'requestToken' 81 | '@format': 'html' 82 | 83 | - 84 | name: 'User: reset password' 85 | uriPattern: 'account/resetpassword/{token}' 86 | defaults: 87 | '@package': 'Sandstorm.UserManagement' 88 | '@controller': 'ResetPassword' 89 | '@action': 'insertNewPassword' 90 | '@format': 'html' 91 | 92 | - 93 | name: 'User: reset password' 94 | uriPattern: 'account/updatepassword' 95 | defaults: 96 | '@package': 'Sandstorm.UserManagement' 97 | '@controller': 'ResetPassword' 98 | '@action': 'updatePassword' 99 | '@format': 'html' 100 | -------------------------------------------------------------------------------- /Classes/Domain/Service/Flow/FlowRedirectTargetService.php: -------------------------------------------------------------------------------- 1 | redirectAfterLogin) 33 | && array_key_exists('action', $this->redirectAfterLogin) 34 | && array_key_exists('controller', $this->redirectAfterLogin) 35 | && array_key_exists('package', $this->redirectAfterLogin) 36 | ) { 37 | $controllerArguments = []; 38 | if (array_key_exists('controllerArguments', $this->redirectAfterLogin) && is_array($this->redirectAfterLogin['controllerArguments'])) { 39 | $controllerArguments = $this->redirectAfterLogin['controllerArguments']; 40 | } 41 | 42 | return $controllerContext->getUriBuilder() 43 | ->reset() 44 | ->setCreateAbsoluteUri(true) 45 | ->uriFor($this->redirectAfterLogin['action'], $controllerArguments, 46 | $this->redirectAfterLogin['controller'], $this->redirectAfterLogin['package']); 47 | } 48 | } 49 | 50 | /** 51 | * @param ControllerContext $controllerContext 52 | * @return string|ActionRequest|NULL 53 | */ 54 | public function onLogout(ControllerContext $controllerContext) 55 | { 56 | if (is_array($this->redirectAfterLogout) 57 | && array_key_exists('action', $this->redirectAfterLogout) 58 | && array_key_exists('controller', $this->redirectAfterLogout) 59 | && array_key_exists('package', $this->redirectAfterLogout) 60 | ) { 61 | $controllerArguments = []; 62 | if (array_key_exists('controllerArguments', $this->redirectAfterLogout) && is_array($this->redirectAfterLogout['controllerArguments'])) { 63 | $controllerArguments = $this->redirectAfterLogout['controllerArguments']; 64 | } 65 | 66 | return $controllerContext->getUriBuilder() 67 | ->reset() 68 | ->setCreateAbsoluteUri(true) 69 | ->uriFor($this->redirectAfterLogout['action'], $controllerArguments, 70 | $this->redirectAfterLogout['controller'], $this->redirectAfterLogout['package']); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Migrations/Mysql/Version20160519150828.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != "mysql"); 20 | 21 | $this->addSql("CREATE TABLE sandstorm_usermanagement_domain_model_user (persistence_object_identifier VARCHAR(40) NOT NULL, account VARCHAR(40) DEFAULT NULL, email VARCHAR(255) NOT NULL, gender VARCHAR(255) DEFAULT NULL, firstname VARCHAR(255) DEFAULT NULL, lastname VARCHAR(255) DEFAULT NULL, companyname VARCHAR(255) DEFAULT NULL, resetpasswordtoken VARCHAR(255) DEFAULT NULL, activationtoken VARCHAR(255) DEFAULT NULL, activationtokenvaliduntil DATETIME DEFAULT NULL, resetpasswordtokenvaliduntil DATETIME DEFAULT NULL, newactivationtokenrequested TINYINT(1) DEFAULT NULL, dtype VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_5DEB8A977D3656A4 (account), PRIMARY KEY(persistence_object_identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB"); 22 | 23 | /** 24 | * We need to check what the name of table flow accounts table is. If you install flow, run migrations, then add usermananagement, 25 | * then run the UserManagement migrations, the name will will be neos_flow_... 26 | * However, if you install everything in one go and run migrations then, the order will be different because this migration 27 | * comes before the Flow migration where the table is renamed (Version20161124185047). So we need to check which of these two 28 | * tables exist and set the FK relation accordingly. 29 | **/ 30 | if ($this->sm->tablesExist('neos_flow_security_account')) { 31 | // "neos_" table is there - this means flow migrations have already been run. 32 | { 33 | $this->addSql("ALTER TABLE sandstorm_usermanagement_domain_model_user ADD CONSTRAINT FK_5DEB8A977D3656A4 FOREIGN KEY (account) REFERENCES neos_flow_security_account (persistence_object_identifier)"); 34 | } 35 | } else if ($this->sm->tablesExist('typo3_flow_security_account')) { 36 | // Flow migrations have not been run fully yet, table still has the old name. 37 | $this->addSql("ALTER TABLE sandstorm_usermanagement_domain_model_user ADD CONSTRAINT FK_5DEB8A977D3656A4 FOREIGN KEY (account) REFERENCES typo3_flow_security_account (persistence_object_identifier)"); 38 | } 39 | } 40 | 41 | /** 42 | * @param Schema $schema 43 | * @return void 44 | */ 45 | public function down(Schema $schema): void 46 | { 47 | // this down() migration is autogenerated, please modify it to your needs 48 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql"); 49 | 50 | $this->addSql("DROP TABLE sandstorm_usermanagement_domain_model_user"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Configuration/Settings.yaml: -------------------------------------------------------------------------------- 1 | Sandstorm: 2 | UserManagement: 3 | # Validity timespan for the activation token for newly registered users. 4 | activationTokenTimeout: '2 days' 5 | # Validity timespan for the token used to reset passwords. 6 | resetPasswordTokenTimeout: '4 hours' 7 | # The message that appears if a user could not be logged in. 8 | # Set the value to 'i18n' to enable translations 9 | # using 'authFailedMessage.title and authFailedMessage.body as id. 10 | authFailedMessage: 11 | title: 'Login nicht möglich' 12 | body: 'Sie haben ungültige Zugangsdaten eingegeben. Bitte versuchen Sie es noch einmal.' 13 | # Email settings 14 | # Set the value to 'i18n' to enable translations 15 | # using 'email.subjectActivation' and 'email.subjectResetPassword' as id. 16 | email: 17 | subjectActivation: 'Please confirm your account' 18 | # Subject line for the password reset email 19 | subjectResetPassword: 'Password reset' 20 | # An array of roles which are assigned to users after they activate their account. 21 | rolesForNewUsers: [] 22 | 23 | # You can make constraints on allowed passwords here. 24 | passwordConstraints: 25 | minLength: 8 26 | maxLength: 128 27 | # per default, we do not prescribe anything about the password content - feel free to change these. 28 | # make sure that the sum of these values does not exceed the minLength, otherwise users can't register. 29 | minNumberOfLowercaseLetters: 0 30 | minNumberOfUppercaseLetters: 0 31 | minNumberOfNumbers: 0 32 | minNumberOfSpecialCharacters: 0 33 | 34 | # Redirect settings after logout/login 35 | redirect: 36 | afterLogin: [] 37 | afterLogout: [] 38 | # To activate redirection, make these settings. controllerArguments are optional. 39 | # afterLogin: 40 | # action: 'action' 41 | # controller: 'Controller' 42 | # package: 'Your.Package' 43 | # controllerArguments: 44 | # yourAdditionalArgument: 'test' 45 | # afterLogout: 46 | # action: 'action' 47 | # controller: 'Controller' 48 | # package: 'Your.Package' 49 | # controllerArguments: 50 | # yourAdditionalArgument: 'test1' 51 | 52 | TemplateMailer: 53 | templatePackages: 54 | 99999: 'Sandstorm.UserManagement' 55 | senderAddresses: 56 | 'sandstorm_usermanagement_sender_email': 57 | name: 'Sandstorm Usermanagement Package' 58 | address: 'test@example.com' 59 | replyToAddresses: 60 | 'sandstorm_usermanagement_replyTo_email': 61 | name: 'Sandstorm Usermanagement Package Reply-To Email' 62 | address: 'test@example.com' 63 | 64 | Neos: 65 | Flow: 66 | mvc: 67 | routes: 68 | 'Sandstorm.UserManagement': TRUE 69 | 70 | 71 | # The auth provider settings below are needed for the standalone case only. 72 | security: 73 | authentication: 74 | providers: 75 | 'Sandstorm.UserManagement:Login': 76 | provider: 'PersistedUsernamePasswordProvider' 77 | entryPoint: 'WebRedirect' 78 | entryPointOptions: 79 | routeValues: 80 | '@package': 'Sandstorm.UserManagement' 81 | '@controller': 'Login' 82 | '@action': 'login' 83 | 84 | Neos: 85 | fusion: 86 | autoInclude: 87 | 'Sandstorm.UserManagement': TRUE 88 | 89 | userInterface: 90 | translation: 91 | autoInclude: 92 | 'Sandstorm.UserManagement': ['NodeTypes/*'] 93 | -------------------------------------------------------------------------------- /Classes/Domain/Model/ResetPasswordFlow.php: -------------------------------------------------------------------------------- 1 | generateResetPasswordToken(); 65 | } 66 | } 67 | 68 | /** 69 | * Generate a new password reset token 70 | * 71 | * @throws Exception If the user doesn't have an account yet 72 | */ 73 | protected function generateResetPasswordToken() 74 | { 75 | $this->resetPasswordToken = Algorithms::generateRandomString(30); 76 | $this->resetPasswordTokenValidUntil = (new \DateTime())->add(\DateInterval::createFromDateString($this->resetPasswordTokenTimeout)); 77 | } 78 | 79 | /** 80 | * Check if the user has a valid reset password token. 81 | * 82 | * @return bool 83 | */ 84 | public function hasValidResetPasswordToken() 85 | { 86 | if ($this->resetPasswordTokenValidUntil == null) { 87 | return false; 88 | } 89 | 90 | return $this->resetPasswordTokenValidUntil->getTimestamp() > time(); 91 | } 92 | 93 | /** 94 | * @return string 95 | */ 96 | public function getEmail() 97 | { 98 | return $this->email; 99 | } 100 | 101 | /** 102 | * @param string $email 103 | */ 104 | public function setEmail($email) 105 | { 106 | $this->email = $email; 107 | } 108 | 109 | /** 110 | * @return string 111 | */ 112 | public function getResetPasswordToken() 113 | { 114 | return $this->resetPasswordToken; 115 | } 116 | 117 | /** 118 | * @param PasswordDto $passwordDto 119 | */ 120 | public function setPasswordDto(PasswordDto $passwordDto) 121 | { 122 | $this->passwordDto = $passwordDto; 123 | } 124 | 125 | public function getEncryptedPassword() 126 | { 127 | return $this->passwordDto->getEncryptedPasswordAndRemoveNonencryptedVersion(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Classes/Domain/Model/PasswordDto.php: -------------------------------------------------------------------------------- 1 | password = $password; 32 | } 33 | 34 | /** 35 | * @param string $passwordConfirmation 36 | */ 37 | public function setPasswordConfirmation($passwordConfirmation) 38 | { 39 | $this->passwordConfirmation = $passwordConfirmation; 40 | } 41 | 42 | /** 43 | * @return bool 44 | */ 45 | public function arePasswordsEqual() 46 | { 47 | return !empty($this->password) && !empty(trim($this->password)) && 48 | ($this->password === $this->passwordConfirmation); 49 | } 50 | 51 | /** 52 | * @param $length 53 | * @return bool 54 | */ 55 | public function isPasswordMinLength($length) 56 | { 57 | return strlen($this->password) >= $length; 58 | } 59 | 60 | /** 61 | * @param $length 62 | * @return bool 63 | */ 64 | public function isPasswordMaxLength($length) 65 | { 66 | return strlen($this->password) <= $length; 67 | } 68 | 69 | /** 70 | * @param int $minAmount 71 | * @return bool 72 | */ 73 | public function doesPasswordContainLowercaseLetters($minAmount) 74 | { 75 | return $this->doesPasswordContain('/[a-z]{1}/', $minAmount); 76 | } 77 | 78 | /** 79 | * @param int $minAmount 80 | * @return bool 81 | */ 82 | public function doesPasswordContainUppercaseLetters($minAmount) 83 | { 84 | return $this->doesPasswordContain('/[A-Z]{1}/', $minAmount); 85 | } 86 | 87 | /** 88 | * @param int $minAmount 89 | * @return bool 90 | */ 91 | public function doesPasswordContainNumbers($minAmount) 92 | { 93 | return $this->doesPasswordContain('/[\d]{1}/', $minAmount); 94 | } 95 | 96 | /** 97 | * @param int $minAmount 98 | * @return bool 99 | */ 100 | public function doesPasswordContainSpecialCharacters($minAmount) 101 | { 102 | // Regexes do not work reliably here - we simply kick out all letters and numbers 103 | $replacedPassword = preg_replace('/[a-zA-Z\d]/', '', $this->password); 104 | return strlen($replacedPassword) >= $minAmount; 105 | } 106 | 107 | /** 108 | * Helper method 109 | * 110 | * @param string $regex 111 | * @return bool 112 | */ 113 | private function doesPasswordContain($regex, $minAmount) 114 | { 115 | $matches = []; 116 | preg_match_all($regex, $this->password, $matches); 117 | return count($matches[0]) >= $minAmount; 118 | } 119 | 120 | public function getEncryptedPasswordAndRemoveNonencryptedVersion() 121 | { 122 | if (!$this->arePasswordsEqual()) { 123 | throw new Exception('Passwords are not equal; so it is not allowed to call getEncryptedPassword().', 124 | 1464087097); 125 | } 126 | 127 | $encrypted = $this->hashService->hashPassword($this->password); 128 | $this->password = null; 129 | $this->passwordConfirmation = null; 130 | 131 | return $encrypted; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Classes/Domain/Model/User.php: -------------------------------------------------------------------------------- 1 | account == null) { 60 | return $this->email; 61 | } else { 62 | return $this->account->getAccountIdentifier(); 63 | } 64 | } 65 | 66 | /** 67 | * Get the full name of an user. 68 | * 69 | * @return string 70 | */ 71 | public function getFullName() 72 | { 73 | return $this->firstName . ' ' . $this->lastName; 74 | } 75 | 76 | /** 77 | * Check if the user is active. 78 | * 79 | * @return bool 80 | */ 81 | public function isActive() 82 | { 83 | return $this->account !== null; 84 | } 85 | 86 | 87 | // ---- Only getters and setters follow 88 | 89 | 90 | /** 91 | * @return string 92 | */ 93 | public function getFirstName() 94 | { 95 | return $this->firstName; 96 | } 97 | 98 | /** 99 | * @param string $firstName 100 | */ 101 | public function setFirstName($firstName) 102 | { 103 | $this->firstName = $firstName; 104 | } 105 | 106 | /** 107 | * @return string 108 | */ 109 | public function getLastName() 110 | { 111 | return $this->lastName; 112 | } 113 | 114 | /** 115 | * @param string $lastName 116 | */ 117 | public function setLastName($lastName) 118 | { 119 | $this->lastName = $lastName; 120 | } 121 | 122 | /** 123 | * @return string 124 | */ 125 | public function getGender() 126 | { 127 | return $this->gender; 128 | } 129 | 130 | /** 131 | * @param string $gender 132 | */ 133 | public function setGender($gender) 134 | { 135 | $this->gender = $gender; 136 | } 137 | 138 | /** 139 | * @return string 140 | */ 141 | public function getEmail() 142 | { 143 | return $this->email; 144 | } 145 | 146 | /** 147 | * @param string $email 148 | */ 149 | public function setEmail($email) 150 | { 151 | $this->email = $email; 152 | } 153 | 154 | /** 155 | * @return \Neos\Flow\Security\Account 156 | */ 157 | public function getAccount() 158 | { 159 | return $this->account; 160 | } 161 | 162 | /** 163 | * @param \Neos\Flow\Security\Account $account 164 | */ 165 | public function setAccount($account) 166 | { 167 | $this->account = $account; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Classes/Domain/Service/Neos/NeosUserCreationService.php: -------------------------------------------------------------------------------- 1 | setAccountIdentifier($registrationFlow->getEmail()); 60 | $account->setCredentialsSource($registrationFlow->getEncryptedPassword()); 61 | $account->setAuthenticationProviderName('Sandstorm.UserManagement:Login'); 62 | 63 | // Assign preconfigured roles 64 | foreach ($this->rolesForNewUsers as $roleString) { 65 | $account->addRole(new Role($roleString)); 66 | } 67 | 68 | // Create the user 69 | $user = new User(); 70 | $name = new PersonName('', $registrationFlow->getAttributes()['firstName'], '', 71 | $registrationFlow->getAttributes()['lastName'], '', $registrationFlow->getEmail()); 72 | $user->setName($name); 73 | 74 | // Assign them to each other and persist 75 | $this->getPartyService()->assignAccountToParty($account, $user); 76 | $this->getPartyRepository()->add($user); 77 | $this->accountRepository->add($account); 78 | $this->persistenceManager->allowObject($user); 79 | $this->persistenceManager->allowObject($user->getPreferences()); 80 | $this->persistenceManager->allowObject($name); 81 | $this->persistenceManager->allowObject($account); 82 | 83 | // Return the user so the controller can directly use it 84 | return $user; 85 | } 86 | 87 | /** 88 | * This method exists to ensure the code runs outside Neos. 89 | * We do not fetch this via injection so it works also in Flow when the class is not present 90 | * 91 | * @return PartyService 92 | */ 93 | protected function getPartyService() 94 | { 95 | return $this->objectManager->get(PartyService::class); 96 | } 97 | 98 | /** 99 | * This method exists to ensure the code runs outside Neos. 100 | * We do not fetch this via injection so it works also in Flow when the class is not present 101 | * 102 | * @return PartyRepository 103 | */ 104 | protected function getPartyRepository() 105 | { 106 | return $this->objectManager->get(PartyRepository::class); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Classes/Controller/ProfileController.php: -------------------------------------------------------------------------------- 1 | request->getInternalArguments(); 56 | $account = $this->securityContext->getAccount(); 57 | $user = $this->userRepository->findOneByAccount($account); 58 | $this->view->assign('account', $account); 59 | $this->view->assign('user', $user); 60 | $this->view->assign('pluginArguments', $pluginArguments); 61 | } 62 | 63 | /** 64 | * @param User $user 65 | */ 66 | public function editProfileAction(User $user) 67 | { 68 | $this->userRepository->update($user); 69 | $this->redirect('index'); 70 | } 71 | 72 | /** 73 | * @param Account $account 74 | * @param array $password Expects an array in the format array('', '') 75 | * @Flow\Validate(argumentName="password", type="\Neos\Neos\Validation\Validator\PasswordValidator", options={ "allowEmpty"=1, "minimum"=1, "maximum"=255 }) 76 | */ 77 | public function setNewPasswordAction(Account $account, array $password = array()) { 78 | $user = $this->userRepository->findOneByAccount($account); 79 | $password = array_shift($password); 80 | if (strlen(trim(strval($password))) > 0) { 81 | $this->setPassword($account, $password); 82 | } 83 | $this->redirect('index'); 84 | 85 | } 86 | 87 | /** 88 | * Disable the technical error flash message 89 | * 90 | * @return boolean 91 | */ 92 | protected function getErrorFlashMessage() 93 | { 94 | return false; 95 | } 96 | 97 | /** 98 | * Sets a new password for the given account 99 | * 100 | * @param Account $account The user to set the password for 101 | * @param string $password A new password 102 | * @return void 103 | * @api 104 | */ 105 | protected function setPassword(Account $account, $password) 106 | { 107 | $tokens = $this->tokenAndProviderFactory->getTokens(); 108 | $indexedTokens = array(); 109 | foreach ($tokens as $token) { 110 | /** @var TokenInterface $token */ 111 | $indexedTokens[$token->getAuthenticationProviderName()] = $token; 112 | } 113 | 114 | /** @var Account $account */ 115 | $authenticationProviderName = $account->getAuthenticationProviderName(); 116 | if (isset($indexedTokens[$authenticationProviderName]) && $indexedTokens[$authenticationProviderName] instanceof UsernamePassword) { 117 | $account->setCredentialsSource($this->hashService->hashPassword($password)); 118 | $this->accountRepository->update($account); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Tests/Unit/Domain/PasswordDtoTest.php: -------------------------------------------------------------------------------- 1 | setPassword('foobar'); 24 | $passwordDto->setPasswordConfirmation('foobar'); 25 | 26 | $this->assertTrue($passwordDto->arePasswordsEqual()); 27 | } 28 | 29 | /** 30 | * @test 31 | */ 32 | public function inequalPasswordsAreNotEqual() 33 | { 34 | $passwordDto = new PasswordDto(); 35 | $passwordDto->setPassword('FOOBAR'); 36 | $passwordDto->setPasswordConfirmation('foobar'); 37 | 38 | $this->assertFalse($passwordDto->arePasswordsEqual()); 39 | } 40 | 41 | /** 42 | * @test 43 | */ 44 | public function passwordMinLength() 45 | { 46 | $passwordDto = new PasswordDto(); 47 | $passwordDto->setPassword('6chars'); 48 | $passwordDto->setPasswordConfirmation('6chars'); 49 | 50 | $this->assertTrue($passwordDto->isPasswordMinLength(6)); 51 | $this->assertFalse($passwordDto->isPasswordMinLength(7)); 52 | } 53 | 54 | /** 55 | * @test 56 | */ 57 | public function passwordMaxLength() 58 | { 59 | $passwordDto = new PasswordDto(); 60 | $passwordDto->setPassword('6chars'); 61 | $passwordDto->setPasswordConfirmation('6chars'); 62 | 63 | $this->assertTrue($passwordDto->isPasswordMaxLength(6)); 64 | $this->assertFalse($passwordDto->isPasswordMaxLength(5)); 65 | } 66 | 67 | /** 68 | * @test 69 | */ 70 | public function passwordContainsLowercaseLetters() 71 | { 72 | $passwordDto = new PasswordDto(); 73 | $passwordDto->setPassword('4loweRCASELETTERS'); 74 | $passwordDto->setPasswordConfirmation('4loweRCASELETTERS'); 75 | 76 | $this->assertTrue($passwordDto->doesPasswordContainLowercaseLetters(3)); 77 | $this->assertTrue($passwordDto->doesPasswordContainLowercaseLetters(4)); 78 | $this->assertFalse($passwordDto->doesPasswordContainLowercaseLetters(5)); 79 | } 80 | 81 | /** 82 | * @test 83 | */ 84 | public function passwordContainsUppercaseLetters() 85 | { 86 | $passwordDto = new PasswordDto(); 87 | $passwordDto->setPassword('4UPPErcaseletters'); 88 | $passwordDto->setPasswordConfirmation('4UPPErcaseletters'); 89 | 90 | $this->assertTrue($passwordDto->doesPasswordContainUppercaseLetters(3)); 91 | $this->assertTrue($passwordDto->doesPasswordContainUppercaseLetters(4)); 92 | $this->assertFalse($passwordDto->doesPasswordContainUppercaseLetters(5)); 93 | } 94 | 95 | /** 96 | * @test 97 | */ 98 | public function passwordContainsNumbers() 99 | { 100 | $passwordDto = new PasswordDto(); 101 | $passwordDto->setPassword('fournumbers1234'); 102 | $passwordDto->setPasswordConfirmation('fournumbers1234'); 103 | 104 | $this->assertTrue($passwordDto->doesPasswordContainNumbers(3)); 105 | $this->assertTrue($passwordDto->doesPasswordContainNumbers(4)); 106 | $this->assertFalse($passwordDto->doesPasswordContainNumbers(5)); 107 | } 108 | 109 | /** 110 | * @test 111 | */ 112 | public function passwordContainsSpecialCharacters() 113 | { 114 | $passwordDto = new PasswordDto(); 115 | $passwordDto->setPassword('4specialCHARS!"%$'); 116 | $passwordDto->setPasswordConfirmation('4specialCHARS!"%$'); 117 | 118 | $this->assertTrue($passwordDto->doesPasswordContainSpecialCharacters(3)); 119 | $this->assertTrue($passwordDto->doesPasswordContainSpecialCharacters(4)); 120 | $this->assertFalse($passwordDto->doesPasswordContainSpecialCharacters(5)); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Classes/Domain/Model/RegistrationFlow.php: -------------------------------------------------------------------------------- 1 | generateActivationToken(); 68 | } 69 | } 70 | 71 | /** 72 | * @param PasswordDto $passwordDto 73 | */ 74 | public function setPasswordDto(PasswordDto $passwordDto) 75 | { 76 | $this->passwordDto = $passwordDto; 77 | } 78 | 79 | /** 80 | * Generate a new activation token 81 | * 82 | * @throws Exception If the user has an account already 83 | */ 84 | public function generateActivationToken() 85 | { 86 | $this->activationToken = Algorithms::generateRandomString(30); 87 | $this->activationTokenValidUntil = (new \DateTime())->add(\DateInterval::createFromDateString($this->activationTokenTimeout)); 88 | } 89 | 90 | /** 91 | * Check if the user has a valid activation token. 92 | * 93 | * @return bool 94 | */ 95 | public function hasValidActivationToken() 96 | { 97 | if ($this->activationTokenValidUntil == null) { 98 | return false; 99 | } 100 | 101 | return $this->activationTokenValidUntil->getTimestamp() > time(); 102 | } 103 | 104 | /** 105 | * @return string 106 | */ 107 | public function getEmail() 108 | { 109 | return $this->email; 110 | } 111 | 112 | /** 113 | * @param string $email 114 | */ 115 | public function setEmail($email) 116 | { 117 | $this->email = $email; 118 | } 119 | 120 | /** 121 | * @return array 122 | */ 123 | public function getAttributes() 124 | { 125 | return $this->attributes; 126 | } 127 | 128 | /** 129 | * @param array $attributes 130 | */ 131 | public function setAttributes($attributes) 132 | { 133 | $this->attributes = $attributes; 134 | } 135 | 136 | public function storeEncryptedPassword() 137 | { 138 | $this->encryptedPassword = $this->passwordDto->getEncryptedPasswordAndRemoveNonencryptedVersion(); 139 | } 140 | 141 | /** 142 | * @return string 143 | */ 144 | public function getEncryptedPassword() 145 | { 146 | return $this->encryptedPassword; 147 | } 148 | 149 | /** 150 | * @return string 151 | */ 152 | public function getActivationToken() 153 | { 154 | return $this->activationToken; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Classes/Domain/Service/Neos/NeosRedirectTargetService.php: -------------------------------------------------------------------------------- 1 | redirectAfterLogin) 36 | && array_key_exists('action', $this->redirectAfterLogin) 37 | && array_key_exists('controller', $this->redirectAfterLogin) 38 | && array_key_exists('package', $this->redirectAfterLogin) 39 | ) { 40 | $controllerArguments = []; 41 | if (array_key_exists('controllerArguments', $this->redirectAfterLogin) && 42 | is_array($this->redirectAfterLogin['controllerArguments']) 43 | ) { 44 | $controllerArguments = $this->redirectAfterLogin['controllerArguments']; 45 | } 46 | 47 | return $controllerContext->getUriBuilder() 48 | ->reset() 49 | ->setCreateAbsoluteUri(true) 50 | ->uriFor($this->redirectAfterLogin['action'], $controllerArguments, 51 | $this->redirectAfterLogin['controller'], $this->redirectAfterLogin['package']); 52 | } 53 | 54 | // Neos only logic (configuration at node or via TS) 55 | /** @var ActionRequest $actionRequest */ 56 | $actionRequest = $controllerContext->getRequest(); 57 | if ($actionRequest->getInternalArgument('__redirectAfterLogin')) { 58 | return $this->getNodeLinkingService() 59 | ->createNodeUri($controllerContext, $actionRequest->getInternalArgument('__redirectAfterLogin')); 60 | } 61 | } 62 | 63 | public function onLogout(ControllerContext $controllerContext) 64 | { 65 | // Check if config for redirect is done 66 | if (is_array($this->redirectAfterLogout) 67 | && array_key_exists('action', $this->redirectAfterLogout) 68 | && array_key_exists('controller', $this->redirectAfterLogout) 69 | && array_key_exists('package', $this->redirectAfterLogout) 70 | ) { 71 | $controllerArguments = []; 72 | if (array_key_exists('controllerArguments', $this->redirectAfterLogout) && 73 | is_array($this->redirectAfterLogout['controllerArguments']) 74 | ) { 75 | $controllerArguments = $this->redirectAfterLogout['controllerArguments']; 76 | } 77 | 78 | return $controllerContext->getUriBuilder() 79 | ->reset() 80 | ->setCreateAbsoluteUri(true) 81 | ->uriFor($this->redirectAfterLogout['action'], $controllerArguments, 82 | $this->redirectAfterLogout['controller'], $this->redirectAfterLogout['package']); 83 | } 84 | 85 | // Neos only logic (configuration at node or via TS) 86 | /** @var ActionRequest $actionRequest */ 87 | $actionRequest = $controllerContext->getRequest(); 88 | if ($actionRequest->getInternalArgument('__redirectAfterLogout')) { 89 | return $this->getNodeLinkingService() 90 | ->createNodeUri($controllerContext, $actionRequest->getInternalArgument('__redirectAfterLogout')); 91 | } 92 | } 93 | 94 | /** 95 | * @return LinkingService 96 | */ 97 | protected function getNodeLinkingService() 98 | { 99 | return $this->objectManager->get(LinkingService::class); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Classes/Domain/Validator/CustomPasswordDtoValidator.php: -------------------------------------------------------------------------------- 1 | getResult(); 45 | 46 | // Matching PW and PW confirmation 47 | if (!$value->arePasswordsEqual()) { 48 | $message = $this->translator->translateById('validations.password.matching', [], null, null, 'Main', 'Sandstorm.UserManagement'); 49 | $result->forProperty('password')->addError(new Error($message, 1464086581)); 50 | } 51 | 52 | // Min length 53 | if (!$value->isPasswordMinLength($this->passwordConstraints['minLength'])) { 54 | $message = $this->translator->translateById('validations.password.minlength', [$this->passwordConstraints['minLength']], null, null, 'Main', 'Sandstorm.UserManagement'); 55 | $result->forProperty('password')->addError(new Error($message, 1542220177)); 56 | } 57 | 58 | // Max length 59 | if (!$value->isPasswordMaxLength($this->passwordConstraints['maxLength'])) { 60 | $message = $this->translator->translateById('validations.password.maxlength', [$this->passwordConstraints['maxLength']], null, null, 'Main', 'Sandstorm.UserManagement'); 61 | $result->forProperty('password')->addError(new Error($message, 1542220177)); 62 | } 63 | 64 | // minNumberOfLowercaseLetters 65 | if (!$value->doesPasswordContainLowercaseLetters($this->passwordConstraints['minNumberOfLowercaseLetters'])) { 66 | $message = $this->translator->translateById('validations.password.lowercase', [$this->passwordConstraints['minNumberOfLowercaseLetters']], $this->passwordConstraints['minNumberOfLowercaseLetters'], null, 'Main', 'Sandstorm.UserManagement'); 67 | $result->forProperty('password')->addError(new Error($message, 1542220177)); 68 | } 69 | 70 | // minNumberOfUppercaseLetters 71 | if (!$value->doesPasswordContainUppercaseLetters($this->passwordConstraints['minNumberOfUppercaseLetters'])) { 72 | $message = $this->translator->translateById('validations.password.uppercase', [$this->passwordConstraints['minNumberOfUppercaseLetters']], $this->passwordConstraints['minNumberOfUppercaseLetters'], null, 'Main', 'Sandstorm.UserManagement'); 73 | $result->forProperty('password')->addError(new Error($message, 1542220177)); 74 | } 75 | 76 | // minNumberOfNumbers 77 | if (!$value->doesPasswordContainNumbers($this->passwordConstraints['minNumberOfNumbers'])) { 78 | $message = $this->translator->translateById('validations.password.numbers', [$this->passwordConstraints['minNumberOfNumbers']], $this->passwordConstraints['minNumberOfNumbers'], null, 'Main', 'Sandstorm.UserManagement'); 79 | $result->forProperty('password')->addError(new Error($message, 1542220177)); 80 | } 81 | 82 | // minNumberOfSpecialCharacters 83 | if (!$value->doesPasswordContainSpecialCharacters($this->passwordConstraints['minNumberOfSpecialCharacters'])) { 84 | $message = $this->translator->translateById('validations.password.special', [$this->passwordConstraints['minNumberOfSpecialCharacters']], $this->passwordConstraints['minNumberOfSpecialCharacters'], null, 'Main', 'Sandstorm.UserManagement'); 85 | $result->forProperty('password')->addError(new Error($message, 1542220177)); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Classes/Controller/RegistrationController.php: -------------------------------------------------------------------------------- 1 | subjectActivation === 'i18n' 54 | ? $this->translator->translateById( 55 | 'email.subjectActivation', 56 | [], 57 | null, 58 | null, 59 | 'Main', 60 | 'Sandstorm.UserManagement' 61 | ) 62 | : $this->subjectActivation; 63 | } 64 | 65 | 66 | /** 67 | * @Flow\SkipCsrfProtection 68 | */ 69 | public function indexAction() 70 | { 71 | $this->view->assign('node', $this->request->getInternalArgument('__node')); 72 | } 73 | 74 | /** 75 | * @param RegistrationFlow $registrationFlow 76 | */ 77 | public function registerAction(RegistrationFlow $registrationFlow) 78 | { 79 | // We remove already existing flows 80 | $alreadyExistingFlows = $this->registrationFlowRepository->findByEmail($registrationFlow->getEmail()); 81 | if (count($alreadyExistingFlows) > 0) { 82 | foreach ($alreadyExistingFlows as $alreadyExistingFlow) { 83 | $this->registrationFlowRepository->remove($alreadyExistingFlow); 84 | } 85 | } 86 | $registrationFlow->storeEncryptedPassword(); 87 | 88 | // Send out a confirmation mail 89 | $activationLink = $this->uriBuilder->reset()->setCreateAbsoluteUri(true)->uriFor( 90 | 'activateAccount', 91 | ['token' => $registrationFlow->getActivationToken()], 92 | 'Registration'); 93 | 94 | $this->emailService->sendTemplateEmail( 95 | 'ActivationToken', 96 | $this->getSubjectActivation(), 97 | [$registrationFlow->getEmail()], 98 | [ 99 | 'activationLink' => $activationLink, 100 | 'registrationFlow' => $registrationFlow 101 | ], 102 | 'sandstorm_usermanagement_sender_email', 103 | [], // cc 104 | [], // bcc 105 | [], // attachments 106 | 'sandstorm_usermanagement_replyTo_email' 107 | ); 108 | 109 | $this->registrationFlowRepository->add($registrationFlow); 110 | 111 | $this->view->assign('registrationFlow', $registrationFlow); 112 | $this->view->assign('node', $this->request->getInternalArgument('__node')); 113 | } 114 | 115 | /** 116 | * @param string $token 117 | */ 118 | public function activateAccountAction($token) 119 | { 120 | /* @var $registrationFlow \Sandstorm\UserManagement\Domain\Model\RegistrationFlow */ 121 | $registrationFlow = $this->registrationFlowRepository->findOneByActivationToken($token); 122 | if (!$registrationFlow) { 123 | $this->view->assign('tokenNotFound', true); 124 | 125 | return; 126 | } 127 | 128 | if (!$registrationFlow->hasValidActivationToken()) { 129 | $this->view->assign('tokenTimeout', true); 130 | 131 | return; 132 | } 133 | 134 | $user = $this->userCreationService->createUserAndAccount($registrationFlow); 135 | $this->registrationFlowRepository->remove($registrationFlow); 136 | $this->persistenceManager->allowObject($registrationFlow); 137 | 138 | $this->view->assign('success', true); 139 | $this->view->assign('user', $user); 140 | $this->view->assign('node', $this->request->getInternalArgument('__node')); 141 | } 142 | 143 | /** 144 | * Disable the technical error flash message 145 | * 146 | * @return boolean 147 | */ 148 | protected function getErrorFlashMessage() 149 | { 150 | return false; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Classes/Controller/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | subjectResetPassword === 'i18n' 61 | ? $this->translator->translateById( 62 | 'email.subjectResetPassword', 63 | [], 64 | null, 65 | null, 66 | 'Main', 67 | 'Sandstorm.UserManagement' 68 | ) 69 | : $this->subjectResetPassword; 70 | } 71 | 72 | /** 73 | * @Flow\SkipCsrfProtection 74 | */ 75 | public function indexAction(string $email = '') 76 | { 77 | $this->view->assign('prefilledEmail', $email); 78 | } 79 | 80 | public function initializeRequestTokenAction() 81 | { 82 | $config = $this->arguments->getArgument('resetPasswordFlow')->getPropertyMappingConfiguration(); 83 | $config->allowProperties('email'); 84 | $config->setTypeConverterOption( 85 | PersistentObjectConverter::class, 86 | PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, 87 | TRUE 88 | ); 89 | } 90 | 91 | /** 92 | * @param ResetPasswordFlow $resetPasswordFlow 93 | */ 94 | public function requestTokenAction(ResetPasswordFlow $resetPasswordFlow) 95 | { 96 | $account = $this->accountRepository->findActiveByAccountIdentifierAndAuthenticationProviderName($resetPasswordFlow->getEmail(), 97 | 'Sandstorm.UserManagement:Login'); 98 | 99 | if ($account !== null) { 100 | $alreadyExistingFlows = $this->resetPasswordFlowRepository->findByEmail($resetPasswordFlow->getEmail()); 101 | if (count($alreadyExistingFlows) > 0) { 102 | foreach ($alreadyExistingFlows as $alreadyExistingFlow) { 103 | $this->resetPasswordFlowRepository->remove($alreadyExistingFlow); 104 | } 105 | } 106 | 107 | // Send out a confirmation mail 108 | $resetPasswordLink = $this->uriBuilder->reset()->setCreateAbsoluteUri(true)->uriFor( 109 | 'insertNewPassword', 110 | ['token' => $resetPasswordFlow->getResetPasswordToken()], 111 | 'ResetPassword'); 112 | 113 | $this->emailService->sendTemplateEmail( 114 | 'ResetPasswordToken', 115 | $this->getSubjectResetPassword(), 116 | [$resetPasswordFlow->getEmail()], 117 | [ 118 | 'resetPasswordLink' => $resetPasswordLink, 119 | 'resetPasswordFlow' => $resetPasswordFlow 120 | ], 121 | 'sandstorm_usermanagement_sender_email', 122 | [], // cc 123 | [], // bcc 124 | [], // attachments 125 | 'sandstorm_usermanagement_replyTo_email' 126 | ); 127 | 128 | $this->resetPasswordFlowRepository->add($resetPasswordFlow); 129 | } 130 | 131 | 132 | $this->view->assign('resetPasswordFlow', $resetPasswordFlow); 133 | $this->view->assign('account', $account); 134 | } 135 | 136 | /** 137 | * @param string $token 138 | */ 139 | public function insertNewPasswordAction($token) 140 | { 141 | /* @var $resetPasswordFlow ResetPasswordFlow */ 142 | $resetPasswordFlow = $this->resetPasswordFlowRepository->findOneByResetPasswordToken($token); 143 | if (!$resetPasswordFlow) { 144 | $this->view->assign('tokenNotFound', true); 145 | 146 | return; 147 | } 148 | 149 | if (!$resetPasswordFlow->hasValidResetPasswordToken()) { 150 | $this->view->assign('tokenTimeout', true); 151 | 152 | return; 153 | } 154 | 155 | $this->view->assign('success', true); 156 | 157 | $this->view->assign('resetPasswordFlow', $resetPasswordFlow); 158 | } 159 | 160 | 161 | /** 162 | * @param ResetPasswordFlow $resetPasswordFlow 163 | */ 164 | public function updatePasswordAction(ResetPasswordFlow $resetPasswordFlow) 165 | { 166 | $account = $this->accountRepository->findActiveByAccountIdentifierAndAuthenticationProviderName($resetPasswordFlow->getEmail(), 167 | 'Sandstorm.UserManagement:Login'); 168 | 169 | if (!$account) { 170 | $this->view->assign('accountNotFound', true); 171 | 172 | return; 173 | } 174 | 175 | $this->view->assign('success', true); 176 | $account->setCredentialsSource($resetPasswordFlow->getEncryptedPassword()); 177 | $this->accountRepository->update($account); 178 | $this->resetPasswordFlowRepository->remove($resetPasswordFlow); 179 | } 180 | 181 | /** 182 | * Disable the default error flash message 183 | * 184 | * @return boolean 185 | */ 186 | protected function getErrorFlashMessage() 187 | { 188 | return false; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Resources/Private/Translations/en/Main.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Email address 7 | 8 | 9 | Login 10 | 11 | 12 | To the login 13 | 14 | 15 | Password 16 | 17 | 18 | Your password 19 | 20 | 21 | Password forgotten 22 | 23 | 24 | Logout 25 | 26 | 27 | You are logged in as {0}. 28 | 29 | 30 | First name 31 | 32 | 33 | Last name 34 | 35 | 36 | Confirm password 37 | 38 | 39 | Repeat password 40 | 41 | 42 | Salutation 43 | 44 | 45 | Ms. 46 | 47 | 48 | Mr. 49 | 50 | 51 | Register 52 | 53 | 54 | We have sent an email to confirm the registration to %s. Please follow the instructions in the email to confirm the registration. 55 | 56 | 57 | The activation link was not valid. 58 | 59 | 60 | Please register again. 61 | 62 | 63 | The activation link has expired. 64 | 65 | 66 | Your account is has been activated. You can now log in to our website. 67 | 68 | 69 | Unable to login. 70 | 71 | 72 | You have entered invalid access data. Please try again. 73 | 74 | 75 | Please confirm your account 76 | 77 | 78 | Password reset 79 | 80 | 81 | Email address {0} is already in use! 82 | 83 | 84 | Your passwords do not match. 85 | 86 | 87 | Your password needs to be at least {0} characters long. 88 | 89 | 90 | Your password must be at most {0} characters long. 91 | 92 | 93 | 94 | Your password needs to contain at least {0} lowercase letter. 95 | 96 | 97 | Your password needs to contain at least {0} lowercase letters. 98 | 99 | 100 | 101 | 102 | Your password needs to contain at least {0} uppercase letter. 103 | 104 | 105 | Your password needs to contain at least {0} uppercase letters. 106 | 107 | 108 | 109 | 110 | Your password needs to contain at least {0} number. 111 | 112 | 113 | Your password needs to contain at least {0} numbers. 114 | 115 | 116 | 117 | 118 | Your password needs to contain at least {0} special character. 119 | 120 | 121 | Your password needs to contain at least {0} special characters. 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /Classes/Controller/LoginController.php: -------------------------------------------------------------------------------- 1 | loginFailedTitle === 'i18n' 59 | ? $this->translator->translateById( 60 | 'authFailedMessage.title', 61 | [], 62 | null, 63 | null, 64 | 'Main', 65 | 'Sandstorm.UserManagement' 66 | ) 67 | : $this->loginFailedTitle; 68 | } 69 | 70 | /** 71 | * @var string 72 | * @Flow\InjectConfiguration(path="authFailedMessage.body") 73 | */ 74 | protected $loginFailedBody; 75 | 76 | /** 77 | * @return string 78 | */ 79 | protected function getLoginFailedBody() 80 | { 81 | return $this->loginFailedBody === 'i18n' 82 | ? $this->translator->translateById( 83 | 'authFailedMessage.body', 84 | [], 85 | null, 86 | null, 87 | 'Main', 88 | 'Sandstorm.UserManagement' 89 | ) 90 | : $this->loginFailedBody; 91 | } 92 | 93 | /** 94 | * SkipCsrfProtection is needed here because we will have errors otherwise if we render multiple 95 | * plugins on the same page 96 | * 97 | * @return void 98 | * @Flow\SkipCsrfProtection 99 | */ 100 | public function loginAction() 101 | { 102 | $this->view->assign('account', $this->securityContext->getAccount()); 103 | $this->view->assign('node', $this->request->getInternalArgument('__node')); 104 | } 105 | 106 | /** 107 | * Is called after a request has been authenticated. 108 | * 109 | * @param \Neos\Flow\Mvc\ActionRequest $originalRequest The request that was intercepted by the security framework, NULL if there was none 110 | * @throws \Neos\Flow\Exception 111 | * @return string 112 | */ 113 | protected function onAuthenticationSuccess(ActionRequest $originalRequest = null) 114 | { 115 | $this->emitAuthenticationSuccess($this->controllerContext, $originalRequest); 116 | 117 | $result = $this->redirectTargetService->onAuthenticationSuccess($this->controllerContext, $originalRequest); 118 | if (is_string($result)) { 119 | $this->redirectToUri($result); 120 | } elseif ($result instanceof ActionRequest) { 121 | $this->redirectToRequest($result); 122 | } 123 | 124 | if ($result === null) { 125 | $this->view->assign('account', $this->securityContext->getAccount()); 126 | } else { 127 | throw new Exception('RedirectTargetServiceInterface::onAuthenticationSuccess must return either null, an URL string or an ActionRequest object, but was: ' . 128 | gettype($result) . ' - ' . get_class($result), 1464164500); 129 | } 130 | } 131 | 132 | /** 133 | * Is called if authentication failed. 134 | * 135 | * Override this method in your login controller to take any 136 | * custom action for this event. Most likely you would want 137 | * to redirect to some action showing the login form again. 138 | * 139 | * @param \Neos\Flow\Security\Exception\AuthenticationRequiredException $exception The exception thrown while the authentication process 140 | * @return void 141 | */ 142 | protected function onAuthenticationFailure(AuthenticationRequiredException $exception = null) 143 | { 144 | $this->emitAuthenticationFailure($this->controllerContext, $exception); 145 | $this->addFlashMessage($this->getLoginFailedBody(), $this->getLoginFailedTitle(),Message::SEVERITY_ERROR, [], ($exception === null ? 1347016771 : $exception->getCode())); 146 | } 147 | 148 | /** 149 | * Logs all active tokens out. 150 | */ 151 | public function logoutAction() 152 | { 153 | parent::logoutAction(); 154 | 155 | $this->emitLogout($this->controllerContext); 156 | 157 | $result = $this->redirectTargetService->onLogout($this->controllerContext); 158 | 159 | if (is_string($result)) { 160 | $this->redirectToUriAndShutdown($result); 161 | } elseif ($result instanceof ActionRequest) { 162 | $this->redirectToRequest($result); 163 | } else if ($result === null) { 164 | // Default: redirect to login 165 | $this->redirect('login'); 166 | } else { 167 | throw new Exception('RedirectTargetServiceInterface::onLogout must return either null, an URL string or an ActionRequest object, but was: ' . 168 | gettype($result) . ' - ' . get_class($result), 1464164500); 169 | } 170 | } 171 | 172 | /** 173 | * Disable the default error flash message 174 | * 175 | * @return boolean 176 | */ 177 | protected function getErrorFlashMessage() 178 | { 179 | return false; 180 | } 181 | 182 | /** 183 | * @param ControllerContext $controllerContext 184 | * @param ActionRequest $originalRequest 185 | * @Flow\Signal 186 | */ 187 | protected function emitAuthenticationSuccess(ControllerContext $controllerContext, ActionRequest $originalRequest = null) 188 | { 189 | } 190 | 191 | /** 192 | * @param ControllerContext $controllerContext 193 | * @param AuthenticationRequiredException $exception 194 | * @Flow\Signal 195 | */ 196 | protected function emitAuthenticationFailure(ControllerContext $controllerContext, AuthenticationRequiredException $exception = null) 197 | { 198 | } 199 | 200 | /** 201 | * @param ControllerContext $controllerContext 202 | * @Flow\Signal 203 | */ 204 | protected function emitLogout(ControllerContext $controllerContext) 205 | { 206 | } 207 | 208 | /** 209 | * @param string $result 210 | */ 211 | protected function redirectToUriAndShutdown(string $result) 212 | { 213 | $escapedUri = htmlentities($result, ENT_QUOTES, 'utf-8'); 214 | $this->redirectToUri($this->uriFactory->createUri((string)$escapedUri)); 215 | $this->bootstrap->shutdown(Bootstrap::RUNLEVEL_RUNTIME); 216 | exit(); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Resources/Private/Translations/de/Main.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Email address 7 | E-mail-Adresse 8 | 9 | 10 | Login 11 | Sich anmelden 12 | 13 | 14 | To the login 15 | Zum Login 16 | 17 | 18 | Password 19 | Passwort 20 | 21 | 22 | Your password 23 | Ihr Passwort 24 | 25 | 26 | Password forgotten 27 | Passwort vergessen 28 | 29 | 30 | Logout 31 | Ausloggen 32 | 33 | 34 | You are logged in as {0}. 35 | Sie sind eingeloggt als {0}. 36 | 37 | 38 | First name 39 | Vorname 40 | 41 | 42 | Last name 43 | Nachname 44 | 45 | 46 | Confirm password 47 | Passwort bestätigen 48 | 49 | 50 | Repeat password 51 | Passwort wiederholen 52 | 53 | 54 | Salutation 55 | Anrede 56 | 57 | 58 | Ms. 59 | Frau 60 | 61 | 62 | Mr. 63 | Herr 64 | 65 | 66 | Register 67 | Registrieren 68 | 69 | 70 | We have sent an email to confirm the registration to %s. Please follow the instructions in the email to confirm the registration. 71 | Wir haben eine E-Mail zur Bestätigung der Registrierung an %s gesendet. Bitte folgen Sie der Anleitung in der E-Mail, um die Registrierung zu bestätigen. 72 | 73 | 74 | The activation link was not valid. 75 | Der Aktivierungslink war nicht gültig. 76 | 77 | 78 | Please register again. 79 | Bitte registrieren Sie sich erneut. 80 | 81 | 82 | The activation link has expired. 83 | Der Aktivierungslink ist nicht mehr gültig. 84 | 85 | 86 | Your account is has been activated. You can now log in to our website. 87 | Ihr Konto ist aktiviert worden. Sie können sich jetzt auf unserer Website einloggen. 88 | 89 | 90 | Unable to login. 91 | Login nicht möglich 92 | 93 | 94 | You have entered invalid access data. Please try again. 95 | Sie haben ungültige Zugangsdaten eingegeben. Bitte versuchen Sie es noch einmal. 96 | 97 | 98 | Please confirm your account 99 | Bitte bestätigen Sie Ihr Konto 100 | 101 | 102 | Password reset 103 | Passwort zurücksetzen 104 | 105 | 106 | Email address {0} is already in use! 107 | Die E-Mail-Adresse {0} wird bereits verwendet! 108 | 109 | 110 | Your passwords do not match. 111 | Die Passwörter stimmen nicht überein. 112 | 113 | 114 | Your password needs to be at least {0} characters long. 115 | Das Passwort muss mindestens {0} Zeichen lang sein. 116 | 117 | 118 | Your password must be at most {0} characters long. 119 | Das Passwort darf höchstens {0} Zeichen lang sein. 120 | 121 | 122 | 123 | Your password needs to contain at least {0} lowercase letter. 124 | Das Passwort muss mindestens {0} Kleinbuchstaben enthalten. 125 | 126 | 127 | Your password needs to contain at least {0} lowercase letters. 128 | Das Passwort muss mindestens {0} Kleinbuchstaben enthalten. 129 | 130 | 131 | 132 | 133 | Your password needs to contain at least {0} uppercase letter. 134 | Das Passwort muss mindestens {0} Großbuchstaben enthalten. 135 | 136 | 137 | Your password needs to contain at least {0} uppercase letters. 138 | Das Passwort muss mindestens {0} Großbuchstaben enthalten. 139 | 140 | 141 | 142 | 143 | Your password needs to contain at least {0} number. 144 | Das Passwort muss mindestens {0} Zahl enthalten. 145 | 146 | 147 | Your password needs to contain at least {0} numbers. 148 | Das Passwort muss mindestens {0} Zahlen enthalten. 149 | 150 | 151 | 152 | 153 | Your password needs to contain at least {0} special character. 154 | Das Passwort muss mindestens {0} Sonderzeichen (z.B. %!=) enthalten. 155 | 156 | 157 | Your password needs to contain at least {0} special characters. 158 | Das Passwort muss mindestens {0} Sonderzeichen (z.B. %!=) enthalten. 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /Resources/Private/Translations/fr/Main.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Email address 7 | Adresse email 8 | 9 | 10 | Login 11 | Se connecter 12 | 13 | 14 | To the login 15 | Vers le login 16 | 17 | 18 | Password 19 | Mot de passe 20 | 21 | 22 | Your password 23 | Votre mot de passe 24 | 25 | 26 | Password forgotten 27 | Mot de passe oublié 28 | 29 | 30 | Logout 31 | Se déconnecter 32 | 33 | 34 | You are logged in as {0}. 35 | Vous êtes connecté en tant que {0}. 36 | 37 | 38 | First name 39 | Prénom 40 | 41 | 42 | Last name 43 | Nom de famille 44 | 45 | 46 | Confirm password 47 | Confirmation du mot de passe 48 | 49 | 50 | Repeat password 51 | Répéter le mot de passe 52 | 53 | 54 | Salutation 55 | Salutation 56 | 57 | 58 | Ms. 59 | Madame 60 | 61 | 62 | Mr. 63 | Monsieur 64 | 65 | 66 | Register 67 | S'inscrire 68 | 69 | 70 | We have sent an email to confirm the registration to {0}. Please follow the instructions in the email to confirm the registration. 71 | Nous avons envoyé un email à {0} pour confirmer l'enregistrement. Veuillez suivre les instructions contenues dans l'e-mail pour confirmer l'inscription. 72 | 73 | 74 | The activation link was not valid. 75 | Le lien d'activation n'était pas valide. 76 | 77 | 78 | Please register again. 79 | Veuillez vous réinscrire. 80 | 81 | 82 | The activation link has expired. 83 | Le lien d'activation a expiré. 84 | 85 | 86 | Your account is has been activated. You can now log in to our website. 87 | Votre compte a été activé. Vous pouvez maintenant vous connecter à notre site web. 88 | 89 | 90 | Unable to login. 91 | Impossible de se connecter. 92 | 93 | 94 | You have entered invalid access data. Please try again. 95 | Vous avez saisi des données d'accès invalides. Veuillez réessayer. 96 | 97 | 98 | Please confirm your account 99 | Veuillez confirmer votre compte 100 | 101 | 102 | Password reset 103 | Réinitialisation du mot de passe 104 | 105 | 106 | Email address {0} is already in use! 107 | L'adresse e-mail {0} est déjà utilisée ! 108 | 109 | 110 | Your passwords do not match. 111 | Vos mots de passe ne correspondent pas. 112 | 113 | 114 | Your password needs to be at least {0} characters long. 115 | Votre mot de passe doit comporter au moins {0} caractères. 116 | 117 | 118 | Your password must be at most {0} characters long. 119 | Votre mot de passe doit comporter au maximum {0} caractères. 120 | 121 | 122 | 123 | Your password needs to contain at least {0} lowercase letter. 124 | Votre mot de passe doit contenir au moins {0} lettre minuscule. 125 | 126 | 127 | Your password needs to contain at least {0} lowercase letters. 128 | Votre mot de passe doit contenir au moins {0} lettres minuscules. 129 | 130 | 131 | 132 | 133 | Your password needs to contain at least {0} uppercase letter. 134 | Votre mot de passe doit contenir au moins {0} lettre majuscule. 135 | 136 | 137 | Your password needs to contain at least {0} uppercase letters. 138 | Votre mot de passe doit contenir au moins {0} lettres majuscules. 139 | 140 | 141 | 142 | 143 | Your password needs to contain at least {0} number. 144 | Votre mot de passe doit contenir au moins {0} chiffre. 145 | 146 | 147 | Your password needs to contain at least {0} numbers. 148 | Votre mot de passe doit contenir au moins {0} chiffres. 149 | 150 | 151 | 152 | 153 | Your password needs to contain at least {0} special character. 154 | Votre mot de passe doit contenir au moins {0} caractère spécial. 155 | 156 | 157 | Your password needs to contain at least {0} special characters. 158 | Votre mot de passe doit contenir au moins {0} caractères spéciaux. 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /Classes/Command/SandstormUserCommandController.php: -------------------------------------------------------------------------------- 1 | 0) { 97 | $attributesSplitBySeparator = explode(';', $additionalAttributes); 98 | array_map(function ($singleAttribute) use (&$attributes) { 99 | $splitAttribute = explode(':', $singleAttribute); 100 | $attributes[$splitAttribute[0]] = $splitAttribute[1]; 101 | }, $attributesSplitBySeparator); 102 | } 103 | 104 | $passwordDto = new PasswordDto(); 105 | $passwordDto->setPassword($password); 106 | $passwordDto->setPasswordConfirmation($password); 107 | $registrationFlow = new RegistrationFlow(); 108 | $registrationFlow->setPasswordDto($passwordDto); 109 | $registrationFlow->setEmail($username); 110 | $registrationFlow->setAttributes($attributes); 111 | 112 | // Remove existing registration flows 113 | $alreadyExistingFlows = $this->registrationFlowRepository->findByEmail($registrationFlow->getEmail()); 114 | if (count($alreadyExistingFlows) > 0) { 115 | foreach ($alreadyExistingFlows as $alreadyExistingFlow) { 116 | $this->registrationFlowRepository->remove($alreadyExistingFlow); 117 | } 118 | } 119 | $registrationFlow->storeEncryptedPassword(); 120 | 121 | // Store the RF and persist so the activate command will find it 122 | $this->registrationFlowRepository->add($registrationFlow); 123 | $this->persistenceManager->persistAll(); 124 | 125 | // Directly activate the account 126 | $this->activateRegistrationCommand($username); 127 | 128 | $this->outputLine('Added the User "%s" with password "%s".', [$username, $password]); 129 | } 130 | 131 | /** 132 | * @param string $username The username identifying a pending registration flow. 133 | */ 134 | public function activateRegistrationCommand($username) 135 | { 136 | /* @var $registrationFlow \Sandstorm\UserManagement\Domain\Model\RegistrationFlow */ 137 | $registrationFlow = $this->registrationFlowRepository->findOneByEmail($username); 138 | 139 | if ($registrationFlow === null) { 140 | $this->outputLine('The user ' . $username . ' doesn\'t have a non-activated account.'); 141 | $this->quit(1); 142 | } 143 | 144 | $this->userCreationService->createUserAndAccount($registrationFlow); 145 | $this->registrationFlowRepository->remove($registrationFlow); 146 | $this->outputLine('The user ' . $username . ' was activated.'); 147 | } 148 | 149 | 150 | /** 151 | * Set a new password for the given user 152 | * 153 | * @param string $username user to modify 154 | * @param string $password new password 155 | * @param string $authenticationProvider Name of the authentication provider to use for finding the user. Default: "Sandstorm.UserManagement:Login". 156 | * @return void 157 | */ 158 | public function setPasswordCommand($username, $password, $authenticationProvider = 'Sandstorm.UserManagement:Login') 159 | { 160 | // If we're in Neos context, we simply forward the command to the Neos command controller. 161 | if ($this->shouldUseNeosService()) { 162 | $cliRequest = new Request($this->request); 163 | $cliRequest->setControllerObjectName(UserCommandController::class); 164 | $cliRequest->setControllerCommandName('setPassword'); 165 | $cliRequest->setArguments([ 166 | 'username' => $username, 167 | 'password' => $password, 168 | 'authenticationProvider' => $authenticationProvider 169 | ]); 170 | $cliResponse = new Response($this->response); 171 | $this->dispatcher->dispatch($cliRequest, $cliResponse); 172 | $this->quit(0); 173 | } 174 | 175 | // Otherwise, we use our own logic. 176 | $account = $this->accountRepository->findByAccountIdentifierAndAuthenticationProviderName($username, 177 | $authenticationProvider); 178 | 179 | if ($account === null) { 180 | $this->outputLine('The user ' . $username . ' could not be found with auth provider ' . 181 | $authenticationProvider . '.'); 182 | $this->quit(1); 183 | } 184 | 185 | $encrypted = $this->hashService->hashPassword($password); 186 | $account->setCredentialsSource($encrypted); 187 | $this->accountRepository->update($account); 188 | $this->outputLine('Password for user ' . $username . ' changed.'); 189 | } 190 | 191 | /** 192 | * Removes a user and his account. 193 | * 194 | * @param string $username user to remove 195 | * @return void 196 | */ 197 | public function removeCommand($username) 198 | { 199 | /** @var User $user */ 200 | $user = $this->userRepository->findOneByEmail($username); 201 | if ($user === null) { 202 | $this->outputLine('The user ' . $username . ' could not be found.'); 203 | $this->quit(1); 204 | } 205 | 206 | $this->userRepository->remove($user); 207 | $this->accountRepository->remove($user->getAccount()); 208 | $this->outputLine('Removed the user ' . $username . '.'); 209 | } 210 | 211 | /** 212 | * Lists all available accounts. 213 | */ 214 | public function listAccountsCommand() 215 | { 216 | /** @var Account[] $accounts */ 217 | $accounts = $this->accountRepository->findAll()->toArray(); 218 | usort($accounts, function ($a, $b) { 219 | /** @var Account $a */ 220 | /** @var Account $b */ 221 | return ($a->getAccountIdentifier() > $b->getAccountIdentifier()); 222 | }); 223 | 224 | $tableRows = []; 225 | $headerRow = ['Identifier', 'Authentication Provider', 'Role(s)']; 226 | 227 | foreach ($accounts as $account) { 228 | $tableRows[] = [ 229 | $account->getAccountIdentifier(), 230 | $account->getAuthenticationProviderName(), 231 | implode(' ,', $account->getRoles()) 232 | ]; 233 | } 234 | 235 | $this->output->outputTable($tableRows, $headerRow); 236 | $this->outputLine(sprintf(' %s accounts total.', count($accounts))); 237 | } 238 | 239 | /** 240 | * Lists all available users. 241 | */ 242 | public function listUsersCommand() 243 | { 244 | // If we're in Neos context, we pass on the command. 245 | if ($this->shouldUseNeosService()) { 246 | $cliRequest = new Request($this->request); 247 | $cliRequest->setControllerObjectName(UserCommandController::class); 248 | $cliRequest->setControllerCommandName('list'); 249 | $cliResponse = new Response($this->response); 250 | $this->dispatcher->dispatch($cliRequest, $cliResponse); 251 | 252 | return; 253 | } 254 | /** @var User[] $users */ 255 | $users = $this->userRepository->findAll()->toArray(); 256 | usort($users, function ($a, $b) { 257 | /** @var User $a */ 258 | /** @var User $b */ 259 | return ($a->getEmail() > $b->getEmail()); 260 | }); 261 | 262 | $tableRows = []; 263 | $headerRow = ['Email', 'Name', 'Role(s)']; 264 | 265 | foreach ($users as $user) { 266 | $tableRows[] = [$user->getEmail(), $user->getFullName(), implode(' ,', $user->getAccount()->getRoles())]; 267 | } 268 | 269 | $this->output->outputTable($tableRows, $headerRow); 270 | $this->outputLine(sprintf(' %s users total.', count($users))); 271 | } 272 | 273 | /** 274 | * Add a role to a given account 275 | * 276 | * @param string $accountIdentifier 277 | * @param string $role 278 | * @throws \Neos\Flow\Persistence\Exception\IllegalObjectTypeException 279 | */ 280 | public function addRoleCommand($accountIdentifier, $role) 281 | { 282 | /** @var Account $account */ 283 | $account = $this->accountRepository->findOneByAccountIdentifier($accountIdentifier); 284 | if ($account === null) { 285 | $this->outputLine(sprintf('Account %s not found! Exiting.', $accountIdentifier)); 286 | $this->quit(1); 287 | } 288 | 289 | $account->addRole(new Role($role)); 290 | $this->accountRepository->update($account); 291 | $this->outputLine(sprintf(' Account %s now has roles %s.', $accountIdentifier, implode(', ', $account->getRoles()))); 292 | } 293 | 294 | /** 295 | * Remove a role from an accont 296 | * 297 | * @param string $accountIdentifier 298 | * @param string $role 299 | * @throws \Neos\Flow\Persistence\Exception\IllegalObjectTypeException 300 | */ 301 | public function removeRoleCommand($accountIdentifier, $role) 302 | { 303 | /** @var Account $account */ 304 | $account = $this->accountRepository->findOneByAccountIdentifier($accountIdentifier); 305 | if ($account === null) { 306 | $this->outputLine(sprintf('Account %s not found! Exiting.', $accountIdentifier)); 307 | $this->quit(1); 308 | } 309 | 310 | $account->removeRole(new Role($role)); 311 | $this->accountRepository->update($account); 312 | $this->outputLine(sprintf(' Account %s now has roles %s.', $accountIdentifier, implode(', ', $account->getRoles()))); 313 | } 314 | 315 | /** 316 | * We check if we're in the Neos context by checking if we're using the Neos user creation service. 317 | * 318 | * @return boolean 319 | */ 320 | protected function shouldUseNeosService() 321 | { 322 | // The userCreationService is a DependencyProxy instance here, we can get the class name from it 323 | return get_class($this->userCreationService) === NeosUserCreationService::class; 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sandstorm.UserManagement Neos / Flow Package 2 | 3 | # 0. Features 4 | This package works in Neos CMS and Flow and provides the following functionality: 5 | 6 | * Registration of (frontend) users via a registration form 7 | * Sending out an e-mail for account confirmation 8 | * Login of registered (frontend) users via a login form 9 | * "Forgotten password" with password reset e-mail 10 | 11 | # 1. Compatibility and Maintenance 12 | Sandstorm.UserManagement is currently being maintained for the following versions: 13 | 14 | | Neos / Flow Version | Sandstorm.UserManagement Version | Branch | Maintained | 15 | |----------------------------|----------------------------------|--------|------------| 16 | | Neos 5.x-8.x, Flow 6.x-8.x | 7.x | master | Yes | 17 | | Neos 4.x, Flow 5.x | 6.x | 6.0 | Yes | 18 | | Neos 3.x, Flow 4.x | 5.x | 5.0 | Bugfixes | 19 | | Neos 2.3 LTS, Flow 3.3 LTS | 3.x | 3.0 | No | 20 | | Neos 2.2, Flow 3.2 | 1.x | - | No | 21 | 22 | ## Breaking changes in Version 5.x 23 | ### Configuration Changes 24 | Since I've removed the direct dependency to swiftmailer in favor of the Sandstorm/TemplateMailer package 25 | (which provides css inlining), the EmailService in this package was removed. This means that you will need 26 | to change some of your config options, because they are now set in the Sandstorm.TemplateMailer config path 27 | instead of inside the Sandstom.UserManagement path. Please refer to the [Sandstorm/TemplateMailer Documentation](https://github.com/sandstorm/TemplateMailer) 28 | for instructions on how to set the following configurations: 29 | 30 | * senderAddress 31 | * senderName 32 | * templatePackage 33 | 34 | Hint: to override the sender address for this package, you will need the following setting: 35 | ```YAML 36 | Sandstorm: 37 | TemplateMailer: 38 | senderAddresses: 39 | sandstorm_usermanagement_sender_email: # You need to use this exact key to override the UserManagement defaults 40 | name: Your-App 41 | address: yoursenderemail@yourapp.de 42 | ``` 43 | 44 | ### Changes to Email Templates 45 | In the registration email templates, two variables are no longer available by default: 46 | * "applicationName" (filled with configured email senderAddress) 47 | * "email" (filled with the email address the mail is sent to) 48 | However, in the registration email, "registrationFlow" is now available, which gives access to the email as well to all 49 | other information the user has entered during the registration process (as long as it is stored in the RegistrationFlow object). 50 | 51 | To overwrite the existing Activation- and PasswordReset templates, do the following: 52 | * Add `ActivationToken.html`, `ActivationToken.txt`, `ResetPasswordToken.html` and `ResetPasswordToken.txt` to `/Resources/Private/EmailTemplates/` 53 | * Fill in the necessary information (you can use the [sandstormUserManagement templates](https://github.com/sandstorm/UserManagement/tree/master/Resources/Private/EmailTemplates) as a point of reference 54 | * Add your package to your TemplateMailer-Configuration https://github.com/sandstorm/TemplateMailer#configuring-template-source-packages 55 | 56 | # 2. Configuration 57 | 58 | ## Setup 59 | There are the basic config steps: 60 | 1. Run `./flow doctrine:migrate` after you add this package to install its model. The package automatically exposes its routes 61 | via auto-inclusion in the package settings. Attention: Any routes defined in the global `Routes.yaml` are loaded before this package's 62 | routes, so they may be overriden. This is especially true for the default Flow subroutes, so make sure you have removed those from your global `Routes.yaml`. 63 | If you can't remove them, just include the subroutes for this package manually before the Flow subroutes. 64 | 65 | 2. Require this package in your own package's `composer.json`. This will inform Flow that it needs to load UserManagement before 66 | your packages, which allows you to override config and will make sure authorizations work correctly. Keep in mind that you have to add this into all 67 | packages that use features from user management - very important if your site is split into multiple packages or plugins. 68 | Here's an example: 69 | ``` 70 | { 71 | "description": "Your Site Package", 72 | "type": "neos-site", (or "neos-package" if you're using Flow only or building a Plugin) 73 | "require": { 74 | "neos/neos": "*", 75 | "sandstorm/usermanagement": "*" 76 | } 77 | ...more settings here... 78 | } 79 | ``` 80 | 81 | 3. Run `./flow neos.flow:package:rescan` to regenerate to order in which all your packages are loaded. 82 | 83 | 4. Add and adapt the configuration settings below to your config (make sure to not miss the special Neos settings). 84 | 85 | ## Basic configuration options 86 | These are the basic configuration options for e-mails, timeouts etc. You will usually want to adapt these to your application. 87 | ``` 88 | Sandstorm: 89 | UserManagement: 90 | # Validity timespan for the activation token for newly registered users. 91 | activationTokenTimeout: '2 days' 92 | # Validity timespan for the token used to reset passwords. 93 | resetPasswordTokenTimeout: '4 hours' 94 | # The message that appears if a user could not be logged in. 95 | authFailedMessage: 96 | title: 'Login nicht möglich' 97 | body: 'Sie haben ungültige Zugangsdaten eingegeben. Bitte versuchen Sie es noch einmal.' 98 | # Email settings 99 | email: 100 | # Subject line for the account confirmation email 101 | subjectActivation: 'Please confirm your account' 102 | # Subject line for the password reset email 103 | subjectResetPassword: 'Password reset' 104 | # An array of roles which are assigned to users after they activate their account. 105 | rolesForNewUsers: [] 106 | ``` 107 | 108 | ### I18N 109 | It is possible to use i18n for the messages configured in the settings. Simply by setting the values to 'i18n'. 110 | ``` 111 | Sandstorm: 112 | UserManagement: 113 | authFailedMessage: 114 | title: 'i18n' 115 | body: 'i18n' 116 | email: 117 | subjectActivation: 'i18n' 118 | subjectResetPassword: 'i18n' 119 | ``` 120 | 121 | ## Additional Settings for usage in Neos 122 | You should switch the implementation of the Redirect and User Creation Services to the Neos services. Add this to your `Objects.yaml`: 123 | ``` 124 | # Use the Neos services 125 | Sandstorm\UserManagement\Domain\Service\RedirectTargetServiceInterface: 126 | className: 'Sandstorm\UserManagement\Domain\Service\Neos\NeosRedirectTargetService' 127 | Sandstorm\UserManagement\Domain\Service\UserCreationServiceInterface: 128 | className: 'Sandstorm\UserManagement\Domain\Service\Neos\NeosUserCreationService' 129 | ``` 130 | 131 | Be aware that the `NeosUserCreationService` requires a non-empty firstName and lastName to be present in the `RegistrationFlow` attributes 132 | as it's in the templates of this package. 133 | 134 | ### Neos 3.0 and higher 135 | 136 | Add the following to your package's (or the global) `Settings.yaml`. This creates a separate authentication provider so Neos can 137 | distinguish between frontend and backend logins. 138 | 139 | ``` 140 | Neos: 141 | Flow: 142 | security: 143 | authentication: 144 | providers: 145 | 'Neos.Neos:Backend': 146 | requestPatterns: 147 | Sandstorm.UserManagement:NeosBackend: 148 | pattern: Sandstorm\UserManagement\Security\NeosRequestPattern 149 | patternOptions: 150 | 'area': 'backend' 151 | 'Sandstorm.UserManagement:Login': 152 | provider: PersistedUsernamePasswordProvider 153 | requestPatterns: 154 | Sandstorm.UserManagement:NeosFrontend: 155 | pattern: Sandstorm\UserManagement\Security\NeosRequestPattern 156 | patternOptions: 157 | 'area': 'frontend' 158 | 159 | ``` 160 | 161 | ### Neos 2.3 (Flow 3.3) 162 | 163 | Before Neos 3.0, the `Neos.Neos:Backend` authentication provider was called `Typo3BackendProvider`. Replace `Neos.Neos:Backend` 164 | with `Typo3BackendProvider` in the config above. 165 | 166 | # 3. Usage 167 | 168 | ## CLI Commands 169 | ### Creating users 170 | The package exposes a command to create users. You can run 171 | 172 | `./flow sandstormuser:create test@example.com password --additionalAttributes="firstName:Max;lastName:Mustermann"` 173 | 174 | to create a user. This will create a Neos user if you're using the package in Neos. You can assign 175 | roles to the new user in the Neos backend afterwards. 176 | 177 | ### Confirming user registration 178 | It is possible to confirm a registrationflow and trigger user creation by running 179 | 180 | `./flow sandstormuser:activateregistration test@example.com` 181 | 182 | ### Resetting passwords 183 | Since 1.1.2, it is possible to reset passwords for users created with this package. 184 | 185 | `./flow sandstormuser:setpassword test@example.com password` 186 | 187 | If the package detects that the NeosUserCreationService is used, it forwards the command to the 188 | Neos `UserCommandController->setPasswordCommand()`. Otherwise, our oackage's own logic is used. 189 | 190 | The Authentication Provider can be passed in as an optional argument to reset passwords for users created 191 | with a different provider that the default UserManagement one (`Sandstorm.UserManagement:Login`): 192 | 193 | `./flow sandstormuser:setpassword test@example.com password --authenticationProvider=Typo3BackendProvider` 194 | 195 | ## Redirect after login/logout 196 | ### Via configuration 197 | To define where users should be redirected after they log in or out, you can set some config options: 198 | ``` 199 | Sandstorm: 200 | UserManagement: 201 | redirect: 202 | # To activate redirection, make these settings: 203 | afterLogin: 204 | action: 'action' 205 | controller: 'Controller' 206 | package: 'Your.Package' 207 | afterLogout: 208 | action: 'action' 209 | controller: 'Controller' 210 | package: 'Your.Package' 211 | ``` 212 | 213 | ### Via node properties 214 | When using the package within Neos, you have another possibility: you can set properties on the LoginForm node type. 215 | The pages you link here will be shown after users log in or out. Please note that when a login/logout form is displayed 216 | on a restricted page: in that case you MUST set a redirect target, otherwise you will receive an error message on logout. 217 | If the redirection is configured via Settings.yaml, they will take precedence over the configuration at the node. 218 | You can, of course, set these properties from TypoScript also if you have a login/logout form directly in you template: 219 | ``` 220 | loginform = Sandstorm.UserManagement:LoginForm { 221 | // This should be set, or there will be problems when you have multiple plugins on a page 222 | argumentNamespace = 'login' 223 | // Redirect to the parent page automatically after logout 224 | redirectAfterLogout = ${q(documentNode).parent().get(0)} 225 | } 226 | ``` 227 | 228 | ### Via custom RedirectTargetService 229 | If redirecting to a specific controller method is still not enough for you, you can simply roll your own implementation of the 230 | `RedirectTargetServiceInterface`. Just add the implementation within your own package and add the following lines to your `Objects.yaml`. 231 | Mind the package loading order, you package should require sandstorm/usermanagement in its composer.json. 232 | ``` 233 | Sandstorm\UserManagement\Domain\Service\RedirectTargetServiceInterface: 234 | className: 'Your\Package\Domain\Service\YourCustomRedirectTargetService' 235 | ``` 236 | 237 | ## Checking for a logged-in user in your templates 238 | There is a ViewHelper available that allows you to check if somebody is logged into the frontend. Here's an example: 239 | 240 | ``` 241 | {namespace um=Sandstorm\UserManagement\ViewHelpers} 242 | 243 | 244 | 245 | You are currently logged in. 246 | 247 | 248 | You are not logged in! 249 | 250 | 251 | 252 | ``` 253 | 254 | If you have configured a different Authentication Provider than the default one, the viewhelper has an `authenticationProviderName` 255 | argument to which you can pass the name of the Auth Provider you are using. 256 | 257 | # Extending the package 258 | 259 | ## Changing / overriding templates 260 | You can change any template via the default method using `Views.yaml`. Please see 261 | http://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/ModelViewController.html#configuring-views-through-views-yaml. 262 | Here's an example how to plug your own login template: 263 | 264 | ```YAML 265 | 266 | - 267 | requestFilter: 'mainRequest.isPackage("Neos.Neos") && isPackage("Sandstorm.UserManagement") && isController("Login") && isAction("login")' 268 | options: 269 | templatePathAndFilename: 'resource://Your.Package/Private/Templates/Login/Login.html' 270 | partialRootPaths: ['resource://Your.Package/Private/Partials'] 271 | layoutRootPaths: ['resource://Your.Package/Private/Layouts'] 272 | ``` 273 | 274 | ## Overriding e-mail templates 275 | As documented in the configuration options above, overriding e-mail templates is easy: 276 | * Copy the `EmailTemplates` folder from the UserManagement's `Resources/Private` folder into your 277 | own package and modify the templates to your heart's desire. 278 | * Add your own package to the templatePackages config, as described in [Sandstorm/TemplateMailer Documentation](https://github.com/sandstorm/TemplateMailer). 279 | 280 | ## Changing the User model 281 | You might want to add additional information to the user model. This can be done by extending 282 | the User model delivered with this package and adding properties as you like. You will then 283 | need to switch out the implementation of `UserCreationServiceInterface` to get control over 284 | the creation process. This can be done via `Objects.yaml`: 285 | ```YAML 286 | Sandstorm\UserManagement\Domain\Service\UserCreationServiceInterface: 287 | className: 'Your\Package\Domain\Service\YourCustomUserCreationService' 288 | ``` 289 | 290 | ## Hooking into the login/logout process 291 | The UserManagement package emits three signals during the login and logout process, into which you can hook 292 | using Flows [Signals and Slots](http://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/SignalsAndSlots.html) 293 | mechanism. You could for example use this to set additional cookies when a user logs in, e.g. to enable JWT authentication 294 | with another service. Here is an example of using all three, you could copy this into your own `Package.php` file: 295 | ```PHP 296 | public function boot(Bootstrap $bootstrap) { 297 | $dispatcher = $bootstrap->getSignalSlotDispatcher(); 298 | $dispatcher->connect( 299 | \Sandstorm\UserManagement\Controller\LoginController::class, 'authenticationSuccess', 300 | \Your\Package\Domain\Service\ExampleService::class, 'onAuthenticationSuccess' 301 | ); 302 | $dispatcher->connect( 303 | \Sandstorm\UserManagement\Controller\LoginController::class, 'authenticationFailure', 304 | \Your\Package\Domain\Service\ExampleService::class, 'onAuthenticationFailure' 305 | ); 306 | $dispatcher->connect( 307 | \Sandstorm\UserManagement\Controller\LoginController::class, 'logout', 308 | \Your\Package\Domain\Service\ExampleService::class, 'onLogout' 309 | ); 310 | } 311 | ``` 312 | 313 | Your example service could then look like this: 314 | ```PHP 315 | namespace Your\Package\Domain\Service; 316 | 317 | use Neos\Flow\Mvc\ActionRequest; 318 | use Neos\Flow\Mvc\Controller\ControllerContext; 319 | use Neos\Flow\Security\Exception\AuthenticationRequiredException; 320 | 321 | class ExampleService 322 | { 323 | public function onAuthenticationSuccess(ControllerContext $controllerContext, ActionRequest $originalRequest = null) 324 | { 325 | // Do custom stuff here 326 | } 327 | 328 | public function onAuthenticationFailure(ControllerContext $controllerContext, AuthenticationRequiredException $exception = null) 329 | { 330 | // Do custom stuff here 331 | } 332 | 333 | public function onLogout(ControllerContext $controllerContext) 334 | { 335 | // Do custom stuff here 336 | } 337 | } 338 | ``` 339 | 340 | ## Changing the Registration Flow and validation logic 341 | The `RegistrationFlow` class is the representation of a user signing up for your application. 342 | It has a few default properties and can be extended with arbitrary additional data via its `attributes` property. 343 | 344 | ### Adding custom fields to the Registration Flow 345 | Exchange the registration template as described above and add a field: 346 | ```HTML 347 | 348 | ``` 349 | This will add the field, but of course you might also want to validate it. 350 | 351 | ### Extending the Registration Flow validation logic 352 | The UserManagement package has a hook for you to implement your custom registration flow validation logic. It is 353 | called directly from the domain model validator of the package. All you need to to is create an implementation of 354 | `Sandstorm\UserManagement\Domain\Service\RegistrationFlowValidationServiceInterface` in your own package. It could 355 | look like this: 356 | ```PHP 357 | class RegistrationFlowValidationService implements RegistrationFlowValidationServiceInterface { 358 | /** 359 | * @param RegistrationFlow $registrationFlow 360 | * @param RegistrationFlowValidator $validator 361 | * @return void 362 | */ 363 | public function validateRegistrationFlow(RegistrationFlow $registrationFlow, RegistrationFlowValidator $validator) { 364 | // This is an example of your own custom validation logic. 365 | if ($registrationFlow->getAttributes()['agb'] !== '1') { 366 | $validator->getResult()->forProperty('attributes.terms')->addError(new \Neos\Flow\Validation\Error('You need to accept the terms and conditions.')); 367 | } 368 | } 369 | } 370 | ``` 371 | 372 | # 4. Running Tests 373 | Run all tests with: 374 | `./bin/phpunit -c ./Build/BuildEssentials/PhpUnit/UnitTests.xml Packages/Application/Sandstorm.UserManagement/Tests/Unit` 375 | 376 | # 5. Known issues 377 | 378 | Feel free to submit issues/PRs :) 379 | 380 | # 6. TODOs 381 | 382 | * More Tests. 383 | 384 | # 7. FAQ 385 | 386 | * *What happens if the user did not receive the registration email?* 387 | Just tell the user to register again. In this case, previous unfinished registrations are discarded. 388 | 389 | # 8. License 390 | MIT. 391 | https://opensource.org/licenses/MIT 392 | --------------------------------------------------------------------------------