cookie_crypt 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/README.md +113 -0
- data/Rakefile +1 -0
- data/app/controllers/devise/cookie_crypt_controller.rb +142 -0
- data/app/views/devise/cookie_crypt/max_login_attempts_reached.html.erb +3 -0
- data/app/views/devise/cookie_crypt/show.html.erb +51 -0
- data/config/locales/en.yml +4 -0
- data/cookie_crypt.gemspec +32 -0
- data/lib/cookie_crypt/controllers/helpers.rb +39 -0
- data/lib/cookie_crypt/hooks/cookie_cryptable.rb +6 -0
- data/lib/cookie_crypt/models/cookie_cryptable.rb +20 -0
- data/lib/cookie_crypt/orm/active_record.rb +12 -0
- data/lib/cookie_crypt/rails.rb +7 -0
- data/lib/cookie_crypt/routes.rb +9 -0
- data/lib/cookie_crypt/schema.rb +8 -0
- data/lib/cookie_crypt/version.rb +3 -0
- data/lib/cookie_crypt.rb +24 -0
- data/lib/generators/active_record/cookie_crypt_generator.rb +14 -0
- data/lib/generators/active_record/templates/migration.rb +13 -0
- data/lib/generators/cookie_crypt/cookie_crypt_generator.rb +32 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 03a95f39e7dfef045da3c962fd52a2df5ace9e11
|
4
|
+
data.tar.gz: af04600f35989fb07666ec55558406210f16612f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 645fa5fe1fe957cd9d4157a0ecf1978ddf0cba504a720029a332239aa9b70b454058f7a64693978d4b12a419e6e167631fb83cee8bdceb5f6ec6cc87c25626cc
|
7
|
+
data.tar.gz: 938a738da2b1f352aeb1309fb6b1bb0036814dd48aa93c8999a98743aee687ac897d8b6288ef0a88a82b4d5d757cfb8573848e6e1da6fbee520de827227eb699
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2013 Louis Alridge
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
## Cookie Crypt
|
2
|
+
|
3
|
+
## Encrypted Cookie Two Factor Authentication for Devise
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* User customizable security questions and answers
|
8
|
+
* Configurable max login attempts
|
9
|
+
* Per user level of control (Allow certain ips to bypass two-factor)
|
10
|
+
* Customizable views
|
11
|
+
|
12
|
+
## Configuration
|
13
|
+
|
14
|
+
### Initial Setup
|
15
|
+
|
16
|
+
In a Rails environment, require the gem in your Gemfile:
|
17
|
+
|
18
|
+
gem 'cookie_crypt'
|
19
|
+
|
20
|
+
Once that's done, run:
|
21
|
+
|
22
|
+
bundle install
|
23
|
+
|
24
|
+
|
25
|
+
### Automatic installation
|
26
|
+
|
27
|
+
In order to add encrypted cookie two factor authorization to a model, run the command:
|
28
|
+
|
29
|
+
bundle exec rails g cookie_crypt MODEL
|
30
|
+
|
31
|
+
Where MODEL is your model name (e.g. User or Admin). This generator will add `:cookie_cryptable` to your model
|
32
|
+
and create a migration in `db/migrate/`, which will add the required columns to your table.
|
33
|
+
|
34
|
+
NOTE: This will create a field called "username" on the table it is creating if that field does not already exist.
|
35
|
+
The fields are security_question_one, security_question_two, security_answer_one, security_answer_two,
|
36
|
+
agent_list, and cookie_crypt_attempts_count
|
37
|
+
|
38
|
+
Finally, run the migration with:
|
39
|
+
|
40
|
+
bundle exec rake db:migrate
|
41
|
+
|
42
|
+
|
43
|
+
### Manual installation
|
44
|
+
|
45
|
+
To manually enable encrypted cookie two factor authentication for the User model, you should add cookie_crypt to your devise line, like:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
devise :database_authenticatable, :registerable,
|
49
|
+
:recoverable, :rememberable, :trackable, :validatable, :cookie_cryptable
|
50
|
+
```
|
51
|
+
|
52
|
+
Config setup
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
config.max_login_attempts = 3
|
56
|
+
config.cookie_deletion_time_frame = '30.days.from_now'
|
57
|
+
```
|
58
|
+
|
59
|
+
### Customisation
|
60
|
+
|
61
|
+
By default encrypted cookie two factor authentication is enabled for each user, you can change it with this method in your User model:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
def need_two_factor_authentication?(request)
|
65
|
+
request.ip != '127.0.0.1'
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
This will disable two factor authentication for local users and just puts the code in the logs.
|
70
|
+
|
71
|
+
### Rationalle
|
72
|
+
|
73
|
+
Cookie Crypt uses a cookie stored on a user's machine to monitor their authentication status. The first time a user passes the initial login
|
74
|
+
they are passed to the cookie crypt controller which prevents further action. They are "logged in" in the devise sense, but have an additional hook
|
75
|
+
preventing them from performing any actions that arent in the cookie crypt controller until they bypass that auth. If this is first time they are going
|
76
|
+
through cookie_crypt, the user will be presented with several labeled text boxes asking them to input two security questions and two security answers.
|
77
|
+
After inputting this information they are authenticated and redirected to the root of the application but not given an auth cookie.
|
78
|
+
|
79
|
+
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
|
+
whatever the user inputs for their answers in the future.
|
81
|
+
|
82
|
+
When the user attempts to login again, they will be displayed their two security questions and asked to answer them with their two security answers.
|
83
|
+
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 agent is logged and
|
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 will be locked out
|
86
|
+
until their cookie_crypt_attempts_count is reset to 0.
|
87
|
+
|
88
|
+
### Two factor Defense
|
89
|
+
|
90
|
+
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
|
+
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
|
+
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.
|
94
|
+
The system will log the attempt as successful and update the user's agent_list with the updated agent value.
|
95
|
+
|
96
|
+
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
|
+
|
98
|
+
But what if an attacker knows the user's username and password? The attacker must also know the user's security question answers to auth as the user.
|
99
|
+
|
100
|
+
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
|
+
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
|
102
|
+
file in Rails.root / log to notify the admins of a hacking attempt. The agent_list field stores information given out by a browser's user agent and contains a
|
103
|
+
decent enough amount of data for fingerprinting. More could be done in this regard (testing to see what fonts/plugins a browser has) but is outside the scope
|
104
|
+
of this gem and would make it more difficult for the gem to be only a minor inconvienence to the users.
|
105
|
+
|
106
|
+
What cookie crypt doesnt prevent:
|
107
|
+
An attacker that knows a user's username and password thats logging in from the user's machine / browser
|
108
|
+
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
|
109
|
+
An attacker that knows a user's username, password, security questions and answers to said questions
|
110
|
+
|
111
|
+
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
|
+
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
|
+
browser.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,142 @@
|
|
1
|
+
include ActionView::Helpers::SanitizeHelper
|
2
|
+
require "useragent"
|
3
|
+
require "logger"
|
4
|
+
class Devise::CookieCryptController < DeviseController
|
5
|
+
prepend_before_filter :authenticate_scope!
|
6
|
+
before_filter :prepare_and_validate, :handle_cookie_crypt
|
7
|
+
|
8
|
+
def show
|
9
|
+
if has_matching_encrypted_cookie?
|
10
|
+
if !using_an_agent_that_is_already_being_used?
|
11
|
+
#An attacker has successfully obtained a user's cookie and login credentials and is trying to pass themselves off as the target
|
12
|
+
#This is an attacker because the agent data does not match the agent data from when a cookie is generated for this user's machine.
|
13
|
+
#A machine that "suddenly" has a cookie despite not being auth'd is an attacker.
|
14
|
+
|
15
|
+
log_hack_attempt
|
16
|
+
|
17
|
+
resource.cookie_crypt_attempts_count = resource.class.max_cookie_crypt_login_attempts
|
18
|
+
resource.save #prevents attacker from deleting cookie and trying to login "normally" by inputting the user's two_fac answers
|
19
|
+
|
20
|
+
sign_out(resource)
|
21
|
+
redirect_to :root and return
|
22
|
+
else
|
23
|
+
authentication_success
|
24
|
+
end
|
25
|
+
else
|
26
|
+
flash[:notice] = "Signed In Successfully, now going through two factor authentication."
|
27
|
+
@user = resource
|
28
|
+
render template: "devise/cookie_crypt/show"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def update
|
33
|
+
if resource.security_question_one.blank? # initial case (first login)
|
34
|
+
|
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
|
+
resource.save
|
40
|
+
|
41
|
+
authentication_success
|
42
|
+
else
|
43
|
+
|
44
|
+
if matching_answers?
|
45
|
+
generate_cookie
|
46
|
+
log_agent_to_resource
|
47
|
+
authentication_success
|
48
|
+
else
|
49
|
+
resource.cookie_crypt_attempts_count += 1
|
50
|
+
resource.save
|
51
|
+
set_flash_message :error, :attempt_failed
|
52
|
+
if resource.max_cookie_crypt_login_attempts?
|
53
|
+
sign_out(resource)
|
54
|
+
render template: 'devise/cookie_crypt/max_login_attempts_reached' and return
|
55
|
+
else
|
56
|
+
render :show
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def authenticate_scope!
|
65
|
+
self.resource = send("current_#{resource_name}")
|
66
|
+
end
|
67
|
+
|
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
|
+
def prepare_and_validate
|
101
|
+
redirect_to :root and return if resource.nil?
|
102
|
+
@limit = resource.class.max_cookie_crypt_login_attempts
|
103
|
+
if resource.max_cookie_crypt_login_attempts?
|
104
|
+
sign_out(resource)
|
105
|
+
render template: 'devise/cookie_crypt/max_login_attempts_reached' and return
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def authentication_success
|
110
|
+
flash[:notice] = 'Signed in through two-factor authentication successfully.'
|
111
|
+
warden.session(resource_name)[:need_cookie_crypt_auth] = false
|
112
|
+
sign_in resource_name, resource, :bypass => true
|
113
|
+
resource.update_attribute(:cookie_crypt_attempts_count, 0)
|
114
|
+
redirect_to stored_location_for(resource_name) || :root
|
115
|
+
end
|
116
|
+
|
117
|
+
def using_an_agent_that_is_already_being_used?
|
118
|
+
unless resource.agent_list.blank?
|
119
|
+
request_agent = UserAgent.parse("#{request.user_agent}")
|
120
|
+
resource.agent_list.split('|').each do |agent_string|
|
121
|
+
if agent_string.include?("#{request_agent.application}")
|
122
|
+
agent = UserAgent.parse("#{agent_string}")
|
123
|
+
if agent.application == request_agent.application && agent.browser == request_agent.browser
|
124
|
+
if request_agent >= agent #version number is higher for example
|
125
|
+
#update user agent string and return true
|
126
|
+
resource.agent_list = resource.agent_list.gsub("#{agent.browser}/#{agent.version}","#{request_agent.browser}/#{request_agent.version}")
|
127
|
+
resource.save
|
128
|
+
return true
|
129
|
+
elsif request_agent.version == agent.version
|
130
|
+
return true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
false
|
137
|
+
end
|
138
|
+
|
139
|
+
def unrecognized_agent?
|
140
|
+
resource.agent_list.include?("#{request.user_agent}")
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
<h2>Two Factor Login Page</h2>
|
3
|
+
|
4
|
+
<%=form_tag([resource_name, :cookie_crypt], method: :put) do %>
|
5
|
+
<% if @user.security_question_one.blank? %>
|
6
|
+
<h2>You have not yet setup two-factor questions and answers. Please follow the instructions below.</h2>
|
7
|
+
|
8
|
+
<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
|
+
<h3>Please input your first security question</h3>
|
11
|
+
|
12
|
+
<%=text_field_tag :security_question_one, nil, size: 50 %>
|
13
|
+
<br></br>
|
14
|
+
|
15
|
+
<h3>Please input your first question's answer</h3>
|
16
|
+
|
17
|
+
<%=text_field_tag :security_answer_one, nil, size: 50 %>
|
18
|
+
<br></br>
|
19
|
+
|
20
|
+
<h3>Please input your second security question </h3>
|
21
|
+
|
22
|
+
<%=text_field_tag :security_question_two, nil, size: 50 %>
|
23
|
+
<br></br>
|
24
|
+
|
25
|
+
<h3>Please input your second question's answer</h3>
|
26
|
+
|
27
|
+
<%=text_field_tag :security_answer_two, nil, size: 50 %>
|
28
|
+
<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
|
+
|
32
|
+
<% else %>
|
33
|
+
|
34
|
+
<h2><%="#{@user.security_question_one}"%></h2>
|
35
|
+
|
36
|
+
<%=text_field_tag :security_answer_one, nil, size: 50 %>
|
37
|
+
|
38
|
+
<br></br>
|
39
|
+
|
40
|
+
<h2><%="#{@user.security_question_two}" %></h2>
|
41
|
+
|
42
|
+
<%=text_field_tag :security_answer_two, nil, size: 50 %>
|
43
|
+
|
44
|
+
<br></br>
|
45
|
+
<% end %>
|
46
|
+
|
47
|
+
<%= submit_tag "Submit" %>
|
48
|
+
|
49
|
+
<% end %>
|
50
|
+
<br></br>
|
51
|
+
<%=link_to "Sign out", destroy_user_session_path, :method => :delete %>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cookie_crypt/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "cookie_crypt"
|
7
|
+
s.version = CookieCrypt::VERSION.dup
|
8
|
+
s.authors = ["Dmitrii Golub","Louis Alridge"]
|
9
|
+
s.email = ["loualrid@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/loualrid/CookieCrypt"
|
11
|
+
s.summary = %q{Encrypted cookie two factor authentication plugin for devise}
|
12
|
+
s.description = <<-EOF
|
13
|
+
### Features ###
|
14
|
+
* User customizable security questions and answers
|
15
|
+
* Configurable max login attempts
|
16
|
+
* per user level control if he really need two factor authentication
|
17
|
+
EOF
|
18
|
+
|
19
|
+
s.rubyforge_project = "cookie_crypt"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
|
26
|
+
s.add_runtime_dependency 'rails', '>= 3.1.1'
|
27
|
+
s.add_runtime_dependency 'devise'
|
28
|
+
s.add_runtime_dependency 'useragent'
|
29
|
+
|
30
|
+
s.add_development_dependency 'bundler'
|
31
|
+
s.license = 'MIT'
|
32
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module CookieCrypt
|
2
|
+
module Controllers
|
3
|
+
module Helpers
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before_filter :handle_cookie_crypt
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def handle_cookie_crypt
|
13
|
+
unless devise_controller?
|
14
|
+
Devise.mappings.keys.flatten.any? do |scope|
|
15
|
+
if signed_in?(scope) and warden.session(scope)[:need_cookie_crypt_auth]
|
16
|
+
handle_failed_cookie_crypt_auth(scope)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle_failed_cookie_crypt_auth(scope)
|
23
|
+
if request.format.present? and request.format.html?
|
24
|
+
session["#{scope}_return_tor"] = request.path if request.get?
|
25
|
+
redirect_to cookie_crypt_auth_path_for(scope)
|
26
|
+
else
|
27
|
+
render nothing: true, status: :unauthorized
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def cookie_crypt_auth_path_for(resource_or_scope = nil)
|
32
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
33
|
+
change_path = "#{scope}_cookie_crypt_path"
|
34
|
+
send(change_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'cookie_crypt/hooks/cookie_cryptable'
|
2
|
+
module Devise
|
3
|
+
module Models
|
4
|
+
module CookieCryptable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
::Devise::Models.config(self, :max_cookie_crypt_login_attempts, :cookie_deletion_time_frame)
|
9
|
+
end
|
10
|
+
|
11
|
+
def need_cookie_crypt_auth?(request)
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def max_cookie_crypt_login_attempts?
|
16
|
+
cookie_crypt_attempts_count >= self.class.max_cookie_crypt_login_attempts
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module CookieCrypt
|
2
|
+
module Orm
|
3
|
+
module ActiveRecord
|
4
|
+
module Schema
|
5
|
+
include CookieCrypt::Schema
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::ConnectionAdapters::Table.send :include, CookieCrypt::Orm::ActiveRecord::Schema
|
12
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.send :include, CookieCrypt::Orm::ActiveRecord::Schema
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module ActionDispatch::Routing
|
2
|
+
class Mapper
|
3
|
+
protected
|
4
|
+
|
5
|
+
def devise_cookie_crypt(mapping, controllers)
|
6
|
+
resource :cookie_crypt, :only => [:show, :update], :path => mapping.path_names[:cookie_crypt], :controller => controllers[:cookie_crypt]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
data/lib/cookie_crypt.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'cookie_crypt/version'
|
2
|
+
require 'devise'
|
3
|
+
require 'digest'
|
4
|
+
require 'active_support/concern'
|
5
|
+
|
6
|
+
module Devise
|
7
|
+
mattr_accessor :max_cookie_crypt_login_attempts, :cookie_deletion_time_frame
|
8
|
+
@@max_cookie_crypt_login_attempts = 3
|
9
|
+
@@cookie_deletion_time_frame = 30.days.from_now
|
10
|
+
end
|
11
|
+
|
12
|
+
module CookieCrypt
|
13
|
+
autoload :Schema, 'cookie_crypt/schema'
|
14
|
+
module Controllers
|
15
|
+
autoload :Helpers, 'cookie_crypt/controllers/helpers'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Devise.add_module :cookie_cryptable, :model => 'cookie_crypt/models/cookie_cryptable', :controller => :cookie_crypt, :route => :cookie_crypt
|
20
|
+
|
21
|
+
require 'cookie_crypt/orm/active_record'
|
22
|
+
require 'cookie_crypt/routes'
|
23
|
+
require 'cookie_crypt/models/cookie_cryptable'
|
24
|
+
require 'cookie_crypt/rails'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Generators
|
5
|
+
class CookieCryptGenerator < ActiveRecord::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
def copy_cookie_crypt_migration
|
9
|
+
migration_template "migration.rb", "db/migrate/cookie_crypt_add_to_#{table_name}"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CookieCryptAddTo<%= table_name.camelize %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
change_table :<%= table_name %> do |t|
|
4
|
+
<%='t.string :username, null: false, default: ""' if ActiveRecord::Base.class_eval("#{table_name.camelize.singularize}.inspect['username: string'].blank?") %>
|
5
|
+
t.string :security_question_one, default: ""
|
6
|
+
t.string :security_question_two, default: ""
|
7
|
+
t.string :security_answer_one, default: ""
|
8
|
+
t.string :security_answer_two, default: ""
|
9
|
+
t.text :agent_list, default: ""
|
10
|
+
t.integer :cookie_crypt_attempts_count, default: 0
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CookieCryptable
|
2
|
+
module Generators
|
3
|
+
class CookieCryptGenerator < Rails::Generators::NamedBase
|
4
|
+
namespace "cookie_crypt"
|
5
|
+
desc "Adds :cookie_cryptable directive in the given model.
|
6
|
+
It also generates an active record migration."
|
7
|
+
|
8
|
+
def inject_cookie_crypt_content
|
9
|
+
paths = [File.join("app", "models", "#{file_path}.rb"),File.join("config", "initializers", "devise.rb")]
|
10
|
+
inject_into_file(paths[0], "cookie_cryptable, :", :after => "devise :") if File.exists?(paths[0])
|
11
|
+
if File.exists?(paths[1])
|
12
|
+
inject_into_file(paths[1], "\n # ==> Cookie Crypt Configuration Parameters\n config.max_cookie_crypt_login_attempts = 3
|
13
|
+
\n # For cookie_deletion_time_frame field, make sure your timeframe parses into an actual date and is a string
|
14
|
+
\n config.cookie_deletion_time_frame = '30.days.from_now'", after: "Devise.setup do |config|")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
source_root File.expand_path('../../../../app/views/devise/cookie_crypt', __FILE__)
|
19
|
+
|
20
|
+
def generate_files
|
21
|
+
Dir.mkdir("app/views/devise") unless Dir.exists?("app/views/devise")
|
22
|
+
unless Dir.exists?("app/views/devise/cookie_crypt")
|
23
|
+
Dir.mkdir("app/views/devise/cookie_crypt")
|
24
|
+
copy_file "max_login_attempts_reached.html.erb", "app/views/devise/cookie_crypt/max_login_attempts_reached.html.erb"
|
25
|
+
copy_file "show.html.erb", "app/views/devise/cookie_crypt/show.html.erb"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
hook_for :orm
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cookie_crypt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dmitrii Golub
|
8
|
+
- Louis Alridge
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 3.1.1
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 3.1.1
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: devise
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: useragent
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: bundler
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
description: |2
|
71
|
+
### Features ###
|
72
|
+
* User customizable security questions and answers
|
73
|
+
* Configurable max login attempts
|
74
|
+
* per user level control if he really need two factor authentication
|
75
|
+
email:
|
76
|
+
- loualrid@gmail.com
|
77
|
+
executables: []
|
78
|
+
extensions: []
|
79
|
+
extra_rdoc_files: []
|
80
|
+
files:
|
81
|
+
- .gitignore
|
82
|
+
- Gemfile
|
83
|
+
- LICENSE
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- app/controllers/devise/cookie_crypt_controller.rb
|
87
|
+
- app/views/devise/cookie_crypt/max_login_attempts_reached.html.erb
|
88
|
+
- app/views/devise/cookie_crypt/show.html.erb
|
89
|
+
- config/locales/en.yml
|
90
|
+
- cookie_crypt.gemspec
|
91
|
+
- lib/cookie_crypt.rb
|
92
|
+
- lib/cookie_crypt/controllers/helpers.rb
|
93
|
+
- lib/cookie_crypt/hooks/cookie_cryptable.rb
|
94
|
+
- lib/cookie_crypt/models/cookie_cryptable.rb
|
95
|
+
- lib/cookie_crypt/orm/active_record.rb
|
96
|
+
- lib/cookie_crypt/rails.rb
|
97
|
+
- lib/cookie_crypt/routes.rb
|
98
|
+
- lib/cookie_crypt/schema.rb
|
99
|
+
- lib/cookie_crypt/version.rb
|
100
|
+
- lib/generators/active_record/cookie_crypt_generator.rb
|
101
|
+
- lib/generators/active_record/templates/migration.rb
|
102
|
+
- lib/generators/cookie_crypt/cookie_crypt_generator.rb
|
103
|
+
homepage: https://github.com/loualrid/CookieCrypt
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project: cookie_crypt
|
123
|
+
rubygems_version: 2.0.6
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: Encrypted cookie two factor authentication plugin for devise
|
127
|
+
test_files: []
|