authentication-logic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/auth/logic/acts_as_authentic/base.rb +118 -0
- data/lib/auth/logic/acts_as_authentic/email.rb +32 -0
- data/lib/auth/logic/acts_as_authentic/logged_in_status.rb +87 -0
- data/lib/auth/logic/acts_as_authentic/login.rb +65 -0
- data/lib/auth/logic/acts_as_authentic/magic_columns.rb +40 -0
- data/lib/auth/logic/acts_as_authentic/password.rb +362 -0
- data/lib/auth/logic/acts_as_authentic/perishable_token.rb +125 -0
- data/lib/auth/logic/acts_as_authentic/persistence_token.rb +72 -0
- data/lib/auth/logic/acts_as_authentic/queries/case_sensitivity.rb +55 -0
- data/lib/auth/logic/acts_as_authentic/queries/find_with_case.rb +85 -0
- data/lib/auth/logic/acts_as_authentic/session_maintenance.rb +189 -0
- data/lib/auth/logic/acts_as_authentic/single_access_token.rb +85 -0
- data/lib/auth/logic/config.rb +41 -0
- data/lib/auth/logic/controller_adapters/abstract_adapter.rb +121 -0
- data/lib/auth/logic/controller_adapters/rack_adapter.rb +74 -0
- data/lib/auth/logic/controller_adapters/rails_adapter.rb +49 -0
- data/lib/auth/logic/controller_adapters/sinatra_adapter.rb +69 -0
- data/lib/auth/logic/cookie_credentials.rb +65 -0
- data/lib/auth/logic/crypto_providers/bcrypt.rb +116 -0
- data/lib/auth/logic/crypto_providers/md5/v2.rb +37 -0
- data/lib/auth/logic/crypto_providers/md5.rb +38 -0
- data/lib/auth/logic/crypto_providers/scrypt.rb +96 -0
- data/lib/auth/logic/crypto_providers/sha1/v2.rb +42 -0
- data/lib/auth/logic/crypto_providers/sha1.rb +43 -0
- data/lib/auth/logic/crypto_providers/sha256/v2.rb +60 -0
- data/lib/auth/logic/crypto_providers/sha256.rb +61 -0
- data/lib/auth/logic/crypto_providers/sha512/v2.rb +41 -0
- data/lib/auth/logic/crypto_providers/sha512.rb +40 -0
- data/lib/auth/logic/crypto_providers.rb +89 -0
- data/lib/auth/logic/errors.rb +52 -0
- data/lib/auth/logic/i18n/translator.rb +20 -0
- data/lib/auth/logic/i18n.rb +100 -0
- data/lib/auth/logic/random.rb +18 -0
- data/lib/auth/logic/session/base.rb +2205 -0
- data/lib/auth/logic/session/magic_column/assigns_last_request_at.rb +49 -0
- data/lib/auth/logic/test_case/mock_api_controller.rb +53 -0
- data/lib/auth/logic/test_case/mock_controller.rb +59 -0
- data/lib/auth/logic/test_case/mock_cookie_jar.rb +112 -0
- data/lib/auth/logic/test_case/mock_logger.rb +14 -0
- data/lib/auth/logic/test_case/mock_request.rb +36 -0
- data/lib/auth/logic/test_case/rails_request_adapter.rb +40 -0
- data/lib/auth/logic/test_case.rb +216 -0
- data/lib/auth/logic/version.rb +7 -0
- data/lib/auth/logic.rb +46 -0
- metadata +426 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e3ea405dd232236ede943193d4e88d29b71325cfbd49792a1172d9ffa7d61f3a
|
4
|
+
data.tar.gz: 0e4feb942a405eeca6c22b6ccfe0aa435652b8ec725945ba04a1fba309482de8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b656538ef705d09b62b9f8ac6cf710814f549d6a0d74459076ff37be764ad2a61b518696a337d8c37670fa83d2f679eb158e2fab03550f7d06ca9a7e8e9534f9
|
7
|
+
data.tar.gz: c7780765733051e7c3317497bddce4dcf87415a029301170c54ee7c64ae001b99edb66239e28db0871b81de6a3216c9c84c87ef91935af2b4a92dfdfa1590e09
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "auth/logic"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require "irb"
|
11
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ActsAsAuthentic
|
6
|
+
# Provides the base functionality for acts_as_authentic
|
7
|
+
module Base
|
8
|
+
def self.included(klass)
|
9
|
+
klass.class_eval do
|
10
|
+
class_attribute :acts_as_authentic_modules
|
11
|
+
self.acts_as_authentic_modules ||= []
|
12
|
+
extend Authentication::Logic::Config
|
13
|
+
extend Config
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# The primary configuration of a model (often, `User`) for use with
|
18
|
+
# auth-logic. These methods become class methods of ::ActiveRecord::Base.
|
19
|
+
module Config
|
20
|
+
# This includes a lot of helpful methods for authenticating records
|
21
|
+
# which the Authentication::Logic::Session module relies on. To use it just do:
|
22
|
+
#
|
23
|
+
# class User < ApplicationRecord
|
24
|
+
# acts_as_authentic
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# Configuration is easy:
|
28
|
+
#
|
29
|
+
# acts_as_authentic do |c|
|
30
|
+
# c.my_configuration_option = my_value
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# See the various sub modules for the configuration they provide.
|
34
|
+
def acts_as_authentic
|
35
|
+
yield self if block_given?
|
36
|
+
return unless db_setup?
|
37
|
+
|
38
|
+
acts_as_authentic_modules.each { |mod| include mod }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Since this part of Authentication::Logic deals with another class, ActiveRecord,
|
42
|
+
# we can't just start including things in ActiveRecord itself. A lot of
|
43
|
+
# these module includes need to be triggered by the acts_as_authentic
|
44
|
+
# method call. For example, you don't want to start adding in email
|
45
|
+
# validations and what not into a model that has nothing to do with
|
46
|
+
# Authentication::Logic.
|
47
|
+
#
|
48
|
+
# That being said, this is your tool for extending Authentication::Logic and
|
49
|
+
# "hooking" into the acts_as_authentic call.
|
50
|
+
def add_acts_as_authentic_module(mod, action = :append)
|
51
|
+
modules = acts_as_authentic_modules.clone
|
52
|
+
case action
|
53
|
+
when :append
|
54
|
+
modules << mod
|
55
|
+
when :prepend
|
56
|
+
modules = [mod] + modules
|
57
|
+
end
|
58
|
+
modules.uniq!
|
59
|
+
self.acts_as_authentic_modules = modules
|
60
|
+
end
|
61
|
+
|
62
|
+
# This is the same as add_acts_as_authentic_module, except that it
|
63
|
+
# removes the module from the list.
|
64
|
+
def remove_acts_as_authentic_module(mod)
|
65
|
+
modules = acts_as_authentic_modules.clone
|
66
|
+
modules.delete(mod)
|
67
|
+
self.acts_as_authentic_modules = modules
|
68
|
+
end
|
69
|
+
|
70
|
+
# Some Authentication::Logic modules requires a database connection with a existing
|
71
|
+
# users table by the moment when you call the `acts_as_authentic`
|
72
|
+
# method. If you try to call `acts_as_authentic` without a database
|
73
|
+
# connection, it will raise a `Authentication::Logic::ModelSetupError`.
|
74
|
+
#
|
75
|
+
# If you rely on the User model before the database is setup correctly,
|
76
|
+
# set this field to false.
|
77
|
+
# * <tt>Default:</tt> false
|
78
|
+
# * <tt>Accepts:</tt> Boolean
|
79
|
+
def raise_on_model_setup_error(value = nil)
|
80
|
+
rw_config(:raise_on_model_setup_error, value, false)
|
81
|
+
end
|
82
|
+
alias raise_on_model_setup_error= raise_on_model_setup_error
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def db_setup?
|
87
|
+
column_names
|
88
|
+
true
|
89
|
+
rescue StandardError
|
90
|
+
raise ModelSetupError if raise_on_model_setup_error
|
91
|
+
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
def first_column_to_exist(*columns_to_check)
|
96
|
+
if db_setup?
|
97
|
+
columns_to_check.each do |column_name|
|
98
|
+
return column_name.to_sym if column_names.include?(column_name.to_s)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
columns_to_check.first&.to_sym
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::Base
|
110
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::Email
|
111
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::LoggedInStatus
|
112
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::Login
|
113
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::MagicColumns
|
114
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::Password
|
115
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::PerishableToken
|
116
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::PersistenceToken
|
117
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::SessionMaintenance
|
118
|
+
::ActiveRecord::Base.include Authentication::Logic::ActsAsAuthentic::SingleAccessToken
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ActsAsAuthentic
|
6
|
+
# Sometimes models won't have an explicit "login" or "username" field.
|
7
|
+
# Instead they want to use the email field. In this case, auth-logic provides
|
8
|
+
# validations to make sure the email submited is actually a valid email.
|
9
|
+
# Don't worry, if you do have a login or username field, Authentication::Logic will
|
10
|
+
# still validate your email field. One less thing you have to worry about.
|
11
|
+
module Email
|
12
|
+
def self.included(klass)
|
13
|
+
klass.class_eval do
|
14
|
+
extend Config
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Configuration to modify how Authentication::Logic handles the email field.
|
19
|
+
module Config
|
20
|
+
# The name of the field that stores email addresses.
|
21
|
+
#
|
22
|
+
# * <tt>Default:</tt> :email, if it exists
|
23
|
+
# * <tt>Accepts:</tt> Symbol
|
24
|
+
def email_field(value = nil)
|
25
|
+
rw_config(:email_field, value, first_column_to_exist(nil, :email, :email_address))
|
26
|
+
end
|
27
|
+
alias email_field= email_field
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ActsAsAuthentic
|
6
|
+
# Since web applications are stateless there is not sure fire way to tell if
|
7
|
+
# a user is logged in or not, from the database perspective. The best way to
|
8
|
+
# do this is to provide a "timeout" based on inactivity. So if that user is
|
9
|
+
# inactive for a certain amount of time we assume they are logged out.
|
10
|
+
# That's what this module is all about.
|
11
|
+
module LoggedInStatus
|
12
|
+
def self.included(klass)
|
13
|
+
klass.class_eval do
|
14
|
+
extend Config
|
15
|
+
add_acts_as_authentic_module(Methods)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# All configuration for the logged in status feature set.
|
20
|
+
module Config
|
21
|
+
# The timeout to determine when a user is logged in or not.
|
22
|
+
#
|
23
|
+
# * <tt>Default:</tt> 10.minutes
|
24
|
+
# * <tt>Accepts:</tt> Fixnum
|
25
|
+
def logged_in_timeout(value = nil)
|
26
|
+
rw_config(:logged_in_timeout, (!value.nil? && value.to_i) || value, 10.minutes.to_i)
|
27
|
+
end
|
28
|
+
alias logged_in_timeout= logged_in_timeout
|
29
|
+
end
|
30
|
+
|
31
|
+
# All methods for the logged in status feature seat.
|
32
|
+
module Methods
|
33
|
+
def self.included(klass)
|
34
|
+
return unless klass.column_names.include?("last_request_at")
|
35
|
+
|
36
|
+
klass.class_eval do
|
37
|
+
include InstanceMethods
|
38
|
+
scope(
|
39
|
+
:logged_in,
|
40
|
+
lambda do
|
41
|
+
where(
|
42
|
+
"last_request_at > ? and current_login_at IS NOT NULL",
|
43
|
+
logged_in_timeout.seconds.ago
|
44
|
+
)
|
45
|
+
end
|
46
|
+
)
|
47
|
+
scope(
|
48
|
+
:logged_out,
|
49
|
+
lambda do
|
50
|
+
where(
|
51
|
+
"last_request_at is NULL or last_request_at <= ?",
|
52
|
+
logged_in_timeout.seconds.ago
|
53
|
+
)
|
54
|
+
end
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# :nodoc:
|
60
|
+
module InstanceMethods
|
61
|
+
# Returns true if the last_request_at > logged_in_timeout.
|
62
|
+
def logged_in?
|
63
|
+
unless respond_to?(:last_request_at)
|
64
|
+
raise(
|
65
|
+
"Can not determine the records login state because " \
|
66
|
+
"there is no last_request_at column"
|
67
|
+
)
|
68
|
+
end
|
69
|
+
!last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
|
70
|
+
end
|
71
|
+
|
72
|
+
# Opposite of logged_in?
|
73
|
+
def logged_out?
|
74
|
+
!logged_in?
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def logged_in_timeout
|
80
|
+
self.class.logged_in_timeout
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "auth/logic/acts_as_authentic/queries/case_sensitivity"
|
4
|
+
require "auth/logic/acts_as_authentic/queries/find_with_case"
|
5
|
+
|
6
|
+
module Authentication
|
7
|
+
module Logic
|
8
|
+
module ActsAsAuthentic
|
9
|
+
# Handles everything related to the login field.
|
10
|
+
module Login
|
11
|
+
def self.included(klass)
|
12
|
+
klass.class_eval do
|
13
|
+
extend Config
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Configuration for the login field.
|
18
|
+
module Config
|
19
|
+
# The name of the login field in the database.
|
20
|
+
#
|
21
|
+
# * <tt>Default:</tt> :login or :username, if they exist
|
22
|
+
# * <tt>Accepts:</tt> Symbol
|
23
|
+
def login_field(value = nil)
|
24
|
+
rw_config(:login_field, value, first_column_to_exist(nil, :login, :username))
|
25
|
+
end
|
26
|
+
alias login_field= login_field
|
27
|
+
|
28
|
+
# This method allows you to find a record with the given login. If you
|
29
|
+
# notice, with Active Record you have the UniquenessValidator class.
|
30
|
+
# They give you a :case_sensitive option. I handle this in the same
|
31
|
+
# manner that they handle that. If you are using the login field, set
|
32
|
+
# false for the :case_sensitive option in
|
33
|
+
# validates_uniqueness_of_login_field_options and the column doesn't
|
34
|
+
# have a case-insensitive collation, this method will modify the query
|
35
|
+
# to look something like:
|
36
|
+
#
|
37
|
+
# "LOWER(#{quoted_table_name}.#{login_field}) = LOWER(#{login})"
|
38
|
+
#
|
39
|
+
# If you don't specify this it just uses a regular case-sensitive search
|
40
|
+
# (with the binary modifier if necessary):
|
41
|
+
#
|
42
|
+
# "BINARY #{login_field} = #{login}"
|
43
|
+
#
|
44
|
+
# The above also applies for using email as your login, except that you
|
45
|
+
# need to set the :case_sensitive in
|
46
|
+
# validates_uniqueness_of_email_field_options to false.
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def find_by_smart_case_login_field(login)
|
50
|
+
field = login_field || email_field
|
51
|
+
sensitive = Queries::CaseSensitivity.new(self, field).sensitive?
|
52
|
+
find_with_case(field, login, sensitive)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
def find_with_case(field, value, sensitive)
|
59
|
+
Queries::FindWithCase.new(self, field, value, sensitive).execute
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ActsAsAuthentic
|
6
|
+
# Magic columns are like ActiveRecord's created_at and updated_at columns.
|
7
|
+
# They are "magically" maintained for you. Authentication::Logic has the same thing, but
|
8
|
+
# these are maintained on the session side. Please see "Magic Columns" in
|
9
|
+
# `Session::Base` for more details. This module merely adds validations for
|
10
|
+
# the magic columns if they exist.
|
11
|
+
module MagicColumns
|
12
|
+
def self.included(klass)
|
13
|
+
klass.class_eval do
|
14
|
+
add_acts_as_authentic_module(Methods)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Methods relating to the magic columns
|
19
|
+
module Methods
|
20
|
+
def self.included(klass)
|
21
|
+
klass.class_eval do
|
22
|
+
if column_names.include?("login_count")
|
23
|
+
validates_numericality_of :login_count,
|
24
|
+
only_integer: true,
|
25
|
+
greater_than_or_equal_to: 0,
|
26
|
+
allow_nil: true
|
27
|
+
end
|
28
|
+
if column_names.include?("failed_login_count")
|
29
|
+
validates_numericality_of :failed_login_count,
|
30
|
+
only_integer: true,
|
31
|
+
greater_than_or_equal_to: 0,
|
32
|
+
allow_nil: true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|