diff --git a/.junie/guidelines.md b/.junie/guidelines.md
index e29420d..b454596 100644
--- a/.junie/guidelines.md
+++ b/.junie/guidelines.md
@@ -9,6 +9,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.2.29
+- laravel/fortify (FORTIFY) - v1
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- laravel/mcp (MCP) - v0
@@ -16,6 +17,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- laravel/sail (SAIL) - v1
- pestphp/pest (PEST) - v3
- phpunit/phpunit (PHPUNIT) - v11
+- tailwindcss (TAILWINDCSS) - v4
## Conventions
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming.
@@ -235,4 +237,71 @@ it('has emails', function (string $email) {
'taylor' => 'taylor@laravel.com',
]);
+
+
+=== tailwindcss/core rules ===
+
+## Tailwind Core
+
+- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own.
+- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..)
+- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically
+- You can use the `search-docs` tool to get exact examples from the official documentation when needed.
+
+### Spacing
+- When listing items, use gap utilities for spacing, don't use margins.
+
+
+
+
Superior
+
Michigan
+
Erie
+
+
+
+
+### Dark Mode
+- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`.
+
+
+=== tailwindcss/v4 rules ===
+
+## Tailwind 4
+
+- Always use Tailwind CSS v4 - do not use the deprecated utilities.
+- `corePlugins` is not supported in Tailwind v4.
+- In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed.
+
+@theme {
+ --color-brand: oklch(0.72 0.11 178);
+}
+
+
+- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
+
+
+ - @tailwind base;
+ - @tailwind components;
+ - @tailwind utilities;
+ + @import "tailwindcss";
+
+
+
+### Replaced Utilities
+- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement.
+- Opacity values are still numeric.
+
+| Deprecated | Replacement |
+|------------+--------------|
+| bg-opacity-* | bg-black/* |
+| text-opacity-* | text-black/* |
+| border-opacity-* | border-black/* |
+| divide-opacity-* | divide-black/* |
+| ring-opacity-* | ring-black/* |
+| placeholder-opacity-* | placeholder-black/* |
+| flex-shrink-* | shrink-* |
+| flex-grow-* | grow-* |
+| overflow-ellipsis | text-ellipsis |
+| decoration-slice | box-decoration-slice |
+| decoration-clone | box-decoration-clone |
diff --git a/CLAUDE.md b/CLAUDE.md
index e29420d..b454596 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -9,6 +9,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.2.29
+- laravel/fortify (FORTIFY) - v1
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- laravel/mcp (MCP) - v0
@@ -16,6 +17,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- laravel/sail (SAIL) - v1
- pestphp/pest (PEST) - v3
- phpunit/phpunit (PHPUNIT) - v11
+- tailwindcss (TAILWINDCSS) - v4
## Conventions
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming.
@@ -235,4 +237,71 @@ it('has emails', function (string $email) {
'taylor' => 'taylor@laravel.com',
]);
+
+
+=== tailwindcss/core rules ===
+
+## Tailwind Core
+
+- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own.
+- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..)
+- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically
+- You can use the `search-docs` tool to get exact examples from the official documentation when needed.
+
+### Spacing
+- When listing items, use gap utilities for spacing, don't use margins.
+
+
+
+
Superior
+
Michigan
+
Erie
+
+
+
+
+### Dark Mode
+- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`.
+
+
+=== tailwindcss/v4 rules ===
+
+## Tailwind 4
+
+- Always use Tailwind CSS v4 - do not use the deprecated utilities.
+- `corePlugins` is not supported in Tailwind v4.
+- In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed.
+
+@theme {
+ --color-brand: oklch(0.72 0.11 178);
+}
+
+
+- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
+
+
+ - @tailwind base;
+ - @tailwind components;
+ - @tailwind utilities;
+ + @import "tailwindcss";
+
+
+
+### Replaced Utilities
+- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement.
+- Opacity values are still numeric.
+
+| Deprecated | Replacement |
+|------------+--------------|
+| bg-opacity-* | bg-black/* |
+| text-opacity-* | text-black/* |
+| border-opacity-* | border-black/* |
+| divide-opacity-* | divide-black/* |
+| ring-opacity-* | ring-black/* |
+| placeholder-opacity-* | placeholder-black/* |
+| flex-shrink-* | shrink-* |
+| flex-grow-* | grow-* |
+| overflow-ellipsis | text-ellipsis |
+| decoration-slice | box-decoration-slice |
+| decoration-clone | box-decoration-clone |
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/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php
new file mode 100644
index 0000000..004ced4
--- /dev/null
+++ b/app/Providers/FortifyServiceProvider.php
@@ -0,0 +1,48 @@
+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/bootstrap/providers.php b/bootstrap/providers.php
index 38b258d..0ad9c57 100644
--- a/bootstrap/providers.php
+++ b/bootstrap/providers.php
@@ -2,4 +2,5 @@
return [
App\Providers\AppServiceProvider::class,
+ App\Providers\FortifyServiceProvider::class,
];
diff --git a/composer.json b/composer.json
index a932e16..3610234 100644
--- a/composer.json
+++ b/composer.json
@@ -10,6 +10,7 @@
"license": "MIT",
"require": {
"php": "^8.2",
+ "laravel/fortify": "^1.32",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1"
},
@@ -92,4 +93,4 @@
},
"minimum-stability": "stable",
"prefer-stable": true
-}
\ No newline at end of file
+}
diff --git a/composer.lock b/composer.lock
index f35c29d..dc9def9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,63 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "8231b83ddd7663aeffa6e25b0d5255c0",
+ "content-hash": "50a06c71ff0a1192048a04a55237300f",
"packages": [
+ {
+ "name": "bacon/bacon-qr-code",
+ "version": "v3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Bacon/BaconQrCode.git",
+ "reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/36a1cb2b81493fa5b82e50bf8068bf84d1542563",
+ "reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563",
+ "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",
+ "spatie/pixelmatch-php": "^1.2.0",
+ "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.3"
+ },
+ "time": "2025-11-19T17:15:36+00:00"
+ },
{
"name": "brick/math",
"version": "0.14.1",
@@ -135,6 +190,56 @@
],
"time": "2024-02-09T16:56:22+00:00"
},
+ {
+ "name": "dasprid/enum",
+ "version": "1.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/DASPRiD/Enum.git",
+ "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
+ "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
+ "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.7"
+ },
+ "time": "2025-09-16T12:23:56+00:00"
+ },
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
@@ -1052,6 +1157,69 @@
],
"time": "2025-08-22T14:27:06+00:00"
},
+ {
+ "name": "laravel/fortify",
+ "version": "v1.32.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/fortify.git",
+ "reference": "26db37ed915770e5f0f91f6b7e153d9b3e6c09e8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/fortify/zipball/26db37ed915770e5f0f91f6b7e153d9b3e6c09e8",
+ "reference": "26db37ed915770e5f0f91f6b7e153d9b3e6c09e8",
+ "shasum": ""
+ },
+ "require": {
+ "bacon/bacon-qr-code": "^3.0",
+ "ext-json": "*",
+ "illuminate/support": "^10.0|^11.0|^12.0",
+ "php": "^8.1",
+ "pragmarx/google2fa": "^9.0",
+ "symfony/console": "^6.0|^7.0"
+ },
+ "require-dev": {
+ "orchestra/testbench": "^8.36|^9.15|^10.8",
+ "phpstan/phpstan": "^1.10"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Laravel\\Fortify\\FortifyServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "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": "2025-11-21T01:53:28+00:00"
+ },
{
"name": "laravel/framework",
"version": "v12.42.0",
@@ -2526,6 +2694,75 @@
],
"time": "2025-11-20T02:34:59+00:00"
},
+ {
+ "name": "paragonie/constant_time_encoding",
+ "version": "v3.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/constant_time_encoding.git",
+ "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
+ "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8"
+ },
+ "require-dev": {
+ "infection/infection": "^0",
+ "nikic/php-fuzzer": "^0",
+ "phpunit/phpunit": "^9|^10|^11",
+ "vimeo/psalm": "^4|^5|^6"
+ },
+ "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": "2025-09-24T15:06:41+00:00"
+ },
{
"name": "phpoption/phpoption",
"version": "1.9.4",
@@ -2601,6 +2838,58 @@
],
"time": "2025-08-21T11:53:16+00:00"
},
+ {
+ "name": "pragmarx/google2fa",
+ "version": "v9.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antonioribeiro/google2fa.git",
+ "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/e6bc62dd6ae83acc475f57912e27466019a1f2cf",
+ "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf",
+ "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/v9.0.0"
+ },
+ "time": "2025-09-19T22:51:08+00:00"
+ },
{
"name": "psr/clock",
"version": "1.0.0",
diff --git a/config/fortify.php b/config/fortify.php
new file mode 100644
index 0000000..cfe8272
--- /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' => '/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/2025_12_13_222736_add_two_factor_columns_to_users_table.php b/database/migrations/2025_12_13_222736_add_two_factor_columns_to_users_table.php
new file mode 100644
index 0000000..45739ef
--- /dev/null
+++ b/database/migrations/2025_12_13_222736_add_two_factor_columns_to_users_table.php
@@ -0,0 +1,42 @@
+text('two_factor_secret')
+ ->after('password')
+ ->nullable();
+
+ $table->text('two_factor_recovery_codes')
+ ->after('two_factor_secret')
+ ->nullable();
+
+ $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([
+ 'two_factor_secret',
+ 'two_factor_recovery_codes',
+ 'two_factor_confirmed_at',
+ ]);
+ });
+ }
+};