challah 1.6.1 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +5 -38
- data/VERSION +1 -1
- data/app/controllers/sessions_controller.rb +11 -10
- data/app/models/authorization.rb +2 -0
- data/lib/challah/audit.rb +38 -36
- data/lib/challah/authenticators/api_key.rb +4 -2
- data/lib/challah/authenticators/password.rb +3 -1
- data/lib/challah/authenticators.rb +5 -3
- data/lib/challah/concerns/authorizeable.rb +4 -0
- data/lib/challah/concerns/user/attributeable.rb +35 -33
- data/lib/challah/concerns/user/authenticateable.rb +2 -0
- data/lib/challah/concerns/user/authorizable.rb +16 -12
- data/lib/challah/concerns/user/findable.rb +13 -10
- data/lib/challah/concerns/user/passwordable.rb +5 -3
- data/lib/challah/concerns/user/provideable.rb +22 -20
- data/lib/challah/concerns/user/statusable.rb +3 -21
- data/lib/challah/concerns/user/validateable.rb +3 -1
- data/lib/challah/concerns/userable.rb +1 -3
- data/lib/challah/controller.rb +69 -65
- data/lib/challah/cookie_store.rb +7 -5
- data/lib/challah/encrypter.rb +4 -2
- data/lib/challah/engine.rb +5 -18
- data/lib/challah/providers/password_provider.rb +9 -7
- data/lib/challah/providers.rb +3 -1
- data/lib/challah/random.rb +6 -4
- data/lib/challah/routes.rb +6 -6
- data/lib/challah/session.rb +27 -25
- data/lib/challah/signup.rb +5 -3
- data/lib/challah/simple_cookie_store.rb +82 -80
- data/lib/challah/techniques/api_key_technique.rb +2 -2
- data/lib/challah/techniques/password_technique.rb +2 -1
- data/lib/challah/techniques/token_technique.rb +1 -1
- data/lib/challah/techniques.rb +2 -0
- data/lib/challah/test.rb +6 -0
- data/lib/challah/validators/email_validator.rb +2 -0
- data/lib/challah/validators/password_validator.rb +5 -3
- data/lib/challah/version.rb +3 -1
- data/lib/challah.rb +2 -5
- data/lib/generators/challah_generator.rb +2 -8
- data/lib/generators/templates/{migration.rb → migration.erb} +3 -6
- metadata +42 -19
- data/lib/challah/plugins.rb +0 -54
@@ -1,20 +1,21 @@
|
|
1
1
|
module Challah
|
2
2
|
class PasswordProvider
|
3
|
+
|
3
4
|
def self.save(user)
|
4
5
|
set(uid: user.username, token: user.password, user_id: user.id, authorization: user.class.authorization_model)
|
5
6
|
end
|
6
7
|
|
7
8
|
def self.set(options = {})
|
8
9
|
user_id = options.fetch(:user_id)
|
9
|
-
uid = options.fetch(:uid,
|
10
|
-
token = options.fetch(:token,
|
10
|
+
uid = options.fetch(:uid, "")
|
11
|
+
token = options.fetch(:token, "")
|
11
12
|
auth_model = options.fetch(:authorization, ::Authorization)
|
12
13
|
|
13
14
|
if token.to_s.blank?
|
14
|
-
authorization = auth_model.get(
|
15
|
+
authorization = auth_model.get(
|
15
16
|
user_id: user_id,
|
16
17
|
provider: :password
|
17
|
-
|
18
|
+
)
|
18
19
|
|
19
20
|
if authorization
|
20
21
|
token = authorization.token
|
@@ -23,12 +24,12 @@ module Challah
|
|
23
24
|
token = Challah::Encrypter.encrypt(token)
|
24
25
|
end
|
25
26
|
|
26
|
-
auth_model.set(
|
27
|
+
auth_model.set(
|
27
28
|
provider: :password,
|
28
29
|
user_id: user_id,
|
29
30
|
uid: uid,
|
30
31
|
token: token
|
31
|
-
|
32
|
+
)
|
32
33
|
end
|
33
34
|
|
34
35
|
def self.valid?(record)
|
@@ -36,5 +37,6 @@ module Challah
|
|
36
37
|
password_validator.new(force: true).validate(record)
|
37
38
|
record.errors[:password].size.zero?
|
38
39
|
end
|
40
|
+
|
39
41
|
end
|
40
|
-
end
|
42
|
+
end
|
data/lib/challah/providers.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Challah
|
2
2
|
module Providers
|
3
|
+
|
3
4
|
# Get a list of all authorization providers other than password provider
|
4
5
|
def custom_providers
|
5
6
|
providers.reject { |k, v| k == :password }
|
@@ -18,5 +19,6 @@ module Challah
|
|
18
19
|
def providers
|
19
20
|
@providers.dup
|
20
21
|
end
|
22
|
+
|
21
23
|
end
|
22
|
-
end
|
24
|
+
end
|
data/lib/challah/random.rb
CHANGED
@@ -2,17 +2,19 @@ module Challah
|
|
2
2
|
# Random string class, uses ActiveSupport's SecureRandom if possible, otherwise gives a fairly
|
3
3
|
# secure random string
|
4
4
|
class Random
|
5
|
+
|
5
6
|
# Returns a random string for use as a token at the given length.
|
6
7
|
def self.token(length = 30)
|
7
|
-
return SecureRandom.hex(length/2) if secure_random?
|
8
|
+
return SecureRandom.hex(length / 2) if secure_random?
|
8
9
|
|
9
|
-
c = [(0..9),(
|
10
|
-
(1..length).map{ c[rand(c.length)] }.join
|
10
|
+
c = [(0..9), ("a".."z"), ("A".."Z")].map { |i| i.to_a }.flatten
|
11
|
+
(1..length).map { c[rand(c.length)] }.join
|
11
12
|
end
|
12
13
|
|
13
14
|
# Is ActiveSupport::SecureRandom available. If so, we'll use it.
|
14
15
|
def self.secure_random?
|
15
16
|
defined?(::SecureRandom)
|
16
17
|
end
|
18
|
+
|
17
19
|
end
|
18
|
-
end
|
20
|
+
end
|
data/lib/challah/routes.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
2
|
unless Challah.options[:skip_routes]
|
3
|
-
post
|
4
|
-
get
|
5
|
-
get
|
3
|
+
post "/sign-in", to: "sessions#create", as: "authenticate"
|
4
|
+
get "/sign-in", to: "sessions#new", as: "signin"
|
5
|
+
get "/sign-out", to: "sessions#destroy", as: "signout"
|
6
6
|
|
7
|
-
post
|
8
|
-
get
|
9
|
-
get
|
7
|
+
post "/login", to: "sessions#create", as: "submit_login"
|
8
|
+
get "/login", to: "sessions#new", as: "login"
|
9
|
+
get "/logout", to: "sessions#destroy", as: "logout"
|
10
10
|
end
|
11
11
|
end
|
data/lib/challah/session.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Challah
|
2
2
|
class Session
|
3
|
+
|
3
4
|
extend ActiveModel::Naming
|
4
5
|
include ActiveModel::Conversion
|
5
6
|
|
@@ -34,7 +35,7 @@ module Challah
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def inspect
|
37
|
-
"#<Session:0x#{object_id.to_s(16)} valid=#{valid?} store=#{self.store.inspect} user=#{user_id || 'nil'}>"
|
38
|
+
"#<Session:0x#{ object_id.to_s(16) } valid=#{ valid? } store=#{ self.store.inspect } user=#{ user_id || 'nil' }>"
|
38
39
|
end
|
39
40
|
|
40
41
|
def persist?
|
@@ -43,7 +44,7 @@ module Challah
|
|
43
44
|
|
44
45
|
def read
|
45
46
|
persistence_token, user_id = self.store.read
|
46
|
-
return false if persistence_token.nil?
|
47
|
+
return false if persistence_token.nil? || user_id.nil?
|
47
48
|
|
48
49
|
store_user = nil
|
49
50
|
|
@@ -53,7 +54,7 @@ module Challah
|
|
53
54
|
nil
|
54
55
|
end
|
55
56
|
|
56
|
-
if store_user
|
57
|
+
if store_user && store_user.valid_session? && (store_user.persistence_token == persistence_token)
|
57
58
|
if store_user.valid_session?
|
58
59
|
self.user = store_user
|
59
60
|
@valid = true
|
@@ -66,7 +67,7 @@ module Challah
|
|
66
67
|
def save
|
67
68
|
return false unless valid?
|
68
69
|
|
69
|
-
if self.user
|
70
|
+
if self.user && persist?
|
70
71
|
self.store.save(self.user.persistence_token, user_id)
|
71
72
|
return true
|
72
73
|
end
|
@@ -90,7 +91,7 @@ module Challah
|
|
90
91
|
# Returns true if this session has been authenticated and is ready to save.
|
91
92
|
def valid?
|
92
93
|
return @valid if @valid != nil
|
93
|
-
return true if self.user
|
94
|
+
return true if self.user && self.user.valid_session?
|
94
95
|
authenticate!
|
95
96
|
end
|
96
97
|
|
@@ -126,7 +127,7 @@ module Challah
|
|
126
127
|
end
|
127
128
|
end
|
128
129
|
|
129
|
-
if user_record
|
130
|
+
if user_record && user_record.valid_session?
|
130
131
|
session.user = user_record
|
131
132
|
session.persist = true
|
132
133
|
end
|
@@ -157,32 +158,33 @@ module Challah
|
|
157
158
|
|
158
159
|
protected
|
159
160
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
161
|
+
# Try and authenticate against the various auth techniques. If one
|
162
|
+
# technique works, then just exit and make the session active.
|
163
|
+
def authenticate!
|
164
|
+
Challah.techniques.values.each do |klass|
|
165
|
+
technique = klass.new(self)
|
166
|
+
technique.user_model = user_model if technique.respond_to?(:"user_model=")
|
166
167
|
|
167
|
-
|
168
|
+
@user = technique.authenticate
|
168
169
|
|
169
|
-
|
170
|
-
|
171
|
-
|
170
|
+
if @user
|
171
|
+
@persist = technique.respond_to?(:persist?) ? technique.persist? : false
|
172
|
+
break
|
173
|
+
end
|
172
174
|
end
|
173
|
-
end
|
174
175
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
176
|
+
if @user
|
177
|
+
# Only update user record if persistence is on for the technique.
|
178
|
+
# Otherwise this builds up quick (one session for each API call)
|
179
|
+
if @persist
|
180
|
+
@user.successful_authentication!(ip)
|
181
|
+
end
|
182
|
+
|
183
|
+
return @valid = true
|
180
184
|
end
|
181
185
|
|
182
|
-
|
186
|
+
@valid = false
|
183
187
|
end
|
184
188
|
|
185
|
-
@valid = false
|
186
|
-
end
|
187
189
|
end
|
188
190
|
end
|
data/lib/challah/signup.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Challah
|
2
2
|
class Signup
|
3
|
+
|
3
4
|
extend ActiveModel::Naming
|
4
5
|
include ActiveModel::Conversion
|
5
6
|
|
@@ -17,7 +18,7 @@ module Challah
|
|
17
18
|
return unless Hash === value
|
18
19
|
|
19
20
|
value.each do |key, value|
|
20
|
-
self.send("#{key}=", value)
|
21
|
+
self.send("#{ key }=", value)
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
@@ -60,7 +61,7 @@ module Challah
|
|
60
61
|
user.errors.each { |a, e| @errors.add(a, e) }
|
61
62
|
end
|
62
63
|
|
63
|
-
if !provider
|
64
|
+
if !provider || !valid_provider?
|
64
65
|
result = false
|
65
66
|
user.errors.each { |a, e| @errors.add(a, e) unless @errors.added?(a, e) }
|
66
67
|
end
|
@@ -71,5 +72,6 @@ module Challah
|
|
71
72
|
def self.model_name
|
72
73
|
ActiveModel::Name.new(Challah::Signup, Challah, "Signup")
|
73
74
|
end
|
75
|
+
|
74
76
|
end
|
75
|
-
end
|
77
|
+
end
|
@@ -4,6 +4,7 @@ module Challah
|
|
4
4
|
# To use a different storage method for persisting a session, just create
|
5
5
|
# a new class that responds to +read+, +save+ and +destroy+
|
6
6
|
class SimpleCookieStore
|
7
|
+
|
7
8
|
def initialize(session)
|
8
9
|
@session = session
|
9
10
|
end
|
@@ -13,7 +14,7 @@ module Challah
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def inspect
|
16
|
-
"#<SimpleCookieStore:0x#{object_id.to_s(16)} valid=#{existing?}>"
|
17
|
+
"#<SimpleCookieStore:0x#{ object_id.to_s(16) } valid=#{ existing? }>"
|
17
18
|
end
|
18
19
|
|
19
20
|
def read
|
@@ -29,107 +30,108 @@ module Challah
|
|
29
30
|
|
30
31
|
private
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def clear
|
34
|
+
cookies.delete(session_cookie_name, domain: domain)
|
35
|
+
cookies.delete(validation_cookie_name, domain: domain)
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def cookie_values
|
39
|
+
session_cookie && session_cookie.to_s.split(joiner)
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
def cookies
|
43
|
+
request.cookie_jar
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
def default_cookie_prefix
|
47
|
+
Challah.options[:cookie_prefix]
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
def domain
|
51
|
+
request.session_options[:domain]
|
52
|
+
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
# Do the cookies exist, and are they valid?
|
55
|
+
def existing?
|
56
|
+
exists = false
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
if session_cookie && validation_cookie
|
59
|
+
session_tmp = session_cookie.to_s
|
60
|
+
validation_tmp = validation_cookie.to_s
|
60
61
|
|
61
|
-
|
62
|
-
|
62
|
+
if validation_tmp == validation_cookie_value(session_tmp)
|
63
|
+
exists = true
|
64
|
+
end
|
63
65
|
end
|
66
|
+
|
67
|
+
exists
|
64
68
|
end
|
65
69
|
|
66
|
-
|
67
|
-
|
70
|
+
def expiration
|
71
|
+
@expiration ||= 1.month.from_now
|
72
|
+
end
|
68
73
|
|
69
|
-
|
70
|
-
|
71
|
-
|
74
|
+
def joiner
|
75
|
+
"@"
|
76
|
+
end
|
72
77
|
|
73
|
-
|
74
|
-
|
75
|
-
|
78
|
+
def prefix
|
79
|
+
@prefix ||= [ default_cookie_prefix, user_model_id ].compact.join("-")
|
80
|
+
end
|
76
81
|
|
77
|
-
|
78
|
-
|
79
|
-
|
82
|
+
def request
|
83
|
+
raise "No Request Provided" unless @session && @session.request
|
84
|
+
@session.request
|
85
|
+
end
|
80
86
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
87
|
+
def session_cookie
|
88
|
+
cookies[session_cookie_name]
|
89
|
+
end
|
85
90
|
|
86
|
-
|
87
|
-
|
88
|
-
|
91
|
+
def session_cookie_name
|
92
|
+
"#{ prefix }-s"
|
93
|
+
end
|
89
94
|
|
90
|
-
|
91
|
-
|
92
|
-
|
95
|
+
def session_cookie_value
|
96
|
+
"#@token#{ joiner }#@user_id"
|
97
|
+
end
|
93
98
|
|
94
|
-
|
95
|
-
|
96
|
-
|
99
|
+
def user_model_id
|
100
|
+
if @session && @session.user_model && @session.user_model.table_name != "users"
|
101
|
+
Encrypter.md5(@session.user_model.table_name).slice(0..5)
|
102
|
+
end
|
103
|
+
end
|
97
104
|
|
98
|
-
|
99
|
-
|
100
|
-
Encrypter.md5(@session.user_model.table_name).slice(0..5)
|
105
|
+
def validation_cookie
|
106
|
+
cookies[validation_cookie_name]
|
101
107
|
end
|
102
|
-
end
|
103
108
|
|
104
|
-
|
105
|
-
|
106
|
-
|
109
|
+
def validation_cookie_name
|
110
|
+
"#{ prefix }-v"
|
111
|
+
end
|
107
112
|
|
108
|
-
|
109
|
-
|
110
|
-
|
113
|
+
def validation_cookie_value(value = nil)
|
114
|
+
value = session_cookie_value unless value
|
115
|
+
Encrypter.md5(value)
|
116
|
+
end
|
111
117
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
118
|
+
def write_cookies!
|
119
|
+
cookies[session_cookie_name] = {
|
120
|
+
value: session_cookie_value,
|
121
|
+
expires: expiration,
|
122
|
+
secure: false,
|
123
|
+
httponly: true,
|
124
|
+
domain: domain
|
125
|
+
}
|
126
|
+
|
127
|
+
cookies[validation_cookie_name] = {
|
128
|
+
value: validation_cookie_value,
|
129
|
+
expires: expiration,
|
130
|
+
secure: false,
|
131
|
+
httponly: true,
|
132
|
+
domain: domain
|
133
|
+
}
|
134
|
+
end
|
116
135
|
|
117
|
-
def write_cookies!
|
118
|
-
cookies[session_cookie_name] = {
|
119
|
-
value: session_cookie_value,
|
120
|
-
expires: expiration,
|
121
|
-
secure: false,
|
122
|
-
httponly: true,
|
123
|
-
domain: domain
|
124
|
-
}
|
125
|
-
|
126
|
-
cookies[validation_cookie_name] = {
|
127
|
-
value: validation_cookie_value,
|
128
|
-
expires: expiration,
|
129
|
-
secure: false,
|
130
|
-
httponly: true,
|
131
|
-
domain: domain
|
132
|
-
}
|
133
|
-
end
|
134
136
|
end
|
135
137
|
end
|