generated from GrantBirki/crystal-base-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5f58ae6
commit 00ac75f
Showing
7 changed files
with
164 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
require "../spec_helper" | ||
|
||
describe "Kemal::Hmac" do | ||
# it "goes to next handler with correct credentials" do | ||
# hmac_handler = Kemal::Hmac::Handler.new() | ||
# request = HTTP::Request.new( | ||
# "GET", | ||
# "/", | ||
# headers: HTTP::Headers{"foo" => "bar"}, | ||
# ) | ||
|
||
# io, context = create_request_and_return_io_and_context(hmac_handler, request) | ||
# response = HTTP::Client::Response.from_io(io, decompress: false) | ||
# response.status_code.should eq 404 | ||
# context.kemal_authorized_client?.should eq("serdar") | ||
# end | ||
|
||
it "returns 401 when a header is provided but it is not for hmac auth" do | ||
hmac_handler = Kemal::Hmac::Handler.new | ||
request = HTTP::Request.new( | ||
"GET", | ||
"/", | ||
headers: HTTP::Headers{"x-no-hmac-auth-whoops" => "foobar"}, | ||
) | ||
io, context = create_request_and_return_io_and_context(hmac_handler, request) | ||
response = HTTP::Client::Response.from_io(io, decompress: false) | ||
response.status_code.should eq 401 | ||
response.headers["missing-hmac-headers"].should eq "HTTP_X_HMAC_CLIENT,HTTP_X_HMAC_TIMESTAMP,HTTP_X_HMAC_TOKEN" | ||
context.kemal_authorized_client?.should eq(nil) | ||
end | ||
|
||
it "returns 401 when no headers are provided at all" do | ||
hmac_handler = Kemal::Hmac::Handler.new | ||
request = HTTP::Request.new( | ||
"GET", | ||
"/" | ||
) | ||
io, context = create_request_and_return_io_and_context(hmac_handler, request) | ||
response = HTTP::Client::Response.from_io(io, decompress: false) | ||
response.status_code.should eq 401 | ||
response.headers["missing-hmac-headers"].should eq "HTTP_X_HMAC_CLIENT,HTTP_X_HMAC_TIMESTAMP,HTTP_X_HMAC_TOKEN" | ||
context.kemal_authorized_client?.should eq(nil) | ||
end | ||
|
||
it "returns 401 when only the client hmac header is provided" do | ||
hmac_handler = Kemal::Hmac::Handler.new | ||
request = HTTP::Request.new( | ||
"GET", | ||
"/", | ||
headers: HTTP::Headers{"HTTP_X_HMAC_CLIENT" => "octo-client"}, | ||
) | ||
io, context = create_request_and_return_io_and_context(hmac_handler, request) | ||
response = HTTP::Client::Response.from_io(io, decompress: false) | ||
response.status_code.should eq 401 | ||
response.headers["missing-hmac-headers"].should eq "HTTP_X_HMAC_TIMESTAMP,HTTP_X_HMAC_TOKEN" | ||
context.kemal_authorized_client?.should eq(nil) | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,21 @@ | ||
require "spec" | ||
require "../src/kemal-hmac" | ||
|
||
class SpecAuthHandler < Kemal::Hmac::Handler | ||
only ["/api"] | ||
|
||
def call(context) | ||
return call_next(context) unless only_match?(context) | ||
super | ||
end | ||
end | ||
|
||
def create_request_and_return_io_and_context(handler, request) | ||
io = IO::Memory.new | ||
response = HTTP::Server::Response.new(io) | ||
context = HTTP::Server::Context.new(request, response) | ||
handler.call(context) | ||
response.close | ||
io.rewind | ||
{io, context} | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,17 @@ | ||
module Kemal::BasicAuth | ||
require "base64" | ||
require "kemal" | ||
require "./kemal-hmac/**" | ||
|
||
# This middleware adds HTTP Basic Auth support to your application. | ||
# Returns 401 "Unauthorized" with wrong credentials. | ||
# | ||
# ```crystal | ||
# basic_auth "username", "password" | ||
# # basic_auth {"username1" => "password2", "username2" => "password2"} | ||
# ``` | ||
# | ||
# `HTTP::Server::Context#authorized_username` is set when the user is | ||
# authorized. | ||
class Handler < Kemal::Handler | ||
BASIC = "Basic" | ||
AUTH = "Authorization" | ||
AUTH_MESSAGE = "Could not verify your access level for that URL.\nYou have to login with proper credentials" | ||
HEADER_LOGIN_REQUIRED = "Basic realm=\"Login Required\"" | ||
|
||
def initialize(@credentials : Credentials) | ||
end | ||
|
||
# backward compatibility | ||
def initialize(username : String, password : String) | ||
initialize({ username => password }) | ||
end | ||
|
||
def initialize(hash : Hash(String, String)) | ||
initialize(Credentials.new(hash)) | ||
end | ||
|
||
def call(context) | ||
if context.request.headers[AUTH]? | ||
if value = context.request.headers[AUTH] | ||
if value.size > 0 && value.starts_with?(BASIC) | ||
if username = authorize?(value) | ||
context.kemal_authorized_username = username | ||
return call_next(context) | ||
end | ||
end | ||
end | ||
end | ||
headers = HTTP::Headers.new | ||
context.response.status_code = 401 | ||
context.response.headers["WWW-Authenticate"] = HEADER_LOGIN_REQUIRED | ||
context.response.print AUTH_MESSAGE | ||
end | ||
|
||
def authorize?(value) : String? | ||
username, password = Base64.decode_string(value[BASIC.size + 1..-1]).split(":") | ||
@credentials.authorize?(username, password) | ||
end | ||
module Kemal | ||
module Hmac | ||
end | ||
end | ||
|
||
# Helper to easily add HTTP Basic Auth support. | ||
def hmac_auth(hmac_client_header : String? = nil, hmac_timestamp_header : String? = nil, hmac_token_header : String? = nil) | ||
add_handler Kemal.config.hmac_handler.new( | ||
hmac_client_header: hmac_client_header, | ||
hmac_timestamp_header: hmac_timestamp_header, | ||
hmac_token_header: hmac_token_header | ||
) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
require "../handler" | ||
|
||
module Kemal | ||
class Config | ||
property hmac_handler : Kemal::Hmac::Handler.class = Kemal::Hmac::Handler | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
class HTTP::Server::Context | ||
property? kemal_authorized_client : String? | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
require "kemal" | ||
|
||
module Kemal::Hmac | ||
# This middleware adds hmac support to your application. | ||
# Returns 401 "Unauthorized" with wrong credentials. | ||
# | ||
# ``` | ||
# hmac_auth "todo" | ||
# # hmac_auth ["todo1", "todo2"] | ||
# ``` | ||
# | ||
# `HTTP::Server::Context#authorized_hmac_client` is set when the client is | ||
# authorized. | ||
class Handler < Kemal::Handler | ||
HMAC_CLIENT_HEADER = ENV.fetch("HMAC_CLIENT_HEADER", "HTTP_X_HMAC_CLIENT") | ||
HMAC_TIMESTAMP_HEADER = ENV.fetch("HMAC_TIMESTAMP_HEADER", "HTTP_X_HMAC_TIMESTAMP") | ||
HMAC_TOKEN_HEADER = ENV.fetch("HMAC_TOKEN_HEADER", "HTTP_X_HMAC_TOKEN") | ||
|
||
def initialize(hmac_client_header : String? = nil, hmac_timestamp_header : String? = nil, hmac_token_header : String? = nil) | ||
@hmac_client_header = hmac_client_header || HMAC_CLIENT_HEADER | ||
@hmac_timestamp_header = hmac_timestamp_header || HMAC_TIMESTAMP_HEADER | ||
@hmac_token_header = hmac_token_header || HMAC_TOKEN_HEADER | ||
@required_hmac_headers = [ | ||
@hmac_client_header, | ||
@hmac_timestamp_header, | ||
@hmac_token_header, | ||
] | ||
end | ||
|
||
def call(context) | ||
headers = load_hmac_headers(context) | ||
missing_headers = missing_hmac_headers(headers) | ||
|
||
# if any of the required headers are missing, return 401 | ||
unless missing_headers.empty? | ||
context.response.status_code = 401 | ||
context.response.headers["missing-hmac-headers"] = missing_headers.join(",") | ||
context.response.print "Unauthorized" | ||
return | ||
end | ||
|
||
context.kemal_authorized_client = headers[@hmac_client_header] | ||
end | ||
|
||
# Load the required headers from the request for hmac authentication | ||
def load_hmac_headers(context) : Hash(String, String?) | ||
@required_hmac_headers.each_with_object({} of String => String?) do |name, hash| | ||
hash[name] = context.request.headers.fetch(name, nil) | ||
end | ||
end | ||
|
||
# If any of the required headers are missing, return the missing headers | ||
def missing_hmac_headers(headers : Hash(String, String?)) : Array(String) | ||
headers.select { |_, v| v.nil? }.keys | ||
end | ||
|
||
def authorize?(value) : String? | ||
username, password = Base64.decode_string(value[BASIC.size + 1..-1]).split(":") | ||
@credentials.authorize?(username, password) | ||
end | ||
end | ||
end |