coopTilleuls / CoopTilleulsForgotPasswordBundle

Provides a "forgot password" complete feature for your API through a Symfony bundle
MIT License
79 stars 25 forks source link

How to Disable forgot-password Routes in ReDoc Documentation Only? #141

Closed jimmyMorizot closed 4 months ago

jimmyMorizot commented 4 months ago

Hello,

I am currently using the CoopTilleulsForgotPasswordBundle in my Symfony project alongside API Platform for generating OpenAPI documentation. I have a specific need to exclude the forgot-password routes from the documentation when using ReDoc, but keep them in the Swagger UI documentation.

Here is the context:

Swagger UI Documentation:

I need to include the documentation for all my exposed entities so my colleague, who is developing the frontend, can see and use this documentation. ReDoc Documentation:

I need to include only specific entities as this documentation will be used for web services that connect their applications to mine. In this documentation, I am struggling to exclude the forgot-password routes. Here is my current CustomOpenApiFactory implementation:


namespace App\OpenApi;

use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\OpenApi;
use ApiPlatform\OpenApi\Model;
use ApiPlatform\OpenApi\Model\Paths;
use Symfony\Component\HttpFoundation\RequestStack;

class CustomOpenApiFactory implements OpenApiFactoryInterface
{
    public function __construct(
        private OpenApiFactoryInterface $decorated,
        private RequestStack $requestStack,
    ){
    }

    public function __invoke(array $context = []): OpenApi
    {
        $openApi = $this->decorated->__invoke($context);
        $request = $this->requestStack->getCurrentRequest();

        if ($request && $request->query->get('ui') === 're_doc') {
            $allowedEntities = [
                '/api/vehicles',
                '/api/vehicles/{id}',
                '/api/contacts',
                '/api/contacts/{id}',
                '/api/contact_has_vehicles',
                '/api/contact_has_vehicles/{id}',
                '/api/leads',
                '/api/leads/{id}',
                '/api/proposals',
                '/api/proposals/{id}',
                '/api/reports',
                '/api/reports/{id}',
            ];

            $filteredPaths = new Paths();
            foreach ($openApi->getPaths()->getPaths() as $path => $pathItem) {
                if (!preg_match('#^/api/forgot-password#', $path) && in_array($path, $allowedEntities)) {
                    $filteredPaths->addPath($path, $pathItem);
                }
            }

            $securityScheme = new Model\SecurityScheme(
                'apiKey',
                'API Key',
                'X-API-KEY',
                'header'
            );

            $securitySchemes = new \ArrayObject(['API' => $securityScheme]);
            $components = $openApi->getComponents()->withSecuritySchemes($securitySchemes);

            return $openApi->withPaths($filteredPaths)
                ->withComponents($components)
                ->withSecurity([['API' => []]]);
        }

        return $openApi;
    }
}

Despite this setup, the forgot-password routes still appear in the ReDoc documentation. Here are the routes I would like to exclude specifically:

/api/forgot-password /api/forgot-password/{tokenValue} /api/forgot-password/{tokenValue}/reset I have verified that the routes are correctly defined and that the paths are being processed. However, they are not being excluded as expected.

Is there a recommended way to specifically disable or exclude these routes from appearing in the ReDoc documentation only, while keeping them available for other API documentation tools like Swagger UI?

Thank you in advance for your assistance.

Capture d’écran du 2024-06-17 09-08-52

jimmyMorizot commented 4 months ago

I found the solution to exclude the forgot-password routes from the ReDoc documentation.

To make it work, it was necessary to create a separate ForgotPasswordOpenApiFactory apart from the CustomOpenApiFactory.

Here's how I did it:

Custom ForgotPasswordOpenApiFactory: I created a custom ForgotPasswordOpenApiFactory class that extends AbstractOpenApiFactory and implements OpenApiFactoryInterface. This class filters out the forgot-password paths.


namespace App\OpenApi;

use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\Model\Paths;
use ApiPlatform\OpenApi\OpenApi;
use CoopTilleuls\ForgotPasswordBundle\Bridge\ApiPlatform\OpenApi\AbstractOpenApiFactory;
use CoopTilleuls\ForgotPasswordBundle\Provider\ProviderChainInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;

