From b561b94918d56089ff7b3572f43cf1308b0ff961 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sat, 18 Mar 2023 19:59:32 +0000 Subject: [PATCH] Proxy working with authentications --- .../src/pages/config/users/formShortcuts.jsx | 4 +- client/src/pages/config/users/proxyman.jsx | 10 ++--- client/src/pages/config/users/routeman.jsx | 9 +++++ dockerfile | 2 +- gupm.json | 3 +- src/httpServer.go | 19 ++-------- src/proxy/routerGen.go | 38 ++++++++++++++++++- src/user/token.go | 16 ++++---- src/utils/middleware.go | 4 +- src/utils/types.go | 1 + src/utils/utils.go | 14 ++++++- 11 files changed, 83 insertions(+), 37 deletions(-) diff --git a/client/src/pages/config/users/formShortcuts.jsx b/client/src/pages/config/users/formShortcuts.jsx index 807ecf0..fe68f1d 100644 --- a/client/src/pages/config/users/formShortcuts.jsx +++ b/client/src/pages/config/users/formShortcuts.jsx @@ -29,13 +29,13 @@ import AnimateButton from '../../../components/@extended/AnimateButton'; import RestartModal from './restart'; -export const CosmosInputText = ({ name, placeholder, label, formik }) => { +export const CosmosInputText = ({ name, type, placeholder, label, formik }) => { return {label} { {routes && routes.map((route,key) => (<> { - routes[key] = newRoute; - }} - up={() => up(key)} - down={() => down(key)} - deleteRoute={() => deleteRoute(key)} + routes[key] = newRoute; + }} + up={() => up(key)} + down={() => down(key)} + deleteRoute={() => deleteRoute(key)} />

))} diff --git a/client/src/pages/config/users/routeman.jsx b/client/src/pages/config/users/routeman.jsx index 0e5a90a..62ec8c3 100644 --- a/client/src/pages/config/users/routeman.jsx +++ b/client/src/pages/config/users/routeman.jsx @@ -44,6 +44,7 @@ const RouteManagement = ({ routeConfig, setRouteConfig, up, down, deleteRoute }) Mode: routeConfig.Mode, Target: routeConfig.Target, UseHost: routeConfig.UseHost, + AuthEnabled: routeConfig.AuthEnabled, Host: routeConfig.Host, UsePathPrefix: routeConfig.UsePathPrefix, PathPrefix: routeConfig.PathPrefix, @@ -145,11 +146,18 @@ const RouteManagement = ({ routeConfig, setRouteConfig, up, down, deleteRoute }) />} + + @@ -157,6 +165,7 @@ const RouteManagement = ({ routeConfig, setRouteConfig, up, down, deleteRoute }) name="ThrottlePerMinute" label="Maximum number of requests Per Minute (0 for no limit, at least 100 or less recommended)" placeholder="Throttle Per Minute" + type="number" formik={formik} /> diff --git a/dockerfile b/dockerfile index 55d383b..ff709b8 100644 --- a/dockerfile +++ b/dockerfile @@ -5,7 +5,7 @@ FROM debian WORKDIR /app COPY build/cosmos . -COPY static . +COPY static ./static VOLUME /config diff --git a/gupm.json b/gupm.json index 30c6762..f6a8d04 100644 --- a/gupm.json +++ b/gupm.json @@ -4,6 +4,7 @@ "aliases": { "certificate": "sh generate-certificate.sh", "client": "g vite dev", + "dockerdev": "g ci/build; g vite build --base=/ui/; docker build --tag cosmos-dev .", "start": "build/bin" } }, @@ -86,6 +87,6 @@ }, "description": "Cosmos Server", "name": "cosmos-server", - "version": "0.0.5", + "version": "0.0.6", "wrapInstallFolder": "src" } \ No newline at end of file diff --git a/src/httpServer.go b/src/httpServer.go index fb664e1..2eea6a8 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -9,7 +9,6 @@ import ( "github.com/gorilla/mux" "strings" "strconv" - "regexp" "time" "encoding/json" "os" @@ -75,6 +74,7 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) { WriteTimeout: 0, IdleTimeout: 30 * time.Second, Handler: router, + DisableGeneralOptionsHandler: true, } // start https server @@ -99,19 +99,6 @@ func tokenMiddleware(next http.Handler) http.Handler { r.Header.Set("x-cosmos-user", u.Nickname) r.Header.Set("x-cosmos-role", strconv.Itoa((int)(u.Role))) - // TODO: If external application, remove the cookie from the request - // to prevent leaking, and generate new JWT token - if false { - cookies := r.Header.Get("Cookie") - // This prob dowsnt work - cookieRemoveRegex := regexp.MustCompile(`jwttoken=[^;]*;`) - cookies = cookieRemoveRegex.ReplaceAllString(cookies, "") - r.Header.Set("Cookie", cookies) - - // Replace the token with a application speicfic one - r.Header.Set("x-cosmos-token", "1234567890") - } - next.ServeHTTP(w, r) }) } @@ -157,12 +144,11 @@ func StartServer() { router.Use(middleware.Recoverer) router.Use(middleware.Logger) - router.Use(tokenMiddleware) router.Use(utils.SetSecurityHeaders) router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/ui", http.StatusMovedPermanently) -})) + })) srapi := router.PathPrefix("/cosmos").Subrouter() @@ -178,6 +164,7 @@ func StartServer() { srapi.HandleFunc("/api/users", user.UsersRoute) // srapi.Use(utils.AcceptHeader("*/*")) + srapi.Use(tokenMiddleware) srapi.Use(utils.CORSHeader(utils.GetMainConfig().HTTPConfig.Hostname)) srapi.Use(utils.MiddlewareTimeout(20 * time.Second)) srapi.Use(httprate.Limit(60, 1*time.Minute, diff --git a/src/proxy/routerGen.go b/src/proxy/routerGen.go index 8772bf5..50a4202 100644 --- a/src/proxy/routerGen.go +++ b/src/proxy/routerGen.go @@ -6,9 +6,44 @@ import ( "github.com/gorilla/mux" "time" "../utils" + "../user" + "strconv" "github.com/go-chi/httprate" + "regexp" ) +func tokenMiddleware(enabled bool) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.Header.Set("x-cosmos-user", "") + r.Header.Set("x-cosmos-role", "") + + u, err := user.RefreshUserToken(w, r) + + if err != nil { + return + } + + r.Header.Set("x-cosmos-user", u.Nickname) + r.Header.Set("x-cosmos-role", strconv.Itoa((int)(u.Role))) + + ogcookies := r.Header.Get("Cookie") + cookieRemoveRegex := regexp.MustCompile(`jwttoken=[^;]*;`) + cookies := cookieRemoveRegex.ReplaceAllString(ogcookies, "") + r.Header.Set("Cookie", cookies) + + // Replace the token with a application speicfic one + r.Header.Set("x-cosmos-token", "1234567890") + + if(enabled) { + utils.LoggedInOnlyWithRedirect(w, r); + } + + next.ServeHTTP(w, r) + }) + } +} + func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination *httputil.ReverseProxy) *mux.Route { var realDestination http.Handler realDestination = destination @@ -49,6 +84,7 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination *ht } origin.Handler( + tokenMiddleware(route.AuthEnabled)( utils.CORSHeader(originCORS)( utils.MiddlewareTimeout(timeout * time.Millisecond)( httprate.Limit(throttlePerMinute, 1*time.Minute, @@ -59,7 +95,7 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination *ht http.StatusTooManyRequests, "HTTP003") return }), - )(realDestination)))) + )(realDestination))))) return origin } \ No newline at end of file diff --git a/src/user/token.go b/src/user/token.go index c07d20b..46d1f7b 100644 --- a/src/user/token.go +++ b/src/user/token.go @@ -95,6 +95,10 @@ func logOutUser(w http.ResponseWriter) { Name: "jwttoken", Value: "", Expires: time.Now().Add(-time.Hour * 24 * 365), + Path: "/", + Secure: true, + HttpOnly: true, + Domain: utils.GetMainConfig().HTTPConfig.Hostname, } http.SetCookie(w, &cookie) @@ -141,17 +145,11 @@ func SendUserToken(w http.ResponseWriter, user utils.User) { Name: "jwttoken", Value: tokenString, Expires: expiration, + Path: "/", + Secure: true, HttpOnly: true, - // TODO: high level cookie for SSO - // Should re-generate app specific cookies on subdomains - // Domain: "yoursite.com", + Domain: utils.GetMainConfig().HTTPConfig.Hostname, } - // cookie2 := http.Cookie{ - // Name: "dummy", - // Value: "asdasdadsasd", - // Expires: expiration, - // HttpOnly: true, - // } http.SetCookie(w, &cookie) // http.SetCookie(w, &cookie2) diff --git a/src/utils/middleware.go b/src/utils/middleware.go index 5a1a04e..551f7b0 100644 --- a/src/utils/middleware.go +++ b/src/utils/middleware.go @@ -39,7 +39,8 @@ func SetSecurityHeaders(next http.Handler) http.Handler { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-XSS-Protection", "1; mode=block") - // w.Header().Set("Referrer-Policy", "no-referrer") + w.Header().Set("X-Served-By-Cosmos", "1") + w.Header().Set("Referrer-Policy", "no-referrer") next.ServeHTTP(w, r) }) @@ -48,6 +49,7 @@ func SetSecurityHeaders(next http.Handler) http.Handler { func CORSHeader(origin string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") diff --git a/src/utils/types.go b/src/utils/types.go index 798d666..f504feb 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -95,6 +95,7 @@ type ProxyRouteConfig struct { ThrottlePerMinute int CORSOrigin string StripPathPrefix bool + AuthEnabled bool Target string `validate:"required"` Mode ProxyMode } diff --git a/src/utils/utils.go b/src/utils/utils.go index f7f92a4..de76bdf 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -197,8 +197,20 @@ func RestartServer() { os.Exit(0) } +func LoggedInOnlyWithRedirect(w http.ResponseWriter, req *http.Request) error { + userNickname := req.Header.Get("x-cosmos-user") + role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role")) + isUserLoggedIn := role > 0 -func loggedInOnly(w http.ResponseWriter, req *http.Request) error { + if !isUserLoggedIn || userNickname == "" { + Error("LoggedInOnlyWithRedirect: User is not logged in", nil) + http.Redirect(w, req, "/ui/login?notlogged=1&redirect=" + req.URL.Path, http.StatusFound) + } + + return nil +} + +func LoggedInOnly(w http.ResponseWriter, req *http.Request) error { userNickname := req.Header.Get("x-cosmos-user") role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role")) isUserLoggedIn := role > 0