diff --git a/README.md b/README.md index 5d24a0b..ec92def 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Add [Laravel Sanctum](https://github.com/laravel/sanctum) support to [Lighthouse - [Email Verification](#email-verification) - [Forgot Password](#forgot-password) - [Reset Password](#reset-password) +- [Custom Identification](#custom-identification) ## Requirements @@ -134,6 +135,8 @@ Apply the Authorization header on subsequent calls using the token "Authorization": "Bearer 1|lJo1cMhrW9tIUuGwlV1EPjKnvfZKzvgpGgplbwX9" ``` +(Using something other than email? See [Custom Identification](#custom-identification)) + ### Logout Revoke the current token. @@ -295,6 +298,33 @@ mutation { } ``` +### Custom Identification + +You can customize which fields are used for authenticating users. + +For example, using `username` instead of the default `email`. +```php +/* +|-------------------------------------------------------------------------- +| Identification +|-------------------------------------------------------------------------- +| +| Configure the credential fields by which the user will be identified. +| Default: email +*/ + +'user_identifier_field_name' => 'username', +``` + +Update the GraphQL schema accordingly + +```graphql +input LoginInput { + username: String! @rules(apply: ["required"]) +} +``` + + ## Testing ```bash diff --git a/config/lighthouse-sanctum.php b/config/lighthouse-sanctum.php index 4d4ca3e..a229fc5 100644 --- a/config/lighthouse-sanctum.php +++ b/config/lighthouse-sanctum.php @@ -26,4 +26,15 @@ | */ 'use_signed_email_verification_url' => false, + + /* + |-------------------------------------------------------------------------- + | Identification + |-------------------------------------------------------------------------- + | + | Configure the credential fields by which the user will be identified. + | Default: email + */ + + 'user_identifier_field_name' => 'email', ]; diff --git a/src/GraphQL/Mutations/Login.php b/src/GraphQL/Mutations/Login.php index f9f66af..be862ff 100644 --- a/src/GraphQL/Mutations/Login.php +++ b/src/GraphQL/Mutations/Login.php @@ -36,8 +36,11 @@ public function __invoke($_, array $args): array { $userProvider = $this->createUserProvider(); + $identificationKey = $this->getConfig() + ->get('lighthouse-sanctum.identification.user_identifier_field_name', 'email'); + $user = $userProvider->retrieveByCredentials([ - 'email' => $args['email'], + $identificationKey => $args[$identificationKey], 'password' => $args['password'], ]); diff --git a/src/Services/ResetPasswordService.php b/src/Services/ResetPasswordService.php index f8c98bb..fcf76c7 100644 --- a/src/Services/ResetPasswordService.php +++ b/src/Services/ResetPasswordService.php @@ -37,7 +37,8 @@ public function transformUrl(CanResetPassword $notifiable, string $token, string public function setResetPasswordUrl(string $url): void { - ResetPasswordNotification::createUrlUsing(function (CanResetPassword $notifiable, string $token) use ($url) { + /** @phpstan-ignore-next-line */ + ResetPasswordNotification::createUrlUsing(function (CanResetPassword $notifiable, string $token) use ($url): string { return $this->transformUrl($notifiable, $token, $url); }); } diff --git a/tests/Integration/GraphQL/Mutations/ForgotPasswordTest.php b/tests/Integration/GraphQL/Mutations/ForgotPasswordTest.php index 7a451a5..a3ca58e 100644 --- a/tests/Integration/GraphQL/Mutations/ForgotPasswordTest.php +++ b/tests/Integration/GraphQL/Mutations/ForgotPasswordTest.php @@ -45,6 +45,7 @@ public function it_sends_a_reset_password_notification(): void ]); Notification::assertSentTo($user, function (ResetPassword $notification) use ($user) { + /** @phpstan-ignore-next-line */ $url = call_user_func($notification::$createUrlCallback, $user, $notification->token); return $url === "https://my-front-end.com/reset-password?email=john.doe@gmail.com&token={$notification->token}"; diff --git a/tests/Integration/Services/ResetPasswordServiceTest.php b/tests/Integration/Services/ResetPasswordServiceTest.php index baabb01..d04ebff 100644 --- a/tests/Integration/Services/ResetPasswordServiceTest.php +++ b/tests/Integration/Services/ResetPasswordServiceTest.php @@ -61,6 +61,7 @@ public function it_sets_the_reset_password_url(): void $this->service->setResetPasswordUrl('https://mysite.com/reset-password/__EMAIL__/__TOKEN__'); + /** @phpstan-ignore-next-line */ $url = call_user_func(ResetPassword::$createUrlCallback, $user, $token); static::assertSame('https://mysite.com/reset-password/user@example.com/token123', $url); diff --git a/tests/Traits/MocksUserProvider.php b/tests/Traits/MocksUserProvider.php index 40dc66a..b609b0d 100644 --- a/tests/Traits/MocksUserProvider.php +++ b/tests/Traits/MocksUserProvider.php @@ -38,7 +38,12 @@ protected function mockConfig() ->shouldReceive('get') ->with('lighthouse-sanctum.provider') ->andReturn('sanctum-provider') - ->getMock(); + ->getMock() + ->shouldReceive('get') + ->with('lighthouse-sanctum.identification.user_identifier_field_name', 'email') + ->andReturn('email') + ->getMock() + ; return $config; } diff --git a/tests/Unit/GraphQL/Mutations/LoginTest.php b/tests/Unit/GraphQL/Mutations/LoginTest.php index 54b05dd..d785f4d 100644 --- a/tests/Unit/GraphQL/Mutations/LoginTest.php +++ b/tests/Unit/GraphQL/Mutations/LoginTest.php @@ -11,6 +11,7 @@ use DanielDeWit\LighthouseSanctum\Tests\Traits\MocksUserProvider; use DanielDeWit\LighthouseSanctum\Tests\Unit\AbstractUnitTest; use Illuminate\Contracts\Auth\UserProvider; +use Illuminate\Contracts\Config\Repository as Config; use Illuminate\Foundation\Auth\User; use Laravel\Sanctum\NewAccessToken; use Mockery; @@ -62,6 +63,64 @@ public function it_logs_a_user_in(): void static::assertSame('1234567890', $result['token']); } + /** + * @test + */ + public function it_logs_a_user_in_using_custom_identification(): void + { + /** @var Config|MockInterface $config */ + $config = Mockery::mock(Config::class) + ->shouldReceive('get') + ->with('lighthouse-sanctum.provider') + ->andReturn('sanctum-provider') + ->getMock() + ->shouldReceive('get') + ->with('lighthouse-sanctum.identification.user_identifier_field_name', 'email') + ->andReturn('custom_key') + ->getMock(); + + $token = Mockery::mock(NewAccessToken::class); + $token->plainTextToken = '1234567890'; + + /** @var UserHasApiTokens|MockInterface $user */ + $user = Mockery::mock(UserHasApiTokens::class) + ->shouldReceive('createToken') + ->with('default') + ->andReturn($token) + ->getMock(); + + /** @var UserProvider|MockInterface $userProvider */ + $userProvider = Mockery::mock(UserProvider::class) + ->shouldReceive('retrieveByCredentials') + ->with([ + 'custom_key' => 'foo@bar.com', + 'password' => 'supersecret', + ]) + ->andReturn($user) + ->getMock() + ->shouldReceive('validateCredentials') + ->with($user, [ + 'custom_key' => 'foo@bar.com', + 'password' => 'supersecret', + ]) + ->andReturnTrue() + ->getMock(); + + $mutation = new Login( + $this->mockAuthManager($userProvider), + $config, + ); + + $result = $mutation(null, [ + 'custom_key' => 'foo@bar.com', + 'password' => 'supersecret', + ]); + + static::assertIsArray($result); + static::assertCount(1, $result); + static::assertSame('1234567890', $result['token']); + } + /** * @test */ diff --git a/tests/stubs/Users/UserHasApiTokens.php b/tests/stubs/Users/UserHasApiTokens.php index 5b1d6b0..9751702 100644 --- a/tests/stubs/Users/UserHasApiTokens.php +++ b/tests/stubs/Users/UserHasApiTokens.php @@ -17,10 +17,13 @@ class UserHasApiTokens extends User implements HasApiTokensContract use HasFactory; use Notifiable; + /** + * @var string + */ protected $table = 'users'; /** - * @var string[] + * @var array */ protected $fillable = [ 'name', @@ -30,7 +33,7 @@ class UserHasApiTokens extends User implements HasApiTokensContract ]; /** - * @var string[] + * @var array */ protected $hidden = [ 'password',