Sha256: a6efedd6ccbdaa12fc50ca295b84bb98181fe5197826b49ce087d886337ac99d

Contents?: true

Size: 1.97 KB

Versions: 5

Compression:

Stored size: 1.97 KB

Contents

# frozen_string_literal: true

class OtpSecret
  attr_reader :user, :secret

  def initialize(user)
    @user = user
    @secret = user.otp_secret
  end

  def account_name
    user.email
  end

  def disable!
    user.update(otp_enabled: false,
                otp_secret: nil,
                last_otp_at: nil,
                recovery_codes: [])
  end

  def enable!(recovery_codes)
    user.update(otp_enabled: true,
                otp_secret: secret,
                last_otp_at: Time.zone.now,
                recovery_codes:)
  end

  def generate
    @secret = ROTP::Base32.random
  end

  def generate_recovery_codes
    Array.new(10) { SecureRandom.alphanumeric(16) }
  end

  def provisioning_uri
    totp.provisioning_uri(account_name)
  end

  def regenerate_recovery_codes!
    generate_recovery_codes.tap do |recovery_codes|
      user.update(recovery_codes:)
    end
  end

  def signed_message
    message_verifier.generate(
      { user_id: user.id, secret: }, expires_in: 1.hour
    )
  end

  def validate_otp!(code)
    return false unless valid_otp?(code)

    user.update(last_otp_at: Time.zone.now)
    true
  end

  def validate_otp_or_recovery_code!(code)
    if /^[\d]{6}$/.match?(code)
      validate_otp!(code)
    else
      validate_recovery_code!(code)
    end
  end

  def validate_recovery_code!(code)
    user.use_recovery_code!(code)
  end

  def verify(params)
    @secret = verify_secret(params[:signed_message])
    valid_otp?(params[:otp])
  end

  private

  def message_verifier
    Rails.application.message_verifier(:otp_secret)
  end

  def totp
    ROTP::TOTP.new(secret, issuer: PagesCore.config.site_name)
  end

  def valid_otp?(otp)
    if user.otp_enabled?
      totp.verify(otp, after: user.last_otp_at, drift_behind: 10)
    else
      totp.verify(otp, drift_behind: 10)
    end
  end

  def verify_secret(signed)
    payload = message_verifier.verify(signed).symbolize_keys
    raise "Wrong user" unless payload[:user_id] == user.id

    payload[:secret]
  end
end

Version data entries

5 entries across 5 versions & 1 rubygems

Version Path
pages_core-3.15.5 app/models/otp_secret.rb
pages_core-3.15.4 app/models/otp_secret.rb
pages_core-3.15.3 app/models/otp_secret.rb
pages_core-3.15.2 app/models/otp_secret.rb
pages_core-3.15.1 app/models/otp_secret.rb