class ForgotPasswordOpenApiFactory extends AbstractOpenApiFactory implements OpenApiFactoryInterface
{
    public function __construct(OpenApiFactoryInterface $decorated, RouterInterface $router, ProviderChainInterface $providerChain, private RequestStack $requestStack)
    {
        parent::__construct($decorated, $router, $providerChain);
    }

    public function __invoke(array $context = []): OpenApi
    {
        $openApi = parent::__invoke($context);
        $request = $this->requestStack->getCurrentRequest();

        if ($request && $request->query->get('ui') === 're_doc') {
            $filteredPaths = new Paths();
            foreach ($openApi->getPaths()->getPaths() as $path => $pathItem) {
                if (!preg_match('#^/api/forgot-password#', $path)) {
                    $filteredPaths->addPath($path, $pathItem);
                }
            }

            return $openApi->withPaths($filteredPaths);
        }

        return $openApi;
    }
}

Service Configuration: I configured the service in services.yaml:

    App\OpenApi\ForgotPasswordOpenApiFactory:
        decorates: 'coop_tilleuls_forgot_password.openapi.factory'
        arguments: ['@App\OpenApi\ForgotPasswordOpenApiFactory.inner', '@router', '@coop_tilleuls_forgot_password.provider_chain', '@request_stack']

By creating a separate ForgotPasswordOpenApiFactory, I was able to successfully exclude the forgot-password routes from the ReDoc documentation.

jimmyMorizot commented 4 months ago

If you have better solutions, please share them with us.

vincentchalamon commented 4 months ago

Hi @jimmyMorizot,

I'm personally not using ReDoc, I usually prefer SwaggerUI. AFAIK, I think ReDoc is based on the OpenAPI documentation, right? If so, you need to find a way to generate different OpenAPI documentations depending on your UI (ReDoc vs SwaggerUI).

The way you did it (depends on a Request Query parameter re_doc) seems the right way to handle such specific use case, as it's not related to API Platform nor to this bundle.

jimmyMorizot commented 4 months ago

Hello Vincent and thank you for your reply. In the end, I succeeded with my solution using a query parameter. But I left the issue in case someone had a better way. But since it works, we won't bother any further. For my part, I also prefer Swagger UI, but I've got directives and I'm asked to provide Redoc documentation for the API's consumer client. If I may take up a little of your time, I've got another problem and, surprisingly, I can't find a solution to it. In the redoc documentation there's a button to download the doc in json format, and my management doesn't want the customer to be able to download this documentation. How can I remove this option? It's amazing that we can't simply make an external_doc: false in the api_platform.yaml, that would be nice. Do you have a solution for me?

Thanks for your time.

Sincerely

Le lun. 17 juin 2024 à 17:21, Vincent @.***> a écrit :

Hi @jimmyMorizot https://github.com/jimmyMorizot,

I'm personally not using ReDoc, I usually prefer SwaggerUI. AFAIK, I think ReDoc is based on the OpenAPI documentation, right? If so, you need to find a way to generate different OpenAPI documentations depending on your UI (ReDoc vs SwaggerUI).

The way you did it (depends on a Request Query parameter re_doc) seems the right way to handle such specific use case, as it's not related to API Platform nor to this bundle.

— Reply to this email directly, view it on GitHub https://github.com/coopTilleuls/CoopTilleulsForgotPasswordBundle/issues/141#issuecomment-2173700453, or unsubscribe https://github.com/notifications/unsubscribe-auth/AZJHW54AWZJO6JDAU2YSRQLZH35JFAVCNFSM6AAAAABJNPCQ3KVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNZTG4YDANBVGM . You are receiving this because you were mentioned.Message ID: <coopTilleuls/CoopTilleulsForgotPasswordBundle/issues/141/2173700453@ github.com>

-- Jimmy Morizot Développeur Web Php Symfony *E-mail: @. @.> LinkedIn: https://www.linkedin.com/in/jimmy-morizot/ https://www.linkedin.com/in/jimmy-morizot/ Site web: https://jimmymorizot.github.io/ https://jimmymorizot.github.io/ Port : 06 27 36 54 61*

[image: Mailtrack] https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11& Sender notified by Mailtrack https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11& 17/06/24 17:40:19

vincentchalamon commented 4 months ago

This is specific to ReDoc, so I would have a look at its configuration to see if any option is available on it to disable this button. On another hand, you can also block the access to the documentation if it fits your needs.