Tengo una página de verificación de dos factores, se muestra una clave secreta (texto cifrado) y ya tengo clipboard.js instalado en mi aplicación.
Me pregunto cómo es posible crear un botón para copiar esa clave secreta.
= 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')
Traté de hacer esto, pero no funcionó:
a.btn.btn-default data-clipboard-action='copy' data-clipboard-target=':otp_secret'
i.fa.fa-clipboard
En el ejemplo anterior, solo copia el texto otp_secret puro.
Spec \ models \ two_factor \ app_spec.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
EDITAR: 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 respuestas
Logré resolverlo en base a las sugerencias de nuestro amigo @lacostenycoder.
Solo había una necesidad de cambiar incluso en el archivo show.html.slim, con este aspecto:
= 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')
Lo que parece que estás tratando de hacer es copiar el valor de un campo de entrada (que ha sido poblado por otro código que tienes) en el portapapeles del sistema. Necesitas usar javascript para hacer esto, si tienes jquery esto debería funcionar.
Para su delgado necesita una identificación para apuntarlo
a.btn.btn-default id= "copy"
i.fa.fa-clipboard
Intente agregar una identificación al elemento de entrada desde el que desea copiar
= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true
Ahora intenta cambiar esto y ver si funciona.
a.btn.btn-default data-clipboard-action='copy' data-clipboard-target='secret'
i.fa.fa-clipboard
Además, en algún lugar de su JavaScript, tendrá que orientar el evento de clip con algo como esto:
new ClipboardJS('#secret');
Vea el ejemplo aquí https://jsfiddle.net/ec3ywrzd/
Entonces necesitará este javascript para cargar en su html. Pero deberá poder orientar el campo de cifrado, en este ejemplo estoy usando id="secret"
. No estoy seguro si el código OTP que tiene genera su propia ID o ahora, por lo que es posible que deba inspeccionar su dom para descubrir cómo apuntarlo para agregar una ID. Puede intentar agregar una identificación aquí:
= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true
De lo contrario, tendrá que usar otros selectores de consulta para orientarlo. Pero es posible que no necesite clipboardjs en absoluto.
Aquí hay un ejemplo básico en jsfiddle para probarlo, simplemente puede agregar cualquier cadena al campo de entrada. Deberá agregar esto a un archivo JS que se cargará según su diseño de vista, es decir, application.js
$(document).ready(function() {
$('#copy').click(function(){
$('#secret').select();
document.execCommand('copy');
alert("copied!");
})
})
También puede ver las respuestas a esta pregunta
Preguntas relacionadas
Nuevas preguntas
ruby
Ruby es un lenguaje interpretado dinámico orientado a objetos, multiplataforma de código abierto. La etiqueta [ruby] es para preguntas relacionadas con el lenguaje Ruby, incluida su sintaxis y sus bibliotecas. Las preguntas de Ruby on Rails deben etiquetarse con [ruby-on-rails].