Search code examples
phpapacheauthenticationsymfonynginx

MIsfunction in symfony 5+ when using custom query in prod environment


I'm developing an app that uses user@domain for login, with an entity for domains and another for users linked to the domain. This app is open source so you can freely clone from https://github.com/omgslinux/vmail/. All tests are under debian.

I started with symfony 3.x, and the features worked (and still do) in dev and when deploying to production. Then, when upgrading to 5.x and 6.x, the upgraded way to use a custom query for login is changed. I do the necessary changes for the new login system and in dev everything works ok, but I find that when deploying to prod, there's a weird behaviour that is 100% reproducible but I'm unable to know why it happens.

The issue is that if I login with any user in domain with id == 0, everything works, but if the domain id is != 0, I am redirected to the login screen, just like if the login was wrong, but no error message. If then I manually type the url I should be redirected, I am redirected back to login. I've dumped $this->getUser() in homepage, and with the same credentials, while in dev returns a user entity, in prod returns null. If I dump the query in the repository, right before going to the homepage, the query returns the expected record.

If I force a wrong password, I can see the login error on screen.

I've tested this with both apache and nginx, by setting the APP_ENV var to "prod". If I set APP_ENV to "dev", everything works, including the debug bar at the bottom.

I've replicated just the prod part in two more servers and get exactly the same results.

To reproduce the results: the default git branch is for symfony 3.x, so you have to clone symfony62 branch for latest changes. After installation, create a new domain, then a user in that new domain and try to login with this new user.

Here's the security.yaml:

security:
    password_hashers:
      App\Entity\User:
        id: App\Utils\PassEncoder
    providers:
        app_vmail_users:
            entity:
              class: App\Entity\User
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            lazy: true
            provider: app_vmail_users
            #logout_on_user_change: true
            form_login:
              login_path: login
              check_path: login
              username_parameter: _username
              password_parameter: _password
            logout:
              path: logout
              target: /

    access_control:
        - { path: ^/login, roles: ['PUBLIC_ACCESS'] }
        - { path: ^/admin, roles: ['ROLE_ADMIN'] }
        - { path: ^/manage, roles: ['ROLE_MANAGER'] }
        - { path: ^/user, roles: ['ROLE_USER'] }
    role_hierarchy:
        ROLE_ADMIN: [ROLE_MANAGER, ROLE_USER]
        ROLE_MANAGER: [ROLE_USER]

And the loader function in the user repository class:

    public function loadUserByIdentifier(string $username): ?UserInterface
    {
        $a=explode('@', $username);
        if (count($a) != 2) {
            return null;
        }
        $user=$a[0];
        $domain=$a[1];
        return $this->createQueryBuilder('u')
            ->leftJoin('u.domain', 'd')
            ->where('u.name = :user AND d.name = :domain')
            ->setParameter('user', $user)
            ->setParameter('domain', $domain)
            ->getQuery()
            ->getOneOrNullResult();
    }

Any help is appreciated. Thanks in advance.


Solution

  • I've found a way to make it work. By leaving the monolog defaults, I got no debug at all in prod (config/monolog.yaml):

    when@prod:
        monolog:
            handlers:
                main:
                    type: fingers_crossed
                    action_level: error
                    handler: nested
                    excluded_http_codes: [404, 405]
                    buffer_size: 50 # How many messages should be saved? Prevent memory leaks
                nested:
                    type: stream
                    path: php://stderr
                    level: debug
                    formatter: monolog.formatter.json
                console:
                    type: console
                    process_psr_3_messages: false
                    channels: ["!event", "!doctrine"]
                deprecation:
                    type: stream
                    channels: [deprecation]
                    path: php://stderr
    

    Since in dev I could debug the app, I replaced the "main" section with the corresponding "dev" portion:

                    type: stream
                    path: "%kernel.logs_dir%/%kernel.environment%.log"
                    level: debug
                    channels: ["!event"]
    

    Without deleting the cache, I found that I started to have logs in prod where the path indicates, and since there were no changes in the app and it was "working", I did nothing else. Then, I thought about deleting the prod cache, allowing to be automatically regenerated. After doing this... everything works!!!

    I've reproduced this by reverting the monolog changes and fully deleting the prod cache. The result is that the "old" misfunction came back, so I undid the latest changes, deleted the cache, and got back to working as it should from the beginning.

    So, I wonder: do the default monolog defaults for prod work always when using a custom query to log in? May this be a bug? I leave the question open for devs or anybody else to check this out.