passkeys-rails 0.1.2 → 0.1.4
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 +9 -0
- data/README.md +4 -4
- data/app/controllers/concerns/passkeys_rails/authentication.rb +29 -0
- data/app/controllers/passkeys_rails/application_controller.rb +22 -0
- data/app/controllers/passkeys_rails/passkeys_controller.rb +61 -0
- data/app/interactors/passkeys_rails/begin_authentication.rb +9 -0
- data/app/interactors/passkeys_rails/begin_challenge.rb +35 -0
- data/app/interactors/passkeys_rails/begin_registration.rb +23 -0
- data/app/interactors/passkeys_rails/finish_authentication.rb +53 -0
- data/app/interactors/passkeys_rails/finish_registration.rb +77 -0
- data/app/interactors/passkeys_rails/generate_auth_token.rb +27 -0
- data/app/interactors/passkeys_rails/refresh_token.rb +17 -0
- data/app/interactors/passkeys_rails/validate_auth_token.rb +33 -0
- data/app/models/concerns/passkeys_rails/authenticatable.rb +17 -0
- data/app/models/passkeys_rails/agent.rb +15 -0
- data/app/models/passkeys_rails/application_record.rb +5 -0
- data/app/models/passkeys_rails/error.rb +14 -0
- data/app/models/passkeys_rails/passkey.rb +8 -0
- data/config/initializers/application_controller.rb +12 -0
- data/config/routes.rb +1 -1
- data/lib/generators/passkeys_rails/USAGE +1 -1
- data/lib/generators/passkeys_rails/install_generator.rb +12 -14
- data/lib/generators/passkeys_rails/templates/README +1 -1
- data/lib/generators/passkeys_rails/templates/passkeys_rails_config.rb +1 -1
- data/lib/passkeys-rails.rb +21 -23
- data/lib/passkeys_rails/engine.rb +21 -0
- data/lib/passkeys_rails/railtie.rb +17 -0
- data/lib/passkeys_rails/version.rb +3 -0
- metadata +22 -24
- data/app/assets/config/passkeys_rails_manifest.js +0 -1
- data/app/assets/stylesheets/passkeys_rails/application.css +0 -15
- data/app/controllers/passkeys/rails/application_controller.rb +0 -24
- data/app/controllers/passkeys/rails/passkeys_controller.rb +0 -63
- data/app/models/concerns/passkeys/rails/authenticatable.rb +0 -19
- data/app/models/passkeys/rails/agent.rb +0 -17
- data/app/models/passkeys/rails/application_record.rb +0 -7
- data/app/models/passkeys/rails/error.rb +0 -16
- data/app/models/passkeys/rails/passkey.rb +0 -10
- data/app/views/layouts/passkeys/rails/application.html.erb +0 -15
- data/lib/passkeys/rails/controllers/helpers.rb +0 -33
- data/lib/passkeys/rails/engine.rb +0 -44
- data/lib/passkeys/rails/interactors/begin_authentication.rb +0 -11
- data/lib/passkeys/rails/interactors/begin_challenge.rb +0 -37
- data/lib/passkeys/rails/interactors/begin_registration.rb +0 -25
- data/lib/passkeys/rails/interactors/finish_authentication.rb +0 -55
- data/lib/passkeys/rails/interactors/finish_registration.rb +0 -79
- data/lib/passkeys/rails/interactors/generate_auth_token.rb +0 -29
- data/lib/passkeys/rails/interactors/refresh_token.rb +0 -19
- data/lib/passkeys/rails/interactors/validate_auth_token.rb +0 -35
- data/lib/passkeys/rails/railtie.rb +0 -19
- data/lib/passkeys/rails/version.rb +0 -5
@@ -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 PasskeysRails
|
10
|
+
class Engine < ::Rails::Engine
|
11
|
+
isolate_namespace PasskeysRails
|
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,17 @@
|
|
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
|
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.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Troy Anderson
|
@@ -351,16 +351,23 @@ files:
|
|
351
351
|
- MIT-LICENSE
|
352
352
|
- README.md
|
353
353
|
- Rakefile
|
354
|
-
- app/
|
355
|
-
- app/
|
356
|
-
- app/controllers/
|
357
|
-
- app/
|
358
|
-
- app/
|
359
|
-
- app/
|
360
|
-
- app/
|
361
|
-
- app/
|
362
|
-
- app/
|
363
|
-
- app/
|
354
|
+
- app/controllers/concerns/passkeys_rails/authentication.rb
|
355
|
+
- app/controllers/passkeys_rails/application_controller.rb
|
356
|
+
- app/controllers/passkeys_rails/passkeys_controller.rb
|
357
|
+
- app/interactors/passkeys_rails/begin_authentication.rb
|
358
|
+
- app/interactors/passkeys_rails/begin_challenge.rb
|
359
|
+
- app/interactors/passkeys_rails/begin_registration.rb
|
360
|
+
- app/interactors/passkeys_rails/finish_authentication.rb
|
361
|
+
- app/interactors/passkeys_rails/finish_registration.rb
|
362
|
+
- app/interactors/passkeys_rails/generate_auth_token.rb
|
363
|
+
- app/interactors/passkeys_rails/refresh_token.rb
|
364
|
+
- app/interactors/passkeys_rails/validate_auth_token.rb
|
365
|
+
- app/models/concerns/passkeys_rails/authenticatable.rb
|
366
|
+
- app/models/passkeys_rails/agent.rb
|
367
|
+
- app/models/passkeys_rails/application_record.rb
|
368
|
+
- app/models/passkeys_rails/error.rb
|
369
|
+
- app/models/passkeys_rails/passkey.rb
|
370
|
+
- config/initializers/application_controller.rb
|
364
371
|
- config/routes.rb
|
365
372
|
- db/migrate/20230620012530_create_passkeys_rails_agents.rb
|
366
373
|
- db/migrate/20230620012600_create_passkeys_rails_passkeys.rb
|
@@ -369,18 +376,9 @@ files:
|
|
369
376
|
- lib/generators/passkeys_rails/templates/README
|
370
377
|
- lib/generators/passkeys_rails/templates/passkeys_rails_config.rb
|
371
378
|
- lib/passkeys-rails.rb
|
372
|
-
- lib/
|
373
|
-
- lib/
|
374
|
-
- lib/
|
375
|
-
- lib/passkeys/rails/interactors/begin_challenge.rb
|
376
|
-
- lib/passkeys/rails/interactors/begin_registration.rb
|
377
|
-
- lib/passkeys/rails/interactors/finish_authentication.rb
|
378
|
-
- lib/passkeys/rails/interactors/finish_registration.rb
|
379
|
-
- lib/passkeys/rails/interactors/generate_auth_token.rb
|
380
|
-
- lib/passkeys/rails/interactors/refresh_token.rb
|
381
|
-
- lib/passkeys/rails/interactors/validate_auth_token.rb
|
382
|
-
- lib/passkeys/rails/railtie.rb
|
383
|
-
- lib/passkeys/rails/version.rb
|
379
|
+
- lib/passkeys_rails/engine.rb
|
380
|
+
- lib/passkeys_rails/railtie.rb
|
381
|
+
- lib/passkeys_rails/version.rb
|
384
382
|
- lib/tasks/passkeys_rails_tasks.rake
|
385
383
|
homepage: https://github.com/alliedcode/passkeys-rails
|
386
384
|
licenses:
|
@@ -405,7 +403,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
405
403
|
- !ruby/object:Gem::Version
|
406
404
|
version: '0'
|
407
405
|
requirements: []
|
408
|
-
rubygems_version: 3.4.
|
406
|
+
rubygems_version: 3.4.17
|
409
407
|
signing_key:
|
410
408
|
specification_version: 4
|
411
409
|
summary: PassKey authentication back end with simple API
|
@@ -1 +0,0 @@
|
|
1
|
-
//= link_directory ../stylesheets/passkeys_rails .css
|
@@ -1,15 +0,0 @@
|
|
1
|
-
/*
|
2
|
-
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
-
* listed below.
|
4
|
-
*
|
5
|
-
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
-
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
-
*
|
8
|
-
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
-
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
-
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
-
* It is generally better to create a new file per style scope.
|
12
|
-
*
|
13
|
-
*= require_tree .
|
14
|
-
*= require_self
|
15
|
-
*/
|
@@ -1,24 +0,0 @@
|
|
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
|
@@ -1,63 +0,0 @@
|
|
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,19 +0,0 @@
|
|
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
|
@@ -1,17 +0,0 @@
|
|
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
|
@@ -1,33 +0,0 @@
|
|
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
|
@@ -1,44 +0,0 @@
|
|
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
|
@@ -1,37 +0,0 @@
|
|
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
|
@@ -1,25 +0,0 @@
|
|
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
|
@@ -1,55 +0,0 @@
|
|
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
|
@@ -1,79 +0,0 @@
|
|
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
|
@@ -1,29 +0,0 @@
|
|
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
|
@@ -1,19 +0,0 @@
|
|
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
|