Class: Passkeyed::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/passkeyed/configuration.rb,
sig/passkeyed/configuration.rbs

Overview

Holds passkeyed's settings. The relevant ones are handed to webauthn-ruby as an isolated WebAuthn::RelyingParty (see relying_party), never written to WebAuthn's global configuration.

Passkeyed.configure do |config|
config.rp_name          = "Acme"
config.rp_id            = "acme.example"           # optional
config.allowed_origins  = ["https://acme.example"] # required when deployed
config.user_verification = "required"
end

Constant Summary collapse

USER_VERIFICATION_LEVELS =

The WebAuthn userVerification enum. We normalize to these exact strings so the value advertised to the client and the value enforced server-side can never diverge (a symbol or mis-cased string would silently disable enforcement while still advertising "required").

Returns:

  • (Array[String])
%w[required preferred discouraged].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



52
53
54
55
56
57
58
59
# File 'lib/passkeyed/configuration.rb', line 52

def initialize
  @rp_name = "Passkeyed"
  @rp_id = nil
  @allowed_origins = nil
  self.user_verification = "required"
  @session_key = :passkeyed_challenge
  self.challenge_timeout = 300
end

Instance Attribute Details

#allowed_originsArray[String]?

Array of origins the server will accept assertions from, e.g. ["https://acme.example"]. Left nil only for local development; required (https-only) in any deployed environment — see validate!.

Returns:

  • (Array[String], nil)


32
33
34
# File 'lib/passkeyed/configuration.rb', line 32

def allowed_origins
  @allowed_origins
end

#challenge_timeoutInteger

How long (in seconds) a stashed challenge stays valid. A ceremony should complete within moments; a challenge consumed hours after it was issued points at a stale tab or a replay attempt, so it is rejected with Passkeyed::ChallengeExpired. Also advertised to the browser as the ceremony timeout (via the relying party), so the credential prompt and the server agree on the lifetime. Read via the accessor; always a positive Integer (see the writer).

Returns:

  • (Integer)


50
51
52
# File 'lib/passkeyed/configuration.rb', line 50

def challenge_timeout
  @challenge_timeout
end

#rp_idString?

The relying party id (effectively your domain). When nil, webauthn-ruby derives it from the request origin.

Returns:

  • (String, nil)


27
28
29
# File 'lib/passkeyed/configuration.rb', line 27

def rp_id
  @rp_id
end

#rp_nameString

Human-readable name of the relying party (your app), shown by some authenticators during the ceremony.

Returns:

  • (String)


23
24
25
# File 'lib/passkeyed/configuration.rb', line 23

def rp_name
  @rp_name
end

#session_keySymbol

Base session key from which the per-ceremony challenge keys are derived (see registration_challenge_key / authentication_challenge_key).

Returns:

  • (Symbol)


41
42
43
# File 'lib/passkeyed/configuration.rb', line 41

def session_key
  @session_key
end

#user_verificationString

User-verification requirement for both ceremonies. "required" is what makes a single passkey gesture genuinely multi-factor. Read via the accessor; always one of USER_VERIFICATION_LEVELS (see the writer).

Returns:

  • (String)


37
38
39
# File 'lib/passkeyed/configuration.rb', line 37

def user_verification
  @user_verification
end

Instance Method Details

#authentication_challenge_keySymbol

Returns:

  • (Symbol)


100
101
102
# File 'lib/passkeyed/configuration.rb', line 100

def authentication_challenge_key
  :"#{session_key}_authentication"
end

#enforce_secure_origins?Boolean

True in any deployed environment: everything except Rails.env.local? (development and test, per Rails >= 7.1 — the gem's declared floor).

Returns:

  • (Boolean)


149
150
151
# File 'lib/passkeyed/configuration.rb', line 149

def enforce_secure_origins?
  defined?(Rails) && Rails.respond_to?(:env) && !Rails.env.local?
end

#registration_challenge_keySymbol

Distinct session keys for the two ceremonies, derived from session_key. Keeping them separate means a registration and an authentication ceremony running concurrently in the same session (a background tab, a stale form) can't overwrite each other's single-use challenge.

Returns:

  • (Symbol)


96
97
98
# File 'lib/passkeyed/configuration.rb', line 96

def registration_challenge_key
  :"#{session_key}_registration"
end

#relying_partyObject

WebAuthn::RelyingParty (webauthn-ruby ships no RBS, so left untyped).

Returns:

  • (Object)


109
110
111
112
113
114
115
116
117
118
# File 'lib/passkeyed/configuration.rb', line 109

def relying_party
  WebAuthn::RelyingParty.new(
    name: rp_name,
    id: rp_id,
    allowed_origins: allowed_origins,
    # Also advertised to the browser as the ceremony timeout, so the
    # credential prompt can't outlive the server-side challenge.
    credential_options_timeout: challenge_timeout * 1000
  )
end

#validate!void

This method returns an undefined value.

Refuse to boot a deployed app with development origins. WebAuthn requires a secure context, so a blank allow-list or an http:// origin outside development and test is always a misconfiguration — most often the generated localhost default left in place. Fail loud at boot rather than silently rejecting every sign-in (or, worse, trusting an insecure origin). Runs in every non-local environment (production, staging, any custom deploy env); no-op in development/test and when Rails isn't loaded (e.g. the gem's own suite).



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/passkeyed/configuration.rb', line 128

def validate!
  return unless enforce_secure_origins?

  if allowed_origins.blank?
    raise Passkeyed::ConfigurationError,
          "allowed_origins is required outside development and test; " \
          "set config.allowed_origins to your HTTPS origin(s)"
  end

  insecure = allowed_origins.reject { |origin| origin.to_s.start_with?("https://") }
  return if insecure.empty?

  raise Passkeyed::ConfigurationError,
        "allowed_origins must use https outside development and test; got #{insecure.inspect} " \
        "(the generated localhost default cannot be used in a deployed environment)"
end