diff --git a/appinfo/routes.php b/appinfo/routes.php index 7db7778..d83acb7 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -13,14 +13,16 @@ return [ 'routes' => [ ['name' => 'settings#admin', 'url' => '/settings', 'verb' => 'POST'], - ['name' => 'register#askEmail', 'url' => '/', 'verb' => 'GET'], - ['name' => 'register#validateEmail', 'url' => '/', 'verb' => 'POST'], - ['name' => 'register#verifyToken', 'url' => '/verify/{token}', 'verb' => 'GET'], - ['name' => 'register#createAccount', 'url' => '/verify/{token}', 'verb' => 'POST'] + ['name' => 'register#showEmailForm', 'url' => '/', 'verb' => 'GET'], + ['name' => 'register#submitEmailForm', 'url' => '/', 'verb' => 'POST'], + ['name' => 'register#showVerificationForm', 'url' => '/verify/{secret}', 'verb' => 'GET'], + ['name' => 'register#submitVerificationForm', 'url' => '/verify/{secret}', 'verb' => 'POST'], + ['name' => 'register#showUserForm', 'url' => '/register/{secret}/{token}', 'verb' => 'GET'], + ['name' => 'register#submitUserForm', 'url' => '/register/{secret}/{token}', 'verb' => 'POST'], ], 'ocs' => [ ['root' => '/registration', 'name' => 'api#validate', 'url' => '/v1/validate', 'verb' => 'POST'], ['root' => '/registration', 'name' => 'api#status', 'url' => '/v1/status', 'verb' => 'POST'], - ['root' => '/registration', 'name' => 'api#register', 'url' => '/v1/register', 'verb' => 'POST'] + ['root' => '/registration', 'name' => 'api#register', 'url' => '/v1/register', 'verb' => 'POST'], ] ]; diff --git a/css/style.css b/css/style.css index 3987ce6..7171a26 100644 --- a/css/style.css +++ b/css/style.css @@ -1,8 +1,14 @@ -#body-login #email, #body-login #username, #body-login #password { +#body-login #email, +#body-login #token, +#body-login #username, +#body-login #password { width: calc(100% - 56px); padding-left: 36px; } -#email-icon, #username-icon, #password-icon { +#email-icon, +#token-icon, +#username-icon, +#password-icon { position: absolute; left: 16px; top: 22px; diff --git a/img/verify.svg b/img/verify.svg new file mode 100644 index 0000000..f1b2091 --- /dev/null +++ b/img/verify.svg @@ -0,0 +1 @@ + diff --git a/lib/Controller/RegisterController.php b/lib/Controller/RegisterController.php index deb7e55..ee0b795 100644 --- a/lib/Controller/RegisterController.php +++ b/lib/Controller/RegisterController.php @@ -16,6 +16,8 @@ use OCA\Registration\Db\Registration; use OCA\Registration\Service\MailService; use OCA\Registration\Service\RegistrationException; use OCA\Registration\Service\RegistrationService; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http\Response; use \OCP\IRequest; use \OCP\AppFramework\Http\TemplateResponse; use \OCP\AppFramework\Http\RedirectResponse; @@ -59,64 +61,138 @@ class RegisterController extends Controller { * @NoCSRFRequired * @PublicPage * - * @param $errormsg - * @param $entered + * @param string $email + * @param string $message * @return TemplateResponse */ - public function askEmail($errormsg, $entered) { + public function showEmailForm(string $email = '', string $message = ''): TemplateResponse { $params = [ - 'errormsg' => $errormsg ? $errormsg : $this->request->getParam('errormsg'), - 'entered' => $entered ? $entered : $this->request->getParam('entered') + 'email' => $email, + 'message' => $message, ]; - return new TemplateResponse('registration', 'register', $params, 'guest'); + return new TemplateResponse('registration', 'form/email', $params, 'guest'); } /** - * User POST email, if email is valid and not duplicate, we send token by mail * @PublicPage * @AnonRateThrottle(limit=5, period=1) * * @param string $email * @return TemplateResponse */ - public function validateEmail($email) {//TODO rename to receiveUserEmail - if (!$this->registrationService->checkAllowedDomains($email)) {//TODO Duplicate code with Service - return new TemplateResponse('registration', 'domains', [ - 'domains' => $this->registrationService->getAllowedDomains() - ], 'guest'); - } + public function submitEmailForm(string $email): Response { try { - $reg = $this->registrationService->validateEmail($email); - if ($reg === true) { + // Registration already in progress, update token and continue with verification + $registration = $this->registrationService->getRegistrationForEmail($email); + $this->registrationService->generateNewToken($registration); + } catch (DoesNotExistException $e) { + // No registration in progress + try { + $this->registrationService->validateEmail($email); $registration = $this->registrationService->createRegistration($email); - $this->mailService->sendTokenByMail($registration); - } else { - $this->registrationService->generateNewToken($reg); - $this->mailService->sendTokenByMail($reg); - return new TemplateResponse('registration', 'message', ['msg' => - $this->l10n->t('There is already a pending registration with this email, a new verification email has been sent to the address.') - ], 'guest'); + } catch (RegistrationException $e) { + return $this->showEmailForm($email, $e->getMessage()); } - } catch (RegistrationException $e) { - return new TemplateResponse('registration', 'message', ['msg' => - $e->getMessage().'
'.$e->getHint() - ], 'guest'); } + try { + $this->mailService->sendTokenByMail($registration); + } catch (RegistrationException $e) { + return $this->showEmailForm($email, $e->getMessage()); + } - return new TemplateResponse('registration', 'message', ['msg' => - $this->l10n->t('Verification email successfully sent.') - ], 'guest'); + return new RedirectResponse( + $this->urlgenerator->linkToRoute( + 'registration.register.showVerificationForm', + ['secret' => $registration->getClientSecret()] + ) + ); } /** * @NoCSRFRequired * @PublicPage * - * @param $token + * @param string $secret + * @param string $message * @return TemplateResponse */ - public function verifyToken($token) { + public function showVerificationForm(string $secret, string $message = ''): TemplateResponse { + try { + $this->registrationService->getRegistrationForSecret($secret); + } catch (RegistrationException $e) { + return new TemplateResponse('core', 'error', [ + 'errors' => [ + $this->l10n->t('The verification secret does not exist anymore'), + ], + ], 'error'); + } + + return new TemplateResponse('registration', 'form/verification', [ + 'message' => $message, + ], 'guest'); + } + + /** + * @PublicPage + * @AnonRateThrottle(limit=5, period=1) + * + * @param string $secret + * @param string $token + * @return Response + */ + public function submitVerificationForm(string $secret, string $token): Response { + try { + $registration = $this->registrationService->getRegistrationForSecret($secret); + + if ($registration->getToken() !== $token) { + return $this->showVerificationForm( + $secret, + $this->l10n->t('The entered verification code is wrong') + ); + } + } catch (RegistrationException $e) { + return new TemplateResponse('core', 'error', [ + 'errors' => [ + $this->l10n->t('The verification secret does not exist anymore'), + ], + ], 'error'); + } + + return new RedirectResponse( + $this->urlgenerator->linkToRoute( + 'registration.register.showUserForm', + [ + 'secret' => $secret, + 'token' => $token, + ] + ) + ); + } + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param string $secret + * @param string $token + * @return TemplateResponse + */ + public function showUserForm(string $secret, string $token): TemplateResponse { + try { + $registration = $this->registrationService->getRegistrationForSecret($secret); + + if ($registration->getToken() !== $token) { + throw new RegistrationException('Invalid verification token'); + } + } catch (RegistrationException $e) { + return new TemplateResponse('core', 'error', [ + 'errors' => [ + $this->l10n->t('The verification secret does not exist anymore or the verification token is invalid'), + ], + ], 'error'); + } + try { /** @var Registration $registration */ $registration = $this->registrationService->verifyToken($token); @@ -131,7 +207,7 @@ class RegisterController extends Controller { ); } - return new TemplateResponse('registration', 'form', [ + return new TemplateResponse('registration', 'form/user', [ 'email' => $registration->getEmail(), 'email_is_login' => $this->config->getAppValue('registration', 'email_is_login', '0') === '1', 'token' => $registration->getToken(), @@ -148,7 +224,7 @@ class RegisterController extends Controller { * @param $token * @return RedirectResponse|TemplateResponse */ - public function createAccount($token) { + public function submitUserForm($token) { $registration = $this->registrationService->getRegistrationForToken($token); if ($this->config->getAppValue('registration', 'email_is_login', '0') === '1') { $username = $registration->getEmail(); diff --git a/lib/RegistrationLoginOption.php b/lib/RegistrationLoginOption.php index d436574..ceda53e 100644 --- a/lib/RegistrationLoginOption.php +++ b/lib/RegistrationLoginOption.php @@ -47,7 +47,7 @@ class RegistrationLoginOption implements IAlternativeLogin { } public function getLink(): string { - return $this->url->linkToRoute('registration.register.askEmail'); + return $this->url->linkToRoute('registration.register.showEmailForm'); } public function getClass(): string { diff --git a/lib/Service/MailService.php b/lib/Service/MailService.php index 5d8f581..0a8b688 100644 --- a/lib/Service/MailService.php +++ b/lib/Service/MailService.php @@ -81,7 +81,10 @@ class MailService { * @throws RegistrationException */ public function sendTokenByMail(Registration $registration): void { - $link = $this->urlGenerator->linkToRouteAbsolute('registration.register.verifyToken', ['token' => $registration->getToken()]); + $link = $this->urlGenerator->linkToRouteAbsolute('registration.register.showUserForm', [ + 'secret' => $registration->getClientSecret(), + 'token' => $registration->getToken(), + ]); $subject = $this->l10n->t('Verify your %s registration request', [$this->defaults->getName()]); $template = $this->mailer->createEMailTemplate('registration_verify', [ @@ -100,6 +103,10 @@ class MailService { $body ); + $template->addBodyText( + $this->l10n->t('Verification code: %s', $registration->getToken()) + ); + $template->addBodyButton( $this->l10n->t('Continue registration'), $link diff --git a/lib/Service/RegistrationService.php b/lib/Service/RegistrationService.php index e29c2ee..3d5bb85 100644 --- a/lib/Service/RegistrationService.php +++ b/lib/Service/RegistrationService.php @@ -135,24 +135,24 @@ class RegistrationService { $registration->setPassword($password); } $this->registrationMapper->generateNewToken($registration); - if ($password !== '' && $username !== '') { - $this->registrationMapper->generateClientSecret($registration); - } + $this->registrationMapper->generateClientSecret($registration); $this->registrationMapper->insert($registration); return $registration; } /** * @param string $email - * @return Registration|true if there is a pending reg with this email, return the pending reg, if there are no problems with the email, return true. * @throws RegistrationException */ - public function validateEmail($email) { + public function validateEmail(string $email): void { $this->mailService->validateEmail($email); // check for pending registrations try { - return $this->registrationMapper->find($email);//if not found DB will throw a exception + $this->registrationMapper->find($email);//if not found DB will throw a exception + throw new RegistrationException( + $this->l10n->t('A user has already taken this email, maybe you already have an account?') + ); } catch (DoesNotExistException $e) { } @@ -171,7 +171,6 @@ class RegistrationService { ) ); } - return true; } /** @@ -235,10 +234,10 @@ class RegistrationService { * Find registration entity for token * * @param string $token - * @return string + * @return Registration * @throws RegistrationException */ - public function verifyToken($token) { + public function verifyToken(string $token): Registration { try { return $this->registrationMapper->findByToken($token); } catch (DoesNotExistException $exception) { @@ -327,18 +326,29 @@ class RegistrationService { } /** - * @param $token + * @param string $email * @return Registration + * @throws DoesNotExistException */ - public function getRegistrationForToken($token) { + public function getRegistrationForEmail(string $email): Registration { + return $this->registrationMapper->find($email); + } + + /** + * @param string $token + * @return Registration + * @throws DoesNotExistException + */ + public function getRegistrationForToken(string $token): Registration { return $this->registrationMapper->findByToken($token); } /** - * @param $secret + * @param string $secret * @return Registration + * @throws DoesNotExistException */ - public function getRegistrationForSecret($secret) { + public function getRegistrationForSecret(string $secret): Registration { return $this->registrationMapper->findBySecret($secret); } diff --git a/templates/form/email.php b/templates/form/email.php new file mode 100644 index 0000000..e68727e --- /dev/null +++ b/templates/form/email.php @@ -0,0 +1,26 @@ + +
+
+ + + + +

+ + + +

+ + + + + t('Back to login')); ?> + +
+
diff --git a/templates/form.php b/templates/form/user.php similarity index 100% rename from templates/form.php rename to templates/form/user.php diff --git a/templates/form/verification.php b/templates/form/verification.php new file mode 100644 index 0000000..816f351 --- /dev/null +++ b/templates/form/verification.php @@ -0,0 +1,26 @@ + +
+
+ + + + +

+ + + +

+ + + + + t('Back to login')); ?> + +
+
diff --git a/templates/register.php b/templates/register.php deleted file mode 100644 index 6c2d542..0000000 --- a/templates/register.php +++ /dev/null @@ -1,58 +0,0 @@ - - - - -
-
- -

- - - -

- - - - - t('Back to login')); ?> - -
-
- - -
-
- - - - - -

- - - -

- - - - - t('Back to login')); ?> - -
-
-