vaultkit 0.1.0 → 0.1.2

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.
@@ -157,7 +157,7 @@ module Vkit
157
157
 
158
158
  desc "deploy", "Deploy a policy bundle to VaultKit"
159
159
  option :bundle, type: :string, default: "dist/policy_bundle.json"
160
- option :org, type: :string, required: true
160
+ option :org, type: :string
161
161
  option :activate, type: :boolean, default: true
162
162
 
163
163
  def deploy
@@ -1,3 +1,5 @@
1
+ require_relative "../api/client"
2
+
1
3
  module Vkit
2
4
  module CLI
3
5
  module Commands
@@ -31,7 +31,7 @@ module Vkit
31
31
  result =
32
32
  case auth
33
33
  when "oidc"
34
- oidc_flow(client, discovery["oidc"]["login_url"])
34
+ oidc_flow(client)
35
35
  when "password"
36
36
  password_flow(client)
37
37
  when "token"
@@ -55,20 +55,20 @@ module Vkit
55
55
 
56
56
  private
57
57
 
58
- def oidc_flow(client, login_url)
58
+ def oidc_flow(client)
59
59
  start = client.start_cli_login
60
60
  poll_token = start["poll_token"]
61
-
61
+ login_url = start["login_url"]
62
+
62
63
  open_browser(login_url)
63
64
  puts "⏳ Waiting for authentication to complete..."
64
-
65
+
65
66
  loop do
66
67
  res = client.poll_cli_login(poll_token)
67
-
68
+
68
69
  case res.code.to_i
69
70
  when 204
70
71
  sleep 2
71
- next
72
72
  when 200
73
73
  body = JSON.parse(res.body)
