diff --git a/httpdocs/config/packages/security.yaml b/httpdocs/config/packages/security.yaml index 6d5cca0..6a73c36 100644 --- a/httpdocs/config/packages/security.yaml +++ b/httpdocs/config/packages/security.yaml @@ -16,12 +16,14 @@ security: auth: pattern: ^/api/auth stateless: true - json_login: - check_path: /api/auth - username_path: email - password_path: password - success_handler: lexik_jwt_authentication.handler.authentication_success - failure_handler: lexik_jwt_authentication.handler.authentication_failure + custom_authenticators: + - App\Security\JwtAuthenticator +# json_login: +# check_path: /api/auth +# username_path: email +# password_path: password +# success_handler: lexik_jwt_authentication.handler.authentication_success +# failure_handler: lexik_jwt_authentication.handler.authentication_failure api: pattern: ^/api/ stateless: true diff --git a/httpdocs/config/routes.yaml b/httpdocs/config/routes.yaml index 83fc8d4..d380b55 100644 --- a/httpdocs/config/routes.yaml +++ b/httpdocs/config/routes.yaml @@ -4,7 +4,7 @@ controllers: namespace: App\Controller type: attribute -# Bestehende Auth-Route +## Bestehende Auth-Route auth: path: /api/auth methods: ['POST'] diff --git a/httpdocs/config/services.yaml b/httpdocs/config/services.yaml index 162a6f9..626fea8 100644 --- a/httpdocs/config/services.yaml +++ b/httpdocs/config/services.yaml @@ -23,6 +23,14 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + App\OpenApi\AuthDecorator: + decorates: 'api_platform.openapi.factory' + arguments: ['@.inner'] + + App\OpenApi\OpenApiFactory: + decorates: 'api_platform.openapi.factory' + arguments: [ '@.inner' ] + acme_api.event.authentication_success_listener: class: App\EventListener\AuthenticationSuccessListener tags: diff --git a/httpdocs/src/Dto/LoginRequest.php b/httpdocs/src/Dto/LoginRequest.php new file mode 100644 index 0000000..839135b --- /dev/null +++ b/httpdocs/src/Dto/LoginRequest.php @@ -0,0 +1,16 @@ + [ + 'content' => [ + 'application/ld+json' => [ + 'schema' => ['$ref' => '#/components/schemas/MediaObject'] + ] + ] + ] + ] + ), + normalizationContext: ['groups' => ['media_object:read']] + ), new GetCollection(), new Post( controller: CreateMediaObjectAction::class, diff --git a/httpdocs/src/OpenApi/AuthDecorator.php b/httpdocs/src/OpenApi/AuthDecorator.php new file mode 100644 index 0000000..b7dfade --- /dev/null +++ b/httpdocs/src/OpenApi/AuthDecorator.php @@ -0,0 +1,111 @@ +decorated)($context); + + $schemas = $openApi->getComponents()->getSchemas(); + + $schemas['Credentials'] = new \ArrayObject([ + 'type' => 'object', + 'properties' => [ + 'email' => [ + 'type' => 'string', + 'example' => 'user@example.com', + ], + 'password' => [ + 'type' => 'string', + 'example' => 'password123', + ], + ], + ]); + + $schemas['AuthResponse'] = new \ArrayObject([ + 'type' => 'object', + 'properties' => [ + 'token' => [ + 'type' => 'string', + 'readOnly' => true, + ], + 'id' => [ + 'type' => 'string', + 'readOnly' => true, + ], + 'email' => [ + 'type' => 'string', + 'readOnly' => true, + ], + 'firstName' => [ + 'type' => 'string', + 'readOnly' => true, + ], + 'lastName' => [ + 'type' => 'string', + 'readOnly' => true, + ], + 'roles' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string' + ], + 'readOnly' => true, + ], + 'user' => [ + '$ref' => '#/components/schemas/User', // API Platform's automatisch generiertes Schema + 'readOnly' => true, + ], + ], + ]); + + $pathItem = new PathItem( + ref: 'JWT Token', + post: new Operation( + operationId: 'postCredentialsItem', + tags: ['Auth'], + responses: [ + '200' => [ + 'description' => 'Get JWT token', + 'content' => [ + 'application/json' => [ + 'schema' => [ + '$ref' => '#/components/schemas/AuthResponse', + ], + ], + ], + ], + ], + summary: 'Get JWT token to login.', + requestBody: new RequestBody( + description: 'Generate new JWT Token', + content: new \ArrayObject([ + 'application/json' => [ + 'schema' => [ + '$ref' => '#/components/schemas/Credentials', + ], + ], + ]), + ), + security: [], + ), + ); + + $openApi->getPaths()->addPath('/api/auth', $pathItem); + + return $openApi; + } +} \ No newline at end of file diff --git a/httpdocs/src/OpenApi/OpenApiFactory.php b/httpdocs/src/OpenApi/OpenApiFactory.php new file mode 100644 index 0000000..14b08a2 --- /dev/null +++ b/httpdocs/src/OpenApi/OpenApiFactory.php @@ -0,0 +1,34 @@ +decorated)($context); + + // Cleanup schema names + $schemas = $openApi->getComponents()->getSchemas(); + $cleanedSchemas = new \ArrayObject(); + + foreach ($schemas as $key => $schema) { + // Remove the .jsonld-media_object.read suffix + $newKey = preg_replace('/\.jsonld-.*/', '', $key); + $cleanedSchemas[$newKey] = $schema; + } + + $openApi = $openApi->withComponents( + $openApi->getComponents()->withSchemas($cleanedSchemas) + ); + + return $openApi; + } +} \ No newline at end of file diff --git a/httpdocs/src/Security/JwtAuthenticator.php b/httpdocs/src/Security/JwtAuthenticator.php new file mode 100644 index 0000000..79aa6b1 --- /dev/null +++ b/httpdocs/src/Security/JwtAuthenticator.php @@ -0,0 +1,75 @@ +getPathInfo() === '/api/auth' && $request->isMethod('POST'); + } + + public function authenticate(Request $request): Passport + { + $data = json_decode($request->getContent(), true); + + if (!$data) { + throw new CustomUserMessageAuthenticationException('Invalid JSON.'); + } + + $email = $data['email'] ?? ''; + $password = $data['password'] ?? ''; + + return new Passport( + new UserBadge($email), + new PasswordCredentials($password) + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + $user = $token->getUser(); + + $jwtToken = $this->jwtManager->create($user); + + return new JsonResponse([ + 'token' => $jwtToken, + 'id' => $user->getId(), + 'email' => $user->getEmail(), + 'firstName' => $user->getFirstName(), + 'lastName' => $user->getLastName(), + 'roles' => $user->getRoles(), + 'user' => $this->microMapper->map( + $user, UserApi::class + ) + ]); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + return new JsonResponse([ + 'message' => $exception->getMessage() + ], Response::HTTP_UNAUTHORIZED); + } +} \ No newline at end of file