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.

93 lines
2.0KB

  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