Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Redis session store #474

Merged
merged 1 commit into from
Oct 25, 2016
Merged

Use Redis session store #474

merged 1 commit into from
Oct 25, 2016

Conversation

typesend
Copy link
Contributor

See 18F/identity-private#785

@typesend typesend force-pushed the redis-self-expiring-sessions branch 3 times, most recently from 650ce0c to 4324a0f Compare September 14, 2016 03:38
Copy link
Contributor

@monfresh monfresh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue that this change exposes is that when the session is removed from redis, when the user signs back in, they will see "Oops. Something went wrong. Please sign in again" due to this.

I believe this used to be the case before, except we didn't see it because we didn't clean up the expired sessions right away.

I'm not sure if this is a bug or if this is how the authenticity token works. Either way, we should fix this because it's a poor user experience to have to essentially sign in twice every time your session expires and is cleaned up.

key: '_upaya_session',
redis: {
driver: :hiredis,
expire_after: Figaro.env.session_timeout_in!.to_i.minutes,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer requiring Figaro keys proactively via config/initializer/figaro.rb. session_timeout_in is already defined there as a required key, so we don't need the ! here. Same with domain_name below.

driver: :hiredis,
expire_after: Figaro.env.session_timeout_in!.to_i.minutes,
key_prefix: "#{Figaro.env.domain_name!}:session:",
url: Figaro.env.redis_url!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add redis_url to config/initializer/figaro.rb (please keep the order alphabetical) instead of using the bang version.

},
# on_redis_down: ->(e, env, sid) { do_something_will_ya!(e) },
# on_session_load_error: ->(e, sid) { do_something_will_ya!(e) },
serializer: :marshal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Marshal and not JSON? JSON is the default in Rails 4+, and what we are currently using for cookies. See cookies_serializer.rb.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Marshal is what the ActiveRecord session store is using now
  • Marshal might be slightly faster than JSON
  • We don't store our sessions in cookies, so config/initializers/cookies_serializer.rb may be ignored?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes. I was confusing the two.

@typesend typesend changed the title Swap session store gems and configure initializer. [WIP] Swap session store gems and configure initializer. Sep 14, 2016
@jessieay
Copy link
Contributor

jessieay commented Oct 3, 2016

What is the status on this pull request? Looks like @monfresh reviewd 19 days ago but there haven't been updates. Are we going to close and move in a different direction or is there a plan to update this?

@monfresh
Copy link
Contributor

monfresh commented Oct 3, 2016

I believe we still need to complete this. @typesend can confirm. Ben, do you still have time to finish this, or should we assign it to someone else?

@jessieay If I recall correctly, what is holding this up is this failing test: https://github.com/18F/identity-idp/blob/master/spec/features/users/sign_in_spec.rb#L126-L149

@pkarman, @typesend and I had an in-person discussion about this, and we think the solution is to delete the session cookie whenever the session is removed from Redis.

@pkarman pkarman mentioned this pull request Oct 4, 2016
@monfresh
Copy link
Contributor

monfresh commented Oct 6, 2016

Paging @typesend.

@typesend
Copy link
Contributor Author

typesend commented Oct 6, 2016 via email

@pkarman
Copy link
Contributor

pkarman commented Oct 6, 2016

encryption of session already prepped in #516

@pkarman pkarman self-assigned this Oct 6, 2016
@pkarman
Copy link
Contributor

pkarman commented Oct 6, 2016

I'm going to wait on this till #516 is merged, which is in turn blocked on #498

@pkarman
Copy link
Contributor

pkarman commented Oct 7, 2016

for reference: the failing test is due to http://stackoverflow.com/a/7744647 -- invalid authenticity token error, because the (temp, un-authenticated) session has expired.

The CSRF token is stored in the session on the server side, and compared against the incoming request to verify they match.

This was not a problem with the ActiveRecord session store, for a bad reason: we currently don't auto-delete the ActiveRecord sessions when they expire. That means the CSRF check succeeds, even though the session is expired.

We can skip the CSRF check for the session create action only, where it is least needed (since the user is not yet authenticated, cross-site attacks aren't effective).

index 2f6b9ae..a01803f 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -3,6 +3,7 @@ module Users
     include ::ActionView::Helpers::DateHelper

     skip_before_action :session_expires_at, only: [:active]
+    skip_before_action :verify_authenticity_token, only: [:create]

     def create
       track_authentication_attempt(params[:user][:email])

@pkarman pkarman force-pushed the redis-self-expiring-sessions branch from 8844a75 to 82f7ffb Compare October 7, 2016 19:13
@pkarman
Copy link
Contributor

pkarman commented Oct 7, 2016

disabling CSRF check on sessions#create was deemed to risky.

instead, JS-driven timer with warning modal on the login page to prevent users from attempting to submit the form if we know in advance it is going to fail.

screen shot 2016-10-07 at 2 46 17 pm

@pkarman
Copy link
Contributor

pkarman commented Oct 7, 2016

I'm going to remove the WIP from this so we can get it reviewed.

However, I'm also going to mark it DO NOT MERGE until we can resolve the session encryption PR in #516.

@pkarman pkarman changed the title [WIP] Swap session store gems and configure initializer. Use Redis session store Oct 7, 2016
@jessieay
Copy link
Contributor

Added the session timeout modal separately in this PR https://github.com/18F/identity-idp/pull/578/files

@pkarman
Copy link
Contributor

pkarman commented Oct 24, 2016

I'm marking this as ready to review and merge.

Copy link
Contributor

@jessieay jessieay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 just two tiny comments from me

key_prefix: "#{Figaro.env.domain_name}:session:",
url: Figaro.env.redis_url
},
# on_redis_down: ->(e, env, sid) { do_something_will_ya!(e) },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these commented line about?

