summaryrefslogtreecommitdiff
path: root/lib/tfa/cli.rb
blob: 159ad7318fd0f8a3bf5488890bc19b49a4807b89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
require "thor"

module TFA
  class CLI < Thor
    package_name "TFA"
    class_option :filename
    class_option :directory

    desc "add NAME SECRET", "add a new secret to the database"
    def add(name, secret)
      secret = clean(secret)
      storage.save(name, secret)
      "Added #{name}"
    end

    desc "destroy NAME", "remove the secret associated with the name"
    def destroy(name)
      storage.delete(name)
    end

    desc "show NAME", "shows the secret for the given key"
    def show(name = nil)
      name ? storage.secret_for(name) : storage.all
    end

    desc "totp NAME", "generate a Time based One Time Password using the secret associated with the given NAME."
    def totp(name = nil)
      TotpCommand.new(storage).run(name)
    end

    desc "now SECRET", "generate a Time based One Time Password for the given secret"
    def now(secret)
      TotpCommand.new(storage).run('', secret)
    end

    desc "upgrade", "upgrade the pstore database to a yml database."
    def upgrade
      pstore_path = File.join(directory, ".#{filename}.pstore")
      yml_path = File.join(directory, ".#{filename}.yml")

      if !File.exist?(pstore_path)
        say "Unable to detect #{pstore_path}"
        return ""
      end

      say "Detected #{pstore_path}"
      case ask "Would you like to upgrade to #{yml_path}", limited_to: ["yes", "no"]
      when "yes"
        say "Let's begin..."
        pstore_storage = Storage.new(pstore_path)
        yaml_storage = Storage.new(yml_path)
        pstore_storage.each do |row|
          row.each do |name, secret|
            case ask "Would you like to migrate `#{name}`?", limited_to: ["yes", "no"]
            when "yes"
              say "Migrating `#{name}`..."
              yaml_storage.save(name, secret)
            end
          end
        end
        case ask "Would you like to delete `#{pstore_path}`? (this action cannot be undone.)", limited_to: ["yes", "no"]
        when "yes"
          File.delete(pstore_path)
        end
      else
        say "Nothing to do. Goodbye!"
      end
      ""
    end

    private

    def storage
      @storage ||= Storage.new(File.exist?(pstore_path) ? pstore_path : yml_path)
    end

    def filename
      options[:filename] || 'tfa'
    end

    def directory
      options[:directory] || Dir.home
    end

    def pstore_path
      File.join(directory, ".#{filename}.pstore")
    end

    def yml_path
      File.join(directory, ".#{filename}.yml")
    end

    def clean(secret)
      if secret.include?("=")
        /secret=([^&]*)/.match(secret).captures.first
      else
        secret
      end
    end
  end
end