class Roda module RodaPlugins module Rodauth Base = Feature.define(:base) do auth_value_methods( :account_id, :account_model, :account_open_status_value, :account_password_hash_column, :account_status_id, :account_unverified_status_value, :default_redirect, :email_from, :email_subject_prefix, :login_column, :login_confirm_label, :login_confirm_param, :login_label, :login_param, :logins_do_not_match_message, :no_matching_login_message, :password_confirm_label, :password_confirm_param, :password_does_not_meet_requirements_message, :password_hash_column, :password_hash_cost, :password_hash_table, :password_label, :password_minimum_length, :password_param, :passwords_do_not_match_message, :prefix, :require_login_notice_message, :require_login_redirect, :session_key, :skip_status_checks?, :title_instance_variable ) auth_methods( :account_from_login, :account_from_session, :account_id_value, :account_session_value, :after_close_account, :already_logged_in, :clear_session, :create_email, :email_to, :logged_in?, :login_errors_message, :login_required, :open_account?, :password_hash, :password_meets_requirements?, :random_key, :session_value, :set_error_flash, :set_notice_flash, :set_password, :set_redirect_error_flash, :set_title, :unverified_account_message, :update_session ) attr_reader :scope attr_reader :account def initialize(scope) @scope = scope end def features self.class.features end def request scope.request end def response scope.response end def session scope.session end def flash scope.flash end # Overridable methods def account_id_value account.send(account_id) end alias account_session_value account_id_value def session_value session[session_key] end def account_status_id_value account.send(account_status_id) end def _account_from_login(login) @account = account_from_login(login) end def account_from_login(login) ds = account_model.where(login_column=>login) ds = ds.where(account_status_id=>[account_unverified_status_value, account_open_status_value]) unless skip_status_checks? ds.first end def open_account? skip_status_checks? || account_status_id_value == account_open_status_value end def unverified_account_message "unverified account, please verify account before logging in" end def update_session clear_session session[session_key] = account_session_value end def check_before(feature) meth = :"check_before_#{feature.feature_name}" if respond_to?(meth) send(meth) elsif feature.account_required? require_account elsif logged_in? already_logged_in end end def account_model ::Account end def db account_model.db end # If the account_password_hash_column is set, the password hash is verified in # ruby, it will not use a database function to do so, it will check the password # hash using bcrypt. def account_password_hash_column nil end def already_logged_in nil end def clear_session session.clear end def default_redirect '/' end def require_login_redirect "#{prefix}/login" end def require_login_notice_message "Please login to continue" end def prefix '' end def login_required set_notice_flash require_login_notice_message request.redirect require_login_redirect end def random_key require 'securerandom' if RUBY_VERSION >= '1.9' SecureRandom.urlsafe_base64(32) else # :nocov: SecureRandom.hex(32) # :nocov: end end def title_instance_variable nil end def set_title(title) if title_instance_variable scope.instance_variable_set(title_instance_variable, title) end end def set_error_flash(message) flash.now[:error] = message end def set_redirect_error_flash(message) flash[:error] = message end def set_notice_flash(message) flash[:notice] = message end def login_column :email end def password_hash_column :password_hash end def password_hash_table :account_password_hashes end def no_matching_login_message "no matching login" end def logged_in? session[session_key] end def require_login login_required unless logged_in? end def require_account require_login unless _account_from_session clear_session login_required end end def login_param 'login' end def login_confirm_param 'login-confirm' end def login_label 'Login' end def login_confirm_label "Confirm #{login_label}" end def password_label 'Password' end def password_confirm_label "Confirm #{password_label}" end def login_errors_message if errors = account.errors.on(login_column) errors.join(', ') end end def logins_do_not_match_message 'logins do not match' end def password_param 'password' end def password_confirm_param 'password-confirm' end def session_key :account_id end def account_id :id end def account_status_id :status_id end def passwords_do_not_match_message 'passwords do not match' end def password_does_not_meet_requirements_message "invalid password, does not meet requirements (minimum #{password_minimum_length} characters)" end def password_minimum_length 6 end def password_meets_requirements?(password) password_minimum_length <= password.length end def account_unverified_status_value 1 end def account_open_status_value 2 end def account_initial_status_value account_open_status_value end def _account_from_session @account = account_from_session end def account_from_session ds = account_model.where(account_id=>scope.session[session_key]) ds = ds.where(account_status_id=>account_open_status_value) unless skip_status_checks? ds.first end def password_hash_cost require 'bcrypt' if ENV['RACK_ENV'] == 'test' BCrypt::Engine::MIN_COST else # :nocov: BCrypt::Engine::DEFAULT_COST # :nocov: end end def password_hash(password) require 'bcrypt' BCrypt::Password.create(password, :cost=>password_hash_cost) end def set_password(password) hash = password_hash(password) if account_password_hash_column account.set(account_password_hash_column=>hash).save_changes(:raise_on_save_failure=>true) else if db[password_hash_table].where(account_id=>account_id_value).update(password_hash_column=>hash) == 0 db[password_hash_table].insert(account_id=>account_id_value, password_hash_column=>hash) end end end def transaction(&block) db.transaction(&block) end def email_from "webmaster@#{request.host}" end def email_to account.email end def create_email(subject, body) require 'mail' m = Mail.new m.from = email_from m.to = email_to m.subject = "#{email_subject_prefix}#{subject}" m.body = body m end def email_subject_prefix nil end def view(page, title) set_title(title) _view(:view, page) end def render(page) _view(:render, page) end def skip_status_checks? false end def after_close_account end private def _view(meth, page) auth = self scope.instance_exec do template_opts = find_template(parse_template_opts(page, :locals=>{:rodauth=>auth})) unless File.file?(template_path(template_opts)) template_opts[:path] = File.join(File.dirname(__FILE__), '../../../../templates', "#{page}.str") end send(meth, template_opts) end end end end end end