cookie_crypt 1.0.0 → 1.1.0
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/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
|