update: two_factor_auth

This commit is contained in:
HDVinnie
2023-11-11 18:15:52 -05:00
parent d95d3462bd
commit a410807d11
10 changed files with 440 additions and 343 deletions
@@ -0,0 +1,31 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
class TwoFactorAuthController extends Controller
{
/**
* Edit user two-factor auth settings.
*/
public function edit(Request $request, User $user): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
abort_unless($request->user()->is($user), 403);
return view('user.two_factor_auth.edit', ['user' => $user]);
}
}
+139
View File
@@ -0,0 +1,139 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author HDVinnie <hdinnovations@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Livewire;
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
use Laravel\Fortify\Features;
use Livewire\Component;
class TwoFactorAuthForm extends Component
{
/**
* Indicates if two-factor authentication QR code is being displayed.
*/
public bool $showingQrCode = false;
/**
* Indicates if the two-factor authentication confirmation input and button are being displayed.
*/
public bool $showingConfirmation = false;
/**
* Indicates if two-factor authentication recovery codes are being displayed.
*/
public bool $showingRecoveryCodes = false;
/**
* The OTP code for confirming two-factor authentication.
*/
public ?string $code;
/**
* Mount the component.
*/
final public function mount(): void
{
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm') &&
is_null(\auth()->user()->two_factor_confirmed_at)) {
app(DisableTwoFactorAuthentication::class)(auth()->user());
}
}
/**
* Enable two-factor authentication for the user.
*/
final public function enableTwoFactorAuthentication(EnableTwoFactorAuthentication $enable): void
{
$enable(auth()->user());
$this->showingQrCode = true;
if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm')) {
$this->showingConfirmation = true;
} else {
$this->showingRecoveryCodes = true;
}
}
/**
* Confirm two-factor authentication for the user.
*
* @throws \Illuminate\Validation\ValidationException
*/
final public function confirmTwoFactorAuthentication(ConfirmTwoFactorAuthentication $confirm): void
{
$confirm(auth()->user(), $this->code);
$this->showingQrCode = false;
$this->showingConfirmation = false;
$this->showingRecoveryCodes = true;
}
/**
* Display the user's recovery codes.
*/
final public function showRecoveryCodes(): void
{
$this->showingRecoveryCodes = true;
}
/**
* Generate new recovery codes for the user.
*/
final public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generate): void
{
$generate(auth()->user());
$this->showingRecoveryCodes = true;
}
/**
* Disable two-factor authentication for the user.
*/
final public function disableTwoFactorAuthentication(DisableTwoFactorAuthentication $disable): void
{
$disable(auth()->user());
$this->showingQrCode = false;
$this->showingConfirmation = false;
$this->showingRecoveryCodes = false;
}
/**
* Get the current user of the application.
*/
final public function getUserProperty(): ?\Illuminate\Contracts\Auth\Authenticatable
{
return auth()->user();
}
/**
* Determine if two-factor authentication is enabled.
*/
final public function getEnabledProperty(): bool
{
return ! empty($this->user->two_factor_secret);
}
/**
* Render the component.
*/
final public function render(): \Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\Foundation\Application
{
return view('livewire.two-factor-auth-form');
}
}
Generated
+48 -48
View File
@@ -4459,16 +4459,16 @@
},
{
"name": "ramsey/uuid",
"version": "4.7.4",
"version": "4.7.5",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "60a4c63ab724854332900504274f6150ff26d286"
"reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286",
"reference": "60a4c63ab724854332900504274f6150ff26d286",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e",
"reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e",
"shasum": ""
},
"require": {
@@ -4535,7 +4535,7 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.7.4"
"source": "https://github.com/ramsey/uuid/tree/4.7.5"
},
"funding": [
{
@@ -4547,7 +4547,7 @@
"type": "tidelift"
}
],
"time": "2023-04-15T23:01:58+00:00"
"time": "2023-11-08T05:53:05+00:00"
},
{
"name": "spatie/db-dumper",
@@ -5227,16 +5227,16 @@
},
{
"name": "symfony/console",
"version": "v6.3.4",
"version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "eca495f2ee845130855ddf1cf18460c38966c8b6"
"reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6",
"reference": "eca495f2ee845130855ddf1cf18460c38966c8b6",
"url": "https://api.github.com/repos/symfony/console/zipball/0d14a9f6d04d4ac38a8cea1171f4554e325dae92",
"reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92",
"shasum": ""
},
"require": {
@@ -5297,7 +5297,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.3.4"
"source": "https://github.com/symfony/console/tree/v6.3.8"
},
"funding": [
{
@@ -5313,7 +5313,7 @@
"type": "tidelift"
}
],
"time": "2023-08-16T10:10:12+00:00"
"time": "2023-10-31T08:09:35+00:00"
},
{
"name": "symfony/css-selector",
@@ -5810,16 +5810,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v6.3.7",
"version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "59d1837d5d992d16c2628cd0d6b76acf8d69b33e"
"reference": "ce332676de1912c4389222987193c3ef38033df6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/59d1837d5d992d16c2628cd0d6b76acf8d69b33e",
"reference": "59d1837d5d992d16c2628cd0d6b76acf8d69b33e",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/ce332676de1912c4389222987193c3ef38033df6",
"reference": "ce332676de1912c4389222987193c3ef38033df6",
"shasum": ""
},
"require": {
@@ -5867,7 +5867,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v6.3.7"
"source": "https://github.com/symfony/http-foundation/tree/v6.3.8"
},
"funding": [
{
@@ -5883,20 +5883,20 @@
"type": "tidelift"
}
],
"time": "2023-10-28T23:55:27+00:00"
"time": "2023-11-07T10:17:15+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v6.3.7",
"version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "6d4098095f93279d9536a0e9124439560cc764d0"
"reference": "929202375ccf44a309c34aeca8305408442ebcc1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/6d4098095f93279d9536a0e9124439560cc764d0",
"reference": "6d4098095f93279d9536a0e9124439560cc764d0",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/929202375ccf44a309c34aeca8305408442ebcc1",
"reference": "929202375ccf44a309c34aeca8305408442ebcc1",
"shasum": ""
},
"require": {
@@ -5980,7 +5980,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-kernel/tree/v6.3.7"
"source": "https://github.com/symfony/http-kernel/tree/v6.3.8"
},
"funding": [
{
@@ -5996,7 +5996,7 @@
"type": "tidelift"
}
],
"time": "2023-10-29T14:31:45+00:00"
"time": "2023-11-10T13:47:32+00:00"
},
{
"name": "symfony/mailer",
@@ -7211,16 +7211,16 @@
},
{
"name": "symfony/string",
"version": "v6.3.5",
"version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "13d76d0fb049051ed12a04bef4f9de8715bea339"
"reference": "13880a87790c76ef994c91e87efb96134522577a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339",
"reference": "13d76d0fb049051ed12a04bef4f9de8715bea339",
"url": "https://api.github.com/repos/symfony/string/zipball/13880a87790c76ef994c91e87efb96134522577a",
"reference": "13880a87790c76ef994c91e87efb96134522577a",
"shasum": ""
},
"require": {
@@ -7277,7 +7277,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.3.5"
"source": "https://github.com/symfony/string/tree/v6.3.8"
},
"funding": [
{
@@ -7293,7 +7293,7 @@
"type": "tidelift"
}
],
"time": "2023-09-18T10:38:32+00:00"
"time": "2023-11-09T08:28:21+00:00"
},
{
"name": "symfony/translation",
@@ -7470,16 +7470,16 @@
},
{
"name": "symfony/uid",
"version": "v6.3.0",
"version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
"reference": "01b0f20b1351d997711c56f1638f7a8c3061e384"
"reference": "819fa5ac210fb7ddda4752b91a82f50be7493dd9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/uid/zipball/01b0f20b1351d997711c56f1638f7a8c3061e384",
"reference": "01b0f20b1351d997711c56f1638f7a8c3061e384",
"url": "https://api.github.com/repos/symfony/uid/zipball/819fa5ac210fb7ddda4752b91a82f50be7493dd9",
"reference": "819fa5ac210fb7ddda4752b91a82f50be7493dd9",
"shasum": ""
},
"require": {
@@ -7524,7 +7524,7 @@
"uuid"
],
"support": {
"source": "https://github.com/symfony/uid/tree/v6.3.0"
"source": "https://github.com/symfony/uid/tree/v6.3.8"
},
"funding": [
{
@@ -7540,20 +7540,20 @@
"type": "tidelift"
}
],
"time": "2023-04-08T07:25:02+00:00"
"time": "2023-10-31T08:07:48+00:00"
},
{
"name": "symfony/var-dumper",
"version": "v6.3.6",
"version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "999ede244507c32b8e43aebaa10e9fce20de7c97"
"reference": "81acabba9046550e89634876ca64bfcd3c06aa0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/999ede244507c32b8e43aebaa10e9fce20de7c97",
"reference": "999ede244507c32b8e43aebaa10e9fce20de7c97",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/81acabba9046550e89634876ca64bfcd3c06aa0a",
"reference": "81acabba9046550e89634876ca64bfcd3c06aa0a",
"shasum": ""
},
"require": {
@@ -7608,7 +7608,7 @@
"dump"
],
"support": {
"source": "https://github.com/symfony/var-dumper/tree/v6.3.6"
"source": "https://github.com/symfony/var-dumper/tree/v6.3.8"
},
"funding": [
{
@@ -7624,7 +7624,7 @@
"type": "tidelift"
}
],
"time": "2023-10-12T18:45:56+00:00"
"time": "2023-11-08T10:42:36+00:00"
},
{
"name": "theodorejb/polycast",
@@ -11675,16 +11675,16 @@
},
{
"name": "symfony/yaml",
"version": "v6.3.7",
"version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "9758b6c69d179936435d0ffb577c3708d57e38a8"
"reference": "3493af8a8dad7fa91c77fa473ba23ecd95334a92"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/9758b6c69d179936435d0ffb577c3708d57e38a8",
"reference": "9758b6c69d179936435d0ffb577c3708d57e38a8",
"url": "https://api.github.com/repos/symfony/yaml/zipball/3493af8a8dad7fa91c77fa473ba23ecd95334a92",
"reference": "3493af8a8dad7fa91c77fa473ba23ecd95334a92",
"shasum": ""
},
"require": {
@@ -11727,7 +11727,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v6.3.7"
"source": "https://github.com/symfony/yaml/tree/v6.3.8"
},
"funding": [
{
@@ -11743,7 +11743,7 @@
"type": "tidelift"
}
],
"time": "2023-10-28T23:31:00+00:00"
"time": "2023-11-06T10:58:05+00:00"
},
{
"name": "ta-tikoma/phpunit-architecture-test",
+20 -30
View File
@@ -1,5 +1,20 @@
parameters:
ignoreErrors:
-
message: "#^Method App\\\\Actions\\\\Fortify\\\\CreateNewUser\\:\\:passwordRules\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: app/Actions/Fortify/CreateNewUser.php
-
message: "#^Method App\\\\Actions\\\\Fortify\\\\ResetUserPassword\\:\\:passwordRules\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: app/Actions/Fortify/ResetUserPassword.php
-
message: "#^Method App\\\\Actions\\\\Fortify\\\\UpdateUserPassword\\:\\:passwordRules\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: app/Actions/Fortify/UpdateUserPassword.php
-
message: "#^Called 'pluck' on Laravel collection, but could have been retrieved as a query\\.$#"
count: 1
@@ -2010,11 +2025,6 @@ parameters:
count: 1
path: app/Http/Controllers/AnnounceController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\ActivationController\\:\\:activate\\(\\) has parameter \\$token with no type specified\\.$#"
count: 1
path: app/Http/Controllers/Auth/ActivationController.php
-
message: "#^Unable to resolve the template type TKey in call to function collect$#"
count: 2
@@ -2025,31 +2035,6 @@ parameters:
count: 2
path: app/Http/Controllers/Auth/ApplicationController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\LoginController\\:\\:authenticated\\(\\) has parameter \\$user with no type specified\\.$#"
count: 1
path: app/Http/Controllers/Auth/LoginController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\RegisterController\\:\\:register\\(\\) has parameter \\$code with no type specified\\.$#"
count: 1
path: app/Http/Controllers/Auth/RegisterController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\RegisterController\\:\\:registrationForm\\(\\) has parameter \\$code with no type specified\\.$#"
count: 1
path: app/Http/Controllers/Auth/RegisterController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\ResetPasswordController\\:\\:resetPassword\\(\\) has parameter \\$password with no type specified\\.$#"
count: 1
path: app/Http/Controllers/Auth/ResetPasswordController.php
-
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\ResetPasswordController\\:\\:resetPassword\\(\\) has parameter \\$user with no type specified\\.$#"
count: 1
path: app/Http/Controllers/Auth/ResetPasswordController.php
-
message: "#^Access to an undefined property App\\\\Models\\\\Torrent\\:\\:\\$meta\\.$#"
count: 10
@@ -3200,6 +3185,11 @@ parameters:
count: 1
path: app/Http/Livewire/TvSearch.php
-
message: "#^Access to an undefined property App\\\\Models\\\\User\\:\\:\\$two_factor_confirmed_at\\.$#"
count: 1
path: app/Http/Livewire/TwoFactorAuthForm.php
-
message: "#^Method App\\\\Http\\\\Livewire\\\\UserActive\\:\\:getActivesProperty\\(\\) return type with generic interface Illuminate\\\\Contracts\\\\Pagination\\\\LengthAwarePaginator does not specify its types\\: TItem$#"
count: 1
@@ -3,112 +3,66 @@
<head>
<meta charset="UTF-8">
<title>{{ __('auth.login') }} - {{ config('other.title') }}</title>
@section('meta')
<meta name="description"
content="{{ __('auth.login-now-on') }} {{ config('other.title') }} . {{ __('auth.not-a-member') }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:title" content="{{ __('auth.login') }}">
<meta property="og:site_name" content="{{ config('other.title') }}">
<meta property="og:type" content="website">
<meta property="og:image" content="{{ url('/img/og.png') }}">
<meta property="og:description" content="{{ config('unit3d.powered-by') }}">
<meta property="og:url" content="{{ url('/') }}">
<meta property="og:locale" content="{{ config('app.locale') }}">
<meta name="csrf-token" content="{{ csrf_token() }}">
@show
<title>{{ __('Two Factor Authentication') }} - {{ config('other.title') }}</title>
<link rel="shortcut icon" href="{{ url('/favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url('/favicon.ico') }}" type="image/x-icon">
<link rel="stylesheet" href="{{ mix('css/main/login.css') }}" crossorigin="anonymous">
<link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous">
</head>
<body>
<!-- Dont Not Change! For Jackett Support -->
<div class="Jackett" style="display:none;">{{ config('unit3d.powered-by') }}</div>
<!-- Dont Not Change! For Jackett Support -->
@if ($errors->any())
<div id="ERROR_COPY" style="display: none;">
@foreach ($errors->all() as $error)
{{ $error }}<br>
@endforeach
</div>
@endif
<div class="wrapper fadeInDown">
<svg viewBox="0 0 800 100" class="sitebanner">
<symbol id="s-text">
<text text-anchor="middle" x="50%" y="50%" dy=".35em">
{{ config('other.title') }}
</text>
</symbol>
<use xlink:href="#s-text" class="text"></use>
<use xlink:href="#s-text" class="text"></use>
<use xlink:href="#s-text" class="text"></use>
<use xlink:href="#s-text" class="text"></use>
<use xlink:href="#s-text" class="text"></use>
</svg>
<div id="formContent">
<div>
<h2 class="active">{{ __('auth.totp.title') }} </h2>
</div>
<div class="fadeIn first">
<img src="{{ url('/img/icon.svg') }}" id="icon" alt="{{ __('auth.user-icon') }}"/>
</div>
<form role="form" method="POST" action="{{ route('two-factor.login') }}">
@csrf
<div>
<label for="username" class="col-md-4 control-label">{{ __('auth.totp.input') }}</label>
<div class="col-md-6">
<input id="code" type="text" class="form-control" name="code"
required autofocus>
</div>
<div class="block">
<div>
<div x-data="{ recovery: false }">
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400" x-show="! recovery">
{{ __('Please confirm access to your account by entering the authentication code provided by your authenticator application.') }}
</div>
<button type="submit" class="fadeIn fourth" id="login-button">{{ __('auth.login') }}</button>
</form>
<div id="formFooter">
<a href="{{ route('password.request') }}">
<h2 class="inactive underlineHover">{{ __('auth.lost-password') }} </h2>
</a>
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400" x-cloak x-show="recovery">
{{ __('Please confirm access to your account by entering one of your emergency recovery codes.') }}
</div>
<form method="POST" action="{{ route('two-factor.login') }}">
@csrf
<div class="mt-4" x-show="! recovery">
<label for="code" value="{{ __('Code') }}"></label>
<input id="code" class="block mt-1 w-full" type="text" inputmode="numeric" name="code" autofocus x-ref="code" autocomplete="one-time-code" />
</div>
<div class="mt-4" x-cloak x-show="recovery">
<label for="recovery_code" value="{{ __('Recovery Code') }}"></label>
<input id="recovery_code" class="block mt-1 w-full" type="text" name="recovery_code" x-ref="recovery_code" autocomplete="one-time-code" />
</div>
<div class="flex items-center justify-end mt-4">
<button type="button" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 underline cursor-pointer"
x-show="! recovery"
x-on:click="
recovery = true;
$nextTick(() => { $refs.recovery_code.focus() })
">
{{ __('Use a recovery code') }}
</button>
<button type="button" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 underline cursor-pointer"
x-cloak
x-show="recovery"
x-on:click="
recovery = false;
$nextTick(() => { $refs.code.focus() })
">
{{ __('Use an authentication code') }}
</button>
<button class="ms-4">
{{ __('Log in') }}
</button>
</div>
</form>
</div>
</div>
</div>
<script src="{{ mix('js/app.js') }}" crossorigin="anonymous"></script>
@foreach (['warning', 'success', 'info'] as $key)
@if (Session::has($key))
<script nonce="{{ HDVinnie\SecureHeaders\SecureHeaders::nonce('script') }}">
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000
})
Toast.fire({
icon: '{{ $key }}',
title: '{{ Session::get($key) }}'
})
</script>
@endif
@endforeach
@if (Session::has('errors'))
<script nonce="{{ HDVinnie\SecureHeaders\SecureHeaders::nonce('script') }}">
Swal.fire({
title: '<strong style=" color: rgb(17,17,17);">Error</strong>',
icon: 'error',
html: document.getElementById('ERROR_COPY').innerHTML,
showCloseButton: true,
})
</script>
@endif
</body>
</html>
</html>
@@ -0,0 +1,110 @@
<section class="panelV2">
<header class="panel__header">
<h2 class="panel__heading">{{ __('Two Factor Authentication') }}</h2>
</header>
<div class="panel__body">
@if ($this->enabled)
@if ($showingConfirmation)
<span class="text-warning">{{ __('Finish enabling two factor authentication.') }}</span>
@else
<span class="text-success">{{ __('You have enabled two factor authentication.') }}</span>
@endif
@else
<span class="text-danger">{{ __('You have not enabled two factor authentication.') }}</span>
@endif
<div>
<span class="text-muted">
{{ __('When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone\'s Google Authenticator application.') }}
</span>
</div>
@if ($this->enabled)
@if ($showingQrCode)
<div>
<p class="text-info">
@if ($showingConfirmation)
{{ __('To finish enabling two factor authentication, scan the following QR code using your phone\'s authenticator application or enter the setup key and provide the generated OTP code.') }}
@else
{{ __('Two factor authentication is now enabled. Scan the following QR code using your phone\'s authenticator application or enter the setup key.') }}
@endif
</p>
</div>
<div>
{!! $this->user->twoFactorQrCodeSvg() !!}
</div>
<div>
<p>
{{ __('Setup Key') }}: {{ decrypt($this->user->two_factor_secret) }}
</p>
</div>
@if ($showingConfirmation)
<div>
<label for="code" value="{{ __('Code') }}"></label>
<input id="code"
name="code"
class="form__text"
type="text"
inputmode="numeric"
autofocus
autocomplete="one-time-code"
wire:model="code"
wire:keydown.enter="confirmTwoFactorAuthentication" />
</div>
@endif
@endif
@if ($showingRecoveryCodes)
<div class="panel__body">
<span class="text-danger">
{{ __('Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.') }}
</span>
<pre>
@foreach (json_decode(decrypt($this->user->two_factor_recovery_codes), true) as $code)
<div>{{ $code }}</div>
@endforeach
</pre>
</div>
@endif
@endif
<div>
@if (! $this->enabled)
<button class="form__button form__button--filled" wire:click="enableTwoFactorAuthentication" wire:loading.attr="disabled">
{{ __('Enable') }}
</button>
@else
@if ($showingRecoveryCodes)
<button class="form__button form__button--filled" wire:click="regenerateRecoveryCodes">
{{ __('Regenerate Recovery Codes') }}
</button>
@elseif ($showingConfirmation)
<button class="form__button form__button--filled" type="button" wire:click="confirmTwoFactorAuthentication" wire:loading.attr="disabled">
{{ __('Confirm') }}
</button>
@else
<button class="form__button form__button--filled" wire:click="showRecoveryCodes">
{{ __('Show Recovery Codes') }}
</button>
@endif
@if ($showingConfirmation)
<button class="form__button form__button--filled" wire:click="disableTwoFactorAuthentication" wire:loading.attr="disabled">
{{ __('Cancel') }}
</button>
@else
<button class="form__button form__button--filled" wire:click="disableTwoFactorAuthentication" wire:loading.attr="disabled">
{{ __('Disable') }}
</button>
@endif
@endif
</div>
</div>
</section>
+3 -3
View File
@@ -122,10 +122,10 @@
API Key
</a>
</li>
<li class="{{ Route::is('users.two_step.edit') ? 'nav-tab--active' : 'nav-tavV2' }}">
<li class="{{ Route::is('users.two_factor_auth.edit') ? 'nav-tab--active' : 'nav-tavV2' }}">
<a
class="{{ Route::is('users.two_step.edit') ? 'nav-tab--active__link' : 'nav-tab__link' }}"
href="{{ route('users.two_step.edit', ['user' => $user]) }}"
class="{{ Route::is('users.two_factor_auth.edit') ? 'nav-tab--active__link' : 'nav-tab__link' }}"
href="{{ route('users.two_factor_auth.edit', ['user' => $user]) }}"
>
{{ __('user.two-step-auth.title') }}
</a>
@@ -0,0 +1,29 @@
@extends('layout.default')
@section('title')
<title>{{ $user->username }} - Security - {{ __('common.members') }} - {{ config('other.title') }}</title>
@endsection
@section('breadcrumbs')
<li class="breadcrumbV2">
<a href="{{ route('users.show', ['user' => $user]) }}" class="breadcrumb__link">
{{ $user->username }}
</a>
</li>
<li class="breadcrumbV2">
<a href="{{ route('users.general_settings.edit', ['user' => $user]) }}" class="breadcrumb__link">
{{ __('user.settings') }}
</a>
</li>
<li class="breadcrumb--active">
{{ __('Two Factor Authentication') }}
</li>
@endsection
@section('nav-tabs')
@include('user.buttons.user')
@endsection
@section('main')
@livewire('two-factor-auth-form')
@endsection
@@ -1,164 +0,0 @@
@extends('layout.default')
@section('title')
<title>{{ $user->username }} - Security - {{ __('common.members') }} - {{ config('other.title') }}</title>
@endsection
@section('breadcrumbs')
<li class="breadcrumbV2">
<a href="{{ route('users.show', ['user' => $user]) }}" class="breadcrumb__link">
{{ $user->username }}
</a>
</li>
<li class="breadcrumbV2">
<a href="{{ route('users.general_settings.edit', ['user' => $user]) }}" class="breadcrumb__link">
{{ __('user.settings') }}
</a>
</li>
<li class="breadcrumb--active">
{{ __('user.two-step-auth.title') }}
</li>
@endsection
@section('nav-tabs')
@include('user.buttons.user')
@endsection
@section('main')
@if (session('status') == 'two-factor-authentication-confirmed' || request()->user()->hasEnabledTwoFactorAuthentication())
<section class="panelV2">
<h2 class="panel__heading">{{ __('user.two-step-auth.totp') }}</h2>
<div class="panel__body">
<p>{{ __('user.two-step-auth.totp-is-enabled') }}</p>
<div>
<div>
<form
class="form"
action="{{ route('two-factor.disable') }}"
method="POST"
>
@csrf
@method('DELETE')
<p>{{ __('user.two-step-auth.password-confirm') }}</p>
<p class="form__group">
<button class="form__button form__button--filled">
{{ __('common.disable') }}
</button>
</p>
</form>
</div>
</div>
</div>
</section>
<section class="panelV2" x-data="{show: false}">
<h2 class="panel__heading">{{ __('user.two-step-auth.recovery-code') }}</h2>
<div class="panel__body">
<p>{{ __('user.two-step-auth.recovery-code-description') }}</p>
<div>
<div>
<form method="POST" action="{{route('two-factor.recovery-codes')}}" class="form__group">
@csrf
<button type="submit" class="form__button form__button--filled">
{{ __('user.two-step-auth.recovery-code-reset') }}
</button>
<div class="form__button form__button--filled" @click="show = !show">
{{ __('user.two-step-auth.recovery-code-reveal') }}
</div>
</form>
</div>
</div>
<div class="twoStep__recoveryCodes" x-cloak x-show="show" >
<p>Recovery Codes:</p>
<p style="font-family: monospace;">
@foreach(auth()->user()->recoveryCodes() as $code)
{{ $code }}<br/>
@endforeach
</p>
</div>
</div>
</section>
@elseif (session('status') == 'two-factor-authentication-enabled')
<section class="panelV2">
<h2 class="panel__heading">{{ __('user.two-step-auth.totp') }}</h2>
<div class="panel__body">
<p>{{ __('user.two-step-auth.complete-setup') }}</p>
<form
class="form"
action="{{ route('two-factor.confirm') }}"
method="POST"
>
@csrf
<div class="twoStep__qrCode">
<img src="data:image/svg+xml;base64,{{base64_encode(request()->user()->twoFactorQrCodeSvg())}}" alt="2FA QR Code">
</div>
<div class="form__group">
<input type="text" class="form__text" name="code" id="code" value="" required>
<label class="form__label form__label--floating" for="code">{{ __('user.two-step-auth.confirm-code') }}</label>
</div>
<div class="form__group">
<button class="form__button form__button--filled">
{{ __('common.enable') }}
</button>
</div>
</form>
</div>
</section>
@else
<section class="panelV2">
<h2 class="panel__heading">{{ __('user.two-step-auth.totp') }}</h2>
<div class="panel__body">
<p>{{ __('user.two-step-auth.totp-is-disabled') }}</p>
<form
class="form"
action="{{ route('two-factor.enable') }}"
method="POST"
>
@csrf
<p>{{ __('user.two-step-auth.password-confirm') }}</p>
<p>{{ __('user.two-step-auth.upon-enabling') }}</p>
<p class="form__group">
<button class="form__button form__button--filled">
{{ __('common.enable') }}
</button>
</p>
</form>
</div>
</section>
<section class="panelV2">
<h2 class="panel__heading">{{ __('user.two-step-auth.email') }}</h2>
<div class="panel__body">
<form
class="form"
action="{{ route('users.two_step.update', ['user' => $user]) }}"
method="POST"
>
@csrf
@method('PATCH')
<p>Upon enabling, you will receive an email including a code to your registered email address.</p>
<p>Token-based two factor authentication is planned for a future update.</p>
<div class="form__group">
<input type="hidden" name="twostep" value="0">
<input
type="checkbox"
class="form__checkbox"
id="twostep"
name="twostep"
value="1"
@checked($user->twostep)
>
<label class="form__label" for="internal">Enable two-step authentication</label>
</div>
<div class="form__group">
<button class="form__button form__button--filled">
{{ __('common.save') }}
</button>
</div>
</form>
</div>
</section>
@endif
@endsection
+9 -1
View File
@@ -2,6 +2,9 @@
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\URL;
use Laravel\Fortify\Features;
use Laravel\Fortify\Http\Controllers\TwoFactorAuthenticatedSessionController;
use Laravel\Fortify\RoutePath;
/**
* NOTICE OF LICENSE.
@@ -38,7 +41,7 @@ Route::middleware('language')->group(function (): void {
| Website (Not Authorized) (Alpha Ordered)
|---------------------------------------------------------------------------------
*/
Route::group(['before' => 'auth', 'middleware' => 'guest'], function (): void {
Route::middleware('guest')->group(function (): void {
// Application Signup
Route::get('/application', [App\Http\Controllers\Auth\ApplicationController::class, 'create'])->name('application.create');
Route::post('/application', [App\Http\Controllers\Auth\ApplicationController::class, 'store'])->name('application.store');
@@ -496,6 +499,11 @@ Route::middleware('language')->group(function (): void {
Route::delete('/{seedbox}', [App\Http\Controllers\User\SeedboxController::class, 'destroy'])->name('destroy');
});
// Two-Factor Authentication
Route::prefix('two-factor-auth')->name('two_factor_auth.')->group(function (): void {
Route::get('/edit', [App\Http\Controllers\User\TwoFactorAuthController::class, 'edit'])->name('edit');
});
// Email
Route::prefix('email')->name('email.')->group(function (): void {
Route::get('/edit', [App\Http\Controllers\User\EmailController::class, 'edit'])->name('edit');