Login Flow v2 compatibility

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2020-07-16 17:05:45 +02:00
parent 1fe3f0b29a
commit 4d5fb2628a
No known key found for this signature in database
GPG Key ID: 7076EA9751AACDDA
11 changed files with 209 additions and 113 deletions

View File

@ -13,14 +13,16 @@
return [ return [
'routes' => [ 'routes' => [
['name' => 'settings#admin', 'url' => '/settings', 'verb' => 'POST'], ['name' => 'settings#admin', 'url' => '/settings', 'verb' => 'POST'],
['name' => 'register#askEmail', 'url' => '/', 'verb' => 'GET'], ['name' => 'register#showEmailForm', 'url' => '/', 'verb' => 'GET'],
['name' => 'register#validateEmail', 'url' => '/', 'verb' => 'POST'], ['name' => 'register#submitEmailForm', 'url' => '/', 'verb' => 'POST'],
['name' => 'register#verifyToken', 'url' => '/verify/{token}', 'verb' => 'GET'], ['name' => 'register#showVerificationForm', 'url' => '/verify/{secret}', 'verb' => 'GET'],
['name' => 'register#createAccount', 'url' => '/verify/{token}', 'verb' => 'POST'] ['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' => [ 'ocs' => [
['root' => '/registration', 'name' => 'api#validate', 'url' => '/v1/validate', 'verb' => 'POST'], ['root' => '/registration', 'name' => 'api#validate', 'url' => '/v1/validate', 'verb' => 'POST'],
['root' => '/registration', 'name' => 'api#status', 'url' => '/v1/status', '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'],
] ]
]; ];

View File

@ -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); width: calc(100% - 56px);
padding-left: 36px; padding-left: 36px;
} }
#email-icon, #username-icon, #password-icon { #email-icon,
#token-icon,
#username-icon,
#password-icon {
position: absolute; position: absolute;
left: 16px; left: 16px;
top: 22px; top: 22px;

1
img/verify.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 16 16" width="16" height="16"><path d="m8 0a3 3 0 0 0 -2.828 2 3 3 0 0 0 -0.172 -0 3 3 0 0 0 -3 3 3 3 0 0 0 0 0.172 3 3 0 0 0 -2 2.828 3 3 0 0 0 2 2.828 3 3 0 0 0 -0 0.172 3 3 0 0 0 3 3 3 3 0 0 0 0.172 -0 3 3 0 0 0 2.828 2 3 3 0 0 0 2.828 -2.01 3 3 0 0 0 0.172 0.01 3 3 0 0 0 3 -3 3 3 0 0 0 -0 -0.172 3 3 0 0 0 2 -2.828 3 3 0 0 0 -2.01 -2.828 3 3 0 0 0 0.01 -0.172 3 3 0 0 0 -3 -3 3 3 0 0 0 -0.172 0 3 3 0 0 0 -2.828 -2zm2.934 4.5625 1.433 1.4336-5.7772 5.7789-2.9511-2.9508 1.414-1.414 1.5371 1.5351 4.3442-4.3828z" fill="#000000"/></svg>

After

Width:  |  Height:  |  Size: 607 B

View File

