Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error in the team invitation email when the Fortify register feature is off #728

Closed
kajgies opened this issue Mar 25, 2021 · 10 comments · Fixed by #741
Closed

Error in the team invitation email when the Fortify register feature is off #728

kajgies opened this issue Mar 25, 2021 · 10 comments · Fixed by #741
Assignees

Comments

@kajgies
Copy link
Contributor

kajgies commented Mar 25, 2021

  • Jetstream Version: 2.x
  • Jetstream Stack: Inertia / Livewire
  • Uses Teams: yes
  • Laravel Version: latest
  • PHP Version: 7.3

Description:

Getting an error when inviting someone to the team with the register feature off.
The email has a button with the "register" route, which doesn't exist when the register feature is off, and thus causes an error.
https://github.com/laravel/jetstream/blob/2.x/resources/views/mail/team-invitation.blade.php

Steps To Reproduce:

  • Turn the register feature off in the Fortify config.
  • Invite someone to your team

Steps To Fix

https://github.com/laravel/jetstream/blob/2.x/resources/views/mail/team-invitation.blade.php
Have a different version of this email when the register feature is off, either using a conditional or by having 2 different views.

@driesvints
Copy link
Member

One could also argue that team invites should be disabled without the register method. Disabling registration basically says "do not let anyone create a new account". I'll let @taylorotwell decide on this one.

@kajgies
Copy link
Contributor Author

kajgies commented Mar 25, 2021

But you still want to be able to invite existing members to your team, right?

@driesvints
Copy link
Member

Yeah, I think so.

@taylorotwell
Copy link
Member

Feel free to PR a suggested fix.

@jayenne
Copy link

jayenne commented Feb 23, 2022

If I may add to this thread...
Sometimes you need a site to be restricted to 'register by invite' only. In these instances, we would still want to comment-out Features::registration() from within config/fortify, but also possibly add a new Features::registrationByInvite ? ...or preferably allow registrations automatically by invite link/signature.

How does this feel?

@flexchar
Copy link

flexchar commented Sep 5, 2022

@kajgies I've made myself an implementation for registration available when a user is invited even though Features::registration() is off. Should I share the code?

@flexchar
Copy link

@driesvints I've developed a logic that allows registrations for invited members even though registration feature is disabled. I'd be open making a PR if you'd say it is something that would add value. :)

@driesvints
Copy link
Member

Thank you but I don't think we're willing to accept many more features to Jetstream at this time.

@jameshulse
Copy link

@flexchar, if you still have the relevant code then it would be useful to share. We just ran into the same limitation so will be looking to re-implement how invitations work also.

@flexchar
Copy link

flexchar commented Mar 1, 2023

Sure @jameshulse. I'll keep it simple and here for this case.

  1. Enable registration in fortify config.
  2. Overwrite registration routes in web.php
// web.php
use use Laravel\Fortify\Http\Controllers\RegisteredUserController;
// Overwrite Fortify registration routes
Route::get('/register', [RegisteredUserController::class, 'create'])
    ->middleware(['guest:' . config('fortify.guard'), 'signed'])
    ->name('register');

Route::post('/register', [RegisteredUserController::class, 'store'])
    ->middleware(['guest:' . config('fortify.guard'), 'signed']);
  1. Update CreateNewUserWithTeams (or CreateNewUser if you don't use Teams) class to validate for invitation existence.
// app/Actions/Fortify/CreateNewUserWithTeams.php
class CreateNewUserWithTeams implements CreatesNewUsers
{
    use PasswordValidationRules;

    /**
     * Create a newly registered user.
     *
     * @param  array  $input
     * @return \App\Models\User
     */
    public function create(array $input)
    {
        Validator::make($input, [
            'name' => ['required', 'string', 'max:255'],
            'email' => [
                'required',
                'string',
                'email',
                'max:255',
                Rule::exists(Jetstream::teamInvitationModel(), 'email'), //! notice validation here
                'unique:users',
            ],
            'password' => $this->passwordRules(),
            'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature()
                ? ['required', 'accepted']
                : '',
        ])
        // and a custom validation message here
            ->setCustomMessages([
                'email.exists' =>
                    'Invitation for this email address is not found. Inviter may have deleted the invitation.',
            ])
            ->validate();

        return DB::transaction(function () use ($input) {
            return tap(
                User::create([
                    'name' => $input['name'],
                    'email' => $input['email'],
                    'password' => Hash::make($input['password']),
                ]),
                function (User $user) {
                    $this->createTeam($user);
                    // Accept all pending invitations
                    TeamInvitation::whereEmail($user->email)
                        ->get()
                        ->each(function (TeamInvitation $invitation) {
                            app(AddsTeamMembers::class)->add(
                                $invitation->team->owner,
                                $invitation->team,
                                $invitation->email,
                                $invitation->role,
                            );
                            $invitation->delete();
                        });
                },
            );
        });
    }
}
  1. Update RegistrationTest
// tests/Feature/RegistrationTest.php
class RegistrationTest extends TestCase
{
    public function test_registration_screen_can_be_rendered()
    {
        $this->get('/register')->assertStatus(403);
        $this->get(URL::signedRoute('register'))->assertStatus(200);
    }

    public function test_new_users_can_register()
    {
        // Enabled for signed URLs.
        // Can only register if there is an active invitation for the user.

        $this->withExceptionHandling()
            ->post(URL::signedRoute('register'), [
                'name' => 'Not Test User',
                'email' => '[email protected]',
                'password' => 'password',
                'password_confirmation' => 'password',
                'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(),
            ])
            ->assertInvalid([
                'email' =>
                    'Invitation for this email address is not found. Inviter may have deleted the invitation.',
            ]);

        $team = Team::factory()->create();

        TeamInvitation::forceCreate([
            'email' => '[email protected]',
            'team_id' => $team->id,
            'role' => 'default',
        ]);

        $this->assertDatabaseHas('team_invitations', [
            'email' => '[email protected]',
        ]);

        $response = $this->post(URL::signedRoute('register'), [
            'name' => 'Test User',
            'email' => '[email protected]',
            'password' => 'password',
            'password_confirmation' => 'password',
            'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(),
        ])
            // ->dump()
            ->assertRedirect(RouteServiceProvider::HOME);
        $this->assertAuthenticated();

        $this->assertDatabaseHas('team_user', [
            'user_id' => auth()->id(),
            'team_id' => $team->id,
            'role' => 'default',
        ]);
    }
}

It's actually super simple now that I see. Please note that I have several customizations that you may not want to have. I've also not yet updated published stubs to include new types. But overall, you should be able to have this in 5 minutes :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants