forked from BookStackApp/BookStack
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding OTP Support for Email Login BookStackApp#5468
- Loading branch information
Emirhan Uysal
committed
Feb 3, 2025
1 parent
786a434
commit 32294eb
Showing
8 changed files
with
215 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
<?php | ||
|
||
namespace BookStack\Access\Controllers; | ||
|
||
use BookStack\Access\LoginService; | ||
use BookStack\Access\Mfa\BackupCodeService; | ||
use BookStack\Access\Mfa\MfaSession; | ||
use BookStack\Access\Mfa\MfaValue; | ||
use BookStack\Activity\ActivityType; | ||
use BookStack\Exceptions\NotFoundException; | ||
use BookStack\Http\Controller; | ||
use Exception; | ||
use Illuminate\Http\Request; | ||
use Illuminate\Validation\ValidationException; | ||
use Illuminate\Support\Str; | ||
|
||
class EmailController extends Controller | ||
{ | ||
use HandlesPartialLogins; | ||
|
||
protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-email'; | ||
|
||
/** | ||
* Show a view that generates and displays backup codes. | ||
*/ | ||
public function generate(BackupCodeService $codeService) | ||
{ | ||
/* $codes = $codeService->generateNewSet(); | ||
session()->put(self::SETUP_SECRET_SESSION_KEY, encrypt($codes)); | ||
$downloadUrl = 'data:application/octet-stream;base64,' . base64_encode(implode("\n\n", $codes)); | ||
$this->setPageTitle(trans('auth.mfa_gen_backup_codes_title')); | ||
return view('mfa.backup-codes-generate', [ | ||
'codes' => $codes, | ||
'downloadUrl' => $downloadUrl, | ||
]); */ | ||
|
||
$code = Str::random(6); | ||
session()->put(self::SETUP_SECRET_SESSION_KEY, encrypt($code)); | ||
|
||
$this->setPageTitle(trans('auth.mfa_gen_email_title')); | ||
|
||
return view('mfa.email-generate'); | ||
} | ||
|
||
/** | ||
* Confirm the setup of backup codes, storing them against the user. | ||
* | ||
* @throws Exception | ||
*/ | ||
public function confirm() | ||
{ | ||
/* if (!session()->has(self::SETUP_SECRET_SESSION_KEY)) { | ||
return response('No generated codes found in the session', 500); | ||
} | ||
$codes = decrypt(session()->pull(self::SETUP_SECRET_SESSION_KEY)); | ||
MfaValue::upsertWithValue($this->currentOrLastAttemptedUser(), MfaValue::METHOD_BACKUP_CODES, json_encode($codes)); | ||
$this->logActivity(ActivityType::MFA_SETUP_METHOD, 'backup-codes'); | ||
if (!auth()->check()) { | ||
$this->showSuccessNotification(trans('auth.mfa_setup_login_notification')); | ||
return redirect('/login'); | ||
} | ||
return redirect('/mfa/setup'); */ | ||
|
||
if (!session()->has(self::SETUP_SECRET_SESSION_KEY)) { | ||
return response('No generated codes found in the session', 500); | ||
} | ||
|
||
$validcode = decrypt(session()->pull(self::SETUP_SECRET_SESSION_KEY)); | ||
MfaValue::upsertWithValue($this->currentOrLastAttemptedUser(), MfaValue::METHOD_EMAIL, $validcode); | ||
|
||
$this->logActivity(ActivityType::MFA_SETUP_METHOD, 'email'); | ||
|
||
if (!auth()->check()) { | ||
$this->showSuccessNotification(trans('auth.mfa_setup_login_notification')); | ||
|
||
return redirect('/login'); | ||
} | ||
|
||
return redirect('/mfa/setup'); | ||
} | ||
|
||
/** | ||
* Verify the MFA method submission on check. | ||
* | ||
* @throws NotFoundException | ||
* @throws ValidationException | ||
*/ | ||
public function verify(Request $request, MfaSession $mfaSession, LoginService $loginService) | ||
{ | ||
$user = $this->currentOrLastAttemptedUser(); | ||
$validcode = MfaValue::getValueForUser($user, MfaValue::METHOD_EMAIL) ?? ''; | ||
|
||
$this->validate($request, [ | ||
'code' => [ | ||
'required', 'max:12', 'min:6', | ||
function ($attribute, $value, $fail) use ($validcode) { | ||
if($value !== $validcode) { | ||
$fail(trans('validation.email_code_wrong')); | ||
} | ||
}, | ||
], | ||
]); | ||
|
||
$newCode = Str::random(6); | ||
MfaValue::upsertWithValue($user, MfaValue::METHOD_EMAIL, $newCode); | ||
|
||
$mfaSession->markVerifiedForUser($user); | ||
$loginService->reattemptLoginFor($user); | ||
|
||
return redirect()->intended(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
namespace BookStack\Access\Notifications; | ||
|
||
use BookStack\App\MailNotification; | ||
use BookStack\Users\Models\User; | ||
use Illuminate\Notifications\Messages\MailMessage; | ||
use BookStack\Activity\Notifications\MessageParts\ListMessageLine; | ||
|
||
class EmailCodeNotification extends MailNotification | ||
{ | ||
public function __construct( | ||
public string $validcode | ||
) { | ||
} | ||
|
||
public function toMail(User $notifiable): MailMessage | ||
{ | ||
$locale = $notifiable->getLocale(); | ||
|
||
$listLines = array_filter([ | ||
$locale->trans('notifications.detail_created_by') => $this->validcode, | ||
]); | ||
|
||
return $this->newMailMessage() | ||
->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')])) | ||
->line(trans('auth.email_reset_text')) | ||
->line(new ListMessageLine($listLines)) | ||
->action(trans('auth.login'), url('login')) | ||
->line(trans('auth.email_reset_not_requested')); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
@extends('layouts.simple') | ||
|
||
@section('body') | ||
<div class="container very-small py-xl"> | ||
|
||
<div class="card content-wrap auto-height"> | ||
<h1 class="list-heading">{{ trans('auth.mfa_verify_access') }}</h1> | ||
<p class="mb-none">{{ trans('auth.mfa_verify_access_desc') }}</p> | ||
|
||
<hr class="my-l"> | ||
|
||
<form action="{{ url('/mfa/email/confirm') }}" method="POST"> | ||
{{ csrf_field() }} | ||
<div class="mt-s text-right"> | ||
<a href="{{ url('/login') }}" class="button outline">{{ trans('common.cancel') }}</a> | ||
<button class="button">{{ trans('auth.mfa_gen_confirm_and_enable') }}</button> | ||
</div> | ||
</form> | ||
</div> | ||
</div> | ||
@stop |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
|
||
<div class="setting-list-label">{{ trans('auth.mfa_verify_backup_code') }}</div> | ||
|
||
<p class="small mb-m">{{ trans('auth.mfa_verify_backup_code_desc') }}</p> | ||
|
||
<form action="{{ url('/mfa/email/verify') }}" method="POST"> | ||
{{ csrf_field() }} | ||
<input type="text" | ||
name="code" | ||
aria-labelledby="totp-verify-input-details" | ||
placeholder="{{ trans('auth.mfa_gen_totp_provide_code_here') }}" | ||
class="input-fill-width {{ $errors->has('code') ? 'neg' : '' }}"> | ||
|
||
@if($errors->has('code')) | ||
<div class="text-neg text-small px-xs">{{ $errors->first('code') }}</div> | ||
@endif | ||
|
||
<div class="mt-s text-right"> | ||
<button class="button">{{ trans('common.confirm') }}</button> | ||
</div> | ||
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters