passkeys-rails 0.1.1 → 0.1.2

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +17 -2
  4. data/app/controllers/passkeys/rails/application_controller.rb +24 -0
  5. data/app/controllers/passkeys/rails/passkeys_controller.rb +63 -0
  6. data/app/models/concerns/passkeys/rails/authenticatable.rb +19 -0
  7. data/app/models/passkeys/rails/agent.rb +17 -0
  8. data/app/models/passkeys/rails/application_record.rb +7 -0
  9. data/app/models/passkeys/rails/error.rb +16 -0
  10. data/app/models/passkeys/rails/passkey.rb +10 -0
  11. data/config/routes.rb +1 -1
  12. data/lib/generators/passkeys_rails/USAGE +1 -1
  13. data/lib/generators/passkeys_rails/install_generator.rb +14 -12
  14. data/lib/generators/passkeys_rails/templates/README +2 -2
  15. data/lib/generators/passkeys_rails/templates/passkeys_rails_config.rb +2 -2
  16. data/lib/passkeys/rails/controllers/helpers.rb +33 -0
  17. data/lib/passkeys/rails/engine.rb +44 -0
  18. data/lib/passkeys/rails/interactors/begin_authentication.rb +11 -0
  19. data/lib/passkeys/rails/interactors/begin_challenge.rb +37 -0
  20. data/lib/passkeys/rails/interactors/begin_registration.rb +25 -0
  21. data/lib/passkeys/rails/interactors/finish_authentication.rb +55 -0
  22. data/lib/passkeys/rails/interactors/finish_registration.rb +79 -0
  23. data/lib/passkeys/rails/interactors/generate_auth_token.rb +29 -0
  24. data/lib/passkeys/rails/interactors/refresh_token.rb +19 -0
  25. data/lib/passkeys/rails/interactors/validate_auth_token.rb +35 -0
  26. data/lib/passkeys/rails/railtie.rb +19 -0
  27. data/lib/passkeys/rails/version.rb +5 -0
  28. data/lib/passkeys-rails.rb +36 -0
  29. metadata +24 -25
  30. data/app/controllers/passkeys_rails/application_controller.rb +0 -22
  31. data/app/controllers/passkeys_rails/passkeys_controller.rb +0 -61
  32. data/app/helpers/passkeys_rails/application_helper.rb +0 -21
  33. data/app/helpers/passkeys_rails/passkeys_helper.rb +0 -4
  34. data/app/interactors/passkeys_rails/begin_authentication.rb +0 -9
  35. data/app/interactors/passkeys_rails/begin_challenge.rb +0 -35
  36. data/app/interactors/passkeys_rails/begin_registration.rb +0 -23
  37. data/app/interactors/passkeys_rails/finish_authentication.rb +0 -53
  38. data/app/interactors/passkeys_rails/finish_registration.rb +0 -77
  39. data/app/interactors/passkeys_rails/generate_auth_token.rb +0 -27
  40. data/app/interactors/passkeys_rails/refresh_token.rb +0 -17
  41. data/app/interactors/passkeys_rails/validate_auth_token.rb +0 -33
  42. data/app/models/concerns/passkeys_rails/authenticatable.rb +0 -17
  43. data/app/models/passkeys_rails/agent.rb +0 -15
  44. data/app/models/passkeys_rails/application_record.rb +0 -5
  45. data/app/models/passkeys_rails/error.rb +0 -14
  46. data/app/models/passkeys_rails/passkey.rb +0 -8
  47. data/lib/passkeys_rails/engine.rb +0 -24
  48. data/lib/passkeys_rails/error_middleware.rb +0 -17
  49. data/lib/passkeys_rails/railtie.rb +0 -17
  50. data/lib/passkeys_rails/version.rb +0 -3
  51. data/lib/passkeys_rails.rb +0 -38
  52. /data/app/views/layouts/{passkeys_rails → passkeys/rails}/application.html.erb +0 -0
