From 1a6e84774b65ee7be9294baaaff77192cec8f0f2 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Tue, 25 Aug 2020 16:07:10 +0200 Subject: [PATCH] feat: add HTTP request flow validator --- selfservice/flow/request.go | 46 ++++++++++++++++++++++++++++++++ selfservice/flow/request_test.go | 22 +++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 selfservice/flow/request.go create mode 100644 selfservice/flow/request_test.go diff --git a/selfservice/flow/request.go b/selfservice/flow/request.go new file mode 100644 index 00000000000..ea355b66caf --- /dev/null +++ b/selfservice/flow/request.go @@ -0,0 +1,46 @@ +package flow + +import ( + "net/http" + + "github.com/justinas/nosurf" + "github.com/ory/herodot" + "github.com/pkg/errors" + + "github.com/ory/kratos/x" +) + +var ErrOriginHeaderNeedsBrowserFlow = herodot.ErrBadRequest. + WithReasonf(`The HTTP Request Header included the "Origin" key, indicating that this request was made as part of an AJAX request in a Browser. The flow however was initiated as an API request. To prevent potential misuse and mitigate several attack vectors including CSRF, the request has been blocked. Please consult the documentation.`) +var ErrCookieHeaderNeedsBrowserFlow = herodot.ErrBadRequest. + WithReasonf(`The HTTP Request Header included the "Cookie" key, indicating that this request was made by a Browser. The flow however was initiated as an API request. To prevent potential misuse and mitigate several attack vectors including CSRF, the request has been blocked. Please consult the documentation.`) + +func VerifyRequest( + r *http.Request, + flowType Type, + generator func(r *http.Request) string, + actual string, +) error { + switch flowType { + case TypeAPI: + // API Based flows to not require anti-CSRF tokens because we can not leverage a session, making this + // endpoint pointless. + + // Let's ensure that no-one mistakenly makes an AJAX request using the API flow. + if r.Header.Get("Origin") != "" { + return errors.WithStack(ErrOriginHeaderNeedsBrowserFlow) + } + + if len(r.Cookies()) > 0 { + return errors.WithStack(ErrCookieHeaderNeedsBrowserFlow) + } + + return nil + default: + if !nosurf.VerifyToken(generator(r), actual) { + return x.ErrInvalidCSRFToken + } + } + + return nil +} diff --git a/selfservice/flow/request_test.go b/selfservice/flow/request_test.go new file mode 100644 index 00000000000..64fc34a80d8 --- /dev/null +++ b/selfservice/flow/request_test.go @@ -0,0 +1,22 @@ +package flow + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ory/kratos/x" +) + +func TestVerifyRequest(t *testing.T) { + require.EqualError(t, VerifyRequest(&http.Request{}, TypeBrowser, x.FakeCSRFTokenGenerator, "not_csrf_token"), x.ErrInvalidCSRFToken.Error()) + require.NoError(t, VerifyRequest(&http.Request{}, TypeBrowser, x.FakeCSRFTokenGenerator, x.FakeCSRFToken), nil) + require.NoError(t, VerifyRequest(&http.Request{}, TypeAPI, x.FakeCSRFTokenGenerator, "")) + require.EqualError(t, VerifyRequest(&http.Request{ + Header: http.Header{"Origin":{"https://www.ory.sh"}}, + }, TypeAPI, x.FakeCSRFTokenGenerator, ""), ErrOriginHeaderNeedsBrowserFlow.Error()) + require.EqualError(t, VerifyRequest(&http.Request{ + Header: http.Header{"Cookie":{"cookie=ory"}}, + }, TypeAPI, x.FakeCSRFTokenGenerator, ""), ErrCookieHeaderNeedsBrowserFlow.Error()) +}