feat: implement relay endpoint (see #237)

This commit is contained in:
Ferdinand Mütsch 2021-10-11 11:00:50 +02:00
parent 5394349c73
commit 8d073aaef2
4 changed files with 82 additions and 0 deletions

View File

@ -39,6 +39,7 @@ security:
cookie_max_age: 172800
allow_signup: true
expose_metrics: false
enable_proxy: false # only intended for production instance at wakapi.dev
sentry:
dsn: # leave blank to disable sentry integration

View File

@ -75,6 +75,7 @@ type appConfig struct {
type securityConfig struct {
AllowSignup bool `yaml:"allow_signup" default:"true" env:"WAKAPI_ALLOW_SIGNUP"`
ExposeMetrics bool `yaml:"expose_metrics" default:"false" env:"WAKAPI_EXPOSE_METRICS"`
EnableProxy bool `yaml:"enable_proxy" default:"false" env:"WAKAPI_ENABLE_PROXY"` // only intended for production instance at wakapi.dev
// this is actually a pepper (https://en.wikipedia.org/wiki/Pepper_(cryptography))
PasswordSalt string `yaml:"password_salt" default:"" env:"WAKAPI_PASSWORD_SALT"`
InsecureCookies bool `yaml:"insecure_cookies" default:"false" env:"WAKAPI_INSECURE_COOKIES"`

View File

@ -3,6 +3,7 @@ package main
import (
"embed"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/routes/relay"
"io/fs"
"log"
"net"
@ -191,6 +192,9 @@ func main() {
loginHandler := routes.NewLoginHandler(userService, mailService)
imprintHandler := routes.NewImprintHandler(keyValueService)
// Other Handlers
relayHandler := relay.NewRelayHandler()
// Setup Routers
router := mux.NewRouter()
rootRouter := router.PathPrefix("/").Subrouter()
@ -219,6 +223,7 @@ func main() {
imprintHandler.RegisterRoutes(rootRouter)
summaryHandler.RegisterRoutes(rootRouter)
settingsHandler.RegisterRoutes(rootRouter)
relayHandler.RegisterRoutes(rootRouter)
// API route registrations
summaryApiHandler.RegisterRoutes(apiRouter)

75
routes/relay/relay.go Normal file
View File

@ -0,0 +1,75 @@
package relay
import (
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
"net/http"
"net/http/httputil"
"net/url"
"regexp"
)
const targetUrlHeader = "X-Target-URL"
const pathMatcherPattern = `^/api/(heartbeat|heartbeats|summary|users|v1/users|compat/wakatime)`
type RelayHandler struct {
config *conf.Config
}
func NewRelayHandler() *RelayHandler {
return &RelayHandler{
config: conf.Get(),
}
}
type filteringMiddleware struct {
handler http.Handler
pathMatcher *regexp.Regexp
}
func newFilteringMiddleware() func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return &filteringMiddleware{
handler: h,
pathMatcher: regexp.MustCompile(pathMatcherPattern),
}
}
}
func (m *filteringMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
targetUrl, err := url.Parse(r.Header.Get(targetUrlHeader))
if err != nil || !m.pathMatcher.MatchString(targetUrl.Path) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte{})
return
}
m.handler.ServeHTTP(w, r)
}
func (h *RelayHandler) RegisterRoutes(router *mux.Router) {
if !h.config.Security.EnableProxy {
return
}
r := router.PathPrefix("/relay").Subrouter()
r.Use(newFilteringMiddleware())
r.Path("").HandlerFunc(h.Any)
}
func (h *RelayHandler) Any(w http.ResponseWriter, r *http.Request) {
targetUrl, err := url.Parse(r.Header.Get(targetUrlHeader))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte{})
return
}
p := httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL = targetUrl
r.Host = targetUrl.Host
},
}
p.ServeHTTP(w, r)
}