devise_fido_usf 0.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +33 -0
- data/Rakefile +33 -0
- data/app/assets/javascript/u2f-api.js +748 -0
- data/app/controllers/devise/fido_usf_authentications_controller.rb +54 -0
- data/app/controllers/devise/fido_usf_registrations_controller.rb +57 -0
- data/app/models/fido_usf/fido_usf_device.rb +7 -0
- data/app/views/devise/fido_usf_authentications/new.html.erb +41 -0
- data/app/views/devise/fido_usf_registrations/_device.html.erb +5 -0
- data/app/views/devise/fido_usf_registrations/_devices.html.erb +14 -0
- data/app/views/devise/fido_usf_registrations/destroy.js.erb +3 -0
- data/app/views/devise/fido_usf_registrations/new.html.erb +43 -0
- data/app/views/devise/fido_usf_registrations/show.html.erb +5 -0
- data/config/locales/en.yml +24 -0
- data/lib/devise_fido_usf.rb +13 -0
- data/lib/devise_fido_usf/controllers/helpers.rb +54 -0
- data/lib/devise_fido_usf/hooks/fido_usf_authenticatable.rb +10 -0
- data/lib/devise_fido_usf/models/fido_usf_authenticatable.rb +15 -0
- data/lib/devise_fido_usf/models/fido_usf_registerable.rb +12 -0
- data/lib/devise_fido_usf/rails.rb +8 -0
- data/lib/devise_fido_usf/routes.rb +15 -0
- data/lib/devise_fido_usf/version.rb +3 -0
- data/lib/generators/devise_fido_usf/install_generator.rb +59 -0
- data/lib/generators/devise_fido_usf/migrate_generator.rb +28 -0
- data/lib/generators/templates/README +44 -0
- data/lib/generators/templates/migration.rb +19 -0
- data/lib/tasks/devise_fido_usf_tasks.rake +4 -0
- metadata +240 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
class Devise::FidoUsfAuthenticationsController < DeviseController
|
2
|
+
before_action :find_resource_and_verify_password, only: [:new, :create]
|
3
|
+
|
4
|
+
def new
|
5
|
+
key_handles = @resource.fido_usf_devices.map(&:key_handle)
|
6
|
+
@app_id = helpers.u2f.app_id
|
7
|
+
@sign_requests = helpers.u2f.authentication_requests(key_handles)
|
8
|
+
@challenge = helpers.u2f.challenge
|
9
|
+
session[:"#{resource_name}_u2f_challenge"] = @challenge
|
10
|
+
render :new
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
begin
|
15
|
+
response = U2F::SignResponse.load_from_json(params[:response])
|
16
|
+
rescue TypeError
|
17
|
+
return redirect_to root_path
|
18
|
+
end
|
19
|
+
|
20
|
+
registration = @resource.fido_usf_devices.find_by_key_handle(response.key_handle)
|
21
|
+
return 'Need to register first' unless registration
|
22
|
+
|
23
|
+
begin
|
24
|
+
|
25
|
+
#helpers.u2f.authenticate!(session[:"#{resource_name}_u2f_challenge"], response, Base64.decode64(registration.public_key), registration.counter)
|
26
|
+
helpers.u2f.authenticate!(session[:"#{resource_name}_u2f_challenge"], response, registration.public_key, registration.counter)
|
27
|
+
registration.update(counter: response.counter, last_authenticated_at: Time.now)
|
28
|
+
|
29
|
+
# Remember the user (if applicable)
|
30
|
+
@resource.remember_me = Devise::TRUE_VALUES.include?(session[:"#{resource_name}_remember_me"]) if @resource.respond_to?(:remember_me=)
|
31
|
+
sign_in(resource_name, @resource)
|
32
|
+
|
33
|
+
set_flash_message(:notice, :signed_in) if is_navigational_format?
|
34
|
+
rescue U2F::Error => e
|
35
|
+
flash[:error] = "Unable to authenticate: #{e.class.name}"
|
36
|
+
return redirect_to root_path
|
37
|
+
ensure
|
38
|
+
session.delete(:"#{resource_name}_u2f_challenge")
|
39
|
+
end
|
40
|
+
|
41
|
+
respond_with resource, :location => after_sign_in_path_for(@resource)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def find_resource_and_verify_password
|
46
|
+
@resource = send("current_#{resource_name}")
|
47
|
+
if @resource.nil?
|
48
|
+
@resource = resource_class.find_by_id(session["#{resource_name}_id"])
|
49
|
+
end
|
50
|
+
if @resource.nil? || !Devise::TRUE_VALUES.include?(session[:"#{resource_name}_password_checked"])
|
51
|
+
redirect_to root_path
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Devise::FidoUsfRegistrationsController < ApplicationController
|
2
|
+
before_action :authenticate_user!
|
3
|
+
|
4
|
+
def new
|
5
|
+
@registration_requests = helpers.u2f.registration_requests
|
6
|
+
session[:challenges] = @registration_requests.map(&:challenge)
|
7
|
+
key_handles = current_user.fido_usf_devices.map(&:key_handle)
|
8
|
+
@sign_requests = helpers.u2f.authentication_requests(key_handles)
|
9
|
+
@app_id = helpers.u2f.app_id
|
10
|
+
render :new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Show a list of all registered devices
|
14
|
+
def show
|
15
|
+
@devices = current_user.fido_usf_devices.all
|
16
|
+
render :show
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy
|
20
|
+
device = current_user.fido_usf_devices.find(params[:id])
|
21
|
+
@fade_out_id = device.id
|
22
|
+
device.destroy
|
23
|
+
@devices = current_user.fido_usf_devices.all
|
24
|
+
flash[:success] = I18n.t('fido_usf.flashs.device.removed')
|
25
|
+
respond_to do |format|
|
26
|
+
format.js
|
27
|
+
format.html { redirect_to user_fido_usf_registration_url }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def create
|
32
|
+
begin
|
33
|
+
response = U2F::RegisterResponse.load_from_json(params[:response])
|
34
|
+
reg = helpers.u2f.register!(session[:challenges], response)
|
35
|
+
|
36
|
+
pubkey = reg.public_key
|
37
|
+
pubkey = Base64.decode64(reg.public_key) unless pubkey.bytesize == 65 && pubkey.byteslice(0) != "\x04"
|
38
|
+
|
39
|
+
FidoUsf::FidoUsfDevice.create!(
|
40
|
+
user: current_user,
|
41
|
+
name: 'Unnamed 1',
|
42
|
+
certificate: reg.certificate,
|
43
|
+
key_handle: reg.key_handle,
|
44
|
+
public_key: pubkey,
|
45
|
+
counter: reg.counter,
|
46
|
+
last_authenticated_at: Time.now)
|
47
|
+
flash[:success] = I18n.t('fido_usf.flashs.device.registered')
|
48
|
+
rescue U2F::Error => e
|
49
|
+
@error_message = "Unable to register: #{e.class.name}"
|
50
|
+
flash[:error] = @error_message
|
51
|
+
ensure
|
52
|
+
session.delete(:challenges)
|
53
|
+
end
|
54
|
+
|
55
|
+
redirect_to user_fido_usf_registration_path()
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<h2>Authenticate key</h2>
|
2
|
+
<p>Please insert one of your registered keys and press the button within 15 seconds</p>
|
3
|
+
<p id="waiting">Waiting...</p>
|
4
|
+
<p id="error" style="display: none;"></p>
|
5
|
+
<%= form_tag user_fido_usf_authentication_path(), method: 'post' do %>
|
6
|
+
<%= hidden_field_tag :response %>
|
7
|
+
<% end %>
|
8
|
+
<script>
|
9
|
+
var appId = <%= @app_id.to_json.html_safe %>;
|
10
|
+
var signRequests = <%= @sign_requests.to_json.html_safe %>;
|
11
|
+
var challenge = <%= @challenge.to_json.html_safe %>;
|
12
|
+
var $waiting = document.getElementById('waiting');
|
13
|
+
var $error = document.getElementById('error');
|
14
|
+
var errorMap = {
|
15
|
+
1: 'Unknown error, try again',
|
16
|
+
2: "Bad request error, try again" ,
|
17
|
+
3: "This key isn't supported, please try another one",
|
18
|
+
4: 'The device is is not registered, please register first.',
|
19
|
+
5: 'Authentication timed out. Please reload to try again.'
|
20
|
+
};
|
21
|
+
var setError = function(code) {
|
22
|
+
$waiting.style.display = 'none';
|
23
|
+
$error.style.display = 'block';
|
24
|
+
$error.innerHTML = errorMap[code];
|
25
|
+
};
|
26
|
+
|
27
|
+
u2f.sign(appId, challenge, signRequests, function(signResponse) {
|
28
|
+
var form, reg;
|
29
|
+
|
30
|
+
if (signResponse.errorCode) {
|
31
|
+
return setError(signResponse.errorCode);
|
32
|
+
}
|
33
|
+
|
34
|
+
form = document.forms[0];
|
35
|
+
response = document.querySelector('[name=response]');
|
36
|
+
|
37
|
+
response.value = JSON.stringify(signResponse);
|
38
|
+
|
39
|
+
form.submit();
|
40
|
+
}, 15);
|
41
|
+
</script>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<tr id="device_<%= device.id %>">
|
2
|
+
<td><%= device.name %></td>
|
3
|
+
<td><%= l(device.last_authenticated_at, format: :long) %></td>
|
4
|
+
<td><%= link_to 'Delete', user_fido_usf_registration_path(id: device.id), remote: true, :method => :delete, data: {confirm: "Should device #{device.name} be deleted?" } %></td>
|
5
|
+
</tr>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<div class="new-device"></div>
|
2
|
+
|
3
|
+
<table id class="table table-bordered table-striped">
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th class="col-md-4"><%= FidoUsf::FidoUsfDevice.human_attribute_name('name') %></th>
|
7
|
+
<th class="col-md-3"><%= FidoUsf::FidoUsfDevice.human_attribute_name('last_authenticated_at') %></th>
|
8
|
+
<th class="col-md-2"><%= t('common.actions') %></th>
|
9
|
+
</tr>
|
10
|
+
</thead>
|
11
|
+
<tbody id="devices">
|
12
|
+
<%= render partial: 'devise/fido_usf_registrations/device', collection: @devices %>
|
13
|
+
</tbody>
|
14
|
+
</table>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<h2>Register key</h2>
|
2
|
+
<p>Please insert the key and press the button within 15 seconds</p>
|
3
|
+
<p id="waiting">Waiting...</p>
|
4
|
+
<p id="error" style="display: none;"></p>
|
5
|
+
|
6
|
+
<%= form_tag user_fido_usf_registration_path(), method: 'post' do %>
|
7
|
+
<%= hidden_field_tag :response %>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<script>
|
11
|
+
var appId = <%= @app_id.to_json.html_safe %>;
|
12
|
+
var registerRequests = <%= @registration_requests.to_json.html_safe %>;
|
13
|
+
var signRequests = <%= @sign_requests.to_json.html_safe %>;
|
14
|
+
var $waiting = document.getElementById('waiting');
|
15
|
+
var $error = document.getElementById('error');
|
16
|
+
var errorMap = {
|
17
|
+
1: 'Unknown error, try again',
|
18
|
+
2: "Bad request error, try again" ,
|
19
|
+
3: "This key isn't supported, please try another one",
|
20
|
+
4: 'The device is already registered, please login',
|
21
|
+
5: 'Authentication timed out. Please reload to try again.'
|
22
|
+
};
|
23
|
+
var setError = function(code) {
|
24
|
+
$waiting.style.display = 'none';
|
25
|
+
$error.style.display = 'block';
|
26
|
+
$error.innerHTML = errorMap[code];
|
27
|
+
};
|
28
|
+
|
29
|
+
u2f.register(appId, registerRequests, signRequests, function(registerResponse) {
|
30
|
+
var form, reg;
|
31
|
+
|
32
|
+
if (registerResponse.errorCode) {
|
33
|
+
return setError(registerResponse.errorCode);
|
34
|
+
}
|
35
|
+
|
36
|
+
form = document.forms[0];
|
37
|
+
response = document.querySelector('[name=response]');
|
38
|
+
|
39
|
+
response.value = JSON.stringify(registerResponse);
|
40
|
+
|
41
|
+
form.submit();
|
42
|
+
}, 15);
|
43
|
+
</script>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
en:
|
2
|
+
fido_usf:
|
3
|
+
pages:
|
4
|
+
devices: "2FA Devices"
|
5
|
+
|
6
|
+
flashs:
|
7
|
+
device:
|
8
|
+
removed: "Device removed."
|
9
|
+
registered: "New 2FA device successfully registered."
|
10
|
+
|
11
|
+
common:
|
12
|
+
actions: 'Actions'
|
13
|
+
|
14
|
+
forms:
|
15
|
+
device:
|
16
|
+
add: "Add 2FA Device"
|
17
|
+
delete: "Delete"
|
18
|
+
delete_confirm: "Do you really want to delete the 2FA device \"%{device_name}\"?"
|
19
|
+
|
20
|
+
activerecord:
|
21
|
+
attributes:
|
22
|
+
fido_usf/fido_usf_device:
|
23
|
+
last_authenticated_at: "Last used"
|
24
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DeviseFidoUsf
|
2
|
+
module Controllers
|
3
|
+
autoload :Helpers, 'devise_fido_usf/controllers/helpers'
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'devise'
|
8
|
+
require 'u2f'
|
9
|
+
require 'devise_fido_usf/routes'
|
10
|
+
require 'devise_fido_usf/rails'
|
11
|
+
|
12
|
+
Devise.add_module :fido_usf_registerable, :model => 'devise_fido_usf/models/fido_usf_registerable', :controller => :fido_usf_registrations, :route => :fido_usf_registration
|
13
|
+
Devise.add_module :fido_usf_authenticatable, :model => 'devise_fido_usf/models/fido_usf_authenticatable', :controller => :fido_usf_authentications, :route => :fido_usf_authentication
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Code loosely based on authy-devise gem from https://github.com/authy/authy-devise
|
2
|
+
|
3
|
+
module DeviseFidoUsf
|
4
|
+
module Controllers
|
5
|
+
module Helpers
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
before_action :check_request_and_redirect_to_verify_fido_usf, :if => :is_user_signing_in?
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def is_devise_sessions_controller?
|
14
|
+
self.class == Devise::SessionsController || self.class.ancestors.include?(Devise::SessionsController)
|
15
|
+
end
|
16
|
+
|
17
|
+
def is_user_signing_in?
|
18
|
+
if devise_controller? && signed_in?(resource_name) &&
|
19
|
+
is_devise_sessions_controller? &&
|
20
|
+
self.action_name == "create"
|
21
|
+
return true
|
22
|
+
end
|
23
|
+
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_request_and_redirect_to_verify_fido_usf
|
28
|
+
if signed_in?(resource_name) && warden.session(resource_name)[:with_fido_usf_authentication]
|
29
|
+
# login with 2fa
|
30
|
+
id = warden.session(resource_name)[:id]
|
31
|
+
|
32
|
+
return_to = session["#{resource_name}_return_to"]
|
33
|
+
remember_me = Devise::TRUE_VALUES.include?(sign_in_params[:remember_me])
|
34
|
+
sign_out
|
35
|
+
|
36
|
+
# It is secure to put these information in a Rails 5 session
|
37
|
+
# because cookies are signed and encrypted.
|
38
|
+
session["#{resource_name}_id"] = id
|
39
|
+
session["#{resource_name}_remember_me"] = remember_me
|
40
|
+
session["#{resource_name}_password_checked"] = true
|
41
|
+
session["#{resource_name}_return_to"] = return_to if return_to
|
42
|
+
|
43
|
+
redirect_to verify_fido_usf_path_for(resource_name)
|
44
|
+
return
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def verify_fido_usf_path_for(resource_or_scope = nil)
|
49
|
+
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
50
|
+
send(:"new_#{scope}_fido_usf_authentication_path")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Warden::Manager.after_authentication do |user, auth, options|
|
2
|
+
if user.respond_to?(:with_fido_usf_authentication?)
|
3
|
+
with_fido_usf_authentication = user.with_fido_usf_authentication?()
|
4
|
+
auth.session(options[:scope])[:with_fido_usf_authentication] = with_fido_usf_authentication
|
5
|
+
if with_fido_usf_authentication
|
6
|
+
auth.session(options[:scope])[:id] = user.id
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'devise_fido_usf/hooks/fido_usf_authenticatable'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
module FidoUsfAuthenticatable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Does the user has a registered FIDO U2F device?
|
9
|
+
def with_fido_usf_authentication?()
|
10
|
+
FidoUsf::FidoUsfDevice.where(user: self).count()>0
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActionDispatch::Routing
|
2
|
+
class Mapper
|
3
|
+
def devise_fido_usf_registration(mapping, controllers)
|
4
|
+
resource :fido_usf_registration, :only => [:show, :new, :create, :destroy],
|
5
|
+
:path => mapping.path_names[:fido_usf_registration], :controller => controllers[:fido_usf_registrations] do
|
6
|
+
end
|
7
|
+
end
|
8
|
+
def devise_fido_usf_authentication(mapping, controllers)
|
9
|
+
resource :fido_usf_authentication, :only => [:new, :create],
|
10
|
+
:path => mapping.path_names[:fido_usf_authentication], :controller => controllers[:fido_usf_authentications] do
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
module DeviseFidoUsf
|
4
|
+
module Generators
|
5
|
+
MissingORMError = Class.new(Thor::Error)
|
6
|
+
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
source_root File.expand_path("../../templates", __FILE__)
|
9
|
+
|
10
|
+
desc "Creates a FIDO U2F initializer for devise and copy locale files to your application."
|
11
|
+
class_option :orm
|
12
|
+
|
13
|
+
def copy_initializer
|
14
|
+
unless options[:orm]
|
15
|
+
raise MissingORMError, <<-ERROR.strip_heredoc
|
16
|
+
An ORM must be set to install Devise in your application.
|
17
|
+
|
18
|
+
Be sure to have an ORM like Active Record or Mongoid loaded in your
|
19
|
+
app or configure your own at `config/application.rb`.
|
20
|
+
|
21
|
+
config.generators do |g|
|
22
|
+
g.orm :your_orm_gem
|
23
|
+
end
|
24
|
+
ERROR
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_application_helper
|
30
|
+
in_root do
|
31
|
+
inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, application_helper_data
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def copy_locale
|
36
|
+
copy_file "../../../config/locales/en.yml", "config/locales/fido_usf.en.yml"
|
37
|
+
end
|
38
|
+
|
39
|
+
def run_migration
|
40
|
+
invoke("devise_fido_usf:migrate", ["FidoUsfDevices"])
|
41
|
+
end
|
42
|
+
|
43
|
+
def show_readme
|
44
|
+
readme("README") if behavior == :invoke
|
45
|
+
end
|
46
|
+
|
47
|
+
def application_helper_data
|
48
|
+
<<RUBY
|
49
|
+
|
50
|
+
def u2f
|
51
|
+
# use base_url as app_id, e.g. 'http://localhost:3000'
|
52
|
+
@u2f ||= U2F::U2F.new(request.base_url)
|
53
|
+
end
|
54
|
+
RUBY
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|