passkeys-rails 0.1.5 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1967aeb595e4864973402bf8108fe5a56b668678b8ed56aafbc403b164c628d
4
- data.tar.gz: c96c2cd39bedbc138c1cd35248432d9cf7626c5a96007b91981741eb6f56e478
3
+ metadata.gz: 631e7354b6296e5fcc14144ba3bd305ca9047c31f9e5b0173f5eb7123f14fa71
4
+ data.tar.gz: 65e1f2d4cbd179ddee7449318b49a838d166db64db9221e1f079ac7e57735bf3
5
5
  SHA512:
6
- metadata.gz: 3eb9182122196e3c94b5ebb78b0984785c39fb194b6d7f5f95432eb63e53482be0190fe24bc2af914a38377ee1a82df80932b11c16f918d155fdbaa731b5a052
7
- data.tar.gz: e16475967c805796248fb3188667456e31fd833f841b98d16c28a6b5964e59170d349a018580a5e1ab312f18f7a9f7f16693b612d70d97f6ce91a5a77a9e5db4
6
+ metadata.gz: f8799f782713ae01ca312506c39ef2bed3a56448fb0b4e78d4c75a441b4a3fbac731a5bcf3f23421f9b240e84346f80028bd4545c8edbc14b4ed67769e92e3ef
7
+ data.tar.gz: 23ce3eab9360b27dd23732589dc3794bb6c6c8cfe6ca9bf289830265ad8697ce0a2779836c0b4ff37c2a5c3211dffaaf74e817e63d0bc96417ad62af8048e444
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ### 0.1.7
2
+
3
+ * Added IntegrationHelpers to support client testing.
4
+ * Updated methods for interfacing with Rails client app.
5
+ * Changed route path added by the generator.
6
+
7
+ ### 0.1.6
8
+
9
+ * Added default_class and class_whitelist config parameters.
10
+
11
+ ### 0.1.5
12
+
13
+ * Updated validation to ensure the agent has completed registration to be considered valid.
14
+
1
15
  ### 0.1.4
2
16
 
3
17
  * Changed namespace from Passkeys::Rails to PasskeysRails
