diff --git a/spec/kemal-hmac/kemal-hmac_spec.cr b/spec/kemal-hmac/kemal-hmac_spec.cr index 3a750d0..06b5929 100644 --- a/spec/kemal-hmac/kemal-hmac_spec.cr +++ b/spec/kemal-hmac/kemal-hmac_spec.cr @@ -50,6 +50,9 @@ describe "Kemal::Hmac" do timestamp = Time::Format::ISO_8601_DATE_TIME.format(Time.utc) hmac_token = Kemal::Hmac::Token.new(client, "/api", timestamp).hexdigest("octo-secret-green") + hmac_client = Kemal::Hmac::Client.new(client, "octo-secret-green", "SHA256") + headers = hmac_client.generate_headers("/api") + request = HTTP::Request.new( "GET", "/api", diff --git a/src/kemal-hmac.cr b/src/kemal-hmac.cr index a6c56a8..58a91e9 100644 --- a/src/kemal-hmac.cr +++ b/src/kemal-hmac.cr @@ -4,6 +4,8 @@ require "./kemal-hmac/**" module Kemal module Hmac + class InvalidSecretError < Exception; end + KEY_VALIDATION_REGEX = /^[A-Z0-9][A-Z0-9_-]+[A-Z0-9]$/ ALGORITHM = algorithm(ENV.fetch("HMAC_ALGORITHM", "SHA256").upcase) end diff --git a/src/kemal-hmac/client.cr b/src/kemal-hmac/client.cr new file mode 100644 index 0000000..398c8cf --- /dev/null +++ b/src/kemal-hmac/client.cr @@ -0,0 +1,27 @@ +require "./token" + +module Kemal::Hmac + class Client + def initialize(client : String, secret : String, algorithm : String? = "SHA256") + @client = client.upcase + @secret = secret + algo = (algorithm || ENV.fetch("HMAC_ALGORITHM", "SHA256")).upcase + @algorithm = Kemal::Hmac.algorithm(algo) + + unless KEY_VALIDATION_REGEX.match(@client) + raise InvalidSecretError.new("client name must only contain letters, numbers, -, or _") + end + end + + def generate_headers(path : String) + timestamp = Time::Format::ISO_8601_DATE_TIME.format(Time.utc) + hmac_token = Kemal::Hmac::Token.new(@client, path, timestamp).hexdigest(@secret) + + return { + "hmac-client" => @client, + "hmac-timestamp" => timestamp, + "hmac-token" => hmac_token, + } + end + end +end diff --git a/src/kemal-hmac/handler.cr b/src/kemal-hmac/handler.cr index c8df1cc..a30fd44 100644 --- a/src/kemal-hmac/handler.cr +++ b/src/kemal-hmac/handler.cr @@ -23,8 +23,6 @@ module Kemal::Hmac HMAC_KEY_SUFFIX_LIST = ENV.fetch("HMAC_KEY_SUFFIX_LIST", "HMAC_SECRET_BLUE,HMAC_SECRET_GREEN").split(",").map(&.strip) HMAC_KEY_DELIMITER = ENV.fetch("HMAC_KEY_DELIMITER", "_") - class InvalidSecretError < Exception; end - # initialize the Kemal::Hmac::Handler # note: "BLUE" and "GREEN" in this context are two different secrets for the same client. This is a common pattern to allow for key rotation without downtime. # examples: