passkeys-rails 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +17 -2
  4. data/app/controllers/concerns/passkeys/rails/authentication.rb +29 -0
  5. data/app/controllers/passkeys/rails/application_controller.rb +24 -0
  6. data/app/controllers/passkeys/rails/passkeys_controller.rb +63 -0
  7. data/app/interactors/{passkeys_rails → passkeys/rails}/begin_authentication.rb +1 -1
  8. data/app/interactors/{passkeys_rails → passkeys/rails}/begin_challenge.rb +1 -1
  9. data/app/interactors/{passkeys_rails → passkeys/rails}/begin_registration.rb +1 -1
  10. data/app/interactors/{passkeys_rails → passkeys/rails}/finish_authentication.rb +1 -1
  11. data/app/interactors/{passkeys_rails → passkeys/rails}/finish_registration.rb +1 -1
  12. data/app/interactors/{passkeys_rails → passkeys/rails}/generate_auth_token.rb +4 -4
  13. data/app/interactors/{passkeys_rails → passkeys/rails}/refresh_token.rb +1 -1
  14. data/app/interactors/{passkeys_rails → passkeys/rails}/validate_auth_token.rb +3 -3
  15. data/app/models/concerns/{passkeys_rails → passkeys/rails}/authenticatable.rb +1 -1
  16. data/app/models/{passkeys_rails → passkeys/rails}/agent.rb +1 -1
  17. data/app/models/{passkeys_rails → passkeys/rails}/application_record.rb +1 -1
  18. data/app/models/{passkeys_rails → passkeys/rails}/error.rb +1 -1
  19. data/app/models/{passkeys_rails → passkeys/rails}/passkey.rb +1 -1
  20. data/config/initializers/application_controller.rb +12 -0
  21. data/config/routes.rb +1 -1
  22. data/lib/generators/passkeys_rails/USAGE +1 -1
  23. data/lib/generators/passkeys_rails/install_generator.rb +14 -12
  24. data/lib/generators/passkeys_rails/templates/README +2 -2
  25. data/lib/generators/passkeys_rails/templates/passkeys_rails_config.rb +2 -2
  26. data/lib/passkeys/rails/engine.rb +21 -0
  27. data/lib/passkeys/rails/railtie.rb +19 -0
  28. data/lib/passkeys/rails/version.rb +5 -0
  29. data/lib/passkeys-rails.rb +36 -0
  30. metadata +26 -26
  31. data/app/controllers/passkeys_rails/application_controller.rb +0 -22
  32. data/app/controllers/passkeys_rails/passkeys_controller.rb +0 -61
  33. data/app/helpers/passkeys_rails/application_helper.rb +0 -21
  34. data/app/helpers/passkeys_rails/passkeys_helper.rb +0 -4
  35. data/lib/passkeys_rails/engine.rb +0 -24
  36. data/lib/passkeys_rails/error_middleware.rb +0 -17
  37. data/lib/passkeys_rails/railtie.rb +0 -17
  38. data/lib/passkeys_rails/version.rb +0 -3
  39. data/lib/passkeys_rails.rb +0 -38
  40. /data/app/views/layouts/{passkeys_rails → passkeys/rails}/application.html.erb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e23ace0ad232fd6a7a54c8f69ff05178b15cbd518bd06228430cd9fde0fdb496
4
- data.tar.gz: 5f7f5e08e273e0192e036b0015f3d3d27fa42181af2c5394a267b937fc29a197
3
+ metadata.gz: 84469b812d394ac75533be6f935292932f43fceab669d0842be1daa077abe7ee
4
+ data.tar.gz: 8430b3679760bc10af93da66835b549646e50bca94bb25fc008ec067a37e32b0
5
5
  SHA512:
