vaultez-cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '07770618a022bbabf6a89511dbeb1366ead244d90fc938435aba94bb9e7ccbbd'
4
- data.tar.gz: 03afca8c6b3846442c78791cddda0232b9882d37104708b31ce8e38e4e985a11
3
+ metadata.gz: f2898cfe0e904ce07df993ad6cf78fd5c66b0843a6a90dccafe0fc21f94c462a
4
+ data.tar.gz: 1792ede773e3d10f26b7a233719690ec660402b21ce1e086e7d1a8b47f7706d9
5
5
  SHA512:
6
- metadata.gz: 5346e840aa94d43ea44fb8c6a0394bc511e1d139bc812f2fcf95c4616619e35a210aa8b94b2d7616b7dfaa271a13bb8759674ae5b752c8d0239fa6742d9a50d4
7
- data.tar.gz: 2dda8d7e5fb79f72d3b8b5b65dc338a5af379149b28cd949984a4ce24ad6db86b5d7b9d138eec86f331278d83246da738c5aa0235fcfa723b13c7f32db6a6614
6
+ metadata.gz: e920faa7ff226351174c5eb7ada3ce7c71e06a8cc0d7dabff2171d60bf7ce244afb52543763351c9705a9951474c3dfe66ae814c1af9b0a1bd089138eb573bea
7
+ data.tar.gz: 5efac1f679ae408baaf10bef7de70dd0c76601419f3b2b48daccf60fc1e36c635ca5546561623b55628292653e525d23e49c21fd0fbb73ffe075b10eb6d3037f
data/lib/vaultez/cli.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "thor"
2
+ require_relative "version"
2
3
  require_relative "commands/auth"
3
4
  require_relative "commands/fetch"
4
5
  require_relative "commands/config_command"
@@ -9,17 +10,30 @@ module Vaultez
9
10
  include Vaultez::Commands::Fetch
10
11
  include Vaultez::Commands::ConfigCommand
11
12
 
12
- desc "login", "Authenticate with Vaultez"
13
+ map %w[--version -v] => :__version
14
+
15
+ class_option :token, type: :string, banner: "TOKEN",
16
+ desc: "Use a project token instead of the saved session"
17
+
18
+ def initialize(*args)
19
+ super
20
+ ENV["VAULTEZ_TOKEN"] = options[:token] if options[:token]
21
+ end
22
+
23
+ desc "login", "Authenticate with email, password, and 2FA code"
13
24
  long_desc <<~DESC, wrap: false
14
25
  Authenticate with your Vaultez account. You will be prompted for your
15
- email and password. Your session token is stored in ~/.vaultez/config.yml.
26
+ email, password, and a two-factor authentication code from your authenticator
27
+ app (or a backup code). Your session token is stored in ~/.vaultez/config.yml.
28
+
29
+ Two-factor authentication is required for all accounts.
16
30
 
17
31
  Example:
18
32
  vaultez login
19
33
  DESC
20
34
  def login; super; end
21
35
 
22
- desc "logout", "Log out and revoke your token"
36
+ desc "logout", "Revoke the current session token"
23
37
  long_desc <<~DESC, wrap: false
24
38
  Revokes your session token on the server and clears your local credentials.
25
39
 
@@ -28,25 +42,36 @@ module Vaultez
28
42
  DESC
29
43
  def logout; super; end
30
44
 
31
- desc "fetch", "Fetch companies, projects, or secrets"
45
+ desc "fetch", "Fetch secrets from a project"
32
46
  long_desc <<~DESC, wrap: false
33
- Fetch resources from Vaultez. The --company flag is optional when you
34
- have a default company set or only belong to one company.
47
+ Fetch resources from Vaultez.
48
+
49
+ USER SESSION (after `vaultez login`):
50
+ The --company flag is optional when you have a default company set or
51
+ only belong to one company. Use --project to scope to a project.
35
52
 
36
- Examples:
37
53
  vaultez fetch --companies
38
54
  vaultez fetch --company="Acme" --projects
39
55
  vaultez fetch --company="Acme" --project="Backend"
40
56
  vaultez fetch --company="Acme" --project="Backend" --secret="DATABASE_URL"
57
+
58
+ PROJECT TOKEN (VAULTEZ_TOKEN env var or --token flag):
59
+ When a project token is active, it already knows which project to use.
60
+ No --project flag is needed.
61
+
62
+ VAULTEZ_TOKEN=vz_... vaultez fetch
63
+ vaultez fetch --token=vz_... --secret="DATABASE_URL"
64
+
65
+ Project tokens can be created in the Tokens tab of your project settings.
41
66
  DESC
42
67
  option :companies, type: :boolean, desc: "List all your companies"
43
68
  option :company, type: :string, desc: "Company name"
44
69
  option :projects, type: :boolean, desc: "List projects in a company"
45
70
  option :project, type: :string, desc: "Project name"
46
- option :secret, type: :string, desc: "Secret name"
71
+ option :secret, type: :string, desc: "Secret name (returns value only)"
47
72
  def fetch; super; end