data/README.md CHANGED
@@ -1,15 +1,38 @@
1
- [![Gem Version](https://badge.fury.io/rb/passkeys-rails.svg?cachebust=5)](https://badge.fury.io/rb/passkeys-rails)
1
+ [![Gem Version](https://badge.fury.io/rb/passkeys-rails.svg?cachebust=7)](https://badge.fury.io/rb/passkeys-rails)
2
2
  [![Build Status](https://app.travis-ci.com/alliedcode/passkeys-rails.svg?branch=main)](https://travis-ci.org/alliedcode/passkeys-rails)
3
3
  [![codecov](https://codecov.io/gh/alliedcode/passkeys-rails/branch/main/graph/badge.svg?token=UHSNJDUL21)](https://codecov.io/gh/alliedcode/passkeys-rails)
4
4
 
5
5
  # PasskeysRails
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
+ Devise is awesome, but we don't need all that UI/UX for PassKeys.
8
+
9
+ The purpose of this gem is to make it easy to provide a rails back end API that supports PassKey authentication. It uses the [`webauthn`](https://github.com/w3c/webauthn) gem to do the cryptographic work and presents a simple API interface for passkey registration and authentication.
10
+
11
+ The target use case for this gem is a mobile application that uses a rails based API service to manage resources. The goal is to make it simple to register and authenticate users using passkeys from mobile applications in a rails API service.
12
+
7
13
 
8
14
  ## Usage
9
- rails passkeys-rails::install
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.
15
+
16
+ **PasskeysRails** maintains an `Agent` model and related `Passkeys`. 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 or set the `PasskeysRails.default_class` to the name of that class.
17
+
18
+ ### Optionally providing a **"user"** model during registration
19
+
20
+ **PasskeysRails** does not require that you supply your own model, but it's often useful to do so. For example, if you have a User model that you would like to have created at registration, you can supply the model name in the `finishRegistration` API call.
21
+
22
+ **PasskeysRails** supports multiple `"user"` models. Whatever model name you supply will be created during a successful the `finishRegiration` API call. When created, it will be provided an opportunity to do any initialization at that time.
23
+
24
+ There are two **PasskeysRails** configuration options related to this: `default_class` and `class_whitelist` - see below.
25
+
26
+ #### `default_class`
27
+
28
+ Configure `default_class` in `passkeys_rails.rb`. Its value will be used during registration if none is provided in the API call. The default value is `"User"`. Since the `default_class` is just a default, it can be overridden in the `finishRegiration` API call to use a different model. If no model is to be used by default, set it to nil.
29
+
30
+ #### `class_whitelist`
31
+
32
+ Configure `class_whitelist` in `passkeys_rails.rb`. The default value is `nil`. When `nil`, no whitelist will be applied. If it is non-nil, it should be an array of class names that are allowed during registration. Supply an empty array to prevent **PasskeysRails** from attempting to create anything other than its own `PasskeysRails::Agent` during registration.
11
33
 
12
34
  ## Installation
35
+
13
36
  Add this line to your application's Gemfile:
14
37
 
15
38
  ```ruby
@@ -17,8 +40,9 @@ gem "passkeys_rails"
17
40
  ```
18
41
 
19
42
  And then execute:
43
+
20
44
  ```bash
21
- $ bundle
45
+ $ bundle install
22
46
  ```
23
47
 
24
48
  Or install it yourself as:
@@ -26,22 +50,139 @@ Or install it yourself as:
26
50
  $ gem install passkeys_rails
27
51
  ```
28
52
 
29
- Depending on your application's configuration some manual setup may be required:
53
+ Finally, execute:
54
+
55
+ ```bash
56
+ $ rails generate passkeys_rails:install
57
+ ```
58
+
59
+ This will add the `passkeys_rails.rb` configuration file, passkeys routes, and a couple of database migrations to your project.
60
+
61
+ ### Adding to an standard rails project
62
+
63
+ 1. Add `before_action :authenticate_passkey!`
64
+
65
+ To prevent access to controller actions, add `before_action :authenticate_passkey!`. If an action is attempted without an authenticated entity, an error will be rendered in JSON with an :unauthorized result code.
66
+
67
+ 1. Use `current_agent` and `current_agent.authenticatable`
68
+
69
+ To access the currently authenticated entity, use `current_agent`. If you associated the registration of the agent with one of your own models, use `current_agent.authenticatable`. For example, if you associated the `User` class with the registration, `current_agent.authenticatable` will be a User object.
30
70
 
31
- 1. Add a before_action to all controllers that require authentication to use.
71
+ 1. Add `include PasskeysRails::Authenticatable` to model class(es)
32
72
 
33
- For example:
73
+ If you have one or more classes that you want to use with authentication - e.g. a User class and an AdminUser class - add `include PasskeysRails::Authenticatable` to each of those classes. That adds a `registered?` method that you can call on your model to determine if they are registerd with your service, and a `registering_with(params)` method that you can override to initialize attributes of your model when it is created during registration. `params` is a hash with params passed to the API when registering. When called, your object has been built, but not yet saved. Upon return, **PasskeysRails** will attempt to save your object before finishing registration. If it is not valid, the registration will fail as well, returning the error error details to the caller.
34
74
 
35
- before_action :authenticate_passkey!, except: [:index]
75
+ ### Adding to a Grape API rails project
36
76
 
37
- 2. Optionally include PasskeysRails::Authenticatable to the model(s) you are using as
38
- your user model(s). For example, the User model.
77
+ 1. Call `PasskeysRails.authenticate(request)` to authenticate the request.
39
78
 
40
- 3. See the reference mobile applications for how to use passkeys-rails for passkey
41
- authentication.
79
+ Call `PasskeysRails.authenticate(request)` to get an object back that responds to `.success?` and `.failure?` as well as `.agent`, `.code`, and `.message`.
80
+
81
+ Alternatively, call `PasskeysRails.authenticate!(request)` from a helper in your base class. It will raise a `PasskeysRails.Error` exception if the caller isn't authenticated. You can catch the exception and render an appropriate error. The exception contains the error code and message.
82
+
83
+ 1. Consider adding the following helpers to your base API class:
84
+
85
+ ```ruby
86
+ helpers do
87
+ # Authenticate the request and cache the result
88
+ def passkey
89
+ @passkey ||= PasskeysRails.authenticate(request)
90
+ end
91
+
92
+ # Raise an exception if the request is not authentic
93
+ def authenticate_passkey!
94
+ error!({ code: passkey.code, message: passkey.message }, :unauthorized) if passkey.failure?
95
+ end
96
+
97
+ # Return the Passkeys::Agent if authentic, else return nil
98
+ def current_agent
99
+ passkey.agent
100
+ end
101
+
102
+ # If you have set authenticatable to be a User, you can use this to access the user from Grape endpoint methods
103
+ def current_user
104
+ user = current_agent&.authenticatable
105
+ user.is_a?(User) ? user : nil # sanity check to be sure authenticatable is a User
106
+ end
107
+ end
108
+ ```
109
+
110
+ To prevent access to various endpoints, add `before_action :authenticate_passkey!` or call `authenticate_passkey!` from any method that requires authentication. If an action is attempted without an authenticated entity, an error will be rendered in JSON with an :unauthorized result code.
111
+
112
+ 1. Use `current_agent` and `current_agent.authenticatable`
113
+
114
+ To access the currently authenticated entity, use `current_agent`. If you associated the registration of the agent with one of your own models, use `current_agent.authenticatable`. For example, if you associated the `User` class with the registration, `current_agent.authenticatable` will be a User object.
115
+
116
+ ### Authentication Failure
117
+
118
+ 1. In the event of authentication failure, PasskeysRails returns an error code and message.
119
+
120
+ 1. In a standard rails controller, the error code and message are rendered in JSON if `before_action :authenticate_passkey!` fails.
121
+
122
+ 1. In Grape, the error code and message are available in the result of the `PasskeysRails.authenticate(request)` method.
123
+
124
+ 1. From standard rails controllers, you can also access `passkey_authentication_result` to get the code and message.
125
+
126
+ 1. For `PasskeysRails.authenticate(request)` and `passkey_authentication_result`, the result is an object that respods to `.success?` and `.failure?`.
127
+ - When `.success?` is true (`.failure?` is false), the resources is authentic and it also responds to `.agent`, returning a PasskeysRails::Agent
128
+ - When `.success?` is false (`.failure?` is true), it responds to `.code` and `.message` to expose the error details.
129
+ - When `.code` is `:missing_token`, `.message` is **X-Auth header is required**, which means the caller didn't supply the auth header.
130
+ - When `.code` is `:invalid_token`, `.message` is **Invalid token - no agent exists with agent_id**, which means that the auth data is not valid.
131
+ - When `.code` is `:expired_token`, `.message` is **The token has expired**, which means that the token is valid, but expired, thuis it's not considered authentic.
132
+ - When `.code` is `:token_error`, `.message` is a description of the error. This is a catch-all in the event we are unable to decode the token.
133
+
134
+ In the future, the intention is to have the `.code` value stay consistent even if the `.message` changes. This also allows you to localize the messages as need using the code.
135
+
136
+ ### Test Helpers
137
+
138
+ PasskeysRails includes some test helpers for integration tests. In order to use them, you need to include the module in your test cases/specs.
139
+
140
+ ### Integration tests
141
+
142
+ Integration test helpers are available by including the `PasskeysRails::IntegrationHelpers` module.
143
+
144
+ ```ruby
145
+ class PostTests < ActionDispatch::IntegrationTest
146
+ include PasskeysRails::Test::IntegrationHelpers
147
+ end
148
+ ```
149
+ Now you can use the following `logged_in_headers` method in your integration tests.`
150
+
151
+ ```ruby
152
+ test 'authenticated users can see posts' do
153
+ user = User.create
154
+ get '/posts', headers: logged_in_headers('username-123', user)
155
+ assert_response :success
156
+ end
157
+ ```
158
+
159
+ RSpec can include the `IntegrationHelpers` module in their `:feature` and `:request` specs.
160
+
161
+ ```ruby
162
+ RSpec.configure do |config|
163
+ config.include PasskeysRails::Test::IntegrationHelpers, type: :feature
164
+ config.include PasskeysRails::Test::IntegrationHelpers, type: :request
165
+ end
166
+ ```
167
+
168
+ ```ruby
169
+ RSpec.describe 'Posts', type: :request do
170
+ let(:user) { User.create }
171
+ it "allows authenticated users to see posts" do
172
+ get '/posts', headers: logged_in_headers('username-123', user)
173
+ expect(response).to be_success
174
+ end
175
+ end
176
+ ```
177
+
178
+ ### Mobile Application Integration
179
+
180
+ **TODO**: Describe the APIs and point to the soon-to-be-created reference mobile applications for how to use **passkeys-rails** for passkey authentication.
42
181
 
43
182
  ## Contributing
44
- Contribution directions go here.
183
+
184
+ Contact me if you'd like to contribute time, energy, etc. to this project.
45
185
 
46
186
  ## License
187
+
47
188
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -9,22 +9,17 @@ module PasskeysRails
9
9
  end
10
10
 
11
11
  def current_agent
12
- @current_agent ||= (request.headers['HTTP_X_AUTH'].present? &&
13
- passkey_authentication_result.success? &&
12
+ @current_agent ||= (passkey_authentication_result.success? &&
14
13
  passkey_authentication_result.agent.registered? &&
15
14
  passkey_authentication_result.agent) || nil
16
15
  end
17
16
 
18
17
  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.")
18
+ @authenticate_passkey ||= PasskeysRails.authenticate!(request)
24
19
  end
25
20
 
26
21
  def passkey_authentication_result
27
- @passkey_authentication_result ||= PasskeysRails::ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
22
+ @passkey_authentication_result ||= PasskeysRails.authenticate(request)
28
23
  end
29
24
  end
30
25
  end
@@ -11,7 +11,7 @@ module PasskeysRails
11
11
 
12
12
  def register
13
13
  result = PasskeysRails::FinishRegistration.call!(credential: attestation_credential_params.to_h,
14
- authenticatable_class:,
14
+ authenticatable_info: authenticatable_info&.to_h,
15
15
  username: session.dig(:passkeys_rails, :username),
16
16
  challenge: session.dig(:passkeys_rails, :challenge))
17
17
 
@@ -43,8 +43,8 @@ module PasskeysRails
43
43
  credential.permit(:id, :rawId, :type, { response: %i[attestationObject clientDataJSON] })
44
44
  end
45
45
 
46
- def authenticatable_class
47
- params[:authenticatable_class]
46
+ def authenticatable_info
47
+ params.require[:authenticatable].permit(:class, :params) if params[:authenticatable].present?
48
48
  end
49
49
 
50
50
  def authentication_params
@@ -3,7 +3,7 @@ module PasskeysRails
3
3
  class FinishRegistration
4
4
  include Interactor
5
5
 
6
- delegate :credential, :username, :challenge, :authenticatable_class, to: :context
6
+ delegate :credential, :username, :challenge, :authenticatable_info, to: :context
7
7
 
8
8
  def call
9
9
  verify_credential!
@@ -40,25 +40,52 @@ module PasskeysRails
40
40
  context.fail! code: :passkey_error, message: e.message
41
41
  end
42
42
 
43
- create_authenticatable! if authenticatable_class.present?
43
+ create_authenticatable! if aux_class_name.present?
44
44
  end
45
45
  end
46
46
 
47
- def create_authenticatable!
48
- klass = begin
49
- authenticatable_class.constantize
50
- rescue StandardError
51
- context.fail!(code: :invalid_authenticatable_class, message: "authenticatable_class (#{authenticatable_class}) is not defined")
52
- end
47
+ def authenticatable_class
48
+ authenticatable_info && authenticatable_info[:class]
49
+ end
50
+
51
+ def authenticatable_params
52
+ authenticatable_info && authenticatable_info[:params]
53
+ end
54
+
55
+ def aux_class_name
56
+ @aux_class_name ||= authenticatable_class || PasskeysRails.default_class
57
+ end
58
+
59
+ def aux_class
60
+ whitelist = PasskeysRails.class_whitelist
53
61
 
54
- begin
55
- authenticatable = klass.create! do |obj|
56
- obj.registering_with(agent) if obj.respond_to?(:registering_with)
62
+ @aux_class ||= begin
63
+ if whitelist.is_a?(Array)
64
+ unless whitelist.include?(aux_class_name)
65
+ context.fail!(code: :invalid_authenticatable_class, message: "authenticatable_class (#{aux_class_name}) is not in the whitelist")
66
+ end
67
+ elsif whitelist.present?
68
+ context.fail!(code: :invalid_class_whitelist,
69
+ message: "class_whitelist is invalid. It should be nil or an array of zero or more class names.")
57
70
  end
58
- agent.update!(authenticatable:)
59
- rescue ActiveRecord::RecordInvalid => e
60
- context.fail!(code: :record_invalid, message: e.message)
71
+
72
+ begin
73
+ aux_class_name.constantize
74
+ rescue StandardError
75
+ context.fail!(code: :invalid_authenticatable_class, message: "authenticatable_class (#{aux_class_name}) is not defined")
76
+ end
77
+ end
78
+ end
79
+
80
+ def create_authenticatable!
81
+ authenticatable = aux_class.create! do |obj|
82
+ obj.agent = agent if obj.respond_to?(:agent=)
83
+ obj.registering_with(authenticatable_params) if obj.respond_to?(:registering_with)
61
84
  end
85
+
86
+ agent.update!(authenticatable:)
87
+ rescue ActiveRecord::RecordInvalid => e
88
+ context.fail!(code: :record_invalid, message: e.message)
62
89
  end
63
90
 
64
91
  def webauthn_credential
@@ -5,11 +5,11 @@ module PasskeysRails
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- has_one :agent, as: :authenticatable
8
+ has_one :agent, as: :authenticatable, class_name: "PasskeysRails::Agent"
9
9
 
10
10
  delegate :registered?, to: :agent, allow_nil: true
11
11
 
12
- def registering_with(_agent)
12
+ def registering_with(_params)
13
13
  # initialize required attributes
14
14
  end
15
15
  end
@@ -2,7 +2,7 @@ Description:
2
2
  Creates a PasskeysRails config file, updates the routes and adds migrations.
3
3
 
4
4
  Example:
5
- bin/rails generate passkeys-rails:install
5
+ bin/rails generate passkeys_rails:install
6
6
 
7
7
  This will:
8
8
  create config/passkeys_rails.rb
@@ -5,14 +5,22 @@ module PasskeysRails
5
5
  class InstallGenerator < ::Rails::Generators::Base
6
6
  source_root File.expand_path("templates", __dir__)
7
7
 
8
+ desc "Adds passkeys config file to your application."
8
9
  def copy_config
9
10
  template 'passkeys_rails_config.rb', "config/initializers/passkeys_rails.rb"
10
11
  end
11
12
 
13
+ desc "Adds passkeys routes to your application."
12
14
  def add_routes
13
- route 'mount PasskeysRails::Engine => "/passkeys_rails"'
15
+ route 'mount PasskeysRails::Engine => "/passkeys"'
14
16
  end
15
17
 
18
+ desc "Copies migrations to your application."
19
+ def copy_migrations
20
+ rake("passkeys_rails:install:migrations")
21
+ end
22
+
23
+ desc "Displays readme during installation."
16
24
  def show_readme
17
25
  readme "README" if behavior == :invoke
18
26
  end
@@ -21,4 +21,28 @@ PasskeysRails.config do |c|
21
21
  # Default is 30 days
22
22
  #
23
23
  # c.auth_token_expires_in = 30.days
24
+
25
+ # Model to use when creating or authenticating a passkey.
26
+ # This can be overridden when calling the API, but if no
27
+ # value is supplied when calling the API, this value is used.
28
+ #
29
+ # If nil, there is no default, and if none is supplied when
30
+ # calling the API, no resource is created other than
31
+ # a PaskeysRails::Agent that is used to track the passkey.
32
+ #
33
+ # This library doesn't assume that there will only be one
34
+ # model, but it is a common use case, so setting the
35
+ # default_class simplifies the use of the API in that case.
36
+ #
37
+ # c.default_class = "User"
38
+
39
+ # By providing a class_whitelist, the API will require that
40
+ # any supplied class is in the whitelist. If it is not, the
41
+ # auth API will return an error. This prevents a caller from
42
+ # attempting to create an unintended records on registration.
43
+ # If nil, any model will be allowed.
44
+ # This should be an array of symbols or strings,
45
+ # for example: %w[User AdminUser]
46
+ #
47
+ # c.class_whitelist = nil
24
48
  end
@@ -4,6 +4,10 @@ require 'passkeys_rails/version'
4
4
  require_relative "generators/passkeys_rails/install_generator"
5
5
 
6
6
  module PasskeysRails
7
+ module Test
8
+ autoload :IntegrationHelpers, 'passkeys_rails/test/integration_helpers'
9
+ end
10
+
7
11
  # Secret used to encode the auth token.
8
12
  # Rails.application.secret_key_base is used if none is defined here.
9
13
  # Changing this value will invalidate all tokens that have been fetched
@@ -19,6 +23,50 @@ module PasskeysRails
19
23
  # Set it to 0 for no expiration (not recommended in production).
20
24
  mattr_accessor :auth_token_expires_in, default: 30.days
21
25
 
26
+ # Model to use when creating or authenticating a passkey.
27
+ # This can be overridden when calling the API, but if no
28
+ # value is supplied when calling the API, this value is used.
29
+ # If nil, there is no default, and if none is supplied when
30
+ # calling the API, no resource is created other than
31
+ # a PaskeysRails::Agent that is used to track the passkey.
32
+ #
33
+ # This library doesn't assume that there will only be one
34
+ # model, but it is a common use case, so setting the
35
+ # default_class simplifies the use of the API in that case.
36
+ mattr_accessor :default_class, default: "User"
37
+
38
+ # By providing a class_whitelist, the API will require that
39
+ # any supplied class is in the whitelist. If it is not, the
40
+ # auth API will return an error. This prevents a caller from
41
+ # attempting to create an unintended record on registration.
42
+ # If nil, any model will be allowed.
43
+ # If [], no model will be allowed.
44
+ # This should be an array of symbols or strings,
45
+ # for example: %w[User AdminUser]
46
+ mattr_accessor :class_whitelist, default: nil
47
+
48
+ # Returns an Interactor::Context that indicates if the request is authentic.
49
+ #
50
+ # .success? is true if authentic
51
+ # .agent is the Passkey::Agent on success
52
+ #
53
+ # .failure? is true if failed (just the opposite of .success?)
54
+ # .code is the error code on failure
55
+ # .message is the human readable error message on failure
56
+ def self.authenticate(request)
57
+ PasskeysRails::ValidateAuthToken.call(auth_token: request.headers['X-Auth'])
58
+ end
59
+
60
+ # Raises a PasskeysRails::Error exception if the request is not authentic.
61
+ def self.authenticate!(request)
62
+ auth = authenticate(request)
63
+ return if auth.success?
64
+
65
+ raise PasskeysRails::Error.new(:authentication,
66
+ code: auth.code,
67
+ message: auth.message)
68
+ end
69
+
22
70
  class << self
23
71
  def config
24
72
  yield self
@@ -0,0 +1,46 @@
1
+ module PasskeysRails
2
+ # PasskeysRails::Test::IntegrationHelpers is a helper module for facilitating
3
+ # authentication on Rails integration tests to bypass the required steps for
4
+ # signin in or signin out a record.
5
+ #
6
+ # Examples
7
+ #
8
+ # class PostsTest < ActionDispatch::IntegrationTest
9
+ # include PasskeysRails::Test::IntegrationHelpers
10
+ #
11
+ # test 'authenticated users can see posts' do
12
+ # get '/posts', headers: logged_in_headers('username-1')
13
+ # assert_response :success
14
+ # end
15
+ # end
16
+ module Test
17
+ module IntegrationHelpers
18
+ def self.included(base)
19
+ base.class_eval do
20
+ setup :setup_integration_for_passkeys_rails
21
+ teardown :teardown_integration_for_passkeys_rails
22
+ end
23
+ end
24
+
25
+ def logged_in_headers(username, authenticatable = nil, headers: {})
26
+ @agent = Agent.create(username:, registered_at: Time.current, authenticatable:)
27
+ result = PasskeysRails::GenerateAuthToken.call(agent:)
28
+ raise result.message if result.failure?
29
+
30
+ headers.merge("X-Auth" => result.auth_token)
31
+ end
32
+
33
+ protected
34
+
35
+ attr_reader :agent
36
+
37
+ def setup_integration_for_passkeys_rails
38
+ # Nothing to do here
39
+ end
40
+
41
+ def teardown_integration_for_passkeys_rails
42
+ @agent&.destroy
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module PasskeysRails
2
- VERSION = "0.1.5".freeze
2
+ VERSION = "0.1.7".freeze
3
3
  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.5
4
+ version: 0.1.7
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-24 00:00:00.000000000 Z
11
+ date: 2023-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -378,6 +378,7 @@ files:
378
378
  - lib/passkeys-rails.rb
379
379
  - lib/passkeys_rails/engine.rb
380
380
  - lib/passkeys_rails/railtie.rb
381
+ - lib/passkeys_rails/test/integration_helpers.rb
381
382
  - lib/passkeys_rails/version.rb
382
383
  - lib/tasks/passkeys_rails_tasks.rake
383
384
  homepage: https://github.com/alliedcode/passkeys-rails