Skip to content

Commit

Permalink
Merge pull request #10 from crisward/master
Browse files Browse the repository at this point in the history
added opt out routes with tests
  • Loading branch information
sdogruyol authored Apr 4, 2017
2 parents ebe3e16 + 7ab97f3 commit da67cfd
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 7 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@ require "kemal-csrf"

add_handler CSRF.new
```
You can also change the name of the form field, header name, the methods which don't need csrf and error message.

You can also change the name of the form field, header name, the methods which don't need csrf,error message and routes which you don't want csrf to apply.
All of these are optional
```crystal
require "kemal-csrf"
add_handler CSRF.new(header: "X_CSRF_TOKEN",allowed_methods: ["GET", "HEAD", "OPTIONS", "TRACE"],parameter_name: "_csrf", error: "CSRF Error" )
add_handler CSRF.new(
header: "X_CSRF_TOKEN",
allowed_methods: ["GET", "HEAD", "OPTIONS", "TRACE"],
allowed_routes: ["/api/somecallback"],
parameter_name: "_csrf",
error: "CSRF Error"
)
```

## Contributing
Expand Down
36 changes: 33 additions & 3 deletions spec/kemal-csrf_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe "CSRF" do
request = HTTP::Request.new("POST", "/",
body: "authenticity_token=#{current_token}&hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded",
"Set-Cookie" => client_response.headers["Set-Cookie"]})
"Set-Cookie" => client_response.headers["Set-Cookie"]})
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 404
Expand All @@ -53,12 +53,42 @@ describe "CSRF" do
request = HTTP::Request.new("POST", "/",
body: "hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded",
"Set-Cookie" => client_response.headers["Set-Cookie"],
"x-csrf-token" => current_token})
"Set-Cookie" => client_response.headers["Set-Cookie"],
"x-csrf-token" => current_token})
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 404
end

it "allows POSTs to allowed route" do
handler = CSRF.new(allowed_routes: ["/allowed"])
request = HTTP::Request.new("POST", "/allowed/",
body: "authenticity_token=cemal&hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"})
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 404
end

it "allows POSTs to route using wildcards" do
handler = CSRF.new(allowed_routes: ["/everything/*"])
request = HTTP::Request.new("POST", "/everything/here/and",
body: "authenticity_token=cemal&hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"})
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 404
end

it "not allows POSTs to mismatched route using wildcards" do
handler = CSRF.new(allowed_routes: ["/nothing/*"])
request = HTTP::Request.new("POST", "/something/",
body: "authenticity_token=cemal&hasan=lamec",
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"})
io, context = process_request(handler, request)
client_response = HTTP::Client::Response.from_io(io, decompress: false)
client_response.status_code.should eq 403
end
end

def process_request(handler, request)
Expand Down
10 changes: 8 additions & 2 deletions src/kemal-csrf.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ require "kemal-session"
# where an attacker can re-submit a form.
#
class CSRF < Kemal::Handler
def initialize(@header = "X_CSRF_TOKEN", @allowed_methods = %w(GET HEAD OPTIONS TRACE), @parameter_name = "authenticity_token", @error = "Forbidden")
def initialize(@header = "X_CSRF_TOKEN", @allowed_methods = %w(GET HEAD OPTIONS TRACE), @parameter_name = "authenticity_token", @error = "Forbidden", @allowed_routes = [] of String)
@allowed_routes.each do |path|
class_name = {{@type.name}}
%w(GET HEAD OPTIONS TRACE PUT POST).each do |method|
@@exclude_routes_tree.add "#{class_name}/#{method.downcase}#{path}", "/#{method.downcase}#{path}"
end
end
end

def call(context)
return call_next(context) if exclude_match?(context)
unless context.session.string?("csrf")
csrf_token = SecureRandom.hex(16)
context.session.string("csrf", csrf_token)
Expand All @@ -27,7 +34,6 @@ class CSRF < Kemal::Handler
end

return call_next(context) if @allowed_methods.includes?(context.request.method)

req = context.request
submitted = if req.headers[@header]?
req.headers[@header]
Expand Down

0 comments on commit da67cfd

Please sign in to comment.