passkeys-rails 0.1.4 → 0.1.6
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/README.md +31 -3
- data/app/controllers/concerns/passkeys_rails/authentication.rb +7 -6
- data/app/interactors/passkeys_rails/begin_registration.rb +4 -2
- data/app/interactors/passkeys_rails/finish_registration.rb +29 -12
- data/lib/generators/passkeys_rails/templates/passkeys_rails_config.rb +24 -0
- data/lib/passkeys-rails.rb +22 -0
- data/lib/passkeys_rails/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1c218e160d157a8ddcf931fae3a053fb6e130130b61a8e03f5a9a2bcade07b1
|
4
|
+
data.tar.gz: 42fb39898dd79867bd8d18fb95b39ac335a47ed5a06d3ccb13b5f3fc2b64b6af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85c226aabf90e17b56744947b37eab49f98c4cbacc42a4963316ea60a68fa6c49b40ea81297d2786a16151361fa6e0f637a5af18d147db12894f5288ef260558
|
7
|
+
data.tar.gz: 9a26aa0b7c5e907b954f2564244ab222205775da8087c0c9b17cde3e00706aace1b21b4c3c57d728b91472ed035eeb3f4c87d2ecc4ccecb1e41c5f767d548ccc
|
data/README.md
CHANGED
@@ -1,13 +1,41 @@
|
|
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
|
-
Devise is awesome, but we don't need all that UI/UX for PassKeys. This gem is to make
|
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,
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
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
|
26
|
-
@
|
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 =
|
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?
|
@@ -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
|
43
|
+
create_authenticatable! if aux_class_name.present?
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
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 (#{
|
67
|
+
context.fail!(code: :invalid_authenticatable_class, message: "authenticatable_class (#{aux_class_name}) is not defined")
|
52
68
|
end
|
69
|
+
end
|
53
70
|
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
data/lib/passkeys-rails.rb
CHANGED
@@ -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
|
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.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-
|
11
|
+
date: 2023-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|