mirror of
https://github.com/System-End/identity-vault.git
synced 2026-04-19 20:55:11 +00:00
73 lines
2.1 KiB
Ruby
73 lines
2.1 KiB
Ruby
class Identity::WebauthnCredential < ApplicationRecord
|
|
belongs_to :identity
|
|
|
|
has_paper_trail
|
|
|
|
include PublicActivity::Model
|
|
tracked owner: proc { |controller, record| record.identity }, recipient: proc { |controller, record| record.identity }, only: [ :create, :destroy ]
|
|
|
|
scope :active, -> { where(compromised_at: nil) }
|
|
scope :compromised, -> { where.not(compromised_at: nil) }
|
|
|
|
validates :external_id, presence: true, uniqueness: true
|
|
validates :public_key, presence: true
|
|
validates :sign_count, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
|
validates :nickname, length: { maximum: 50 }, allow_blank: true
|
|
|
|
before_validation :set_initial_sign_count, on: :create
|
|
|
|
# WebAuthn credential IDs and public keys are binary data that need to be
|
|
# base64url encoded for storage and transmission
|
|
def webauthn_id
|
|
Base64.urlsafe_decode64(external_id)
|
|
end
|
|
|
|
def webauthn_id=(value)
|
|
self.external_id = Base64.urlsafe_encode64(value, padding: false)
|
|
end
|
|
|
|
def webauthn_public_key
|
|
Base64.urlsafe_decode64(public_key)
|
|
end
|
|
|
|
def webauthn_public_key=(value)
|
|
self.public_key = Base64.urlsafe_encode64(value, padding: false)
|
|
end
|
|
|
|
# Increment the sign count after successful authentication
|
|
# This helps detect credential cloning attacks
|
|
def increment_sign_count!
|
|
increment!(:sign_count)
|
|
end
|
|
|
|
# Mark credential as potentially compromised (e.g., sign count anomaly)
|
|
def mark_as_compromised!
|
|
update!(compromised_at: Time.current)
|
|
Rails.logger.warn "WebAuthn credential marked as compromised: id=#{id}, identity_id=#{identity_id}"
|
|
end
|
|
|
|
def compromised?
|
|
compromised_at.present?
|
|
end
|
|
|
|
def active?
|
|
!compromised?
|
|
end
|
|
|
|
# Human-readable display for the credential
|
|
def display_name
|
|
nickname.presence || "Passkey created #{created_at.strftime('%b %d, %Y')}"
|
|
end
|
|
|
|
# Class method to get all decoded credential IDs for a collection
|
|
# Useful for building WebAuthn allow/exclude lists
|
|
def self.raw_credential_ids
|
|
pluck(:external_id).map { |id| Base64.urlsafe_decode64(id) }
|
|
end
|
|
|
|
private
|
|
|
|
def set_initial_sign_count
|
|
self.sign_count ||= 0
|
|
end
|
|
end
|