@@ -0,0 +1,35 @@
1
+ module Passkeys
2
+ module Rails
3
+ class ValidateAuthToken
4
+ include Interactor
5
+
6
+ delegate :auth_token, to: :context
7
+
8
+ def call
9
+ context.fail!(code: :missing_token, message: "X-Auth header is required") if auth_token.blank?
10
+
11
+ context.agent = fetch_agent
12
+ end
13
+
14
+ private
15
+
16
+ def fetch_agent
17
+ agent = Agent.find_by(id: payload['agent_id'])
18
+ context.fail!(code: :invalid_token, message: "Invalid token - no agent exists with agent_id") if agent.blank?
19
+
20
+ agent
21
+ end
22
+
23
+ def payload
24
+ JWT.decode(auth_token,
25
+ Passkeys::Rails.auth_token_secret,
26
+ true,
27
+ { required_claims: %w[exp agent_id], algorithm: Passkeys::Rails.auth_token_algorithm }).first
28
+ rescue JWT::ExpiredSignature
29
+ context.fail!(code: :expired_token, message: "The token has expired")
30
+ rescue StandardError => e
31
+ context.fail!(code: :token_error, message: e.message)
32
+ end
33
+ end
34
+ end
35
+ 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,5 @@
1
+ module Passkeys
2
+ module Rails
3
+ VERSION = "0.1.2".freeze
4
+ end
5
+ 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.1
4
+ version: 0.1.2
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. This
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,20 @@ 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/passkeys_rails/application_controller.rb
356
- - app/controllers/passkeys_rails/passkeys_controller.rb
357
- - app/helpers/passkeys_rails/application_helper.rb
358
- - app/helpers/passkeys_rails/passkeys_helper.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
356
+ - app/controllers/passkeys/rails/application_controller.rb
357
+ - app/controllers/passkeys/rails/passkeys_controller.rb
358
+ - app/models/concerns/passkeys/rails/authenticatable.rb
359
+ - app/models/passkeys/rails/agent.rb
360
+ - app/models/passkeys/rails/application_record.rb
361
+ - app/models/passkeys/rails/error.rb
362
+ - app/models/passkeys/rails/passkey.rb
363
+ - app/views/layouts/passkeys/rails/application.html.erb
373
364
  - config/routes.rb
374
365
  - db/migrate/20230620012530_create_passkeys_rails_agents.rb
375
366
  - db/migrate/20230620012600_create_passkeys_rails_passkeys.rb
@@ -377,11 +368,19 @@ files:
377
368
  - lib/generators/passkeys_rails/install_generator.rb
378
369
  - lib/generators/passkeys_rails/templates/README
379
370
  - lib/generators/passkeys_rails/templates/passkeys_rails_config.rb
380
- - lib/passkeys_rails.rb
381
- - lib/passkeys_rails/engine.rb
382
- - lib/passkeys_rails/error_middleware.rb
383
- - lib/passkeys_rails/railtie.rb
384
- - lib/passkeys_rails/version.rb
371
+ - lib/passkeys-rails.rb
372
+ - lib/passkeys/rails/controllers/helpers.rb
373
+ - lib/passkeys/rails/engine.rb
374
+ - lib/passkeys/rails/interactors/begin_authentication.rb
375
+ - lib/passkeys/rails/interactors/begin_challenge.rb
376
+ - lib/passkeys/rails/interactors/begin_registration.rb
377
+ - lib/passkeys/rails/interactors/finish_authentication.rb
378
+ - lib/passkeys/rails/interactors/finish_registration.rb
379
+ - lib/passkeys/rails/interactors/generate_auth_token.rb
380
+ - lib/passkeys/rails/interactors/refresh_token.rb
381
+ - lib/passkeys/rails/interactors/validate_auth_token.rb
382
+ - lib/passkeys/rails/railtie.rb
383
+ - lib/passkeys/rails/version.rb
385
384
  - lib/tasks/passkeys_rails_tasks.rake
386
385
  homepage: https://github.com/alliedcode/passkeys-rails
387
386
  licenses:
@@ -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,4 +0,0 @@
1
- module PasskeysRails
2
- module PasskeysHelper
3
- end
4
- end
@@ -1,9 +0,0 @@
1
- module PasskeysRails
2
- class BeginAuthentication
3
- include Interactor
4
-
5
- def call
6
- context.options = WebAuthn::Credential.options_for_get
7
- end
8
- end
9
- end
@@ -1,35 +0,0 @@
1
- module PasskeysRails
2
- class BeginChallenge
3
- include Interactor
4
-
5
- delegate :username, to: :context
6
-
7
- def call
8
- result = generate_challenge!
9
-
10
- options = result.options
11
-
12
- context.response = options
13
- context.session_data = session_data(options)
14
- rescue Interactor::Failure => e
15
- context.fail! code: e.context.code, message: e.context.message
16
- end
17
-
18
- private
19
-
20
- def generate_challenge!
21
- if username.present?
22
- BeginRegistration.call!(username:)
23
- else
24
- BeginAuthentication.call!
25
- end
26
- end
27
-
28
- def session_data(options)
29
- {
30
- username:,
31
- challenge: WebAuthn.standard_encoder.encode(options.challenge)
32
- }
33
- end
34
- end
35
- end
@@ -1,23 +0,0 @@
1
- module PasskeysRails
2
- class BeginRegistration
3
- include Interactor
4
-
5
- delegate :username, to: :context
6
-
7
- def call
8
- agent = create_unregistered_agent
9
-
10
- context.options = WebAuthn::Credential.options_for_create(user: { id: agent.webauthn_identifier, name: agent.username })
11
- end
12
-
13
- private
14
-
15
- def create_unregistered_agent
16
- agent = Agent.create(username:, webauthn_identifier: WebAuthn.generate_user_id)
17
-
18
- context.fail!(code: :validation_errors, message: agent.errors.full_messages.to_sentence) unless agent.valid?
19
-
20
- agent
21
- end
22
- end
23
- end
@@ -1,53 +0,0 @@
1
- # Finish authentication ceremony
2
- module PasskeysRails
3
- class FinishAuthentication
4
- include Interactor
5
-
6
- delegate :credential, :challenge, to: :context
7
-
8
- def call
9
- verify_credential!
10
-
11
- context.username = agent.username
12
- context.auth_token = GenerateAuthToken.call!(agent:).auth_token
13
- rescue Interactor::Failure => e
14
- context.fail! code: e.context.code, message: e.context.message
15
- end
16
-
17
- private
18
-
19
- def verify_credential!
20
- webauthn_credential.verify(
21
- challenge,
22
- public_key: passkey.public_key,
23
- sign_count: passkey.sign_count
24
- )
25
-
26
- passkey.update!(sign_count: webauthn_credential.sign_count)
27
- agent.update!(last_authenticated_at: Time.current)
28
- rescue WebAuthn::SignCountVerificationError
29
- # Cryptographic verification of the authenticator data succeeded, but the signature counter was less than or equal
30
- # to the stored value. This can have several reasons and depending on your risk tolerance you can choose to fail or
31
- # pass authentication. For more information see https://www.w3.org/TR/webauthn/#sign-counter
32
- rescue WebAuthn::Error => e
33
- context.fail!(code: :webauthn_error, message: e.message)
34
- end
35
-
36
- def webauthn_credential
37
- @webauthn_credential ||= WebAuthn::Credential.from_get(credential)
38
- end
39
-
40
- def passkey
41
- @passkey ||= begin
42
- passkey = Passkey.find_by(identifier: webauthn_credential.id)
43
- context.fail!(code: :passkey_not_found, message: "Unable to find the specified passkey") if passkey.blank?
44
-
45
- passkey
46
- end
47
- end
48
-
49
- def agent
50
- passkey.agent
51
- end
52
- end
53
- end
@@ -1,77 +0,0 @@
1
- # Finish registration ceremony
2
- module PasskeysRails
3
- class FinishRegistration
4
- include Interactor
5
-
6
- delegate :credential, :username, :challenge, :authenticatable_class, to: :context
7
-
8
- def call
9
- verify_credential!
10
- store_passkey_and_register_agent!
11
-
12
- context.username = agent.username
13
- context.auth_token = GenerateAuthToken.call!(agent:).auth_token
14
- rescue Interactor::Failure => e
15
- context.fail! code: e.context.code, message: e.context.message
16
- end
17
-
18
- private
19
-
20
- def verify_credential!
21
- webauthn_credential.verify(challenge)
22
- rescue WebAuthn::Error => e
23
- context.fail!(code: :webauthn_error, message: e.message)
24
- rescue StandardError => e
25
- context.fail!(code: :error, message: e.message)
26
- end
27
-
28
- def store_passkey_and_register_agent!
29
- agent.transaction do
30
- begin
31
- # Store Credential ID, Credential Public Key and Sign Count for future authentications
32
- agent.passkeys.create!(
33
- identifier: webauthn_credential.id,
34
- public_key: webauthn_credential.public_key,
35
- sign_count: webauthn_credential.sign_count
36
- )
37
-
38
- agent.update! registered_at: Time.current
39
- rescue StandardError => e
40
- context.fail! code: :passkey_error, message: e.message
41
- end
42
-
43
- create_authenticatable! if authenticatable_class.present?
44
- end
45
- end
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
53
-
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)
61
- end
62
- end
63
-
64
- def webauthn_credential
65
- @webauthn_credential ||= WebAuthn::Credential.from_create(credential)
66
- end
67
-
68
- def agent
69
- @agent ||= begin
70
- agent = Agent.find_by(username:)
71
- context.fail!(code: :agent_not_found, message: "Agent not found for session value: \"#{username}\"") if agent.blank?
72
-
73
- agent
74
- end
75
- end
76
- end
77
- end
@@ -1,27 +0,0 @@
1
- module PasskeysRails
2
- class GenerateAuthToken
3
- include Interactor
4
-
5
- delegate :agent, to: :context
6
-
7
- def call
8
- context.auth_token = generate_auth_token
9
- end
10
-
11
- private
12
-
13
- def generate_auth_token
14
- JWT.encode(jwt_payload,
15
- PasskeysRails.auth_token_secret,
16
- PasskeysRails.auth_token_algorithm)
17
- end
18
-
19
- def jwt_payload
20
- expiration = (Time.current + PasskeysRails.auth_token_expires_in).to_i
21
-
22
- payload = { agent_id: agent.id }
23
- payload[:exp] = expiration unless expiration.zero?
24
- payload
25
- end
26
- end
27
- end
@@ -1,17 +0,0 @@
1
- # Finish authentication ceremony
2
- module PasskeysRails
3
- class RefreshToken
4
- include Interactor
5
-
6
- delegate :token, to: :context
7
-
8
- def call
9
- agent = ValidateAuthToken.call!(auth_token: token).agent
10
-
11
- context.username = agent.username
12
- context.auth_token = GenerateAuthToken.call!(agent:).auth_token
13
- rescue Interactor::Failure => e
14
- context.fail! code: e.context.code, message: e.context.message
15
- end
16
- end
17
- end
@@ -1,33 +0,0 @@
1
- module PasskeysRails
2
- class ValidateAuthToken
3
- include Interactor
4
-
5
- delegate :auth_token, to: :context
6
-
7
- def call
8
- context.fail!(code: :missing_token, message: "X-Auth header is required") if auth_token.blank?
9
-
10
- context.agent = fetch_agent
11
- end
12
-
13
- private
14
-
15
- def fetch_agent
16
- agent = Agent.find_by(id: payload['agent_id'])
17
- context.fail!(code: :invalid_token, message: "Invalid token - no agent exists with agent_id") if agent.blank?
18
-
19
- agent
20
- end
21
-
22
- def payload
23
- JWT.decode(auth_token,
24
- PasskeysRails.auth_token_secret,
25
- true,
26
- { required_claims: %w[exp agent_id], algorithm: PasskeysRails.auth_token_algorithm }).first
27
- rescue JWT::ExpiredSignature
28
- context.fail!(code: :expired_token, message: "The token has expired")
29
- rescue StandardError => e
30
- context.fail!(code: :token_error, message: e.message)
31
- end
32
- end
33
- end
@@ -1,17 +0,0 @@
1
- require 'active_support/concern'
2
-
3
- module PasskeysRails
4
- module Authenticatable
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- has_one :agent, as: :authenticatable
9
-
10
- delegate :registered?, to: :agent, allow_nil: true
11
-
12
- def registering_with(_agent)
13
- # initialize required attributes
14
- end
15
- end
16
- end
17
- end
@@ -1,15 +0,0 @@
1
- module PasskeysRails
2
- class Agent < ApplicationRecord
3
- belongs_to :authenticatable, polymorphic: true, optional: true
4
- has_many :passkeys
5
-
6
- scope :registered, -> { where.not registered_at: nil }
7
- scope :unregistered, -> { where registered_at: nil }
8
-
9
- validates :username, presence: true, uniqueness: true
10
-
11
- def registered?
12
- registered_at.present?
13
- end
14
- end
15
- end
@@ -1,5 +0,0 @@
1
- module PasskeysRails
2
- class ApplicationRecord < ActiveRecord::Base
3
- self.abstract_class = true
4
- end
5
- end
@@ -1,14 +0,0 @@
1
- module PasskeysRails
2
- class Error < StandardError
3
- attr_reader :hash
4
-
5
- def initialize(message, hash = {})
6
- @hash = hash
7
- super(message)
8
- end
9
-
10
- def to_h
11
- { error: hash.merge(context: message) }
12
- end
13
- end
14
- end
@@ -1,8 +0,0 @@
1
- module PasskeysRails
2
- class Passkey < ApplicationRecord
3
- belongs_to :agent
4
- validates :identifier, presence: true, uniqueness: true
5
- validates :public_key, presence: true
6
- validates :sign_count, presence: true
7
- end
8
- 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