Module: Authgasm::ActsAsAuthentic::ClassMethods

Defined in:
lib/authgasm/acts_as_authentic.rb

Overview

Acts As Authentic

Provides and “acts_as” method to include in your models to help with authentication. See method below.

Instance Method Summary collapse

Instance Method Details

#acts_as_authentic(options = {}) ⇒ Object

Call this method in your model to add in basic authentication madness:

  1. Adds various validations for the login field

  2. Adds various validations for the password field

  3. Handles password encryption

  4. Adds usefule methods to dealing with authentication

Methods

For example purposes lets assume you have a User model.

Class method name           Description
User.unique_token           returns unique token generated by your :crypto_provider
User.crypto_provider        The class that you set in your :crypto_provider option
User.forget_all!            Resets all records so they will not be remembered on their next visit. Basically makes their cookies invalid

Named Scopes
User.logged_in              Find all users who are logged in, based on your :logged_in_timeout option
User.logged_out             Same as above, but logged out

Isntace method name
user.password=              Method name based on the :password_field option. This is used to set the password. Pass the *raw* password to this
user.confirm_password=      Confirms the password, needed to change the password
user.valid_password?(pass)  Based on the valid of :password_field. Determines if the password passed is valid. The password could be encrypted or raw.
user.randomize_password!    Basically resets the password to a random password using only letters and numbers
user.logged_in?             Based on the :logged_in_timeout option. Tells you if the user is logged in or not
user.forget!                Changes their remember token, making their cookie invalid.

Options

  • session_class: default: “#nameSession”, the related session class. Used so that you don’t have to repeat yourself here. A lot of the configuration will be based off of the configuration values of this class.

  • crypto_provider: default: Authgasm::Sha256CryptoProvider, class that provides Sha256 encryption. What ultimately encrypts your password.

  • crypto_provider_type: default: options.respond_to?(:decrypt) ? :encryption : :hash. You can explicitly set this if you wish. Since encryptions and hashes are handled different this is the flag Authgasm uses.

  • login_field: default: options.login_field, the name of the field used for logging in

  • login_field_type: default: options == :email ? :email : :login, tells authgasm how to validation the field, what regex to use, etc.

  • password_field: default: options.password_field, the name of the field to set the password, NOT the field the encrypted password is stored

  • crypted_password_field: default: depends on which columns are present, checks: crypted_password, encrypted_password, password_hash, pw_hash, if none are present defaults to crypted_password. This is the name of column that your encrypted password is stored.

  • password_salt_field: default: depends on which columns are present, checks: password_salt, pw_salt, salt, if none are present defaults to password_salt. This is the name of the field your salt is stored, only relevant for a hash crypto provider.

  • remember_token_field: default: options.remember_token_field, the name of the field your remember token is stored. What the cookie stores so the session can be “remembered”

  • logged_in_timeout: default: 10.minutes, this allows you to specify a time the determines if a user is logged in or out. Useful if you want to count how many users are currently logged in.

  • session_ids: default: [nil], the sessions that we want to automatically reset when a user is created or updated so you don’t have to worry about this. Set to [] to disable. Should be an array of ids. See Authgasm::Session::Base#initialize for information on ids. The order is important. The first id should be your main session, the session they need to log into first. This is generally nil, meaning so explicitly set id.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/authgasm/acts_as_authentic.rb', line 49

