Skip to content

Commit f3c6a0b

Browse files
committed
fix invitations not being respected during signup when email case differs
1 parent 619c602 commit f3c6a0b

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

app/Service/InvitationService.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class InvitationService
2323
*/
2424
public function inviteUser(Organization $organization, string $email, Role $role, User $inviter): OrganizationInvitation
2525
{
26+
// Normalize the email so it matches how user emails are stored (see UserService::createUser),
27+
// otherwise a mixed-case invite silently fails to link on registration.
28+
$email = strtolower($email);
29+
2630
if (app(MemberService::class)->isEmailAlreadyMember($organization, $email)) {
2731
throw new UserIsAlreadyMemberOfOrganizationApiException;
2832
}
@@ -55,7 +59,7 @@ public function processAcceptedInvitations(User $user): Collection
5559
$organizations = new Collection;
5660

5761
$invitations = OrganizationInvitation::query()
58-
->where('email', $user->email)
62+
->whereRaw('lower(email) = ?', [strtolower($user->email)])
5963
->whereNotNull('accepted_at')
6064
->get();
6165

tests/Feature/RegistrationTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,40 @@ public function test_registration_does_not_create_private_organization_if_invite
380380
$this->assertSame($user->organization->id, $organizations->first()->id);
381381
}
382382

383+
public function test_registration_joins_invited_organization_even_if_invitation_email_casing_differs(): void
384+
{
385+
// Arrange: invitation stored with a different casing than the registration email
386+
$user = $this->createUserWithPermission();
387+
OrganizationInvitation::factory()
388+
->forOrganization($user->organization)
389+
->role(Role::Employee)
390+
->accepted()
391+
->create([
392+
'email' => 'Invited.User@example.com',
393+
]);
394+
395+
// Act
396+
$response = $this->post('/register', [
397+
'name' => 'Invited User',
398+
'email' => 'invited.user@example.com',
399+
'password' => 'password',
400+
'password_confirmation' => 'password',
401+
'terms' => true,
402+
]);
403+
404+
// Assert: joined the inviting organization, no extra personal organization, invitation consumed
405+
$this->assertAuthenticated();
406+
$response->assertRedirect(RouteServiceProvider::HOME);
407+
$newUser = User::where('email', 'invited.user@example.com')->first();
408+
$this->assertNotNull($newUser);
409+
$this->assertDatabaseMissing(OrganizationInvitation::class, [
410+
'email' => 'Invited.User@example.com',
411+
]);
412+
$organizations = $newUser->organizations;
413+
$this->assertCount(1, $organizations);
414+
$this->assertSame($user->organization->id, $organizations->first()->id);
415+
}
416+
383417
public function test_registration_logs_and_skips_accepted_invitation_with_invalid_role(): void
384418
{
385419
// Arrange

0 commit comments

Comments
 (0)