You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/lib/provision_token.rb

47 lines
1.5 KiB

# frozen_string_literal: true
# Verifier for the HMAC-signed provisioning token minted by the parent EHR app.
#
# Token format: `body + "." + sig` where
# body = Base64.urlsafe_encode64(payload_json, padding: false) (compact JSON)
# sig = OpenSSL::HMAC.hexdigest("SHA256", secret, body)
#
# The shared secret lives in ENV["DOCUSEAL_PROVISION_SECRET"]. Fail closed on
# everything: a blank secret, a malformed/missing token, a signature mismatch,
# or an expired `exp` claim all return nil — never a half-trusted payload.
module ProvisionToken
ENV_KEY = 'DOCUSEAL_PROVISION_SECRET'
module_function
def secret
ENV[ENV_KEY].presence
end
# Returns the decoded payload Hash (string keys) when the token is valid and
# unexpired, otherwise nil.
def verify(token, secret: self.secret)
return if secret.blank?
return if token.blank?
body, sig = token.split('.', 2)
return if body.blank? || sig.blank?
expected = OpenSSL::HMAC.hexdigest('SHA256', secret, body)
return unless ActiveSupport::SecurityUtils.secure_compare(sig, expected)
payload = JSON.parse(Base64.urlsafe_decode64(body))
return unless payload.is_a?(Hash)
exp = payload['exp']
return unless exp.is_a?(Integer)
return if exp <= Time.now.to_i
payload
rescue ArgumentError, JSON::ParserError
# Base64.urlsafe_decode64 raises ArgumentError on malformed input;
# JSON.parse raises JSON::ParserError. Both mean "not a valid token".
nil
end
end