passkeys-rails 0.1.3 → 0.1.5
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 +4 -0
- data/README.md +4 -4
- data/app/controllers/concerns/passkeys_rails/authentication.rb +30 -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 → passkeys_rails}/begin_authentication.rb +1 -1
- data/app/interactors/{passkeys/rails → passkeys_rails}/begin_challenge.rb +1 -1
- data/app/interactors/{passkeys/rails → passkeys_rails}/begin_registration.rb +5 -3
- data/app/interactors/{passkeys/rails → passkeys_rails}/finish_authentication.rb +1 -1
- data/app/interactors/{passkeys/rails → passkeys_rails}/finish_registration.rb +1 -1
- data/app/interactors/{passkeys/rails → passkeys_rails}/generate_auth_token.rb +4 -4
- data/app/interactors/{passkeys/rails → passkeys_rails}/refresh_token.rb +1 -1
- data/app/interactors/{passkeys/rails → passkeys_rails}/validate_auth_token.rb +3 -3
- data/app/models/concerns/{passkeys/rails → passkeys_rails}/authenticatable.rb +1 -1
- data/app/models/{passkeys/rails → passkeys_rails}/agent.rb +1 -1
- data/app/models/{passkeys/rails → passkeys_rails}/application_record.rb +1 -1
- data/app/models/{passkeys/rails → passkeys_rails}/error.rb +1 -1
- data/app/models/{passkeys/rails → passkeys_rails}/passkey.rb +1 -1
- data/config/initializers/application_controller.rb +5 -5
- 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 → passkeys_rails}/engine.rb +2 -2
- data/lib/passkeys_rails/railtie.rb +17 -0
- data/lib/passkeys_rails/version.rb +3 -0
- metadata +21 -24
- data/app/assets/config/passkeys_rails_manifest.js +0 -1
- data/app/assets/stylesheets/passkeys_rails/application.css +0 -15
- data/app/controllers/concerns/passkeys/rails/authentication.rb +0 -29
- data/app/controllers/passkeys/rails/application_controller.rb +0 -24
- data/app/controllers/passkeys/rails/passkeys_controller.rb +0 -63
- data/app/views/layouts/passkeys/rails/application.html.erb +0 -15
- data/lib/passkeys/rails/railtie.rb +0 -19
- data/lib/passkeys/rails/version.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1967aeb595e4864973402bf8108fe5a56b668678b8ed56aafbc403b164c628d
|
4
|
+
data.tar.gz: c96c2cd39bedbc138c1cd35248432d9cf7626c5a96007b91981741eb6f56e478
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3eb9182122196e3c94b5ebb78b0984785c39fb194b6d7f5f95432eb63e53482be0190fe24bc2af914a38377ee1a82df80932b11c16f918d155fdbaa731b5a052
|
7
|
+
data.tar.gz: e16475967c805796248fb3188667456e31fd833f841b98d16c28a6b5964e59170d349a018580a5e1ab312f18f7a9f7f16693b612d70d97f6ce91a5a77a9e5db4
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
[](https://badge.fury.io/rb/passkeys-rails)
|
2
2
|
[](https://travis-ci.org/alliedcode/passkeys-rails)
|
3
3
|
[](https://codecov.io/gh/alliedcode/passkeys-rails)
|
4
4
|
|
5
|
-
#
|
5
|
+
# PasskeysRails
|
6
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.
|
7
7
|
|
8
8
|
## Usage
|
9
9
|
rails passkeys-rails::install
|
10
|
-
|
10
|
+
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.
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
Add this line to your application's Gemfile:
|
@@ -34,7 +34,7 @@ Depending on your application's configuration some manual setup may be required:
|
|
34
34
|
|
35
35
|
before_action :authenticate_passkey!, except: [:index]
|
36
36
|
|
37
|
-
2. Optionally include
|
37
|
+
2. Optionally include PasskeysRails::Authenticatable to the model(s) you are using as
|
38
38
|
your user model(s). For example, the User model.
|
39
39
|
|
40
40
|
3. See the reference mobile applications for how to use passkeys-rails for passkey
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module PasskeysRails
|
2
|
+
module Authentication
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
rescue_from PasskeysRails::Error do |e|
|
7
|
+
render json: e.to_h, status: :unauthorized
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_agent
|
12
|
+
@current_agent ||= (request.headers['HTTP_X_AUTH'].present? &&
|
13
|
+
passkey_authentication_result.success? &&
|
14
|
+
passkey_authentication_result.agent.registered? &&
|
15
|
+
passkey_authentication_result.agent) || nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def authenticate_passkey!
|
19
|
+
return if current_agent.present?
|
20
|
+
|
21
|
+
raise PasskeysRails::Error.new(:authentication,
|
22
|
+
code: :unauthorized,
|
23
|
+
message: "You are not authorized to access this resource.")
|
24
|
+
end
|
25
|
+
|
26
|
+
def passkey_authentication_result
|
27
|
+
@passkey_authentication_result ||= PasskeysRails::ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
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
|
@@ -0,0 +1,61 @@
|
|
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,18 +1,20 @@
|
|
1
|
-
module
|
1
|
+
module PasskeysRails
|
2
2
|
class BeginRegistration
|
3
3
|
include Interactor
|
4
4
|
|
5
5
|
delegate :username, to: :context
|
6
6
|
|
7
7
|
def call
|
8
|
-
agent =
|
8
|
+
agent = create_or_replace_unregistered_agent
|
9
9
|
|
10
10
|
context.options = WebAuthn::Credential.options_for_create(user: { id: agent.webauthn_identifier, name: agent.username })
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
|
-
def
|
15
|
+
def create_or_replace_unregistered_agent
|
16
|
+
Agent.unregistered.where(username:).destroy_all
|
17
|
+
|
16
18
|
agent = Agent.create(username:, webauthn_identifier: WebAuthn.generate_user_id)
|
17
19
|
|
18
20
|
context.fail!(code: :validation_errors, message: agent.errors.full_messages.to_sentence) unless agent.valid?
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module PasskeysRails
|
2
2
|
class GenerateAuthToken
|
3
3
|
include Interactor
|
4
4
|
|
@@ -12,12 +12,12 @@ module Passkeys::Rails
|
|
12
12
|
|
13
13
|
def generate_auth_token
|
14
14
|
JWT.encode(jwt_payload,
|
15
|
-
|
16
|
-
|
15
|
+
PasskeysRails.auth_token_secret,
|
16
|
+
PasskeysRails.auth_token_algorithm)
|
17
17
|
end
|
18
18
|
|
19
19
|
def jwt_payload
|
20
|
-
expiration = (Time.current +
|
20
|
+
expiration = (Time.current + PasskeysRails.auth_token_expires_in).to_i
|
21
21
|
|
22
22
|
payload = { agent_id: agent.id }
|
23
23
|
payload[:exp] = expiration unless expiration.zero?
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module PasskeysRails
|
2
2
|
class ValidateAuthToken
|
3
3
|
include Interactor
|
4
4
|
|
@@ -21,9 +21,9 @@ module Passkeys::Rails
|
|
21
21
|
|
22
22
|
def payload
|
23
23
|
JWT.decode(auth_token,
|
24
|
-
|
24
|
+
PasskeysRails.auth_token_secret,
|
25
25
|
true,
|
26
|
-
{ required_claims: %w[exp agent_id], algorithm:
|
26
|
+
{ required_claims: %w[exp agent_id], algorithm: PasskeysRails.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,12 +1,12 @@
|
|
1
1
|
# These should be autoloaded, but if these aren't required here, apps using this
|
2
|
-
# gem will throw an exception that
|
3
|
-
require_relative '../../app/controllers/concerns/
|
4
|
-
require_relative '../../app/models/
|
2
|
+
# gem will throw an exception that PasskeysRails::Authentication can't be found
|
3
|
+
require_relative '../../app/controllers/concerns/passkeys_rails/authentication'
|
4
|
+
require_relative '../../app/models/passkeys_rails/error'
|
5
5
|
|
6
6
|
class ActionController::Base
|
7
|
-
include
|
7
|
+
include PasskeysRails::Authentication
|
8
8
|
end
|
9
9
|
|
10
10
|
class ActionController::API
|
11
|
-
include
|
11
|
+
include PasskeysRails::Authentication
|
12
12
|
end
|
data/config/routes.rb
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
require 'rails/generators'
|
2
2
|
|
3
|
-
module
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
source_root File.expand_path("templates", __dir__)
|
3
|
+
module PasskeysRails
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < ::Rails::Generators::Base
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
def copy_config
|
9
|
+
template 'passkeys_rails_config.rb', "config/initializers/passkeys_rails.rb"
|
10
|
+
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def add_routes
|
13
|
+
route 'mount PasskeysRails::Engine => "/passkeys_rails"'
|
14
|
+
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
16
|
+
def show_readme
|
17
|
+
readme "README" if behavior == :invoke
|
20
18
|
end
|
21
19
|
end
|
22
20
|
end
|
@@ -8,7 +8,7 @@ Depending on your application's configuration some manual setup may be required:
|
|
8
8
|
|
9
9
|
before_action :authenticate_passkey!, except: [:index]
|
10
10
|
|
11
|
-
2. Optionally include
|
11
|
+
2. Optionally include PasskeysRails::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
|
data/lib/passkeys-rails.rb
CHANGED
@@ -1,36 +1,34 @@
|
|
1
1
|
# rubocop:disable Naming/FileName
|
2
|
-
require '
|
3
|
-
require '
|
2
|
+
require 'passkeys_rails/engine'
|
3
|
+
require 'passkeys_rails/version'
|
4
4
|
require_relative "generators/passkeys_rails/install_generator"
|
5
5
|
|
6
|
-
module
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
mattr_accessor(:auth_token_secret)
|
6
|
+
module PasskeysRails
|
7
|
+
# Secret used to encode the auth token.
|
8
|
+
# Rails.application.secret_key_base is used if none is defined here.
|
9
|
+
# Changing this value will invalidate all tokens that have been fetched
|
10
|
+
# through the API.
|
11
|
+
mattr_accessor(:auth_token_secret)
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
# Algorithm used to generate the auth token.
|
14
|
+
# Changing this value will invalidate all tokens that have been fetched
|
15
|
+
# through the API.
|
16
|
+
mattr_accessor :auth_token_algorithm, default: "HS256"
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
# How long the auth token is valid before requiring a refresh or new login.
|
19
|
+
# Set it to 0 for no expiration (not recommended in production).
|
20
|
+
mattr_accessor :auth_token_expires_in, default: 30.days
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
22
|
+
class << self
|
23
|
+
def config
|
24
|
+
yield self
|
27
25
|
end
|
28
|
-
|
29
|
-
require 'passkeys/rails/railtie' if defined?(Rails)
|
30
26
|
end
|
27
|
+
|
28
|
+
require 'passkeys_rails/railtie' if defined?(Rails)
|
31
29
|
end
|
32
30
|
|
33
31
|
ActiveSupport.on_load(:before_initialize) do
|
34
|
-
|
32
|
+
PasskeysRails.auth_token_secret ||= Rails.application.secret_key_base
|
35
33
|
end
|
36
34
|
# rubocop:enable Naming/FileName
|
@@ -6,9 +6,9 @@ require "interactor"
|
|
6
6
|
require "jwt"
|
7
7
|
require "webauthn"
|
8
8
|
|
9
|
-
module
|
9
|
+
module PasskeysRails
|
10
10
|
class Engine < ::Rails::Engine
|
11
|
-
isolate_namespace
|
11
|
+
isolate_namespace PasskeysRails
|
12
12
|
|
13
13
|
config.generators do |g|
|
14
14
|
g.test_framework :rspec
|
@@ -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,14 +1,14 @@
|
|
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.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Troy Anderson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -351,25 +351,22 @@ 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/interactors/
|
360
|
-
- app/interactors/
|
361
|
-
- app/interactors/
|
362
|
-
- app/interactors/
|
363
|
-
- app/interactors/
|
364
|
-
- app/interactors/
|
365
|
-
- app/
|
366
|
-
- app/
|
367
|
-
- app/models/
|
368
|
-
- app/models/
|
369
|
-
- app/models/
|
370
|
-
- app/models/passkeys/rails/error.rb
|
371
|
-
- app/models/passkeys/rails/passkey.rb
|
372
|
-
- app/views/layouts/passkeys/rails/application.html.erb
|
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
|
373
370
|
- config/initializers/application_controller.rb
|
374
371
|
- config/routes.rb
|
375
372
|
- db/migrate/20230620012530_create_passkeys_rails_agents.rb
|
@@ -379,9 +376,9 @@ files:
|
|
379
376
|
- lib/generators/passkeys_rails/templates/README
|
380
377
|
- lib/generators/passkeys_rails/templates/passkeys_rails_config.rb
|
381
378
|
- lib/passkeys-rails.rb
|
382
|
-
- lib/
|
383
|
-
- lib/
|
384
|
-
- lib/
|
379
|
+
- lib/passkeys_rails/engine.rb
|
380
|
+
- lib/passkeys_rails/railtie.rb
|
381
|
+
- lib/passkeys_rails/version.rb
|
385
382
|
- lib/tasks/passkeys_rails_tasks.rake
|
386
383
|
homepage: https://github.com/alliedcode/passkeys-rails
|
387
384
|
licenses:
|
@@ -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,29 +0,0 @@
|
|
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
|
@@ -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 '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
|