devise-webauthn 0.0.0 → 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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +75 -0
  3. data/.gitignore +14 -0
  4. data/.rspec +0 -1
  5. data/.rubocop.yml +38 -0
  6. data/.ruby-version +1 -0
  7. data/Appraisals +25 -0
  8. data/CHANGELOG.md +7 -0
  9. data/Gemfile +19 -1
  10. data/Gemfile.lock +363 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +121 -7
  13. data/Rakefile +3 -1
  14. data/app/controllers/devise/passkeys_controller.rb +57 -0
  15. data/app/views/devise/passkeys/new.html.erb +5 -0
  16. data/app/views/devise/sessions/new.html.erb +28 -0
  17. data/bin/console +1 -0
  18. data/bin/rails +15 -0
  19. data/config/locales/en.yml +8 -0
  20. data/config.ru +10 -0
  21. data/devise-webauthn.gemspec +10 -10
  22. data/gemfiles/rails_7_1.gemfile +23 -0
  23. data/gemfiles/rails_7_2.gemfile +21 -0
  24. data/gemfiles/rails_8_0.gemfile +21 -0
  25. data/gemfiles/rails_edge.gemfile +21 -0
  26. data/lib/devise/models/passkey_authenticatable.rb +22 -0
  27. data/lib/devise/strategies/passkey_authenticatable.rb +45 -0
  28. data/lib/devise/webauthn/engine.rb +30 -0
  29. data/lib/devise/webauthn/helpers/credentials_helper.rb +77 -0
  30. data/lib/devise/webauthn/routes.rb +13 -0
  31. data/lib/devise/webauthn/test/authenticator_helpers.rb +17 -0
  32. data/lib/devise/webauthn/url_helpers.rb +44 -0
  33. data/lib/devise/webauthn/version.rb +3 -1
  34. data/lib/devise/webauthn.rb +13 -3
  35. data/lib/generators/devise/webauthn/controllers_generator.rb +29 -0
  36. data/lib/generators/devise/webauthn/install/install_generator.rb +39 -0
  37. data/lib/generators/devise/webauthn/install/templates/webauthn.rb +37 -0
  38. data/lib/generators/devise/webauthn/stimulus/stimulus_generator.rb +24 -0
  39. data/lib/generators/devise/webauthn/stimulus/templates/webauthn_credentials_controller.js +31 -0
  40. data/lib/generators/devise/webauthn/templates/controllers/README +14 -0
  41. data/lib/generators/devise/webauthn/templates/controllers/passkeys_controller.rb.tt +31 -0
  42. data/lib/generators/devise/webauthn/views_generator.rb +44 -0
  43. data/lib/generators/devise/webauthn/webauthn_credential_model/webauthn_credential_model_generator.rb +54 -0
  44. data/lib/generators/devise/webauthn/webauthn_id/webauthn_id_generator.rb +41 -0
  45. metadata +47 -34
  46. data/.travis.yml +0 -7
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Webauthn
5
+ # Create url helpers to be used with resource/scope configuration. Acts as
6
+ # proxies to the generated routes created by devise.
7
+ # Resource param can be a string or symbol, a class, or an instance object.
8
+ # Example using a :user resource:
9
+ #
10
+ # new_passkey_path(:user) => new_user_passkey_path
11
+ # passkeys_path(:user) => user_passkeys_path
12
+ # passkey_path(:user) => user_passkey_path
13
+ #
14
+ # Those helpers are included by default to ActionController::Base.
15
+ #
16
+ # In case you want to add such helpers to another class, you can do
17
+ # that as long as this new class includes both url_helpers and
18
+ # mounted_helpers. Example:
19
+ #
20
+ # include Rails.application.routes.url_helpers
21
+ # include Rails.application.routes.mounted_helpers
22
+ #
23
+ module UrlHelpers
24
+ {
25
+ passkeys: [nil],
26
+ passkey: [nil, :new]
27
+ }.each do |route, actions|
28
+ %i[path url].each do |path_or_url|
29
+ actions.each do |action|
30
+ action = action ? "#{action}_" : ""
31
+ method = :"#{action}#{route}_#{path_or_url}"
32
+
33
+ define_method method do |resource_or_scope, *args|
34
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
35
+ router_name = Devise.mappings[scope].router_name
36
+ context = router_name ? send(router_name) : _devise_route_context
37
+ context.send("#{action}#{scope}_#{route}_#{path_or_url}", *args)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Webauthn
3
- VERSION = "0.0.0"
5
+ VERSION = "0.1.0"
4
6
  end
