Скопируйте текст в html .slim, используя буфер обмена. js - PullRequest
1 голос
/ 11 февраля 2020

У меня есть страница двухфакторной проверки, на ней отображается секретный ключ (Ciphertext), и у меня уже есть буфер обмена. js установлен в моем приложении.

enter image description here

Интересно, как можно создать кнопку для копирования этого секретного ключа?

= simple_form_for @google_auth, as: 'google_auth', url: verify_google_auth_path do |f|
  h4 = t('.step-1')
  p
    span = t('.download-app')
    span == t('.guide-link')

  h4 = t('.step-2')
  p: span = t('.scan-qr-code')

  = f.input :uri do
    = qr_tag(@google_auth.uri)

  = f.input :otp_secret do
    .input-group
      = f.input_field :otp_secret, class: 'upcase', readonly: true
      span.input-group-btn
        a.btn.btn-default href='#{verify_google_auth_path(:app, refresh: true)}'
          i.fa.fa-refresh

  h4 = t('.step-3')
  p: span = t('.enter-passcode')

  = f.input :otp

  hr.split
  = f.button :wrapped, t('.submit'), cancel: settings_path

= content_for :guide do
  ul.list-unstyled
    li: a target='_blank' href='https://apps.apple.com/br/app/authy/id494168017'
      i.fa.fa-apple
      span = t('.ios')
    li: a target='_blank' href='https://play.google.com/store/apps/details?id=com.authy.authy'
      i.fa.fa-android
      span = t('.android')

Я пытался сделать так, но это не сработало:

a.btn.btn-default data-clipboard-action='copy' data-clipboard-target=':otp_secret'
  i.fa.fa-clipboard

В приведенном выше примере копируется только чистый otp_secret текст.

spec \ models \ two_factor \ app_spe c .rb:

require 'spec_helper'

describe TwoFactor::App do
  let(:member) { create :member }
  let(:app) { member.app_two_factor  }

  describe "generate code" do
    subject { app }

    its(:otp_secret) { should_not be_blank }
  end

  describe '#refresh' do
    context 'inactivated' do
      it {
        orig_otp_secret = app.otp_secret.dup
        app.refresh!
        expect(app.otp_secret).not_to eq(orig_otp_secret)
      }
    end

    context 'activated' do
      subject { create :two_factor_app, activated: true }

      it {
        orig_otp_secret = subject.otp_secret.dup
        subject.refresh!
        expect(subject.otp_secret).to eq(orig_otp_secret)
      }
    end
  end

  describe 'uniq validate' do
    let(:member) { create :member }

    it "reject duplicate creation" do
      duplicate = TwoFactor.new app.attributes
      expect(duplicate).not_to be_valid
    end
  end

  describe 'self.fetch_by_type' do
    it "return nil for wrong type" do
      expect(TwoFactor.by_type(:foobar)).to be_nil
    end

    it "create new one by type" do
      expect {
        expect(app).not_to be_nil
      }.to change(TwoFactor::App, :count).by(1)
    end

    it "retrieve exist one instead of creating" do
      two_factor = member.app_two_factor
      expect(member.app_two_factor).to eq(two_factor)
    end
  end

  describe '#active!' do
    subject { member.app_two_factor }
    before { subject.active! }

    its(:activated?) { should be_true }
  end

  describe '#deactive!' do
    subject { create :two_factor_app, activated: true }
    before { subject.deactive! }

    its(:activated?) { should_not be_true }
  end


  describe '.activated' do
    before { create :member, :app_two_factor_activated }

    it "should has activated" do
      expect(TwoFactor.activated?).to be_true
    end
  end

  describe 'send_notification_mail' do
    let(:mail) { ActionMailer::Base.deliveries.last }

    describe "activated" do
      before { app.active! }

      it { expect(mail.subject).to match('Google authenticator activated') }
    end

    describe "deactived" do
      let(:member) { create :member, :app_two_factor_activated }
      before { app.deactive! }

      it { expect(mail.subject).to match('Google authenticator deactivated') }
    end
  end

end

app.rb:

class TwoFactor::App < ::TwoFactor

  def verify?
    return false if otp_secret.blank?

    rotp = ROTP::TOTP.new(otp_secret)

    if rotp.verify(otp)
      touch(:last_verify_at)
      true
    else
      errors.add :otp, :invalid
      false
    end
  end

  def uri
    totp = ROTP::TOTP.new(otp_secret)
    totp.provisioning_uri(member.email) + "&issuer=#{ENV['URL_HOST']}"
  end

  def now
    ROTP::TOTP.new(otp_secret).now
  end

  def refresh!
    return if activated?
    super
  end

  private

  def gen_code
    self.otp_secret = ROTP::Base32.random_base32
    self.refreshed_at = Time.new
  end

  def send_notification
    return if not self.activated_changed?

    if self.activated
      MemberMailer.google_auth_activated(member.id).deliver
    else
      MemberMailer.google_auth_deactivated(member.id).deliver
    end
  end

end

