Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2fa #205

Merged
merged 3 commits into from
Apr 5, 2024
Merged

2fa #205

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/root.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Cone\Root\Http\Middleware\Authenticate;
use Cone\Root\Http\Middleware\TwoFactorAuthenticate;

return [

Expand Down Expand Up @@ -44,9 +45,23 @@
'web',
Authenticate::class,
'verified',
TwoFactorAuthenticate::class,
'can:viewRoot',
],

/*
|--------------------------------------------------------------------------
| Two Factor Verification
|--------------------------------------------------------------------------
|
| You can configure the two factor authentication here.
|
*/

'two_factor' => [
'expiration' => 600,
],

/*
|--------------------------------------------------------------------------
| Branding
Expand Down
28 changes: 28 additions & 0 deletions resources/views/auth/two-factor.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@extends('root::auth.layout')

{{-- Title --}}
@section('title', __('Two Factor Authentication'))

{{-- Content --}}
@section('content')
<p>{{ __('To finish the two factor authentication, please use the link we sent, or request a new one!') }}</p>
<form method="POST" action="{{ URL::route('root.auth.two-factor.resend') }}">
@csrf
<div class="form-group-stack">
<div class="form-group">
<button type="submit" class="btn btn--primary btn--lg btn--block btn--primary-shadow">
{{ __('Resend Two Factor Authentication Link') }}
</button>
</div>
</div>
</form>
<span class="or-separator" aria-hidden="true">{{ __('or') }}</span>
<form method="POST" action="{{ URL::route('root.auth.logout') }}">
@csrf
<div class="form-group-stack">
<button type="submit" class="btn btn--light btn--sm">
{{ __('Logout') }}
</button>
</div>
</form>
@endsection
12 changes: 9 additions & 3 deletions routes/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@
use Cone\Root\Http\Controllers\Auth\ForgotPasswordController;
use Cone\Root\Http\Controllers\Auth\LoginController;
use Cone\Root\Http\Controllers\Auth\ResetPasswordController;
use Cone\Root\Http\Controllers\Auth\TwoFactorController;
use Illuminate\Support\Facades\Route;

// Login
Route::get('login', [LoginController::class, 'show'])->name('login');
Route::post('login', [LoginController::class, 'login']);
Route::post('logout', [LoginController::class, 'logout'])->name('logout');
Route::get('/login', [LoginController::class, 'show'])->name('login');
Route::post('/login', [LoginController::class, 'login']);
Route::post('/logout', [LoginController::class, 'logout'])->name('logout');

// Reset
Route::get('/password/reset', [ForgotPasswordController::class, 'show'])->name('password.request');
Route::post('/password/email', [ForgotPasswordController::class, 'send'])->name('password.email');
Route::get('/password/reset/{token}/{email}', [ResetPasswordController::class, 'show'])->name('password.reset');
Route::post('/password/reset', [ResetPasswordController::class, 'reset'])->name('password.update');

// Verify
Route::get('/two-factor', [TwoFactorController::class, 'verify'])->name('two-factor.verify');
Route::get('/two-factor/resend', [TwoFactorController::class, 'show'])->name('two-factor.show');
Route::post('/two-factor/resend', [TwoFactorController::class, 'resend'])->name('two-factor.resend');
10 changes: 10 additions & 0 deletions src/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace Cone\Root\Http\Controllers\Auth;

use Cone\Root\Http\Controllers\Controller;
use Cone\Root\Interfaces\TwoFactorAuthenticatable;
use Cone\Root\Notifications\TwoFactorLink;
use Illuminate\Auth\Events\Login;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -60,6 +62,12 @@ public function login(Request $request): RedirectResponse
new Login(Auth::getDefaultDriver(), $request->user(), $request->filled('remember'))
);

if ($request->user() instanceof TwoFactorAuthenticatable && $request->user()->can('viewRoot')) {
$request->user()->notify(new TwoFactorLink());

$request->session()->flash('status', __('The two factor authentication link has been sent!'));
}

return Redirect::intended(URL::route('root.dashboard'));
}

Expand All @@ -70,6 +78,8 @@ public function logout(Request $request): RedirectResponse
{
Auth::guard()->logout();

$request->session()->remove('root.auth.verified');

$request->session()->invalidate();

$request->session()->regenerateToken();
Expand Down
66 changes: 66 additions & 0 deletions src/Http/Controllers/Auth/TwoFactorController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Cone\Root\Http\Controllers\Auth;

use Cone\Root\Http\Controllers\Controller;
use Cone\Root\Http\Middleware\Authenticate;
use Cone\Root\Notifications\TwoFactorLink;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Response as ResponseFactory;
use Illuminate\Support\Facades\URL;

class TwoFactorController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct()
{
$this->middleware(Authenticate::class);
$this->middleware('throttle:6,1')->only(['resend']);
}

/**
* Show the verification resend form.
*/
public function show(Request $request): Response|RedirectResponse
{
if ($request->session()->has('root.auth.two-factor')) {
return ResponseFactory::redirectToRoute('root.dashboard');
}

return ResponseFactory::view('root::auth.two-factor');
}

/**
* Verify the link.
*/
public function verify(Request $request): RedirectResponse
{
if (! $request->hasValidSignature() || ! hash_equals($request->input('hash'), sha1($request->user()->email))) {
return ResponseFactory::redirectToRoute('root.auth.two-factor.show')
->with('status', __('The authentication link is not valid! Please request a new link!'));
}

$request->session()->put('root.auth.two-factor', true);

return ResponseFactory::redirectToIntended(URL::route('root.dashboard'));
}

/**
* Resend the verification link.
*/
public function resend(Request $request): RedirectResponse
{
if ($request->session()->has('root.auth.two-factor')) {
return ResponseFactory::redirectToRoute('root.dashboard');
}

$request->user()->notify(new TwoFactorLink());

return ResponseFactory::redirectToRoute('root.auth.two-factor.show')
->with('status', __('The two factor authentication link has been sent!'));
}
}
26 changes: 26 additions & 0 deletions src/Http/Middleware/TwoFactorAuthenticate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Cone\Root\Http\Middleware;

use Closure;
use Cone\Root\Interfaces\TwoFactorAuthenticatable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
use Symfony\Component\HttpFoundation\Response;

class TwoFactorAuthenticate
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->user() instanceof TwoFactorAuthenticatable && ! $request->session()->has('root.auth.two-factor')) {
return Redirect::route('root.auth.two-factor.show');
}

return $next($request);
}
}
8 changes: 8 additions & 0 deletions src/Interfaces/TwoFactorAuthenticatable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Cone\Root\Interfaces;

interface TwoFactorAuthenticatable
{
//
}
42 changes: 42 additions & 0 deletions src/Notifications/TwoFactorLink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Cone\Root\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;

class TwoFactorLink extends Notification implements ShouldQueue
{
use Queueable;

/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['mail'];
}

/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
$url = URL::temporarySignedRoute(
'root.auth.two-factor.verify',
Config::get('root.two_factor.expiration', 600),
['hash' => sha1($notifiable->email)]
);

return (new MailMessage())
->subject(__('Two Factor Authentication Link'))
->line(__('To finish the two factor authentication process, please click the link below.'))
->action(__('Finish Two Factor Login'), $url);
}
}
Loading