5
7
  end
@@ -1,8 +1,18 @@
1
- require "devise/webauthn/version"
1
+ # frozen_string_literal: true
2
+
3
+ require "devise"
4
+ require "webauthn"
5
+
6
+ require_relative "webauthn/version"
7
+ require_relative "webauthn/engine"
8
+ require_relative "webauthn/helpers/credentials_helper"
9
+ require_relative "webauthn/routes"
10
+ require_relative "webauthn/url_helpers"
2
11
 
3
12
  module Devise
4
13
  module Webauthn
5
- class Error < StandardError; end
6
- # Your code goes here...
14
+ module Test
15
+ autoload :AuthenticatorHelpers, "devise/webauthn/test/authenticator_helpers"
16
+ end
7
17
  end
8
18
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Devise
6
+ module Webauthn
7
+ class ControllersGenerator < Rails::Generators::Base
8
+ CONTROLLERS = %w[passkeys].freeze
9
+
10
+ desc "Create inherited Devise::Webauthn controllers in your app/controllers folder."
11
+
12
+ source_root File.expand_path("templates/controllers", __dir__)
13
+ argument :scope, required: true,
14
+ desc: "The scope to create controllers in, e.g. users, admins"
15
+
16
+ def create_controllers
17
+ @scope_prefix = scope.blank? ? "" : "#{scope.camelize}::"
18
+ CONTROLLERS.each do |name|
19
+ template "#{name}_controller.rb",
20
+ "app/controllers/#{scope}/#{name}_controller.rb"
21
+ end
22
+ end
23
+
24
+ def show_readme
25
+ readme "README" if behavior == :invoke
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Devise
6
+ module Webauthn
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Install Devise::Webauthn configuration into your application"
11
+
12
+ class_option :resource_name, type: :string, default: "user", desc: "The resource name for Devise (default: user)"
13
+
14
+ def install
15
+ say "Installing DeviseWebauthn configuration...", :green
16
+
17
+ template "webauthn.rb", "config/initializers/webauthn.rb"
18
+ say "Created initializer: config/initializers/webauthn.rb", :green
19
+ end
20
+
21
+ def generate_webauthn_credential_model
22
+ invoke "devise:webauthn:webauthn_credential_model", [], resource_name: options[:resource_name]
23
+ end
24
+
25
+ def generate_webauthn_id_column
26
+ invoke "devise:webauthn:webauthn_id", [], resource_name: options[:resource_name]
27
+ end
28
+
29
+ def generate_stimulus_controller
30
+ invoke "devise:webauthn:stimulus"
31
+ end
32
+
33
+ def final_message
34
+ say "\nAlmost done! Now edit `config/initializers/webauthn.rb` and set the `allowed_origins` for your app.",
35
+ :yellow
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ WebAuthn.configure do |config|
4
+ # This value needs to match `window.location.origin` evaluated by
5
+ # the User Agent during registration and authentication ceremonies.
6
+ # Multiple origins can be used when needed. Using more than one will imply you MUST configure rp_id explicitely.
7
+ # please see [webauthn-ruby Advanced Configuration section](https://github.com/cedarcode/webauthn-ruby/blob/master/docs/advanced_configuration.md)
8
+ # instead of adding multiple origins.
9
+ # config.allowed_origins = [ "https://auth.example.com" ]
10
+
11
+ # Relying Party name for display purposes
12
+ # config.rp_name = "Example Inc."
13
+
14
+ # Optionally configure a client timeout hint, in milliseconds.
15
+ # This hint specifies how long the browser should wait for any
16
+ # interaction with the user.
17
+ # This hint may be overridden by the browser.
18
+ # https://www.w3.org/TR/webauthn/#dom-publickeycredentialcreationoptions-timeout
19
+ # config.credential_options_timeout = 120_000
20
+
21
+ # You can optionally specify a different Relying Party ID
22
+ # (https://www.w3.org/TR/webauthn/#relying-party-identifier)
23
+ # if it differs from the default one.
24
+ #
25
+ # config.rp_id = "localhost"
26
+
27
+ # Configure preferred binary-to-text encoding scheme. This should match the encoding scheme
28
+ # used in your client-side (user agent) code before sending the credential to the server.
29
+ # Supported values: `:base64url` (default), `:base64` or `false` to disable all encoding.
30
+ #
31
+ # config.encoding = :base64url
32
+
33
+ # Possible values: "ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512", "RS1"
34
+ # Default: ["ES256", "PS256", "RS256"]
35
+ #
36
+ # config.algorithms << "ES384"
37
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Devise
6
+ module Webauthn
7
+ class StimulusGenerator < Rails::Generators::Base
8
+ hide!
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Copy DeviseWebauthn Stimulus controller to your application"
12
+
13
+ def copy_stimulus_controller
14
+ copy_file "webauthn_credentials_controller.js",
15
+ "app/javascript/controllers/webauthn_credentials_controller.js"
16
+ end
17
+
18
+ def show_instructions
19
+ say "✓ Stimulus controller setup complete!", :green
20
+ say "The webauthn_credentials controller has been installed and will be automatically registered by Stimulus."
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["credentialHiddenInput"]
5
+
6
+ async create({ params: { options } }) {
7
+ try {
8
+ const credentialOptions = PublicKeyCredential.parseCreationOptionsFromJSON(options);
9
+ const credential = await navigator.credentials.create({ publicKey: credentialOptions });
10
+
11
+ this.credentialHiddenInputTarget.value = JSON.stringify(credential);
12
+
13
+ this.element.submit();
14
+ } catch (error) {
15
+ alert(error.message || error);
16
+ }
17
+ }
18
+
19
+ async get({ params: { options } }) {
20
+ try {
21
+ const credentialOptions = PublicKeyCredential.parseRequestOptionsFromJSON(options);
22
+ const credential = await navigator.credentials.get({ publicKey: credentialOptions });
23
+
24
+ this.credentialHiddenInputTarget.value = JSON.stringify(credential);
25
+
26
+ this.element.submit();
27
+ } catch (error) {
28
+ alert(error.message || error);
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,14 @@
1
+ ===============================================================================
2
+
3
+ Some setup you must do manually if you haven't yet:
4
+
5
+ Ensure you have overridden routes for generated controllers in your routes.rb.
6
+ For example:
7
+
8
+ Rails.application.routes.draw do
9
+ devise_for :users, controllers: {
10
+ passkeys: 'users/passkeys'
11
+ }
12
+ end
13
+
14
+ ===============================================================================
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= @scope_prefix %>PasskeysController < Devise::PasskeysController
4
+ # GET /resource/passkeys/new
5
+ # def new
6
+ # super
7
+ # end
8
+
9
+ # POST /resource/passkeys
10
+ # def create
11
+ # super
12
+ # end
13
+
14
+ # DELETE /resource/passkeys/:id
15
+ # def destroy
16
+ # super
17
+ # end
18
+
19
+ # private
20
+
21
+ # Verifies the passkey coming in the params and saves it to the database.
22
+ # def verify_and_save_passkey(passkey_from_params)
23
+ # super
24
+ # end
25
+
26
+ # The default url to be used after creating a passkey. You can overwrite
27
+ # this method in your own PasskeysController.
28
+ # def after_update_path
29
+ # super
30
+ # end
31
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Devise
6
+ module Webauthn
7
+ class ViewsGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("../../../../app/views/devise", __dir__)
9
+
10
+ desc "Copies Devise::Webauthn views to your application."
11
+
12
+ argument :scope, required: false, default: nil,
13
+ desc: "The scope to copy views to"
14
+
15
+ class_option :views, aliases: "-v", type: :array,
16
+ desc: "Select specific view directories to generate (sessions, passkeys)"
17
+
18
+ def copy_views
19
+ if options[:views]
20
+ options[:views].each do |directory|
21
+ view_directory directory.to_sym
22
+ end
23
+ else
24
+ view_directory :passkeys
25
+ view_directory :sessions
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def view_directory(name)
32
+ directory name.to_s, "#{target_path}/#{name}"
33
+ end
34
+
35
+ def target_path
36
+ "app/views/#{plural_scope || :devise}"
37
+ end
38
+
39
+ def plural_scope
40
+ scope.presence && scope.underscore.pluralize
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Devise
7
+ module Webauthn
8
+ class WebauthnCredentialModelGenerator < Rails::Generators::Base
9
+ hide!
10
+ namespace "devise:webauthn:webauthn_credential_model"
11
+
12
+ desc "Generate a WebauthnCredential model with the required fields for WebAuthn"
13
+ class_option :resource_name, type: :string, default: "user", desc: "The resource name for Devise (default: user)"
14
+
15
+ def generate_model
16
+ invoke "active_record:model", [
17
+ "webauthn_credential",
18
+ "external_id:string:uniq",
19
+ "name:string",
20
+ "public_key:text",
21
+ "sign_count:integer{8}",
22
+ "#{user_model_name}:references"
23
+ ]
24
+ end
25
+
26
+ def inject_webauthn_credential_content
27
+ inject_into_file("app/models/webauthn_credential.rb", before: /^end\s*$/) do
28
+ <<~RUBY.indent(2)
29
+ validates :external_id, :public_key, :name, :sign_count, presence: true
30
+ validates :external_id, uniqueness: true
31
+ RUBY
32
+ end
33
+ end
34
+
35
+ def show_instructions
36
+ say <<~MSG
37
+ WebauthnCredential model has been generated! Next steps:
38
+
39
+ 1. Run the migration:
40
+ rails db:migrate
41
+
42
+ 2. Make sure your User model includes :passkey_authenticatable in the devise line:
43
+ devise :database_authenticatable, :passkey_authenticatable, ...
44
+ MSG
45
+ end
46
+
47
+ private
48
+
49
+ def user_model_name
50
+ options[:resource_name].underscore
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Devise
7
+ module Webauthn
8
+ class WebauthnIdGenerator < Rails::Generators::Base
9
+ hide!
10
+ namespace "devise:webauthn:webauthn_id"
11
+
12
+ desc "Add webauthn_id field to User model"
13
+ class_option :resource_name, type: :string, default: "user", desc: "The resource name for Devise (default: user)"
14
+
15
+ def generate_migration
16
+ invoke "active_record:migration", [
17
+ "add_webauthn_id_to_#{user_table_name}",
18
+ "webauthn_id:string:uniq"
19
+ ]
20
+ end
21
+
22
+ def show_instructions
23
+ say <<~MSG
24
+ WebAuthn ID field has been added! Next steps:
25
+
26
+ 1. Run the migration:
27
+ rails db:migrate
28
+
29
+ 2. Make sure your User model includes :passkey_authenticatable in the devise line:
30
+ devise :database_authenticatable, :passkey_authenticatable, ...
31
+ MSG
32
+ end
33
+
34
+ private
35
+
36
+ def user_table_name
37
+ options[:resource_name].pluralize.underscore
38
+ end
39
+ end
40
+ end
41
+ end
metadata CHANGED
@@ -1,81 +1,96 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-webauthn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - Braulio Martinez
8
- autorequire:
7
+ - Cedarcode
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2019-04-02 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: bundler
13
+ name: devise
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '1.17'
20
- type: :development
18
+ version: '4.9'
19
+ type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '1.17'
25
+ version: '4.9'
27
26
  - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '10.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '10.0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
27
+ name: webauthn
43
28
  requirement: !ruby/object:Gem::Requirement
44
29
  requirements:
45
30
  - - "~>"
46
31
  - !ruby/object:Gem::Version
47
32
  version: '3.0'
48
- type: :development
33
+ type: :runtime
49
34
  prerelease: false
50
35
  version_requirements: !ruby/object:Gem::Requirement
51
36
  requirements:
52
37
  - - "~>"
53
38
  - !ruby/object:Gem::Version
54
39
  version: '3.0'
55
- description:
56
40
  email:
57
- - braulio@cedarcode.com
41
+ - webauthn@cedarcode.com
58
42
  executables: []
59
43
  extensions: []
60
44
  extra_rdoc_files: []
61
45
  files:
46
+ - ".github/workflows/ruby.yml"
62
47
  - ".gitignore"
63
48
  - ".rspec"
64
- - ".travis.yml"
49
+ - ".rubocop.yml"
50
+ - ".ruby-version"
51
+ - Appraisals
52
+ - CHANGELOG.md
65
53
  - Gemfile
54
+ - Gemfile.lock
66
55
  - LICENSE.txt
67
56
  - README.md
68
57
  - Rakefile
58
+ - app/controllers/devise/passkeys_controller.rb
59
+ - app/views/devise/passkeys/new.html.erb
60
+ - app/views/devise/sessions/new.html.erb
69
61
  - bin/console
62
+ - bin/rails
70
63
  - bin/setup
64
+ - config.ru
65
+ - config/locales/en.yml
71
66
  - devise-webauthn.gemspec
67
+ - gemfiles/rails_7_1.gemfile
68
+ - gemfiles/rails_7_2.gemfile
69
+ - gemfiles/rails_8_0.gemfile
70
+ - gemfiles/rails_edge.gemfile
71
+ - lib/devise/models/passkey_authenticatable.rb
72
+ - lib/devise/strategies/passkey_authenticatable.rb
72
73
  - lib/devise/webauthn.rb
74
+ - lib/devise/webauthn/engine.rb
75
+ - lib/devise/webauthn/helpers/credentials_helper.rb
76
+ - lib/devise/webauthn/routes.rb
77
+ - lib/devise/webauthn/test/authenticator_helpers.rb
78
+ - lib/devise/webauthn/url_helpers.rb
73
79
  - lib/devise/webauthn/version.rb
74
- homepage:
80
+ - lib/generators/devise/webauthn/controllers_generator.rb
81
+ - lib/generators/devise/webauthn/install/install_generator.rb
82
+ - lib/generators/devise/webauthn/install/templates/webauthn.rb
83
+ - lib/generators/devise/webauthn/stimulus/stimulus_generator.rb
84
+ - lib/generators/devise/webauthn/stimulus/templates/webauthn_credentials_controller.js
85
+ - lib/generators/devise/webauthn/templates/controllers/README
86
+ - lib/generators/devise/webauthn/templates/controllers/passkeys_controller.rb.tt
87
+ - lib/generators/devise/webauthn/views_generator.rb
88
+ - lib/generators/devise/webauthn/webauthn_credential_model/webauthn_credential_model_generator.rb
89
+ - lib/generators/devise/webauthn/webauthn_id/webauthn_id_generator.rb
75
90
  licenses:
76
91
  - MIT
77
- metadata: {}
78
- post_install_message:
92
+ metadata:
93
+ rubygems_mfa_required: 'true'
79
94
  rdoc_options: []
80
95
  require_paths:
81
96
  - lib
@@ -83,16 +98,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
83
98
  requirements:
84
99
  - - ">="
85
100
  - !ruby/object:Gem::Version
86
- version: '2.3'
101
+ version: '2.7'
87
102
  required_rubygems_version: !ruby/object:Gem::Requirement
88
103
  requirements:
89
104
  - - ">="
90
105
  - !ruby/object:Gem::Version
91
106
  version: '0'
92
107
  requirements: []
93
- rubyforge_project:
94
- rubygems_version: 2.7.6
95
- signing_key:
108
+ rubygems_version: 3.6.7
96
109
  specification_version: 4
97
110
  summary: Devise extension to support WebAuthn.
98
111
  test_files: []
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.5.3
7
- before_install: gem install bundler -v 1.17.1