Proxy working with authentications

This commit is contained in:
Yann Stepienik 2023-03-18 19:59:32 +00:00
parent 28a44da3ad
commit b561b94918
11 changed files with 83 additions and 37 deletions

View file

@ -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 <Grid item xs={12}>
<Stack spacing={1}>
<InputLabel htmlFor={name}>{label}</InputLabel>
<OutlinedInput
id={name}
type="text"
type={type ? type : 'text'}
value={formik.values[name]}
name={name}
onBlur={formik.handleBlur}

View file

@ -115,11 +115,11 @@ const ProxyManagement = () => {
<RestartModal openModal={openModal} setOpenModal={setOpenModal} />
{routes && routes.map((route,key) => (<>
<RouteManagement routeConfig={route} setRouteConfig={(newRoute) => {
routes[key] = newRoute;
}}
up={() => up(key)}
down={() => down(key)}
deleteRoute={() => deleteRoute(key)}
routes[key] = newRoute;
}}
up={() => up(key)}
down={() => down(key)}
deleteRoute={() => deleteRoute(key)}
/>
<br /><br />
</>))}

View file

@ -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 })
/>}
<CosmosFormDivider title={'Security'}/>
<CosmosCheckbox
name="AuthEnabled"
label="Authentication Required"
formik={formik}
/>
<CosmosInputText
name="Timeout"
label="Timeout in milliseconds (0 for no timeout, at least 30000 or less recommended)"
placeholder="Timeout"
type="number"
formik={formik}
/>
@ -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}
/>

View file

@ -5,7 +5,7 @@ FROM debian
WORKDIR /app
COPY build/cosmos .
COPY static .
COPY static ./static
VOLUME /config

View file

@ -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"
}

View file

@ -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,

View file

@ -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
}

View file

@ -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)

View file

@ -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")

View file

@ -95,6 +95,7 @@ type ProxyRouteConfig struct {
ThrottlePerMinute int
CORSOrigin string
StripPathPrefix bool
AuthEnabled bool
Target string `validate:"required"`
Mode ProxyMode
}

View file

@ -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