User-based `token_lifespan` instead with Devise - ruby-on-rails

one important step before filing an issue is to share reproducible steps. This is exactly where we're having trouble with; the bug below only happens in production.
The issue is once we shorten the token_lifespan from 2 weeks to 30 minutes (or 1800). It gives users http 500s and failing to reset their passwords.
One idea is to set the lifespan to a specific user. So we can either isolate the bug and/or the fix before rolling it out to everybody else. If nobody has other ideas, How can we set different token_lifespan for a user with Devise?
Example of what we see in the prod logs:
NoMethodError: undefined method `[]' for nil:NilClass
args[:expiry] = tokens[args[:client_id]]['expiry']
^^^^^^^^^^
Our configuration:
DeviseTokenAuth.setup do |config|
config.change_headers_on_each_request = true
config.token_lifespan = ENV.fetch('TOKEN_LIFESPAN', 1800).to_i
config.token_cost = Rails.env.test? ? 4 : 10
config.batch_request_buffer_throttle = 10.seconds
config.default_callbacks = false
config.bypass_sign_in = false
end

Related

Unknown logs interrupt me while `binding.pry` on Rails

I’m developing with Ruby on Rails. When I start an application server with Puma, the following logs continue to show every a few seconds.
{"method":{},"path":{},"format":{},"params":{},"controller":"ApplicationCable::Connection","action":"connect","status":200,"duration":8.75,"backtrace":null,"host":null,"user_id":null,"user_type":null,"remote_ip":null,"user_agent":null,"os":null,"os_version":null,"browser":null,"browser_version":null,"#timestamp":"2021-07-28T10:24:34.068Z","#version":"1","message":"[200] (ApplicationCable::Connection#connect)"}
{"method":{},"path":{},"format":{},"params":{},"controller":"ApplicationCable::Connection","action":"disconnect","status":200,"duration":0.58,"backtrace":null,"host":null,"user_id":null,"user_type":null,"remote_ip":null,"user_agent":null,"os":null,"os_version":null,"browser":null,"browser_version":null,"#timestamp":"2021-07-28T10:24:34.069Z","#version":"1","message":"[200] (ApplicationCable::Connection#disconnect)"}
This interrupts binding.pry prompts as follow, so I can’t debug an application properly.
[1] pry(#<SomeController>)> {"method":{},"path":{},"format":{},"params":{},"controller":"ApplicationCable::Connection","action":"connect","status":200,"duration":8.75,"backtrace":null,"host":null,"user_id":null,"user_type":null,"remote_ip":null,"user_agent":null,"os":null,"os_version":null,"browser":null,"browser_version":null,"#timestamp":"2021-07-28T10:24:34.068Z","#version":"1","message":"[200] (ApplicationCable::Connection#connect)"}
{"method":{},"path":{},"format":{},"params":{},"controller":"ApplicationCable::Connection","action":"disconnect","status":200,"duration":0.58,"backtrace":null,"host":null,"user_id":null,"user_type":null,"remote_ip":null,"user_agent":null,"os":null,"os_version":null,"browser":null,"browser_version":null,"#timestamp":"2021-07-28T10:24:34.069Z","#version":"1","message":"[200] (ApplicationCable::Connection#disconnect)"}
I wasn’t able to find from which these logs show.
What I’ve tried is adding ActionCable.server.config.logger = Logger.new(nil) to config/application.rb. But I still have the problem.
https://dev.to/xlts/fixing-rails-action-cable-logger-la8#option-2-try-to-do-it-systematically
How can I fix this problem?
Thank you in advance.
I’m using Lograge, so I’ve resolved this problem by adding the following configuration to config/initializers/lograge.rb.
Rails.application.configure do
# ...
# ...
# ...
config.lograge.ignore_actions = [
"ApplicationCable::Connection#connect",
"ApplicationCable::Connection#disconnect"
]
end

Capybara + Selenium-webdriver + RSpec file fixtures + SSR giving Net::ReadTimeout

I'm noticing a strange issue that I haven't been able to solve for a few days.
I have a Rails 5 API server with system tests using RSpec and Capybara + Selenium-webdriver driving headless Chrome.
I'm using Capybara.app_host = 'http://localhost:4200' to make the tests hit a separate development server which is running an Ember front-end. The Ember front-end looks at the user agent to know to then send requests to the Rails API test database.
All the tests run fine except for ones which use RSpec file fixtures.
Here's one spec that is failing:
describe 'the affiliate program', :vcr, type: :system do
fixtures :all
before do
Capybara.session_name = :affiliate
visit('/')
signup_and_verify_email(signup_intent: :seller)
visit_affiliate_settings
end
it 'can use the affiliate page' do
affiliate_token = page.text[/Your affiliate token is \b(.+?)\b/i, 1]
expect(affiliate_token).to be_present
# When a referral signs up.
Capybara.session_name = :referral
visit("?client=#{affiliate_token}")
signup_and_verify_email(signup_intent: :member)
refresh
# It can track the referral.
Capybara.session_name = :affiliate
refresh
expect(page).to have_selector('.referral-row', count: 1)
# When a referral makes a purchase.
Capybara.session_name = :referral
find('[href="/videos"]').click
find('.price-area .coin-usd-amount', match: :first).click
find('.cart-dropdown-body .checkout-button').click
find('.checkout-button').click
wait_for { find('.countdown-timer') }
order = Order.last
order.force_complete_payment!
Rake::Task['affiliate_referral:update_amounts_earned'].invoke
# It can track the earnings.
Capybara.session_name = :affiliate
refresh
amount = (order.price * AffiliateReferral::COMMISSION_PERCENTAGE).floor.to_f
amount_in_dom = find('.referral-amount-earned', match: :first).text.gsub(/[^\d\.]/, '').to_f * 100
expect(amount).to equal(amount_in_dom)
end
end
This will fail maybe 99% of the time. There is the odd case where it passes. I can get my test suite to eventually pass by running it on a loop for a day.
I ended up upgrading all versions to the latest (Node 10, latest Ember, latest Rails) but the issue persists.
I can post a sample repo that reproduces the issue later. I just wanted to get this posted in case anyone has encountered the issue.
Here's a typical stack trace when the timeout happens:
1.1) Failure/Error: page.evaluate_script('window.location.reload()')
Net::ReadTimeout:
Net::ReadTimeout
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/webmock-3.3.0/lib/webmock/http_lib_adapters/net_http.rb:97:in `block in request'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/webmock-3.3.0/lib/webmock/http_lib_adapters/net_http.rb:110:in `block in request'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/webmock-3.3.0/lib/webmock/http_lib_adapters/net_http.rb:109:in `request'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/http/default.rb:121:in `response_for'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/http/default.rb:76:in `request'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/http/common.rb:62:in `call'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/bridge.rb:164:in `execute'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/oss/bridge.rb:584:in `execute'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/oss/bridge.rb:267:in `execute_script'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/common/driver.rb:211:in `execute_script'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/capybara-3.8.2/lib/capybara/selenium/driver.rb:84:in `execute_script'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/capybara-3.8.2/lib/capybara/selenium/driver.rb:88:in `evaluate_script'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/capybara-3.8.2/lib/capybara/session.rb:575:in `evaluate_script'
# ./spec/support/selenium.rb:48:in `refresh'
# ./spec/support/pages.rb:70:in `signup_and_verify_email'
# ./spec/system/payment_spec.rb:43:in `block (3 levels) in <top (required)>'
I should point out it doesn't always happen with page.evaluate_script('window.location.reload()'). It can happen with something benign like visit('/').
Edit: I tried disabling Ember FastBoot (server-side rendering) using the DISABLE_FASTBOOT env variable and suddenly all tests pass. I'm thinking that somehow the RSpec fixtures are causing Ember FastBoot to not finish rendering in some cases. This certainly lines up with dropped connections I've occasionally seen in production logs.
I've been experimenting with the client code and it may be due to my use of FastBoot's deferRendering call.
Edit: I'm using the following versions:
ember-cli: 3.1.3
ember-data: 3.0.2
rails: 5.2.1
rspec: 3.8.0
capybara: 3.8.2
selenium-webdriver: 3.14.0
google chrome: 69.0.3497.100 (Official Build) (64-bit)
Edit: I'm using this somewhat flaky Node/Express library fastboot-app-server to do server-side rendering. I've discovered that it sometimes strips important response headers (Content-Type and Content-Encoding). I'm wondering if this is contributing to the issue.
Edit: I added a strict Content Security Policy to make sure there are no external requests running during the test suite that could be causing the Net::ReadTimeout.
I inspect the Chrome network tab at the point when it locks up and it seems to be loading nothing. Manually refreshing the browser allows the tests to pick up and continue running. How strange.
I've spent a couple weeks on this now and it may be time to give up on Selenium tests.
I upgraded to Chrome 70 and chromedriver 2.43. It didn't seem to make a difference.
I tried using the rspec-retry gem to force a refresh when the timeout occurs but the gem seems to fail to catch the timeout exception.
I've inspected the raw request to chromedriver where things hang. It looks like it's always POST http://127.0.0.1/session/<session id>/refresh. I tried refreshing in an alternate way: visit(page.current_path) which seems to fix things!
I finally got my test suite to pass by switching page.driver.browser.navigate.refresh to visit(page.current_path).
I know it's an ugly hack but it's the only thing I could find to get things working (see my various attempts in the question edits).
I looked at the request to chromedriver that was causing the timeouts each time: POST http://127.0.0.1/session/<session id>/refresh. I can only guess that it's some kind of issue with chromedriver. Perhaps incidentally, it only hangs when multiple chromedriver instances are active (which happens when multiple Capybara sessions are being used).
Edit: I needed to account for query params as well:
def refresh
query = URI.parse(page.current_url).query
path = page.current_path
path += "?#{query}" if query.present?
visit(path)
end
I tried just doing visit(page.current_url) but that was giving timeouts as well.