def acts_as_authentic(options = {})
  # Setup default options
  options[:session_class] ||= "#{name}Session".constantize
  options[:crypto_provider] ||= Sha256CryptoProvider
  options[:crypto_provider_type] ||= options[:crypto_provider].respond_to?(:decrypt) ? :encryption : :hash
  options[:login_field] ||= options[:session_class].
  options[:login_field_type] ||= options[:login_field] == :email ? :email : :login
  options[:password_field] ||= options[:session_class].password_field
  options[:crypted_password_field] ||=
    (columns.include?("crypted_password") && :crypted_password) ||
    (columns.include?("encrypted_password") && :encrypted_password) ||
    (columns.include?("password_hash") && :password_hash) ||
    (columns.include?("pw_hash") && :pw_hash) ||
    :crypted_password
  options[:password_salt_field] ||= 
    (columns.include?("password_salt") && :password_salt) ||
    (columns.include?("pw_salt") && :pw_salt) ||
    (columns.include?("salt") && :salt) ||
    :password_salt
  options[:remember_token_field] ||= options[:session_class].remember_token_field
  options[:logged_in_timeout] ||= 10.minutes
  options[:session_ids] ||= [nil]
  
  # Validations
  case options[:login_field_type]
  when :email
    validates_length_of options[:login_field], :within => 6..100
    email_name_regex  = '[\w\.%\+\-]+'
    domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
    domain_tld_regex  = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'
    email_regex       = /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
    validates_format_of options[:login_field], :with => email_regex, :message => "should look like an email address."
  else
    validates_length_of options[:login_field], :within => 2..100
    validates_format_of options[:login_field], :with => /\A\w[\w\.\-_@]+\z/, :message => "use only letters, numbers, and .-_@ please."
  end
  
  validates_uniqueness_of options[:login_field]
  validate :validate_password
  validates_numericality_of :login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
  
  if column_names.include?("last_click_at")
    named_scope :logged_in, lambda { {:conditions => ["last_click_at > ?", options[:logged_in_timeout].ago]} }
    named_scope :logged_out, lambda { {:conditions => ["last_click_at <= ?", options[:logged_in_timeout].ago]} }
  end
  
  after_create :create_sessions!
  after_update :update_sessions!
  
  # Attributes
  attr_writer "confirm_#{options[:password_field]}"
  attr_accessor "tried_to_set_#{options[:password_field]}"
  attr_protected "tried_to_set_#{options[:password_field]}"
  
  # Class methods
  class_eval "    def self.unique_token\n      crypto_provider.encrypt(Time.now.to_s + (1..10).collect{ rand.to_s }.join)\n    end\n    \n    def self.crypto_provider\n      \#{options[:crypto_provider]}\n    end\n    \n    def self.forget_all!\n      # Paginate these to save on memory\n      records = nil\n      i = 0\n      begin\n        records = find(:all, :limit => 50, :offset => i)\n        records.each { |record| records.update_attribute(:\#{options[:remember_token_field]}, unique_token) }\n        i += 50\n      end while !records.blank?\n    end\n  end_eval\n  \n  # Instance methods\n  if column_names.include?(\"last_click_at\")\n    class_eval <<-\"end_eval\", __FILE__, __LINE__\n      def logged_in?\n        !last_click_at.nil? && last_click_at > \#{options[:logged_in_timeout].to_i}.seconds.ago\n      end\n    end_eval\n  end\n  \n  case options[:crypto_provider_type]\n  when :hash\n    class_eval <<-\"end_eval\", __FILE__, __LINE__\n      def \#{options[:password_field]}=(pass)\n        return if pass.blank?\n        self.tried_to_set_\#{options[:password_field]} = true\n        @\#{options[:password_field]} = pass\n        self.\#{options[:remember_token_field]} = self.class.unique_token\n        self.\#{options[:password_salt_field]} = self.class.unique_token\n        self.\#{options[:crypted_password_field]} = crypto_provider.encrypt(@\#{options[:password_field]} + \#{options[:password_salt_field]})\n      end\n      \n      def valid_\#{options[:password_field]}?(attempted_password)\n        return false if attempted_password.blank?\n        attempted_password == \#{options[:crypted_password_field]} || \#{options[:crypted_password_field]} == crypto_provider.encrypt(attempted_password + \#{options[:password_salt_field]})\n      end\n    end_eval\n  when :encryption\n    class_eval <<-\"end_eval\", __FILE__, __LINE__\n      def \#{options[:password_field]}=(pass)\n        return if pass.blank?\n        self.tried_to_set_\#{options[:password_field]} = true\n        @\#{options[:password_field]} = pass\n        self.\#{options[:remember_token_field]} = self.class.unique_token\n        self.\#{options[:crypted_password_field]} = crypto_provider.encrypt(@\#{options[:password_field]})\n      end\n    \n      def valid_\#{options[:password_field]}?(attemtped_password)\n        return false if attempted_password.blank?\n        attempted_password == \#{options[:crypted_password_field]} || \#{options[:crypted_password_field]} = crypto_provider.decrypt(attempted_password)\n      end\n    end_eval\n  end\n  \n  class_eval <<-\"end_eval\", __FILE__, __LINE__\n    def \#{options[:password_field]}; end\n    def confirm_\#{options[:password_field]}; end\n    \n    def crypto_provider\n      self.class.crypto_provider\n    end\n    \n    def forget!\n      update_attribute(:\#{options[:remember_token_field]}, self.class.unique_token)\n    end\n    \n    def randomize_\#{options[:password_field]}!\n      chars = (\"a\"..\"z\").to_a + (\"A\"..\"Z\").to_a + (\"0\"..\"9\").to_a\n      newpass = \"\"\n      1.upto(10) { |i| newpass << chars[rand(chars.size-1)] }\n      self.\#{options[:password_field]} = newpass\n      self.confirm_\#{options[:password_field]} = newpass\n    end\n    \n    def save_from_session(*args)\n      @saving_from_session = true\n      result = save(*args)\n      @saving_from_session = false\n      result\n    end\n    \n    protected\n      def create_sessions!\n        return if !\#{options[:session_class]}.activated? || \#{options[:session_ids].inspect}.blank?\n        \n        # We only want to automatically login into the first session, since this is the main session. The other sessions are sessions\n        # that need to be created after logging into the main session.\n        session_id = \#{options[:session_ids].inspect}.first\n        \n        # If we are already logged in, ignore this completely. All that we care about is updating ourself.\n        next if \#{options[:session_class]}.find(*[session_id].compact)\n        \n        # Log me in\n        args = [self, session_id].compact\n        \#{options[:session_class]}.create(*args)\n      end\n      \n      def update_sessions!\n        return if @saving_from_session || !\#{options[:session_class]}.activated?\n        \n        \#{options[:session_ids].inspect}.each do |session_id|\n          session = \#{options[:session_class]}.find(*[session_id].compact)\n          \n          # Ignore if we can't find the session or the session isn't this record\n          next if !session || session.record != self\n          \n          # We know we are logged in and this is our record, update the session\n          session.save\n        end\n      end\n      \n      def tried_to_set_password?\n        tried_to_set_password == true\n      end\n      \n      def validate_password\n        if new_record? || tried_to_set_\#{options[:password_field]}?\n          if @\#{options[:password_field]}.blank?\n            errors.add(:\#{options[:password_field]}, \"can not be blank\")\n          else\n            errors.add(:confirm_\#{options[:password_field]}, \"did not match\") if @confirm_\#{options[:password_field]} != @\#{options[:password_field]}\n          end\n        end\n      end\n  end_eval\nend\n", __FILE__, __LINE__