-
Notifications
You must be signed in to change notification settings - Fork 637
/
base.rb
2097 lines (1896 loc) · 74 KB
/
base.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# frozen_string_literal: true
require "request_store"
module Authlogic
module Session
module Activation
# :nodoc:
class NotActivatedError < ::StandardError
def initialize
super(
"You must activate the Authlogic::Session::Base.controller with " \
"a controller object before creating objects"
)
end
end
end
module Existence
# :nodoc:
class SessionInvalidError < ::StandardError
def initialize(session)
message = I18n.t(
"error_messages.session_invalid",
default: "Your session is invalid and has the following errors:"
)
message += " #{session.errors.full_messages.to_sentence}"
super message
end
end
end
# This is the most important class in Authlogic. You will inherit this class
# for your own eg. `UserSession`.
#
# Ongoing consolidation of modules
# ================================
#
# We are consolidating modules into this class (inlining mixins). When we
# are done, there will only be this one file. It will be quite large, but it
# will be easier to trace execution.
#
# Once consolidation is complete, we hope to identify and extract
# collaborating objects. For example, there may be a "session adapter" that
# connects this class with the existing `ControllerAdapters`. Perhaps a
# data object or a state machine will reveal itself.
#
# Activation
# ==========
#
# Activating Authlogic requires that you pass it an
# Authlogic::ControllerAdapters::AbstractAdapter object, or a class that
# extends it. This is sort of like a database connection for an ORM library,
# Authlogic can't do anything until it is "connected" to a controller. If
# you are using a supported framework, Authlogic takes care of this for you.
#
# ActiveRecord Trickery
# =====================
#
# Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not
# ActiveRecord. That's the goal here. This is useful for the various rails
# helper methods such as form_for, error_messages_for, or any method that
# expects an ActiveRecord object. The point is to disguise the object as an
# ActiveRecord object so we can take advantage of the many ActiveRecord
# tools.
#
# Brute Force Protection
# ======================
#
# A brute force attacks is executed by hammering a login with as many password
# combinations as possible, until one works. A brute force attacked is generally
# combated with a slow hashing algorithm such as BCrypt. You can increase the cost,
# which makes the hash generation slower, and ultimately increases the time it takes
# to execute a brute force attack. Just to put this into perspective, if a hacker was
# to gain access to your server and execute a brute force attack locally, meaning
# there is no network lag, it would probably take decades to complete. Now throw in
# network lag and it would take MUCH longer.
#
# But for those that are extra paranoid and can't get enough protection, why not stop
# them as soon as you realize something isn't right? That's what this module is all
# about. By default the consecutive_failed_logins_limit configuration option is set to
# 50, if someone consecutively fails to login after 50 attempts their account will be
# suspended. This is a very liberal number and at this point it should be obvious that
# something is not right. If you wish to lower this number just set the configuration
# to a lower number:
#
# class UserSession < Authlogic::Session::Base
# consecutive_failed_logins_limit 10
# end
#
# Callbacks
# =========
#
# Between these callbacks and the configuration, this is the contract between me and
# you to safely modify Authlogic's behavior. I will do everything I can to make sure
# these do not change.
#
# Check out the sub modules of Authlogic::Session. They are very concise, clear, and
# to the point. More importantly they use the same API that you would use to extend
# Authlogic. That being said, they are great examples of how to extend Authlogic and
# add / modify behavior to Authlogic. These modules could easily be pulled out into
# their own plugin and become an "add on" without any change.
#
# Now to the point of this module. Just like in ActiveRecord you have before_save,
# before_validation, etc. You have similar callbacks with Authlogic, see the METHODS
# constant below. The order of execution is as follows:
#
# before_persisting
# persist
# after_persisting
# [save record if record.has_changes_to_save?]
#
# before_validation
# before_validation_on_create
# before_validation_on_update
# validate
# after_validation_on_update
# after_validation_on_create
# after_validation
# [save record if record.has_changes_to_save?]
#
# before_save
# before_create
# before_update
# after_update
# after_create
# after_save
# [save record if record.has_changes_to_save?]
#
# before_destroy
# [save record if record.has_changes_to_save?]
# after_destroy
#
# Notice the "save record if has_changes_to_save" lines above. This helps with performance. If
# you need to make changes to the associated record, there is no need to save the
# record, Authlogic will do it for you. This allows multiple modules to modify the
# record and execute as few queries as possible.
#
# **WARNING**: unlike ActiveRecord, these callbacks must be set up on the class level:
#
# class UserSession < Authlogic::Session::Base
# before_validation :my_method
# validate :another_method
# # ..etc
# end
#
# You can NOT define a "before_validation" method, this is bad practice and does not
# allow Authlogic to extend properly with multiple extensions. Please ONLY use the
# method above.
#
# HTTP Basic Authentication
# =========================
#
# Handles all authentication that deals with basic HTTP auth. Which is
# authentication built into the HTTP protocol:
#
# http://username:password@whatever.com
#
# Also, if you are not comfortable letting users pass their raw username and
# password you can use a single access token, as described below.
#
# Magic Columns
# =============
#
# Just like ActiveRecord has "magic" columns, such as: created_at and updated_at.
# Authlogic has its own "magic" columns too:
#
# * login_count - Increased every time an explicit login is made. This will *NOT*
# increase if logging in by a session, cookie, or basic http auth
# * failed_login_count - This increases for each consecutive failed login. See
# the consecutive_failed_logins_limit option for details.
# * last_request_at - Updates every time the user logs in, either by explicitly
# logging in, or logging in by cookie, session, or http auth
# * current_login_at - Updates with the current time when an explicit login is made.
# * last_login_at - Updates with the value of current_login_at before it is reset.
# * current_login_ip - Updates with the request ip when an explicit login is made.
# * last_login_ip - Updates with the value of current_login_ip before it is reset.
#
# Multiple Simultaneous Sessions
# ==============================
#
# See `id`. Allows you to separate sessions with an id, ultimately letting
# you create multiple sessions for the same user.
#
# Timeout
# =======
#
# Think about financial websites, if you are inactive for a certain period
# of time you will be asked to log back in on your next request. You can do
# this with Authlogic easily, there are 2 parts to this:
#
# 1. Define the timeout threshold:
#
# acts_as_authentic do |c|
# c.logged_in_timeout = 10.minutes # default is 10.minutes
# end
#
# 2. Enable logging out on timeouts
#
# class UserSession < Authlogic::Session::Base
# logout_on_timeout true # default if false
# end
#
# This will require a user to log back in if they are inactive for more than
# 10 minutes. In order for this feature to be used you must have a
# last_request_at datetime column in your table for whatever model you are
# authenticating with.
#
# Params
# ======
#
# This module is responsible for authenticating the user via params, which ultimately
# allows the user to log in using a URL like the following:
#
# https://www.domain.com?user_credentials=4LiXF7FiGUppIPubBPey
#
# Notice the token in the URL, this is a single access token. A single access token is
# used for single access only, it is not persisted. Meaning the user provides it,
# Authlogic grants them access, and that's it. If they want access again they need to
# provide the token again. Authlogic will *NEVER* try to persist the session after
# authenticating through this method.
#
# For added security, this token is *ONLY* allowed for RSS and ATOM requests. You can
# change this with the configuration. You can also define if it is allowed dynamically
# by defining a single_access_allowed? method in your controller. For example:
#
# class UsersController < ApplicationController
# private
# def single_access_allowed?
# action_name == "index"
# end
#
# Also, by default, this token is permanent. Meaning if the user changes their
# password, this token will remain the same. It will only change when it is explicitly
# reset.
#
# You can modify all of this behavior with the Config sub module.
#
# Perishable Token
# ================
#
# Maintains the perishable token, which is helpful for confirming records or
# authorizing records to reset their password. All that this module does is
# reset it after a session have been saved, just keep it changing. The more
# it changes, the tighter the security.
#
# See Authlogic::ActsAsAuthentic::PerishableToken for more information.
#
# Scopes
# ======
#
# Authentication can be scoped, and it's easy, you just need to define how you want to
# scope everything. See `.with_scope`.
#
# Unauthorized Record
# ===================
#
# Allows you to create session with an object. Ex:
#
# UserSession.create(my_user_object)
#
# Be careful with this, because Authlogic is assuming that you have already
# confirmed that the user is who he says he is.
#
# For example, this is the method used to persist the session internally.
# Authlogic finds the user with the persistence token. At this point we know
# the user is who he says he is, so Authlogic just creates a session with
# the record. This is particularly useful for 3rd party authentication
# methods, such as OpenID. Let that method verify the identity, once it's
# verified, pass the object and create a session.
#
# Magic States
# ============
#
# Authlogic tries to check the state of the record before creating the session. If
# your record responds to the following methods and any of them return false,
# validation will fail:
#
# Method name Description
# active? Is the record marked as active?
# approved? Has the record been approved?
# confirmed? Has the record been confirmed?
#
# Authlogic does nothing to define these methods for you, its up to you to define what
# they mean. If your object responds to these methods Authlogic will use them,
# otherwise they are ignored.
#
# What's neat about this is that these are checked upon any type of login. When
# logging in explicitly, by cookie, session, or basic http auth. So if you mark a user
# inactive in the middle of their session they wont be logged back in next time they
# refresh the page. Giving you complete control.
#
# Need Authlogic to check your own "state"? No problem, check out the hooks section
# below. Add in a before_validation to do your own checking. The sky is the limit.
#
# Validation
# ==========
#
# The errors in Authlogic work just like ActiveRecord. In fact, it uses
# the `ActiveModel::Errors` class. Use it the same way:
#
# ```
# class UserSession
# validate :check_if_awesome
#
# private
#
# def check_if_awesome
# if login && !login.include?("awesome")
# errors.add(:login, "must contain awesome")
# end
# unless attempted_record.awesome?
# errors.add(:base, "You must be awesome to log in")
# end
# end
# end
# ```
class Base
extend ActiveModel::Naming
extend ActiveModel::Translation
extend Authlogic::Config
include ActiveSupport::Callbacks
E_AC_PARAMETERS = <<~EOS
Passing an ActionController::Parameters to Authlogic is not allowed.
In Authlogic 3, especially during the transition of rails to Strong
Parameters, it was common for Authlogic users to forget to `permit`
their params. They would pass their params into Authlogic, we'd call
`to_h`, and they'd be surprised when authentication failed.
In 2018, people are still making this mistake. We'd like to help them
and make authlogic a little simpler at the same time, so in Authlogic
3.7.0, we deprecated the use of ActionController::Parameters. Instead,
pass a plain Hash. Please replace:
UserSession.new(user_session_params)
UserSession.create(user_session_params)
with
UserSession.new(user_session_params.to_h)
UserSession.create(user_session_params.to_h)
And don't forget to `permit`!
We discussed this issue thoroughly between late 2016 and early
2018. Notable discussions include:
- https://github.com/binarylogic/authlogic/issues/512
- https://github.com/binarylogic/authlogic/pull/558
- https://github.com/binarylogic/authlogic/pull/577
EOS
VALID_SAME_SITE_VALUES = [nil, "Lax", "Strict"].freeze
# Callbacks
# =========
METHODS = %w[
before_persisting
persist
after_persisting
before_validation
before_validation_on_create
before_validation_on_update
validate
after_validation_on_update
after_validation_on_create
after_validation
before_save
before_create
before_update
after_update
after_create
after_save
before_destroy
after_destroy
].freeze
# Defines the "callback installation methods" used below.
METHODS.each do |method|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def self.#{method}(*filter_list, &block)
set_callback(:#{method}, *filter_list, &block)
end
EOS
end
# Defines session life cycle events that support callbacks.
define_callbacks(
*METHODS,
terminator: ->(_target, result_lambda) { result_lambda.call == false }
)
define_callbacks(
"persist",
terminator: ->(_target, result_lambda) { result_lambda.call == true }
)
# Use the "callback installation methods" defined above
# -----------------------------------------------------
before_persisting :reset_stale_state
# `persist` callbacks, in order of priority
persist :persist_by_params
persist :persist_by_cookie
persist :persist_by_session
persist :persist_by_http_auth, if: :persist_by_http_auth?
after_persisting :enforce_timeout
after_persisting :update_session, unless: :single_access?
after_persisting :set_last_request_at
before_save :update_info
before_save :set_last_request_at
after_save :reset_perishable_token!
after_save :save_cookie
after_save :update_session
after_destroy :destroy_cookie
after_destroy :update_session
# `validate` callbacks, in deliberate order. For example,
# validate_magic_states must run *after* a record is found.
validate :validate_by_password, if: :authenticating_with_password?
validate(
:validate_by_unauthorized_record,
if: :authenticating_with_unauthorized_record?
)
validate :validate_magic_states, unless: :disable_magic_states?
validate :reset_failed_login_count, if: :reset_failed_login_count?
validate :validate_failed_logins, if: :being_brute_force_protected?
validate :increase_failed_login_count
# Accessors
# =========
class << self
attr_accessor(
:configured_password_methods,
:configured_klass_methods
)
end
attr_accessor(
:invalid_password,
:new_session,
:priority_record,
:record,
:single_access,
:stale_record,
:unauthorized_record
)
attr_writer(
:scope,
:id
)
# Public class methods
# ====================
class << self
# Returns true if a controller has been set and can be used properly.
# This MUST be set before anything can be done. Similar to how
# ActiveRecord won't allow you to do anything without establishing a DB
# connection. In your framework environment this is done for you, but if
# you are using Authlogic outside of your framework, you need to assign
# a controller object to Authlogic via
# Authlogic::Session::Base.controller = obj. See the controller= method
# for more information.
def activated?
!controller.nil?
end
# Do you want to allow your users to log in via HTTP basic auth?
#
# I recommend keeping this enabled. The only time I feel this should be
# disabled is if you are not comfortable having your users provide their
# raw username and password. Whatever the reason, you can disable it
# here.
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def allow_http_basic_auth(value = nil)
rw_config(:allow_http_basic_auth, value, false)
end
alias allow_http_basic_auth= allow_http_basic_auth
# Lets you change which model to use for authentication.
#
# * <tt>Default:</tt> inferred from the class name. UserSession would
# automatically try User
# * <tt>Accepts:</tt> an ActiveRecord class
def authenticate_with(klass)
@klass_name = klass.name
@klass = klass
end
alias authenticate_with= authenticate_with
# The current controller object
def controller
RequestStore.store[:authlogic_controller]
end
# This accepts a controller object wrapped with the Authlogic controller
# adapter. The controller adapters close the gap between the different
# controllers in each framework. That being said, Authlogic is expecting
# your object's class to extend
# Authlogic::ControllerAdapters::AbstractAdapter. See
# Authlogic::ControllerAdapters for more info.
#
# Lastly, this is thread safe.
def controller=(value)
RequestStore.store[:authlogic_controller] = value
end
# To help protect from brute force attacks you can set a limit on the
# allowed number of consecutive failed logins. By default this is 50,
# this is a very liberal number, and if someone fails to login after 50
# tries it should be pretty obvious that it's a machine trying to login
# in and very likely a brute force attack.
#
# In order to enable this field your model MUST have a
# failed_login_count (integer) field.
#
# If you don't know what a brute force attack is, it's when a machine
# tries to login into a system using every combination of character
# possible. Thus resulting in possibly millions of attempts to log into
# an account.
#
# * <tt>Default:</tt> 50
# * <tt>Accepts:</tt> Integer, set to 0 to disable
def consecutive_failed_logins_limit(value = nil)
rw_config(:consecutive_failed_logins_limit, value, 50)
end
alias consecutive_failed_logins_limit= consecutive_failed_logins_limit
# The name of the cookie or the key in the cookies hash. Be sure and use
# a unique name. If you have multiple sessions and they use the same
# cookie it will cause problems. Also, if a id is set it will be
# inserted into the beginning of the string. Example:
#
# session = UserSession.new
# session.cookie_key => "user_credentials"
#
# session = UserSession.new(:super_high_secret)
# session.cookie_key => "super_high_secret_user_credentials"
#
# * <tt>Default:</tt> "#{klass_name.underscore}_credentials"
# * <tt>Accepts:</tt> String
def cookie_key(value = nil)
rw_config(:cookie_key, value, "#{klass_name.underscore}_credentials")
end
alias cookie_key= cookie_key
# A convenience method. The same as:
#
# session = UserSession.new(*args)
# session.save
#
# Instead you can do:
#
# UserSession.create(*args)
def create(*args, &block)
session = new(*args)
session.save(&block)
session
end
# Same as create but calls create!, which raises an exception when
# validation fails.
def create!(*args)
session = new(*args)
session.save!
session
end
# Set this to true if you want to disable the checking of active?, approved?, and
# confirmed? on your record. This is more or less of a convenience feature, since
# 99% of the time if those methods exist and return false you will not want the
# user logging in. You could easily accomplish this same thing with a
# before_validation method or other callbacks.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def disable_magic_states(value = nil)
rw_config(:disable_magic_states, value, false)
end
alias disable_magic_states= disable_magic_states
# Once the failed logins limit has been exceed, how long do you want to
# ban the user? This can be a temporary or permanent ban.
#
# * <tt>Default:</tt> 2.hours
# * <tt>Accepts:</tt> Fixnum, set to 0 for permanent ban
def failed_login_ban_for(value = nil)
rw_config(:failed_login_ban_for, (!value.nil? && value) || value, 2.hours.to_i)
end
alias failed_login_ban_for= failed_login_ban_for
# This is how you persist a session. This finds the record for the
# current session using a variety of methods. It basically tries to "log
# in" the user without the user having to explicitly log in. Check out
# the other Authlogic::Session modules for more information.
#
# The best way to use this method is something like:
#
# helper_method :current_user_session, :current_user
#
# def current_user_session
# return @current_user_session if defined?(@current_user_session)
# @current_user_session = UserSession.find
# end
#
# def current_user
# return @current_user if defined?(@current_user)
# @current_user = current_user_session && current_user_session.user
# end
#
# Also, this method accepts a single parameter as the id, to find
# session that you marked with an id:
#
# UserSession.find(:secure)
#
# See the id method for more information on ids.
#
# Priority Record
# ===============
#
# This internal feature supports ActiveRecord's optimistic locking feature,
# which is automatically enabled when a table has a `lock_version` column.
#
# ```
# # https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
# p1 = Person.find(1)
# p2 = Person.find(1)
# p1.first_name = "Michael"
# p1.save
# p2.first_name = "should fail"
# p2.save # Raises an ActiveRecord::StaleObjectError
# ```
#
# Now, consider the following Authlogic scenario:
#
# ```
# User.log_in_after_password_change = true
# ben = User.find(1)
# UserSession.create(ben)
# ben.password = "newpasswd"
# ben.password_confirmation = "newpasswd"
# ben.save
# ```
#
# We've used one of Authlogic's session maintenance features,
# `log_in_after_password_change`. So, when we call `ben.save`, there is a
# `before_save` callback that logs Ben in (`UserSession.find`). Well, when
# we log Ben in, we update his user record, eg. `login_count`. When we're
# done logging Ben in, then the normal `ben.save` happens. So, there were
# two `update` queries. If those two updates came from different User
# instances, we would get a `StaleObjectError`.
#
# Our solution is to carefully pass around a single `User` instance, using
# it for all `update` queries, thus avoiding the `StaleObjectError`.
def find(id = nil, priority_record = nil)
session = new({ priority_record: priority_record }, id)
session.priority_record = priority_record
if session.persisting?
session
end
end
# Authlogic tries to validate the credentials passed to it. One part of
# validation is actually finding the user and making sure it exists.
# What method it uses the do this is up to you.
#
# Let's say you have a UserSession that is authenticating a User. By
# default UserSession will call User.find_by_login(login). You can
# change what method UserSession calls by specifying it here. Then in
# your User model you can make that method do anything you want, giving
# you complete control of how users are found by the UserSession.
#
# Let's take an example: You want to allow users to login by username or
# email. Set this to the name of the class method that does this in the
# User model. Let's call it "find_by_username_or_email"
#
# class User < ActiveRecord::Base
# def self.find_by_username_or_email(login)
# find_by_username(login) || find_by_email(login)
# end
# end
#
# Now just specify the name of this method for this configuration option
# and you are all set. You can do anything you want here. Maybe you
# allow users to have multiple logins and you want to search a has_many
# relationship, etc. The sky is the limit.
#
# * <tt>Default:</tt> "find_by_smart_case_login_field"
# * <tt>Accepts:</tt> Symbol or String
def find_by_login_method(value = nil)
rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
end
alias find_by_login_method= find_by_login_method
# The text used to identify credentials (username/password) combination
# when a bad login attempt occurs. When you show error messages for a
# bad login, it's considered good security practice to hide which field
# the user has entered incorrectly (the login field or the password
# field). For a full explanation, see
# http://www.gnucitizen.org/blog/username-enumeration-vulnerabilities/
#
# Example of use:
#
# class UserSession < Authlogic::Session::Base
# generalize_credentials_error_messages true
# end
#
# This would make the error message for bad logins and bad passwords
# look identical:
#
# Login/Password combination is not valid
#
# Alternatively you may use a custom message:
#
# class UserSession < AuthLogic::Session::Base
# generalize_credentials_error_messages "Your login information is invalid"
# end
#
# This will instead show your custom error message when the UserSession is invalid.
#
# The downside to enabling this is that is can be too vague for a user
# that has a hard time remembering their username and password
# combinations. It also disables the ability to to highlight the field
# with the error when you use form_for.
#
# If you are developing an app where security is an extreme priority
# (such as a financial application), then you should enable this.
# Otherwise, leaving this off is fine.
#
# * <tt>Default</tt> false
# * <tt>Accepts:</tt> Boolean
def generalize_credentials_error_messages(value = nil)
rw_config(:generalize_credentials_error_messages, value, false)
end
alias generalize_credentials_error_messages= generalize_credentials_error_messages
# HTTP authentication realm
#
# Sets the HTTP authentication realm.
#
# Note: This option has no effect unless request_http_basic_auth is true
#
# * <tt>Default:</tt> 'Application'
# * <tt>Accepts:</tt> String
def http_basic_auth_realm(value = nil)
rw_config(:http_basic_auth_realm, value, "Application")
end
alias http_basic_auth_realm= http_basic_auth_realm
# Should the cookie be set as httponly? If true, the cookie will not be
# accessible from javascript
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def httponly(value = nil)
rw_config(:httponly, value, true)
end
alias httponly= httponly
# How to name the class, works JUST LIKE ActiveRecord, except it uses
# the following namespace:
#
# authlogic.models.user_session
def human_name(*)
I18n.t("models.#{name.underscore}", count: 1, default: name.humanize)
end
def i18n_scope
I18n.scope
end
# The name of the class that this session is authenticating with. For
# example, the UserSession class will authenticate with the User class
# unless you specify otherwise in your configuration. See
# authenticate_with for information on how to change this value.
def klass
@klass ||= klass_name ? klass_name.constantize : nil
end
# The string of the model name class guessed from the actual session class name.
def klass_name
return @klass_name if defined?(@klass_name)
@klass_name = name.scan(/(.*)Session/)[0]
@klass_name = klass_name ? klass_name[0] : nil
end
# The name of the method you want Authlogic to create for storing the
# login / username. Keep in mind this is just for your
# Authlogic::Session, if you want it can be something completely
# different than the field in your model. So if you wanted people to
# login with a field called "login" and then find users by email this is
# completely doable. See the find_by_login_method configuration option
# for more details.
#
# * <tt>Default:</tt> klass.login_field || klass.email_field
# * <tt>Accepts:</tt> Symbol or String
def login_field(value = nil)
rw_config(:login_field, value, klass.login_field || klass.email_field)
end
alias login_field= login_field
# With acts_as_authentic you get a :logged_in_timeout configuration
# option. If this is set, after this amount of time has passed the user
# will be marked as logged out. Obviously, since web based apps are on a
# per request basis, we have to define a time limit threshold that
# determines when we consider a user to be "logged out". Meaning, if
# they login and then leave the website, when do mark them as logged
# out? I recommend just using this as a fun feature on your website or
# reports, giving you a ballpark number of users logged in and active.
# This is not meant to be a dead accurate representation of a user's
# logged in state, since there is really no real way to do this with web
# based apps. Think about a user that logs in and doesn't log out. There
# is no action that tells you that the user isn't technically still
# logged in and active.
#
# That being said, you can use that feature to require a new login if
# their session times out. Similar to how financial sites work. Just set
# this option to true and if your record returns true for stale? then
# they will be required to log back in.
#
# Lastly, UserSession.find will still return an object if the session is
# stale, but you will not get a record. This allows you to determine if
# the user needs to log back in because their session went stale, or
# because they just aren't logged in. Just call
# current_user_session.stale? as your flag.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def logout_on_timeout(value = nil)
rw_config(:logout_on_timeout, value, false)
end
alias logout_on_timeout= logout_on_timeout
# Every time a session is found the last_request_at field for that record is
# updated with the current time, if that field exists. If you want to limit how
# frequent that field is updated specify the threshold here. For example, if your
# user is making a request every 5 seconds, and you feel this is too frequent, and
# feel a minute is a good threshold. Set this to 1.minute. Once a minute has
# passed in between requests the field will be updated.
#
# * <tt>Default:</tt> 0
# * <tt>Accepts:</tt> integer representing time in seconds
def last_request_at_threshold(value = nil)
rw_config(:last_request_at_threshold, value, 0)
end
alias last_request_at_threshold= last_request_at_threshold
# Works exactly like cookie_key, but for params. So a user can login via
# params just like a cookie or a session. Your URL would look like:
#
# http://www.domain.com?user_credentials=my_single_access_key
#
# You can change the "user_credentials" key above with this
# configuration option. Keep in mind, just like cookie_key, if you
# supply an id the id will be appended to the front. Check out
# cookie_key for more details. Also checkout the "Single Access /
# Private Feeds Access" section in the README.
#
# * <tt>Default:</tt> cookie_key
# * <tt>Accepts:</tt> String
def params_key(value = nil)
rw_config(:params_key, value, cookie_key)
end
alias params_key= params_key
# Works exactly like login_field, but for the password instead. Returns
# :password if a login_field exists.
#
# * <tt>Default:</tt> :password
# * <tt>Accepts:</tt> Symbol or String
def password_field(value = nil)
rw_config(:password_field, value, login_field && :password)
end
alias password_field= password_field
# Whether or not to request HTTP authentication
#
# If set to true and no HTTP authentication credentials are sent with
# the request, the Rails controller method
# authenticate_or_request_with_http_basic will be used and a '401
# Authorization Required' header will be sent with the response. In
# most cases, this will cause the classic HTTP authentication popup to
# appear in the users browser.
#
# If set to false, the Rails controller method
# authenticate_with_http_basic is used and no 401 header is sent.
#
# Note: This parameter has no effect unless allow_http_basic_auth is
# true
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def request_http_basic_auth(value = nil)
rw_config(:request_http_basic_auth, value, false)
end
alias request_http_basic_auth= request_http_basic_auth
# If sessions should be remembered by default or not.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def remember_me(value = nil)
rw_config(:remember_me, value, false)
end
alias remember_me= remember_me
# The length of time until the cookie expires.
#
# * <tt>Default:</tt> 3.months
# * <tt>Accepts:</tt> Integer, length of time in seconds, such as 60 or 3.months
def remember_me_for(value = nil)
rw_config(:remember_me_for, value, 3.months)
end
alias remember_me_for= remember_me_for
# Should the cookie be prevented from being send along with cross-site
# requests?
#
# * <tt>Default:</tt> nil
# * <tt>Accepts:</tt> String, one of nil, 'Lax' or 'Strict'
def same_site(value = nil)
unless VALID_SAME_SITE_VALUES.include?(value)
msg = "Invalid same_site value: #{value}. Valid: #{VALID_SAME_SITE_VALUES.inspect}"
raise ArgumentError, msg
end
rw_config(:same_site, value)
end
alias same_site= same_site
# The current scope set, should be used in the block passed to with_scope.
def scope
RequestStore.store[:authlogic_scope]
end
# Should the cookie be set as secure? If true, the cookie will only be sent over
# SSL connections
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def secure(value = nil)
rw_config(:secure, value, true)
end
alias secure= secure
# Should the cookie be signed? If the controller adapter supports it, this is a
# measure against cookie tampering.
def sign_cookie(value = nil)
if value && !controller.cookies.respond_to?(:signed)
raise "Signed cookies not supported with #{controller.class}!"
end
rw_config(:sign_cookie, value, false)
end
alias sign_cookie= sign_cookie
# Works exactly like cookie_key, but for sessions. See cookie_key for more info.
#
# * <tt>Default:</tt> cookie_key
# * <tt>Accepts:</tt> Symbol or String
def session_key(value = nil)
rw_config(:session_key, value, cookie_key)
end
alias session_key= session_key
# Authentication is allowed via a single access token, but maybe this is
# something you don't want for your application as a whole. Maybe this
# is something you only want for specific request types. Specify a list
# of allowed request types and single access authentication will only be
# allowed for the ones you specify.
#
# * <tt>Default:</tt> ["application/rss+xml", "application/atom+xml"]
# * <tt>Accepts:</tt> String of a request type, or :all or :any to
# allow single access authentication for any and all request types
def single_access_allowed_request_types(value = nil)
rw_config(
:single_access_allowed_request_types,
value,
["application/rss+xml", "application/atom+xml"]
)
end
alias single_access_allowed_request_types= single_access_allowed_request_types
# The name of the method in your model used to verify the password. This
# should be an instance method. It should also be prepared to accept a
# raw password and a crytped password.
#
# * <tt>Default:</tt> "valid_password?" defined in acts_as_authentic/password.rb
# * <tt>Accepts:</tt> Symbol or String
def verify_password_method(value = nil)
rw_config(:verify_password_method, value, "valid_password?")
end
alias verify_password_method= verify_password_method