diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php new file mode 100644 index 0000000..7bf18d0 --- /dev/null +++ b/app/Actions/Fortify/CreateNewUser.php @@ -0,0 +1,40 @@ + $input + */ + public function create(array $input): User + { + Validator::make($input, [ + 'name' => ['required', 'string', 'max:255'], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + Rule::unique(User::class), + ], + 'password' => $this->passwordRules(), + ])->validate(); + + return User::create([ + 'name' => $input['name'], + 'email' => $input['email'], + 'password' => Hash::make($input['password']), + ]); + } +} diff --git a/app/Actions/Fortify/PasswordValidationRules.php b/app/Actions/Fortify/PasswordValidationRules.php new file mode 100644 index 0000000..76b19d3 --- /dev/null +++ b/app/Actions/Fortify/PasswordValidationRules.php @@ -0,0 +1,18 @@ +|string> + */ + protected function passwordRules(): array + { + return ['required', 'string', Password::default(), 'confirmed']; + } +} diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php new file mode 100644 index 0000000..7a57c50 --- /dev/null +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -0,0 +1,29 @@ + $input + */ + public function reset(User $user, array $input): void + { + Validator::make($input, [ + 'password' => $this->passwordRules(), + ])->validate(); + + $user->forceFill([ + 'password' => Hash::make($input['password']), + ])->save(); + } +} diff --git a/app/Actions/Fortify/UpdateUserPassword.php b/app/Actions/Fortify/UpdateUserPassword.php new file mode 100644 index 0000000..7005639 --- /dev/null +++ b/app/Actions/Fortify/UpdateUserPassword.php @@ -0,0 +1,32 @@ + $input + */ + public function update(User $user, array $input): void + { + Validator::make($input, [ + 'current_password' => ['required', 'string', 'current_password:web'], + 'password' => $this->passwordRules(), + ], [ + 'current_password.current_password' => __('The provided password does not match your current password.'), + ])->validateWithBag('updatePassword'); + + $user->forceFill([ + 'password' => Hash::make($input['password']), + ])->save(); + } +} diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php new file mode 100644 index 0000000..0930ddf --- /dev/null +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -0,0 +1,58 @@ + $input + */ + public function update(User $user, array $input): void + { + Validator::make($input, [ + 'name' => ['required', 'string', 'max:255'], + + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + Rule::unique('users')->ignore($user->id), + ], + ])->validateWithBag('updateProfileInformation'); + + if ($input['email'] !== $user->email && + $user instanceof MustVerifyEmail) { + $this->updateVerifiedUser($user, $input); + } else { + $user->forceFill([ + 'name' => $input['name'], + 'email' => $input['email'], + ])->save(); + } + } + + /** + * Update the given verified user's profile information. + * + * @param array $input + */ + protected function updateVerifiedUser(User $user, array $input): void + { + $user->forceFill([ + 'name' => $input['name'], + 'email' => $input['email'], + 'email_verified_at' => null, + ])->save(); + + $user->sendEmailVerificationNotification(); + } +} diff --git a/app/Http/Controllers/MediaController.php b/app/Http/Controllers/MediaController.php index b9e206a..f310165 100644 --- a/app/Http/Controllers/MediaController.php +++ b/app/Http/Controllers/MediaController.php @@ -24,7 +24,7 @@ class MediaController extends Controller{ * @return void */ public function __construct(){ - parent::__construct(); + //parent::__construct(); $this->rulesUpdate = $this->rulesInsert; unset($this->rulesUpdate['originalUrl']); } @@ -522,4 +522,4 @@ private function getMimeType($url){ } return $mimeType; } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/TaxonomyController.php b/app/Http/Controllers/TaxonomyController.php index 08e29a9..f314437 100644 --- a/app/Http/Controllers/TaxonomyController.php +++ b/app/Http/Controllers/TaxonomyController.php @@ -13,7 +13,7 @@ class TaxonomyController extends Controller{ * @return void */ public function __construct(){ - parent::__construct(); + //parent::__construct(); } /** @@ -282,4 +282,4 @@ public function showAllDescriptions($id, Request $request){ return response()->json($retObj); } -} \ No newline at end of file +} diff --git a/app/Models/User.php b/app/Models/User.php index 7e70d72..a5352be 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,10 +6,11 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Fortify\TwoFactorAuthenticatable; use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { - use HasApiTokens, Notifiable, HasFactory; + use HasApiTokens, TwoFactorAuthenticatable, Notifiable, HasFactory; protected $primaryKey = 'uid'; const CREATED_AT = 'initialTimestamp'; diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php new file mode 100644 index 0000000..e146a00 --- /dev/null +++ b/app/Providers/FortifyServiceProvider.php @@ -0,0 +1,153 @@ +app->instance(LoginResponse::class, new class implements LoginResponse { + public function toResponse($request) { + return response(view('pages/home')) + ->header('HX-Replace-URL', config('fortify.home')) + ->header('HX-Retarget', 'body') + ->header('HX-Boosted', 'true'); + } + }); + + $this->app->instance(RegisterResponse::class, new class implements RegisterResponse { + public function toResponse($request) { + return response(view('pages/home')) + ->header('HX-Replace-URL', config('fortify.home')) + ->header('HX-Retarget', 'body') + ->header('HX-Boosted', 'true'); + } + }); + + $this->app->instance(TwoFactorDisabledResponse::class, new class implements TwoFactorDisabledResponse { + public function toResponse($request) { + return response(view('pages/user/profile')) + ->header('HX-Retarget', 'body') + ->header('HX-Boosted', 'true'); + } + }); + + $this->app->instance(PasswordConfirmedResponse::class, new class implements PasswordConfirmedResponse { + public function toResponse($request) { + return response(view('/pages/user/profile')) + ->header('HX-Retarget', 'body') + ->header('HX-Request', 'true'); + } + }); + + $this->app->instance(FailedPasswordConfirmationResponse::class, new class implements FailedPasswordConfirmationResponse { + public function toResponse($request) { + + $message = __('The provided password was incorrect.'); + + return response(view('/pages/auth/confirm-password', + [ 'errors' => new MessageBag([$message]) ] + )) + ->header('HX-Retarget', 'body') + ->header('HX-Request', 'true'); + } + }); + } + + /** + * Bootstrap any application services. + */ + public function boot(): void { + + Fortify::loginView(function (Request $request) { + //If Request Redirect on to self then only send fragment. This is for htmx to do the correct swap + // + // If its on current page ignore target + if($request->headers->get('hx-request') && !$request->headers->get('hx-target')) { + return view('pages/login')->fragment('form'); + } + + return response(view('pages/login'))->header('HX-Replace-URL', '/login'); + }); + + Fortify::registerView(function (Request $request) { + //If Request Redirect on to self then only send fragment. This is for htmx to do the correct swap + if($request->headers->get('hx-request') && !$request->headers->get('hx-target')) { + return view('pages/signup')->fragment('form'); + } + + return response(view('pages/signup'))->header('HX-Replace-URL', '/register'); + }); + + Fortify::confirmPasswordView(function () { + return response(view('pages/auth/confirm-password')) + //->header('HX-Replace-URL', '/auth/confirm-password') + ->header('HX-Retarget', 'body') + ->header('HX-Request', 'true'); + }); + + Fortify::twoFactorChallengeView(function () { + return response(view('pages/auth/two-factor-challenge')) + ->header('HX-Request', 'true') + ->header('HX-Replace-URL', '/two-factor-challenge') + ->header('HX-Boosted', 'true') + ->header('HX-Retarget', 'body'); + }); + + Fortify::authenticateUsing(function (Request $request) { + $user = User::where('email', $request->email)->first(); + + if ($user && + Hash::check($request->password, $user->password)) { + return $user; + } + + /* + //Check Old Password + $result = DB::select(' + SELECT uid + FROM users + WHERE (old_password = CONCAT(\'*\', UPPER(SHA1(UNHEX(SHA1(?)))))) AND + email = ?', + [$request->password, $request->email] + ); + */ + + }); + + Fortify::createUsersUsing(CreateNewUser::class); + Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class); + Fortify::updateUserPasswordsUsing(UpdateUserPassword::class); + Fortify::resetUserPasswordsUsing(ResetUserPassword::class); + + RateLimiter::for('login', function (Request $request) { + $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip()); + + return Limit::perMinute(5)->by($throttleKey); + }); + + RateLimiter::for('two-factor', function (Request $request) { + return Limit::perMinute(5)->by($request->session()->get('login.id')); + }); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 1cf5f15..680b1a5 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -8,8 +8,7 @@ use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\Route; -class RouteServiceProvider extends ServiceProvider -{ +class RouteServiceProvider extends ServiceProvider { /** * The path to your application's "home" route. * @@ -17,7 +16,7 @@ class RouteServiceProvider extends ServiceProvider * * @var string */ - public const HOME = '/home'; + public const HOME = '/'; /** * Define your route model bindings, pattern filters, and other route configuration. diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore old mode 100644 new mode 100755 diff --git a/composer.json b/composer.json index 5e90c72..0ac600c 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "darkaonline/l5-swagger": "^8.6", "gehrisandro/tailwind-merge-laravel": "^1.2", "guzzlehttp/guzzle": "^7.2", + "laravel/fortify": "^1.24", "laravel/framework": "^11.0", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.8", diff --git a/composer.lock b/composer.lock index d7265d8..c934691 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,62 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d888ad52f663e9b7b68e4c602044b10a", + "content-hash": "e9989a88d1989d80734a40c410afe34a", "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f9cc1f52b5a463062251d666761178dbdb6b544f", + "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^8.1" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.12", + "phpunit/phpunit": "^10.5.11 || 11.0.4", + "spatie/phpunit-snapshot-assertions": "^5.1.5", + "squizlabs/php_codesniffer": "^3.9" + }, + "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": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.1" + }, + "time": "2024-10-01T13:55:55+00:00" + }, { "name": "brick/math", "version": "0.12.1", @@ -215,6 +269,56 @@ ], "time": "2024-10-28T06:29:43+00:00" }, + { + "name": "dasprid/enum", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90", + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "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/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.6" + }, + "time": "2024-08-09T14:30:48+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -1402,6 +1506,71 @@ ], "time": "2023-12-03T19:50:20+00:00" }, + { + "name": "laravel/fortify", + "version": "v1.24.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/fortify.git", + "reference": "bba8c2ecc3fcc78e8632e0d719ae10bef6343eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/fortify/zipball/bba8c2ecc3fcc78e8632e0d719ae10bef6343eef", + "reference": "bba8c2ecc3fcc78e8632e0d719ae10bef6343eef", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^3.0", + "ext-json": "*", + "illuminate/support": "^10.0|^11.0", + "php": "^8.1", + "pragmarx/google2fa": "^8.0", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^8.16|^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Fortify\\FortifyServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Fortify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Backend controllers and scaffolding for Laravel authentication.", + "keywords": [ + "auth", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/fortify/issues", + "source": "https://github.com/laravel/fortify" + }, + "time": "2024-11-12T14:51:12+00:00" + }, { "name": "laravel/framework", "version": "v11.32.0", @@ -2739,6 +2908,73 @@ ], "time": "2024-10-15T16:15:16+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" + }, + "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" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2024-05-08T12:36:18+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", @@ -2814,6 +3050,58 @@ ], "time": "2024-07-20T21:41:07+00:00" }, + { + "name": "pragmarx/google2fa", + "version": "v8.0.3", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.9", + "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" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa/issues", + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.3" + }, + "time": "2024-09-05T11:56:40+00:00" + }, { "name": "psr/cache", "version": "3.0.0", diff --git a/config/app.php b/config/app.php index 0cbd8e2..cb56df4 100644 --- a/config/app.php +++ b/config/app.php @@ -169,6 +169,7 @@ // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Providers\FortifyServiceProvider::class, L5Swagger\L5SwaggerServiceProvider::class, ])->toArray(), diff --git a/config/fortify.php b/config/fortify.php new file mode 100644 index 0000000..2385239 --- /dev/null +++ b/config/fortify.php @@ -0,0 +1,159 @@ + 'web', + + /* + |-------------------------------------------------------------------------- + | Fortify Password Broker + |-------------------------------------------------------------------------- + | + | Here you may specify which password broker Fortify can use when a user + | is resetting their password. This configured value should match one + | of your password brokers setup in your "auth" configuration file. + | + */ + + 'passwords' => 'users', + + /* + |-------------------------------------------------------------------------- + | Username / Email + |-------------------------------------------------------------------------- + | + | This value defines which model attribute should be considered as your + | application's "username" field. Typically, this might be the email + | address of the users but you are free to change this value here. + | + | Out of the box, Fortify expects forgot password and reset password + | requests to have a field named 'email'. If the application uses + | another name for the field you may define it below as needed. + | + */ + + 'username' => 'email', + + 'email' => 'email', + + /* + |-------------------------------------------------------------------------- + | Lowercase Usernames + |-------------------------------------------------------------------------- + | + | This value defines whether usernames should be lowercased before saving + | them in the database, as some database system string fields are case + | sensitive. You may disable this for your application if necessary. + | + */ + + 'lowercase_usernames' => true, + + /* + |-------------------------------------------------------------------------- + | Home Path + |-------------------------------------------------------------------------- + | + | Here you may configure the path where users will get redirected during + | authentication or password reset when the operations are successful + | and the user is authenticated. You are free to change this value. + | + */ + + 'home' => '/', + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Prefix / Subdomain + |-------------------------------------------------------------------------- + | + | Here you may specify which prefix Fortify will assign to all the routes + | that it registers with the application. If necessary, you may change + | subdomain under which all of the Fortify routes will be available. + | + */ + + 'prefix' => '', + + 'domain' => null, + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Middleware + |-------------------------------------------------------------------------- + | + | Here you may specify which middleware Fortify will assign to the routes + | that it registers with the application. If necessary, you may change + | these middleware but typically this provided default is preferred. + | + */ + + 'middleware' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Rate Limiting + |-------------------------------------------------------------------------- + | + | By default, Fortify will throttle logins to five requests per minute for + | every email and IP address combination. However, if you would like to + | specify a custom rate limiter to call then you may specify it here. + | + */ + + 'limiters' => [ + 'login' => 'login', + 'two-factor' => 'two-factor', + ], + + /* + |-------------------------------------------------------------------------- + | Register View Routes + |-------------------------------------------------------------------------- + | + | Here you may specify if the routes returning views should be disabled as + | you may not need them when building your own application. This may be + | especially true if you're writing a custom single-page application. + | + */ + + 'views' => true, + + /* + |-------------------------------------------------------------------------- + | Features + |-------------------------------------------------------------------------- + | + | Some of the Fortify features are optional. You may disable the features + | by removing them from this array. You're free to only remove some of + | these features or you can even remove all of these if you need to. + | + */ + + 'features' => [ + Features::registration(), + Features::resetPasswords(), + // Features::emailVerification(), + Features::updateProfileInformation(), + Features::updatePasswords(), + Features::twoFactorAuthentication([ + 'confirm' => true, + 'confirmPassword' => true, + // 'window' => 0, + ]), + ], + +]; diff --git a/database/migrations/2024_09_24_194122_create_users_table_add_two_factor_columns.php b/database/migrations/2024_09_24_194122_create_users_table_add_two_factor_columns.php new file mode 100644 index 0000000..b490e24 --- /dev/null +++ b/database/migrations/2024_09_24_194122_create_users_table_add_two_factor_columns.php @@ -0,0 +1,46 @@ +text('two_factor_secret') + ->after('password') + ->nullable(); + + $table->text('two_factor_recovery_codes') + ->after('two_factor_secret') + ->nullable(); + + if (Fortify::confirmsTwoFactorAuthentication()) { + $table->timestamp('two_factor_confirmed_at') + ->after('two_factor_recovery_codes') + ->nullable(); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(array_merge([ + 'two_factor_secret', + 'two_factor_recovery_codes', + ], Fortify::confirmsTwoFactorAuthentication() ? [ + 'two_factor_confirmed_at', + ] : [])); + }); + } +}; diff --git a/database/seeders/SampleSeeder.php b/database/seeders/SampleSeeder.php new file mode 100644 index 0000000..3616b87 --- /dev/null +++ b/database/seeders/SampleSeeder.php @@ -0,0 +1,26 @@ +create(); + $collection = Collection::factory()->create([ + 'iid' => $inst->iid, + 'institutionCode' => $inst->InstitutionCode, + ]); + + $occurrence = Occurrence::factory(10000)->create([ + 'collid' => $collection->collid, + ]); + } +} diff --git a/resources/js/app.js b/resources/js/app.js index 44fb234..310b475 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -5,6 +5,7 @@ import 'htmx.org'; window.Alpine = Alpine; Alpine.plugin(focus); + queueMicrotask(() => { Alpine.start() }); diff --git a/resources/views/core/header.blade.php b/resources/views/core/header.blade.php index 091d1b9..a0ab55f 100644 --- a/resources/views/core/header.blade.php +++ b/resources/views/core/header.blade.php @@ -20,9 +20,15 @@ {{ Auth::user()->name }}! + {{-- My Profile + --}} + + + My Profile + @@ -36,7 +42,7 @@ - + {!! __("header.sign_in") !!} diff --git a/resources/views/core/layout.blade.php b/resources/views/core/layout.blade.php index 13fc46b..3544028 100644 --- a/resources/views/core/layout.blade.php +++ b/resources/views/core/layout.blade.php @@ -1,11 +1,11 @@ @php $navigations = [ - ["title" => __("header.home"), "link" => '/'], + ["title" => __("header.home"), "link" => '/', "htmx" => true], ["title" => __("header.collections"), "link" => config('portal.name') . '/collections/search/index.php'], ["title" => __("header.map_search"), "link" => config('portal.name') . '/collections/map/index.php'], ["title" => __("header.species_checklists"), "link" => config('portal.name') . '/checklists/index.php'], - ["title" => __("header.images"), "link" => '/media/search'], - ["title" => __("header.data_use"), "link" => '/usagepolicy'], + ["title" => __("header.images"), "link" => '/media/search', 'htmx' => true], + ["title" => __("header.data_use"), "link" => '/usagepolicy', 'htmx' => true], ["title" => __("header.symbiota_help"), "link" => 'https://biokic.github.io/symbiota-docs/'], ["title" => __("header.sitemap"), "link" => config('portal.name') . '/sitemap.php'], ]; diff --git a/resources/views/core/navbar.blade.php b/resources/views/core/navbar.blade.php index a519345..2f01835 100644 --- a/resources/views/core/navbar.blade.php +++ b/resources/views/core/navbar.blade.php @@ -4,7 +4,7 @@