passkeys-rails 0.1.4 → 0.1.6

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: 2eb3101d4ecba95639b4cc95adfa6a86939b7bdc3e6db0e3c26ed9e154d9fa97
4
- data.tar.gz: 86cc744ced315cb5a662743f2cbf44fa20a05f7fbe444103b62a82281b009edd
3
+ metadata.gz: c1c218e160d157a8ddcf931fae3a053fb6e130130b61a8e03f5a9a2bcade07b1
4
+ data.tar.gz: 42fb39898dd79867bd8d18fb95b39ac335a47ed5a06d3ccb13b5f3fc2b64b6af
5
5
  SHA512:
6
- metadata.gz: f2b55ac5a4aea2f969ab32cff126f6cab53f2a0b819a998668d58b794231a4f6ca378247af0e2561fbf0062760ea0724641c36b4915c2bd2a9f24aedf2909a23
7
- data.tar.gz: 115f5c9c098da302f13d45e6871ae0152a8bd454d4d5281effc29afcc7e1dfacf0b838322bb55ff5f9b17caf2f846917e1596c0f8e4fc8a525bf710e986e7a5f
6
+ metadata.gz: 85c226aabf90e17b56744947b37eab49f98c4cbacc42a4963316ea60a68fa6c49b40ea81297d2786a16151361fa6e0f637a5af18d147db12894f5288ef260558
7
+ data.tar.gz: 9a26aa0b7c5e907b954f2564244ab222205775da8087c0c9b17cde3e00706aace1b21b4c3c57d728b91472ed035eeb3f4c87d2ecc4ccecb1e41c5f767d548ccc
data/README.md CHANGED
@@ -1,13 +1,41 @@
1
- [![Gem Version](https://badge.fury.io/rb/passkeys-rails.svg?cachebust=4)](https://badge.fury.io/rb/passkeys-rails)
1
+ [![Gem Version](https://badge.fury.io/rb/passkeys-rails.svg?cachebust=5)](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
+ Devise is awesome, but we don't need all that UI/UX for PassKeys. This gem is to make
7
+ it easy to provide a back end that authenticates a mobile front end with PassKeys.
7
8
 
8
9
  ## Usage
9
10
  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.
11
+ PasskeysRails maintains an Agent model and related Passeys. If you have a user model,
12
+ add `include PasskeysRails::Authenticatable` to your model and include the name of that
13
+ class (e.g. "User") in the authenticatable_class param when calling the register API.
14
+
15
+ ### Optionally providing a "user" model during registration
16
+ PasskeysRails does not require that you supply your own model, but it's often useful to do so. For example,
17
+ if you have a User model that you would like to have created at registration, you can supply the model name
18
+ in the finishRegistration API call.
19
+
20
+ PasskeysRails supports multiple "user" models. Whatever model name you supply in the finishRegiration call will
21
+ be created and provided an opportunity to do any required initialization at that time.
22
+
23
+ There are two PasskeysRails configuration options related to this.
24
+
25
+ default_class and class_whitelist
26
+
27
+ #### default_class
28
+
29
+ Configure default_class in passkeys_rails.rb. Its value will be used during registration if none
30
+ is provided in the API call. It is "User" by default. Since it's just a default, it can be overridden
31
+ in the API call for any other model. If no model is to be used, change it to nil.
32
+
33
+ #### class_whitelist
34
+
35
+ Configure class_whitelist in passkeys_rails.rb. It is nil by default. When nil, no whitelist will be applied.
36
+ If it is non-nil, it should be an array of class names that are allowed during registration. Supply an empty
37
+ array to prevent PasskeysRails from attempting to create anything other than its own PasskeysRails::Agent during
38
+ registration.
11
39
 
12
40
  ## Installation
13
41
  Add this line to your application's Gemfile:
@@ -9,21 +9,22 @@ module PasskeysRails
9
9
  end
10
10
 
11
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
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
15
16
  end
16
17
 
17
18
  def authenticate_passkey!
18
- return if validated_auth_token.success?
19
+ return if current_agent.present?
19
20
 
20
21
  raise PasskeysRails::Error.new(:authentication,
21
22
  code: :unauthorized,
22
23
  message: "You are not authorized to access this resource.")
23
24
  end
24
25
 
25
- def validated_auth_token
26
- @validated_auth_token ||= PasskeysRails::ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
26
+ def passkey_authentication_result
27
+ @passkey_authentication_result ||= PasskeysRails::ValidateAuthToken.call(auth_token: request.headers['HTTP_X_AUTH'])
27
28
  end
28
29
  end
29
30
  end
@@ -5,14 +5,16 @@ module PasskeysRails
5
5
  delegate :username, to: :context
6
6
 
7
7
  def call
8
- agent = create_unregistered_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 create_unregistered_agent
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?
@@ -40,25 +40,42 @@ 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
47
+ def aux_class_name
48
+ @aux_class_name ||= authenticatable_class || PasskeysRails.default_class
49
+ end
50
+
51
+ def aux_class
52
+ whitelist = PasskeysRails.class_whitelist
53
+
54
+ @aux_class ||= begin
55
+ case whitelist
56
+ when Array
57
+ unless whitelist.include?(aux_class_name)
58
+ context.fail!(code: :invalid_authenticatable_class, message: "authenticatable_class (#{aux_class_name}) is not in the whitelist")
59
+ end
60
+ when present?
61
+ context.fail!(code: :invalid_class_whitelist,
62
+ message: "class_whitelist is invalid. It should be nil or an array of zero or more class names.")
63
+ end
64
+
65
+ aux_class_name.constantize
50
66
  rescue StandardError
51
- context.fail!(code: :invalid_authenticatable_class, message: "authenticatable_class (#{authenticatable_class}) is not defined")
67
+ context.fail!(code: :invalid_authenticatable_class, message: "authenticatable_class (#{aux_class_name}) is not defined")
52
68
  end
69
+ end
53
70
 
54
- begin
55
- authenticatable = klass.create! do |obj|
56
- obj.registering_with(agent) if obj.respond_to?(:registering_with)
57
- end
58
- agent.update!(authenticatable:)
59
- rescue ActiveRecord::RecordInvalid => e
60
- context.fail!(code: :record_invalid, message: e.message)
71
+ def create_authenticatable!
72
+ authenticatable = aux_class.create! do |obj|
73
+ obj.registering_with(agent) if obj.respond_to?(:registering_with)
61
74
  end
75
+
76
+ agent.update!(authenticatable:)
77
+ rescue ActiveRecord::RecordInvalid => e
78
+ context.fail!(code: :record_invalid, message: e.message)
62
79
  end
63
80
 
64
81
  def webauthn_credential
@@ -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
@@ -19,6 +19,28 @@ module PasskeysRails
19
19
  # Set it to 0 for no expiration (not recommended in production).
20
20
  mattr_accessor :auth_token_expires_in, default: 30.days
21
21
 
22
+ # Model to use when creating or authenticating a passkey.
23
+ # This can be overridden when calling the API, but if no
24
+ # value is supplied when calling the API, this value is used.
25
+ # If nil, there is no default, and if none is supplied when
26
+ # calling the API, no resource is created other than
27
+ # a PaskeysRails::Agent that is used to track the passkey.
28
+ #
29
+ # This library doesn't assume that there will only be one
30
+ # model, but it is a common use case, so setting the
31
+ # default_class simplifies the use of the API in that case.
32
+ mattr_accessor :default_class, default: "User"
33
+
34
+ # By providing a class_whitelist, the API will require that
35
+ # any supplied class is in the whitelist. If it is not, the
36
+ # auth API will return an error. This prevents a caller from
37
+ # attempting to create an unintended record on registration.
38
+ # If nil, any model will be allowed.
39
+ # If [], no model will be allowed.
40
+ # This should be an array of symbols or strings,
41
+ # for example: %w[User AdminUser]
42
+ mattr_accessor :class_whitelist, default: nil
43
+
22
44
  class << self
23
45
  def config
24
46
  yield self
@@ -1,3 +1,3 @@
1
1
  module PasskeysRails
2
- VERSION = "0.1.4".freeze
2
+ VERSION = "0.1.6".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.4
4
+ version: 0.1.6
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-23 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