passkeys-rails 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +15 -0
 - data/README.md +17 -2
 - data/app/controllers/passkeys/rails/application_controller.rb +24 -0
 - data/app/controllers/passkeys/rails/passkeys_controller.rb +63 -0
 - data/app/models/concerns/passkeys/rails/authenticatable.rb +19 -0
 - data/app/models/passkeys/rails/agent.rb +17 -0
 - data/app/models/passkeys/rails/application_record.rb +7 -0
 - data/app/models/passkeys/rails/error.rb +16 -0
 - data/app/models/passkeys/rails/passkey.rb +10 -0
 - data/config/routes.rb +1 -1
 - data/lib/generators/passkeys_rails/USAGE +1 -1
 - data/lib/generators/passkeys_rails/install_generator.rb +14 -12
 - data/lib/generators/passkeys_rails/templates/README +2 -2
 - data/lib/generators/passkeys_rails/templates/passkeys_rails_config.rb +2 -2
 - data/lib/passkeys/rails/controllers/helpers.rb +33 -0
 - data/lib/passkeys/rails/engine.rb +44 -0
 - data/lib/passkeys/rails/interactors/begin_authentication.rb +11 -0
 - data/lib/passkeys/rails/interactors/begin_challenge.rb +37 -0
 - data/lib/passkeys/rails/interactors/begin_registration.rb +25 -0
 - data/lib/passkeys/rails/interactors/finish_authentication.rb +55 -0
 - data/lib/passkeys/rails/interactors/finish_registration.rb +79 -0
 - data/lib/passkeys/rails/interactors/generate_auth_token.rb +29 -0
 - data/lib/passkeys/rails/interactors/refresh_token.rb +19 -0
 - data/lib/passkeys/rails/interactors/validate_auth_token.rb +35 -0
 - data/lib/passkeys/rails/railtie.rb +19 -0
 - data/lib/passkeys/rails/version.rb +5 -0
 - data/lib/passkeys-rails.rb +36 -0
 - metadata +24 -25
 - data/app/controllers/passkeys_rails/application_controller.rb +0 -22
 - data/app/controllers/passkeys_rails/passkeys_controller.rb +0 -61
 - data/app/helpers/passkeys_rails/application_helper.rb +0 -21
 - data/app/helpers/passkeys_rails/passkeys_helper.rb +0 -4
 - data/app/interactors/passkeys_rails/begin_authentication.rb +0 -9
 - data/app/interactors/passkeys_rails/begin_challenge.rb +0 -35
 - data/app/interactors/passkeys_rails/begin_registration.rb +0 -23
 - data/app/interactors/passkeys_rails/finish_authentication.rb +0 -53
 - data/app/interactors/passkeys_rails/finish_registration.rb +0 -77
 - data/app/interactors/passkeys_rails/generate_auth_token.rb +0 -27
 - data/app/interactors/passkeys_rails/refresh_token.rb +0 -17
 - data/app/interactors/passkeys_rails/validate_auth_token.rb +0 -33
 - data/app/models/concerns/passkeys_rails/authenticatable.rb +0 -17
 - data/app/models/passkeys_rails/agent.rb +0 -15
 - data/app/models/passkeys_rails/application_record.rb +0 -5
 - data/app/models/passkeys_rails/error.rb +0 -14
 - data/app/models/passkeys_rails/passkey.rb +0 -8
 - data/lib/passkeys_rails/engine.rb +0 -24
 - data/lib/passkeys_rails/error_middleware.rb +0 -17
 - data/lib/passkeys_rails/railtie.rb +0 -17
 - data/lib/passkeys_rails/version.rb +0 -3
 - data/lib/passkeys_rails.rb +0 -38
 - /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:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: c6b2553d498c30365341aba0aad49eafd09c5b49dd5453bbad29c20a026ab39c
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 795d362e15d977c47b5381a4fe5cc852cdd546a0fa8f81e5a5452343f2b4fa1e
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 15694fd664f6b60343ede055acfefdfef0d8378ffe386420e7cd5d996920fe19a9321ac1148f3e22605b85a8486cbd1f56999167a84be33f9acf950ea0e5e722
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: b6f224af15ee0feb2489c786eefe68293d4854b43b7dcf3a2d17dea4514497cdd5df5f80bd5941972918b4c05864529c62bbe694c09ff58540a87529216bf8f3
         
     | 
    
        data/CHANGELOG.md
    ADDED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -1,12 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            [](https://badge.fury.io/rb/passkeys-rails)
         
     | 
| 
       1 
2 
     | 
    
         
             
            [](https://travis-ci.org/alliedcode/passkeys-rails)
         
     | 
| 
       2 
3 
     | 
    
         
             
            [](https://codecov.io/gh/alliedcode/passkeys-rails)
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
            #  
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
      
 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,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
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_support/concern'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Authenticatable
         
     | 
| 
      
 6 
     | 
    
         
            +
                  extend ActiveSupport::Concern
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  included do
         
     | 
| 
      
 9 
     | 
    
         
            +
                    has_one :agent, as: :authenticatable
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    delegate :registered?, to: :agent, allow_nil: true
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def registering_with(_agent)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      # initialize required attributes
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Agent < ApplicationRecord
         
     | 
| 
      
 4 
     | 
    
         
            +
                  belongs_to :authenticatable, polymorphic: true, optional: true
         
     | 
| 
      
 5 
     | 
    
         
            +
                  has_many :passkeys
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  scope :registered, -> { where.not registered_at: nil }
         
     | 
| 
      
 8 
     | 
    
         
            +
                  scope :unregistered, -> { where registered_at: nil }
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  validates :username, presence: true, uniqueness: true
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def registered?
         
     | 
| 
      
 13 
     | 
    
         
            +
                    registered_at.present?
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
    
        data/config/routes.rb
    CHANGED
    
    
| 
         @@ -1,20 +1,22 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'rails/generators'
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            module  
     | 
| 
       4 
     | 
    
         
            -
              module  
     | 
| 
       5 
     | 
    
         
            -
                 
     | 
| 
       6 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
      
 9 
     | 
    
         
            +
                    def copy_config
         
     | 
| 
      
 10 
     | 
    
         
            +
                      template 'passkeys_rails_config.rb', "config/initializers/passkeys_rails.rb"
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
       11 
12 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def add_routes
         
     | 
| 
      
 14 
     | 
    
         
            +
                      route 'mount Passkeys::Rails::Engine => "/passkeys_rails"'
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
       15 
16 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 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 : 
     | 
| 
      
 9 
     | 
    
         
            +
                    before_action :authenticate_passkey!, except: [:index]
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
              2. Optionally include  
     | 
| 
      
 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
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Controllers
         
     | 
| 
      
 4 
     | 
    
         
            +
                  module Helpers
         
     | 
| 
      
 5 
     | 
    
         
            +
                    extend ActiveSupport::Concern
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                    included do
         
     | 
| 
      
 8 
     | 
    
         
            +
                      rescue_from Passkeys::Rails::Error do |e|
         
     | 
| 
      
 9 
     | 
    
         
            +
                        render json: e.to_h, status: :unauthorized
         
     | 
| 
      
 10 
     | 
    
         
            +
                      end
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def current_agent
         
     | 
| 
      
 14 
     | 
    
         
            +
                      return nil if request.headers['HTTP_X_AUTH'].blank?
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                      @current_agent ||= validated_auth_token&.success? && validated_auth_token&.agent
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    def authenticate_passkey!
         
     | 
| 
      
 20 
     | 
    
         
            +
                      return if validated_auth_token.success?
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                      raise Passkeys::Rails::Error.new(:authentication,
         
     | 
| 
      
 23 
     | 
    
         
            +
                                                       code: :unauthorized,
         
     | 
| 
      
 24 
     | 
    
         
            +
                                                       message: "You are not authorized to access this resource.")
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    def validated_auth_token
         
     | 
| 
      
 28 
     | 
    
         
            +
                      @validated_auth_token ||= Passkeys::Rails::ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,44 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
            require_relative 'controllers/helpers'
         
     | 
| 
      
 10 
     | 
    
         
            +
            require_relative "interactors/begin_authentication"
         
     | 
| 
      
 11 
     | 
    
         
            +
            require_relative "interactors/begin_challenge"
         
     | 
| 
      
 12 
     | 
    
         
            +
            require_relative "interactors/begin_registration"
         
     | 
| 
      
 13 
     | 
    
         
            +
            require_relative "interactors/finish_authentication"
         
     | 
| 
      
 14 
     | 
    
         
            +
            require_relative "interactors/finish_registration"
         
     | 
| 
      
 15 
     | 
    
         
            +
            require_relative "interactors/generate_auth_token"
         
     | 
| 
      
 16 
     | 
    
         
            +
            require_relative "interactors/refresh_token"
         
     | 
| 
      
 17 
     | 
    
         
            +
            require_relative "interactors/validate_auth_token"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 20 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 21 
     | 
    
         
            +
                class Engine < ::Rails::Engine
         
     | 
| 
      
 22 
     | 
    
         
            +
                  isolate_namespace Passkeys::Rails
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  config.generators do |g|
         
     | 
| 
      
 25 
     | 
    
         
            +
                    g.test_framework :rspec
         
     | 
| 
      
 26 
     | 
    
         
            +
                    g.fixture_replacement :factory_bot
         
     | 
| 
      
 27 
     | 
    
         
            +
                    g.factory_bot dir: 'spec/factories'
         
     | 
| 
      
 28 
     | 
    
         
            +
                    g.assets false
         
     | 
| 
      
 29 
     | 
    
         
            +
                    g.helper false
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  # Include helpers
         
     | 
| 
      
 33 
     | 
    
         
            +
                  initializer "passkeys-rails.helpers" do
         
     | 
| 
      
 34 
     | 
    
         
            +
                    ActiveSupport.on_load(:action_controller_base) do
         
     | 
| 
      
 35 
     | 
    
         
            +
                      include Passkeys::Rails::Controllers::Helpers
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    ActiveSupport.on_load(:action_controller_api) do
         
     | 
| 
      
 39 
     | 
    
         
            +
                      include Passkeys::Rails::Controllers::Helpers
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 3 
     | 
    
         
            +
                class BeginChallenge
         
     | 
| 
      
 4 
     | 
    
         
            +
                  include Interactor
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  delegate :username, to: :context
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 9 
     | 
    
         
            +
                    result = generate_challenge!
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    options = result.options
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    context.response = options
         
     | 
| 
      
 14 
     | 
    
         
            +
                    context.session_data = session_data(options)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  rescue Interactor::Failure => e
         
     | 
| 
      
 16 
     | 
    
         
            +
                    context.fail! code: e.context.code, message: e.context.message
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  private
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def generate_challenge!
         
     | 
| 
      
 22 
     | 
    
         
            +
                    if username.present?
         
     | 
| 
      
 23 
     | 
    
         
            +
                      BeginRegistration.call!(username:)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    else
         
     | 
| 
      
 25 
     | 
    
         
            +
                      BeginAuthentication.call!
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def session_data(options)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    {
         
     | 
| 
      
 31 
     | 
    
         
            +
                      username:,
         
     | 
| 
      
 32 
     | 
    
         
            +
                      challenge: WebAuthn.standard_encoder.encode(options.challenge)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    }
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 3 
     | 
    
         
            +
                class BeginRegistration
         
     | 
| 
      
 4 
     | 
    
         
            +
                  include Interactor
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  delegate :username, to: :context
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 9 
     | 
    
         
            +
                    agent = create_unregistered_agent
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    context.options = WebAuthn::Credential.options_for_create(user: { id: agent.webauthn_identifier, name: agent.username })
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  private
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def create_unregistered_agent
         
     | 
| 
      
 17 
     | 
    
         
            +
                    agent = Agent.create(username:, webauthn_identifier: WebAuthn.generate_user_id)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    context.fail!(code: :validation_errors, message: agent.errors.full_messages.to_sentence) unless agent.valid?
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    agent
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,55 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Finish authentication ceremony
         
     | 
| 
      
 2 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 3 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 4 
     | 
    
         
            +
                class FinishAuthentication
         
     | 
| 
      
 5 
     | 
    
         
            +
                  include Interactor
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  delegate :credential, :challenge, to: :context
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 10 
     | 
    
         
            +
                    verify_credential!
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    context.username = agent.username
         
     | 
| 
      
 13 
     | 
    
         
            +
                    context.auth_token = GenerateAuthToken.call!(agent:).auth_token
         
     | 
| 
      
 14 
     | 
    
         
            +
                  rescue Interactor::Failure => e
         
     | 
| 
      
 15 
     | 
    
         
            +
                    context.fail! code: e.context.code, message: e.context.message
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  private
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def verify_credential!
         
     | 
| 
      
 21 
     | 
    
         
            +
                    webauthn_credential.verify(
         
     | 
| 
      
 22 
     | 
    
         
            +
                      challenge,
         
     | 
| 
      
 23 
     | 
    
         
            +
                      public_key: passkey.public_key,
         
     | 
| 
      
 24 
     | 
    
         
            +
                      sign_count: passkey.sign_count
         
     | 
| 
      
 25 
     | 
    
         
            +
                    )
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    passkey.update!(sign_count: webauthn_credential.sign_count)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    agent.update!(last_authenticated_at: Time.current)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  rescue WebAuthn::SignCountVerificationError
         
     | 
| 
      
 30 
     | 
    
         
            +
                    # Cryptographic verification of the authenticator data succeeded, but the signature counter was less than or equal
         
     | 
| 
      
 31 
     | 
    
         
            +
                    # to the stored value. This can have several reasons and depending on your risk tolerance you can choose to fail or
         
     | 
| 
      
 32 
     | 
    
         
            +
                    # pass authentication. For more information see https://www.w3.org/TR/webauthn/#sign-counter
         
     | 
| 
      
 33 
     | 
    
         
            +
                  rescue WebAuthn::Error => e
         
     | 
| 
      
 34 
     | 
    
         
            +
                    context.fail!(code: :webauthn_error, message: e.message)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def webauthn_credential
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @webauthn_credential ||= WebAuthn::Credential.from_get(credential)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def passkey
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @passkey ||= begin
         
     | 
| 
      
 43 
     | 
    
         
            +
                      passkey = Passkey.find_by(identifier: webauthn_credential.id)
         
     | 
| 
      
 44 
     | 
    
         
            +
                      context.fail!(code: :passkey_not_found, message: "Unable to find the specified passkey") if passkey.blank?
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                      passkey
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def agent
         
     | 
| 
      
 51 
     | 
    
         
            +
                    passkey.agent
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Finish registration ceremony
         
     | 
| 
      
 2 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 3 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 4 
     | 
    
         
            +
                class FinishRegistration
         
     | 
| 
      
 5 
     | 
    
         
            +
                  include Interactor
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  delegate :credential, :username, :challenge, :authenticatable_class, to: :context
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 10 
     | 
    
         
            +
                    verify_credential!
         
     | 
| 
      
 11 
     | 
    
         
            +
                    store_passkey_and_register_agent!
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    context.username = agent.username
         
     | 
| 
      
 14 
     | 
    
         
            +
                    context.auth_token = GenerateAuthToken.call!(agent:).auth_token
         
     | 
| 
      
 15 
     | 
    
         
            +
                  rescue Interactor::Failure => e
         
     | 
| 
      
 16 
     | 
    
         
            +
                    context.fail! code: e.context.code, message: e.context.message
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  private
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def verify_credential!
         
     | 
| 
      
 22 
     | 
    
         
            +
                    webauthn_credential.verify(challenge)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  rescue WebAuthn::Error => e
         
     | 
| 
      
 24 
     | 
    
         
            +
                    context.fail!(code: :webauthn_error, message: e.message)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  rescue StandardError => e
         
     | 
| 
      
 26 
     | 
    
         
            +
                    context.fail!(code: :error, message: e.message)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def store_passkey_and_register_agent!
         
     | 
| 
      
 30 
     | 
    
         
            +
                    agent.transaction do
         
     | 
| 
      
 31 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 32 
     | 
    
         
            +
                        # Store Credential ID, Credential Public Key and Sign Count for future authentications
         
     | 
| 
      
 33 
     | 
    
         
            +
                        agent.passkeys.create!(
         
     | 
| 
      
 34 
     | 
    
         
            +
                          identifier: webauthn_credential.id,
         
     | 
| 
      
 35 
     | 
    
         
            +
                          public_key: webauthn_credential.public_key,
         
     | 
| 
      
 36 
     | 
    
         
            +
                          sign_count: webauthn_credential.sign_count
         
     | 
| 
      
 37 
     | 
    
         
            +
                        )
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                        agent.update! registered_at: Time.current
         
     | 
| 
      
 40 
     | 
    
         
            +
                      rescue StandardError => e
         
     | 
| 
      
 41 
     | 
    
         
            +
                        context.fail! code: :passkey_error, message: e.message
         
     | 
| 
      
 42 
     | 
    
         
            +
                      end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                      create_authenticatable! if authenticatable_class.present?
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  def create_authenticatable!
         
     | 
| 
      
 49 
     | 
    
         
            +
                    klass = begin
         
     | 
| 
      
 50 
     | 
    
         
            +
                      authenticatable_class.constantize
         
     | 
| 
      
 51 
     | 
    
         
            +
                    rescue StandardError
         
     | 
| 
      
 52 
     | 
    
         
            +
                      context.fail!(code: :invalid_authenticatable_class, message: "authenticatable_class (#{authenticatable_class}) is not defined")
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 56 
     | 
    
         
            +
                      authenticatable = klass.create! do |obj|
         
     | 
| 
      
 57 
     | 
    
         
            +
                        obj.registering_with(agent) if obj.respond_to?(:registering_with)
         
     | 
| 
      
 58 
     | 
    
         
            +
                      end
         
     | 
| 
      
 59 
     | 
    
         
            +
                      agent.update!(authenticatable:)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    rescue ActiveRecord::RecordInvalid => e
         
     | 
| 
      
 61 
     | 
    
         
            +
                      context.fail!(code: :record_invalid, message: e.message)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def webauthn_credential
         
     | 
| 
      
 66 
     | 
    
         
            +
                    @webauthn_credential ||= WebAuthn::Credential.from_create(credential)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  def agent
         
     | 
| 
      
 70 
     | 
    
         
            +
                    @agent ||= begin
         
     | 
| 
      
 71 
     | 
    
         
            +
                      agent = Agent.find_by(username:)
         
     | 
| 
      
 72 
     | 
    
         
            +
                      context.fail!(code: :agent_not_found, message: "Agent not found for session value: \"#{username}\"") if agent.blank?
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                      agent
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 3 
     | 
    
         
            +
                class GenerateAuthToken
         
     | 
| 
      
 4 
     | 
    
         
            +
                  include Interactor
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  delegate :agent, to: :context
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 9 
     | 
    
         
            +
                    context.auth_token = generate_auth_token
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  private
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def generate_auth_token
         
     | 
| 
      
 15 
     | 
    
         
            +
                    JWT.encode(jwt_payload,
         
     | 
| 
      
 16 
     | 
    
         
            +
                               Passkeys::Rails.auth_token_secret,
         
     | 
| 
      
 17 
     | 
    
         
            +
                               Passkeys::Rails.auth_token_algorithm)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def jwt_payload
         
     | 
| 
      
 21 
     | 
    
         
            +
                    expiration = (Time.current + Passkeys::Rails.auth_token_expires_in).to_i
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    payload = { agent_id: agent.id }
         
     | 
| 
      
 24 
     | 
    
         
            +
                    payload[:exp] = expiration unless expiration.zero?
         
     | 
| 
      
 25 
     | 
    
         
            +
                    payload
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Finish authentication ceremony
         
     | 
| 
      
 2 
     | 
    
         
            +
            module Passkeys
         
     | 
| 
      
 3 
     | 
    
         
            +
              module Rails
         
     | 
| 
      
 4 
     | 
    
         
            +
                class RefreshToken
         
     | 
| 
      
 5 
     | 
    
         
            +
                  include Interactor
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  delegate :token, to: :context
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def call
         
     | 
| 
      
 10 
     | 
    
         
            +
                    agent = ValidateAuthToken.call!(auth_token: token).agent
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    context.username = agent.username
         
     | 
| 
      
 13 
     | 
    
         
            +
                    context.auth_token = GenerateAuthToken.call!(agent:).auth_token
         
     | 
| 
      
 14 
     | 
    
         
            +
                  rescue Interactor::Failure => e
         
     | 
| 
      
 15 
     | 
    
         
            +
                    context.fail! code: e.context.code, message: e.context.message
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     |