48
73
 
49
- desc "config", "Configure Vaultez CLI settings"
74
+ desc "config", "Set default company or token"
50
75
  long_desc <<~DESC, wrap: false
51
76
  Update your local Vaultez CLI configuration.
52
77
 
@@ -55,5 +80,38 @@ module Vaultez
55
80
  DESC
56
81
  option :"default-company", type: :string, desc: "Set the default company"
57
82
  def config; super; end
83
+
84
+ desc "__version", "Show the installed CLI version", hide: true
85
+ def __version
86
+ puts "vaultez-cli #{Vaultez::VERSION}"
87
+ end
88
+
89
+ def help(command = nil, subcommand: false)
90
+ if command
91
+ super
92
+ else
93
+ puts "Vaultez CLI — secure secret management from your terminal"
94
+ puts
95
+ puts "Usage: vaultez <command> [options]"
96
+ puts
97
+ puts "Commands:"
98
+ puts " login Authenticate with email, password, and 2FA code"
99
+ puts " logout Revoke the current session token"
100
+ puts " fetch Fetch secrets from a project"
101
+ puts " config Set default company or token"
102
+ puts " help Show help for any command"
103
+ puts
104
+ puts "Options:"
105
+ puts " --token Use a project token instead of the saved session"
106
+ puts " --version Show the installed CLI version"
107
+ puts
108
+ puts "Examples:"
109
+ puts " vaultez login"
110
+ puts " vaultez fetch --project=\"Backend\""
111
+ puts " vaultez fetch --project=\"Backend\" --secret=\"DATABASE_URL\""
112
+ puts " vaultez fetch --token=\"vz_...\""
113
+ puts " vaultez fetch --token=\"vz_...\" --secret=\"DATABASE_URL\""
114
+ end
115
+ end
58
116
  end
59
117
  end
@@ -5,18 +5,23 @@ require "uri"
5
5
  module Vaultez
6
6
  class Client
7
7
  def initialize
8
- @api_url = Vaultez::Config.api_url
9
- @token = Vaultez::Config.token
8
+ @api_url = Vaultez::Config.api_url
9
+ @token = Vaultez::Config.token
10
+ @project_token = ENV["VAULTEZ_TOKEN"]
10
11
  end
11
12
 
12
- def login(email, password)
13
- post("/api/v1/auth/login", { email: email, password: password }, authenticated: false)
13
+ def login(email, password, otp_code)
14
+ post("/api/v1/auth/login", { email: email, password: password, otp_code: otp_code }, authenticated: false)
14
15
  end
15
16
 
16
17
  def logout
17
18
  delete("/api/v1/auth/logout")
18
19
  end
19
20
 
21
+ def project_token_mode?
22
+ !@project_token.nil?
23
+ end
24
+
20
25
  def companies
21
26
  get("/api/v1/companies")
22
27
  end
@@ -53,8 +58,9 @@ module Vaultez
53
58
  req["Accept"] = "application/json"
54
59
 
55
60
  if authenticated
56
- raise Vaultez::NotAuthenticatedError, "Not logged in. Run `vaultez login` first." unless @token
57
- req["Authorization"] = "Bearer #{@token}"
61
+ active_token = @project_token || @token
62
+ raise Vaultez::NotAuthenticatedError, "Not logged in. Run `vaultez login` first, or set VAULTEZ_TOKEN." unless active_token
63
+ req["Authorization"] = "Bearer #{active_token}"
58
64
  end
59
65
 
60
66
  req.body = body.to_json if body
@@ -75,9 +81,10 @@ module Vaultez
75
81
 
76
82
  case response.code.to_i
77
83
  when 200, 201 then body
78
- when 401 then raise Vaultez::AuthenticationError, body["error"] || "Authentication failed"
79
- when 404 then raise Vaultez::NotFoundError, body["error"] || "Not found"
80
- else raise Vaultez::ApiError, body["error"] || "API error (#{response.code})"
84
+ when 401 then raise Vaultez::AuthenticationError, body["error"] || "Authentication failed"
85
+ when 403 then raise Vaultez::TwoFactorRequiredError, body["error"] || "Two-factor authentication required"
86
+ when 404 then raise Vaultez::NotFoundError, body["error"] || "Not found"
87
+ else raise Vaultez::ApiError, body["error"] || "API error (#{response.code})"
81
88
  end
82
89
  end
83
90
  end
@@ -11,11 +11,18 @@ module Vaultez
11
11
  system("stty echo")
12
12
  puts
13
13
 
14
+ puts "One-time code (Proton Authenticator / TOTP): "
15
+ otp_code = $stdin.gets.chomp
16
+
14
17
  client = Vaultez::Client.new
15
- response = client.login(email, password)
18
+ response = client.login(email, password, otp_code)
16
19
 
17
20
  Vaultez::Config.set("token", response["token"])
18
21
  puts "Logged in successfully."
