passkeys-rails 0.1.1 → 0.1.3
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 +20 -0
- data/README.md +17 -2
- data/app/controllers/concerns/passkeys/rails/authentication.rb +29 -0
- data/app/controllers/passkeys/rails/application_controller.rb +24 -0
- data/app/controllers/passkeys/rails/passkeys_controller.rb +63 -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 +1 -1
- 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 +12 -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/engine.rb +21 -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 +26 -26
- 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/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: 84469b812d394ac75533be6f935292932f43fceab669d0842be1daa077abe7ee
|
4
|
+
data.tar.gz: 8430b3679760bc10af93da66835b549646e50bca94bb25fc008ec067a37e32b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 654a20573b11dae74154bc3b8576f7204d2694218732ec88fdb80004286d20dc8ef0426fbf2f186efc33645ff9ce8604e32cdf59cae9b77712af271255807103
|
7
|
+
data.tar.gz: cb5380598b67e0969ec619570729cea1e534a8e100942adbb9d4389ce9b1eba0937e35d359dd3d8eb2646965e5343519c5404eccefbe606fed1f5a25d9ca6b08
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
### 0.1.3
|
2
|
+
|
3
|
+
* More restructuring and fixed issue where autoloading failed
|
4
|
+
during client app initialization.
|
5
|
+
|
6
|
+
### 0.1.2
|
7
|
+
|
8
|
+
* Restructured lib directory.
|
9
|
+
|
10
|
+
* Fixed naming convention for gem/gemspec.
|
11
|
+
|
12
|
+
* Fixed exception handling.
|
13
|
+
|
14
|
+
### 0.1.1
|
15
|
+
|
16
|
+
* Fixed dependency
|
17
|
+
|
18
|
+
### 0.1.0
|
19
|
+
|
20
|
+
* Initial release - looking for feedback
|
data/README.md
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
+
[](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,29 @@
|
|
1
|
+
module Passkeys::Rails
|
2
|
+
module Authentication
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
rescue_from Passkeys::Rails::Error do |e|
|
7
|
+
render json: e.to_h, status: :unauthorized
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_agent
|
12
|
+
return nil if request.headers['HTTP_X_AUTH'].blank?
|
13
|
+
|
14
|
+
@current_agent ||= validated_auth_token&.success? && validated_auth_token&.agent
|
15
|
+
end
|
16
|
+
|
17
|
+
def authenticate_passkey!
|
18
|
+
return if validated_auth_token.success?
|
19
|
+
|
20
|
+
raise Passkeys::Rails::Error.new(:authentication,
|
21
|
+
code: :unauthorized,
|
22
|
+
message: "You are not authorized to access this resource.")
|
23
|
+
end
|
24
|
+
|
25
|
+
def validated_auth_token
|
26
|
+
@validated_auth_token ||= Passkeys::Rails::ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Passkeys
|
2
|
+
module Rails
|
3
|
+
class ApplicationController < ActionController::Base
|
4
|
+
rescue_from ::Interactor::Failure, with: :handle_interactor_failure
|
5
|
+
rescue_from ActionController::ParameterMissing, with: :handle_missing_parameter
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def handle_missing_parameter(error)
|
10
|
+
render_error(:authentication, 'missing_parameter', error.message)
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle_interactor_failure(failure)
|
14
|
+
render_error(:authentication, failure.context.code, failure.context.message)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def render_error(context, code, message, status: :unprocessable_entity)
|
20
|
+
render json: { error: { context:, code:, message: } }, status:
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Passkeys
|
2
|
+
module Rails
|
3
|
+
class PasskeysController < ApplicationController
|
4
|
+
def challenge
|
5
|
+
result = Passkeys::Rails::BeginChallenge.call!(username: challenge_params[:username])
|
6
|
+
|
7
|
+
# Store the challenge so we can verify the future register or authentication request
|
8
|
+
session[:passkeys_rails] = result.session_data
|
9
|
+
|
10
|
+
render json: result.response.as_json
|
11
|
+
end
|
12
|
+
|
13
|
+
def register
|
14
|
+
result = Passkeys::Rails::FinishRegistration.call!(credential: attestation_credential_params.to_h,
|
15
|
+
authenticatable_class:,
|
16
|
+
username: session.dig(:passkeys_rails, :username),
|
17
|
+
challenge: session.dig(:passkeys_rails, :challenge))
|
18
|
+
|
19
|
+
render json: { username: result.username, auth_token: result.auth_token }
|
20
|
+
end
|
21
|
+
|
22
|
+
def authenticate
|
23
|
+
result = Passkeys::Rails::FinishAuthentication.call!(credential: authentication_params.to_h,
|
24
|
+
challenge: session.dig(:passkeys_rails, :challenge))
|
25
|
+
|
26
|
+
render json: { username: result.username, auth_token: result.auth_token }
|
27
|
+
end
|
28
|
+
|
29
|
+
def refresh
|
30
|
+
result = Passkeys::Rails::RefreshToken.call!(token: refresh_params[:auth_token])
|
31
|
+
render json: { username: result.username, auth_token: result.auth_token }
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def challenge_params
|
37
|
+
params.permit(:username)
|
38
|
+
end
|
39
|
+
|
40
|
+
def attestation_credential_params
|
41
|
+
credential = params.require(:credential)
|
42
|
+
credential.require(%i[id rawId type response])
|
43
|
+
credential.require(:response).require(%i[attestationObject clientDataJSON])
|
44
|
+
credential.permit(:id, :rawId, :type, { response: %i[attestationObject clientDataJSON] })
|
45
|
+
end
|
46
|
+
|
47
|
+
def authenticatable_class
|
48
|
+
params[:authenticatable_class]
|
49
|
+
end
|
50
|
+
|
51
|
+
def authentication_params
|
52
|
+
params.require(%i[id rawId type response])
|
53
|
+
params.require(:response).require(%i[authenticatorData clientDataJSON signature userHandle])
|
54
|
+
params.permit(:id, :rawId, :type, { response: %i[authenticatorData clientDataJSON signature userHandle] })
|
55
|
+
end
|
56
|
+
|
57
|
+
def refresh_params
|
58
|
+
params.require(:auth_token)
|
59
|
+
params.permit(:auth_token)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module Passkeys::Rails
|
2
2
|
class GenerateAuthToken
|
3
3
|
include Interactor
|
4
4
|
|
@@ -12,12 +12,12 @@ module PasskeysRails
|
|
12
12
|
|
13
13
|
def generate_auth_token
|
14
14
|
JWT.encode(jwt_payload,
|
15
|
-
|
16
|
-
|
15
|
+
Passkeys::Rails.auth_token_secret,
|
16
|
+
Passkeys::Rails.auth_token_algorithm)
|
17
17
|
end
|
18
18
|
|
19
19
|
def jwt_payload
|
20
|
-
expiration = (Time.current +
|
20
|
+
expiration = (Time.current + Passkeys::Rails.auth_token_expires_in).to_i
|
21
21
|
|
22
22
|
payload = { agent_id: agent.id }
|
23
23
|
payload[:exp] = expiration unless expiration.zero?
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module Passkeys::Rails
|
2
2
|
class ValidateAuthToken
|
3
3
|
include Interactor
|
4
4
|
|
@@ -21,9 +21,9 @@ module PasskeysRails
|
|
21
21
|
|
22
22
|
def payload
|
23
23
|
JWT.decode(auth_token,
|
24
|
-
|
24
|
+
Passkeys::Rails.auth_token_secret,
|
25
25
|
true,
|
26
|
-
{ required_claims: %w[exp agent_id], algorithm:
|
26
|
+
{ required_claims: %w[exp agent_id], algorithm: Passkeys::Rails.auth_token_algorithm }).first
|
27
27
|
rescue JWT::ExpiredSignature
|
28
28
|
context.fail!(code: :expired_token, message: "The token has expired")
|
29
29
|
rescue StandardError => e
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# These should be autoloaded, but if these aren't required here, apps using this
|
2
|
+
# gem will throw an exception that Passkeys::Rails::Authentication can't be found
|
3
|
+
require_relative '../../app/controllers/concerns/passkeys/rails/authentication'
|
4
|
+
require_relative '../../app/models/passkeys/rails/error'
|
5
|
+
|
6
|
+
class ActionController::Base
|
7
|
+
include Passkeys::Rails::Authentication
|
8
|
+
end
|
9
|
+
|
10
|
+
class ActionController::API
|
11
|
+
include Passkeys::Rails::Authentication
|
12
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,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,21 @@
|
|
1
|
+
require "rails"
|
2
|
+
require "active_support/core_ext/numeric/time"
|
3
|
+
require "active_support/dependencies"
|
4
|
+
|
5
|
+
require "interactor"
|
6
|
+
require "jwt"
|
7
|
+
require "webauthn"
|
8
|
+
|
9
|
+
module Passkeys::Rails
|
10
|
+
class Engine < ::Rails::Engine
|
11
|
+
isolate_namespace Passkeys::Rails
|
12
|
+
|
13
|
+
config.generators do |g|
|
14
|
+
g.test_framework :rspec
|
15
|
+
g.fixture_replacement :factory_bot
|
16
|
+
g.factory_bot dir: 'spec/factories'
|
17
|
+
g.assets false
|
18
|
+
g.helper false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'passkeys-rails'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Passkeys
|
5
|
+
module Rails
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
railtie_name :passkeys_rails
|
8
|
+
|
9
|
+
rake_tasks do
|
10
|
+
path = File.expand_path(__dir__)
|
11
|
+
Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
|
12
|
+
end
|
13
|
+
|
14
|
+
generators do
|
15
|
+
require "generators/passkeys_rails/install_generator"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# rubocop:disable Naming/FileName
|
2
|
+
require 'passkeys/rails/engine'
|
3
|
+
require 'passkeys/rails/version'
|
4
|
+
require_relative "generators/passkeys_rails/install_generator"
|
5
|
+
|
6
|
+
module Passkeys
|
7
|
+
module Rails
|
8
|
+
# Secret used to encode the auth token.
|
9
|
+
# Rails.application.secret_key_base is used if none is defined here.
|
10
|
+
# Changing this value will invalidate all tokens that have been fetched
|
11
|
+
# through the API.
|
12
|
+
mattr_accessor(:auth_token_secret)
|
13
|
+
|
14
|
+
# Algorithm used to generate the auth token.
|
15
|
+
# Changing this value will invalidate all tokens that have been fetched
|
16
|
+
# through the API.
|
17
|
+
mattr_accessor :auth_token_algorithm, default: "HS256"
|
18
|
+
|
19
|
+
# How long the auth token is valid before requiring a refresh or new login.
|
20
|
+
# Set it to 0 for no expiration (not recommended in production).
|
21
|
+
mattr_accessor :auth_token_expires_in, default: 30.days
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def config
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'passkeys/rails/railtie' if defined?(Rails)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveSupport.on_load(:before_initialize) do
|
34
|
+
Passkeys::Rails.auth_token_secret ||= Rails.application.secret_key_base
|
35
|
+
end
|
36
|
+
# rubocop:enable Naming/FileName
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: passkeys-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Troy Anderson
|
@@ -338,7 +338,7 @@ dependencies:
|
|
338
338
|
- - "~>"
|
339
339
|
- !ruby/object:Gem::Version
|
340
340
|
version: 2.22.0
|
341
|
-
description: Devise is awesome, but we don't need all that UI/UX for PassKeys.
|
341
|
+
description: Devise is awesome, but we don't need all that UI/UX for PassKeys. This
|
342
342
|
gem is to make it easy to provide a back end that authenticates a mobile front end
|
343
343
|
with PassKeys.
|
344
344
|
email:
|
@@ -347,29 +347,30 @@ executables: []
|
|
347
347
|
extensions: []
|
348
348
|
extra_rdoc_files: []
|
349
349
|
files:
|
350
|
+
- CHANGELOG.md
|
350
351
|
- MIT-LICENSE
|
351
352
|
- README.md
|
352
353
|
- Rakefile
|
353
354
|
- app/assets/config/passkeys_rails_manifest.js
|
354
355
|
- app/assets/stylesheets/passkeys_rails/application.css
|
355
|
-
- app/controllers/
|
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/interactors/
|
366
|
-
- app/
|
367
|
-
- app/models/
|
368
|
-
- app/models/
|
369
|
-
- app/models/
|
370
|
-
- app/models/
|
371
|
-
- app/
|
372
|
-
-
|
356
|
+
- app/controllers/concerns/passkeys/rails/authentication.rb
|
357
|
+
- app/controllers/passkeys/rails/application_controller.rb
|
358
|
+
- app/controllers/passkeys/rails/passkeys_controller.rb
|
359
|
+
- app/interactors/passkeys/rails/begin_authentication.rb
|
360
|
+
- app/interactors/passkeys/rails/begin_challenge.rb
|
361
|
+
- app/interactors/passkeys/rails/begin_registration.rb
|
362
|
+
- app/interactors/passkeys/rails/finish_authentication.rb
|
363
|
+
- app/interactors/passkeys/rails/finish_registration.rb
|
364
|
+
- app/interactors/passkeys/rails/generate_auth_token.rb
|
365
|
+
- app/interactors/passkeys/rails/refresh_token.rb
|
366
|
+
- app/interactors/passkeys/rails/validate_auth_token.rb
|
367
|
+
- app/models/concerns/passkeys/rails/authenticatable.rb
|
368
|
+
- app/models/passkeys/rails/agent.rb
|
369
|
+
- app/models/passkeys/rails/application_record.rb
|
370
|
+
- app/models/passkeys/rails/error.rb
|
371
|
+
- app/models/passkeys/rails/passkey.rb
|
372
|
+
- app/views/layouts/passkeys/rails/application.html.erb
|
373
|
+
- config/initializers/application_controller.rb
|
373
374
|
- config/routes.rb
|
374
375
|
- db/migrate/20230620012530_create_passkeys_rails_agents.rb
|
375
376
|
- db/migrate/20230620012600_create_passkeys_rails_passkeys.rb
|
@@ -377,11 +378,10 @@ files:
|
|
377
378
|
- lib/generators/passkeys_rails/install_generator.rb
|
378
379
|
- lib/generators/passkeys_rails/templates/README
|
379
380
|
- lib/generators/passkeys_rails/templates/passkeys_rails_config.rb
|
380
|
-
- lib/
|
381
|
-
- lib/
|
382
|
-
- lib/
|
383
|
-
- lib/
|
384
|
-
- lib/passkeys_rails/version.rb
|
381
|
+
- lib/passkeys-rails.rb
|
382
|
+
- lib/passkeys/rails/engine.rb
|
383
|
+
- lib/passkeys/rails/railtie.rb
|
384
|
+
- lib/passkeys/rails/version.rb
|
385
385
|
- lib/tasks/passkeys_rails_tasks.rake
|
386
386
|
homepage: https://github.com/alliedcode/passkeys-rails
|
387
387
|
licenses:
|
@@ -406,7 +406,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
406
406
|
- !ruby/object:Gem::Version
|
407
407
|
version: '0'
|
408
408
|
requirements: []
|
409
|
-
rubygems_version: 3.4.
|
409
|
+
rubygems_version: 3.4.17
|
410
410
|
signing_key:
|
411
411
|
specification_version: 4
|
412
412
|
summary: PassKey authentication back end with simple API
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module PasskeysRails
|
2
|
-
class ApplicationController < ActionController::Base
|
3
|
-
rescue_from ::Interactor::Failure, with: :handle_interactor_failure
|
4
|
-
rescue_from ActionController::ParameterMissing, with: :handle_missing_parameter
|
5
|
-
|
6
|
-
protected
|
7
|
-
|
8
|
-
def handle_missing_parameter(error)
|
9
|
-
render_error(:authentication, 'missing_parameter', error.message)
|
10
|
-
end
|
11
|
-
|
12
|
-
def handle_interactor_failure(failure)
|
13
|
-
render_error(:authentication, failure.context.code, failure.context.message)
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def render_error(context, code, message, status: :unprocessable_entity)
|
19
|
-
render json: { error: { context:, code:, message: } }, status:
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
module PasskeysRails
|
2
|
-
class PasskeysController < ApplicationController
|
3
|
-
def challenge
|
4
|
-
result = PasskeysRails::BeginChallenge.call!(username: challenge_params[:username])
|
5
|
-
|
6
|
-
# Store the challenge so we can verify the future register or authentication request
|
7
|
-
session[:passkeys_rails] = result.session_data
|
8
|
-
|
9
|
-
render json: result.response.as_json
|
10
|
-
end
|
11
|
-
|
12
|
-
def register
|
13
|
-
result = PasskeysRails::FinishRegistration.call!(credential: attestation_credential_params.to_h,
|
14
|
-
authenticatable_class:,
|
15
|
-
username: session.dig(:passkeys_rails, :username),
|
16
|
-
challenge: session.dig(:passkeys_rails, :challenge))
|
17
|
-
|
18
|
-
render json: { username: result.username, auth_token: result.auth_token }
|
19
|
-
end
|
20
|
-
|
21
|
-
def authenticate
|
22
|
-
result = PasskeysRails::FinishAuthentication.call!(credential: authentication_params.to_h,
|
23
|
-
challenge: session.dig(:passkeys_rails, :challenge))
|
24
|
-
|
25
|
-
render json: { username: result.username, auth_token: result.auth_token }
|
26
|
-
end
|
27
|
-
|
28
|
-
def refresh
|
29
|
-
result = PasskeysRails::RefreshToken.call!(token: refresh_params[:auth_token])
|
30
|
-
render json: { username: result.username, auth_token: result.auth_token }
|
31
|
-
end
|
32
|
-
|
33
|
-
protected
|
34
|
-
|
35
|
-
def challenge_params
|
36
|
-
params.permit(:username)
|
37
|
-
end
|
38
|
-
|
39
|
-
def attestation_credential_params
|
40
|
-
credential = params.require(:credential)
|
41
|
-
credential.require(%i[id rawId type response])
|
42
|
-
credential.require(:response).require(%i[attestationObject clientDataJSON])
|
43
|
-
credential.permit(:id, :rawId, :type, { response: %i[attestationObject clientDataJSON] })
|
44
|
-
end
|
45
|
-
|
46
|
-
def authenticatable_class
|
47
|
-
params[:authenticatable_class]
|
48
|
-
end
|
49
|
-
|
50
|
-
def authentication_params
|
51
|
-
params.require(%i[id rawId type response])
|
52
|
-
params.require(:response).require(%i[authenticatorData clientDataJSON signature userHandle])
|
53
|
-
params.permit(:id, :rawId, :type, { response: %i[authenticatorData clientDataJSON signature userHandle] })
|
54
|
-
end
|
55
|
-
|
56
|
-
def refresh_params
|
57
|
-
params.require(:auth_token)
|
58
|
-
params.permit(:auth_token)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module PasskeysRails
|
2
|
-
module ApplicationHelper
|
3
|
-
def current_agent
|
4
|
-
return nil if request.headers['HTTP_X_AUTH'].blank?
|
5
|
-
|
6
|
-
@current_agent ||= validated_auth_token&.success? && validated_auth_token&.agent
|
7
|
-
end
|
8
|
-
|
9
|
-
def authenticate!
|
10
|
-
return if validated_auth_token.success?
|
11
|
-
|
12
|
-
raise PasskeysRails::Error.new(:authentication,
|
13
|
-
code: :unauthorized,
|
14
|
-
message: "You are not authorized to access this resource.")
|
15
|
-
end
|
16
|
-
|
17
|
-
def validated_auth_token
|
18
|
-
@validated_auth_token ||= ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require_relative 'error_middleware'
|
2
|
-
module PasskeysRails
|
3
|
-
class Engine < ::Rails::Engine
|
4
|
-
isolate_namespace PasskeysRails
|
5
|
-
|
6
|
-
config.generators do |g|
|
7
|
-
g.test_framework :rspec
|
8
|
-
g.fixture_replacement :factory_bot
|
9
|
-
g.factory_bot dir: 'spec/factories'
|
10
|
-
g.assets false
|
11
|
-
g.helper false
|
12
|
-
end
|
13
|
-
|
14
|
-
config.to_prepare do
|
15
|
-
# include our helper methods in the host application's ApplicationController
|
16
|
-
::ApplicationController.include ApplicationHelper
|
17
|
-
end
|
18
|
-
|
19
|
-
# provide a way to bail out of the render flow if needed
|
20
|
-
initializer 'passkeys_rails.configure.middleware' do |app|
|
21
|
-
app.middleware.use ErrorMiddleware
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module PasskeysRails
|
2
|
-
class ErrorMiddleware
|
3
|
-
def initialize(app)
|
4
|
-
@app = app
|
5
|
-
end
|
6
|
-
|
7
|
-
def call(env)
|
8
|
-
begin
|
9
|
-
response = @app.call(env)
|
10
|
-
rescue Error => e
|
11
|
-
return [401, { 'Content-Type' => 'application/json' }, e.to_h.to_json]
|
12
|
-
end
|
13
|
-
|
14
|
-
response
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
require 'passkeys_rails'
|
2
|
-
require 'rails'
|
3
|
-
|
4
|
-
module PasskeysRails
|
5
|
-
class Railtie < Rails::Railtie
|
6
|
-
railtie_name :passkeys_rails
|
7
|
-
|
8
|
-
rake_tasks do
|
9
|
-
path = File.expand_path(__dir__)
|
10
|
-
Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
|
11
|
-
end
|
12
|
-
|
13
|
-
generators do
|
14
|
-
require "generators/passkeys_rails/install_generator"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
data/lib/passkeys_rails.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require_relative "passkeys_rails/version"
|
2
|
-
require_relative "passkeys_rails/engine"
|
3
|
-
require_relative "generators/passkeys_rails/install_generator"
|
4
|
-
require "rails"
|
5
|
-
require "active_support/core_ext/numeric/time"
|
6
|
-
require "active_support/dependencies"
|
7
|
-
require "interactor"
|
8
|
-
require "jwt"
|
9
|
-
require "webauthn"
|
10
|
-
|
11
|
-
module PasskeysRails
|
12
|
-
# Secret used to encode the auth token.
|
13
|
-
# Rails.application.secret_key_base is used if none is defined here.
|
14
|
-
# Changing this value will invalidate all tokens that have been fetched
|
15
|
-
# through the API.
|
16
|
-
mattr_accessor(:auth_token_secret)
|
17
|
-
|
18
|
-
# Algorithm used to generate the auth token.
|
19
|
-
# Changing this value will invalidate all tokens that have been fetched
|
20
|
-
# through the API.
|
21
|
-
mattr_accessor :auth_token_algorithm, default: "HS256"
|
22
|
-
|
23
|
-
# How long the auth token is valid before requiring a refresh or new login.
|
24
|
-
# Set it to 0 for no expiration (not recommended in production).
|
25
|
-
mattr_accessor :auth_token_expires_in, default: 30.days
|
26
|
-
|
27
|
-
class << self
|
28
|
-
def config
|
29
|
-
yield self
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
require 'passkeys_rails/railtie' if defined?(Rails)
|
34
|
-
end
|
35
|
-
|
36
|
-
ActiveSupport.on_load(:before_initialize) do
|
37
|
-
PasskeysRails.auth_token_secret ||= Rails.application.secret_key_base
|
38
|
-
end
|
File without changes
|