we-promise/sure · error · Provider::Sophtron::Error

invalid_access_key

invalid_access_key

Error message

Invalid Sophtron Access Key: #{e.message}

What it means

Raised by Provider::Sophtron#auth_header_for when building the FIApiAUTH HMAC-SHA256 signature fails with an ArgumentError - most commonly because Base64-decoding the stored access_key produced empty bytes ('decoded key is empty'). The Sophtron access key must be a Base64-encoded HMAC key; a blank, nil, or non-key string fails here before any HTTP request is made.

Source

Thrown at app/models/provider/sophtron.rb:42

  attr_reader :user_id, :access_key, :base_url

  def initialize(user_id, access_key, base_url: DEFAULT_BASE_URL)
    @user_id = user_id
    @access_key = access_key
    @base_url = normalize_base_url(base_url)
    super()
  end

  def auth_header_for(method, api_path)
    auth_path = self.class.auth_path(api_path)
    plain_key = "#{method.to_s.upcase}\n#{auth_path}"
    key_bytes = Base64.decode64(access_key.to_s)
    raise ArgumentError, "decoded key is empty" if key_bytes.blank?
    signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), key_bytes, plain_key)
    "FIApiAUTH:#{user_id}:#{Base64.strict_encode64(signature)}:#{auth_path}"
  rescue ArgumentError => e
    raise Error.new("Invalid Sophtron Access Key: #{e.message}", :invalid_access_key)
  end

  def self.auth_path(api_path)
    path = URI.parse(api_path.to_s).path
    last_segment = path.to_s.split("/").last.to_s
    "/#{last_segment}".downcase
  rescue URI::InvalidURIError
    last_segment = api_path.to_s.split("?").first.to_s.split("/").last.to_s
    "/#{last_segment}".downcase
  end

  def self.job_success?(job)
    job = job.with_indifferent_access
    job[:SuccessFlag] == true || job[:success_flag] == true || job[:LastStatus].to_s == "AccountsReady" || job[:last_status].to_s == "AccountsReady"
  end

  def self.job_failed?(job)
    job = job.with_indifferent_access

View on GitHub (pinned to e69894adb9)

Solutions

  1. Check that the access_key passed to Provider::Sophtron.new is present and non-blank in the failing environment
  2. Re-copy the key from the Sophtron dashboard, ensuring it is the Base64 access key (not the user ID), with no trailing newline
  3. Verify it decodes to non-empty bytes: Base64.decode64(key).length must be > 0 before constructing the client
  4. After fixing the key, confirm end-to-end with a lightweight call - if a 401 follows, the user_id is the wrong half of the pair

Example fix

# before - constructing the client with possibly-missing config
client = Provider::Sophtron.new(user_id, ENV["SOPHTRON_ACCESS_KEY"])

# after - fail fast on an unusable key
key = ENV["SOPHTRON_ACCESS_KEY"].to_s
raise ArgumentError, "SOPHTRON_ACCESS_KEY missing or empty" if Base64.decode64(key).bytesize.zero?
client = Provider::Sophtron.new(user_id, key)
Defensive patterns

Strategy: validation

Validate before calling

key = credentials.access_key.to_s
if key.blank? || Base64.decode64(key).bytesize.zero?
  raise ArgumentError, "Sophtron access key is missing or decodes empty - check SOPHTRON_ACCESS_KEY"
end
client = Provider::Sophtron.new(credentials.user_id, key)

Type guard

def usable_sophtron_key?(key)
  decoded = Base64.decode64(key.to_s)
  !key.to_s.blank? && decoded.bytesize.positive?
end

Try / catch

begin
  client.get_accounts
rescue Provider::Sophtron::Error => e
  raise ConfigError, "Re-link Sophtron credentials" if e.error_type == :invalid_access_key
  raise
end

Prevention

When it happens

Trigger: Instantiating Provider::Sophtron with an empty/nil access_key (missing SOPHTRON_ACCESS_KEY env var), a placeholder value, or a string whose Base64 decode yields zero bytes; every request method calls auth_headers -> auth_header_for, so the first API call after construction raises.

Common situations: SOPHTRON_ACCESS_KEY never set in a new environment (staging/CI); key cleared during a credentials rotation but not replaced; whitespace/newline pasted with the key making decode succeed-but-wrong (then auth fails as 401 instead); key set on a different Rails env than the one running.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/df35f4107d29f19a. Report an issue: GitHub.