diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index d0aa258e..cd048900 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -2,10 +2,11 @@ namespace App\Actions\Fortify; +use App\Actions\Settings\SetDefaultSettingsForUser; use App\Models\User; -use App\Settings\UserSettings; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Str; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use Laravel\Fortify\Contracts\CreatesNewUsers; @@ -35,10 +36,15 @@ class CreateNewUser implements CreatesNewUsers 'password' => $this->passwordRules(), ])->validate(); - return User::create([ + $user = User::create([ 'name' => $input['name'], 'email' => $input['email'], 'password' => Hash::make($input['password']), + 'api_token' => Str::random(32), ]); + + (new SetDefaultSettingsForUser($user))->up(); + + return $user; } } diff --git a/app/Actions/Fortify/CreateUserInvitation.php b/app/Actions/Fortify/CreateUserInvitation.php new file mode 100644 index 00000000..fc7a78aa --- /dev/null +++ b/app/Actions/Fortify/CreateUserInvitation.php @@ -0,0 +1,19 @@ + Str::random(32), + 'email' => $email, + 'inviter_id' => auth()->id(), + 'valid_until' => now()->addDays(3), + ]); + } +} diff --git a/app/Console/Commands/RegisterUserCommand.php b/app/Console/Commands/RegisterUserCommand.php index 39d84cad..665cac1e 100644 --- a/app/Console/Commands/RegisterUserCommand.php +++ b/app/Console/Commands/RegisterUserCommand.php @@ -33,15 +33,13 @@ class RegisterUserCommand extends Command $password = $this->secret('Please enter a password for ' . $name); - $user = (new CreateNewUser)->create([ + (new CreateNewUser)->create([ 'name' => $name, 'email' => $email, 'password' => $password, 'password_confirmation' => $password, ]); - (new SetDefaultSettingsForUser($user))->up(); - $this->info('User ' . $name . ' registered.'); } } diff --git a/app/Http/Controllers/Admin/UserManagementController.php b/app/Http/Controllers/Admin/UserManagementController.php index 1b8a46e6..b0c02b95 100644 --- a/app/Http/Controllers/Admin/UserManagementController.php +++ b/app/Http/Controllers/Admin/UserManagementController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin; +use App\Actions\Fortify\CreateUserInvitation; use App\Http\Controllers\Controller; use App\Http\Requests\Admin\InviteUserRequest; use App\Models\User; @@ -9,7 +10,6 @@ use App\Models\UserInvitation; use App\Notifications\UserInviteNotification; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Event; -use Illuminate\Support\Str; use OwenIt\Auditing\Events\AuditCustom; class UserManagementController extends Controller @@ -26,27 +26,13 @@ class UserManagementController extends Controller public function inviteUser(InviteUserRequest $request): RedirectResponse { - $invitation = UserInvitation::create([ - 'token' => Str::random(32), - 'email' => $request->input('email'), - 'inviter_id' => $request->user()->id, - 'valid_until' => now()->addDays(3), - ]); - + $invitation = CreateUserInvitation::run($request->input('email')); $invitation->notify(new UserInviteNotification()); flash()->warning(trans('admin.user_management.invite_successful')); return redirect()->back(); } - public function acceptInvitation() - { - // @TODO - // check if request is valid, check if invitation with token was found - // present view with registration form and pre-filled email - // handle user registration - } - public function deleteInvitation(UserInvitation $invitation): RedirectResponse { $invitation->delete(); diff --git a/app/Http/Controllers/RegistrationController.php b/app/Http/Controllers/RegistrationController.php new file mode 100644 index 00000000..78aaa168 --- /dev/null +++ b/app/Http/Controllers/RegistrationController.php @@ -0,0 +1,55 @@ +hasValidSignature()) { + abort(401, trans('admin.user_management.invite_link_invalid')); + } + + $token = $request->input('token'); + $invitation = UserInvitation::where('token', $token)->first(); + + if ($invitation === null) { + abort(401, trans('admin.user_management.invite_token_invalid')); + } + + if (!$invitation->isValid()) { + abort(401, trans('admin.user_management.invite_expired')); + } + + return view('auth.register', [ + 'invitation' => $invitation, + ]); + } + + public function register(RegisterRequest $request) + { + $invitation = UserInvitation::where('token', $request->input('token'))->first(); + + if ($invitation === null) { + abort(401, trans('admin.user_management.invite_token_invalid')); + } + + if (!$invitation->isValid()) { + abort(401, trans('admin.user_management.invite_expired')); + } + + $newUser = (new CreateNewUser())->create($request->input()); + Auth::login($newUser, true); + + $invitation->created_user_id = $newUser->id; + $invitation->save(); + + return redirect()->route('dashboard'); + } +} diff --git a/app/Http/Requests/Auth/RegisterRequest.php b/app/Http/Requests/Auth/RegisterRequest.php new file mode 100644 index 00000000..bd55df6e --- /dev/null +++ b/app/Http/Requests/Auth/RegisterRequest.php @@ -0,0 +1,30 @@ + ['required'], + 'name' => ['required', 'string', 'max:255'], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + Rule::unique(User::class), + ], + 'password' => $this->passwordRules(), + ]; + } +} diff --git a/app/Models/UserInvitation.php b/app/Models/UserInvitation.php index 394f449c..1d8ba4c7 100644 --- a/app/Models/UserInvitation.php +++ b/app/Models/UserInvitation.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\URL; use OwenIt\Auditing\Auditable as AuditableTrait; use OwenIt\Auditing\Contracts\Auditable; use OwenIt\Auditing\Redactors\RightRedactor; @@ -27,6 +28,10 @@ class UserInvitation extends Model implements Auditable 'token', ]; + protected $casts = [ + 'valid_until' => 'datetime', + ]; + /* * ======================================================================== * AUDIT SETTINGS @@ -65,4 +70,24 @@ class UserInvitation extends Model implements Auditable { return $this->belongsTo(User::class, 'created_user_id'); } + + /* + * ======================================================================== + * METHODS + */ + + public function inviteUrl(): string + { + return URL::temporarySignedRoute('auth.accept-invite', $this->valid_until, ['token' => $this->token]); + } + + public function isValid(): bool + { + return $this->valid_until->gt(now()) && $this->created_user_id === null; + } + + public function isCompleted(): bool + { + return $this->created_user_id !== null; + } } diff --git a/app/Notifications/UserInviteNotification.php b/app/Notifications/UserInviteNotification.php index 6c4c555b..538c939f 100644 --- a/app/Notifications/UserInviteNotification.php +++ b/app/Notifications/UserInviteNotification.php @@ -6,7 +6,6 @@ use App\Models\UserInvitation; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; -use Illuminate\Support\Facades\URL; class UserInviteNotification extends Notification { @@ -26,7 +25,7 @@ class UserInviteNotification extends Notification return (new MailMessage) ->subject(trans('admin.user_management.invite_notification_title')) ->line(trans('admin.user_management.invite_notification')) - ->action(trans('admin.user_management.invite_accept'), $this->inviteUrl($invitation)) + ->action(trans('admin.user_management.invite_accept'), $invitation->inviteUrl()) ->line(trans('admin.user_management.invite_valid_until_info', ['datetime' => $invitation->valid_until])); } @@ -34,16 +33,7 @@ class UserInviteNotification extends Notification { return [ 'invitation' => $invitation, - 'inviteUrl' => $this->inviteUrl($invitation), + 'inviteUrl' => $invitation->inviteUrl(), ]; } - - protected function inviteUrl(UserInvitation $invitation): string - { - return URL::temporarySignedRoute( - 'user-management-accept-invite', - $invitation->valid_until, - ['token' => $invitation->token] - ); - } } diff --git a/lang/en_US/admin.php b/lang/en_US/admin.php index 2d75600f..276d7b3a 100644 --- a/lang/en_US/admin.php +++ b/lang/en_US/admin.php @@ -9,11 +9,16 @@ return [ 'invite_delete_confirmation' => 'Do you really want to delete this Invitation?', 'invite_successful' => 'The invitation was sent successfully.', 'invite_accept' => 'Accept invitation', + 'invite_accepted_by' => 'Invitation was accepted by :user (ID :id)', 'invite_delete_successful' => 'Invitation to :email was deleted successfully.', 'invite_notification_title' => 'You have been invited to join LinkAce!', 'invite_notification' => 'You have been invited to join LinkAce, a social bookmarking tool. Click the button below to set up your user account. If you did not request an invitation or do not expect one, please ignore this email or contact your administrator.', + 'invite_link_invalid' => 'The invitation is expired or the link is incorrect. Please contact your administrator.', + 'invite_token_invalid' => 'The invitation link is invalid or the invitation was deleted.', + 'invite_expired' => 'The invitation is expired or was already used. Please contact your administrator to receive a new invitation.', + 'invite_valid_until' => 'Valid until :datetime', 'invite_valid_until_info' => 'This invitation is valid until :datetime', ], diff --git a/lang/en_US/auth.php b/lang/en_US/auth.php index b857b035..12aba3e8 100644 --- a/lang/en_US/auth.php +++ b/lang/en_US/auth.php @@ -13,6 +13,9 @@ return [ | */ + 'register' => 'Register', + 'register_welcome' => 'Welcome to LinkAce! You have been invited to join this social bookmarking tool. Please select a user name and a password. After the successful registration, you will be redirected to the dashboard.', + 'failed' => 'These credentials do not match our records.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', diff --git a/resources/views/admin/user-management/partials/invitations.blade.php b/resources/views/admin/user-management/partials/invitations.blade.php index 2da91bd1..7cd1511d 100644 --- a/resources/views/admin/user-management/partials/invitations.blade.php +++ b/resources/views/admin/user-management/partials/invitations.blade.php @@ -31,11 +31,15 @@