statping/handlers/middleware.go

178 lines
5.0 KiB
Go
Raw Normal View History

2019-03-05 20:13:25 +00:00
package handlers
import (
2020-01-26 21:01:43 +00:00
"compress/gzip"
2020-01-03 06:10:25 +00:00
"crypto/subtle"
2020-01-20 05:02:15 +00:00
"encoding/json"
"fmt"
2020-03-09 18:17:55 +00:00
"github.com/statping/statping/types/core"
2020-04-17 03:21:17 +00:00
"github.com/statping/statping/types/errors"
2020-03-09 18:17:55 +00:00
"github.com/statping/statping/utils"
2020-01-26 21:01:43 +00:00
"io"
2019-03-05 20:13:25 +00:00
"net/http"
"net/http/httptest"
2020-01-26 21:01:43 +00:00
"strings"
2019-03-05 20:13:25 +00:00
"time"
)
2020-01-03 06:10:25 +00:00
var (
authUser string
authPass string
)
2020-01-26 21:01:43 +00:00
// Gzip Compression
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w gzipResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
func Gzip(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
handler.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
handler.ServeHTTP(gzw, r)
})
}
2020-01-03 06:10:25 +00:00
// basicAuthHandler is a middleware to implement HTTP basic authentication using
// AUTH_USERNAME and AUTH_PASSWORD environment variables
func basicAuthHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user),
[]byte(authUser)) != 1 || subtle.ConstantTimeCompare([]byte(pass),
[]byte(authPass)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="statping"`)
w.WriteHeader(401)
w.Write([]byte("You are unauthorized to access the application.\n"))
return
}
next.ServeHTTP(w, r)
})
}
2020-02-17 17:45:33 +00:00
// apiMiddleware will confirm if Core has been setup
func apiMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2020-03-04 10:29:00 +00:00
if !core.App.Setup {
2020-02-17 17:45:33 +00:00
sendErrorJson(errors.New("statping has not been setup"), w, r)
return
}
next.ServeHTTP(w, r)
})
}
// sendLog is a http middleware that will log the duration of request and other useful fields
func sendLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t1 := utils.Now()
2020-02-01 03:53:00 +00:00
if r.RequestURI == "/api/logs" || r.RequestURI == "/api/logs/last" {
next.ServeHTTP(w, r)
2019-12-30 08:08:51 +00:00
return
}
2020-03-08 18:13:27 +00:00
next.ServeHTTP(w, r)
2020-03-09 16:54:55 +00:00
t2 := utils.Now().Sub(t1)
log.WithFields(utils.ToFields(w, r)).
WithField("url", r.RequestURI).
WithField("method", r.Method).
WithField("load_micro_seconds", t2.Microseconds()).
Infoln(fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host))
})
}
2020-05-02 00:53:35 +00:00
// scoped is a middleware handler that will remove private fields based on struct tags
// this will look for the `scope:"user,admin"` tag and remove the JSON field from response
// if user is not authenticated based on the scope.
2020-01-20 05:02:15 +00:00
func scoped(handler func(r *http.Request) interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
data := handler(r)
2020-03-29 01:21:32 +00:00
err, ok := data.(error)
if ok {
sendErrorJson(err, w, r)
return
}
2020-01-20 05:02:15 +00:00
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(scope{data: data, scope: ScopeName(r)})
})
}
2019-03-05 20:13:25 +00:00
// authenticated is a middleware function to check if user is an Admin before running original request
func authenticated(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler {
2020-01-11 07:22:42 +00:00
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2019-03-05 20:13:25 +00:00
if !IsFullAuthenticated(r) {
if redirect {
2020-01-13 07:11:53 +00:00
http.Redirect(w, r, basePath, http.StatusSeeOther)
2019-03-05 20:13:25 +00:00
} else {
sendUnauthorizedJson(w, r)
}
return
}
handler(w, r)
})
}
// readOnly is a middleware function to check if user is a User before running original request
func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler {
2020-01-11 07:22:42 +00:00
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2019-03-05 20:13:25 +00:00
if !IsReadAuthenticated(r) {
if redirect {
2020-01-13 07:11:53 +00:00
http.Redirect(w, r, basePath, http.StatusSeeOther)
2019-03-05 20:13:25 +00:00
} else {
sendUnauthorizedJson(w, r)
}
return
}
handler(w, r)
})
}
// cached is a middleware function that accepts a duration and content type and will cache the response of the original request
2020-02-25 07:41:28 +00:00
func cached(duration, contentType string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2019-03-05 20:13:25 +00:00
content := CacheStorage.Get(r.RequestURI)
w.Header().Set("Content-Type", contentType)
w.Header().Set("Access-Control-Allow-Origin", "*")
2020-03-04 10:29:00 +00:00
if !core.App.Setup {
2019-03-05 20:13:25 +00:00
handler(w, r)
return
}
if content != nil {
w.Write(content)
} else {
c := httptest.NewRecorder()
handler(c, r)
content := c.Body.Bytes()
result := c.Result()
if result.StatusCode != 200 {
w.WriteHeader(result.StatusCode)
w.Write(content)
return
}
w.Write(content)
if d, err := time.ParseDuration(duration); err == nil {
go CacheStorage.Set(r.RequestURI, content, d)
}
}
})
}
2020-04-16 17:32:54 +00:00
func DecodeJSON(r *http.Request, obj interface{}) error {
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&obj)
if err != nil {
2020-04-17 03:21:17 +00:00
return errors.DecodeJSON
2020-04-16 17:32:54 +00:00
}
defer r.Body.Close()
return nil
}