mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-25 09:42:10 +08:00
Updated email confirmation flow so confirmation is done via POST
To avoid non-user GET requests (Such as those from email scanners) auto-triggering the confirm submission. Made auto-submit the form via JavaScript in this extra added step with user-link backup to keep existing user flow experience. Closes #3797
This commit is contained in:
parent
0e627a6e05
commit
a1b1f8138a
|
@ -51,14 +51,28 @@ class ConfirmEmailController extends Controller
|
||||||
return view('auth.user-unconfirmed', ['user' => $user]);
|
return view('auth.user-unconfirmed', ['user' => $user]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for a user to provide their positive confirmation of their email.
|
||||||
|
*/
|
||||||
|
public function showAcceptForm(string $token)
|
||||||
|
{
|
||||||
|
return view('auth.register-confirm-accept', ['token' => $token]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirms an email via a token and logs the user into the system.
|
* Confirms an email via a token and logs the user into the system.
|
||||||
*
|
*
|
||||||
* @throws ConfirmationEmailException
|
* @throws ConfirmationEmailException
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function confirm(string $token)
|
public function confirm(Request $request)
|
||||||
{
|
{
|
||||||
|
$validated = $this->validate($request, [
|
||||||
|
'token' => ['required', 'string']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$token = $validated['token'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$userId = $this->emailConfirmationService->checkTokenAndGetUserId($token);
|
$userId = $this->emailConfirmationService->checkTokenAndGetUserId($token);
|
||||||
} catch (UserTokenNotFoundException $exception) {
|
} catch (UserTokenNotFoundException $exception) {
|
||||||
|
|
12
resources/js/components/auto-submit.js
Normal file
12
resources/js/components/auto-submit.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
class AutoSubmit {
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.form = this.$el;
|
||||||
|
|
||||||
|
this.form.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AutoSubmit;
|
|
@ -4,6 +4,7 @@ import ajaxForm from "./ajax-form.js"
|
||||||
import attachments from "./attachments.js"
|
import attachments from "./attachments.js"
|
||||||
import attachmentsList from "./attachments-list.js"
|
import attachmentsList from "./attachments-list.js"
|
||||||
import autoSuggest from "./auto-suggest.js"
|
import autoSuggest from "./auto-suggest.js"
|
||||||
|
import autoSubmit from "./auto-submit.js";
|
||||||
import backToTop from "./back-to-top.js"
|
import backToTop from "./back-to-top.js"
|
||||||
import bookSort from "./book-sort.js"
|
import bookSort from "./book-sort.js"
|
||||||
import chapterContents from "./chapter-contents.js"
|
import chapterContents from "./chapter-contents.js"
|
||||||
|
@ -64,6 +65,7 @@ const componentMapping = {
|
||||||
"attachments": attachments,
|
"attachments": attachments,
|
||||||
"attachments-list": attachmentsList,
|
"attachments-list": attachmentsList,
|
||||||
"auto-suggest": autoSuggest,
|
"auto-suggest": autoSuggest,
|
||||||
|
"auto-submit": autoSubmit,
|
||||||
"back-to-top": backToTop,
|
"back-to-top": backToTop,
|
||||||
"book-sort": bookSort,
|
"book-sort": bookSort,
|
||||||
"chapter-contents": chapterContents,
|
"chapter-contents": chapterContents,
|
||||||
|
|
|
@ -61,6 +61,8 @@ return [
|
||||||
'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.',
|
'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.',
|
||||||
'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.',
|
'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.',
|
||||||
'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.',
|
'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.',
|
||||||
|
'email_confirm_thanks' => 'Thanks for confirming!',
|
||||||
|
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
|
||||||
|
|
||||||
'email_not_confirmed' => 'Email Address Not Confirmed',
|
'email_not_confirmed' => 'Email Address Not Confirmed',
|
||||||
'email_not_confirmed_text' => 'Your email address has not yet been confirmed.',
|
'email_not_confirmed_text' => 'Your email address has not yet been confirmed.',
|
||||||
|
|
27
resources/views/auth/register-confirm-accept.blade.php
Normal file
27
resources/views/auth/register-confirm-accept.blade.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
@extends('layouts.simple')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="container very-small mt-xl">
|
||||||
|
<div class="card content-wrap auto-height">
|
||||||
|
<h1 class="list-heading">{{ trans('auth.email_confirm_thanks') }}</h1>
|
||||||
|
|
||||||
|
<p class="mb-none">{{ trans('auth.email_confirm_thanks_desc') }}</p>
|
||||||
|
|
||||||
|
<div class="flex-container-row items-center wrap">
|
||||||
|
<div class="flex min-width-s">
|
||||||
|
@include('common.loading-icon')
|
||||||
|
</div>
|
||||||
|
<div class="flex min-width-s text-s-right">
|
||||||
|
<form component="auto-submit" action="{{ url('/register/confirm/accept') }}" method="post">
|
||||||
|
{{ csrf_field() }}
|
||||||
|
<input type="hidden" name="token" value="{{ $token }}">
|
||||||
|
<button class="text-button">{{ trans('common.continue') }}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
|
@ -316,7 +316,8 @@ Route::get('/register', [Auth\RegisterController::class, 'getRegister']);
|
||||||
Route::get('/register/confirm', [Auth\ConfirmEmailController::class, 'show']);
|
Route::get('/register/confirm', [Auth\ConfirmEmailController::class, 'show']);
|
||||||
Route::get('/register/confirm/awaiting', [Auth\ConfirmEmailController::class, 'showAwaiting']);
|
Route::get('/register/confirm/awaiting', [Auth\ConfirmEmailController::class, 'showAwaiting']);
|
||||||
Route::post('/register/confirm/resend', [Auth\ConfirmEmailController::class, 'resend']);
|
Route::post('/register/confirm/resend', [Auth\ConfirmEmailController::class, 'resend']);
|
||||||
Route::get('/register/confirm/{token}', [Auth\ConfirmEmailController::class, 'confirm']);
|
Route::get('/register/confirm/{token}', [Auth\ConfirmEmailController::class, 'showAcceptForm']);
|
||||||
|
Route::post('/register/confirm/accept', [Auth\ConfirmEmailController::class, 'confirm']);
|
||||||
Route::post('/register', [Auth\RegisterController::class, 'postRegister']);
|
Route::post('/register', [Auth\RegisterController::class, 'postRegister']);
|
||||||
|
|
||||||
// SAML routes
|
// SAML routes
|
||||||
|
|
|
@ -46,8 +46,18 @@ class RegistrationTest extends TestCase
|
||||||
return $notification->token === $emailConfirmation->token;
|
return $notification->token === $emailConfirmation->token;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check confirmation email confirmation activation.
|
// Check confirmation email confirmation accept page.
|
||||||
$this->get('/register/confirm/' . $emailConfirmation->token)->assertRedirect('/login');
|
$resp = $this->get('/register/confirm/' . $emailConfirmation->token);
|
||||||
|
$acceptPage = $this->withHtml($resp);
|
||||||
|
$resp->assertOk();
|
||||||
|
$resp->assertSee('Thanks for confirming!');
|
||||||
|
$acceptPage->assertElementExists('form[method="post"][action$="/register/confirm/accept"][component="auto-submit"] button');
|
||||||
|
$acceptPage->assertFieldHasValue('token', $emailConfirmation->token);
|
||||||
|
|
||||||
|
// Check acceptance confirm
|
||||||
|
$this->post('/register/confirm/accept', ['token' => $emailConfirmation->token])->assertRedirect('/login');
|
||||||
|
|
||||||
|
// Check state on login redirect
|
||||||
$this->get('/login')->assertSee('Your email has been confirmed! You should now be able to login using this email address.');
|
$this->get('/login')->assertSee('Your email has been confirmed! You should now be able to login using this email address.');
|
||||||
$this->assertDatabaseMissing('email_confirmations', ['token' => $emailConfirmation->token]);
|
$this->assertDatabaseMissing('email_confirmations', ['token' => $emailConfirmation->token]);
|
||||||
$this->assertDatabaseHas('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
|
$this->assertDatabaseHas('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user