--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
++use Illuminate\Http\Request;
++use Password;
+
+class ForgotPasswordController extends Controller
+{
+ /*
+ |--------------------------------------------------------------------------
+ | Password Reset Controller
+ |--------------------------------------------------------------------------
+ |
+ | This controller is responsible for handling password reset emails and
+ | includes a trait which assists in sending these notifications from
+ | your application to your users. Feel free to explore this trait.
+ |
+ */
+
+ use SendsPasswordResetEmails;
+
+ /**
+ * Create a new controller instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->middleware('guest');
+ parent::__construct();
+ }
++
++
++ /**
++ * Send a reset link to the given user.
++ *
++ * @param \Illuminate\Http\Request $request
++ * @return \Illuminate\Http\RedirectResponse
++ */
++ public function sendResetLinkEmail(Request $request)
++ {
++ $this->validate($request, ['email' => 'required|email']);
++
++ // We will send the password reset link to this user. Once we have attempted
++ // to send the link, we will examine the response then see the message we
++ // need to show to the user. Finally, we'll send out a proper response.
++ $response = $this->broker()->sendResetLink(
++ $request->only('email')
++ );
++
++ if ($response === Password::RESET_LINK_SENT) {
++ $message = 'A password reset link has been sent to ' . $request->get('email') . '.';
++ session()->flash('success', $message);
++ return back()->with('status', trans($response));
++ }
++
++ // If an error was returned by the password broker, we will get this message
++ // translated so we can notify a user of the problem. We'll redirect back
++ // to where the users came from so they can attempt this process again.
++ return back()->withErrors(
++ ['email' => trans($response)]
++ );
++ }
++
+}
--- /dev/null
+<?php
+
+namespace BookStack\Http\Controllers\Auth;
+
+use BookStack\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\ResetsPasswords;
+
+class ResetPasswordController extends Controller
+{
+ /*
+ |--------------------------------------------------------------------------
+ | Password Reset Controller
+ |--------------------------------------------------------------------------
+ |
+ | This controller is responsible for handling password reset requests
+ | and uses a simple trait to include this behavior. You're free to
+ | explore this trait and override any methods you wish to tweak.
+ |
+ */
+
+ use ResetsPasswords;
+
++ protected $redirectTo = '/';
++
+ /**
+ * Create a new controller instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->middleware('guest');
+ parent::__construct();
+ }
++
++ /**
++ * Get the response for a successful password reset.
++ *
++ * @param string $response
++ * @return \Illuminate\Http\Response
++ */
++ protected function sendResetResponse($response)
++ {
++ $message = 'Your password has been successfully reset.';
++ session()->flash('success', $message);
++ return redirect($this->redirectPath())
++ ->with('status', trans($response));
++ }
+}
return [
'app-name' => 'BookStack',
+ 'app-name-header' => true,
'app-editor' => 'wysiwyg',
'app-color' => '#0288D1',
- 'app-color-light' => 'rgba(21, 101, 192, 0.15)'
+ 'app-color-light' => 'rgba(21, 101, 192, 0.15)',
+ 'app-custom-head' => false,
+ 'registration-enabled' => false,
];
margin-bottom: 0.43137255em;
}
h3 {
- font-size: 1.75em;
+ font-size: 2.333em;
line-height: 1.571428572em;
margin-top: 0.78571429em;
margin-bottom: 0.43137255em;
}
h4 {
- font-size: 1em;
+ font-size: 1.666em;
line-height: 1.375em;
margin-top: 0.78571429em;
margin-bottom: 0.43137255em;
}
-h1, h2, h3, h4 {
+h1, h2, h3, h4, h5, h6 {
font-weight: 400;
position: relative;
display: block;
color: #555;
.subheader {
- //display: block;
font-size: 0.5em;
line-height: 1em;
color: lighten($text-dark, 32%);
}
}
+h5 {
+ font-size: 1.4em;
+}
+
+h5, h6 {
+ font-weight: 500;
+ line-height: 1.2em;
+ margin-top: 0.78571429em;
+ margin-bottom: 0.66em;
+}
+
/*
* Link styling
*/
ol {
list-style: decimal;
- padding-left: $-m * 1.3;
+ padding-left: $-m * 2;
overflow: hidden;
}
@extends('public')
+ @section('header-buttons')
+ <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
+ @if(setting('registration-enabled'))
+ <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a>
+ @endif
+ @stop
+
@section('content')
@extends('public')
+ @section('header-buttons')
+ <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a>
+ @if(setting('registration-enabled'))
+ <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a>
+ @endif
+ @stop
+
@section('body-class', 'image-cover login')
@section('content')
@include('partials/custom-styles')
<!-- Custom user content -->
- @if(setting('app-custom-head', false))
+ @if(setting('app-custom-head'))
{!! setting('app-custom-head') !!}
@endif
</head>
@if(setting('app-logo', '') !== 'none')
<img class="logo-image" src="{{ setting('app-logo', '') === '' ? baseUrl('/logo.png') : baseUrl(setting('app-logo', '')) }}" alt="Logo">
@endif
- <span class="logo-text">{{ setting('app-name') }}</span>
+ @if (setting('app-name-header'))
+ <span class="logo-text">{{ setting('app-name') }}</span>
+ @endif
</a>
</div>
<div class="col-lg-4 col-sm-3 text-center">
<!-- Scripts -->
<script src="{{ baseUrl("/libs/jquery/jquery.min.js?version=2.1.4") }}"></script>
@include('partials/custom-styles')
+
+ <!-- Custom user content -->
+ @if(setting('app-custom-head'))
+ {!! setting('app-custom-head') !!}
+ @endif
</head>
<body class="@yield('body-class')" ng-app="bookStack">
@if(setting('app-logo', '') !== 'none')
<img class="logo-image" src="{{ setting('app-logo', '') === '' ? baseUrl('/logo.png') : baseUrl(setting('app-logo', '')) }}" alt="Logo">
@endif
- <span class="logo-text">{{ setting('app-name') }}</span>
+ @if (setting('app-name-header'))
+ <span class="logo-text">{{ setting('app-name') }}</span>
+ @endif
</a>
</div>
<div class="col-md-6">
<?php
-use BookStack\EmailConfirmation;
+use BookStack\Notifications\ConfirmEmail;
+use Illuminate\Support\Facades\Notification;
class AuthTest extends TestCase
{
public function test_confirmed_registration()
{
+ // Fake notifications
+ Notification::fake();
+
// Set settings and get user instance
$this->setSettings(['registration-enabled' => 'true', 'registration-confirmation' => 'true']);
$user = factory(\BookStack\User::class)->make();
- // Mock Mailer to ensure mail is being sent
- $mockMailer = Mockery::mock('Illuminate\Contracts\Mail\Mailer');
- $mockMailer->shouldReceive('send')->with('emails/email-confirmation', Mockery::type('array'), Mockery::type('callable'))->twice();
- $this->app->instance('mailer', $mockMailer);
-
// Go through registration process
$this->visit('/register')
->see('Sign Up')
->seePageIs('/register/confirm')
->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => false]);
+ // Ensure notification sent
+ $dbUser = \BookStack\User::where('email', '=', $user->email)->first();
+ Notification::assertSentTo($dbUser, ConfirmEmail::class);
+
// Test access and resend confirmation email
$this->login($user->email, $user->password)
->seePageIs('/register/confirm/awaiting')
->seePageIs('/register/confirm/awaiting')
->press('Resend Confirmation Email');
- // Get confirmation
- $user = $user->where('email', '=', $user->email)->first();
- $emailConfirmation = EmailConfirmation::where('user_id', '=', $user->id)->first();
-
-
- // Check confirmation email button and confirmation activation.
- $this->visit('/register/confirm/' . $emailConfirmation->token . '/email')
- ->see('Email Confirmation')
- ->click('Confirm Email')
+ // Get confirmation and confirm notification matches
+ $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
+ Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) {
+ return $notification->token === $emailConfirmation->token;
+ });
+
+ // Check confirmation email confirmation activation.
+ $this->visit('/register/confirm/' . $emailConfirmation->token)
->seePageIs('/')
->see($user->name)
->notSeeInDatabase('email_confirmations', ['token' => $emailConfirmation->token])
- ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => true]);
+ ->seeInDatabase('users', ['name' => $dbUser->name, 'email' => $dbUser->email, 'email_confirmed' => true]);
}
public function test_restricted_registration()
public function test_user_updating()
{
- $user = \BookStack\User::all()->last();
+ $user = $this->getNormalUser();
$password = $user->password;
$this->asAdmin()
->visit('/settings/users')
public function test_user_password_update()
{
- $user = \BookStack\User::all()->last();
+ $user = $this->getNormalUser();
$userProfilePage = '/settings/users/' . $user->id;
$this->asAdmin()
->visit($userProfilePage)
->seePageIs('/login');
}
+ public function test_reset_password_flow()
+ {
+ $this->visit('/login')->click('Forgot Password?')
+ ->seePageIs('/password/email')
+ ->press('Send Reset Link')
+
+ $this->seeInDatabase('password_resets', [
+ ]);
+
+ $reset = DB::table('password_resets')->where('email', '=', '
[email protected]')->first();
+ $this->visit('/password/reset/' . $reset->token)
+ ->see('Reset Password')
+ ->submitForm('Reset Password', [
+ 'password' => 'randompass',
+ 'password_confirmation' => 'randompass'
+ ])->seePageIs('/')
+ ->see('Your password has been successfully reset');
+ }
+
+ public function test_reset_password_page_shows_sign_links()
+ {
+ $this->setSettings(['registration-enabled' => 'true']);
+ $this->visit('/password/email')
+ ->seeLink('Sign in')
+ ->seeLink('Sign up');
+ }
+
/**
* Perform a login
* @param string $email
*/
protected function getTestImage($fileName)
{
- return new \Illuminate\Http\UploadedFile(base_path('tests/test-image.jpg'), $fileName, 'image/jpeg', 5238);
+ return new \Illuminate\Http\UploadedFile(base_path('tests/test-data/test-image.jpg'), $fileName, 'image/jpeg', 5238);
}
/**
$relPath = $this->uploadImage($imageName, $page->id);
$this->assertResponseOk();
- $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image exists');
+ $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image not found at path: '. public_path($relPath));
$this->deleteImage($relPath);
$this->seeInDatabase('images', [
- 'url' => url($relPath),
+ 'url' => $this->baseUrl . $relPath,
'type' => 'gallery',
'uploaded_to' => $page->id,
'path' => $relPath,
'updated_by' => $admin->id,
'name' => $imageName
]);
-
}
$this->assertResponseOk();
$this->dontSeeInDatabase('images', [
- 'url' => $relPath,
+ 'url' => $this->baseUrl . $relPath,
'type' => 'gallery'
]);