diff --git a/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml b/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml index 45a023f..a9ee319 100644 --- a/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml +++ b/src/bundle/Resources/config/graphql/PlatformMutation.types.yaml @@ -24,6 +24,14 @@ PlatformMutation: language: type: RepositoryLanguage! description: "The language the content items must be created in" + createToken: + type: CreatedTokenPayload + resolve: '@=mutation("CreateToken", args)' + args: + username: + type: String! + password: + type: String! UploadedFilesPayload: type: object @@ -44,3 +52,13 @@ DeleteContentPayload: id: type: ID description: "Global ID" + +CreatedTokenPayload: + type: object + config: + fields: + token: + type: String + message: + type: String + description: "The reason why authentication has failed, if it has" diff --git a/src/bundle/Resources/config/services/resolvers.yaml b/src/bundle/Resources/config/services/resolvers.yaml index fde4e23..d25fffc 100644 --- a/src/bundle/Resources/config/services/resolvers.yaml +++ b/src/bundle/Resources/config/services/resolvers.yaml @@ -62,6 +62,10 @@ services: tags: - { name: overblog_graphql.resolver, alias: "Thumbnail", method: "resolveThumbnail" } + Ibexa\GraphQL\Mutation\AuthenticationMutation: + tags: + - { name: overblog_graphql.mutation, alias: "CreateToken", method: "createToken" } + Ibexa\GraphQL\Mutation\UploadFiles: arguments: $repository: '@ibexa.siteaccessaware.repository' diff --git a/src/bundle/Resources/config/services/services.yaml b/src/bundle/Resources/config/services/services.yaml index 048c9bb..5e2dd63 100644 --- a/src/bundle/Resources/config/services/services.yaml +++ b/src/bundle/Resources/config/services/services.yaml @@ -50,12 +50,3 @@ services: $contentLoader: '@Ibexa\GraphQL\DataLoader\ContentLoader' tags: - { name: ibexa.field_type.image_asset.mapper.strategy, priority: 0 } - - Ibexa\GraphQL\Security\JWTAuthenticator: - arguments: - $userProvider: '@ibexa.security.user_provider' - - Ibexa\GraphQL\Security\JWTTokenMutationFormatEventSubscriber: - tags: - - name: kernel.event_subscriber - dispatcher: security.event_dispatcher.ibexa_jwt_graphql diff --git a/src/lib/Mutation/AuthenticationMutation.php b/src/lib/Mutation/AuthenticationMutation.php new file mode 100644 index 0000000..8a6c069 --- /dev/null +++ b/src/lib/Mutation/AuthenticationMutation.php @@ -0,0 +1,57 @@ + + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + */ + public function createToken(Argument $args): array + { + $arguments = $args->getArrayCopy(); + $username = $arguments['username']; + $password = $arguments['password']; + + if (!isset($username, $password)) { + return [ + 'message' => 'Missing username or password', + 'token' => null, + ]; + } + + try { + $user = $this->userService->loadUserByLogin($username); + $this->userService->checkUserCredentials($user, $password); + } catch (NotFoundException) { + return [ + 'message' => 'Wrong username or password', + 'token' => null, + ]; + } + + return [ + 'token' => $this->tokenManager->create(new User($user)), + ]; + } +} diff --git a/src/lib/Security/JWTAuthenticator.php b/src/lib/Security/JWTAuthenticator.php deleted file mode 100644 index 6714986..0000000 --- a/src/lib/Security/JWTAuthenticator.php +++ /dev/null @@ -1,149 +0,0 @@ -getContent(), true); - if (!isset($payload['query'])) { - return false; - } - - try { - $credentials = $this->extractCredentials($payload['query']); - } catch (Exception) { - return false; - } - - if (isset($credentials['username'], $credentials['password'])) { - $this->username = $credentials['username']; - $this->password = $credentials['password']; - - return true; - } - - return false; - } - - public function authenticate(Request $request): Passport - { - $passport = new Passport( - new UserBadge($this->username, [$this->userProvider, 'loadUserByUsername']), - new PasswordCredentials($this->password) - ); - - $user = $passport->getUser(); - if ($user instanceof IbexaUser) { - $this->permissionResolver->setCurrentUserReference($user->getAPIUser()); - } - - $passport->setAttribute('token', $this->tokenManager->create($user)); - - return $passport; - } - - /** - * @throws \JsonException - */ - public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response - { - $user = $token->getUser(); - if ($user === null) { - throw new AuthenticationException('No authenticated user found.', 401); - } - - return new Response( - json_encode( - [ - 'token' => $this->tokenManager->create($user), - 'message' => null, - ], - JSON_THROW_ON_ERROR - ) - ); - } - - /** - * @throws \JsonException - */ - public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response - { - return new Response( - json_encode( - [ - 'token' => null, - 'message' => $exception->getMessageKey(), - ], - JSON_THROW_ON_ERROR - ), - Response::HTTP_FORBIDDEN - ); - } - - public function isInteractive(): bool - { - return true; - } - - /** - * @return array - * - * @throws \Exception - */ - private function extractCredentials(string $graphqlQuery): array - { - $parsed = Parser::parse($graphqlQuery); - $credentials = []; - Visitor::visit( - $parsed, - [ - NodeKind::ARGUMENT => static function (ArgumentNode $node) use (&$credentials): void { - /** @var \GraphQL\Language\AST\StringValueNode $nodeValue */ - $nodeValue = $node->value; - - $credentials[$node->name->value] = $nodeValue->value; - }, - ] - ); - - return $credentials; - } -} diff --git a/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php b/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php deleted file mode 100644 index e55d4c4..0000000 --- a/src/lib/Security/JWTTokenMutationFormatEventSubscriber.php +++ /dev/null @@ -1,56 +0,0 @@ - ['onAuthorizationFinishes', 10], - LoginFailureEvent::class => ['onAuthorizationFinishes', 10], - ]; - } - - /** - * @throws \JsonException - */ - public function onAuthorizationFinishes(LoginSuccessEvent|LoginFailureEvent $event): void - { - $response = $event->getResponse(); - if ($response === null) { - return; - } - - $response->setContent( - $this->formatMutationResponseData($response->getContent()) - ); - } - - /** - * @throws \JsonException - */ - private function formatMutationResponseData(mixed $data): string - { - $formatted = json_encode([ - 'data' => [ - 'CreateToken' => json_decode($data, true, 512, JSON_THROW_ON_ERROR), - ], - ]); - - return $formatted === false ? '' : $formatted; - } -}