mirror of
https://github.com/discourse/discourse.git
synced 2025-02-28 10:57:58 +08:00

Previously, we would build the stack of omniauth authenticators once on boot. That meant that all strategies had to be included, even if they were disabled. We then used the `before_request_phase` to ensure disabled strategies could not be used. This works well, but it means that omniauth is often doing unnecessary work running logic in disabled strategies. This commit refactors things so that we build the stack of strategies on each request. That means we only need to include the enabled strategies in the stack - disabled strategies are totally ignored. Building the stack on-demand like this does add some overhead to auth requests, but on the majority of sites that will be significantly outweighed by the fact we're now skipping logic for disabled authenticators. As well as the slight performance improvement, this new approach means that: - Broken (i.e. exception-raising) strategies cannot cause issues on a site if they're disabled - `other_phase` of disabled strategies will never appear in the backtrace of other authentication errors
70 lines
2.6 KiB
Ruby
70 lines
2.6 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)
|
|
|
|
OmniAuth.config.before_request_phase do |env|
|
|
request = ActionDispatch::Request.new(env)
|
|
|
|
# Check for CSRF token in POST requests
|
|
CSRFTokenVerifier.new.call(env) if request.request_method.downcase.to_sym != :get
|
|
|
|
# If the user is trying to reconnect to an existing account, store in session
|
|
request.session[:auth_reconnect] = !!request.params["reconnect"]
|
|
|
|
# If the client provided an origin, store in session to redirect back
|
|
request.session[:destination_url] = request.params["origin"]
|
|
end
|
|
end
|
|
|
|
def call(env)
|
|
if env["PATH_INFO"].start_with?("/auth")
|
|
begin
|
|
# When only one provider is enabled, assume it can be completely trusted, and allow GET requests
|
|
only_one_provider =
|
|
!SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1
|
|
OmniAuth.config.allowed_request_methods = only_one_provider ? %i[get post] : [:post]
|
|
|
|
omniauth =
|
|
OmniAuth::Builder.new(@app) do
|
|
Discourse.enabled_authenticators.each do |authenticator|
|
|
authenticator.register_middleware(self)
|
|
end
|
|
end
|
|
|
|
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
|