Session not getting initialized

I've been trying for the last 4 days to understand what has happened so that session isn't getting initialized anymore.
My app was working just fine, until one day suddenly i started having the error
undefined local variable or method `session' for < StaticPagesController:0x7c84728 >
I debugged it and tracked it down to request_forgery_protection.rb file, under action_controller\metal.
def form_authenticity_token
session[:_csrf_token] ||= SecureRandom.base64(32)
end
So, apparently, session wasn't being loaded.
I then tried suggestions from How force that session is loaded?, as you may see below.
def root
if signed_in?
...
else
session[:init] = true
session[:init]
#prospect = Prospect.new()
render 'retailers/retailers_home'
end
end
but still the same error keeps showing up.
Under my intializers, session_store.rb, everything seems fine :
RecibosOnline::Application.config.session_store :cookie_store, key: '_RecibosOnline_session'
as this exact code works on other developer's machine, as well as on other server.
So this must be something specific to my machine...but why??
What might be causing this?
When all else fails and nothing makes sense anymore, reset the world:
git clean -fdx

Delayed_job is no longer logging

For some reason, my Rails app is no longer logging my DelayedJob gem's activity, either to a separate log (delayed_job.log) or to the main Rails development log. I am also using the Workless gem, should this be relevant.
It used to say things like this:
2013-06-07T19:16:25-0400: [Worker(delayed_job.workless host:MyNames-MacBook-Pro.local pid:28504)] Starting job worker
2013-06-07T19:16:38-0400: [Worker(delayed_job.workless host:MyNames-MacBook-Pro.local pid:28504)] MyApp#scrape completed after 13.0290
2013-06-07T19:16:38-0400: [Worker(delayed_job.workless host:MyNames-MacBook-Pro.local pid:28504)] 1 jobs processed at 0.0761 j/s, 0 failed ...
2013-06-07T19:16:43-0400: [Worker(delayed_job.workless host:MyNames-MacBook-Pro.local pid:28504)] Exiting...
But nothing is appearing anymore in any of the logs.
How can I make sure that DelayedJob activity logs to the main development log? I don't mind if it also logs to a separate log, but the important thing is that I see its activity in my Mac's console.
I have searched for answers to this issue online (such as here and nothing is working.
Here is my delayed_job_config.rb: (in config/initializers)
Delayed::Worker.sleep_delay = 60
Delayed::Worker.max_attempts = 2
Delayed::Worker.max_run_time = 20.minutes
Delayed::Worker.logger = Rails.logger
Delayed::Worker.logger.auto_flushing = true
Please let me know if you'd like more code from my app - I'd be happy to provide it. Much thanks from a Rails newbie.
I finally got this to work. All thanks to Seamus Abshere's answer to the question here. I put what he posted below in an initializer file. This got delayed_job to log to my development.rb file (huzzah!).
However, delayed_job still isn't logging into my console (for reasons I still don't understand). I solved that by opening a new console tab and entering tail -f logs/development.log.
Different from what Seamus wrote, though, auto-flushing=true is deprecated in Rails 4 and my Heroku app crashed. I resolved this by removing it from my initializer file and placing it in my environments/development.rb file as config.autoflush_log = true. However, I found that neither of the two types of flushing were necessary to make this work.
Here is his code (without the auto-flushing):
file_handle = File.open("log/#{Rails.env}_delayed_jobs.log", (File::WRONLY | File::APPEND | File::CREAT))
# Be paranoid about syncing
file_handle.sync = true
# Hack the existing Rails.logger object to use our new file handle
Rails.logger.instance_variable_set :#log, file_handle
# Calls to Rails.logger go to the same object as Delayed::Worker.logger
Delayed::Worker.logger = Rails.logger
If the above code doesn't work, try replacing Rails.logger with RAILS_DEFAULT_LOGGER.

Getting super_exception_notifier to work

I've installed super_exception_notifier by running:
sudo gem install super_exception_notifier
and then I've tried enabling it in my project (which already has mailing working, since it sends emails for other purposes) like this. On environment.rb I added
# Notification configuration
require 'exception_notifier'
ExceptionNotifier.configure_exception_notifier do |config|
config[:exception_recipients] = %w(info#isitsciencefiction.com)
config[:notify_error_codes] = %W( 404 405 500 503 )
end
and on my application_controller.rb I have:
require 'exception_notifiable'
class ApplicationController < ActionController::Base
include ExceptionNotifiable
Am I missing something? because no matter what error I generate. Either a 404, a route error, division by zero in a controller or in the console, in development or production mode, I get no emails and no error messages or anything at all.
Any ideas?
Pablo,
Thanks for pointing out the holes in the documentation. I will setup a blank rails project and then clearly enumerate the steps. I have already updated the Readme in response to the tickets you created on github.
To help with you immediate problem this is how I have it setup, (and it works for me! :)
Not all parts of this are essential to it working, but I'm not editing it (much), so you can see what I have:
I have this in my environment.rb:
config.load_paths += %W( #{RAILS_ROOT}/app/middlewares #{RAILS_ROOT}/app/mixins #{RAILS_ROOT}/app/classes #{RAILS_ROOT}/app/mailers #{RAILS_ROOT}/app/observers )
I have an initializer in config/initializers/super_exception_notification.rb
#The constants ($) are set in the config/environments files.
ExceptionNotifier.configure_exception_notifier do |config|
config[:render_only] = false
config[:skip_local_notification] = false
config[:view_path] = 'app/views/errors'
config[:exception_recipients] = $ERROR_MAIL_RECIPIENTS
config[:send_email_error_codes] = $ERROR_STATUS_SEND_EMAIL
#config[:sender_address] = %("RINO #{(defined?(Rails) ? Rails.env : RAILS_ENV).humanize} Error" )
config[:sender_address] = "errors#swankywebdesign.com"
config[:email_prefix] = "[RINO #{(defined?(Rails) ? Rails.env : RAILS_ENV).capitalize} ERROR] "
end
Then in my application.rb I have this:
include ExceptionNotifiable, CustomEnvironments
alias :rescue_action_locally :rescue_action_in_public if Environments.local_environments.include?(Rails.env)
self.error_layout = 'errors'
self.exception_notifiable_verbose = false
self.exception_notifiable_silent_exceptions = [MethodDisabled]
Then I also have this mixin in my app/mixins directory:
module CustomEnvironments
module Environments
def self.local_environments
%w( development test )
end
def self.deployed_environments
%w( production staging )
end
end
end
One other thing, this plugin does not abolish the rails standard which is that things in public are trump. So if you have 404.html in public, it will always get rendered for 404's.
Peter
Maybe it has has something to do with this:
http://github.com/pboling/exception_notification
Email notifications will only occur when the IP address is determined not to be local. You can specify certain addresses to always be local so that you’ll get a detailed error instead of the generic error page. You do this in your controller (or even per-controller).
consider_local "64.72.18.143", "14.17.21.25"
You can specify subnet masks as well, so that all matching addresses are considered local:
consider_local "64.72.18.143/24"
The address "127.0.0.1" is always considered local. If you want to completely reset the list of all addresses (for instance, if you wanted "127.0.0.1" to NOT be considered local), you can simply do, somewhere in your controller:
local_addresses.clear

Resources