From e47d49377669b53f65b9ddfa85d1810b45856cbe Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 24 Mar 2023 14:54:17 +0100 Subject: [PATCH] Add users, teams, authentication, profile/login/register/navbar views --- app/Actions/Fortify/CreateNewUser.php | 40 ++ .../Fortify/PasswordValidationRules.php | 18 + app/Actions/Fortify/ResetUserPassword.php | 29 ++ app/Actions/Fortify/UpdateUserPassword.php | 32 ++ .../Fortify/UpdateUserProfileInformation.php | 58 +++ app/Http/Livewire/SwitchTeam.php | 26 ++ app/Models/BaseModel.php | 18 + app/Models/Team.php | 13 + app/Models/User.php | 27 +- app/Providers/FortifyServiceProvider.php | 60 +++ app/Providers/RouteServiceProvider.php | 2 +- composer.json | 4 +- composer.lock | 344 +++++++++++++++++- config/app.php | 1 + config/fortify.php | 147 ++++++++ .../2014_10_12_000000_create_users_table.php | 2 + ..._add_two_factor_columns_to_users_table.php | 46 +++ ...01_create_personal_access_tokens_table.php | 1 + .../2023_03_20_112811_create_teams_table.php | 30 ++ ...23_03_20_112812_create_team_user_table.php | 32 ++ database/seeders/DatabaseSeeder.php | 16 +- database/seeders/TeamSeeder.php | 45 +++ database/seeders/UserSeeder.php | 26 ++ resources/views/auth/login.blade.php | 20 + resources/views/auth/register.blade.php | 20 + resources/views/components/button.blade.php | 3 - resources/views/components/layout.blade.php | 10 +- resources/views/components/navbar.blade.php | 28 +- resources/views/demo.blade.php | 2 - resources/views/home.blade.php | 10 + .../views/livewire/run-command.blade.php | 6 +- .../views/livewire/switch-team.blade.php | 6 + resources/views/profile.blade.php | 17 + resources/views/welcome.blade.php | 22 -- routes/web.php | 11 +- run => scripts/run | 0 36 files changed, 1106 insertions(+), 66 deletions(-) create mode 100644 app/Actions/Fortify/CreateNewUser.php create mode 100644 app/Actions/Fortify/PasswordValidationRules.php create mode 100644 app/Actions/Fortify/ResetUserPassword.php create mode 100644 app/Actions/Fortify/UpdateUserPassword.php create mode 100644 app/Actions/Fortify/UpdateUserProfileInformation.php create mode 100644 app/Http/Livewire/SwitchTeam.php create mode 100644 app/Models/BaseModel.php create mode 100644 app/Models/Team.php create mode 100644 app/Providers/FortifyServiceProvider.php create mode 100644 config/fortify.php create mode 100644 database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php create mode 100644 database/migrations/2023_03_20_112811_create_teams_table.php create mode 100644 database/migrations/2023_03_20_112812_create_team_user_table.php create mode 100644 database/seeders/TeamSeeder.php create mode 100644 database/seeders/UserSeeder.php create mode 100644 resources/views/auth/login.blade.php create mode 100644 resources/views/auth/register.blade.php delete mode 100644 resources/views/components/button.blade.php create mode 100644 resources/views/home.blade.php create mode 100644 resources/views/livewire/switch-team.blade.php create mode 100644 resources/views/profile.blade.php delete mode 100644 resources/views/welcome.blade.php rename run => scripts/run (100%) diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php new file mode 100644 index 000000000..7bf18d0a4 --- /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 000000000..92fcc7532 --- /dev/null +++ b/app/Actions/Fortify/PasswordValidationRules.php @@ -0,0 +1,18 @@ + + */ + protected function passwordRules(): array + { + return ['required', 'string', new Password, 'confirmed']; + } +} diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php new file mode 100644 index 000000000..7a57c5037 --- /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 000000000..700563905 --- /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 000000000..0930ddf38 --- /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/Livewire/SwitchTeam.php b/app/Http/Livewire/SwitchTeam.php new file mode 100644 index 000000000..edb68852a --- /dev/null +++ b/app/Http/Livewire/SwitchTeam.php @@ -0,0 +1,26 @@ +user()->teams->contains($team_id)) { + return; + } + $team_to_switch_to = Team::find($team_id); + if (!$team_to_switch_to) { + return; + } + session(['currentTeam' => $team_to_switch_to]); + return redirect(request()->header('Referer')); + } + public function render() + { + return view('livewire.switch-team'); + } +} diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php new file mode 100644 index 000000000..22cbfc085 --- /dev/null +++ b/app/Models/BaseModel.php @@ -0,0 +1,18 @@ +uuid = (string) new Cuid2(); + }); + } +} \ No newline at end of file diff --git a/app/Models/Team.php b/app/Models/Team.php new file mode 100644 index 000000000..90d4d73d9 --- /dev/null +++ b/app/Models/Team.php @@ -0,0 +1,13 @@ + 'boolean', + ]; + protected $fillable = [ + 'name', + ]; +} diff --git a/app/Models/User.php b/app/Models/User.php index 23b406346..1c958190a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,11 +2,12 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; +use Xaevik\Cuid2\Cuid2; class User extends Authenticatable { @@ -41,4 +42,28 @@ class User extends Authenticatable protected $casts = [ 'email_verified_at' => 'datetime', ]; + + protected static function boot() + { + parent::boot(); + + static::creating(function (Model $model) { + $model->uuid = (string) new Cuid2(); + }); + } + public function teams() + { + return $this->belongsToMany(Team::class); + } + public function currentTeam() + { + return $this->belongsTo(Team::class); + } + public function otherTeams() + { + $team_id = session('currentTeam')->id; + return auth()->user()->teams->filter(function ($team) use ($team_id) { + return $team->id != $team_id; + }); + } } diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php new file mode 100644 index 000000000..f2f8ec1d7 --- /dev/null +++ b/app/Providers/FortifyServiceProvider.php @@ -0,0 +1,60 @@ + view('auth.login')); + Fortify::registerView(fn () => view('auth.register')); + Fortify::authenticateUsing(function (Request $request) { + $user = User::where('email', $request->email)->with('teams')->first(); + if ( + $user && + Hash::check($request->password, $user->password) + ) { + session(['currentTeam' => $user->currentTeam = $user->teams->firstWhere('personal_team', true)]); + return $user; + } + }); + Fortify::createUsersUsing(CreateNewUser::class); + Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class); + Fortify::updateUserPasswordsUsing(UpdateUserPassword::class); + Fortify::resetUserPasswordsUsing(ResetUserPassword::class); + + RateLimiter::for('login', function (Request $request) { + $email = (string) $request->email; + + return Limit::perMinute(5)->by($email.$request->ip()); + }); + + 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 bc4910996..4c26a1946 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -17,7 +17,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/composer.json b/composer.json index 207edd878..c6447fbeb 100644 --- a/composer.json +++ b/composer.json @@ -7,13 +7,15 @@ "require": { "php": "^8.2", "guzzlehttp/guzzle": "^7.2", + "laravel/fortify": "^1.16", "laravel/framework": "^10.0", "laravel/sanctum": "^3.2", "laravel/tinker": "^2.8", "livewire/livewire": "^2.12", "spatie/laravel-activitylog": "^4.7", "spatie/laravel-data": "^3.2", - "spatie/laravel-ray": "^1.32" + "spatie/laravel-ray": "^1.32", + "xaevik/cuid2": "^1.7" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index ad3d8bd5e..818e9721b 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": "c6bfcd94dc256f9124e84a380eaaf556", + "content-hash": "0860841db6def79a62c268b0858d1f8a", "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "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/2.0.8" + }, + "time": "2022-12-07T17:46:57+00:00" + }, { "name": "brick/math", "version": "0.10.2", @@ -62,6 +116,56 @@ ], "time": "2022-08-10T22:54:19+00:00" }, + { + "name": "dasprid/enum", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", + "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "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.4" + }, + "time": "2023-03-01T18:44:03+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.2", @@ -1024,6 +1128,69 @@ ], "time": "2021-10-07T12:57:01+00:00" }, + { + "name": "laravel/fortify", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/fortify.git", + "reference": "e626fc70fcd940d01326c6c44512398cccc3113c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/fortify/zipball/e626fc70fcd940d01326c6c44512398cccc3113c", + "reference": "e626fc70fcd940d01326c6c44512398cccc3113c", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^2.0", + "ext-json": "*", + "illuminate/support": "^8.82|^9.0|^10.0", + "php": "^7.3|^8.0", + "pragmarx/google2fa": "^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^9.3" + }, + "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": "2023-01-06T15:57:08+00:00" + }, { "name": "laravel/framework", "version": "v10.3.3", @@ -2319,6 +2486,73 @@ ], "time": "2023-02-08T01:06:31+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "58c3f47f650c94ec05a151692652a868995d2938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "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": "2022-06-14T06:56:20+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -2603,6 +2837,58 @@ }, "time": "2021-10-28T11:13:42+00:00" }, + { + "name": "pragmarx/google2fa", + "version": "v8.0.1", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "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" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa/issues", + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1" + }, + "time": "2022-06-13T21:57:56+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -6384,6 +6670,62 @@ }, "time": "2022-06-03T18:03:27+00:00" }, + { + "name": "xaevik/cuid2", + "version": "1.7.0", + "source": { + "type": "git", + "url": "git@gitlab.com:xaevik/php-cuid2.git", + "reference": "e435e5005b2e9e9e9d30b64025749c0b7aa5dcf7" + }, + "dist": { + "type": "zip", + "url": "https://gitlab.com/api/v4/projects/xaevik%2Fphp-cuid2/repository/archive.zip?sha=e435e5005b2e9e9e9d30b64025749c0b7aa5dcf7", + "reference": "e435e5005b2e9e9e9d30b64025749c0b7aa5dcf7", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.29", + "ext-ctype": "*", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^10.0", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "^5.4" + }, + "suggest": { + "ext-gmp": "*" + }, + "type": "library", + "autoload": { + "files": [ + "src/compat.php" + ], + "psr-4": { + "Xaevik\\Cuid2\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alan Brault", + "email": "alan.brault@visus.io" + } + ], + "description": "A PHP library for generating collision-resistant ids (CUIDs).", + "keywords": [ + "cuid", + "identifier" + ], + "abandoned": "visus/cuid2", + "time": "2023-02-21T14:01:02+00:00" + }, { "name": "zbateson/mail-mime-parser", "version": "2.4.0", diff --git a/config/app.php b/config/app.php index ef76a7ed6..9d1a71e6d 100644 --- a/config/app.php +++ b/config/app.php @@ -190,6 +190,7 @@ * Application Service Providers... */ App\Providers\AppServiceProvider::class, + App\Providers\FortifyServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, diff --git a/config/fortify.php b/config/fortify.php new file mode 100644 index 000000000..510a53912 --- /dev/null +++ b/config/fortify.php @@ -0,0 +1,147 @@ + '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', + + /* + |-------------------------------------------------------------------------- + | 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' => RouteServiceProvider::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/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 444fafb7f..9acafc53c 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -13,6 +13,8 @@ public function up(): void { Schema::create('users', function (Blueprint $table) { $table->id(); + $table->string('uuid')->unique(); + $table->boolean('is_root_user')->default(false); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); diff --git a/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php b/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php new file mode 100644 index 000000000..5cc9f78b1 --- /dev/null +++ b/database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.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/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php index e828ad818..954f7518f 100644 --- a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -16,6 +16,7 @@ public function up(): void $table->morphs('tokenable'); $table->string('name'); $table->string('token', 64)->unique(); + $table->string('team_id'); $table->text('abilities')->nullable(); $table->timestamp('last_used_at')->nullable(); $table->timestamp('expires_at')->nullable(); diff --git a/database/migrations/2023_03_20_112811_create_teams_table.php b/database/migrations/2023_03_20_112811_create_teams_table.php new file mode 100644 index 000000000..77e7b840f --- /dev/null +++ b/database/migrations/2023_03_20_112811_create_teams_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name'); + $table->boolean('personal_team')->default(false); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('teams'); + } +}; diff --git a/database/migrations/2023_03_20_112812_create_team_user_table.php b/database/migrations/2023_03_20_112812_create_team_user_table.php new file mode 100644 index 000000000..3e2787630 --- /dev/null +++ b/database/migrations/2023_03_20_112812_create_team_user_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('team_id'); + $table->foreignId('user_id'); + $table->string('role')->nullable(); + $table->timestamps(); + + $table->unique(['team_id', 'user_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('team_user'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index a9f4519fc..7b2f6bbcd 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,21 +2,17 @@ namespace Database\Seeders; -// use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { - /** - * Seed the application's database. - */ public function run(): void { - // \App\Models\User::factory(10)->create(); - - // \App\Models\User::factory()->create([ - // 'name' => 'Test User', - // 'email' => 'test@example.com', - // ]); + if (env('APP_ENV') === 'local') { + $this->call([ + UserSeeder::class, + TeamSeeder::class, + ]); + } } } diff --git a/database/seeders/TeamSeeder.php b/database/seeders/TeamSeeder.php new file mode 100644 index 000000000..cedc67924 --- /dev/null +++ b/database/seeders/TeamSeeder.php @@ -0,0 +1,45 @@ + "Root Team", + 'personal_team' => true, + ]); + $root_user_other_team = Team::create([ + 'name' => "Root User's Other Team", + 'personal_team' => false, + ]); + $normal_user_personal_team = Team::create([ + 'name' => 'Normal Team', + 'personal_team' => true, + ]); + DB::table('team_user')->insert([ + 'team_id' => $root_user_personal_team->id, + 'user_id' => $root_user->id, + 'role' => 'admin', + ]); + DB::table('team_user')->insert([ + 'team_id' => $root_user_other_team->id, + 'user_id' => $root_user->id, + 'role' => 'admin', + ]); + DB::table('team_user')->insert([ + 'team_id' => $normal_user_personal_team->id, + 'user_id' => $normal_user->id, + 'role' => 'admin', + ]); + } +} diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 000000000..a92626d45 --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,26 @@ +create([ + 'id' => 1, + 'name' => 'Root User', + 'email' => 'test@example.com', + 'is_root_user' => true, + ]); + User::factory()->create([ + 'id' => 2, + 'name' => 'Normal User', + 'email' => 'test2@example.com', + ]); + } +} diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php new file mode 100644 index 000000000..698501518 --- /dev/null +++ b/resources/views/auth/login.blade.php @@ -0,0 +1,20 @@ + +
+
+ @csrf + + + +
+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+
diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php new file mode 100644 index 000000000..23ac7abf0 --- /dev/null +++ b/resources/views/auth/register.blade.php @@ -0,0 +1,20 @@ + +
+ @csrf + + + + + +
+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
diff --git a/resources/views/components/button.blade.php b/resources/views/components/button.blade.php deleted file mode 100644 index f59d45973..000000000 --- a/resources/views/components/button.blade.php +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/resources/views/components/layout.blade.php b/resources/views/components/layout.blade.php index f69d56d92..33431c531 100644 --- a/resources/views/components/layout.blade.php +++ b/resources/views/components/layout.blade.php @@ -1,5 +1,5 @@ - + @@ -10,11 +10,9 @@ @livewireStyles - - @auth - - @endauth -
+ + +
{{ $slot }}
diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 8635e6cfc..008e4a57b 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -1,15 +1,15 @@ -