РЕДАКТИРОВАТЬ: app \ models \ two_factor.rb:

class TwoFactor < ActiveRecord::Base
  belongs_to :member

  before_validation :gen_code, on: :create
  after_update :send_notification

  validates_presence_of :member, :otp_secret, :refreshed_at

  attr_accessor :otp

  SUBCLASS = ['app', 'sms', 'email', 'wechat']

  validates_uniqueness_of :type, scope: :member_id

  scope :activated, -> { where(activated: true) }
  scope :require_signin, -> { where(require_signin: 1) }

  class << self
    def by_type(type)
      return if not SUBCLASS.include?(type.to_s)

      klass = "two_factor/#{type}".camelize.constantize
      klass.find_or_create_by(type: klass.name)
    end

    def activated?
      activated.any?
    end

    def require_signin?
      require_signin.any?
    end
  end

  def verify?
    msg = "#{self.class.name}#verify? is not implemented."
    raise NotImplementedError.new(msg)
  end

  def expired?
    Time.now >= 30.minutes.since(refreshed_at)
  end

  def refresh!
    gen_code
    save
  end

  def active!
    update activated: true, last_verify_at: Time.now
  end

  def set_require_signin
    update require_signin: 1
  end

  def reset_require_signin
    update require_signin: nil
  end

  def deactive!
    update activated: false, require_signin: nil
  end

  private

  def gen_code
    msg = "#{self.class.name}#gen_code is not implemented."
    raise NotImplementedError.new(msg)
  end

  def send_notification
    msg = "#{self.class.name}#send_notification is not implemented."
    raise NotImplementedError.new(msg)
  end

end

Ответы [ 2 ]

2 голосов
/ 11 февраля 2020

То, что вы пытаетесь сделать, это просто скопировать значение поля ввода (которое было заполнено другим вашим кодом) в системный буфер обмена. Вам нужно использовать javascript, чтобы сделать это, если у вас есть jquery, это должно сработать.

Для вашего слима вам нужен идентификатор для его нацеливания

a.btn.btn-default id= "copy"
  i.fa.fa-clipboard

Попробуйте добавить идентификатор элемента ввода, из которого вы хотите скопировать

= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true 

Теперь попробуйте изменить это и посмотрите, работает ли.

a.btn.btn-default data-clipboard-action='copy' data-clipboard-target='secret'
  i.fa.fa-clipboard

Также где-то в вашем javascript вам нужно будет нацелить событие клипа на что-то вроде этого:

new ClipboardJS('#secret');

См. пример здесь https://jsfiddle.net/ec3ywrzd/

Тогда вам понадобится это javascript для загрузки в ваш html. Но вы должны быть в состоянии нацелить поле шифра, в этом примере я использую id="secret". Я не уверен, генерирует ли ваш OTP-код свой собственный идентификатор или сейчас, поэтому вам, возможно, придется проверить своего домохозяйства, чтобы выяснить, как нацелиться на него, чтобы добавить идентификатор. Вы можете попробовать добавить идентификатор здесь:

= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true 

В противном случае вам придется использовать другие селекторы запросов для его нацеливания. Но вам может вообще не понадобиться буфер обмена js.

Вот пример basi c на jsfiddle , чтобы проверить его, вы можете просто добавить любую строку в поле ввода. Вам нужно будет добавить это в JS файл, который будет загружен вашим макетом просмотра, т.е. application.js

$(document).ready(function() {
  $('#copy').click(function(){
    $('#secret').select();
    document.execCommand('copy');
    alert("copied!");
  })
})

Вы также можете посмотреть ответы на этот вопрос

0 голосов
/ 12 февраля 2020

Мне удалось решить, основываясь на предложениях нашего друга @ lacostenycoder.

Нужно было изменить даже в шоу. html .slim файл, похожий на этот:

= simple_form_for @google_auth, as: 'google_auth', url: verify_google_auth_path do |f|
  h4 = t('.step-1')
  p
    span = t('.download-app')
    span == t('.guide-link')

  h4 = t('.step-2')
  p: span = t('.scan-qr-code')

  = f.input :uri do
    = qr_tag(@google_auth.uri)

  = f.input :otp_secret do
    .input-group
      .form-control.form-control-static = @google_auth.otp_secret
      .input-group
          a.btn.btn-default href="javascript:void(0)" data-clipboard-text = @google_auth.otp_secret
            i.fa.fa-clipboard
          a.btn.btn-default href='#{verify_google_auth_path(:app, refresh: true)}'
            i.fa.fa-refresh

  h4 = t('.step-3')
  p: span = t('.enter-passcode')

  = f.input :otp

  hr.split
  = f.button :wrapped, t('.submit'), cancel: settings_path

= content_for :guide do
  ul.list-unstyled
    li: a target='_blank' href='https://apps.apple.com/br/app/authy/id494168017'
      i.fa.fa-apple
      span = t('.ios')
    li: a target='_blank' href='https://play.google.com/store/apps/details?id=com.authy.authy'
      i.fa.fa-android
      span = t('.android')
...