6
- metadata.gz: a669fc055ae57baa578661cda651ea6938e7d77d6d294d8edbb4fc4c9662bfcbb44d385901bb3e891251b5b137d83f1d6ea304d16eb73b2c238f7ef306cf313e
7
- data.tar.gz: b1b6334a3a2e586458394aae70d3da78c2b00126b1a52dfebc709311e22673207b47ce44a519168ca8252e18337b02cfd9b0f338ac748613c7fceb62d5675a44
6
+ metadata.gz: 654a20573b11dae74154bc3b8576f7204d2694218732ec88fdb80004286d20dc8ef0426fbf2f186efc33645ff9ce8604e32cdf59cae9b77712af271255807103
7
+ data.tar.gz: cb5380598b67e0969ec619570729cea1e534a8e100942adbb9d4389ce9b1eba0937e35d359dd3d8eb2646965e5343519c5404eccefbe606fed1f5a25d9ca6b08
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ ### 0.1.3
2
+
3
+ * More restructuring and fixed issue where autoloading failed
4
+ during client app initialization.
5
+
6
+ ### 0.1.2
7
+
8
+ * Restructured lib directory.
9
+
10
+ * Fixed naming convention for gem/gemspec.
11
+
12
+ * Fixed exception handling.
13
+
14
+ ### 0.1.1
15
+
16
+ * Fixed dependency
17
+
18
+ ### 0.1.0
19
+
20
+ * Initial release - looking for feedback
data/README.md CHANGED
@@ -1,12 +1,13 @@
1
+ [![Gem Version](https://badge.fury.io/rb/passkeys-rails.svg?cachebust=1)](https://badge.fury.io/rb/passkeys-rails)
1
2
  [![Build Status](https://app.travis-ci.com/alliedcode/passkeys-rails.svg?branch=main)](https://travis-ci.org/alliedcode/passkeys-rails)
2
3
  [![codecov](https://codecov.io/gh/alliedcode/passkeys-rails/branch/main/graph/badge.svg?token=UHSNJDUL21)](https://codecov.io/gh/alliedcode/passkeys-rails)
3
4
 
4
- # PasskeysRails
5
+ # Passkeys::Rails
5
6
  Devise is awesome, but we don't need all that UI/UX for PassKeys. This gem is to make it easy to provide a back end that authenticates a mobile front end with PassKeys.
6
7
 
7
8
  ## Usage
8
9
  rails passkeys-rails::install
9
- PasskeysRails maintains an Agent model and related Passeys. If you have a user model, add `include PasskeysRails::Authenticatable` to your model and include the name of that class (e.g. "User") in the authenticatable_class param when calling the register API.
10
+ Passkeys::Rails maintains an Agent model and related Passeys. If you have a user model, add `include Passkeys::Rails::Authenticatable` to your model and include the name of that class (e.g. "User") in the authenticatable_class param when calling the register API.
10
11
 
11
12
  ## Installation
12
13
  Add this line to your application's Gemfile:
@@ -25,6 +26,20 @@ Or install it yourself as:
25
26
  $ gem install passkeys_rails
26
27
  ```
27
28
 
29
+ Depending on your application's configuration some manual setup may be required:
30
+
31
+ 1. Add a before_action to all controllers that require authentication to use.
32
+
33
+ For example:
34
+
35
+ before_action :authenticate_passkey!, except: [:index]
36
+
37
+ 2. Optionally include Passkeys::Rails::Authenticatable to the model(s) you are using as
38
+ your user model(s). For example, the User model.
39
+
40
+ 3. See the reference mobile applications for how to use passkeys-rails for passkey
41
+ authentication.
42
+
28
43
  ## Contributing
29
44
  Contribution directions go here.
30
45
 
@@ -0,0 +1,29 @@
1
+ module Passkeys::Rails
2
+ module Authentication
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ rescue_from Passkeys::Rails::Error do |e|
7
+ render json: e.to_h, status: :unauthorized
8
+ end
9
+ end
10
+
11
+ def current_agent
12
+ return nil if request.headers['HTTP_X_AUTH'].blank?
13
+
14
+ @current_agent ||= validated_auth_token&.success? && validated_auth_token&.agent
15
+ end
16
+
17
+ def authenticate_passkey!
18
+ return if validated_auth_token.success?
19
+
20
+ raise Passkeys::Rails::Error.new(:authentication,
21
+ code: :unauthorized,
22
+ message: "You are not authorized to access this resource.")
23
+ end
24
+
25
+ def validated_auth_token
26
+ @validated_auth_token ||= Passkeys::Rails::ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module Passkeys
2
+ module Rails
3
+ class ApplicationController < ActionController::Base
4
+ rescue_from ::Interactor::Failure, with: :handle_interactor_failure
5
+ rescue_from ActionController::ParameterMissing, with: :handle_missing_parameter
6
+
7
+ protected
8
+
9
+ def handle_missing_parameter(error)
10
+ render_error(:authentication, 'missing_parameter', error.message)
11
+ end
12
+
13
+ def handle_interactor_failure(failure)
14
+ render_error(:authentication, failure.context.code, failure.context.message)
15
+ end
16
+
17
+ private
18
+
19
+ def render_error(context, code, message, status: :unprocessable_entity)
20
+ render json: { error: { context:, code:, message: } }, status:
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ module Passkeys
2
+ module Rails
3
+ class PasskeysController < ApplicationController
4
+ def challenge
5
+ result = Passkeys::Rails::BeginChallenge.call!(username: challenge_params[:username])
6
+
7
+ # Store the challenge so we can verify the future register or authentication request
8
+ session[:passkeys_rails] = result.session_data
9
+
10
+ render json: result.response.as_json
11
+ end
12
+
13
+ def register
14
+ result = Passkeys::Rails::FinishRegistration.call!(credential: attestation_credential_params.to_h,
15
+ authenticatable_class:,
16
+ username: session.dig(:passkeys_rails, :username),
17
+ challenge: session.dig(:passkeys_rails, :challenge))
18
+
19
+ render json: { username: result.username, auth_token: result.auth_token }
20
+ end
21
+
22
+ def authenticate
23
+ result = Passkeys::Rails::FinishAuthentication.call!(credential: authentication_params.to_h,
24
+ challenge: session.dig(:passkeys_rails, :challenge))
25
+
26
+ render json: { username: result.username, auth_token: result.auth_token }
27
+ end
28
+
29
+ def refresh
30
+ result = Passkeys::Rails::RefreshToken.call!(token: refresh_params[:auth_token])
31
+ render json: { username: result.username, auth_token: result.auth_token }
32
+ end
33
+
34
+ protected
35
+
36
+ def challenge_params
37
+ params.permit(:username)
38
+ end
39
+
40
+ def attestation_credential_params
41
+ credential = params.require(:credential)
42
+ credential.require(%i[id rawId type response])
43
+ credential.require(:response).require(%i[attestationObject clientDataJSON])
44
+ credential.permit(:id, :rawId, :type, { response: %i[attestationObject clientDataJSON] })
45
+ end
46
+
47
+ def authenticatable_class
48
+ params[:authenticatable_class]
49
+ end
50
+
51
+ def authentication_params
52
+ params.require(%i[id rawId type response])
53
+ params.require(:response).require(%i[authenticatorData clientDataJSON signature userHandle])
54
+ params.permit(:id, :rawId, :type, { response: %i[authenticatorData clientDataJSON signature userHandle] })
55
+ end
56
+
57
+ def refresh_params
58
+ params.require(:auth_token)
59
+ params.permit(:auth_token)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class BeginAuthentication
3
3
  include Interactor
4
4
 
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class BeginChallenge
3
3
  include Interactor
4
4
 
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class BeginRegistration
3
3
  include Interactor
4
4
 
@@ -1,5 +1,5 @@
1
1
  # Finish authentication ceremony
2
- module PasskeysRails
2
+ module Passkeys::Rails
3
3
  class FinishAuthentication
4
4
  include Interactor
5
5
 
@@ -1,5 +1,5 @@
1
1
  # Finish registration ceremony
2
- module PasskeysRails
2
+ module Passkeys::Rails
3
3
  class FinishRegistration
4
4
  include Interactor
5
5
 
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class GenerateAuthToken
3
3
  include Interactor
4
4
 
@@ -12,12 +12,12 @@ module PasskeysRails
12
12
 
13
13
  def generate_auth_token
14
14
  JWT.encode(jwt_payload,
15
- PasskeysRails.auth_token_secret,
16
- PasskeysRails.auth_token_algorithm)
15
+ Passkeys::Rails.auth_token_secret,
16
+ Passkeys::Rails.auth_token_algorithm)
17
17
  end
18
18
 
19
19
  def jwt_payload
20
- expiration = (Time.current + PasskeysRails.auth_token_expires_in).to_i
20
+ expiration = (Time.current + Passkeys::Rails.auth_token_expires_in).to_i
21
21
 
22
22
  payload = { agent_id: agent.id }
23
23
  payload[:exp] = expiration unless expiration.zero?
@@ -1,5 +1,5 @@
1
1
  # Finish authentication ceremony
2
- module PasskeysRails
2
+ module Passkeys::Rails
3
3
  class RefreshToken
4
4
  include Interactor
5
5
 
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class ValidateAuthToken
3
3
  include Interactor
4
4
 
@@ -21,9 +21,9 @@ module PasskeysRails
21
21
 
22
22
  def payload
23
23
  JWT.decode(auth_token,
24
- PasskeysRails.auth_token_secret,
24
+ Passkeys::Rails.auth_token_secret,
25
25
  true,
26
- { required_claims: %w[exp agent_id], algorithm: PasskeysRails.auth_token_algorithm }).first
26
+ { required_claims: %w[exp agent_id], algorithm: Passkeys::Rails.auth_token_algorithm }).first
27
27
  rescue JWT::ExpiredSignature
28
28
  context.fail!(code: :expired_token, message: "The token has expired")
29
29
  rescue StandardError => e
@@ -1,6 +1,6 @@
1
1
  require 'active_support/concern'
2
2
 
3
- module PasskeysRails
3
+ module Passkeys::Rails
4
4
  module Authenticatable
5
5
  extend ActiveSupport::Concern
6
6
 
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class Agent < ApplicationRecord
3
3
  belongs_to :authenticatable, polymorphic: true, optional: true
4
4
  has_many :passkeys
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class ApplicationRecord < ActiveRecord::Base
3
3
  self.abstract_class = true
4
4
  end
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class Error < StandardError
3
3
  attr_reader :hash
4
4
 
@@ -1,4 +1,4 @@
1
- module PasskeysRails
1
+ module Passkeys::Rails
2
2
  class Passkey < ApplicationRecord
3
3
  belongs_to :agent
4
4
  validates :identifier, presence: true, uniqueness: true
@@ -0,0 +1,12 @@
1
+ # These should be autoloaded, but if these aren't required here, apps using this
2
+ # gem will throw an exception that Passkeys::Rails::Authentication can't be found
3
+ require_relative '../../app/controllers/concerns/passkeys/rails/authentication'
4
+ require_relative '../../app/models/passkeys/rails/error'
5
+
6
+ class ActionController::Base
7
+ include Passkeys::Rails::Authentication
8
+ end
9
+
10
+ class ActionController::API
11
+ include Passkeys::Rails::Authentication
12
+ end
data/config/routes.rb CHANGED
@@ -1,4 +1,4 @@
1
- PasskeysRails::Engine.routes.draw do
1
+ Passkeys::Rails::Engine.routes.draw do
2
2
  post 'passkeys/challenge'
3
3
  post 'passkeys/register'
4
4
  post 'passkeys/authenticate'
@@ -1,5 +1,5 @@
1
1
  Description:
2
- Creates a PasskeysRails config file, updates the routes and adds migrations.
2
+ Creates a Passkeys::Rails config file, updates the routes and adds migrations.
3
3
 
4
4
  Example:
5
5
  bin/rails generate passkeys-rails:install
@@ -1,20 +1,22 @@
1
1
  require 'rails/generators'
2
2
 
3
- module PasskeysRails
4
- module Generators
5
- class InstallGenerator < Rails::Generators::Base
6
- source_root File.expand_path("templates", __dir__)
3
+ module Passkeys
4
+ module Rails
5
+ module Generators
6
+ class InstallGenerator < ::Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
7
8
 
8
- def copy_config
9
- template 'passkeys_rails_config.rb', "config/initializers/passkeys_rails.rb"
10
- end
9
+ def copy_config
10
+ template 'passkeys_rails_config.rb', "config/initializers/passkeys_rails.rb"
11
+ end
11
12
 
12
- def add_routes
13
- route 'mount PasskeysRails::Engine => "/passkeys_rails"'
14
- end
13
+ def add_routes
14
+ route 'mount Passkeys::Rails::Engine => "/passkeys_rails"'
15
+ end
15
16
 
16
- def show_readme
17
- readme "README" if behavior == :invoke
17
+ def show_readme
18
+ readme "README" if behavior == :invoke
19
+ end
18
20
  end
19
21
  end
20
22
  end
@@ -6,9 +6,9 @@ Depending on your application's configuration some manual setup may be required:
6
6
 
7
7
  For example:
8
8
 
9
- before_action :authitencate_passkey!, except: [:index]
9
+ before_action :authenticate_passkey!, except: [:index]
10
10
 
11
- 2. Optionally include PasskeysRails::Authenticatable to the model(s) you are using as
11
+ 2. Optionally include Passkeys::Rails::Authenticatable to the model(s) you are using as
12
12
  your user model(s). For example, the User model.
13
13
 
14
14
  3. See the reference mobile applications for how to use passkeys-rails for passkey
@@ -1,6 +1,6 @@
1
- require 'passkeys_rails'
1
+ require 'passkeys-rails'
2
2
 
3
- PasskeysRails.config do |c|
3
+ Passkeys::Rails.config do |c|
4
4
  # Secret used to encode the auth token.
5
5
  # Changing this value will invalidate all tokens that have been fetched
6
6
  # through the API.
@@ -0,0 +1,21 @@
1
+ require "rails"
2
+ require "active_support/core_ext/numeric/time"
3
+ require "active_support/dependencies"
4
+
5
+ require "interactor"
6
+ require "jwt"
7
+ require "webauthn"
8
+
9
+ module Passkeys::Rails
10
+ class Engine < ::Rails::Engine
11
+ isolate_namespace Passkeys::Rails
12
+
13
+ config.generators do |g|
14
+ g.test_framework :rspec
15
+ g.fixture_replacement :factory_bot
16
+ g.factory_bot dir: 'spec/factories'
17
+ g.assets false
18
+ g.helper false
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'passkeys-rails'
2
+ require 'rails'
3
+
4
+ module Passkeys
5
+ module Rails
6
+ class Railtie < ::Rails::Railtie
7
+ railtie_name :passkeys_rails
8
+
9
+ rake_tasks do
10
+ path = File.expand_path(__dir__)
11
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
12
+ end
13
+
14
+ generators do
15
+ require "generators/passkeys_rails/install_generator"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ module Passkeys
2
+ module Rails
3
+ VERSION = "0.1.3".freeze
4
+ end
5
+ end
@@ -0,0 +1,36 @@
1
+ # rubocop:disable Naming/FileName
2
+ require 'passkeys/rails/engine'
3
+ require 'passkeys/rails/version'
4
+ require_relative "generators/passkeys_rails/install_generator"
5
+
6
+ module Passkeys
7
+ module Rails
8
+ # Secret used to encode the auth token.
9
+ # Rails.application.secret_key_base is used if none is defined here.
10
+ # Changing this value will invalidate all tokens that have been fetched
11
+ # through the API.
12
+ mattr_accessor(:auth_token_secret)
13
+
14
+ # Algorithm used to generate the auth token.
15
+ # Changing this value will invalidate all tokens that have been fetched
16
+ # through the API.
17
+ mattr_accessor :auth_token_algorithm, default: "HS256"
18
+
19
+ # How long the auth token is valid before requiring a refresh or new login.
20
+ # Set it to 0 for no expiration (not recommended in production).
21
+ mattr_accessor :auth_token_expires_in, default: 30.days
22
+
23
+ class << self
24
+ def config
25
+ yield self
26
+ end
27
+ end
28
+
29
+ require 'passkeys/rails/railtie' if defined?(Rails)
30
+ end
31
+ end
32
+
33
+ ActiveSupport.on_load(:before_initialize) do
34
+ Passkeys::Rails.auth_token_secret ||= Rails.application.secret_key_base
35
+ end
36
+ # rubocop:enable Naming/FileName
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passkeys-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Troy Anderson
@@ -338,7 +338,7 @@ dependencies:
338
338
  - - "~>"
339
339
  - !ruby/object:Gem::Version
340
340
  version: 2.22.0
341
- description: Devise is awesome, but we don't need all that UI/UX for PassKeys. This
341
+ description: Devise is awesome, but we don't need all that UI/UX for PassKeys. This
342
342
  gem is to make it easy to provide a back end that authenticates a mobile front end
343
343
  with PassKeys.
344
344
  email:
@@ -347,29 +347,30 @@ executables: []
347
347
  extensions: []
348
348
  extra_rdoc_files: []
349
349
  files:
350
+ - CHANGELOG.md
350
351
  - MIT-LICENSE
351
352
  - README.md
352
353
  - Rakefile
353
354
  - app/assets/config/passkeys_rails_manifest.js
354
355
  - app/assets/stylesheets/passkeys_rails/application.css
355
- - app/controllers/passkeys_rails/application_controller.rb
356
- - app/controllers/passkeys_rails/passkeys_controller.rb
357
- - app/helpers/passkeys_rails/application_helper.rb
358
- - app/helpers/passkeys_rails/passkeys_helper.rb
359
- - app/interactors/passkeys_rails/begin_authentication.rb
360
- - app/interactors/passkeys_rails/begin_challenge.rb
361
- - app/interactors/passkeys_rails/begin_registration.rb
362
- - app/interactors/passkeys_rails/finish_authentication.rb
363
- - app/interactors/passkeys_rails/finish_registration.rb
364
- - app/interactors/passkeys_rails/generate_auth_token.rb
365
- - app/interactors/passkeys_rails/refresh_token.rb
366
- - app/interactors/passkeys_rails/validate_auth_token.rb
367
- - app/models/concerns/passkeys_rails/authenticatable.rb
368
- - app/models/passkeys_rails/agent.rb
369
- - app/models/passkeys_rails/application_record.rb
370
- - app/models/passkeys_rails/error.rb
371
- - app/models/passkeys_rails/passkey.rb
372
- - app/views/layouts/passkeys_rails/application.html.erb
356
+ - app/controllers/concerns/passkeys/rails/authentication.rb
357
+ - app/controllers/passkeys/rails/application_controller.rb
358
+ - app/controllers/passkeys/rails/passkeys_controller.rb
359
+ - app/interactors/passkeys/rails/begin_authentication.rb
360
+ - app/interactors/passkeys/rails/begin_challenge.rb
361
+ - app/interactors/passkeys/rails/begin_registration.rb
362
+ - app/interactors/passkeys/rails/finish_authentication.rb
363
+ - app/interactors/passkeys/rails/finish_registration.rb
364
+ - app/interactors/passkeys/rails/generate_auth_token.rb
365
+ - app/interactors/passkeys/rails/refresh_token.rb
366
+ - app/interactors/passkeys/rails/validate_auth_token.rb
367
+ - app/models/concerns/passkeys/rails/authenticatable.rb
368
+ - app/models/passkeys/rails/agent.rb
369
+ - app/models/passkeys/rails/application_record.rb
370
+ - app/models/passkeys/rails/error.rb
371
+ - app/models/passkeys/rails/passkey.rb
372
+ - app/views/layouts/passkeys/rails/application.html.erb
373
+ - config/initializers/application_controller.rb
373
374
  - config/routes.rb
374
375
  - db/migrate/20230620012530_create_passkeys_rails_agents.rb
375
376
  - db/migrate/20230620012600_create_passkeys_rails_passkeys.rb
@@ -377,11 +378,10 @@ files:
377
378
  - lib/generators/passkeys_rails/install_generator.rb
378
379
  - lib/generators/passkeys_rails/templates/README
379
380
  - lib/generators/passkeys_rails/templates/passkeys_rails_config.rb
380
- - lib/passkeys_rails.rb
381
- - lib/passkeys_rails/engine.rb
382
- - lib/passkeys_rails/error_middleware.rb
383
- - lib/passkeys_rails/railtie.rb
384
- - lib/passkeys_rails/version.rb
381
+ - lib/passkeys-rails.rb
382
+ - lib/passkeys/rails/engine.rb
383
+ - lib/passkeys/rails/railtie.rb
384
+ - lib/passkeys/rails/version.rb
385
385
  - lib/tasks/passkeys_rails_tasks.rake
386
386
  homepage: https://github.com/alliedcode/passkeys-rails
387
387
  licenses:
@@ -406,7 +406,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
406
406
  - !ruby/object:Gem::Version
407
407
  version: '0'
408
408
  requirements: []
409
- rubygems_version: 3.4.12
409
+ rubygems_version: 3.4.17
410
410
  signing_key:
411
411
  specification_version: 4
412
412
  summary: PassKey authentication back end with simple API
@@ -1,22 +0,0 @@
1
- module PasskeysRails
2
- class ApplicationController < ActionController::Base
3
- rescue_from ::Interactor::Failure, with: :handle_interactor_failure
4
- rescue_from ActionController::ParameterMissing, with: :handle_missing_parameter
5
-
6
- protected
7
-
8
- def handle_missing_parameter(error)
9
- render_error(:authentication, 'missing_parameter', error.message)
10
- end
11
-
12
- def handle_interactor_failure(failure)
13
- render_error(:authentication, failure.context.code, failure.context.message)
14
- end
15
-
16
- private
17
-
18
- def render_error(context, code, message, status: :unprocessable_entity)
19
- render json: { error: { context:, code:, message: } }, status:
20
- end
21
- end
22
- end
@@ -1,61 +0,0 @@
1
- module PasskeysRails
2
- class PasskeysController < ApplicationController
3
- def challenge
4
- result = PasskeysRails::BeginChallenge.call!(username: challenge_params[:username])
5
-
6
- # Store the challenge so we can verify the future register or authentication request
7
- session[:passkeys_rails] = result.session_data
8
-
9
- render json: result.response.as_json
10
- end
11
-
12
- def register
13
- result = PasskeysRails::FinishRegistration.call!(credential: attestation_credential_params.to_h,
14
- authenticatable_class:,
15
- username: session.dig(:passkeys_rails, :username),
16
- challenge: session.dig(:passkeys_rails, :challenge))
17
-
18
- render json: { username: result.username, auth_token: result.auth_token }
19
- end
20
-
21
- def authenticate
22
- result = PasskeysRails::FinishAuthentication.call!(credential: authentication_params.to_h,
23
- challenge: session.dig(:passkeys_rails, :challenge))
24
-
25
- render json: { username: result.username, auth_token: result.auth_token }
26
- end
27
-
28
- def refresh
29
- result = PasskeysRails::RefreshToken.call!(token: refresh_params[:auth_token])
30
- render json: { username: result.username, auth_token: result.auth_token }
31
- end
32
-
33
- protected
34
-
35
- def challenge_params
36
- params.permit(:username)
37
- end
38
-
39
- def attestation_credential_params
40
- credential = params.require(:credential)
41
- credential.require(%i[id rawId type response])
42
- credential.require(:response).require(%i[attestationObject clientDataJSON])
43
- credential.permit(:id, :rawId, :type, { response: %i[attestationObject clientDataJSON] })
44
- end
45
-
46
- def authenticatable_class
47
- params[:authenticatable_class]
48
- end
49
-
50
- def authentication_params
51
- params.require(%i[id rawId type response])
52
- params.require(:response).require(%i[authenticatorData clientDataJSON signature userHandle])
53
- params.permit(:id, :rawId, :type, { response: %i[authenticatorData clientDataJSON signature userHandle] })
54
- end
55
-
56
- def refresh_params
57
- params.require(:auth_token)
58
- params.permit(:auth_token)
59
- end
60
- end
61
- end
@@ -1,21 +0,0 @@
1
- module PasskeysRails
2
- module ApplicationHelper
3
- def current_agent
4
- return nil if request.headers['HTTP_X_AUTH'].blank?
5
-
6
- @current_agent ||= validated_auth_token&.success? && validated_auth_token&.agent
7
- end
8
-
9
- def authenticate!
10
- return if validated_auth_token.success?
11
-
12
- raise PasskeysRails::Error.new(:authentication,
13
- code: :unauthorized,
14
- message: "You are not authorized to access this resource.")
15
- end
16
-
17
- def validated_auth_token
18
- @validated_auth_token ||= ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
19
- end
20
- end
21
- end
@@ -1,4 +0,0 @@
1
- module PasskeysRails
2
- module PasskeysHelper
3
- end
4
- end
@@ -1,24 +0,0 @@
1
- require_relative 'error_middleware'
2
- module PasskeysRails
3
- class Engine < ::Rails::Engine
4
- isolate_namespace PasskeysRails
5
-
6
- config.generators do |g|
7
- g.test_framework :rspec
8
- g.fixture_replacement :factory_bot
9
- g.factory_bot dir: 'spec/factories'
10
- g.assets false
11
- g.helper false
12
- end
13
-
14
- config.to_prepare do
15
- # include our helper methods in the host application's ApplicationController
16
- ::ApplicationController.include ApplicationHelper
17
- end
18
-
19
- # provide a way to bail out of the render flow if needed
20
- initializer 'passkeys_rails.configure.middleware' do |app|
21
- app.middleware.use ErrorMiddleware
22
- end
23
- end
24
- end
@@ -1,17 +0,0 @@
1
- module PasskeysRails
2
- class ErrorMiddleware
3
- def initialize(app)
4
- @app = app
5
- end
6
-
7
- def call(env)
8
- begin
9
- response = @app.call(env)
10
- rescue Error => e
11
- return [401, { 'Content-Type' => 'application/json' }, e.to_h.to_json]
12
- end
13
-
14
- response
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- require 'passkeys_rails'
2
- require 'rails'
3
-
4
- module PasskeysRails
5
- class Railtie < Rails::Railtie
6
- railtie_name :passkeys_rails
7
-
8
- rake_tasks do
9
- path = File.expand_path(__dir__)
10
- Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
11
- end
12
-
13
- generators do
14
- require "generators/passkeys_rails/install_generator"
15
- end
16
- end
17
- end
@@ -1,3 +0,0 @@
1
- module PasskeysRails
2
- VERSION = "0.1.1".freeze
3
- end
@@ -1,38 +0,0 @@
1
- require_relative "passkeys_rails/version"
2
- require_relative "passkeys_rails/engine"
3
- require_relative "generators/passkeys_rails/install_generator"
4
- require "rails"
5
- require "active_support/core_ext/numeric/time"
6
- require "active_support/dependencies"
7
- require "interactor"
8
- require "jwt"
9
- require "webauthn"
10
-
11
- module PasskeysRails
12
- # Secret used to encode the auth token.
13
- # Rails.application.secret_key_base is used if none is defined here.
14
- # Changing this value will invalidate all tokens that have been fetched
15
- # through the API.
16
- mattr_accessor(:auth_token_secret)
17
-
18
- # Algorithm used to generate the auth token.
19
- # Changing this value will invalidate all tokens that have been fetched
20
- # through the API.
21
- mattr_accessor :auth_token_algorithm, default: "HS256"
22
-
23
- # How long the auth token is valid before requiring a refresh or new login.
24
- # Set it to 0 for no expiration (not recommended in production).
25
- mattr_accessor :auth_token_expires_in, default: 30.days
26
-
27
- class << self
28
- def config
29
- yield self
30
- end
31
- end
32
-
33
- require 'passkeys_rails/railtie' if defined?(Rails)
34
- end
35
-
36
- ActiveSupport.on_load(:before_initialize) do
37
- PasskeysRails.auth_token_secret ||= Rails.application.secret_key_base
38
- end