def update_session_ttl(sid)
expiry = default_options[:expire_after]
redis.expire(prefixed(sid), expiry)
rescue Errno::ECONNREFUSED, Redis::CannotConnectError => error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need / should we have a test for this?

key: '_upaya_session',
redis: {
driver: :hiredis,
expire_after: Figaro.env.session_timeout_in_minutes.to_i,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you accidentally removed the .minutes from the end here? I have this branch locally and it has the .minutes at the end. Otherwise, won't it expire every 8 seconds?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe I added it in my local branch when I realized the bug 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given how often we make this mistake, I wonder if we should perhaps use seconds for the Figaro setting, but that would be another PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just updated my PR to use Devise.timeout_in so that we are dealing with an integer rather than a string. Should we do that here, too? https://github.com/18F/identity-idp/pull/596/files

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think config files relying on one another for initialization order can get messy fast. I'd prefer to always pull from Figaro.

Copy link
Contributor

@jessieay jessieay Oct 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call. I like the idea of having the .to_i.minutes call being in one place -- but perhaps not tenable

expiry = default_options[:expire_after]
redis.expire(prefixed(sid), expiry)
rescue Errno::ECONNREFUSED, Redis::CannotConnectError => error
on_redis_down.call(error, env, sid) if on_redis_down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the on_redis_down option is not set in our session store config, this won't do anything. So, we should either get rid of this section, or actually configure on_redis_down to do something, and in that case, we can get rid of if on_redis_down because it will be present.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also address the reek/rubocop issues.

on_redis_down.call(error, env, sid) if on_redis_down
return false
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know why we even need this monkey patch? Doesn't the gem expire sessions already based on the expire_after option? I will test now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea. I was just going off what @typesend had written.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the gem works as expected out of the box. Here's how I tested:

  1. I made the session store look like this:
options = {
  key: '_upaya_session',
  redis: {
    driver: :hiredis,
    expire_after: Figaro.env.session_timeout_in_minutes.to_i.minutes,
    key_prefix: "#{Figaro.env.domain_name}:session:",
    url: Figaro.env.redis_url
  },
  # on_redis_down: ->(e, env, sid) { do_something_will_ya!(e) },
  # on_session_load_error: ->(e, sid) { do_something_will_ya!(e) },
  serializer: :marshal
}
Rails.application.config.session_store :redis_session_store, options
  1. redis-cli flushall
  2. bin/setup
  3. Set Figaro.session_timeout_in_minutes to 1
  4. foreman start
  5. Visit localhost:3000
  6. Open a Redis console with redis-cli, select the "0" database, select 0, and look at all the keys: keys *.
  7. Verify there is a key that starts with localhost:3000:session.
  8. Wait 1 minute
  9. run keys * again and verify the key is gone

Also tested signing in and letting session time out on its own. When the user is signed out, the previous session they had is removed from Redis, and then they immediately get a new session. If they wait another minute, that session gets removed as well and no new sessions will appear in Redis until a new request is made.

Copy link
Contributor

@monfresh monfresh Oct 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also tested that the expiry time automatically resets every time a new request is made. So, if a user signs in at 12:00:00, then makes their last request 30 seconds later, the session times out at 12:01:30, not 12:01:00.

@pkarman pkarman force-pushed the redis-self-expiring-sessions branch from 9ec0e86 to f4b5b32 Compare October 24, 2016 21:48
@pkarman
Copy link
Contributor

pkarman commented Oct 24, 2016

@monfresh thanks for the thorough test. I have zapped the custom subclass.

let's wait to merge this till after #596 is resolved.

@@ -1 +1,14 @@
Rails.application.config.session_store :active_record_store, key: '_upaya_session'
require 'idle_session_expiration_store'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please zap this too.

@pkarman pkarman force-pushed the redis-self-expiring-sessions branch 3 times, most recently from 1904181 to 0232acf Compare October 25, 2016 14:17
**Why**:
* speed of session access
* auto-deletion of expired sessions
* separation of data stores (db and session)
@pkarman pkarman force-pushed the redis-self-expiring-sessions branch from 2433b89 to 9356d69 Compare October 25, 2016 15:13
@pkarman
Copy link
Contributor

pkarman commented Oct 25, 2016

ok @monfresh this should be good to go

Copy link
Contributor

@monfresh monfresh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@monfresh monfresh merged commit bff4570 into master Oct 25, 2016
@monfresh monfresh deleted the redis-self-expiring-sessions branch October 25, 2016 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants