diff --git a/.php_cs.cache b/.php_cs.cache new file mode 100644 index 00000000..c0b73c54 --- /dev/null +++ b/.php_cs.cache @@ -0,0 +1 @@ +{"php":"7.4.11","version":"2.16.4:v2.16.4#1023c3458137ab052f6ff1e09621a721bfdeca13","indent":" ","lineEnding":"\n","rules":{"array_syntax":{"syntax":"short"},"binary_operator_spaces":{"align_double_arrow":true,"align_equals":true},"blank_line_after_namespace":true,"blank_line_after_opening_tag":true,"blank_line_before_return":true,"blank_line_before_statement":true,"braces":true,"cast_spaces":true,"class_attributes_separation":true,"class_definition":true,"combine_consecutive_unsets":true,"concat_space":true,"declare_equal_normalize":true,"elseif":true,"encoding":true,"full_opening_tag":true,"fully_qualified_strict_types":true,"function_declaration":true,"function_typehint_space":true,"heredoc_to_nowdoc":true,"include":true,"increment_style":{"style":"post"},"indentation_type":true,"line_ending":true,"linebreak_after_opening_tag":true,"list_syntax":{"syntax":"short"},"lowercase_cast":true,"lowercase_constants":true,"lowercase_keywords":true,"lowercase_static_reference":true,"magic_constant_casing":true,"magic_method_casing":true,"method_argument_space":true,"method_separation":true,"multiline_whitespace_before_semicolons":true,"native_function_casing":true,"new_with_braces":true,"no_alias_functions":true,"no_blank_lines_after_class_opening":true,"no_blank_lines_after_phpdoc":true,"no_closing_tag":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":true,"no_extra_consecutive_blank_lines":true,"no_leading_import_slash":true,"no_leading_namespace_whitespace":true,"no_mixed_echo_print":true,"no_multiline_whitespace_around_double_arrow":true,"no_multiline_whitespace_before_semicolons":true,"no_short_bool_cast":true,"no_short_echo_tag":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_after_function_name":true,"no_spaces_around_offset":true,"no_spaces_inside_parenthesis":true,"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_trailing_whitespace_in_comment":true,"no_trailing_whitespace":true,"no_unneeded_control_parentheses":true,"no_unreachable_default_argument_value":true,"no_unused_imports":true,"no_useless_else":true,"no_useless_return":true,"no_whitespace_before_comma_in_array":true,"no_whitespace_in_blank_line":true,"normalize_index_brace":true,"not_operator_with_successor_space":true,"object_operator_without_whitespace":true,"ordered_imports":{"sortAlgorithm":"alpha"},"php_unit_strict":true,"php_unit_test_class_requires_covers":true,"phpdoc_add_missing_param_annotation":true,"phpdoc_align":true,"phpdoc_indent":true,"phpdoc_inline_tag":true,"phpdoc_no_access":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_order":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_types":true,"phpdoc_var_without_name":true,"psr4":true,"self_accessor":true,"semicolon_after_instruction":true,"short_scalar_cast":true,"simplified_null_return":true,"single_blank_line_at_eof":true,"single_blank_line_before_namespace":true,"single_class_element_per_statement":true,"single_import_per_statement":true,"single_line_after_imports":true,"single_line_comment_style":{"comment_types":["hash"]},"single_quote":true,"space_after_semicolon":true,"standardize_not_equals":true,"strict_comparison":true,"strict_param":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"ternary_operator_spaces":true,"trailing_comma_in_multiline_array":true,"trim_array_spaces":true,"unary_operator_spaces":true,"visibility_required":true,"whitespace_after_comma_in_array":true},"hashes":{"tests\/Analysis\/AnalysisTest.php":2269863672,"tests\/Components\/ExportUserDataTest.php":2923185317,"tests\/Components\/UpdateProfilePhotoFormTest.php":490626868,"tests\/Components\/UpdateTimezoneFormTest.php":1498554513,"tests\/TestCase.php":78583716,"tests\/Actions\/CreateNewUserTest.php":2118307707,"tests\/database\/migrations\/2014_10_12_000000_create_users_table.php":1723824529,"tests\/Pest.php":1169021441,"tests\/MediaUser.php":3700109144,"tests\/Helpers.php":1374867389,"resources\/lang\/en\/menu.php":1447561255,"resources\/lang\/en\/pages.php":3678040609,"resources\/lang\/en\/forms.php":851048379,"resources\/lang\/en\/metatags.php":1348954319,"resources\/lang\/en\/auth.php":1987199608,"resources\/lang\/en\/actions.php":528775330,"src\/Components\/UpdateProfileInformationForm.php":1726477055,"src\/Components\/Concerns\/InteractsWithUser.php":604797978,"src\/Components\/Concerns\/ValidatesPassword.php":997171193,"src\/Components\/ExportUserData.php":659094774,"src\/Components\/RegisterForm.php":1011346723,"src\/Components\/UpdateProfilePhotoForm.php":3129785372,"src\/Components\/DeleteUserForm.php":4127726762,"src\/Components\/LogoutOtherBrowserSessionsForm.php":1945407058,"src\/Components\/UpdateTimezoneForm.php":3626051886,"src\/Components\/UpdatePasswordForm.php":3386160155,"src\/Components\/TwoFactorAuthenticationForm.php":3974019434,"src\/Rules\/OneTimePassword.php":1527130763,"src\/Models\/Concerns\/HasPhoto.php":3120665243,"src\/Models\/Concerns\/HasLocalizedTimestamps.php":3255316602,"src\/Models\/User.php":2344707040,"src\/FortifyServiceProvider.php":3020670563,"src\/Models.php":1170854951,"src\/Actions\/UpdateUserPassword.php":3027603934,"src\/Actions\/CreateNewUser.php":1334182225,"src\/Actions\/GenerateTwoFactorAuthenticationSecretKey.php":1464696352,"src\/Actions\/EnableTwoFactorAuthentication.php":167336416,"src\/Actions\/UpdateUserProfileInformation.php":1227061479,"src\/Actions\/DeleteUser.php":2694141662,"src\/Actions\/PasswordValidationRules.php":1174304774,"src\/Actions\/ResetUserPassword.php":1701156092,"src\/Responses\/TwoFactorLoginResponse.php":3770476152,"src\/Responses\/FailedTwoFactorLoginResponse.php":36363793,"config\/fortify.php":543398167}} \ No newline at end of file diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php index 6d4ef12a..b47bb48d 100644 --- a/resources/lang/en/forms.php +++ b/resources/lang/en/forms.php @@ -15,4 +15,11 @@ 'requirements_notice' => 'Password must be 12–128 characters, and include a number, a symbol, a lower and an upper case letter.', ], + 'password_rules' => [ + 'needs_lowercase' => 'One lowercase character', + 'needs_uppercase' => 'One uppercase character', + 'needs_numeric' => 'One number', + 'needs_special_character' => 'One special character', + 'is_too_short' => '12 characters minumum', + ], ]; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index a3a80d5f..7c900676 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -36,7 +36,6 @@ class="flex flex-col p-8 mx-4 border rounded-lg border-theme-secondary-200 md:mx
+ @csrf + + @if($invitation) + + @else +
+
+ +
+
+ @endif + +
+
+ +
+
+ + @if($invitation) + + @else +
+
+ +
+
+ @endif + + + + + +
+
+ +
+
+ +
+ + @slot('label') + Creating an account means you're okay with our + Terms of Service and + Privacy Policy. + @endslot + + + @error('terms') +

{{ $message }}

+ @enderror +
+ +
+ +
+ +
+
+ Already a member? Sign in +
+
+ diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 2a79d532..823a734f 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -18,126 +18,7 @@
Sign up to MarketSquare and connect with a growing community of like-minded blockchain enthusiasts and developers.
-
- @csrf - - @if($invitation) - - @else -
-
- -
-
- @endif - -
-
- -
-
- - @if($invitation) - - @else -
-
- -
-
- @endif - -
-
- -
- - @if (! request()->session()->get('errors')) -
@lang('fortify::forms.update_password.requirements_notice')
- @endif -
- -
-
- -
-
- -
- - @slot('label') - Creating an account means you're okay with our - Terms of Service and - Privacy Policy. - @endslot - - - @error('terms') -

{{ $message }}

- @enderror -
- -
- -
- -
-
- Already a member? Sign in -
-
-
+
diff --git a/resources/views/components/password-rules.blade.php b/resources/views/components/password-rules.blade.php new file mode 100644 index 00000000..4667c72f --- /dev/null +++ b/resources/views/components/password-rules.blade.php @@ -0,0 +1,19 @@ +@props(['passwordRules']) + +
+
+ {{ $slot }} +
+ +
+ @foreach($passwordRules as $ruleName => $ruleIsValid) +
+ + + + @lang('fortify::forms.password_rules.' . Str::snake($ruleName)) + +
+ @endforeach +
+
diff --git a/resources/views/profile/update-password-form.blade.php b/resources/views/profile/update-password-form.blade.php index 2834ee7e..47ee2818 100644 --- a/resources/views/profile/update-password-form.blade.php +++ b/resources/views/profile/update-password-form.blade.php @@ -6,7 +6,11 @@
- + + + + +
diff --git a/src/Actions/CreateNewUser.php b/src/Actions/CreateNewUser.php index f58a3bcc..3e9815a4 100644 --- a/src/Actions/CreateNewUser.php +++ b/src/Actions/CreateNewUser.php @@ -24,7 +24,7 @@ public function create(array $input) 'name' => ['required', 'string', 'max:255'], 'username' => ['required', 'string', 'max:255', 'unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], - 'password' => ['required', 'string', $this->passwordRules(), 'confirmed'], + 'password' => $this->passwordRules(), 'terms' => ['required', 'accepted'], ])->validate(); diff --git a/src/Components/Concerns/InteractsWithUser.php b/src/Components/Concerns/InteractsWithUser.php index 8450f63a..b4da28a4 100644 --- a/src/Components/Concerns/InteractsWithUser.php +++ b/src/Components/Concerns/InteractsWithUser.php @@ -7,7 +7,7 @@ trait InteractsWithUser { - public function getUserProperty(): ?\Illuminate\Contracts\Auth\Authenticatable + public function getUserProperty(): ?Authenticatable { return Auth::user(); } diff --git a/src/Components/Concerns/ValidatesPassword.php b/src/Components/Concerns/ValidatesPassword.php new file mode 100644 index 00000000..842a29d8 --- /dev/null +++ b/src/Components/Concerns/ValidatesPassword.php @@ -0,0 +1,32 @@ + false, + 'needsUppercase' => false, + 'needsNumeric' => false, + 'needsSpecialCharacter' => false, + 'isTooShort' => false, + ]; + + public function updatedStatePassword($password) + { + $this->errorMessages = []; + + $passwordValidator = (new Password()) + ->length(12) + ->requireUppercase() + ->requireNumeric() + ->requireSpecialCharacter(); + + collect($this->passwordRules) + ->each(function ($val, $ruleName) use ($passwordValidator, $password) { + $this->passwordRules[$ruleName] = ! $passwordValidator->{$ruleName}($password); + }); + } +} diff --git a/src/Components/RegisterForm.php b/src/Components/RegisterForm.php new file mode 100644 index 00000000..7ca57be5 --- /dev/null +++ b/src/Components/RegisterForm.php @@ -0,0 +1,53 @@ + '', + 'username' => '', + 'email' => '', + 'password' => '', + 'password_confirmation' => '', + 'terms' => false, + ]; + + public string $formUrl; + + protected $invitation = null; + + public function mount() + { + $this->state = [ + 'name' => old('name', ''), + 'username' => old('username', ''), + 'email' => old('email', ''), + 'terms' => old('terms', ''), + ]; + + $this->formUrl = request()->fullUrl(); + + if (request()->has('invitation')) { + $this->invitation = Models::invitation()::findByUuid(request()->get('invitation')); + } + } + + /** + * Render the component. + * + * @return \Illuminate\View\View + */ + public function render() + { + return view('ark-fortify::auth.register-form', [ + 'invitation' => $this->invitation, + ]); + } +} diff --git a/src/Components/UpdatePasswordForm.php b/src/Components/UpdatePasswordForm.php index 72f0e31a..62ab1fbf 100644 --- a/src/Components/UpdatePasswordForm.php +++ b/src/Components/UpdatePasswordForm.php @@ -3,6 +3,7 @@ namespace ARKEcosystem\Fortify\Components; use ARKEcosystem\Fortify\Components\Concerns\InteractsWithUser; +use ARKEcosystem\Fortify\Components\Concerns\ValidatesPassword; use Illuminate\Support\Facades\Auth; use Laravel\Fortify\Contracts\UpdatesUserPasswords; use Livewire\Component; @@ -10,6 +11,9 @@ class UpdatePasswordForm extends Component { use InteractsWithUser; + use ValidatesPassword; + + protected $listeners = ['passwordUpdated' => 'passwordUpdated']; /** * The component's state. diff --git a/src/FortifyServiceProvider.php b/src/FortifyServiceProvider.php index 90a40680..8d29aa90 100644 --- a/src/FortifyServiceProvider.php +++ b/src/FortifyServiceProvider.php @@ -9,6 +9,7 @@ use ARKEcosystem\Fortify\Components\DeleteUserForm; use ARKEcosystem\Fortify\Components\ExportUserData; use ARKEcosystem\Fortify\Components\LogoutOtherBrowserSessionsForm; +use ARKEcosystem\Fortify\Components\RegisterForm; use ARKEcosystem\Fortify\Components\TwoFactorAuthenticationForm; use ARKEcosystem\Fortify\Components\UpdatePasswordForm; use ARKEcosystem\Fortify\Components\UpdateProfileInformationForm; @@ -48,7 +49,7 @@ public function boot() $this->registerPublishers(); - $this->registerComponents(); + $this->registerLivewireComponents(); $this->registerActions(); @@ -85,11 +86,11 @@ public function registerPublishers(): void } /** - * Register the components. + * Register the Livewire components. * * @return void */ - public function registerComponents(): void + public function registerLivewireComponents(): void { Livewire::component('profile.delete-user-form', DeleteUserForm::class); Livewire::component('profile.export-user-data', ExportUserData::class); @@ -99,6 +100,7 @@ public function registerComponents(): void Livewire::component('profile.update-profile-information-form', UpdateProfileInformationForm::class); Livewire::component('profile.update-profile-photo-form', UpdateProfilePhotoForm::class); Livewire::component('profile.update-timezone-form', UpdateTimezoneForm::class); + Livewire::component('auth.register-form', RegisterForm::class); } /** @@ -136,16 +138,7 @@ private function registerViews(): void }); Fortify::registerView(function ($request) { - $invitation = null; - - if ($request->has('invitation')) { - $invitation = Models::invitation()::findByUuid($request->get('invitation')); - } - - return view('ark-fortify::auth.register', [ - 'formUrl' => $request->fullUrl(), - 'invitation' => $invitation, - ]); + return view('ark-fortify::auth.register'); }); Fortify::requestPasswordResetLinkView(function () { diff --git a/tests/Actions/CreateNewUserTest.php b/tests/Actions/CreateNewUserTest.php new file mode 100644 index 00000000..729c1c48 --- /dev/null +++ b/tests/Actions/CreateNewUserTest.php @@ -0,0 +1,188 @@ +validPassword = 'Pas3w05d&123456'; +}); + +it('should create a valid user with the create user action', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + $user = (new CreateNewUser())->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'email' => 'alfonso@ark.io', + 'password' => $this->validPassword, + 'password_confirmation' => $this->validPassword, + 'terms' => true, + ]); + + $this->assertSame('alfonsobries', $user->username); + $this->assertSame('alfonso@ark.io', $user->email); + $this->assertSame('Alfonso Bribiesca', $user->name); + $this->assertTrue(Hash::check($this->validPassword, $user->password)); +}); + +it('should require a username', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'email' => 'alfonso@ark.io', + 'password' => $this->validPassword, + 'password_confirmation' => $this->validPassword, + 'terms' => true, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('username')); + } +}); + +it('should require an email', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'password' => $this->validPassword, + 'password_confirmation' => $this->validPassword, + 'terms' => true, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('email')); + } +}); + +it('should require a valid email', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'email' => 'alfonsobries', + 'password' => $this->validPassword, + 'password_confirmation' => $this->validPassword, + 'terms' => true, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('email')); + } +}); + +it('should require the terms to be accepted', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'email' => 'alfonso@ark.io', + 'password' => $this->validPassword, + 'password_confirmation' => $this->validPassword, + 'terms' => false, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('terms')); + } +}); + +it('should match the confirmation', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'email' => 'alfonso@ark.io', + 'password' => $this->validPassword, + 'password_confirmation' => 'password', + 'terms' => false, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('password')); + } +}); + +it('should be equal to or longer than 12 characters', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'email' => 'alfonso@ark.io', + 'password' => 'Sec$r2t', + 'password_confirmation' => 'Sec$r2t', + 'terms' => true, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('password')); + } +}); + +it('should require an uppercase letter', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'email' => 'alfonso@ark.io', + 'password' => 'sec$r2t12345', + 'password_confirmation' => 'sec$r2t12345', + 'terms' => true, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('password')); + } +}); + +it('should require one number', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'email' => 'alfonso@ark.io', + 'password' => 'sec$%Asfhhdfhfdhgd', + 'password_confirmation' => 'sec$%Asfhhdfhfdhgd', + 'terms' => true, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('password')); + } +}); + +it('should require one special character', function () { + Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class); + + try { + (new CreateNewUser()) + ->create([ + 'name' => 'Alfonso Bribiesca', + 'username' => 'alfonsobries', + 'email' => 'alfonso@ark.io', + 'password' => 'sec23Asfhhdfhfdhgd', + 'password_confirmation' => 'sec23Asfhhdfhfdhgd', + 'terms' => true, + ]); + } catch (ValidationException $e) { + $this->assertTrue($e->validator->errors()->has('password')); + } +}); diff --git a/tests/Helpers.php b/tests/Helpers.php index 9dd97131..8b2b7588 100644 --- a/tests/Helpers.php +++ b/tests/Helpers.php @@ -9,6 +9,7 @@ function createUserModel() { return User::create([ 'name' => 'John Doe', + 'username' => 'john.doe', 'email' => 'john@doe.com', 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), diff --git a/tests/MediaUser.php b/tests/MediaUser.php index 5c05b36a..e2e4eb45 100644 --- a/tests/MediaUser.php +++ b/tests/MediaUser.php @@ -17,6 +17,7 @@ public static function fake(): self { return static::create([ 'name' => 'John Doe', + 'username' => 'john.doe', 'email' => 'john@doe.com', 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), diff --git a/tests/database/migrations/2014_10_12_000000_create_users_table.php b/tests/database/migrations/2014_10_12_000000_create_users_table.php index 732cbf4d..0ddaffa1 100644 --- a/tests/database/migrations/2014_10_12_000000_create_users_table.php +++ b/tests/database/migrations/2014_10_12_000000_create_users_table.php @@ -11,6 +11,7 @@ public function up() Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name')->index(); + $table->string('username')->index(); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password');