vinistock / sail

Sail is a lightweight Rails engine that brings an admin panel for managing configuration settings on a live Rails app
Other
507 stars 32 forks source link

Issues with sessions when using `redis_store` and custom session key names #466

Open lavilet opened 2 years ago

lavilet commented 2 years ago

Describe the bug Hey guys 👋 . Recently we've migrated from using ActiveRecord Session Store (https://github.com/rails/activerecord-session_store) to redis_store (https://github.com/redis-store/redis-actionpack). After this change our Sail Dashboard stopped working:

Zrzut ekranu 2022-04-28 o 10 00 48

When debugging from within Sails:

[1] pry(#<Sail::SettingsController>)> session.options
=> #<ActionDispatch::Request::Session::Options:0x00007fa5b36098b0
 @by=
  #<ActionDispatch::Session::RedisStore:0x00007fa593577a48
   @app=#<ActionDispatch::Routing::RouteSet:0x00007fa571addf18>,
   @conn=
    #<Redis::Rack::Connection:0x00007fa5935771b0
     @options=
      {:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :redis_server=>nil},
     @pool=nil,
     @pooled=false,
     @store=#<Redis client v4.6.0 for redis://0.0.0.0:6379/0>>,
   @cookie_only=true,
   @default_options=
    {:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :redis_server=>nil},
   @key="_session_id",
   @mutex=#<Thread::Mutex:0x00007fa5935771d8>,
   @same_site=nil>,
 @delegate=
  {:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :redis_server=>nil}>

What I've noticed - the session key ("_session_id") looks like kind of a default key - in our configuration we are using different key name, so when we would try to fetch sesssion.id we'll get nil, because parent app stores session under different key.

When I've updated this line https://github.com/vinistock/sail/blob/7da7c74031ea51a652330f283a95eab52d485188/lib/sail/engine.rb#L42 to

if Rails.application.config.session_store.nil?
  config.middleware.use ActionDispatch::Session::CookieStore
end

Everything works as expected - dashboard loads, and when debugging and checking whats inside session object:

=> #<ActionDispatch::Request::Session::Options:0x00007fb952e3a720
 @by=
  #<ActionDispatch::Session::RedisStore:0x00007fb9e2a5fa98
   @app=
    #<ActionDispatch::ContentSecurityPolicy::Middleware:0x00007fb9e2a5fcc8
     @app=
      #<Rack::Head:0x00007fb9e2a5fed0
       @app=
        #<Rack::ConditionalGet:0x00007fb9e2a5ff48
         @app=
          #<Rack::ETag:0x00007fb9e2a54008
           @app=
            #<Rack::TempfileReaper:0x00007fb9e2a54058
             @app=
              #<Warden::Manager:0x00007fb9e2a54170
               @app=
                #<ApolloUploadServer::Middleware:0x00007fb9e2a541c0
                 @app=
                  #<Warden::JWTAuth::Middleware:0x00007fb9e2a54210
                   @app=#<Bullet::Rack:0x00007fb9e2a54260 @app=#<ActionDispatch::Routing::RouteSet:0x00007fb984297210>>>>,
               @config=
                {:default_scope=>:account,
                 :scope_defaults=>{},
                 :default_strategies=>
                  {:account=>[:jwt, :database_authenticatable],
                   ...},
                 :intercept_401=>false,
                 :failure_app=>#<Devise::Delegator:0x00007fb98468e3f0>}>>,
           @cache_control="max-age=0, private, must-revalidate",
           @no_cache_control="no-cache">>>>,
   @conn=
    #<Redis::Rack::Connection:0x00007fb9e2a5f368
     @options=
      {:path=>"/",
       :domain=>nil,
       :expire_after=>1 minute,
       :secure=>false,
       :httponly=>true,
       :defer=>false,
       :renew=>false,
       :redis_server=>["redis://0.0.0.0:6379/0/session"],
       :servers=>["redis://0.0.0.0:6379/0/session"],
       :threadsafe=>true},
     @pool=nil,
     @pooled=false,
     @store=#<Redis client v4.6.0 for redis://0.0.0.0:6379/0>>,
   @cookie_only=true,
   @default_options=
    {:path=>"/",
     :domain=>nil,
     :expire_after=>1 minute,
     :secure=>false,
     :httponly=>true,
     :defer=>false,
     :renew=>false,
     :redis_server=>["redis://0.0.0.0:6379/0/session"],
     :servers=>["redis://0.0.0.0:6379/0/session"],
     :threadsafe=>true},
   @key="_hub_session",
   @mutex=#<Thread::Mutex:0x00007fb9e2a5f390>,
   @same_site=nil>,
 @delegate=
  {:path=>"/",
   :domain=>nil,
   :expire_after=>1 minute,
   :secure=>false,
   :httponly=>true,
   :defer=>false,
   :renew=>false,
   :redis_server=>["redis://0.0.0.0:6379/0/session"],
   :servers=>["redis://0.0.0.0:6379/0/session"],
   :threadsafe=>true,
   :id=>"7f9d96ee441b7b418f5f2573263fa03c"}>
[7] pry(#<Sail::SettingsController>)>

So here I can see way more details and options, also the key matches the one we've configured ("_hub_session"). Looks like when we are adding a middleware in after_initialize block it somehow overrides some of the configs from the base/parent app 🤔 .

Environment Gemfile

source 'https://rubygems.org'

ruby '2.7.6'
gem 'rails', '~> 6.0.0'
gem 'redis-actionpack'
gem 'sail'
...

To Reproduce Steps to reproduce the behavior: Install 'redis-actionpack' gem, create app/initializers/session_store.rb

opts = {
    key: '_hub_session',
    servers: ['redis_url'],
    expire_after: 1.hour,
    threadsafe: true,
    secure: !(Rails.env.development? || Rails.env.test?)
  }
  Rails.application.config.session_store :redis_store, opts

routes:

...
authenticate :user, ->(u) { u.admin? } do
    mount Sail::Engine => '/sail'
...

Login and visit localhost:3000/sail

Expected behavior User should see a Sail Dashboard

Actual behavior User sees an error page.