diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 0f742f2..47413e2 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -44,7 +44,7 @@ class LoginController extends Controller // We can't customise the error message, since that would imply overriding the login method, which is large. // Also, the user should never know that they're banned. - public function attemptLogin(Request $request) + public function attemptLogin(Request $request) { $user = User::where('email', $request->email)->first(); @@ -60,9 +60,9 @@ class LoginController extends Controller return $this->originalAttemptLogin($request); } } - + return $this->originalAttemptLogin($request); - + } diff --git a/app/Http/Controllers/Auth/TwofaController.php b/app/Http/Controllers/Auth/TwofaController.php new file mode 100644 index 0000000..a3ecac9 --- /dev/null +++ b/app/Http/Controllers/Auth/TwofaController.php @@ -0,0 +1,16 @@ +user()->has2FA()) + { + if ($request->session()->has('twofaAttemptFailed')) + { + $twoFactorSecret = $request->session()->get('current2FA'); + } + else + { + $twoFactorSecret = Google2FA::generateSecretKey(32, ''); + $request->session()->put('current2FA', $twoFactorSecret); + } + + $QRCode = Google2FA::getQRCodeInline( + config('app.name'), + $request->user()->email, + $twoFactorSecret + ); + } + return view('dashboard.user.profile.useraccount') - ->with('ip', request()->ip()); + ->with('ip', request()->ip()) + ->with('twofaQRCode', $QRCode); } @@ -247,10 +273,67 @@ class UserController extends Controller } + public function add2FASecret(Add2FASecretRequest $request) + { + $currentSecret = $request->session()->get('current2FA'); + $isValid = Google2FA::verifyKey($currentSecret, $request->otp); + + if ($isValid) + { + $request->user()->twofa_secret = $currentSecret; + $request->user()->save(); + + Log::warning('SECURITY: User activated two-factor authentication', [ + 'initiator' => $request->user()->email, + 'ip' => $request->ip() + ]); + + Google2FA::login(); + + Log::warning('SECURITY: Started two factor session automatically', [ + 'initiator' => $request->user()->email, + 'ip' => $request->ip() + ]); + + $request->session()->forget('current2FA'); + + if ($request->session()->has('twofaAttemptFailed')) + $request->session()->forget('twofaAttemptFailed'); + + + $request->session()->flash('success', '2FA succesfully enabled! You\'ll now be prompted for an OTP each time you log in.'); + } + else + { + $request->session()->flash('error', 'Incorrect code. Please reopen the 2FA settings panel and try again.'); + $request->session()->put('twofaAttemptFailed', true); + } + + return redirect()->back(); + } + + public function remove2FASecret(Remove2FASecretRequest $request) + { + Log::warning('SECURITY: Disabling two factor authentication (user initiated)', [ + 'initiator' => $request->user()->email, + 'ip' => $request->ip() + ]); + + $request->user()->twofa_secret = null; + $request->user()->save(); + + $request->session()->flash('success', 'Two-factor authentication disabled.'); + return redirect()->back(); + } + + + + public function terminate(Request $request, User $user) { $this->authorize('terminate', User::class); + // TODO: move logic to policy if (!$user->isStaffMember() || $user->is(Auth::user())) { $request->session()->flash('error', 'You cannot terminate this user.'); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 42605f5..58399e7 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -64,6 +64,7 @@ class Kernel extends HttpKernel 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'eligibility' => \App\Http\Middleware\ApplicationEligibility::class, 'usernameUUID' => \App\Http\Middleware\UsernameUUID::class, - 'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class + 'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class, + '2fa' => \PragmaRX\Google2FALaravel\Middleware::class, ]; } diff --git a/app/Http/Requests/Add2FASecretRequest.php b/app/Http/Requests/Add2FASecretRequest.php new file mode 100644 index 0000000..13fa9dd --- /dev/null +++ b/app/Http/Requests/Add2FASecretRequest.php @@ -0,0 +1,31 @@ + 'required|string|min:6|max:6' + ]; + } +} diff --git a/app/Http/Requests/Remove2FASecretRequest.php b/app/Http/Requests/Remove2FASecretRequest.php new file mode 100644 index 0000000..6583b93 --- /dev/null +++ b/app/Http/Requests/Remove2FASecretRequest.php @@ -0,0 +1,31 @@ + 'required|password', + 'consent' => 'required|accepted' + ]; + } +} diff --git a/app/Traits/AuthenticatesTwoFactor.php b/app/Traits/AuthenticatesTwoFactor.php new file mode 100644 index 0000000..4b6ab31 --- /dev/null +++ b/app/Traits/AuthenticatesTwoFactor.php @@ -0,0 +1,40 @@ +user()->twofa_secret, $request->otp); + + if ($isValid) + { + Google2FA::login(); + + Log::info('SECURITY (postauth): One-time password verification succeeded', [ + 'initiator' => $request->user()->email, + 'ip' => $request->ip() + ]); + + return redirect()->to($this->redirectTo); + } + else + { + Log::warning('SECURITY (preauth): One-time password verification failed', [ + 'initiator' => $request->user()->email, + 'ip' => $request->ip() + ]); + + $request->session()->flash('error', 'Your one time password is invalid.'); + return redirect()->back(); + } + } + +} diff --git a/app/User.php b/app/User.php index c51084c..1e76521 100644 --- a/app/User.php +++ b/app/User.php @@ -68,18 +68,24 @@ class User extends Authenticatable } - public function isBanned() { return !$this->bans()->get()->isEmpty(); } + + public function isStaffMember() { return $this->hasAnyRole('reviewer', 'admin', 'hiringManager'); } + public function has2FA() + { + return !is_null($this->twofa_secret); + } + public function routeNotificationForSlack($notification) diff --git a/composer.json b/composer.json index b36fdd9..b5adcc6 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "laravel/slack-notification-channel": "^2.0", "laravel/tinker": "^2.0", "laravel/ui": "^2.0", + "pragmarx/google2fa-laravel": "^1.3", "sentry/sentry-laravel": "1.7.1", "spatie/laravel-permission": "^3.13" }, diff --git a/composer.lock b/composer.lock index 38beddb..edfbfc4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "51429857899e8134bbe6b4fa61145cc3", + "content-hash": "7bd6f57124bb77e0b496e2200f729267", "packages": [ { "name": "almasaeed2010/adminlte", @@ -221,6 +221,55 @@ ], "time": "2019-12-24T22:41:47+00:00" }, + { + "name": "bacon/bacon-qr-code", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "6e53ced3d2499cee4a0ef23a7c4d6449607ac7da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/6e53ced3d2499cee4a0ef23a7c4d6449607ac7da", + "reference": "6e53ced3d2499cee4a0ef23a7c4d6449607ac7da", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0", + "ext-iconv": "*", + "php": "^7.1" + }, + "require-dev": { + "phly/keep-a-changelog": "^1.4", + "phpunit/phpunit": "^6.4", + "squizlabs/php_codesniffer": "^3.1" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "http://www.dasprids.de", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "time": "2020-07-14T11:04:05+00:00" + }, { "name": "brick/math", "version": "0.8.15", @@ -374,6 +423,48 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "time": "2020-04-23T11:49:37+00:00" }, + { + "name": "dasprid/enum", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "631ef6e638e9494b0310837fa531bedd908fc22b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/631ef6e638e9494b0310837fa531bedd908fc22b", + "reference": "631ef6e638e9494b0310837fa531bedd908fc22b", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^6.4", + "squizlabs/php_codesniffer": "^3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "time": "2017-10-25T22:45:27+00:00" + }, { "name": "dnoegel/php-xdg-base-dir", "version": "v0.1.1", @@ -2260,6 +2351,68 @@ ], "time": "2020-05-25T09:32:45+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2", + "reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7", + "vimeo/psalm": "^1|^2|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "time": "2019-11-06T19:20:29+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.99", @@ -2783,6 +2936,183 @@ ], "time": "2020-03-21T18:07:53+00:00" }, + { + "name": "pragmarx/google2fa", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/26c4c5cf30a2844ba121760fd7301f8ad240100b", + "reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1.0|^2.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.18", + "phpunit/phpunit": "^7.5.15|^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa" + ], + "time": "2020-04-05T10:47:18+00:00" + }, + { + "name": "pragmarx/google2fa-laravel", + "version": "v1.3.3", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa-laravel.git", + "reference": "ed6e0a9ea1519550688ffb5afb4919204e46ecea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/ed6e0a9ea1519550688ffb5afb4919204e46ecea", + "reference": "ed6e0a9ea1519550688ffb5afb4919204e46ecea", + "shasum": "" + }, + "require": { + "laravel/framework": ">=5.4.36", + "php": ">=7.0", + "pragmarx/google2fa-qrcode": "^1.0" + }, + "require-dev": { + "orchestra/testbench": "3.4.*|3.5.*|3.6.*|3.7.*|4.*", + "phpunit/phpunit": "~5|~6|~7|~8" + }, + "suggest": { + "bacon/bacon-qr-code": "Required to generate inline QR Codes.", + "pragmarx/recovery": "Generate recovery codes." + }, + "type": "library", + "extra": { + "component": "package", + "frameworks": [ + "Laravel" + ], + "branch-alias": { + "dev-master": "0.2-dev" + }, + "laravel": { + "providers": [ + "PragmaRX\\Google2FALaravel\\ServiceProvider" + ], + "aliases": { + "Google2FA": "PragmaRX\\Google2FALaravel\\Facade" + } + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FALaravel\\": "src/", + "PragmaRX\\Google2FALaravel\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "Authentication", + "Two Factor Authentication", + "google2fa", + "laravel" + ], + "time": "2020-04-05T17:39:30+00:00" + }, + { + "name": "pragmarx/google2fa-qrcode", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa-qrcode.git", + "reference": "fd5ff0531a48b193a659309cc5fb882c14dbd03f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/fd5ff0531a48b193a659309cc5fb882c14dbd03f", + "reference": "fd5ff0531a48b193a659309cc5fb882c14dbd03f", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "~1.0|~2.0", + "php": ">=5.4", + "pragmarx/google2fa": ">=4.0" + }, + "require-dev": { + "khanamiryan/qrcode-detector-decoder": "^1.0", + "phpunit/phpunit": "~4|~5|~6|~7" + }, + "type": "library", + "extra": { + "component": "package", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FAQRCode\\": "src/", + "PragmaRX\\Google2FAQRCode\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "QR Code package for Google2FA", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa", + "qr code", + "qrcode" + ], + "time": "2019-03-20T16:42:58+00:00" + }, { "name": "psr/container", "version": "1.0.0", diff --git a/config/google2fa.php b/config/google2fa.php new file mode 100644 index 0000000..4dd5824 --- /dev/null +++ b/config/google2fa.php @@ -0,0 +1,79 @@ + env('OTP_ENABLED', true), + + /* + * Lifetime in minutes. + * + * In case you need your users to be asked for a new one time passwords from time to time. + */ + 'lifetime' => env('OTP_LIFETIME', 0), // 0 = eternal + + /* + * Renew lifetime at every new request. + */ + 'keep_alive' => env('OTP_KEEP_ALIVE', true), + + /* + * Auth container binding. + */ + 'auth' => 'auth', + + /* + * 2FA verified session var. + */ + + 'session_var' => 'google2fa', + + /* + * One Time Password request input name. + */ + 'otp_input' => 'otp', + + /* + * One Time Password Window. + */ + 'window' => 1, + + /* + * Forbid user to reuse One Time Passwords. + */ + 'forbid_old_passwords' => false, + + /* + * User's table column for google2fa secret. + */ + 'otp_secret_column' => 'twofa_secret', + + /* + * One Time Password View. + */ + 'view' => 'auth.2fa', + + /* + * One Time Password error message. + */ + 'error_messages' => [ + 'wrong_otp' => "Your one time code was incorrect.", + 'cannot_be_empty' => 'The one time code cannot be empty.', + 'unknown' => 'An unknown error has occurred. Please try again.', + ], + + /* + * Throw exceptions or just fire events? + */ + 'throw_exceptions' => env('OTP_THROW_EXCEPTION', true), + + /* + * Which image backend to use for generating QR codes? + * + * Supports imagemagick, svg and eps + */ + 'qrcode_image_backend' => \PragmaRX\Google2FALaravel\Support\Constants::QRCODE_IMAGE_BACKEND_IMAGEMAGICK, + +]; diff --git a/database/migrations/2020_07_17_053247_add_twofa_secret_to_users.php b/database/migrations/2020_07_17_053247_add_twofa_secret_to_users.php new file mode 100644 index 0000000..b4dd0a9 --- /dev/null +++ b/database/migrations/2020_07_17_053247_add_twofa_secret_to_users.php @@ -0,0 +1,32 @@ +string('twofa_secret')->nullable()->after('password'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('twofa_secret'); + }); + } +} diff --git a/public/css/authpages.css b/public/css/authpages.css deleted file mode 100644 index 3708e9e..0000000 --- a/public/css/authpages.css +++ /dev/null @@ -1,6 +0,0 @@ -/* overrides customizations for the AdminLTE auth pages */ - -.login-page, .register-page { - background-image: url('/img/authbg.jpg') !important; - background-size: cover !important; -} diff --git a/public/css/login.css b/public/css/login.css new file mode 100644 index 0000000..0631eca --- /dev/null +++ b/public/css/login.css @@ -0,0 +1,81 @@ +body { + font-family: "Karla", sans-serif; + background-color: #d0d0ce; + min-height: 100vh; } + +.brand-wrapper { + margin-bottom: 19px; } + .brand-wrapper .logo { + height: 37px; } + +.login-card { + border: 0; + border-radius: 27.5px; + box-shadow: 0 10px 30px 0 rgba(172, 168, 168, 0.43); + overflow: hidden; } + .login-card-img { + border-radius: 0; + position: absolute; + width: 100%; + height: 100%; + -o-object-fit: cover; + object-fit: cover; } + .login-card .card-body { + padding: 85px 60px 60px; } + @media (max-width: 422px) { + .login-card .card-body { + padding: 35px 24px; } } + .login-card-description { + font-size: 25px; + color: #000; + font-weight: normal; + margin-bottom: 23px; } + .login-card form { + max-width: 326px; } + .login-card .form-control { + border: 1px solid #d5dae2; + padding: 15px 25px; + margin-bottom: 20px; + min-height: 45px; + font-size: 13px; + line-height: 15; + font-weight: normal; } + .login-card .form-control::-webkit-input-placeholder { + color: #919aa3; } + .login-card .form-control::-moz-placeholder { + color: #919aa3; } + .login-card .form-control:-ms-input-placeholder { + color: #919aa3; } + .login-card .form-control::-ms-input-placeholder { + color: #919aa3; } + .login-card .form-control::placeholder { + color: #919aa3; } + .login-card .login-btn { + padding: 13px 20px 12px; + background-color: #000; + border-radius: 4px; + font-size: 17px; + font-weight: bold; + line-height: 20px; + color: #fff; + margin-bottom: 24px; } + .login-card .login-btn:hover { + border: 1px solid #000; + background-color: transparent; + color: #000; } + .login-card .forgot-password-link { + font-size: 14px; + color: #919aa3; + margin-bottom: 12px; } + .login-card-footer-text { + font-size: 16px; + color: #0d2366; + margin-bottom: 60px; } + @media (max-width: 767px) { + .login-card-footer-text { + margin-bottom: 24px; } } + .login-card-footer-nav a { + font-size: 14px; + color: #919aa3; } + +/*# sourceMappingURL=login.css.map */ diff --git a/public/css/login.css.map b/public/css/login.css.map new file mode 100755 index 0000000..df5383a --- /dev/null +++ b/public/css/login.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["login.scss"],"names":[],"mappings":"AAAA;EACI,gCAAgC;EAChC,yBAAyB;EACzB,iBAAiB,EAAA;;AAGrB;EACI,mBAAmB,EAAA;EADvB;IAIQ,YAAY,EAAA;;AAIpB;EACI,SAAS;EACT,qBAAqB;EACrB,mDAAmD;EACnD,gBAAgB,EAAA;EAGhB;IACI,gBAAgB;IAChB,kBAAkB;IAClB,WAAW;IACX,YAAY;IACZ,oBAAiB;OAAjB,iBAAiB,EAAA;EAZzB;IAeQ,uBAAuB,EAAA;IAEvB;MAjBR;QAkBY,kBAAkB,EAAA,EAEzB;EAED;IACI,eAAe;IACf,WAAW;IACX,mBAAmB;IACnB,mBAAmB,EAAA;EA1B3B;IA8BQ,gBAAgB,EAAA;EA9BxB;IAkCQ,yBAAyB;IACzB,kBAAkB;IAClB,mBAAmB;IACnB,gBAAgB;IAChB,eAAe;IACf,eAAe;IACf,mBAAmB,EAAA;IAxC3B;MA2CY,cAAc,EAAA;IA3C1B;MA2CY,cAAc,EAAA;IA3C1B;MA2CY,cAAc,EAAA;IA3C1B;MA2CY,cAAc,EAAA;IA3C1B;MA2CY,cAAc,EAAA;EA3C1B;IAgDQ,uBAAuB;IACvB,sBAAsB;IACtB,kBAAkB;IAClB,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,WAAW;IACX,mBAAmB,EAAA;IAvD3B;MA0DY,sBAAsB;MACtB,6BAA6B;MAC7B,WAAW,EAAA;EA5DvB;IAiEQ,eAAe;IACf,cAAc;IACd,mBAAmB,EAAA;EAGvB;IACI,eAAe;IACf,cAAc;IACd,mBAAmB,EAAA;IAEnB;MALJ;QAMQ,mBAAmB,EAAA,EAE1B;EAEA;IAEO,eAAe;IACf,cAAc,EAAA","file":"login.css","sourcesContent":["body {\n font-family: \"Karla\", sans-serif;\n background-color: #d0d0ce;\n min-height: 100vh;\n}\n\n.brand-wrapper {\n margin-bottom: 19px;\n\n .logo {\n height: 37px;\n }\n}\n\n.login-card {\n border: 0;\n border-radius: 27.5px;\n box-shadow: 0 10px 30px 0 rgba(172, 168, 168, 0.43);\n overflow: hidden;\n\n\n &-img {\n border-radius: 0;\n position: absolute;\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n .card-body {\n padding: 85px 60px 60px;\n\n @media (max-width: 422px) {\n padding: 35px 24px;\n }\n }\n\n &-description {\n font-size: 25px;\n color: #000;\n font-weight: normal;\n margin-bottom: 23px;\n }\n\n form {\n max-width: 326px;\n }\n\n .form-control {\n border: 1px solid #d5dae2;\n padding: 15px 25px;\n margin-bottom: 20px;\n min-height: 45px;\n font-size: 13px;\n line-height: 15;\n font-weight: normal;\n\n &::placeholder {\n color: #919aa3;\n }\n }\n\n .login-btn {\n padding: 13px 20px 12px;\n background-color: #000;\n border-radius: 4px;\n font-size: 17px;\n font-weight: bold;\n line-height: 20px;\n color: #fff;\n margin-bottom: 24px;\n\n &:hover {\n border: 1px solid #000;\n background-color: transparent;\n color: #000;\n }\n }\n\n .forgot-password-link {\n font-size: 14px;\n color: #919aa3;\n margin-bottom: 12px;\n }\n\n &-footer-text {\n font-size: 16px;\n color: #0d2366;\n margin-bottom: 60px;\n\n @media (max-width: 767px) {\n margin-bottom: 24px;\n }\n }\n\n &-footer-nav {\n a {\n font-size: 14px;\n color: #919aa3;\n }\n }\n}\n"]} \ No newline at end of file diff --git a/public/img/login.jpg b/public/img/login.jpg new file mode 100755 index 0000000..7dd901b Binary files /dev/null and b/public/img/login.jpg differ diff --git a/resources/views/auth/2fa.blade.php b/resources/views/auth/2fa.blade.php new file mode 100644 index 0000000..43d515e --- /dev/null +++ b/resources/views/auth/2fa.blade.php @@ -0,0 +1,36 @@ +@extends('breadcrumbs.auth.main') + +@section('authpage') + +
+
+
+
+ +
+
+
+
+ {{ config('adminlte.logo') }} +
+ +
+ @csrf +
+ + +
+ +
+ + +
+
+
+
+
+ +@stop diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 0c40972..85fa936 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -1 +1,44 @@ -@extends('adminlte::auth.login') \ No newline at end of file +@extends('breadcrumbs.auth.main') + +@section('authpage') + +
+
+
+
+ +
+
+
+
+ {{ config('adminlte.logo') }} +
+ +
+ @csrf +
+ + +
+
+ + +
+
+ + +
+ +
+ Forgot password? + + +
+
+
+
+
+@stop diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 8522004..9a00cd2 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -1 +1,53 @@ -@extends('adminlte::auth.register') \ No newline at end of file +@extends('breadcrumbs.auth.main') + +@section('authpage') + +
+
+
+
+ +
+
+
+
+ {{ config('adminlte.logo') }} +
+ +
+ @csrf +
+ + +
+
+ + +
+
+ + +
+ + +
+ +
+ + +
+ + + + +
+
+
+
+
+ +@stop diff --git a/resources/views/auth/verify.blade.php b/resources/views/auth/verify.blade.php index 1a7b0a6..005a7db 100644 --- a/resources/views/auth/verify.blade.php +++ b/resources/views/auth/verify.blade.php @@ -1 +1 @@ -@extends('adminlte::auth.verify') \ No newline at end of file +@extends('adminlte::auth.verify') diff --git a/resources/views/breadcrumbs/app.blade.php b/resources/views/breadcrumbs/app.blade.php index b9cf000..5b1dbf9 100644 --- a/resources/views/breadcrumbs/app.blade.php +++ b/resources/views/breadcrumbs/app.blade.php @@ -18,7 +18,7 @@ @yield('content') - @include('breadcrumbs.footer') + @include('breadcrumbs.footer') diff --git a/resources/views/breadcrumbs/auth/main.blade.php b/resources/views/breadcrumbs/auth/main.blade.php new file mode 100644 index 0000000..0e3ed4b --- /dev/null +++ b/resources/views/breadcrumbs/auth/main.blade.php @@ -0,0 +1,29 @@ + + + + + + + {{ config('app.name') . '| ID' }} + + + + + + + + + + +
+ @yield('authpage') +
+ + + + + + + + + diff --git a/resources/views/dashboard/user/profile/useraccount.blade.php b/resources/views/dashboard/user/profile/useraccount.blade.php index ebfd9e9..d9fd90c 100644 --- a/resources/views/dashboard/user/profile/useraccount.blade.php +++ b/resources/views/dashboard/user/profile/useraccount.blade.php @@ -19,6 +19,92 @@ @stop @section('content') + + @if (!Auth::user()->has2FA()) + + + + +

We're glad you decided to increase your account's security!

+ +

Supported apps you can install:

+ + +

Scan the QR code below with your preferred app, and then copy the code here.

+ + +
+
+
+ + 2FA Security key + +
+
+
+ +
+ +
+ +
+ @csrf + @method('PATCH') + + + +
+ +
+ +
+ + + + + + + + + +
+ + @endif + + @if (Auth::user()->has2FA()) + + + +

Are you sure? Removing two-factor authentication will reduce the security of your account.

+ +
+ @csrf + @method('PATCH') + + +

To prevent unauthorized changes, a password is always required for sensitive operations.

+ +
+ + + Click to confirm + +
+ +
+ + + + + + + +
+ + @endif +