diff --git a/.nvmrc b/.nvmrc index 19c7bdb..b6a7d89 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16 \ No newline at end of file +16 diff --git a/build arm64.sh b/build arm64.sh index 79cd79c..a1e896a 100644 --- a/build arm64.sh +++ b/build arm64.sh @@ -3,4 +3,5 @@ env GOARCH=arm64 go build -o build/cosmos src/*.go if [ $? -ne 0 ]; then exit 1 fi -cp -r static build/ \ No newline at end of file +cp -r static build/ +cp package.json build/ \ No newline at end of file diff --git a/build.sh b/build.sh index f26b560..20ac0d9 100644 --- a/build.sh +++ b/build.sh @@ -3,4 +3,9 @@ go build -o build/cosmos src/*.go if [ $? -ne 0 ]; then exit 1 fi -cp -r static build/ \ No newline at end of file +cp -r static build/ +echo '{' > build/meta.json +cat package.json | grep -E '"version"' >> build/meta.json +echo ' "buildDate": "'`date`'",' >> build/meta.json +echo ' "built from": "'`hostname`'"' >> build/meta.json +echo '}' >> build/meta.json \ No newline at end of file diff --git a/client/src/api/docker.jsx b/client/src/api/docker.jsx index cd09631..4df500f 100644 --- a/client/src/api/docker.jsx +++ b/client/src/api/docker.jsx @@ -8,6 +8,15 @@ function list() { }, })) } + +function secure(id, res) { + return wrap(fetch('/cosmos/api/servapps/' + id + '/secure/'+res, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + })) +} const newDB = () => { return wrap(fetch('/cosmos/api/newDB', { @@ -21,4 +30,5 @@ const newDB = () => { export { list, newDB, + secure }; \ No newline at end of file diff --git a/client/src/pages/authentication/AuthWrapper.jsx b/client/src/pages/authentication/AuthWrapper.jsx index 2dc40c9..887cdaa 100644 --- a/client/src/pages/authentication/AuthWrapper.jsx +++ b/client/src/pages/authentication/AuthWrapper.jsx @@ -19,7 +19,7 @@ const AuthWrapper = ({ children }) => { const darkMode = theme.palette.mode === 'dark'; return + background: darkMode ? 'none' : '#fafafb' }}> { + if(lockTarget) { + onContainerChange(TargetContainer) + } + }, []) + React.useEffect(() => { let active = true; @@ -93,6 +101,7 @@ export function CosmosContainerPicker({formik}) { (async () => { const res = await API.docker.list() setContainers(res.data); + let names = res.data.map((container) => container.Names[0]) @@ -118,27 +127,27 @@ export function CosmosContainerPicker({formik}) { { - setOpen(true); + !lockTarget && setOpen(true); }} onClose={() => { - setOpen(false); + !lockTarget && setOpen(false); }} onChange={(event, newValue) => { - onContainerChange(newValue) + !lockTarget && onContainerChange(newValue) }} isOptionEqualToValue={(option, value) => { - console.log(option.Names[0], value.Names[0]) - return option.Names[0] === value.Names[0] + return !lockTarget && (option.Names[0] === value.Names[0]) }} getOptionLabel={(option) => { - return option.Names[0] + return !lockTarget ? option.Names[0] : TargetContainer.Names[0] }} options={containers} loading={loading} freeSolo={true} placeholder={"Please select a container"} - defaultValue={targetResult.containerObject} + defaultValue={lockTarget ? TargetContainer : targetResult.containerObject} renderInput={(params) => ( { +export const CosmosInputText = ({ name, style, type, placeholder, onChange, label, formik }) => { return - + {label} } -export const CosmosSelect = ({ name, label, formik, options }) => { +export const CosmosSelect = ({ name, label, formik, disabled, options }) => { return {label} @@ -137,6 +137,7 @@ export const CosmosSelect = ({ name, label, formik, options }) => { variant="outlined" name={name} id={name} + disabled={disabled} select value={formik.values[name]} onChange={formik.handleChange} @@ -158,7 +159,7 @@ export const CosmosSelect = ({ name, label, formik, options }) => { ; } -export const CosmosCheckbox = ({ name, label, formik }) => { +export const CosmosCheckbox = ({ name, label, formik, style }) => { return { as={FormControlLabel} control={} label={label} + style={style} /> @@ -182,7 +184,8 @@ export const CosmosCollapse = ({ children, title }) => { aria-controls="panel1a-content" id="panel1a-header" > - {title} + + {title} {children} diff --git a/client/src/pages/config/users/proxyman.jsx b/client/src/pages/config/users/proxyman.jsx index 6b48efe..c703251 100644 --- a/client/src/pages/config/users/proxyman.jsx +++ b/client/src/pages/config/users/proxyman.jsx @@ -95,6 +95,7 @@ const ProxyManagement = () => { routes.push({ Name: 'New Route', Description: 'New Route', + Mode: "SERVAPP", UseHost: false, Host: '', UsePathPrefix: false, @@ -103,8 +104,7 @@ const ProxyManagement = () => { ThrottlePerMinute: 100, CORSOrigin: '', StripPathPrefix: false, - Static: false, - SPAMode: false, + AuthEnabled: false, }); updateRoutes(routes); }}>Create @@ -114,7 +114,8 @@ const ProxyManagement = () => { {config && <> {routes && routes.map((route,key) => (<> - { + { routes[key] = newRoute; }} up={() => up(key)} diff --git a/client/src/pages/config/users/routeman.jsx b/client/src/pages/config/users/routeman.jsx index 44ed941..4f0e656 100644 --- a/client/src/pages/config/users/routeman.jsx +++ b/client/src/pages/config/users/routeman.jsx @@ -33,7 +33,7 @@ import { CosmosCheckbox, CosmosCollapse, CosmosFormDivider, CosmosInputText, Cos import { DownOutlined, UpOutlined, CheckOutlined, DeleteOutlined } from '@ant-design/icons'; import { CosmosContainerPicker } from './containerPicker'; -const RouteManagement = ({ routeConfig, setRouteConfig, up, down, deleteRoute }) => { +const RouteManagement = ({ routeConfig, TargetContainer, noControls=false, lockTarget=false, setRouteConfig, up, down, deleteRoute }) => { const [confirmDelete, setConfirmDelete] = React.useState(false); return
@@ -67,6 +67,7 @@ const RouteManagement = ({ routeConfig, setRouteConfig, up, down, deleteRoute }) {(formik) => (
{routeConfig.Name}   } onClick={() => up()}/>   } onClick={() => down()}/>   @@ -93,11 +94,15 @@ const RouteManagement = ({ routeConfig, setRouteConfig, up, down, deleteRoute }) + + What are you trying to access with this route? + { - formik.values.Mode === "SERVAPP" ? + (formik.values.Mode === "SERVAPP")? : + name="Target" + label={formik.values.Mode == "PROXY" ? "Target URL" : "Target Folder Path"} + placeholder={formik.values.Mode == "PROXY" ? "localhost:8080" : "/path/to/my/app"} + formik={formik} + /> } - + + What URL do you want to access your target from? + } } {formik.values.UsePathPrefix && } + + Additional security settings. MFA and Captcha are not yet implemented. + + ({ + backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff', + ...theme.typography.body2, + padding: theme.spacing(1), + textAlign: 'center', + color: theme.palette.text.secondary, +})); const ServeApps = () => { - const {serveApps, setServeApps} = useState([]); + isLoggedIn(); + + const [serveApps, setServeApps] = useState([]); + const [isUpdating, setIsUpdating] = useState({}); + const [search, setSearch] = useState(""); + const [config, setConfig] = useState(null); + const [openModal, setOpenModal] = useState(false); + const [newRoute, setNewRoute] = useState(null); + const [openRestartModal, setOpenRestartModal] = useState(false); + + const hasCosmosNetwork = (containerName) => { + const container = serveApps.find((app) => { + return app.Names[0].replace('/', '') === containerName.replace('/', ''); + }); + return container && container.NetworkSettings.Networks && Object.keys(container.NetworkSettings.Networks).some((network) => { + if(network.startsWith('cosmos-network')) + return true; + }) + } + + const refreshServeApps = () => { + API.docker.list().then((res) => { + setServeApps(res.data); + }); + API.config.get().then((res) => { + setConfig(res.data); + }); + setIsUpdating({}); + }; + + const setIsUpdatingId = (id, value) => { + setIsUpdating({ + ...isUpdating, + [id]: value + }); + } + + const getContainersRoutes = (containerName) => { + return config && config.HTTPConfig && config.HTTPConfig.ProxyConfig.Routes.filter((route) => { + return route.Mode == "SERVAPP" && ( + route.Target.startsWith(containerName) || + route.Target.split('://')[1].startsWith(containerName) + ) + }) + } + + useEffect(() => { + refreshServeApps(); + }, []); + + function updateRoutes() { + let con = { + ...config, + HTTPConfig: { + ...config.HTTPConfig, + ProxyConfig: { + ...config.HTTPConfig.ProxyConfig, + Routes: [ + ...config.HTTPConfig.ProxyConfig.Routes, + newRoute, + ] + }, + }, + }; + + API.config.set(con).then((res) => { + setOpenModal(false); + setOpenRestartModal(true); + }); + } + const gridAnim = { + transition: 'all 0.2s ease', + opacity: 1, + transform: 'translateY(0px)', + '&.MuiGrid2-item--hidden': { + opacity: 0, + transform: 'translateY(-20px)', + }, + }; return
- Implementation currently in progress! If you want to voice your opinion on where Cosmos is going, please join us on Discord! + + setOpenModal(false)}> + Connect ServApp + {openModal && <> + + + +
+ Welcome to the Connect Wizard. This interface will help you expose your ServApp securely to the internet. +
+
+ {openModal && !hasCosmosNetwork(openModal.Names[0]) && This ServApp does not appear to be connected to a Cosmos Network, so the hostname might not be accessible. The easiest way to fix this is to check the box "Force Secure Network" or manually create a sub-network in Docker.} +
+
+ { + setNewRoute(_newRoute); + }} + up={() => {}} + down={() => {}} + deleteRoute={() => {}} + noControls + lockTarget + /> +
+
+
+
+ + + + + } +
+ + + + + + + } + onChange={(e) => { + setSearch(e.target.value); + }} + /> + + + + + + + + + + {serveApps && serveApps.filter(app => search.length < 2 || app.Names[0].includes(search)).map((app) => { + return + + }> + + + { + ({ + "created": , + "restarting": , + "running": , + "removing": , + "paused": , + "exited": , + "dead": , + })[app.State] + } + + + + {app.Names[0].replace('/', '')}  + + + {app.Image} + + + + + + Ports + + + {app.Ports.map((port) => { + return + + + })} + + + + + Networks + + + {app.NetworkSettings.Networks && Object.keys(app.NetworkSettings.Networks).map((network) => { + return + })} + + + {isUpdating[app.Id] ?
+ +
: + + + Settings + + + { + setIsUpdatingId(app.Id, true); + API.docker.secure(app.Id, e.target.checked).then(() => { + setTimeout(() => { + setIsUpdatingId(app.Id, false); + refreshServeApps(); + }, 3000); + }) + }} + /> Force Secure Network + } + + + Proxies + + + {getContainersRoutes(app.Names[0].replace('/', '')).map((route) => { + return + })} + {getContainersRoutes(app.Names[0].replace('/', '')).length == 0 && + } + + + + + +
+
+ }) + } +
+
} diff --git a/client/src/themes/index.jsx b/client/src/themes/index.jsx index 5266601..03ef80b 100644 --- a/client/src/themes/index.jsx +++ b/client/src/themes/index.jsx @@ -18,6 +18,8 @@ export default function ThemeCustomization({ children }) { window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + console.log(theme) + // eslint-disable-next-line react-hooks/exhaustive-deps const themeTypography = Typography(`'Public Sans', sans-serif`); const themeCustomShadows = useMemo(() => CustomShadows(theme), [theme]); diff --git a/client/src/themes/overrides/Button.jsx b/client/src/themes/overrides/Button.jsx index 85bc778..0da0d0a 100644 --- a/client/src/themes/overrides/Button.jsx +++ b/client/src/themes/overrides/Button.jsx @@ -3,7 +3,7 @@ export default function Button(theme) { const disabledStyle = { '&.Mui-disabled': { - backgroundColor: theme.palette.grey[200] + backgroundColor: theme.palette.grey[400] } }; diff --git a/client/src/themes/palette.jsx b/client/src/themes/palette.jsx index 7eafb5e..b064ce1 100644 --- a/client/src/themes/palette.jsx +++ b/client/src/themes/palette.jsx @@ -55,24 +55,26 @@ const Palette = (mode) => { } } } : { - mode, - common: { - black: '#000', - white: '#fff' - }, - ...paletteColor, - text: { - primary: paletteColor.grey[700], - secondary: paletteColor.grey[500], - disabled: paletteColor.grey[400] - }, - action: { - disabled: paletteColor.grey[300] - }, - divider: paletteColor.grey[200], - background: { - paper: paletteColor.grey[0], - default: paletteColor.grey.A50 + palette: { + mode, + common: { + black: '#000', + white: '#fff' + }, + ...paletteColor, + text: { + primary: paletteColor.grey[700], + secondary: paletteColor.grey[600], + disabled: paletteColor.grey[500] + }, + action: { + disabled: paletteColor.grey[300] + }, + divider: paletteColor.grey[200], + background: { + paper: paletteColor.grey[0], + default: paletteColor.grey.A50 + } } }); }; diff --git a/go.mod b/go.mod index e0bf3b0..aabd9de 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect github.com/imdario/mergo v0.3.14 // indirect github.com/jarcoal/httpmock v1.0.7 // indirect + github.com/jasonlvhit/gocron v0.0.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/json-iterator/go v1.1.10 // indirect diff --git a/go.sum b/go.sum index 9505f49..c26df79 100644 --- a/go.sum +++ b/go.sum @@ -187,6 +187,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-resty/resty/v2 v2.4.0 h1:s6TItTLejEI+2mn98oijC5w/Rk2YU+OA6x0mnZN6r6k= github.com/go-resty/resty/v2 v2.4.0/go.mod h1:B88+xCTEwvfD94NOuE6GS1wMlnoKNY8eEiNizfNwOwA= @@ -302,6 +303,8 @@ github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+h github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.0.7 h1:d1a2VFpSdm5gtjhCPWsQHSnx8+5V3ms5431YwvmkuNk= github.com/jarcoal/httpmock v1.0.7/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jasonlvhit/gocron v0.0.1 h1:qTt5qF3b3srDjeOIR4Le1LfeyvoYzJlYpqvG7tJX5YU= +github.com/jasonlvhit/gocron v0.0.1/go.mod h1:k9a3TV8VcU73XZxfVHCHWMWF9SOqgoku0/QlY2yvlA4= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -391,7 +394,9 @@ github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4r github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= diff --git a/package.json b/package.json index ffeb68c..3fea4f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.0.9", + "version": "0.1.0", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/CRON.go b/src/CRON.go new file mode 100644 index 0000000..a013b28 --- /dev/null +++ b/src/CRON.go @@ -0,0 +1,69 @@ +package main + +import ( + "github.com/jasonlvhit/gocron" + "io/ioutil" + "net/http" + "github.com/azukaar/cosmos-server/src/utils" + "os" + "path/filepath" + "encoding/json" +) + +type Version struct { + Version string `json:"version"` +} + +func checkVersion() { + + ex, err := os.Executable() + if err != nil { + panic(err) + } + exPath := filepath.Dir(ex) + + pjs, errPR := os.Open(exPath + "/meta.json") + if errPR != nil { + utils.Error("checkVersion", errPR) + return + } + + packageJson, _ := ioutil.ReadAll(pjs) + + utils.Debug("checkVersion" + string(packageJson)) + + var version Version + errJ := json.Unmarshal(packageJson, &version) + if errJ != nil { + utils.Error("checkVersion", errJ) + return + } + + myVersion := version.Version + + response, err := http.Get("https://comos-technologies.com/versions/" + myVersion) + if err != nil { + utils.Error("checkVersion", err) + return + } + + defer response.Body.Close() + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + utils.Error("checkVersion", err) + return + } + + if string(body) != myVersion { + utils.Log("New version available: " + string(body)) + // update + } else { + utils.Log("No new version available") + } +} + +func CRON() { + gocron.Every(1).Day().At("00:00").Do(checkVersion) + <-gocron.Start() +} \ No newline at end of file diff --git a/src/configapi/set.go b/src/configapi/set.go index cc23b54..1d47ab5 100644 --- a/src/configapi/set.go +++ b/src/configapi/set.go @@ -33,6 +33,7 @@ func ConfigApiSet(w http.ResponseWriter, req *http.Request) { config := utils.GetBaseMainConfig() request.HTTPConfig.AuthPrivateKey = config.HTTPConfig.AuthPrivateKey request.HTTPConfig.TLSKey = config.HTTPConfig.TLSKey + request.NewInstall = config.NewInstall utils.SaveConfigTofile(request) diff --git a/src/docker/api_secureContainer.go b/src/docker/api_secureContainer.go index 805fde7..0ee598b 100644 --- a/src/docker/api_secureContainer.go +++ b/src/docker/api_secureContainer.go @@ -15,7 +15,8 @@ func SecureContainerRoute(w http.ResponseWriter, req *http.Request) { } vars := mux.Vars(req) - containerName := utils.Sanitize(vars["container"]) + containerName := utils.Sanitize(vars["containerId"]) + status := utils.Sanitize(vars["status"]) if(req.Method == "GET") { container, err := DockerClient.ContainerInspect(DockerContext, containerName) @@ -26,10 +27,10 @@ func SecureContainerRoute(w http.ResponseWriter, req *http.Request) { } AddLabels(container, map[string]string{ - "cosmos-force-network-secured": "true", + "cosmos-force-network-secured": status, }); - utils.Log("API: Add Force network secured: " + containerName) + utils.Log("API: Set Force network secured "+status+" : " + containerName) _, errEdit := EditContainer(container.ID, container) if errEdit != nil { diff --git a/src/docker/docker.go b/src/docker/docker.go index aae5bdb..0284d50 100644 --- a/src/docker/docker.go +++ b/src/docker/docker.go @@ -138,7 +138,9 @@ func ListContainers() ([]types.Container, error) { return nil, errD } - containers, err := DockerClient.ContainerList(DockerContext, types.ContainerListOptions{}) + containers, err := DockerClient.ContainerList(DockerContext, types.ContainerListOptions{ + All: true, + }) if err != nil { return nil, err } diff --git a/src/httpServer.go b/src/httpServer.go index ff86b18..bf5ed97 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -202,7 +202,7 @@ func StartServer() { srapi.HandleFunc("/api/users/{nickname}", user.UsersIdRoute) srapi.HandleFunc("/api/users", user.UsersRoute) - srapi.HandleFunc("/api/servapps/{container}/secure", docker.SecureContainerRoute) + srapi.HandleFunc("/api/servapps/{containerId}/secure/{status}", docker.SecureContainerRoute) srapi.HandleFunc("/api/servapps", docker.ContainersRoute) srapi.Use(tokenMiddleware) diff --git a/src/index.go b/src/index.go index d64a72f..e133961 100644 --- a/src/index.go +++ b/src/index.go @@ -14,6 +14,8 @@ func main() { LoadConfig() + go CRON() + docker.Test() docker.DockerListenEvents() diff --git a/src/proxy/buildFromConfig.go b/src/proxy/buildFromConfig.go index 612ecd4..91223da 100644 --- a/src/proxy/buildFromConfig.go +++ b/src/proxy/buildFromConfig.go @@ -15,7 +15,7 @@ func BuildFromConfig(router *mux.Router, config utils.ProxyConfig) *mux.Router { for i := len(config.Routes)-1; i >= 0; i-- { routeConfig := config.Routes[i] - RouterGen(routeConfig, router, RouteTo(routeConfig.Target)) + RouterGen(routeConfig, router, RouteTo(routeConfig)) } return router diff --git a/src/proxy/routeTo.go b/src/proxy/routeTo.go index e1bfa94..106d1db 100644 --- a/src/proxy/routeTo.go +++ b/src/proxy/routeTo.go @@ -4,6 +4,7 @@ import ( "net/http" "net/http/httputil" "net/url" + spa "github.com/roberthodgen/spa-server" "github.com/azukaar/cosmos-server/src/utils" // "io/ioutil" // "io" @@ -20,7 +21,6 @@ func NewProxy(targetHost string) (*httputil.ReverseProxy, error) { proxy := httputil.NewSingleHostReverseProxy(url) - // upgrade the request to websocket proxy.ModifyResponse = func(resp *http.Response) error { utils.Debug("Response from backend: " + resp.Status) utils.Debug("URL was " + resp.Request.URL.String()) @@ -30,20 +30,31 @@ func NewProxy(targetHost string) (*httputil.ReverseProxy, error) { return proxy, nil } -// ProxyRequestHandler handles the http request using proxy -func ProxyRequestHandler(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - proxy.ServeHTTP(w, r) - } -} -func RouteTo(destination string) *httputil.ReverseProxy /*func(http.ResponseWriter, *http.Request)*/ { +func RouteTo(route utils.ProxyRouteConfig) http.Handler /*func(http.ResponseWriter, *http.Request)*/ { // initialize a reverse proxy and pass the actual backend server url here - proxy, err := NewProxy(destination) - if err != nil { - panic(err) - } - // create a handler function which uses the reverse proxy - return proxy //ProxyRequestHandler(proxy) + destination := route.Target + routeType := route.Mode + + if(routeType == "SERVAPP" || routeType == "PROXY") { + proxy, err := NewProxy(destination) + if err != nil { + utils.Error("Create Route", err) + } + + // create a handler function which uses the reverse proxy + return proxy + } else if (routeType == "STATIC") { + return http.FileServer(http.Dir(destination)) + } else if (routeType == "SPA") { + return spa.SpaHandler(destination, "index.html") + } else if(routeType == "REDIRECT") { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, destination, 302) + }) + } else { + utils.Error("Invalid route type", nil) + return nil + } } \ No newline at end of file diff --git a/src/proxy/routerGen.go b/src/proxy/routerGen.go index bb5a7c2..d6de234 100644 --- a/src/proxy/routerGen.go +++ b/src/proxy/routerGen.go @@ -2,7 +2,6 @@ package proxy import ( "net/http" - "net/http/httputil" "github.com/gorilla/mux" "time" "github.com/azukaar/cosmos-server/src/utils" @@ -44,10 +43,7 @@ func tokenMiddleware(enabled bool) func(next http.Handler) http.Handler { } } -func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination *httputil.ReverseProxy) *mux.Route { - var realDestination http.Handler - realDestination = destination - +func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination http.Handler) *mux.Route { origin := router.Methods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD") if(route.UseHost) { @@ -55,11 +51,17 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination *ht } if(route.UsePathPrefix) { + if(route.PathPrefix != "" && route.PathPrefix[0] != '/') { + utils.Error("PathPrefix must start with a /", nil) + } origin = origin.PathPrefix(route.PathPrefix) } if(route.UsePathPrefix && route.StripPathPrefix) { - realDestination = http.StripPrefix(route.PathPrefix, destination) + if(route.PathPrefix != "" && route.PathPrefix[0] != '/') { + utils.Error("PathPrefix must start with a /", nil) + } + destination = http.StripPrefix(route.PathPrefix, destination) } timeout := route.Timeout @@ -83,6 +85,10 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination *ht } } + if(route.UsePathPrefix && !route.StripPathPrefix && (route.Mode == "STATIC" || route.Mode == "SPA")) { + utils.Warn("PathPrefix is used, but StripPathPrefix is false. The route mode is " + (string)(route.Mode) + ". This will likely cause issues with the route. Ignore this warning if you know what you are doing.") + } + origin.Handler( tokenMiddleware(route.AuthEnabled)( utils.CORSHeader(originCORS)( @@ -95,7 +101,9 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination *ht http.StatusTooManyRequests, "HTTP003") return }), - )(realDestination))))) + )(destination))))) + + utils.Log("Added route: ["+ (string)(route.Mode) + "] " + route.Host + route.PathPrefix + " to " + route.Target + "") return origin } \ No newline at end of file