Class: BellaBaxter::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/bella_baxter/client.rb

Overview

Thread-safe Bella Baxter API client backed by the Kiota-generated BellaClient.

Instantiate once and reuse across requests (e.g. in a Rails initializer). End-to-end encryption is always enabled — secret values are never visible in plaintext over the wire.

Basic usage

client = BellaBaxter::Client.new(
  baxter_url: "https://baxter.example.com",
  api_key:    "bax-..."
)
secrets = client.all_secrets.secrets
ENV["DATABASE_URL"] = secrets["DATABASE_URL"]

Prerequisites: run ‘apps/sdk/generate.sh` to generate `lib/bella_baxter/generated/` first.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(private_key: nil, on_wrapped_dek_received: nil, **opts) ⇒ Client

Returns a new instance of Client.

Parameters:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/bella_baxter/client.rb', line 50

def initialize(private_key: nil, on_wrapped_dek_received: nil, **opts)
  config = opts.size == 1 && opts.key?(:config) ? opts[:config] : Configuration.new(**opts)

  resolved_private_key = private_key || ENV["BELLA_BAXTER_PRIVATE_KEY"]
  key_pair = resolved_private_key ? E2EE::KeyPair.from_pem(resolved_private_key) : nil

  auth = HmacAuthProvider.new(config.api_key)

  @conn = Faraday.new(url: config.baxter_url.chomp("/")) do |f|
    f.headers["User-Agent"]     = "bella-ruby-sdk/1.0"
    f.headers["X-Bella-Client"] = "bella-ruby-sdk"
    f.use E2EEFaradayMiddleware, key_pair: key_pair, on_wrapped_dek_received: on_wrapped_dek_received
    f.adapter :net_http
  end

  adapter = MicrosoftKiotaFaraday::FaradayRequestAdapter.new(auth, nil, nil, @conn)
  adapter.set_base_url(config.baxter_url.chomp("/"))

  @kiota  = BellaBaxterGenerated::BellaClient.new(adapter)
  @config = config
end

Class Method Details

.from_envBellaBaxter::Client

Convenience constructor — reads BELLA_API_KEY and BELLA_BAXTER_URL from ENV.

Returns:



42
43
44
45
46
47
# File 'lib/bella_baxter/client.rb', line 42

def self.from_env
  new(
    baxter_url: ENV.fetch("BELLA_BAXTER_URL", "https://api.bella-baxter.io"),
    api_key:    ENV.fetch("BELLA_API_KEY")
  )
end

Instance Method Details

#all_secrets(project: nil, environment: nil) ⇒ BellaBaxter::AllSecretsResponse

Fetch all secrets. Project and environment are auto-discovered from the API key. Can be overridden via keyword args for advanced use cases.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/bella_baxter/client.rb', line 105

def all_secrets(project: nil, environment: nil)
  ctx         = key_context
  project     ||= ctx["projectSlug"]
  environment ||= ctx["environmentSlug"]

  resp = @kiota.api.v1.projects.by_id(project)
    .environments.by_env_slug(environment).secrets.get.resume

  secrets = resp&.secrets&.additional_data || {}

  AllSecretsResponse.new(
    environment_slug: resp&.environment_slug || environment,
    environment_name: resp&.environment_name || "",
    secrets:          secrets.transform_values(&:to_s),
    version:          resp&.version || 0,
    last_modified:    resp&.last_modified&.to_s || ""
  )
end

#clientObject

Access the full Kiota API navigator for all other endpoints (TOTP, projects, providers, environments, etc.).

Example:

client.client.api.v1.projects.by_id("my-app")
  .environments.by_env_slug("prod").totp.get


173
174
175
# File 'lib/bella_baxter/client.rb', line 173

def client
  @kiota
end

#key_contextHash

Calls GET /api/v1/keys/me to discover the project + environment this key is scoped to. Result is cached for the lifetime of the client instance.

Returns:

  • (Hash)

    parsed JSON response with ‘projectSlug’, ‘environmentSlug’, etc.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/bella_baxter/client.rb', line 78

def key_context
  @key_context ||= begin
    parts     = @config.api_key.split("-", 3)
    key_id    = parts[1]
    secret    = [parts[2]].pack("H*")
    path      = "/api/v1/keys/me"
    timestamp = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
    body_hash = Digest::SHA256.hexdigest("")
    sts       = "GET\n#{path}\n\n#{timestamp}\n#{body_hash}"
    signature = OpenSSL::HMAC.hexdigest("SHA256", secret, sts)

    resp = @conn.get(path) do |req|
      req.headers["Accept"]            = "application/json"
      req.headers["X-Bella-Key-Id"]    = key_id
      req.headers["X-Bella-Timestamp"] = timestamp
      req.headers["X-Bella-Signature"] = signature
    end
    JSON.parse(resp.body)
  end
end

#load_into_env!(project: nil, environment: nil, overwrite: false) ⇒ Integer

Convenience: load all secrets into ENV.

Secrets that are already set in ENV are NOT overwritten (existing environment variables take precedence — useful for local dev overrides).

Parameters:

  • overwrite (Boolean) (defaults to: false)

    force-overwrite existing ENV values (default: false)

Returns:

  • (Integer)

    number of secrets injected



156
157
158
159
160
161
162
163
164
165
# File 'lib/bella_baxter/client.rb', line 156

def load_into_env!(project: nil, environment: nil, overwrite: false)
  resp  = all_secrets(project: project, environment: environment)
  count = 0
  resp.secrets.each do |key, value|
    next if !overwrite && ENV.key?(key)
    ENV[key] = value
    count   += 1
  end
  count
end

#pull_secrets(project: nil, environment: nil) ⇒ Hash<String, String>

Convenience: fetch all secrets and return them as a plain Hash.

Returns:

  • (Hash<String, String>)


127
128
129
# File 'lib/bella_baxter/client.rb', line 127

def pull_secrets(project: nil, environment: nil)
  all_secrets(project: project, environment: environment).secrets
end

#secrets_version(project: nil, environment: nil) ⇒ BellaBaxter::SecretsVersionResponse

Lightweight version check. Project/environment auto-discovered from the API key.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/bella_baxter/client.rb', line 134

def secrets_version(project: nil, environment: nil)
  ctx         = key_context
  project     ||= ctx["projectSlug"]
  environment ||= ctx["environmentSlug"]

  resp = @kiota.api.v1.projects.by_id(project)
    .environments.by_env_slug(environment).secrets.version.get.resume

  SecretsVersionResponse.new(
    environment_slug: resp&.environment_slug || environment,
    version:          resp&.version || 0,
    last_modified:    resp&.last_modified&.to_s || ""
  )
end