From 5f8e16cbe273a46d1db7ba7acaa95bf5a692e7fb Mon Sep 17 00:00:00 2001 From: Umputun Date: Fri, 3 Jun 2022 18:13:02 -0500 Subject: [PATCH] add email auth validation with middleware --- backend/app/rest/api/rest.go | 30 ++++++++++++++++++++++++++++++ backend/app/rest/api/rest_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index 664259b0..eb0851b0 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "regexp" "strings" "sync" "time" @@ -224,6 +225,7 @@ func (s *Rest) routes() chi.Router { router.Group(func(r chi.Router) { r.Use(middleware.Timeout(5 * time.Second)) r.Use(logInfoWithBody, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil)), middleware.NoCache) + r.Use(validEmaiAuth()) // reject suspicious email logins r.Mount("/auth", authHandler) }) @@ -650,6 +652,34 @@ func subscribersOnly(enable bool) func(http.Handler) http.Handler { } } +// validEmaiAuth is a middleware for auth endpoints for email method. +// it rejects login request if user or email are suspicious +func validEmaiAuth() func(http.Handler) http.Handler { + + // matches ui side validation, adding min/max limitation + reUser := regexp.MustCompile(`^[\p{L}\d\s_]{4,64}$`) + + return func(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + + if !strings.Contains(r.URL.Path, "/email/login") { + // not email login, skip the check + h.ServeHTTP(w, r) + return + } + + if u := r.URL.Query().Get("user"); u != "" { + if !reUser.MatchString(u) { + http.Error(w, "Access denied", http.StatusForbidden) + return + } + } + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} + func parseError(err error, defaultCode int) (code int) { code = defaultCode diff --git a/backend/app/rest/api/rest_test.go b/backend/app/rest/api/rest_test.go index 6a24eb57..7f4b70d9 100644 --- a/backend/app/rest/api/rest_test.go +++ b/backend/app/rest/api/rest_test.go @@ -389,6 +389,34 @@ func TestRest_subscribersOnly(t *testing.T) { } } +func Test_validEmaiAuth(t *testing.T) { + tbl := []struct { + req string + status int + }{ + {"/auth/email/login?site=remark42&address=umputun%example.com&user=someone", http.StatusOK}, + {"/auth/email/login?site=remark42&address=umputun%example.com&user=someone+blah", http.StatusOK}, + {"/auth/email/login?site=remark42&address=umputun%example.com&user=Евгений+Умпутун", http.StatusOK}, + {"/auth/email/login?site=remark42&address=umputun%example.com&user=12", http.StatusForbidden}, + {"/auth/email/login?site=remark42&address=umputun%example.com&user=..blah+blah", http.StatusForbidden}, + {"/auth/email/login?site=remark42&address=umputun%example.com&user=someonelooong+loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", http.StatusForbidden}, + {"/auth/twitter/login?site=remark42&address=umputun%example.com&user=..blah+blah", http.StatusOK}, + {"/auth/email/login?site=remark42&address=umputun%example.com", http.StatusOK}, + } + + for i, tt := range tbl { + t.Run(strconv.Itoa(i), func(t *testing.T) { + req := httptest.NewRequest("GET", "http://example.com"+tt.req, http.NoBody) + w := httptest.NewRecorder() + h := validEmaiAuth()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + h.ServeHTTP(w, req) + resp := w.Result() + assert.Equal(t, tt.status, resp.StatusCode) + assert.NoError(t, resp.Body.Close()) + }) + } +} + // randomPath pick a file or folder name which is not in use for sure func randomPath(tempDir, basename, suffix string) (string, error) { for i := 0; i < 10; i++ {