diff --git a/shard.yml b/shard.yml index ad446cc..016cbed 100644 --- a/shard.yml +++ b/shard.yml @@ -4,7 +4,7 @@ version: 0.1.0 authors: - Sergey Makridenkov -crystal: 0.22.0 +crystal: 0.23.0 license: MIT diff --git a/src/crystal_std_patch/access_token.cr b/src/crystal_std_patch/access_token.cr deleted file mode 100644 index 9b65d8e..0000000 --- a/src/crystal_std_patch/access_token.cr +++ /dev/null @@ -1,70 +0,0 @@ -# TODO remove hack after release -# https://github.com/crystal-lang/crystal/commit/a3b77d32f1896e996230fbdd07328761b27f6000 - -# Base class for the two possible access tokens: Bearer and Mac. -# -# Use `#authenticate` to authenticate an `HTTP::Client`. -abstract class OAuth2::AccessToken - def self.new(pull : JSON::PullParser) - token_type = nil - access_token = nil - expires_in = nil - refresh_token = nil - scope = nil - mac_algorithm = nil - mac_key = nil - extra = nil - - pull.read_object do |key| - case key - when "token_type" then token_type = pull.read_string - when "access_token" then access_token = pull.read_string - when "expires_in" then expires_in = pull.read_int - when "refresh_token" then refresh_token = pull.read_string_or_null - when "scope" then scope = pull.read_string_or_null - when "mac_algorithm" then mac_algorithm = pull.read_string - when "mac_key" then mac_key = pull.read_string - else - extra ||= {} of String => String - extra[key] = pull.read_raw - end - end - - access_token = access_token.not_nil! - token_type ||= "bearer" - - case token_type.downcase - when "bearer" - Bearer.new(access_token, expires_in, refresh_token, scope, extra) - when "mac" - Mac.new(access_token, expires_in, mac_algorithm.not_nil!, mac_key.not_nil!, refresh_token, scope, Time.now.epoch, extra) - else - raise "Uknown token_type in access token json: #{token_type}" - end - end - - property access_token : String - property expires_in : Int64? - property refresh_token : String? - property scope : String? - - # JSON key-value pairs that are outside of the OAuth2 spec are - # stored in this property in case they are needed. Their value - # is the raw JSON string found in the JSON value (with possible - # changes in the string format, but preserving JSON semantic). - # For example if the value was `[1, 2, 3]` then the value in this hash - # will be the string "[1,2,3]". - property extra : Hash(String, String)? - - def initialize(@access_token : String, expires_in : Int?, @refresh_token : String? = nil, @scope : String? = nil, @extra = nil) - @expires_in = expires_in.try &.to_i64 - end - - abstract def authenticate(request : HTTP::Request, tls) - - def authenticate(client : HTTP::Client) - client.before_request do |request| - authenticate request, client.tls? - end - end -end diff --git a/src/crystal_std_patch/access_token_mac.cr b/src/crystal_std_patch/access_token_mac.cr deleted file mode 100644 index 7d30907..0000000 --- a/src/crystal_std_patch/access_token_mac.cr +++ /dev/null @@ -1,76 +0,0 @@ -# TODO remove hack after release -# https://github.com/crystal-lang/crystal/commit/a3b77d32f1896e996230fbdd07328761b27f6000 - -require "secure_random" -require "openssl/hmac" -require "base64" -require "./access_token" - -class OAuth2::AccessToken::Mac < OAuth2::AccessToken - def self.new(pull : JSON::PullParser) - OAuth2::AccessToken.new(pull).as(self) - end - - property mac_algorithm : String - property mac_key : String - property issued_at : Int64 - - def initialize(access_token, expires_in, @mac_algorithm, @mac_key, refresh_token = nil, scope = nil, @issued_at = Time.now.epoch, extra = nil) - super(access_token, expires_in, refresh_token, scope, extra) - end - - def token_type - "Mac" - end - - def authenticate(request : HTTP::Request, tls) - ts = Time.now.epoch - nonce = "#{ts - @issued_at}:#{SecureRandom.hex}" - method = request.method - uri = request.resource - host, port = host_and_port request, tls - ext = "" - - mac = Mac.signature ts, nonce, method, uri, host, port, ext, mac_algorithm, mac_key - - header = %(MAC id="#{access_token}", nonce="#{nonce}", ts="#{ts}", mac="#{mac}") - request.headers["Authorization"] = header - end - - def self.signature(ts, nonce, method, uri, host, port, ext, mac_algorithm, mac_key) - normalized_request_string = "#{ts}\n#{nonce}\n#{method}\n#{uri}\n#{host}\n#{port}\n#{ext}\n" - - digest = case mac_algorithm - when "hmac-sha-1" then :sha1 - when "hmac-sha-256" then :sha256 - else raise "Unsupported algorithm: #{mac_algorithm}" - end - Base64.strict_encode OpenSSL::HMAC.digest(digest, mac_key, normalized_request_string) - end - - def to_json(json : JSON::Builder) - json.object do - json.field "token_type", "mac" - json.field "access_token", access_token - json.field "expires_in", expires_in - json.field "refresh_token", refresh_token if refresh_token - json.field "scope", scope if scope - json.field "mac_algorithm", mac_algorithm - json.field "mac_key", mac_key - end - end - - def_equals_and_hash access_token, expires_in, mac_algorithm, mac_key, refresh_token, scope - - private def host_and_port(request, tls) - host_header = request.headers["Host"] - if colon_index = host_header.index ':' - host = host_header[0...colon_index] - port = host_header[colon_index + 1..-1].to_i - else - host = host_header - port = tls ? 443 : 80 - end - {host, port} - end -end diff --git a/src/multi_auth.cr b/src/multi_auth.cr index 12f9a91..3894c0e 100644 --- a/src/multi_auth.cr +++ b/src/multi_auth.cr @@ -1,5 +1,4 @@ require "oauth2" -require "./crystal_std_patch/**" require "./multi_auth/**" module MultiAuth