passkeyed 0.1.0
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +271 -0
- data/lib/generators/passkeyed/install/USAGE +14 -0
- data/lib/generators/passkeyed/install/install_generator.rb +105 -0
- data/lib/generators/passkeyed/install/templates/create_passkeyed_credentials.rb.erb +56 -0
- data/lib/generators/passkeyed/install/templates/initializer.rb +30 -0
- data/lib/generators/passkeyed/install/templates/passkey_controller.js +246 -0
- data/lib/passkeyed/ceremonies.rb +255 -0
- data/lib/passkeyed/configuration.rb +188 -0
- data/lib/passkeyed/credential.rb +58 -0
- data/lib/passkeyed/errors.rb +34 -0
- data/lib/passkeyed/model.rb +98 -0
- data/lib/passkeyed/version.rb +5 -0
- data/lib/passkeyed.rb +19 -0
- data/sig/passkeyed/ceremonies.rbs +22 -0
- data/sig/passkeyed/configuration.rbs +31 -0
- data/sig/passkeyed/credential.rbs +9 -0
- data/sig/passkeyed/errors.rbs +22 -0
- data/sig/passkeyed/model.rbs +16 -0
- data/sig/passkeyed/version.rbs +3 -0
- metadata +208 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Passkeyed
|
|
4
|
+
# A stored passkey: the public half of a WebAuthn credential, plus the
|
|
5
|
+
# signature counter used to spot cloned authenticators. The private key
|
|
6
|
+
# never reaches us, so there is nothing secret in this table.
|
|
7
|
+
#
|
|
8
|
+
# The owner is polymorphic (+user+) so any model can hold passkeys, not just
|
|
9
|
+
# one called User.
|
|
10
|
+
class Credential < Passkeyed.base_record_class
|
|
11
|
+
self.table_name = "passkeyed_credentials"
|
|
12
|
+
|
|
13
|
+
belongs_to :user, polymorphic: true, optional: false
|
|
14
|
+
|
|
15
|
+
# The WebAuthn credential id (base64url). Unique across the whole table:
|
|
16
|
+
# authentication looks a credential up by this alone.
|
|
17
|
+
validates :external_id, presence: true, uniqueness: true
|
|
18
|
+
validates :public_key, presence: true
|
|
19
|
+
validates :sign_count,
|
|
20
|
+
presence: true,
|
|
21
|
+
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
|
22
|
+
# Optional user-supplied label. Cap it at the string column's width so a long
|
|
23
|
+
# value is rejected cleanly rather than silently truncated (e.g. on MySQL).
|
|
24
|
+
validates :nickname, length: { maximum: 255 }, allow_nil: true
|
|
25
|
+
|
|
26
|
+
# Persist the state a successful assertion produces: the advanced signature
|
|
27
|
+
# counter, and — when the columns are present — a last-used timestamp and
|
|
28
|
+
# the current backup state (a device-bound passkey may later start syncing,
|
|
29
|
+
# flipping the flag). The columns are guarded so authentication keeps
|
|
30
|
+
# working on apps that trimmed the optional columns from the migration.
|
|
31
|
+
def record_sign_in!(new_sign_count, backed_up: nil)
|
|
32
|
+
attributes = { sign_count: new_sign_count }
|
|
33
|
+
attributes[:last_used_at] = Time.current if has_attribute?(:last_used_at)
|
|
34
|
+
attributes[:backed_up] = backed_up if !backed_up.nil? && has_attribute?(:backed_up)
|
|
35
|
+
assign_attributes(attributes)
|
|
36
|
+
# Everything written here is server-derived, so skip validations: a record
|
|
37
|
+
# invalid by rules added after it was persisted (e.g. a nickname cap
|
|
38
|
+
# introduced in a future version) must not start failing every sign-in.
|
|
39
|
+
save!(validate: false)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# +transports+ is stored as a JSON string (portable across databases);
|
|
43
|
+
# expose it as the Array WebAuthn reported, e.g. ["internal", "hybrid"].
|
|
44
|
+
# Unparseable stored data (a manual write, a botched import) reads as nil
|
|
45
|
+
# rather than raising out of every page that lists credentials.
|
|
46
|
+
def transports
|
|
47
|
+
raw = super
|
|
48
|
+
raw.is_a?(String) ? JSON.parse(raw) : raw
|
|
49
|
+
rescue JSON::ParserError
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def transports=(value)
|
|
54
|
+
value = JSON.generate(value) unless value.nil? || value.is_a?(String)
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Passkeyed
|
|
4
|
+
# Base class for every error raised by passkeyed. Rescue this to catch any
|
|
5
|
+
# ceremony failure in one place.
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
# Raised when a registration (attestation) ceremony fails verification.
|
|
9
|
+
class RegistrationError < Error; end
|
|
10
|
+
|
|
11
|
+
# Raised when an authentication (assertion) ceremony fails verification.
|
|
12
|
+
class AuthenticationError < Error; end
|
|
13
|
+
|
|
14
|
+
# Raised during authentication when the asserted credential is not one we
|
|
15
|
+
# have on record.
|
|
16
|
+
class CredentialNotFound < AuthenticationError; end
|
|
17
|
+
|
|
18
|
+
# Raised when the server-issued challenge is absent from the session, which
|
|
19
|
+
# usually means the ceremony was started in a different session or the
|
|
20
|
+
# challenge was already consumed.
|
|
21
|
+
class ChallengeMissing < Error; end
|
|
22
|
+
|
|
23
|
+
# Raised when the stored challenge is older than the configured
|
|
24
|
+
# +challenge_timeout+. A challenge should be signed within moments of being
|
|
25
|
+
# issued; a stale one usually means a form left open for hours. Subclasses
|
|
26
|
+
# ChallengeMissing because the remedy is identical (restart the ceremony), so
|
|
27
|
+
# one rescue can handle both.
|
|
28
|
+
class ChallengeExpired < ChallengeMissing; end
|
|
29
|
+
|
|
30
|
+
# Raised at boot/use time when passkeyed is misconfigured or the host model
|
|
31
|
+
# is missing required schema (e.g. an invalid +user_verification+ value or a
|
|
32
|
+
# missing +webauthn_id+ column). Fails fast rather than degrading silently.
|
|
33
|
+
class ConfigurationError < Error; end
|
|
34
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module Passkeyed
|
|
6
|
+
# Mix into the model that owns passkeys (typically your User):
|
|
7
|
+
#
|
|
8
|
+
# class User < ApplicationRecord
|
|
9
|
+
# include Passkeyed::Model
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# It wires up the credentials association and assigns the random WebAuthn
|
|
13
|
+
# user handle (+webauthn_id+) on create. The host table needs a
|
|
14
|
+
# +webauthn_id:string+ column (the install generator adds it).
|
|
15
|
+
module Model
|
|
16
|
+
extend ActiveSupport::Concern
|
|
17
|
+
|
|
18
|
+
included do
|
|
19
|
+
has_many :passkeyed_credentials,
|
|
20
|
+
class_name: "Passkeyed::Credential",
|
|
21
|
+
as: :user,
|
|
22
|
+
dependent: :destroy
|
|
23
|
+
|
|
24
|
+
before_create :assign_passkeyed_webauthn_id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# The name passed to the authenticator at registration time. Prefers an
|
|
28
|
+
# +email+, then a +name+, falling back to the opaque handle. Override in
|
|
29
|
+
# your model if you want something else shown.
|
|
30
|
+
def passkey_name
|
|
31
|
+
return email if respond_to?(:email) && email.present?
|
|
32
|
+
return name if respond_to?(:name) && name.present?
|
|
33
|
+
|
|
34
|
+
passkeyed_webauthn_id!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The human-friendly display name (WebAuthn's user.displayName) shown by
|
|
38
|
+
# account pickers, distinct from +passkey_name+ (the account identifier).
|
|
39
|
+
# Defaults to +passkey_name+; override to show e.g. a full name next to an
|
|
40
|
+
# email.
|
|
41
|
+
def passkey_display_name
|
|
42
|
+
passkey_name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Rename one of this owner's passkeys (e.g. from a credential-management UI).
|
|
46
|
+
# Looked up *through the association*, so an id belonging to another owner
|
|
47
|
+
# raises ActiveRecord::RecordNotFound rather than letting one user relabel
|
|
48
|
+
# another's credential. A blank nickname clears the label. Returns the
|
|
49
|
+
# credential.
|
|
50
|
+
def rename_passkey(credential_id, nickname)
|
|
51
|
+
credential = passkeyed_credentials.find(credential_id)
|
|
52
|
+
credential.update!(nickname: nickname.presence)
|
|
53
|
+
credential
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Revoke (delete) one of this owner's passkeys. Scoped through the
|
|
57
|
+
# association like +rename_passkey+. Returns the destroyed credential; raises
|
|
58
|
+
# ActiveRecord::RecordNotFound if the id isn't this owner's.
|
|
59
|
+
#
|
|
60
|
+
# This does not stop a user removing their last passkey — guard against that
|
|
61
|
+
# in your app if it would lock the account out.
|
|
62
|
+
def revoke_passkey(credential_id)
|
|
63
|
+
passkeyed_credentials.find(credential_id).destroy!
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Return this record's WebAuthn user handle, assigning one when absent.
|
|
67
|
+
# +before_create+ only covers rows created after the gem is installed, so a
|
|
68
|
+
# pre-existing user (or one whose backfill was missed) would otherwise reach
|
|
69
|
+
# the ceremony with a nil handle, which webauthn-ruby drops from the options
|
|
70
|
+
# JSON and the browser then rejects. Persists the handle when the record is
|
|
71
|
+
# already saved; assigns it in memory otherwise (the +before_create+ keeps
|
|
72
|
+
# it for an unsaved record). Raises if the +webauthn_id+ column is missing.
|
|
73
|
+
def passkeyed_webauthn_id!
|
|
74
|
+
ensure_webauthn_id_column!
|
|
75
|
+
return webauthn_id if webauthn_id.present?
|
|
76
|
+
|
|
77
|
+
handle = WebAuthn.generate_user_id
|
|
78
|
+
self.webauthn_id = handle
|
|
79
|
+
update_column(:webauthn_id, handle) if persisted?
|
|
80
|
+
handle
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def assign_passkeyed_webauthn_id
|
|
86
|
+
ensure_webauthn_id_column!
|
|
87
|
+
self.webauthn_id ||= WebAuthn.generate_user_id
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def ensure_webauthn_id_column!
|
|
91
|
+
return if self.class.column_names.include?("webauthn_id")
|
|
92
|
+
|
|
93
|
+
raise Passkeyed::ConfigurationError,
|
|
94
|
+
"#{self.class.name} has no webauthn_id column; run the passkeyed install migration " \
|
|
95
|
+
"(bin/rails generate passkeyed:install && bin/rails db:migrate)"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/passkeyed.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webauthn"
|
|
4
|
+
require "active_support"
|
|
5
|
+
|
|
6
|
+
require "passkeyed/version"
|
|
7
|
+
require "passkeyed/errors"
|
|
8
|
+
require "passkeyed/configuration"
|
|
9
|
+
|
|
10
|
+
# passkeyed adds passwordless, usernameless passkey (WebAuthn) authentication
|
|
11
|
+
# to Rails apps as a small, transparent runtime plus an install generator.
|
|
12
|
+
#
|
|
13
|
+
# The heavier pieces (the ActiveRecord credential model and the two concerns)
|
|
14
|
+
# are autoloaded so the gem can be required before ActiveRecord is ready.
|
|
15
|
+
module Passkeyed
|
|
16
|
+
autoload :Credential, "passkeyed/credential"
|
|
17
|
+
autoload :Model, "passkeyed/model"
|
|
18
|
+
autoload :Ceremonies, "passkeyed/ceremonies"
|
|
19
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Passkeyed
|
|
2
|
+
module Ceremonies
|
|
3
|
+
def passkey_registration_options: (untyped user) -> untyped
|
|
4
|
+
def passkey_register!: (untyped user, untyped credential, ?nickname: String?) -> untyped
|
|
5
|
+
def passkey_authentication_options: () -> untyped
|
|
6
|
+
def passkey_authenticate!: (untyped credential) -> untyped
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def after_passkey_registration: (untyped credential) -> void
|
|
11
|
+
def after_passkey_authentication: (untyped credential, untyped user) -> void
|
|
12
|
+
def verify_registration: (untyped user, untyped credential, ?nickname: String?) -> untyped
|
|
13
|
+
def verify_authentication: (untyped credential) -> untyped
|
|
14
|
+
def passkeyed_session: () -> untyped
|
|
15
|
+
def stash_challenge: (Symbol key, String challenge) -> void
|
|
16
|
+
def consume_challenge: (Symbol key, String ceremony) -> String
|
|
17
|
+
def credential_metadata: (untyped webauthn_credential) -> Hash[Symbol, untyped]
|
|
18
|
+
def passkeyed_user_verification?: () -> bool
|
|
19
|
+
def parse_credential: (Symbol method, untyped credential, untyped error_class) -> untyped
|
|
20
|
+
def normalize_credential: (untyped credential) -> untyped
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Passkeyed
|
|
2
|
+
class Configuration
|
|
3
|
+
USER_VERIFICATION_LEVELS: Array[String]
|
|
4
|
+
|
|
5
|
+
attr_accessor rp_name: String
|
|
6
|
+
attr_accessor rp_id: String?
|
|
7
|
+
attr_accessor allowed_origins: Array[String]?
|
|
8
|
+
attr_reader user_verification: String
|
|
9
|
+
attr_accessor session_key: Symbol
|
|
10
|
+
attr_reader challenge_timeout: Integer
|
|
11
|
+
|
|
12
|
+
def initialize: () -> void
|
|
13
|
+
def user_verification=: (untyped value) -> String
|
|
14
|
+
def challenge_timeout=: (untyped value) -> Integer
|
|
15
|
+
def registration_challenge_key: () -> Symbol
|
|
16
|
+
def authentication_challenge_key: () -> Symbol
|
|
17
|
+
# WebAuthn::RelyingParty (webauthn-ruby ships no RBS, so left untyped).
|
|
18
|
+
def relying_party: () -> untyped
|
|
19
|
+
def validate!: () -> void
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def enforce_secure_origins?: () -> bool
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.base_record_class: () -> untyped
|
|
27
|
+
def self.base_record_class=: (untyped) -> untyped
|
|
28
|
+
def self.configuration: () -> Configuration
|
|
29
|
+
def self.configure: () ?{ (Configuration) -> void } -> Configuration
|
|
30
|
+
def self.reset_configuration!: () -> Configuration
|
|
31
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Passkeyed
|
|
2
|
+
# Inherits Passkeyed.base_record_class (ApplicationRecord / ActiveRecord::Base)
|
|
3
|
+
# at load time; declared here against ActiveRecord::Base for typing.
|
|
4
|
+
class Credential < ActiveRecord::Base
|
|
5
|
+
def record_sign_in!: (Integer new_sign_count, ?backed_up: bool?) -> void
|
|
6
|
+
def transports: () -> Array[String]?
|
|
7
|
+
def transports=: (Array[String] | String | nil value) -> untyped
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Passkeyed
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class RegistrationError < Error
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class AuthenticationError < Error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class CredentialNotFound < AuthenticationError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class ChallengeMissing < Error
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class ChallengeExpired < ChallengeMissing
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class ConfigurationError < Error
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Passkeyed
|
|
2
|
+
module Model
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def passkey_name: () -> String
|
|
6
|
+
def passkey_display_name: () -> String
|
|
7
|
+
def rename_passkey: (untyped credential_id, String? nickname) -> untyped
|
|
8
|
+
def revoke_passkey: (untyped credential_id) -> untyped
|
|
9
|
+
def passkeyed_webauthn_id!: () -> String
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def assign_passkeyed_webauthn_id: () -> void
|
|
14
|
+
def ensure_webauthn_id_column!: () -> void
|
|
15
|
+
end
|
|
16
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: passkeyed
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Paul Ardeleanu
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-07-05 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: activesupport
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: railties
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: webauthn
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.4'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.4'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: minitest
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '5.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '5.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rake
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '13.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '13.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.60'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.60'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: rubocop-minitest
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.34'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.34'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: rubocop-rake
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0.6'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0.6'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: sqlite3
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '1.6'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - ">="
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '1.6'
|
|
152
|
+
description: |-
|
|
153
|
+
passkeyed adds passwordless, usernameless passkey sign-in to Rails apps:
|
|
154
|
+
a small, transparent runtime (a model concern, a controller concern, and a
|
|
155
|
+
Stimulus controller) plus an install generator, built on webauthn-ruby.
|
|
156
|
+
email:
|
|
157
|
+
- pardel@gmail.com
|
|
158
|
+
executables: []
|
|
159
|
+
extensions: []
|
|
160
|
+
extra_rdoc_files: []
|
|
161
|
+
files:
|
|
162
|
+
- LICENSE.txt
|
|
163
|
+
- README.md
|
|
164
|
+
- lib/generators/passkeyed/install/USAGE
|
|
165
|
+
- lib/generators/passkeyed/install/install_generator.rb
|
|
166
|
+
- lib/generators/passkeyed/install/templates/create_passkeyed_credentials.rb.erb
|
|
167
|
+
- lib/generators/passkeyed/install/templates/initializer.rb
|
|
168
|
+
- lib/generators/passkeyed/install/templates/passkey_controller.js
|
|
169
|
+
- lib/passkeyed.rb
|
|
170
|
+
- lib/passkeyed/ceremonies.rb
|
|
171
|
+
- lib/passkeyed/configuration.rb
|
|
172
|
+
- lib/passkeyed/credential.rb
|
|
173
|
+
- lib/passkeyed/errors.rb
|
|
174
|
+
- lib/passkeyed/model.rb
|
|
175
|
+
- lib/passkeyed/version.rb
|
|
176
|
+
- sig/passkeyed/ceremonies.rbs
|
|
177
|
+
- sig/passkeyed/configuration.rbs
|
|
178
|
+
- sig/passkeyed/credential.rbs
|
|
179
|
+
- sig/passkeyed/errors.rbs
|
|
180
|
+
- sig/passkeyed/model.rbs
|
|
181
|
+
- sig/passkeyed/version.rbs
|
|
182
|
+
homepage: https://github.com/pardel/passkeyed
|
|
183
|
+
licenses:
|
|
184
|
+
- MIT
|
|
185
|
+
metadata:
|
|
186
|
+
homepage_uri: https://github.com/pardel/passkeyed
|
|
187
|
+
source_code_uri: https://github.com/pardel/passkeyed
|
|
188
|
+
bug_tracker_uri: https://github.com/pardel/passkeyed/issues
|
|
189
|
+
documentation_uri: https://rubydoc.info/gems/passkeyed
|
|
190
|
+
rubygems_mfa_required: 'true'
|
|
191
|
+
rdoc_options: []
|
|
192
|
+
require_paths:
|
|
193
|
+
- lib
|
|
194
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
195
|
+
requirements:
|
|
196
|
+
- - ">="
|
|
197
|
+
- !ruby/object:Gem::Version
|
|
198
|
+
version: '3.1'
|
|
199
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
200
|
+
requirements:
|
|
201
|
+
- - ">="
|
|
202
|
+
- !ruby/object:Gem::Version
|
|
203
|
+
version: '0'
|
|
204
|
+
requirements: []
|
|
205
|
+
rubygems_version: 3.6.2
|
|
206
|
+
specification_version: 4
|
|
207
|
+
summary: Passwordless passkey (WebAuthn) authentication for Rails.
|
|
208
|
+
test_files: []
|