74
74
  return {
@@ -83,7 +83,7 @@ module Vkit
83
83
  raise "Unexpected response: #{res.code}"
84
84
  end
85
85
  end
86
- end
86
+ end
87
87
 
88
88
  def password_flow(client)
89
89
  email = @email || prompt("Email")
@@ -3,6 +3,21 @@ module Vkit
3
3
  module Commands
4
4
  class LogoutCommand < BaseCommand
5
5
  def call
6
+ token = credential_store.token
7
+
8
+ if token
9
+ begin
10
+ client = Vkit::Core::AuthClient.new(
11
+ base_url: credential_store.endpoint
12
+ )
13
+
14
+ client.logout(token)
15
+ rescue => e
16
+ warn "⚠️ Server logout failed: #{e.message}"
17
+ warn "⚠️ Continuing with local logout"
18
+ end
19
+ end
20
+
6
21
  credential_store.clear_token!
7
22
  puts "🧹 Logged out"
8
23
  end
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
4
+ require "fileutils"
2
5
  require_relative "../../policy/bundle_compiler"
3
6
 
4
7
  module Vkit
5
8
  module CLI
6
9
  module Commands
7
- class PolicyBundleCommand
10
+ class PolicyBundleCommand < BaseCommand
8
11
  def call(policies_dir:, registry_dir:, out:, org:, version:)
9
12
  policies_dir = File.expand_path(policies_dir)
10
13
  registry_dir = File.expand_path(registry_dir)
@@ -15,26 +18,46 @@ module Vkit
15
18
 
16
19
  version ||= git_sha
17
20
 
18
- bundle = Vkit::Policy::BundleCompiler.compile!(
19
- org_slug: org || "unknown",
20
- bundle_version: version,
21
- policies_dir: policies_dir,
22
- registry_dir: registry_dir,
23
- source: {
24
- repo: git_repo,
25
- ref: git_ref,
26
- commit_sha: version
27
- }
28
- )
29
-
30
- FileUtils.mkdir_p(File.dirname(out))
31
- File.write(out, JSON.pretty_generate(bundle))
32
-
33
- puts "✅ Policy bundle created"
34
- puts " Org: #{bundle.dig("bundle", "org_slug")}"
35
- puts " Version: #{bundle.dig("bundle", "bundle_version")}"
36
- puts " Checksum: #{bundle.dig("bundle", "checksum")}"
37
- puts " Output: #{out}"
21
+ with_auth do
22
+ derived_org = credential_store.user["organization_slug"]
23
+
24
+ raise "Unable to determine organization from credentials. Please login." \
25
+ if derived_org.nil? || derived_org.empty?
26
+
27
+ if org && org != derived_org
28
+ raise <<~MSG
29
+ Organization mismatch detected.
30
+
31
+ Authenticated organization: #{derived_org}
32
+ Provided via --org: #{org}
33
+
34
+ Refusing to continue to prevent cross-organization policy bundles.
35
+ MSG
36
+ end
37
+
38
+ org_slug = org || derived_org
39
+
40
+ bundle = Vkit::Policy::BundleCompiler.compile!(
41
+ org_slug: org_slug,
42
+ bundle_version: version,
43
+ policies_dir: policies_dir,
44
+ registry_dir: registry_dir,
45
+ source: {
46
+ repo: git_repo,
47
+ ref: git_ref,
48
+ commit_sha: version
49
+ }
50
+ )
51
+
52
+ FileUtils.mkdir_p(File.dirname(out))
53
+ File.write(out, JSON.pretty_generate(bundle))
54
+
55
+ puts "✅ Policy bundle created"
56
+ puts " Org: #{bundle.dig("bundle", "org_slug")}"
57
+ puts " Version: #{bundle.dig("bundle", "bundle_version")}"
58
+ puts " Checksum: #{bundle.dig("bundle", "checksum")}"
59
+ puts " Output: #{out}"
60
+ end
38
61
  end
39
62
 
40
63
  private
@@ -11,10 +11,28 @@ module Vkit
11
11
  bundle_path = File.expand_path(bundle_path)
12
12
  raise "Bundle not found: #{bundle_path}" unless File.exist?(bundle_path)
13
13
 
14
+ derived_org = credential_store.user["organization_slug"]
15
+
16
+ raise "Unable to determine organization from credentials. Please login." \
17
+ if derived_org.nil? || derived_org.empty?
18
+
19
+ if org && org != derived_org
20
+ raise <<~MSG
21
+ Organization mismatch detected.
22
+
23
+ Authenticated organization: #{derived_org}
24
+ Provided via --org: #{org}
25
+
26
+ Refusing to deploy policy bundle to a different organization.
27
+ MSG
28
+ end
29
+
30
+ org_slug = org || derived_org
31
+
14
32
  bundle = JSON.parse(File.read(bundle_path))
15
33
 
16
34
  response = authenticated_client.post(
17
- "/api/v1/orgs/#{org}/policy_bundles",
35
+ "/api/v1/orgs/#{org_slug}/policy_bundles",
18
36
  body: {
19
37
  bundle: bundle,
20
38
  activate: activate
@@ -22,6 +40,7 @@ module Vkit
22
40
  )
23
41
 
24
42
  puts "🚀 Policy bundle deployed"
43
+ puts " Org: #{org_slug}"
25
44
  puts " Version: #{response['bundle_version']}"
26
45
  puts " State: #{response['state']}"
27
46
  end
@@ -3,10 +3,40 @@ module Vkit
3
3
  module Commands
4
4
  class WhoamiCommand < BaseCommand
5
5
  def call
6
- with_auth do
7
- user = credential_store.user
8
- puts "👤 #{user['email']} (role: #{user['role']}, org: #{user['organization_slug']})"
9
- end
6
+ user = fetch_user_from_server_or_fallback
7
+
8
+ puts "👤 #{user['email']} " \
9
+ "(role: #{user['role']}, org: #{user['organization_slug']})"
10
+ end
11
+
12
+ private
13
+
14
+ def fetch_user_from_server_or_fallback
15
+ token = credential_store.token
16
+ endpoint = credential_store.endpoint
17
+
18
+ raise "Not logged in" if token.nil? || endpoint.nil?
19
+
20
+ client = Vkit::Core::AuthClient.new(base_url: endpoint)
21
+ server_user = client.whoami(token)
22
+
23
+ # keep cache in sync if server is authoritative
24
+ credential_store.save_user(server_user)
25
+
26
+ server_user
27
+ rescue => e
28
+ fallback_local_user(e)
29
+ end
30
+
31
+ def fallback_local_user(error)
32
+ user = credential_store.user
33
+ raise "Not logged in" if user.nil?
34
+
35
+ warn "⚠️ Unable to verify identity with server"
36
+ warn "⚠️ #{error.message}"
37
+ warn "⚠️ Showing cached identity"
38
+
39
+ user
10
40
  end
11
41
  end
12
42
  end
@@ -29,11 +29,15 @@ module Vkit
29
29
  end
30
30
 
31
31
  def poll_cli_login(poll_token)
32
- uri = uri_for("/auth/cli/poll?token=#{poll_token}")
33
- req = Net::HTTP::Get.new(uri)
34
-
32
+ uri = uri_for("/auth/cli/poll")
33
+ req = Net::HTTP::Post.new(uri)
34
+ req["Content-Type"] = "application/json"
35
+ req.body = JSON.dump(
36
+ poll_token: poll_token
37
+ )
38
+
35
39
  http_request(uri, req, allow_non_200: true)
36
- end
40
+ end
37
41
 
38
42
  def password_login(email:, password:)
39
43
  uri = uri_for("/api/users/sign_in")
@@ -66,6 +70,14 @@ module Vkit
66
70
  body["user"]
67
71
  end
68
72
 
73
+ def logout(token)
74
+ uri = uri_for("/api/users/sign_out")
75
+ req = Net::HTTP::Delete.new(uri)
76
+ req["Authorization"] = "Bearer #{token}"
77
+
78
+ http_request(uri, req, allow_non_200: true)
79
+ end
80
+
69
81
  private
70
82
 
71
83
  def uri_for(path)
@@ -33,6 +33,22 @@ module Vkit
33
33
  true
34
34
  end
35
35
 
36
+ def save_user(user)
37
+ payload = load_payload
38
+ return unless payload
39
+
40
+ payload["user"] = user
41
+
42
+ case
43
+ when mac?
44
+ mac_keychain_store(payload)
45
+ when linux? && secret_tool_available?
46
+ linux_secret_service_store(payload)
47
+ else
48
+ file_store(payload)
49
+ end
50
+ end
51
+
36
52
  def endpoint
37
53
  load_payload&.dig("endpoint")
38
54
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vaultkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nnamdi Ogundu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-03 00:00:00.000000000 Z
11
+ date: 2026-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -68,7 +68,7 @@ files:
68
68
  - lib/vkit/utils/logger.rb
69
69
  homepage: https://vaultkit.io
70
70
  licenses:
71
- - Proprietary
71
+ - Nonstandard
72
72
  metadata:
73
73
  rubygems_mfa_required: 'true'
74
74
  source_code_uri: https://github.com/ndbaba1/vaultkitcli