Sha256: 0d793256e8f5e4b2c7b1f07248d4d6615471d2ee47c999af85b8b13b6179be4d

Contents?: true

Size: 1.92 KB

Versions: 1

Compression:

Stored size: 1.92 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
    10.times.map { 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 code =~ /^[\d]{6}$/
      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)
  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)
    raise "Wrong user" unless payload[:user_id] == user.id

    payload[:secret]
  end
end

Version data entries

1 entries across 1 versions & 1 rubygems

Version Path
pages_core-3.14.0 app/models/otp_secret.rb