You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

otp.rb 2.0KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. require 'cgi'
  2. require 'rotp'
  3. require 'tempfile'
  4. require 'uri'
  5. require 'rqrcode'
  6. module OtpCli
  7. class OTP
  8. attr_reader :name, :issuer
  9. def self.get(line)
  10. uri = URI line
  11. type = uri.host.to_sym
  12. name = uri.path.sub %r{^/}, ''
  13. query = CGI.parse uri.query
  14. secret = query['secret'].first
  15. issuer = query['issuer'].first
  16. algorithm = query['algorithm'].first || 'sha1'
  17. digits = numeric_value query, 'digits', 6
  18. counter = numeric_value query, 'counter'
  19. period = numeric_value query, 'period', 30
  20. case type
  21. when :hotp then
  22. HOTP.new line, name, secret, issuer, algorithm, digits, counter
  23. when :totp then
  24. TOTP.new line, name, secret, issuer, algorithm, digits, period
  25. end
  26. end
  27. def qrcode
  28. ::Tempfile.open %w[qrcode .png] do |file|
  29. qrcode = RQRCode::QRCode.new @line
  30. IO.write file, qrcode.as_png
  31. yield file
  32. end
  33. end
  34. def to_s
  35. s = self.name.clone
  36. s << %{ (#{self.issuer})} unless self.issuer.nil?
  37. s
  38. end
  39. def <=>(other)
  40. self.to_s.downcase <=> other.to_s.downcase
  41. end
  42. protected
  43. def initialize(otp, line, name, secret, issuer=nil, algorithm='sha1', digits=6)
  44. @otp = otp
  45. @line = line
  46. @name = name
  47. @secret = secret
  48. @issuer = issuer
  49. @algorithm = algorithm
  50. @digits = digits
  51. end
  52. private_class_method
  53. def self.numeric_value(query, name, default_value = nil)
  54. value = query[name].first
  55. return default_value if value.nil?
  56. value.to_i
  57. end
  58. end
  59. class HOTP < OTP
  60. def initialize(line, name, secret, issuer=nil, algorithm='sha1', digits=6, counter=0)
  61. super line, name, secret, issuer, algorithm, digits
  62. @counter = counter
  63. end
  64. end
  65. class TOTP < OTP
  66. def initialize(line, name, secret, issuer=nil, algorithm='sha1', digits=6, period=30)
  67. @period = period
  68. otp = ::ROTP::TOTP.new secret, digits: digits, digest: algorithm, interval: period
  69. super otp, line, name, secret, issuer, algorithm, digits
  70. end
  71. def code
  72. @otp.now
  73. end
  74. def delay
  75. @period - (Time.now.to_i % @period)
  76. end
  77. end
  78. end