cookie_crypt 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +77 -29
- data/app/controllers/devise/cookie_crypt_controller.rb +79 -63
- data/app/views/devise/cookie_crypt/_extra_fields.html.erb +14 -0
- data/app/views/devise/cookie_crypt/show.html.erb +58 -20
- data/app/views/devise/cookie_crypt/show.js.erb +8 -0
- data/cookie_crypt.gemspec +17 -2
- data/lib/cookie_crypt.rb +6 -2
- data/lib/cookie_crypt/controllers/helpers.rb +138 -4
- data/lib/cookie_crypt/models/cookie_cryptable.rb +1 -1
- data/lib/cookie_crypt/version.rb +1 -1
- data/lib/generators/active_record/cookie_crypt_generator.rb +15 -2
- data/lib/generators/active_record/templates/migration_1_1.rb +8 -0
- data/lib/generators/active_record/templates/migration_1_1_cleanup.rb +8 -0
- data/lib/generators/cookie_crypt/cookie_crypt_generator.rb +79 -11
- metadata +22 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79b8a44522cb81c5ce3f8f162a2b5e85f2f9066b
|
4
|
+
data.tar.gz: d23f608c9c30e1fa0c4bcbcdcfef8341bd219425
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b0f355e9348f6422fcf4e38699d61d5a68d99bcc75d1df2f7655745e42c6bbdcd05df941451d72064a0f8ce00473e7dafea8348a578c5e5ecc468f30c9fd590
|
7
|
+
data.tar.gz: e0c5418dc790bc69108996cce3435338e78e719c8f51e128042d02f133601a6c61f41e17423c627f1c99cdf41c8ea20039c674b717fa9269e73874a909b453c8
|
data/README.md
CHANGED
@@ -5,9 +5,9 @@
|
|
5
5
|
## Features
|
6
6
|
|
7
7
|
* User customizable security questions and answers
|
8
|
-
* Configurable max login attempts
|
8
|
+
* Configurable max login attempts & cookie expiration time
|
9
|
+
* Configurable authentication styles
|
9
10
|
* Per user level of control (Allow certain ips to bypass two-factor)
|
10
|
-
* Customizable views
|
11
11
|
|
12
12
|
## Configuration
|
13
13
|
|
@@ -31,32 +31,47 @@ In order to add encrypted cookie two factor authorization to a model, run the co
|
|
31
31
|
Where MODEL is your model name (e.g. User or Admin). This generator will add `:cookie_cryptable` to your model
|
32
32
|
and create a migration in `db/migrate/`, which will add the required columns to your table.
|
33
33
|
|
34
|
-
NOTE
|
35
|
-
The fields are security_question_one, security_question_two, security_answer_one, security_answer_two,
|
36
|
-
agent_list, and cookie_crypt_attempts_count
|
34
|
+
### NOTE!
|
37
35
|
|
38
|
-
|
36
|
+
This will create a field called "username" on the table it is creating if that field does not already exist.
|
37
|
+
The fields are security_hash, security_cycle, agent_list, and cookie_crypt_attempts_count.
|
38
|
+
|
39
|
+
Having rails generate the files will also create views in the app/views/devise/cookie_crypt directory so you can
|
40
|
+
style your two-factor pages. If you like the default look of the views, feel free to delete these files and the gem
|
41
|
+
will serve the default ones.
|
42
|
+
|
43
|
+
Run the migration with:
|
39
44
|
|
40
45
|
bundle exec rake db:migrate
|
41
46
|
|
47
|
+
With the 1.1 update, more steps are required. After following the above steps or upgrading from a previous version, run the command:
|
42
48
|
|
43
|
-
|
49
|
+
bundle exec rails g cookie_crypt MODEL
|
44
50
|
|
45
|
-
|
51
|
+
On your model again to generate the 1.1 cleanup and migration files. After doing this run
|
46
52
|
|
47
|
-
|
48
|
-
devise :database_authenticatable, :registerable,
|
49
|
-
:recoverable, :rememberable, :trackable, :validatable, :cookie_cryptable
|
50
|
-
```
|
53
|
+
bundle exec rake db:migrate
|
51
54
|
|
52
|
-
|
55
|
+
This process will move your data (in a dev environment) from the old system to the new system.
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
### Production Updating from 1.0 to 1.1
|
58
|
+
|
59
|
+
Assuming all files are already on the production box, run
|
60
|
+
|
61
|
+
bundle exec rake db:migrate:up
|
58
62
|
|
59
|
-
|
63
|
+
To go forward only to the next migration, then run
|
64
|
+
|
65
|
+
bundle exec rails g cookie_crypt MODEL
|
66
|
+
|
67
|
+
On your model to export the security question and answer data to security_hash (nothing else will be added though it may notify you of conflicts).
|
68
|
+
Do not overwrite the conflicting migration file. Then run
|
69
|
+
|
70
|
+
bundle exec rake db:migrate:up
|
71
|
+
|
72
|
+
Again to remove the old fields.
|
73
|
+
|
74
|
+
### Customization
|
60
75
|
|
61
76
|
By default encrypted cookie two factor authentication is enabled for each user, you can change it with this method in your User model:
|
62
77
|
|
@@ -66,7 +81,9 @@ By default encrypted cookie two factor authentication is enabled for each user,
|
|
66
81
|
end
|
67
82
|
```
|
68
83
|
|
69
|
-
This will disable two factor authentication for local users and just
|
84
|
+
This will disable two factor authentication for local users and just put the code in the logs.
|
85
|
+
|
86
|
+
It is recommended to take a look at the source for the views, they are not complex but the default ones may not suit your design.
|
70
87
|
|
71
88
|
### Rationalle
|
72
89
|
|
@@ -79,23 +96,23 @@ After inputting this information they are authenticated and redirected to the ro
|
|
79
96
|
It is important to note that the security answers are not saved as plaintext in the database. They are encrypted and that output is matched against
|
80
97
|
whatever the user inputs for their answers in the future.
|
81
98
|
|
82
|
-
When the user attempts to login again, they will be
|
99
|
+
When the user attempts to login again, they will be shown their two security questions and asked to answer them with their two security answers.
|
83
100
|
If successful, an auth cookie is generated on the user's machine based on the user's username and encrypted password. The cookie is username - application
|
84
|
-
specific. No two cookies should ever be the same if the username field is unique. After receiving their auth cookie, the user's user
|
85
|
-
they are sent to the root of the system as fully authenticated. If the user was unsuccessful in authenticating 3 (or more) times, they
|
86
|
-
until their cookie_crypt_attempts_count is reset to 0.
|
101
|
+
specific. No two cookies from different users should ever be the same if the username field is unique. After receiving their auth cookie, the user's user
|
102
|
+
agent is logged and they are sent to the root of the system as fully authenticated. If the user was unsuccessful in authenticating 3 (or more) times, they
|
103
|
+
will be locked out until their cookie_crypt_attempts_count is reset to 0.
|
87
104
|
|
88
|
-
### Two
|
105
|
+
### Two Factor Defense
|
89
106
|
|
90
107
|
So a user now has an auth cookie and the server knows it gave an auth cookie to this user that possessed this user agent, what now? If that same user with
|
91
108
|
that same agent tries to login again, they will authenticate through cookie crypt auth without any work on their part. The server simply matches the value
|
92
109
|
of their cookie with what it expects it should be. If they match AND the user agent the user is using is in the list of agents allocated to that user,
|
93
|
-
everything is square and they are authenticated. Using the UserAgent gem, incremental updates in a user's user agent will not be treated as differing agents.
|
110
|
+
everything is square and they are authenticated. Using the [UserAgent](https://github.com/josh/useragent) gem, incremental updates in a user's user agent will not be treated as differing agents.
|
94
111
|
The system will log the attempt as successful and update the user's agent_list with the updated agent value.
|
95
112
|
|
96
113
|
But what if they're logging in through a different machine / browser? Then they input their security answers and are given a cookie for that agent.
|
97
114
|
|
98
|
-
But what if an attacker knows the user's username and password? The attacker must also know the user's security
|
115
|
+
But what if an attacker knows the user's username and password? The attacker must also know the user's security answers to auth as the user.
|
99
116
|
|
100
117
|
But what if an attacker knows the user's username and password AND has a copy of the user's cookie in their browser? Cookie crypt detects this case and
|
101
118
|
locks out the attacker by referencing the agent_list. A user that has a cookie but not a validated agent is obviously an attacker. This case also creates a
|
@@ -104,10 +121,41 @@ decent enough amount of data for fingerprinting. More could be done in this rega
|
|
104
121
|
of this gem and would make it more difficult for the gem to be only a minor inconvienence to the users.
|
105
122
|
|
106
123
|
What cookie crypt doesnt prevent:
|
107
|
-
|
108
|
-
|
109
|
-
|
124
|
+
|
125
|
+
* An attacker that knows a user's username and password thats logging in from the user's machine / browser.
|
126
|
+
* An attacker that knows a user's username and password thats also spoofing the user's agent and also has the user's same auth-cookie.
|
127
|
+
* An attacker that knows a user's username, password, security questions and answers to said questions.
|
110
128
|
|
111
129
|
Afterword: Spoofing a user agent is not that difficult, any modern browser with dev tools can change its user agent rather easily. The catch is that the values
|
112
130
|
need to match with what the user already has which requires additional work on the attacker's part. Also, The system recognizes updates to both the user's OS AND
|
113
131
|
browser.
|
132
|
+
|
133
|
+
|
134
|
+
### Whats new with the 1.1 Update
|
135
|
+
* Reworked security questions and answers to allow for more customization options
|
136
|
+
* cookie_crypt_auth_through
|
137
|
+
** :one_question_cyclical
|
138
|
+
*** The default
|
139
|
+
*** Each user must answer only one of their questions at the end of a cookie cycle to authenticate.
|
140
|
+
*** The questions are chosen cyclically, the user will not answer the same question the next time they have to auth through two-factor
|
141
|
+
*** This prevents users logging in on a new machine from always being shown the same questions and is more secure
|
142
|
+
** :one_question_random
|
143
|
+
*** The user is shown a random question that was not their previous question every time they auth through two-factor, otherwise exactly like cyclical
|
144
|
+
** :two_questions_cyclical
|
145
|
+
*** Exactly like one_question_cyclical except two questions must be answered every auth
|
146
|
+
** :two_questions_random
|
147
|
+
*** Exactly like one_question_random except two questions must be answered every auth
|
148
|
+
** :all_questions
|
149
|
+
*** This option is not advised, but is available. It is the old functionality the system had.
|
150
|
+
*** The user must answer all authentication questions every auth session
|
151
|
+
* cookie_crypt_minimum_questions
|
152
|
+
** Default is 3
|
153
|
+
** Minimum number of questions and answers the user must enter into the system on their initial attempt
|
154
|
+
** Systems upgrading from 1.0 will prompt the user to add the difference in numbers of questions and answers
|
155
|
+
* cycle_question_on_fail_count
|
156
|
+
** Default is 2
|
157
|
+
** Minimum number of failed attempts before the question(s) is(are) cycled to the next question(s)
|
158
|
+
** Works in conjunction with max_cookie_crypt_login_attempts
|
159
|
+
* enable_custom_question_counts
|
160
|
+
** Default is false
|
161
|
+
** Allows users to have more than the minimum number of security question / answer pairs.
|
@@ -4,6 +4,7 @@ require "logger"
|
|
4
4
|
class Devise::CookieCryptController < DeviseController
|
5
5
|
prepend_before_filter :authenticate_scope!
|
6
6
|
before_filter :prepare_and_validate, :handle_cookie_crypt
|
7
|
+
before_filter :set_questions, :if => :show_request
|
7
8
|
|
8
9
|
def show
|
9
10
|
if has_matching_encrypted_cookie?
|
@@ -30,19 +31,36 @@ class Devise::CookieCryptController < DeviseController
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def update
|
33
|
-
|
34
|
+
h = Hash.class_eval(resource.security_hash)
|
35
|
+
if h.empty? # initial case (first login)
|
36
|
+
|
37
|
+
(1..(params[:security].keys.count/2)).each do |n|
|
38
|
+
h["security_question_#{n}"] = sanitize(params[:security]["security_question_#{n}".to_sym])
|
39
|
+
h["security_answer_#{n}"] = Digest::SHA512.hexdigest(sanitize(params[:security]["security_answer_#{n}".to_sym]))
|
40
|
+
end
|
41
|
+
|
42
|
+
resource.security_hash = h.to_s
|
43
|
+
|
44
|
+
resource.save
|
45
|
+
|
46
|
+
authentication_success
|
47
|
+
elsif (h.keys.count/2) < resource.class.cookie_crypt_minimum_questions # Need to update hash from an old install
|
48
|
+
|
49
|
+
((h.keys.count/2)+1..(params[:security].keys.count/2)+((h.keys.count/2))).each do |n|
|
50
|
+
h["security_question_#{n}"] = sanitize(params[:security]["security_question_#{n}".to_sym])
|
51
|
+
h["security_answer_#{n}"] = Digest::SHA512.hexdigest(sanitize(params[:security]["security_answer_#{n}".to_sym]))
|
52
|
+
end
|
53
|
+
|
54
|
+
resource.security_hash = h.to_s
|
34
55
|
|
35
|
-
resource.security_question_one = sanitize(params[:security_question_one])
|
36
|
-
resource.security_question_two = sanitize(params[:security_question_two])
|
37
|
-
resource.security_answer_one = Digest::SHA512.hexdigest(sanitize(params[:security_answer_one]))
|
38
|
-
resource.security_answer_two = Digest::SHA512.hexdigest(sanitize(params[:security_answer_two]))
|
39
56
|
resource.save
|
40
57
|
|
41
58
|
authentication_success
|
42
59
|
else
|
43
60
|
|
44
|
-
if matching_answers?
|
61
|
+
if matching_answers?(h)
|
45
62
|
generate_cookie
|
63
|
+
update_resource_cycle(h)
|
46
64
|
log_agent_to_resource
|
47
65
|
authentication_success
|
48
66
|
else
|
@@ -50,6 +68,7 @@ class Devise::CookieCryptController < DeviseController
|
|
50
68
|
resource.save
|
51
69
|
set_flash_message :error, :attempt_failed
|
52
70
|
if resource.max_cookie_crypt_login_attempts?
|
71
|
+
update_resource_cycle(h)
|
53
72
|
sign_out(resource)
|
54
73
|
render template: 'devise/cookie_crypt/max_login_attempts_reached' and return
|
55
74
|
else
|
@@ -65,38 +84,6 @@ class Devise::CookieCryptController < DeviseController
|
|
65
84
|
self.resource = send("current_#{resource_name}")
|
66
85
|
end
|
67
86
|
|
68
|
-
def encrypted_username_and_pass
|
69
|
-
Digest::SHA512.hexdigest("#{resource.username}_#{resource.encrypted_password}")
|
70
|
-
end
|
71
|
-
|
72
|
-
def generate_cookie
|
73
|
-
cookies["#{resource.username}_#{Rails.application.class.to_s.split("::").first}".to_sym] = {
|
74
|
-
value: "#{encrypted_username_and_pass}",
|
75
|
-
expires: Date.class_eval("#{resource.class.cookie_deletion_time_frame}")
|
76
|
-
}
|
77
|
-
end
|
78
|
-
|
79
|
-
def has_matching_encrypted_cookie?
|
80
|
-
cookies["#{resource.username}_#{Rails.application.class.to_s.split("::").first}"] == encrypted_username_and_pass
|
81
|
-
end
|
82
|
-
|
83
|
-
def log_hack_attempt
|
84
|
-
logger = Logger.new("#{Rails.root.join('log','hack_attempts.log')}")
|
85
|
-
logger.warn "Attempt to bypass two factor authentication and devise detected from ip #{request.remote_ip} using #{resource_name}: #{resource.inspect}!"
|
86
|
-
end
|
87
|
-
|
88
|
-
def log_agent_to_resource
|
89
|
-
unless using_an_agent_that_is_already_being_used?
|
90
|
-
resource.agent_list = "#{resource.agent_list}#{'|' unless resource.agent_list.blank?}#{request.user_agent}"
|
91
|
-
resource.save
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def matching_answers?
|
96
|
-
resource.security_answer_one == Digest::SHA512.hexdigest(sanitize(params[:security_answer_one])) &&
|
97
|
-
resource.security_answer_two == Digest::SHA512.hexdigest(sanitize(params[:security_answer_two]))
|
98
|
-
end
|
99
|
-
|
100
87
|
def prepare_and_validate
|
101
88
|
redirect_to :root and return if resource.nil?
|
102
89
|
@limit = resource.class.max_cookie_crypt_login_attempts
|
@@ -106,37 +93,66 @@ class Devise::CookieCryptController < DeviseController
|
|
106
93
|
end
|
107
94
|
end
|
108
95
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
96
|
+
def set_questions # Options are: :one_question_cyclical, :one_question_random, :two_questions_cyclical, :two_questions_random, :all_questions
|
97
|
+
h = Hash.class_eval(resource.security_hash)
|
98
|
+
@questions = []
|
99
|
+
unless h.empty?
|
100
|
+
if resource.class.cookie_crypt_auth_through == :one_question_cyclical
|
101
|
+
set_cyclicial_cyclemod(h)
|
102
|
+
elsif resource.class.cookie_crypt_auth_through == :one_question_random
|
103
|
+
set_random_cyclemod
|
104
|
+
elsif resource.class.cookie_crypt_auth_through == :two_questions_cyclical
|
105
|
+
set_cyclicial_cyclemod(h)
|
106
|
+
|
107
|
+
if session[:cyclemod]+resource.security_cycle+1 <= h.keys.count/2
|
108
|
+
next_question_mod = session[:cyclemod]+1
|
109
|
+
else
|
110
|
+
next_question_mod = 0
|
111
|
+
end
|
116
112
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
return true
|
131
|
-
end
|
113
|
+
@questions << h["security_question_#{next_question_mod}"]
|
114
|
+
elsif resource.class.cookie_crypt_auth_through == :two_questions_random
|
115
|
+
if resource.cookie_crypt_attempts_count == 0
|
116
|
+
session[:cyclemod] = Random.rand(0..(h.keys.count/2))
|
117
|
+
r = Random.rand(0..(h.keys.count/2))
|
118
|
+
while session[:cyclemod] != r && resource.security_cycle != r
|
119
|
+
r = Random.rand(0..(h.keys.count/2))
|
120
|
+
end
|
121
|
+
session[:cyclemod2] = r
|
122
|
+
elsif resource.cookie_crypt_attempts_count != 0 && resource.cookie_crypt_attempts_count%resource.class.cycle_question_on_fail_count == 0
|
123
|
+
r = Random.rand(0..(h.keys.count/2))
|
124
|
+
while session[:cyclemod] != r && resource.security_cycle != r
|
125
|
+
r = Random.rand(0..(h.keys.count/2))
|
132
126
|
end
|
127
|
+
session[:cyclemod] = r
|
128
|
+
|
129
|
+
r = Random.rand(0..(h.keys.count/2))
|
130
|
+
while session[:cyclemod] != r && resource.security_cycle != r
|
131
|
+
r = Random.rand(0..(h.keys.count/2))
|
132
|
+
end
|
133
|
+
session[:cyclemod2] = r
|
134
|
+
end
|
135
|
+
|
136
|
+
@questions << h["security_question_#{session[:cyclemod2]}"]
|
137
|
+
else #:all_questions case
|
138
|
+
h.keys.delete_if{|x| x.include?("answer")}.each do |key|
|
139
|
+
@questions << h[key]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
unless resource.class.cookie_crypt_auth_through == :all_questions
|
145
|
+
if resource.class.cookie_crypt_auth_through == :one_question_cyclical ||
|
146
|
+
resource.class.cookie_crypt_auth_through == :two_questions_cyclical
|
147
|
+
@questions << h["security_question_#{resource.security_cycle+session[:cyclemod]}"]
|
148
|
+
else #random cyclemod case
|
149
|
+
@questions << h["security_question_#{session[:cyclemod]}"]
|
133
150
|
end
|
134
151
|
end
|
135
152
|
end
|
136
|
-
false
|
137
153
|
end
|
138
154
|
|
139
|
-
def
|
140
|
-
|
155
|
+
def show_request
|
156
|
+
action_name == "show"
|
141
157
|
end
|
142
158
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<div id="security_binder_<%= session[:cookie_crypt_questions_count]%>">
|
2
|
+
<h3>Please input a security question</h3>
|
3
|
+
|
4
|
+
<%=text_field_tag "security_question_#{session[:cookie_crypt_questions_count]}", nil, size: 50, name: "security[security_question_#{session[:cookie_crypt_questions_count]}]" %>
|
5
|
+
<br></br>
|
6
|
+
|
7
|
+
<h3>Please input a security answer</h3>
|
8
|
+
|
9
|
+
<%=text_field_tag "security_answer_#{session[:cookie_crypt_questions_count]}", nil, size: 50, name: "security[security_answer_#{session[:cookie_crypt_questions_count]}]" %>
|
10
|
+
<br></br>
|
11
|
+
|
12
|
+
<%= link_to "Remove this question / answer pair?", "#{request.fullpath}?remove=#{session[:cookie_crypt_questions_count]}", remote: true %>
|
13
|
+
<br></br>
|
14
|
+
</div>
|
@@ -2,46 +2,84 @@
|
|
2
2
|
<h2>Two Factor Login Page</h2>
|
3
3
|
|
4
4
|
<%=form_tag([resource_name, :cookie_crypt], method: :put) do %>
|
5
|
-
<%
|
5
|
+
<% security_hash = Hash.class_eval(@user.security_hash) %>
|
6
|
+
<% if security_hash.empty? %>
|
6
7
|
<h2>You have not yet setup two-factor questions and answers. Please follow the instructions below.</h2>
|
7
8
|
|
8
9
|
<h2>Note: It is a generally a good idea to have your questions be about people/events/objects that do not change over time.</h2>
|
9
10
|
|
10
|
-
|
11
|
+
<% (1..@user.class.cookie_crypt_minimum_questions).each do |n| %>
|
12
|
+
<h3>Please input a security question</h3>
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
+
<%=text_field_tag "security_question_#{n}", nil, size: 50, name: "security[security_question_#{n}]" %>
|
15
|
+
<br></br>
|
14
16
|
|
15
|
-
|
17
|
+
<h3>Please input a security answer</h3>
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
<%=text_field_tag "security_answer_#{n}", nil, size: 50, name: "security[security_answer_#{n}]" %>
|
20
|
+
<br></br>
|
21
|
+
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<% if @user.class.enable_custom_question_counts %>
|
25
|
+
<div id="cookie_crypt_additions_binder"></div>
|
26
|
+
<%= link_to "Add more security questions and answers?", "#{request.fullpath}", remote: true %>
|
27
|
+
<br></br>
|
28
|
+
<% end %>
|
19
29
|
|
20
|
-
<
|
30
|
+
<h2>Please note that you will not be given a security token to login by skipping two factor until you login next time.</h2>
|
21
31
|
|
22
|
-
|
23
|
-
<
|
32
|
+
<% elsif (security_hash.keys.count/2) < @user.class.cookie_crypt_minimum_questions %>
|
33
|
+
<h2>There has been a change with the system that requires you to input more security questions and answers. Please follow the instructions below.</h2>
|
24
34
|
|
25
|
-
<h3>
|
35
|
+
<h3 class="centered">Your current questions are
|
26
36
|
|
27
|
-
|
37
|
+
<% h = Hash.class_eval(@user.security_hash) %>
|
38
|
+
<% h.keys.delete_if{|x| x.include?("answer")}.each do |key| %>
|
39
|
+
|
40
|
+
<h3><%="#{h[key]}"%></h2>
|
41
|
+
|
42
|
+
<% end %>
|
28
43
|
<br></br>
|
29
|
-
|
30
|
-
<h2>Please note that you will not be given a security token to login by skipping two factor until you login next time.</h2>
|
31
44
|
|
32
|
-
|
45
|
+
<% (1..@user.class.cookie_crypt_minimum_questions).each do |n| %>
|
33
46
|
|
34
|
-
|
47
|
+
<% next if security_hash["security_question_#{n}"] %>
|
35
48
|
|
36
|
-
|
49
|
+
<h3>Please input a security question</h3>
|
37
50
|
|
38
|
-
|
51
|
+
<%=text_field_tag "security_question_#{n}", nil, size: 50, name: "security[security_question_#{n}]" %>
|
52
|
+
<br></br>
|
53
|
+
|
54
|
+
<h3>Please input a security answer</h3>
|
55
|
+
|
56
|
+
<%=text_field_tag "security_answer_#{n}", nil, size: 50, name: "security[security_answer_#{n}]" %>
|
57
|
+
<br></br>
|
39
58
|
|
40
|
-
|
59
|
+
<% end %>
|
41
60
|
|
42
|
-
|
61
|
+
<% if @user.class.enable_custom_question_counts %>
|
62
|
+
<div id="cookie_crypt_additions_binder"></div>
|
63
|
+
<%= link_to "Add more security questions and answers?", "#{request.fullpath}", remote: true %>
|
64
|
+
<br></br>
|
65
|
+
<% end %>
|
43
66
|
|
67
|
+
<h2>You will be authenticated for this session but not for your next login. You will need to enter your security answers next time.</h2>
|
44
68
|
<br></br>
|
69
|
+
<% else %>
|
70
|
+
|
71
|
+
<% h = Hash.class_eval(@user.security_hash) %>
|
72
|
+
|
73
|
+
<% @questions.each do |q| %>
|
74
|
+
|
75
|
+
<h2><%="#{q}"%></h2>
|
76
|
+
|
77
|
+
<%=text_field_tag h.key(q).gsub('question','answer'), nil, size: 50, name: "security_answers[#{h.key(q).gsub('question','answer')}]" %>
|
78
|
+
|
79
|
+
<br></br>
|
80
|
+
|
81
|
+
<% end %>
|
82
|
+
|
45
83
|
<% end %>
|
46
84
|
|
47
85
|
<%= submit_tag "Submit" %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<% if params[:remove] %>
|
2
|
+
$('#security_binder_<%= params[:remove] %>').remove();
|
3
|
+
<% else %>
|
4
|
+
<% session[:cookie_crypt_questions_count] ||= @user.class.cookie_crypt_minimum_questions %>
|
5
|
+
<% session[:cookie_crypt_questions_count] += 1 %>
|
6
|
+
|
7
|
+
$('#cookie_crypt_additions_binder').append('<%= escape_javascript(render partial: "extra_fields")%>');
|
8
|
+
<% end %>
|
data/cookie_crypt.gemspec
CHANGED
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.description = <<-EOF
|
13
13
|
### Features ###
|
14
14
|
* User customizable security questions and answers
|
15
|
-
* Configurable max login attempts
|
16
|
-
*
|
15
|
+
* Configurable max login attempts & cookie expiration time
|
16
|
+
* Per user level of control (Allow certain ips to bypass two-factor)
|
17
17
|
EOF
|
18
18
|
|
19
19
|
s.rubyforge_project = "cookie_crypt"
|
@@ -29,4 +29,19 @@ Gem::Specification.new do |s|
|
|
29
29
|
|
30
30
|
s.add_development_dependency 'bundler'
|
31
31
|
s.license = 'MIT'
|
32
|
+
s.post_install_message = <<END_DESC
|
33
|
+
|
34
|
+
********************************************
|
35
|
+
|
36
|
+
A major revision was made with the 1.1 update for CookieCrypt.
|
37
|
+
|
38
|
+
You will need to run 'bundle exec rails g cookie_crypt MODEL' again
|
39
|
+
|
40
|
+
to start the upgrade process from 1.0 to 1.1.
|
41
|
+
|
42
|
+
For more information check the homepage at 'https://github.com/loualrid/CookieCrypt'
|
43
|
+
|
44
|
+
********************************************
|
45
|
+
|
46
|
+
END_DESC
|
32
47
|
end
|
data/lib/cookie_crypt.rb
CHANGED
@@ -4,9 +4,13 @@ require 'digest'
|
|
4
4
|
require 'active_support/concern'
|
5
5
|
|
6
6
|
module Devise
|
7
|
-
mattr_accessor :max_cookie_crypt_login_attempts, :cookie_deletion_time_frame
|
7
|
+
mattr_accessor :max_cookie_crypt_login_attempts, :cookie_deletion_time_frame, :cookie_crypt_auth_through, :cookie_crypt_minimum_questions, :cycle_question_on_fail_count, :enable_custom_question_counts
|
8
8
|
@@max_cookie_crypt_login_attempts = 3
|
9
|
-
@@cookie_deletion_time_frame = 30.days.from_now
|
9
|
+
@@cookie_deletion_time_frame = '30.days.from_now'
|
10
|
+
@@cookie_crypt_auth_through = :one_question_cyclical
|
11
|
+
@@cookie_crypt_minimum_questions = 3
|
12
|
+
@@cycle_question_on_fail_count = 2
|
13
|
+
@@enable_custom_question_counts = false
|
10
14
|
end
|
11
15
|
|
12
16
|
module CookieCrypt
|
@@ -9,6 +9,31 @@ module CookieCrypt
|
|
9
9
|
|
10
10
|
private
|
11
11
|
|
12
|
+
def authentication_success
|
13
|
+
flash[:notice] = 'Signed in through two-factor authentication successfully.'
|
14
|
+
warden.session(resource_name)[:need_cookie_crypt_auth] = false
|
15
|
+
sign_in resource_name, resource, :bypass => true
|
16
|
+
resource.update_attribute(:cookie_crypt_attempts_count, 0)
|
17
|
+
redirect_to stored_location_for(resource_name) || :root
|
18
|
+
end
|
19
|
+
|
20
|
+
def cookie_crypt_auth_path_for(resource_or_scope = nil)
|
21
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
22
|
+
change_path = "#{scope}_cookie_crypt_path"
|
23
|
+
send(change_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def encrypted_username_and_pass
|
27
|
+
Digest::SHA512.hexdigest("#{resource.username}_#{resource.encrypted_password}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_cookie
|
31
|
+
cookies["#{resource.username}_#{Rails.application.class.to_s.split("::").first}".to_sym] = {
|
32
|
+
value: "#{encrypted_username_and_pass}",
|
33
|
+
expires: Date.class_eval("#{resource.class.cookie_deletion_time_frame}")
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
12
37
|
def handle_cookie_crypt
|
13
38
|
unless devise_controller?
|
14
39
|
Devise.mappings.keys.flatten.any? do |scope|
|
@@ -28,12 +53,121 @@ module CookieCrypt
|
|
28
53
|
end
|
29
54
|
end
|
30
55
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
56
|
+
def has_matching_encrypted_cookie?
|
57
|
+
cookies["#{resource.username}_#{Rails.application.class.to_s.split("::").first}"] == encrypted_username_and_pass
|
58
|
+
end
|
59
|
+
|
60
|
+
def log_hack_attempt
|
61
|
+
logger = Logger.new("#{Rails.root.join('log','hack_attempts.log')}")
|
62
|
+
logger.warn "Attempt to bypass two factor authentication and devise detected from ip #{request.remote_ip} using #{resource_name}: #{resource.inspect}!"
|
63
|
+
end
|
64
|
+
|
65
|
+
def log_agent_to_resource
|
66
|
+
unless using_an_agent_that_is_already_being_used?
|
67
|
+
resource.agent_list = "#{resource.agent_list}#{'|' unless resource.agent_list.blank?}#{request.user_agent}"
|
68
|
+
resource.save
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def matching_answers? hash
|
73
|
+
answers = []
|
74
|
+
unless resource.class.cookie_crypt_auth_through == :all_questions
|
75
|
+
if resource.class.cookie_crypt_auth_through == :one_question_cyclical ||
|
76
|
+
resource.class.cookie_crypt_auth_through == :two_questions_cyclical
|
77
|
+
answers << h["security_answer_#{resource.security_cycle+session[:cyclemod]}"]
|
78
|
+
else #random cyclemod case
|
79
|
+
answers << h["security_answer_#{session[:cyclemod]}"]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if resource.class.cookie_crypt_auth_through == :two_questions_cyclical
|
84
|
+
if session[:cyclemod]+resource.security_cycle+1 <= hash.keys.count/2
|
85
|
+
next_question_mod = session[:cyclemod]+1
|
86
|
+
else
|
87
|
+
next_question_mod = 0
|
88
|
+
end
|
89
|
+
|
90
|
+
answers << "security_answer_#{next_question_mod}"
|
91
|
+
elsif resource.class.cookie_crypt_auth_through == :two_questions_random
|
92
|
+
answers << "security_answer_#{session[:cyclemod2]}"
|
93
|
+
elsif resource.class.cookie_crypt_auth_through == :all_questions
|
94
|
+
hash.keys.delete_if{|x| x.include?("question")}.each do |key|
|
95
|
+
answers << key
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
authed = false
|
100
|
+
a_arr = []
|
101
|
+
answers.sort.each do |key|
|
102
|
+
if hash[key] == Digest::SHA512.hexdigest(sanitize(params[:security_answers][key]))
|
103
|
+
a_arr[answers.index(key)] = true
|
104
|
+
else
|
105
|
+
a_arr[answers.index(key)] = false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
authed = true unless q_arr.include?(false)
|
110
|
+
authed
|
35
111
|
end
|
36
112
|
|
113
|
+
def set_cyclicial_cyclemod hash
|
114
|
+
if resource.cookie_crypt_attempts_count == 0
|
115
|
+
session[:cyclemod] = 0
|
116
|
+
elsif resource.cookie_crypt_attempts_count != 0 && resource.cookie_crypt_attempts_count%resource.class.cycle_question_on_fail_count == 0
|
117
|
+
session[:cyclemod] += 1
|
118
|
+
end
|
119
|
+
|
120
|
+
session[:cyclemod] = 0 if session[:cyclemod]+resource.security_cycle > hash.keys.count/2
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_random_cyclemod hash
|
124
|
+
if resource.cookie_crypt_attempts_count == 0
|
125
|
+
session[:cyclemod] = Random.rand(0..(hash.keys.count/2))
|
126
|
+
elsif resource.cookie_crypt_attempts_count != 0 && resource.cookie_crypt_attempts_count%resource.class.cycle_question_on_fail_count == 0
|
127
|
+
r = Random.rand(0..(hash.keys.count/2))
|
128
|
+
while session[:cyclemod] != r && resource.security_cycle != r
|
129
|
+
r = Random.rand(0..(hash.keys.count/2))
|
130
|
+
end
|
131
|
+
session[:cyclemod] = r
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def update_resource_cycle hash
|
137
|
+
if resource.security_cycle+1 > hash.keys.count/2
|
138
|
+
resource.security_cycle = 1
|
139
|
+
else
|
140
|
+
resource.security_cycle += 1
|
141
|
+
end
|
142
|
+
|
143
|
+
resource.save
|
144
|
+
end
|
145
|
+
|
146
|
+
def using_an_agent_that_is_already_being_used?
|
147
|
+
unless resource.agent_list.blank?
|
148
|
+
request_agent = UserAgent.parse("#{request.user_agent}")
|
149
|
+
resource.agent_list.split('|').each do |agent_string|
|
150
|
+
if agent_string.include?("#{request_agent.application}")
|
151
|
+
agent = UserAgent.parse("#{agent_string}")
|
152
|
+
if agent.application == request_agent.application && agent.browser == request_agent.browser
|
153
|
+
if request_agent >= agent #version number is higher for example
|
154
|
+
#update user agent string and return true
|
155
|
+
resource.agent_list = resource.agent_list.gsub("#{agent.browser}/#{agent.version}","#{request_agent.browser}/#{request_agent.version}")
|
156
|
+
resource.save
|
157
|
+
return true
|
158
|
+
elsif request_agent.version == agent.version
|
159
|
+
return true
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
false
|
166
|
+
end
|
167
|
+
|
168
|
+
def unrecognized_agent?
|
169
|
+
resource.agent_list.include?("#{request.user_agent}")
|
170
|
+
end
|
37
171
|
end
|
38
172
|
end
|
39
173
|
end
|
@@ -5,7 +5,7 @@ module Devise
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
::Devise::Models.config(self, :max_cookie_crypt_login_attempts, :cookie_deletion_time_frame)
|
8
|
+
::Devise::Models.config(self, :max_cookie_crypt_login_attempts, :cookie_deletion_time_frame, :cookie_crypt_auth_through, :cookie_crypt_minimum_questions, :cycle_question_on_fail_count, :enable_custom_question_counts)
|
9
9
|
end
|
10
10
|
|
11
11
|
def need_cookie_crypt_auth?(request)
|
data/lib/cookie_crypt/version.rb
CHANGED
@@ -5,10 +5,23 @@ module ActiveRecord
|
|
5
5
|
class CookieCryptGenerator < ActiveRecord::Generators::Base
|
6
6
|
source_root File.expand_path("../templates", __FILE__)
|
7
7
|
|
8
|
-
def
|
9
|
-
|
8
|
+
def copy_cookie_crypt_migration_1_0
|
9
|
+
if ActiveRecord::Base.class_eval("#{table_name.camelize.singularize}.inspect['security_question_one: string'].blank?")
|
10
|
+
migration_template "migration.rb", "db/migrate/cookie_crypt_add_to_#{table_name}"
|
11
|
+
end
|
10
12
|
end
|
11
13
|
|
14
|
+
def copy_cookie_crypt_migration_1_1
|
15
|
+
if ActiveRecord::Base.class_eval("#{table_name.camelize.singularize}.inspect['security_hash: text'].blank?")
|
16
|
+
migration_template "migration_1_1.rb", "db/migrate/cookie_crypt_1_1_update_to_#{table_name}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def copy_cookie_crypt_migration_1_1_cleanup
|
21
|
+
if $generate_1_1_cleanup_migration
|
22
|
+
migration_template "migration_1_1_cleanup.rb", "db/migrate/cookie_crypt_1_1_cleanup_to_#{table_name}"
|
23
|
+
end
|
24
|
+
end
|
12
25
|
end
|
13
26
|
end
|
14
27
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class CookieCrypt11CleanupTo<%= table_name.camelize %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
remove_column :<%= table_name %>, :security_question_one
|
4
|
+
remove_column :<%= table_name %>, :security_question_two
|
5
|
+
remove_column :<%= table_name %>, :security_answer_one
|
6
|
+
remove_column :<%= table_name %>, :security_answer_two
|
7
|
+
end
|
8
|
+
end
|
@@ -2,30 +2,98 @@ module CookieCryptable
|
|
2
2
|
module Generators
|
3
3
|
class CookieCryptGenerator < Rails::Generators::NamedBase
|
4
4
|
namespace "cookie_crypt"
|
5
|
-
desc "
|
6
|
-
|
7
|
-
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
inject_into_file(paths[
|
13
|
-
|
14
|
-
\n
|
5
|
+
desc "Automates setup process, will also update between versions."
|
6
|
+
|
7
|
+
#BEGIN 1.0 generator
|
8
|
+
def inject_1_0_cookie_crypt_content
|
9
|
+
if ActiveRecord::Base.class_eval("#{table_name.camelize.singularize}.inspect['security_question_one: string'].blank?")
|
10
|
+
puts "Beginning 1.0 content injection..."
|
11
|
+
paths = [File.join("app", "models", "#{file_path}.rb"),File.join("config", "initializers", "devise.rb")]
|
12
|
+
inject_into_file(paths[0], "cookie_cryptable, :", :after => "devise :") if File.exists?(paths[0])
|
13
|
+
if File.exists?(paths[1])
|
14
|
+
inject_into_file(paths[1], "\n # ==> Cookie Crypt Configuration Parameters\n config.max_cookie_crypt_login_attempts = 3
|
15
|
+
\n # For cookie_deletion_time_frame field, make sure your timeframe parses into an actual date and is a string
|
16
|
+
\n config.cookie_deletion_time_frame = '30.days.from_now'", after: "Devise.setup do |config|")
|
17
|
+
end
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
18
21
|
source_root File.expand_path('../../../../app/views/devise/cookie_crypt', __FILE__)
|
19
22
|
|
20
|
-
def
|
23
|
+
def generate_1_0_files
|
21
24
|
Dir.mkdir("app/views/devise") unless Dir.exists?("app/views/devise")
|
22
25
|
unless Dir.exists?("app/views/devise/cookie_crypt")
|
26
|
+
puts "Beginning 1.0 views creation..."
|
23
27
|
Dir.mkdir("app/views/devise/cookie_crypt")
|
24
28
|
copy_file "max_login_attempts_reached.html.erb", "app/views/devise/cookie_crypt/max_login_attempts_reached.html.erb"
|
25
29
|
copy_file "show.html.erb", "app/views/devise/cookie_crypt/show.html.erb"
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
33
|
+
source_root File.expand_path(__FILE__)
|
34
|
+
|
35
|
+
#BEGIN 1.1 generator
|
36
|
+
def inject_1_1_cookie_crypt_content
|
37
|
+
if ActiveRecord::Base.class_eval("#{table_name.camelize.singularize}.inspect['security_hash: text'].blank?")
|
38
|
+
puts "Beginning 1.1 content injection..."
|
39
|
+
paths = [File.join("app", "models", "#{file_path}.rb"),File.join("config", "initializers", "devise.rb")]
|
40
|
+
if File.exists?(paths[1])
|
41
|
+
inject_into_file(paths[1], "\n # cookie_crypt_auth_through manages the various styles of authenticating through two factor when the need arises.
|
42
|
+
\n # Valid options are: :one_question_cyclical, :one_question_random, :two_questions_cyclical, :two_questions_random, :all_questions
|
43
|
+
\n config.cookie_crypt_auth_through = :one_question_cyclical
|
44
|
+
\n # cookie_crypt_minimum_questions determines how many questions and answers the user must create the first time they are auth'ing through CC
|
45
|
+
\n # This option must be greater than or equal to 2.
|
46
|
+
\n config.cookie_crypt_minimum_questions = 3
|
47
|
+
\n # cycle_question_on_fail_count determines how many tries the user gets per question(s) before the system changes the questions shown
|
48
|
+
\n # It is recommended to set this to at least 2, but 1 is allowed. This value is ignored if the system is set to :all_questions
|
49
|
+
\n config.cycle_question_on_fail_count = 2
|
50
|
+
\n # enable_custom_question_counts allows users to have *more* than the minimum number of questions. This works via ajax and javascript.
|
51
|
+
\n config.enable_custom_question_counts = false", after: " # ==> Cookie Crypt Configuration Parameters")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
source_root File.expand_path('../../../../app/views/devise/cookie_crypt', __FILE__)
|
57
|
+
|
58
|
+
def generate_1_1_files
|
59
|
+
unless File.exist?("app/views/devise/cookie_crypt/show.js.erb")
|
60
|
+
puts "Beginning 1.1 views creation..."
|
61
|
+
copy_file "show.js.erb", "app/views/devise/cookie_crypt/show.js.erb"
|
62
|
+
copy_file "_extra_fields.html.erb", "app/views/devise/cookie_crypt/_extra_fields.html.erb"
|
63
|
+
File.delete("app/views/devise/cookie_crypt/show.html.erb")
|
64
|
+
copy_file "show.html.erb", "app/views/devise/cookie_crypt/show.html.erb"
|
65
|
+
|
66
|
+
puts "Please run rake db:migrate then run this generator again to cleanup unused fields."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_1_1_update
|
71
|
+
unless ActiveRecord::Base.class_eval("#{table_name.camelize.singularize}.inspect['security_hash: text'].blank?")
|
72
|
+
unless ActiveRecord::Base.class_eval("#{table_name.camelize.singularize}.inspect['security_question_one: string'].blank?")
|
73
|
+
puts "Beginning data cleanup, moving 1.0 database data to 1.1 database style..."
|
74
|
+
objs = ActiveRecord::Base.class_eval("#{table_name.camelize.singularize}.all")
|
75
|
+
objs.each do |obj|
|
76
|
+
next if obj.security_question_one.blank?
|
77
|
+
h = {}
|
78
|
+
h["security_question_1"] = obj.security_question_one
|
79
|
+
h["security_answer_1"] = obj.security_answer_one
|
80
|
+
h["security_question_2"] = obj.security_question_two
|
81
|
+
h["security_answer_2"] = obj.security_answer_two
|
82
|
+
obj.security_hash = h.to_s
|
83
|
+
|
84
|
+
obj.save
|
85
|
+
|
86
|
+
puts "#{obj.security_hash}"
|
87
|
+
end
|
88
|
+
|
89
|
+
puts "Completed data cleanup, database is now 1.1 ready."
|
90
|
+
puts "Generating cleanup migration that will remove now unneeded security_question_one, security_answer_one, security_question_two, security_answer_two fields."
|
91
|
+
|
92
|
+
$generate_1_1_cleanup_migration = true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
29
97
|
hook_for :orm
|
30
98
|
end
|
31
99
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cookie_crypt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitrii Golub
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -70,8 +70,8 @@ dependencies:
|
|
70
70
|
description: |2
|
71
71
|
### Features ###
|
72
72
|
* User customizable security questions and answers
|
73
|
-
* Configurable max login attempts
|
74
|
-
*
|
73
|
+
* Configurable max login attempts & cookie expiration time
|
74
|
+
* Per user level of control (Allow certain ips to bypass two-factor)
|
75
75
|
email:
|
76
76
|
- loualrid@gmail.com
|
77
77
|
executables: []
|
@@ -84,8 +84,10 @@ files:
|
|
84
84
|
- README.md
|
85
85
|
- Rakefile
|
86
86
|
- app/controllers/devise/cookie_crypt_controller.rb
|
87
|
+
- app/views/devise/cookie_crypt/_extra_fields.html.erb
|
87
88
|
- app/views/devise/cookie_crypt/max_login_attempts_reached.html.erb
|
88
89
|
- app/views/devise/cookie_crypt/show.html.erb
|
90
|
+
- app/views/devise/cookie_crypt/show.js.erb
|
89
91
|
- config/locales/en.yml
|
90
92
|
- cookie_crypt.gemspec
|
91
93
|
- lib/cookie_crypt.rb
|
@@ -99,12 +101,27 @@ files:
|
|
99
101
|
- lib/cookie_crypt/version.rb
|
100
102
|
- lib/generators/active_record/cookie_crypt_generator.rb
|
101
103
|
- lib/generators/active_record/templates/migration.rb
|
104
|
+
- lib/generators/active_record/templates/migration_1_1.rb
|
105
|
+
- lib/generators/active_record/templates/migration_1_1_cleanup.rb
|
102
106
|
- lib/generators/cookie_crypt/cookie_crypt_generator.rb
|
103
107
|
homepage: https://github.com/loualrid/CookieCrypt
|
104
108
|
licenses:
|
105
109
|
- MIT
|
106
110
|
metadata: {}
|
107
|
-
post_install_message:
|
111
|
+
post_install_message: |2+
|
112
|
+
|
113
|
+
********************************************
|
114
|
+
|
115
|
+
A major revision was made with the 1.1 update for CookieCrypt.
|
116
|
+
|
117
|
+
You will need to run 'bundle exec rails g cookie_crypt MODEL' again
|
118
|
+
|
119
|
+
to start the upgrade process from 1.0 to 1.1.
|
120
|
+
|
121
|
+
For more information check the homepage at 'https://github.com/loualrid/CookieCrypt'
|
122
|
+
|
123
|
+
********************************************
|
124
|
+
|
108
125
|
rdoc_options: []
|
109
126
|
require_paths:
|
110
127
|
- lib
|