@ -16,6 +16,8 @@ use OCA\Registration\Db\Registration;
use OCA\Registration\Service\MailService; use OCA\Registration\Service\MailService;
use OCA\Registration\Service\RegistrationException; use OCA\Registration\Service\RegistrationException;
use OCA\Registration\Service\RegistrationService; use OCA\Registration\Service\RegistrationService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http\Response;
use \OCP\IRequest; use \OCP\IRequest;
use \OCP\AppFramework\Http\TemplateResponse; use \OCP\AppFramework\Http\TemplateResponse;
use \OCP\AppFramework\Http\RedirectResponse; use \OCP\AppFramework\Http\RedirectResponse;
@ -59,64 +61,138 @@ class RegisterController extends Controller {
* @NoCSRFRequired * @NoCSRFRequired
* @PublicPage * @PublicPage
* *
* @param $errormsg * @param string $email
* @param $entered * @param string $message
* @return TemplateResponse * @return TemplateResponse
*/ */
public function askEmail($errormsg, $entered) { public function showEmailForm(string $email = '', string $message = ''): TemplateResponse {
$params = [ $params = [
'errormsg' => $errormsg ? $errormsg : $this->request->getParam('errormsg'), 'email' => $email,
'entered' => $entered ? $entered : $this->request->getParam('entered') '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 * @PublicPage
* @AnonRateThrottle(limit=5, period=1) * @AnonRateThrottle(limit=5, period=1)
* *
* @param string $email * @param string $email
* @return TemplateResponse * @return TemplateResponse
*/ */
public function validateEmail($email) {//TODO rename to receiveUserEmail public function submitEmailForm(string $email): Response {
if (!$this->registrationService->checkAllowedDomains($email)) {//TODO Duplicate code with Service
return new TemplateResponse('registration', 'domains', [
'domains' => $this->registrationService->getAllowedDomains()
], 'guest');
}
try { try {
$reg = $this->registrationService->validateEmail($email); // Registration already in progress, update token and continue with verification
if ($reg === true) { $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); $registration = $this->registrationService->createRegistration($email);
$this->mailService->sendTokenByMail($registration); } catch (RegistrationException $e) {
} else { return $this->showEmailForm($email, $e->getMessage());
$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 new TemplateResponse('registration', 'message', ['msg' =>
$e->getMessage().'<br/>'.$e->getHint()
], 'guest');
} }
try {
$this->mailService->sendTokenByMail($registration);
} catch (RegistrationException $e) {
return $this->showEmailForm($email, $e->getMessage());
}
return new TemplateResponse('registration', 'message', ['msg' => return new RedirectResponse(
$this->l10n->t('Verification email successfully sent.') $this->urlgenerator->linkToRoute(
], 'guest'); 'registration.register.showVerificationForm',
['secret' => $registration->getClientSecret()]
)
);
} }
/** /**
* @NoCSRFRequired * @NoCSRFRequired
* @PublicPage * @PublicPage
* *
* @param $token * @param string $secret
* @param string $message
* @return TemplateResponse * @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 { try {
/** @var Registration $registration */ /** @var Registration $registration */
$registration = $this->registrationService->verifyToken($token); $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' => $registration->getEmail(),
'email_is_login' => $this->config->getAppValue('registration', 'email_is_login', '0') === '1', 'email_is_login' => $this->config->getAppValue('registration', 'email_is_login', '0') === '1',
'token' => $registration->getToken(), 'token' => $registration->getToken(),
@ -148,7 +224,7 @@ class RegisterController extends Controller {
* @param $token * @param $token
* @return RedirectResponse|TemplateResponse * @return RedirectResponse|TemplateResponse
*/ */
public function createAccount($token) { public function submitUserForm($token) {
$registration = $this->registrationService->getRegistrationForToken($token); $registration = $this->registrationService->getRegistrationForToken($token);
if ($this->config->getAppValue('registration', 'email_is_login', '0') === '1') { if ($this->config->getAppValue('registration', 'email_is_login', '0') === '1') {
$username = $registration->getEmail(); $username = $registration->getEmail();

View File

@ -47,7 +47,7 @@ class RegistrationLoginOption implements IAlternativeLogin {
} }
public function getLink(): string { public function getLink(): string {
return $this->url->linkToRoute('registration.register.askEmail'); return $this->url->linkToRoute('registration.register.showEmailForm');
} }
public function getClass(): string { public function getClass(): string {

View File

@ -81,7 +81,10 @@ class MailService {
* @throws RegistrationException * @throws RegistrationException
*/ */
public function sendTokenByMail(Registration $registration): void { 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()]); $subject = $this->l10n->t('Verify your %s registration request', [$this->defaults->getName()]);
$template = $this->mailer->createEMailTemplate('registration_verify', [ $template = $this->mailer->createEMailTemplate('registration_verify', [
@ -100,6 +103,10 @@ class MailService {
$body $body
); );
$template->addBodyText(
$this->l10n->t('Verification code: %s', $registration->getToken())
);
$template->addBodyButton( $template->addBodyButton(
$this->l10n->t('Continue registration'), $this->l10n->t('Continue registration'),
$link $link

View File

@ -135,24 +135,24 @@ class RegistrationService {
$registration->setPassword($password); $registration->setPassword($password);
} }
$this->registrationMapper->generateNewToken($registration); $this->registrationMapper->generateNewToken($registration);
if ($password !== '' && $username !== '') { $this->registrationMapper->generateClientSecret($registration);
$this->registrationMapper->generateClientSecret($registration);
}
$this->registrationMapper->insert($registration); $this->registrationMapper->insert($registration);
return $registration; return $registration;
} }
/** /**
* @param string $email * @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 * @throws RegistrationException
*/ */
public function validateEmail($email) { public function validateEmail(string $email): void {
$this->mailService->validateEmail($email); $this->mailService->validateEmail($email);
// check for pending registrations // check for pending registrations
try { 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) { } catch (DoesNotExistException $e) {
} }
@ -171,7 +171,6 @@ class RegistrationService {
) )
); );
} }
return true;
} }
/** /**
@ -235,10 +234,10 @@ class RegistrationService {
* Find registration entity for token * Find registration entity for token
* *
* @param string $token * @param string $token
* @return string * @return Registration
* @throws RegistrationException * @throws RegistrationException
*/ */
public function verifyToken($token) { public function verifyToken(string $token): Registration {
try { try {
return $this->registrationMapper->findByToken($token); return $this->registrationMapper->findByToken($token);
} catch (DoesNotExistException $exception) { } catch (DoesNotExistException $exception) {
@ -327,18 +326,29 @@ class RegistrationService {
} }
/** /**
* @param $token * @param string $email
* @return Registration * @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); return $this->registrationMapper->findByToken($token);
} }
/** /**
* @param $secret * @param string $secret
* @return Registration * @return Registration
* @throws DoesNotExistException
*/ */
public function getRegistrationForSecret($secret) { public function getRegistrationForSecret(string $secret): Registration {
return $this->registrationMapper->findBySecret($secret); return $this->registrationMapper->findBySecret($secret);
} }

26
templates/form/email.php Normal file
View File

@ -0,0 +1,26 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
?>
<form action="" method="post">
<fieldset>
<?php if ($_['message']): ?>
<ul class="error">
<li><?php p($_['message']); ?></li>
</ul>
<?php endif; ?>
<p class="groupofone">
<input type="email" name="email" id="email" placeholder="<?php p($l->t('Email')); ?>" value="<?php p($_['email']); ?>" required autofocus />
<label for="email" class="infield"><?php p($l->t('Email')); ?></label>
<img id="email-icon" class="svg" src="<?php print_unescaped(image_path('', 'actions/mail.svg')); ?>" alt=""/>
</p>
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>" />
<input type="submit" id="submit" value="<?php p($l->t('Request verification link')); ?>" />
<a id="lost-password-back" href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('core.login.showLoginForm')) ?>">
<?php p($l->t('Back to login')); ?>
</a>
</fieldset>
</form>

View File

@ -0,0 +1,26 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
?>
<form action="" method="post">
<fieldset>
<?php if ($_['message']): ?>
<ul class="error">
<li><?php p($_['message']); ?></li>
</ul>
<?php endif; ?>
<p class="groupofone">
<input type="text" name="token" id="token" placeholder="<?php p($l->t('Verification code')); ?>" value="" required autofocus />
<label for="token" class="infield"><?php p($l->t('Verification code')); ?></label>
<img id="token-icon" class="svg" src="<?php print_unescaped(image_path('registration', 'verify.svg')); ?>" alt=""/>
</p>
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>" />
<input type="submit" id="submit" value="<?php p($l->t('Verify')); ?>" />
<a id="lost-password-back" href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('core.login.showLoginForm')) ?>">
<?php p($l->t('Back to login')); ?>
</a>
</fieldset>
</form>

View File

@ -1,58 +0,0 @@
<?php
/** @var array $_ */
/** @var \OCP\IL10N $l */
style('registration', 'style');
if ($_['entered']): ?>
<?php if (empty($_['errormsg'])): ?>
<ul class="success">
<li>
<?php p($l->t('Thank you for registering, you should receive a verification link in a few minutes.')); ?>
</li>
</ul>
<?php else: ?>
<form action="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('registration.register.validateEmail')) ?>" method="post">
<fieldset>
<ul class="error">
<li><?php p($_['errormsg']); ?></li>
</ul>
<p class="groupofone">
<input type="email" name="email" id="email" placeholder="<?php p($l->t('Email')); ?>" value="" required autofocus />
<label for="email" class="infield"><?php p($l->t('Email')); ?></label>
<img id="email-icon" class="svg" src="<?php print_unescaped(image_path('', 'actions/mail.svg')); ?>" alt=""/>
</p>
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>" />
<input type="submit" id="submit" value="<?php p($l->t('Request verification link')); ?>" />
<a id="lost-password-back" href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('core.login.showLoginForm')) ?>">
<?php p($l->t('Back to login')); ?>
</a>
</fieldset>
</form>
<?php endif; ?>
<?php else: ?>
<form action="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('registration.register.validateEmail')) ?>" method="post">
<fieldset>
<?php if ($_['errormsg']): ?>
<ul class="error">
<li><?php p($_['errormsg']); ?></li>
<li><?php p($l->t('Please re-enter a valid email address')); ?></li>
</ul>
<?php else: ?>
<ul class="msg">
<li><?php p($l->t('You will receive an email with a verification link')); ?></li>
</ul>
<?php endif; ?>
<p class="groupofone">
<input type="email" name="email" id="email" placeholder="<?php p($l->t('Email')); ?>" value="" required autofocus />
<label for="email" class="infield"><?php p($l->t('Email')); ?></label>
<img id="email-icon" class="svg" src="<?php print_unescaped(image_path('', 'actions/mail.svg')); ?>" alt=""/>
</p>
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>" />
<input type="submit" id="submit" value="<?php p($l->t('Request verification link')); ?>" />
<a id="lost-password-back" href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('core.login.showLoginForm')) ?>">
<?php p($l->t('Back to login')); ?>
</a>
</fieldset>
</form>
<?php endif; ?>