# 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

    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