discourse/lib/middleware/omniauth_bypass_middleware.rb
David Taylor 750802bf56
UX: Improve error handling for common OmniAuth exceptions (#7991)
This displays more useful messages for the most common issues we see:
- CSRF (when the user switches browser)
- Invalid IAT (when the server clock is wrong)
- OAuth::Unauthorized for OAuth1 providers, when the credentials are incorrect

This commit also stops earlier for disabled authenticators. Now we stop at the request phase, rather than the callback phase.
2019-08-12 10:55:02 +01:00

68 lines
2.5 KiB
Ruby

# frozen_string_literal: true
require "csrf_token_verifier"
# omniauth loves spending lots cycles in its magic middleware stack
# this middleware bypasses omniauth middleware and only hits it when needed
class Middleware::OmniauthBypassMiddleware
class AuthenticatorDisabled < StandardError; end
def initialize(app, options = {})
@app = app
Discourse.plugins.each(&:notify_before_auth)
# if you need to test this and are having ssl issues see:
# http://stackoverflow.com/questions/6756460/openssl-error-using-omniauth-specified-ssl-path-but-didnt-work
# OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
@omniauth = OmniAuth::Builder.new(app) do
Discourse.authenticators.each do |authenticator|
authenticator.register_middleware(self)
end
end
@omniauth.before_request_phase do |env|
# Check for CSRF token
CSRFTokenVerifier.new.call(env)
# Check whether the authenticator is enabled
if !Discourse.enabled_authenticators.any? { |a| a.name == env['omniauth.strategy'].name }
raise AuthenticatorDisabled
end
# If the user is trying to reconnect to an existing account, store in session
request = ActionDispatch::Request.new(env)
request.session[:auth_reconnect] = !!request.params["reconnect"]
end
end
def call(env)
if env["PATH_INFO"].start_with?("/auth")
begin
@omniauth.call(env)
rescue AuthenticatorDisabled => e
# Authenticator is disabled, pretend it doesn't exist and pass request to app
@app.call(env)
rescue OAuth::Unauthorized => e
# OAuth1 (i.e. Twitter) makes a web request during the setup phase
# If it fails, Omniauth does not handle the error. Handle it here
env["omniauth.error.type"] ||= "request_error"
Rails.logger.error "Authentication failure! request_error: #{e.class}, #{e.message}"
OmniAuth::FailureEndpoint.call(env)
rescue JWT::InvalidIatError => e
# Happens for openid-connect (including google) providers, when the server clock is wrong
env["omniauth.error.type"] ||= "invalid_iat"
Rails.logger.error "Authentication failure! invalid_iat: #{e.class}, #{e.message}"
OmniAuth::FailureEndpoint.call(env)
rescue CSRFTokenVerifier::InvalidCSRFToken => e
# Happens when CSRF token is missing from request
env["omniauth.error.type"] ||= "csrf_detected"
OmniAuth::FailureEndpoint.call(env)
end
else
@app.call(env)
end
end
end