lib/kingsman.rb in kingsman-0.0.0.beta vs lib/kingsman.rb in kingsman-0.1.0

- old
+ new

@@ -1,8 +1,432 @@ -# frozen_string_literal: true +$:.unshift(File.expand_path("../", __FILE__)) -require_relative "kingsman/version" +require "kingsman/autoloader" +Kingsman::Autoloader.setup +require "memoist" +require "rainbow/ext/string" +require "jets-responders" + module Kingsman class Error < StandardError; end - # Your code goes here... + + ALL = [] + CONTROLLERS = {} + ROUTES = {} + STRATEGIES = {} + URL_HELPERS = {} + + # Strategies that do not require user input. + NO_INPUT = [] + + # True values used to check params + TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'] + + # Secret key used by the key generator + mattr_accessor :secret_key + @@secret_key = nil + + # Custom domain or key for cookies. Not set by default + mattr_accessor :rememberable_options + @@rememberable_options = {} + + # The number of times to hash the password. + mattr_accessor :stretches + @@stretches = 12 + + # The default key used when authenticating over http auth. + mattr_accessor :http_authentication_key + @@http_authentication_key = nil + + # Keys used when authenticating a user. + mattr_accessor :authentication_keys + @@authentication_keys = [:email] + + # Request keys used when authenticating a user. + mattr_accessor :request_keys + @@request_keys = [] + + # Keys that should be case-insensitive. + mattr_accessor :case_insensitive_keys + @@case_insensitive_keys = [:email] + + # Keys that should have whitespace stripped. + mattr_accessor :strip_whitespace_keys + @@strip_whitespace_keys = [:email] + + # If http authentication is enabled by default. + mattr_accessor :http_authenticatable + @@http_authenticatable = false + + # If http headers should be returned for ajax requests. True by default. + mattr_accessor :http_authenticatable_on_xhr + @@http_authenticatable_on_xhr = true + + # If params authenticatable is enabled by default. + mattr_accessor :params_authenticatable + @@params_authenticatable = true + + # The realm used in Http Basic Authentication. + mattr_accessor :http_authentication_realm + @@http_authentication_realm = "Application" + + # Email regex used to validate email formats. It asserts that there are no + # @ symbols or whitespaces in either the localpart or the domain, and that + # there is a single @ symbol separating the localpart and the domain. + mattr_accessor :email_regexp + @@email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # Range validation for password length + mattr_accessor :password_length + @@password_length = 6..128 + + # The time the user will be remembered without asking for credentials again. + mattr_accessor :remember_for + @@remember_for = 2.weeks + + # If true, extends the user's remember period when remembered via cookie. + mattr_accessor :extend_remember_period + @@extend_remember_period = false + + # If true, all the remember me tokens are going to be invalidated when the user signs out. + mattr_accessor :expire_all_remember_me_on_sign_out + @@expire_all_remember_me_on_sign_out = true + + # Time interval you can access your account before confirming your account. + # nil - allows unconfirmed access for unlimited time + mattr_accessor :allow_unconfirmed_access_for + @@allow_unconfirmed_access_for = 0.days + + # Time interval the confirmation token is valid. nil = unlimited + mattr_accessor :confirm_within + @@confirm_within = nil + + # Defines which key will be used when confirming an account. + mattr_accessor :confirmation_keys + @@confirmation_keys = [:email] + + # Defines if email should be reconfirmable. + mattr_accessor :reconfirmable + @@reconfirmable = true + + # Time interval to timeout the user session without activity. + mattr_accessor :timeout_in + @@timeout_in = 30.minutes + + # Used to hash the password. Please generate one with rails secret. + mattr_accessor :pepper + @@pepper = nil + + # Used to send notification to the original user email when their email is changed. + mattr_accessor :send_email_changed_notification + @@send_email_changed_notification = false + + # Used to enable sending notification to user when their password is changed. + mattr_accessor :send_password_change_notification + @@send_password_change_notification = false + + # Scoped views. Since it relies on fallbacks to render default views, it's + # turned off by default. + mattr_accessor :scoped_views + @@scoped_views = false + + # Defines which strategy can be used to lock an account. + # Values: :failed_attempts, :none + mattr_accessor :lock_strategy + @@lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + mattr_accessor :unlock_keys + @@unlock_keys = [:email] + + # Defines which strategy can be used to unlock an account. + # Values: :email, :time, :both + mattr_accessor :unlock_strategy + @@unlock_strategy = :both + + # Number of authentication tries before locking an account + mattr_accessor :maximum_attempts + @@maximum_attempts = 20 + + # Time interval to unlock the account if :time is defined as unlock_strategy. + mattr_accessor :unlock_in + @@unlock_in = 1.hour + + # Defines which key will be used when recovering the password for an account + mattr_accessor :reset_password_keys + @@reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key + mattr_accessor :reset_password_within + @@reset_password_within = 6.hours + + # When set to false, resetting a password does not automatically sign in a user + mattr_accessor :sign_in_after_reset_password + @@sign_in_after_reset_password = true + + # The default scope which is used by warden. + mattr_accessor :default_scope + @@default_scope = nil + + # Address which sends Kingsman e-mails. + mattr_accessor :mailer_sender + @@mailer_sender = nil + + # Skip session storage for the following strategies + mattr_accessor :skip_session_storage + @@skip_session_storage = [:http_auth] + + # Which formats should be treated as navigational. + mattr_accessor :navigational_formats + @@navigational_formats = ["*/*", :html, :turbo_stream] + + # The default responder used by Kingsman, used to customize status codes with: + # + # `config.responder.error_status` + # `config.responder.redirect_status` + # + # Can be replaced by a custom application responder. + mattr_accessor :responder + @@responder = Kingsman::Controllers::Responder + + # When set to true, signing out a user signs out all other scopes. + mattr_accessor :sign_out_all_scopes + @@sign_out_all_scopes = true + + # The default method used while signing out + mattr_accessor :sign_out_via + @@sign_out_via = :delete + + # The parent controller all Kingsman controllers inherits from. + # Defaults to ApplicationController. This should be set early + # in the initialization process and should be set to a string. + mattr_accessor :parent_controller + @@parent_controller = "ApplicationController" + + # The parent mailer all Kingsman mailers inherit from. + # Defaults to ActionMailer::Base. This should be set early + # in the initialization process and should be set to a string. + mattr_accessor :parent_mailer + @@parent_mailer = "ActionMailer::Base" + + # The router Kingsman should use to generate routes. Defaults + # to :main_app. Should be overridden by engines in order + # to provide custom routes. + mattr_accessor :router_name + @@router_name = nil + + # Set the OmniAuth path prefix so it can be overridden when + # Kingsman is used in a mountable engine + mattr_accessor :omniauth_path_prefix + @@omniauth_path_prefix = nil + + # Set if we should clean up the CSRF Token on authentication + mattr_accessor :clean_up_csrf_token_on_authentication + @@clean_up_csrf_token_on_authentication = true + + # When false, Kingsman will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Kingsman mappings to be loaded during boot time the application + # won't boot properly. + mattr_accessor :reload_routes + @@reload_routes = true + + # PRIVATE CONFIGURATION + + # Store scopes mappings. + mattr_reader :mappings + @@mappings = {} + + # OmniAuth configurations. + mattr_reader :omniauth_configs + @@omniauth_configs = {} + + # Private methods to interface with Warden. + mattr_accessor :warden_config + @@warden_config = nil + @@warden_config_blocks = [] + + # When true, enter in paranoid mode to avoid user enumeration. + mattr_accessor :paranoid + @@paranoid = false + + # When true, warn user if they just used next-to-last attempt of authentication + mattr_accessor :last_attempt_warning + @@last_attempt_warning = true + + # Stores the token generator + mattr_accessor :token_generator + @@token_generator = nil + + # When set to false, changing a password does not automatically sign in a user + mattr_accessor :sign_in_after_change_password + @@sign_in_after_change_password = true + + def self.available_router_name + router_name || :main_app + end + + def self.omniauth_providers + omniauth_configs.keys + end + + # Get the mailer class from the mailer reference object. + def self.mailer + @@mailer_ref.constantize + end + + # Set the mailer reference object to access the mailer. + def self.mailer=(class_name) + @@mailer_ref = class_name + end + self.mailer = "Kingsman::Mailer" + + def self.add_mapping(resource, options) + mapping = Mapping.new(resource, options) + @@mappings[mapping.name] = mapping + @@default_scope ||= mapping.name + Kingsman::Controllers::Helpers.define_helpers(mapping) + mapping # important to return the mapping + end + + def self.add_module(module_name, options = {}) + ALL.insert (options[:insert_at] || -1), module_name + + if strategy = options[:strategy] + strategy = (strategy == true ? module_name : strategy) + STRATEGIES[module_name] = strategy + end + + if controller = options[:controller] + controller = (controller == true ? module_name : controller) + CONTROLLERS[module_name] = controller + end + + NO_INPUT << strategy if options[:no_input] + + if route = options[:route] + case route + when TrueClass + key, value = module_name, [] + when Symbol + key, value = route, [] + when Hash + key, value = route.keys.first, route.values.flatten + else + raise ArgumentError, ":route should be true, a Symbol or a Hash" + end + + URL_HELPERS[key] ||= [] + URL_HELPERS[key].concat(value) + URL_HELPERS[key].uniq! + + ROUTES[module_name] = key + end + + if options[:model] + path = (options[:model] == true ? "kingsman/models/#{module_name}" : options[:model]) + camelized = ActiveSupport::Inflector.camelize(module_name.to_s) + Kingsman::Models.send(:autoload, camelized.to_sym, path) + end + + Kingsman::Mapping.add_module module_name + end + + # Sets warden configuration using a block that will be invoked on warden + # initialization. + # + # Kingsman.setup do |config| + # config.allow_unconfirmed_access_for = 2.days + # + # config.warden do |manager| + # # Configure warden to use other strategies, like oauth. + # manager.oauth(:twitter) + # end + # end + def self.warden(&block) + @@warden_config_blocks << block + end + + # Specify an OmniAuth provider. + # + # config.omniauth :github, APP_ID, APP_SECRET + # + def self.omniauth(provider, *args) + config = Kingsman::OmniAuth::Config.new(provider, args) + @@omniauth_configs[config.strategy_name.to_sym] = config + end + + # Include helpers in the given scope to AC and AV. + def self.include_helpers(scope) + ActiveSupport.on_load(:jets_controller) do + include scope::Helpers if defined?(scope::Helpers) + include scope::UrlHelpers + end + + ActiveSupport.on_load(:action_view) do + include scope::UrlHelpers + end + end + + # Regenerates url helpers considering Kingsman.mapping + def self.regenerate_helpers! + Kingsman::Controllers::UrlHelpers.remove_helpers! + Kingsman::Controllers::UrlHelpers.generate_helpers! + end + + # A method used internally to complete the setup of warden manager after routes are loaded. + # See lib/kingsman/rails/routes.rb - ActionDispatch::Routing::RouteSet#finalize_with_kingsman! + def self.configure_warden! #:nodoc: + @@warden_configured ||= begin + # warden_config.failure_app = Kingsman::SessionsController.action(:unauthenticated) + warden_config.failure_app = Kingsman::Delegator.new + warden_config.default_scope = Kingsman.default_scope + warden_config.intercept_401 = false + + Kingsman.mappings.each_value do |mapping| + warden_config.scope_defaults mapping.name, strategies: mapping.strategies + + warden_config.serialize_into_session(mapping.name) do |record| + mapping.to.serialize_into_session(record) + end + + warden_config.serialize_from_session(mapping.name) do |args| + mapping.to.serialize_from_session(*args) + end + end + + @@warden_config_blocks.map { |block| block.call Kingsman.warden_config } + true + end + end + + # Generate a friendly string randomly to be used as token. + # By default, length is 20 characters. + def self.friendly_token(length = 20) + # To calculate real characters, we must perform this operation. + # See SecureRandom.urlsafe_base64 + rlength = (length * 3) / 4 + SecureRandom.urlsafe_base64(rlength).tr('lIO0', 'sxyz') + end + + # constant-time comparison algorithm to prevent timing attacks + def self.secure_compare(a, b) + return false if a.blank? || b.blank? || a.bytesize != b.bytesize + l = a.unpack "C#{a.bytesize}" + + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 + end + + # Default way to set up Kingsman. Run jets generate kingsman:install to create + # a fresh initializer with all configuration values. + def self.setup + yield self + end end + +require "warden" +require "kingsman/models" +require "kingsman/modules" +require "kingsman/jets"