lib/rodauth/rails/feature.rb in rodauth-rails-0.11.0 vs lib/rodauth/rails/feature.rb in rodauth-rails-0.12.0

- old
+ new

@@ -1,234 +1,21 @@ module Rodauth Feature.define(:rails) do - depends :email_base + # Assign feature and feature configuration to constants for introspection. + Rodauth::Rails::Feature = self + Rodauth::Rails::FeatureConfiguration = self.configuration - # List of overridable methods. - auth_methods( - :rails_render, - :rails_csrf_tag, - :rails_csrf_param, - :rails_csrf_token, - :rails_check_csrf!, - :rails_controller, - ) + require "rodauth/rails/feature/base" + require "rodauth/rails/feature/callbacks" + require "rodauth/rails/feature/csrf" + require "rodauth/rails/feature/render" + require "rodauth/rails/feature/email" + require "rodauth/rails/feature/instrumentation" - auth_cached_method :rails_controller_instance - - # Renders templates with layout. First tries to render a user-defined - # template, otherwise falls back to Rodauth's template. - def view(page, *) - rails_render(action: page.tr("-", "_"), layout: true) || - rails_render(html: super.html_safe, layout: true) - end - - # Renders templates without layout. First tries to render a user-defined - # template or partial, otherwise falls back to Rodauth's template. - def render(page) - rails_render(partial: page.tr("-", "_"), layout: false) || - rails_render(action: page.tr("-", "_"), layout: false) || - super.html_safe - end - - # Render Rails CSRF tags in Rodauth templates. - def csrf_tag(*) - rails_csrf_tag - end - - # Verify Rails' authenticity token. - def check_csrf - rails_check_csrf! - end - - # Have Rodauth call #check_csrf automatically. - def check_csrf? - true - end - - # Reset Rails session to protect from session fixation attacks. - def clear_session - rails_controller_instance.reset_session - end - - # Default the flash error key to Rails' default :alert. - def flash_error_key - :alert - end - - # Evaluates the block in context of a Rodauth controller instance. - def rails_controller_eval(&block) - rails_controller_instance.instance_exec(&block) - end - - def button(*) - super.html_safe - end - - delegate :rails_routes, :rails_request, to: :scope - - private - - # Runs controller callbacks and rescue handlers around Rodauth actions. - def _around_rodauth(&block) - result = nil - - rails_instrument_request do - rails_controller_rescue do - rails_controller_callbacks do - result = catch(:halt) { super(&block) } - end - end - - result = handle_rails_controller_response(result) - end - - throw :halt, result if result - end - - # Handles controller rendering a response or setting response headers. - def handle_rails_controller_response(result) - if rails_controller_instance.performed? - rails_controller_response - elsif result - result[1].merge!(rails_controller_instance.response.headers) - result - end - end - - # Runs any #(before|around|after)_action controller callbacks. - def rails_controller_callbacks - # don't verify CSRF token as part of callbacks, Rodauth will do that - rails_controller_forgery_protection { false } - - rails_controller_instance.run_callbacks(:process_action) do - # turn the setting back to default so that form tags generate CSRF tags - rails_controller_forgery_protection { rails_controller.allow_forgery_protection } - - yield - end - end - - # Runs any registered #rescue_from controller handlers. - def rails_controller_rescue - yield - rescue Exception => exception - rails_controller_instance.rescue_with_handler(exception) || raise - - unless rails_controller_instance.performed? - raise Rodauth::Rails::Error, "rescue_from handler didn't write any response" - end - end - - def rails_instrument_request - ActiveSupport::Notifications.instrument("start_processing.rodauth", rodauth: self) - ActiveSupport::Notifications.instrument("process_request.rodauth", rodauth: self) do |payload| - begin - status, headers, body = yield - payload[:status] = status || 404 - payload[:headers] = headers - payload[:body] = body - ensure - rails_controller_instance.send(:append_info_to_payload, payload) - end - end - end - - # Returns Roda response from controller response if set. - def rails_controller_response - controller_response = rails_controller_instance.response - - response.status = controller_response.status - response.headers.merge! controller_response.headers - response.write controller_response.body - - response.finish - end - - # Create emails with ActionMailer which uses configured delivery method. - def create_email_to(to, subject, body) - Mailer.create_email(to: to, from: email_from, subject: "#{email_subject_prefix}#{subject}", body: body) - end - - # Delivers the given email. - def send_email(email) - email.deliver_now - end - - # Calls the Rails renderer, returning nil if a template is missing. - def rails_render(*args) - return if rails_api_controller? - - rails_controller_instance.render_to_string(*args) - rescue ActionView::MissingTemplate - nil - end - - # Calls the controller to verify the authenticity token. - def rails_check_csrf! - rails_controller_instance.send(:verify_authenticity_token) - end - - # Hidden tag with Rails CSRF token inserted into Rodauth templates. - def rails_csrf_tag - %(<input type="hidden" name="#{rails_csrf_param}" value="#{rails_csrf_token}">) - end - - # The request parameter under which to send the Rails CSRF token. - def rails_csrf_param - rails_controller.request_forgery_protection_token - end - - # The Rails CSRF token value inserted into Rodauth templates. - def rails_csrf_token - rails_controller_instance.send(:form_authenticity_token) - end - - # allows/disables forgery protection - def rails_controller_forgery_protection(&value) - return if rails_api_controller? - - rails_controller_instance.allow_forgery_protection = value.call - end - - # Instances of the configured controller with current request's env hash. - def _rails_controller_instance - controller = rails_controller.new - prepare_rails_controller(controller, rails_request) - controller - end - - if ActionPack.version >= Gem::Version.new("5.0") - def prepare_rails_controller(controller, rails_request) - controller.set_request! rails_request - controller.set_response! rails_controller.make_response!(rails_request) - end - else - def prepare_rails_controller(controller, rails_request) - controller.send(:set_response!, rails_request) - controller.instance_variable_set(:@_request, rails_request) - end - end - - def rails_api_controller? - defined?(ActionController::API) && rails_controller <= ActionController::API - end - - def rails_controller - if only_json? && Rodauth::Rails.api_only? - ActionController::API - else - ActionController::Base - end - end - - # ActionMailer subclass for correct email delivering. - class Mailer < ActionMailer::Base - def create_email(**options) - mail(**options) - end - end + include Rodauth::Rails::Feature::Base + include Rodauth::Rails::Feature::Callbacks + include Rodauth::Rails::Feature::Csrf + include Rodauth::Rails::Feature::Render + include Rodauth::Rails::Feature::Email + include Rodauth::Rails::Feature::Instrumentation end - - # Assign feature and feature configuration to constants for introspection. - Rails::Feature = FEATURES[:rails] - Rails::FeatureConfiguration = FEATURES[:rails].configuration end