22
+ rescue Vaultez::TwoFactorRequiredError => error
23
+ puts "Error: #{error.message}"
24
+ puts "Set up two-factor authentication at https://vaultez.app/two_factor/new"
25
+ exit 1
19
26
  rescue Vaultez::AuthenticationError => error
20
27
  puts "Error: #{error.message}"
21
28
  exit 1
@@ -4,7 +4,9 @@ module Vaultez
4
4
  def fetch
5
5
  client = Vaultez::Client.new
6
6
 
7
- if options[:companies]
7
+ if client.project_token_mode?
8
+ fetch_with_project_token(client)
9
+ elsif options[:companies]
8
10
  fetch_companies(client)
9
11
  elsif options[:projects]
10
12
  fetch_projects(client)
@@ -29,6 +31,41 @@ module Vaultez
29
31
 
30
32
  private
31
33
 
34
+ def fetch_with_project_token(client)
35
+ if options[:secret]
36
+ secrets = fetch_all_secrets_for_token(client)
37
+ secret = secrets.find { |s| s["name"] == options[:secret] }
38
+ unless secret
39
+ puts "Error: secret \"#{options[:secret]}\" not found."
40
+ exit 1
41
+ end
42
+ print secret["value"]
43
+ else
44
+ secrets = fetch_all_secrets_for_token(client)
45
+ if secrets.empty?
46
+ puts "No secrets found."
47
+ return
48
+ end
49
+ secrets.each { |s| puts "#{s["name"]}=#{s["value"]}" }
50
+ end
51
+ end
52
+
53
+ def fetch_all_secrets_for_token(client)
54
+ companies = client.companies
55
+ company = companies.first
56
+ unless company
57
+ puts "Error: no company found for this token."
58
+ exit 1
59
+ end
60
+ projects = client.projects(company["id"])
61
+ project = projects.first
62
+ unless project
63
+ puts "Error: no project found for this token."
64
+ exit 1
65
+ end
66
+ client.secrets(project["id"])
67
+ end
68
+
32
69
  def fetch_companies(client)
33
70
  companies = client.companies
34
71
  if companies.empty?
@@ -71,7 +108,7 @@ module Vaultez
71
108
  company = resolve_company(client)
72
109
  project = resolve_project(client, company)
73
110
  secrets = client.secrets(project["id"])
74
- secret = secrets.find { |secret| secret["name"] == options[:secret] }
111
+ secret = secrets.find { |s| s["name"] == options[:secret] }
75
112
 
76
113
  unless secret
77
114
  puts "Error: secret \"#{options[:secret]}\" not found in #{project["name"]}."
@@ -85,7 +122,7 @@ module Vaultez
85
122
  companies = client.companies
86
123
 
87
124
  if options[:company]
88
- company = companies.find { |company| company["name"] == options[:company] }
125
+ company = companies.find { |c| c["name"] == options[:company] }
89
126
  unless company
90
127
  puts "Error: company \"#{options[:company]}\" not found."
91
128
  exit 1
@@ -95,13 +132,11 @@ module Vaultez
95
132
 
96
133
  default_name = Vaultez::Config.default_company
97
134
  if default_name
98
- company = companies.find { |company| company["name"] == default_name }
135
+ company = companies.find { |c| c["name"] == default_name }
99
136
  return company if company
100
137
  end
101
138
 
102
- if companies.size == 1
103
- return companies.first
104
- end
139
+ return companies.first if companies.size == 1
105
140
 
106
141
  puts "Error: multiple companies found. Specify one with --company or set a default with `vaultez config --default-company`."
107
142
  exit 1
@@ -109,7 +144,7 @@ module Vaultez
109
144
 
110
145
  def resolve_project(client, company)
111
146
  projects = client.projects(company["id"])
112
- project = projects.find { |project| project["name"] == options[:project] }
147
+ project = projects.find { |p| p["name"] == options[:project] }
113
148
  unless project
114
149
  puts "Error: project \"#{options[:project]}\" not found in #{company["name"]}."
115
150
  exit 1
@@ -26,7 +26,7 @@ module Vaultez
26
26
  end
27
27
 
28
28
  def self.token
29
- get("token")
29
+ ENV["VAULTEZ_TOKEN"] || get("token")
30
30
  end
31
31
 
32
32
  def self.api_url
@@ -1,7 +1,8 @@
1
1
  module Vaultez
2
- class Error < StandardError; end
3
- class NotAuthenticatedError < Error; end
4
- class AuthenticationError < Error; end
5
- class NotFoundError < Error; end
6
- class ApiError < Error; end
2
+ class Error < StandardError; end
3
+ class NotAuthenticatedError < Error; end
4
+ class AuthenticationError < Error; end
5
+ class TwoFactorRequiredError < Error; end
6
+ class NotFoundError < Error; end
7
+ class ApiError < Error; end
7
8
  end
@@ -1,3 +1,3 @@
1
1
  module Vaultez
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vaultez-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nur Ketene