diff --git a/changelog.md b/changelog.md index b4a8a3b..4756dcb 100644 --- a/changelog.md +++ b/changelog.md @@ -1,13 +1,16 @@ ## Version 0.3.0 - - Set Max nb simulatneous connections per user - - Set Global Max nb simulatneous connections - - Display nickname on invite page - - Reset self-signed certificates when hostnames changes - - Block based on geo-locations - - Block common bots - - DNS challenge for letsencrypt - Implement 2 FA - Implement SMTP to Send Email (password reset / invites) + - Add homepage + - DNS challenge for letsencrypt + - Set Max nb simulatneous connections per user + - Admin only routes (See in security tab) + - Set Global Max nb simulatneous connections + - Block based on geo-locations + - Block common bots + - Display nickname on invite page + - Reset self-signed certificates when hostnames changes + - Edit user emails - Show loading on user rows on actions ## Version 0.2.0 diff --git a/client/src/assets/images/wallpaper(1).png b/client/src/assets/images/wallpaper(1).png new file mode 100644 index 0000000..a21be36 Binary files /dev/null and b/client/src/assets/images/wallpaper(1).png differ diff --git a/client/src/assets/images/wallpaper.png b/client/src/assets/images/wallpaper.png new file mode 100644 index 0000000..63cfa9b Binary files /dev/null and b/client/src/assets/images/wallpaper.png differ diff --git a/client/src/components/countrySelect.jsx b/client/src/components/countrySelect.jsx index 0450fa4..da8dd5c 100644 --- a/client/src/components/countrySelect.jsx +++ b/client/src/components/countrySelect.jsx @@ -451,6 +451,12 @@ export default function CountrySelect({name, label, formik}) { onChange={(event, value) => { formik.setFieldValue(name, value) }} + filterOptions={(options, state) => { + const inputValue = state.inputValue.toUpperCase(); + return options.filter((option) => { + return countries[option].label.toUpperCase().includes(inputValue) + }) + }} error={Boolean(formik.touched[name] && formik.errors[name])} getOptionLabel={(option) =>
{ }) .map((row, key) => ( onRowClick && onRowClick(row, key)} key={getKey(row)} sx={{ cursor: 'pointer', @@ -81,6 +80,7 @@ const PrettyTableView = ({ getKey, data, columns, onRowClick, linkTo }) => { (!column.screenMin || screenMin[column.screenMin]) && !column.clickable && onRowClick && onRowClick(row, key)} to={linkTo && linkTo(row, key)} className={column.underline ? 'emphasis' : ''} sx={{ diff --git a/client/src/menu-items/dashboard.jsx b/client/src/menu-items/dashboard.jsx index 8708cbc..78e424e 100644 --- a/client/src/menu-items/dashboard.jsx +++ b/client/src/menu-items/dashboard.jsx @@ -1,5 +1,5 @@ // assets -import { HomeOutlined, AppstoreOutlined } from '@ant-design/icons'; +import { HomeOutlined, AppstoreOutlined, DashboardOutlined } from '@ant-design/icons'; // icons const icons = { @@ -17,16 +17,17 @@ const dashboard = { id: 'home', title: 'Home', type: 'item', - url: '/ui', + url: '/ui/', icon: icons.HomeOutlined, breadcrumbs: false }, { - id: 'servapps', - title: 'ServApps', + id: 'dashboard', + title: 'Dashboard', type: 'item', - url: '/ui/servapps', - icon: AppstoreOutlined + url: '/ui/dashboard', + icon: DashboardOutlined, + breadcrumbs: false }, ] }; diff --git a/client/src/menu-items/pages.jsx b/client/src/menu-items/pages.jsx index cdd1949..c5af6c4 100644 --- a/client/src/menu-items/pages.jsx +++ b/client/src/menu-items/pages.jsx @@ -1,5 +1,5 @@ // assets -import { ProfileOutlined, SettingOutlined, NodeExpandOutlined} from '@ant-design/icons'; +import { ProfileOutlined, SettingOutlined, NodeExpandOutlined, AppstoreOutlined} from '@ant-design/icons'; // icons const icons = { @@ -15,6 +15,13 @@ const pages = { title: 'Management', type: 'group', children: [ + { + id: 'servapps', + title: 'ServApps', + type: 'item', + url: '/ui/servapps', + icon: AppstoreOutlined + }, { id: 'url', title: 'URLs', @@ -24,7 +31,7 @@ const pages = { }, { id: 'users', - title: 'Manage Users', + title: 'Users', type: 'item', url: '/ui/config-users', icon: icons.ProfileOutlined, diff --git a/client/src/pages/authentication/auth-forms/AuthLogin.jsx b/client/src/pages/authentication/auth-forms/AuthLogin.jsx index 7e84775..9df8cf4 100644 --- a/client/src/pages/authentication/auth-forms/AuthLogin.jsx +++ b/client/src/pages/authentication/auth-forms/AuthLogin.jsx @@ -51,6 +51,7 @@ const AuthLogin = () => { // TODO: Extract ?redirect= to redirect to a specific page after login const urlSearchParams = new URLSearchParams(window.location.search); const notLogged = urlSearchParams.get('notlogged') == 1; + const notLoggedAdmin = urlSearchParams.get('notlogged') == 2; const invalid = urlSearchParams.get('invalid') == 1; const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/ui'; @@ -78,6 +79,11 @@ const AuthLogin = () => {
} + { notLoggedAdmin && + You need to be Admin +
+
} + { invalid && You have been disconnected. Please login to continue
diff --git a/client/src/pages/config/routes/routeSecurity.jsx b/client/src/pages/config/routes/routeSecurity.jsx index 6af2f8a..c0fa2ee 100644 --- a/client/src/pages/config/routes/routeSecurity.jsx +++ b/client/src/pages/config/routes/routeSecurity.jsx @@ -30,6 +30,7 @@ const RouteSecurity = ({ routeConfig }) => { MaxBandwith: routeConfig.MaxBandwith, BlockAPIAbuse: routeConfig.BlockAPIAbuse, BlockCommonBots: routeConfig.BlockCommonBots, + AdminOnly: routeConfig.AdminOnly, _SmartShield_Enabled: (routeConfig.SmartShield ? routeConfig.SmartShield.Enabled : false), _SmartShield_PolicyStrictness: (routeConfig.SmartShield ? routeConfig.SmartShield.PolicyStrictness : 0), _SmartShield_PerUserTimeBudget: (routeConfig.SmartShield ? routeConfig.SmartShield.PerUserTimeBudget : 0), @@ -97,6 +98,12 @@ const RouteSecurity = ({ routeConfig }) => { formik={formik} /> + + { LoggingLevel: config.LoggingLevel, RequireMFA: config.RequireMFA, GeoBlocking: config.BlockedCountries, + AutoUpdate: config.AutoUpdate, Hostname: config.HTTPConfig.Hostname, GenerateMissingTLSCert: config.HTTPConfig.GenerateMissingTLSCert, @@ -89,6 +90,7 @@ const ConfigManagement = () => { MongoDB: values.MongoDB, LoggingLevel: values.LoggingLevel, RequireMFA: values.RequireMFA, + AutoUpdate: values.AutoUpdate, BlockedCountries: values.GeoBlocking, HTTPConfig: { ...config.HTTPConfig, @@ -150,6 +152,12 @@ const ConfigManagement = () => { formik={formik} helperText="Require MFA for all users" /> + + diff --git a/client/src/pages/config/users/usermanagement.jsx b/client/src/pages/config/users/usermanagement.jsx index f1129c4..1638d26 100644 --- a/client/src/pages/config/users/usermanagement.jsx +++ b/client/src/pages/config/users/usermanagement.jsx @@ -29,6 +29,7 @@ const UserManagement = () => { const [openCreateForm, setOpenCreateForm] = React.useState(false); const [openDeleteForm, setOpenDeleteForm] = React.useState(false); const [openInviteForm, setOpenInviteForm] = React.useState(false); + const [openEditEmail, setOpenEditEmail] = React.useState(false); const [toAction, setToAction] = React.useState(null); const [loadingRow, setLoadingRow] = React.useState(null); @@ -120,6 +121,35 @@ const UserManagement = () => { }}>Delete + + setOpenEditEmail(false)}> + Edit Email + + + Use this form to invite edit {openEditEmail}'s Email. + + + + + + + + setOpenCreateForm(false)}> Create User @@ -174,6 +204,9 @@ const UserManagement = () => { {!isLoading && rows && ( { + setOpenEditEmail(r.nickname); + }} getKey={(r) => r.nickname} columns={[ { diff --git a/client/src/pages/dashboard/index.jsx b/client/src/pages/dashboard/index.jsx index 6c6a883..6eea393 100644 --- a/client/src/pages/dashboard/index.jsx +++ b/client/src/pages/dashboard/index.jsx @@ -116,6 +116,12 @@ const DashboardDefault = () => { )} + {coStatus && coStatus.newVersionAvailable && ( + + A new version of Cosmos is available! Please update to the latest version to get the latest features and bug fixes. + + )} + {coStatus && coStatus.needsRestart && ( You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes. diff --git a/client/src/pages/home/index.jsx b/client/src/pages/home/index.jsx new file mode 100644 index 0000000..88b1047 --- /dev/null +++ b/client/src/pages/home/index.jsx @@ -0,0 +1,151 @@ +import { useParams } from "react-router"; +import Back from "../../components/back"; +import { Alert, Box, CircularProgress, Grid, Stack, useTheme } from "@mui/material"; +import { useEffect, useState } from "react"; +import * as API from "../../api"; +import wallpaper from '../../assets/images/wallpaper.png'; +import Grid2 from "@mui/material/Unstable_Grid2/Grid2"; +import { getFaviconURL } from "../../utils/routes"; +import { Link } from "react-router-dom"; +import { getFullOrigin } from "../../utils/routes"; +import IsLoggedIn from "../../isLoggedIn"; + + +const HomeBackground = () => { + const theme = useTheme(); + return ( + + Cosmos + + ); +}; + +const blockStyle = { + margin: 0, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: '78%', + verticalAlign: 'middle', +} + +const HomePage = () => { + const { routeName } = useParams(); + const [config, setConfig] = useState(null); + const [coStatus, setCoStatus] = useState(null); + + const refreshStatus = () => { + API.getStatus().then((res) => { + setCoStatus(res.data); + }); + } + + const refreshConfig = () => { + API.config.get().then((res) => { + setConfig(res.data); + }); + }; + + let routes = config && (config.HTTPConfig.ProxyConfig.Routes || []); + + useEffect(() => { + refreshConfig(); + refreshStatus(); + }, []); + + + return + + + + + {coStatus && !coStatus.database && ( + + No Database is setup for Cosmos! User Management and Authentication will not work.
+ You can either setup the database, or disable user management in the configuration panel.
+
+ )} + + {coStatus && coStatus.letsencrypt && ( + + You have enabled Let's Encrypt for automatic HTTPS Certificate. You need to provide the configuration with an email address to use for Let's Encrypt in the configs. + + )} + + {coStatus && coStatus.newVersionAvailable && ( + + A new version of Cosmos is available! Please update to the latest version to get the latest features and bug fixes. + + )} + + {coStatus && coStatus.needsRestart && ( + + You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes. + + )} + + {coStatus && coStatus.domain && ( + + You are using localhost or 0.0.0.0 as a hostname in the configuration. It is recommended that you use a domain name instead. + + )} + + {coStatus && !coStatus.docker && ( + + Docker is not connected! Please check your docker connection.
+ Did you forget to add
-v /var/run/docker.sock:/var/run/docker.sock
to your docker run command?
+ if your docker daemon is running somewhere else, please add
-e DOCKER_HOST=...
to your docker run command. +
+ )} +
+ + + {config && config.HTTPConfig.ProxyConfig.Routes.map((route) => { + return + + + + + +
+

{route.Name}

+

{route.Description}

+

{route.Target}

+
+
+ +
+
+ })} +
+
+} + +export default HomePage; \ No newline at end of file diff --git a/client/src/pages/newInstall/newInstall.jsx b/client/src/pages/newInstall/newInstall.jsx index 20a425f..243e881 100644 --- a/client/src/pages/newInstall/newInstall.jsx +++ b/client/src/pages/newInstall/newInstall.jsx @@ -63,7 +63,7 @@ const NewInstall = () => { label: 'Docker 🐋 (step 1/4)', component:
- Cosmos is using docker to run applications. It is optionnal, but Cosmos will run in reverse-proxy-only mode if it cannot connect to Docker. + Cosmos is using docker to run applications. It is optional, but Cosmos will run in reverse-proxy-only mode if it cannot connect to Docker.
{(status && status.docker) ? @@ -98,7 +98,7 @@ const NewInstall = () => { label: 'Database 🗄️ (step 2/4)', component:
- Cosmos is using a MongoDB database to store all the data. It is optionnal, but Authentication as well as the UI will not work without a database. + Cosmos is using a MongoDB database to store all the data. It is optional, but Authentication as well as the UI will not work without a database.
{(status && status.database) ? @@ -412,7 +412,7 @@ const NewInstall = () => { Well done! You have successfully installed Cosmos. You can now login to your server using the admin account you created. If you have changed the hostname, don't forget to use that URL to access your server after the restart. If you have are running into issues, check the logs for any error messages and edit the file in the /config folder. - If you still don't manage, please join our Discord server and we'll be happy to help! + If you still don't manage, please join our Discord server and we'll be happy to help!
, nextButtonLabel: () => { return 'Apply and Restart'; diff --git a/client/src/routes/MainRoutes.jsx b/client/src/routes/MainRoutes.jsx index b0239be..0850ffb 100644 --- a/client/src/routes/MainRoutes.jsx +++ b/client/src/routes/MainRoutes.jsx @@ -10,6 +10,7 @@ import ServeApps from '../pages/servapps/servapps'; import { Navigate } from 'react-router'; import RouteConfigPage from '../pages/config/routeConfigPage'; import logo from '../assets/images/icons/cosmos.png'; +import HomePage from '../pages/home'; // render - dashboard @@ -43,6 +44,10 @@ const MainRoutes = { }, { path: '/ui', + element: + }, + { + path: '/ui/dashboard', element: }, { diff --git a/client/src/utils/routes.jsx b/client/src/utils/routes.jsx index dd6e1c9..df11674 100644 --- a/client/src/utils/routes.jsx +++ b/client/src/utils/routes.jsx @@ -33,7 +33,7 @@ const addProtocol = (url) => { } export const getOrigin = (route) => { - return (route.UseHost ? route.Host : '') + (route.UsePathPrefix ? route.PathPrefix : ''); + return (route.UseHost ? route.Host : window.location.origin) + (route.UsePathPrefix ? route.PathPrefix : ''); } export const getFullOrigin = (route) => { diff --git a/go.mod b/go.mod index a630c27..c0dafba 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.0 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect github.com/PuerkitoBio/goquery v1.6.0 // indirect diff --git a/go.sum b/go.sum index a2db335..41d5f6d 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= diff --git a/package.json b/package.json index 83dc588..675eed6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.3.0-unstable13", + "version": "0.3.0-unstable14", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/CRON.go b/src/CRON.go index f1ef444..d5d7ab8 100644 --- a/src/CRON.go +++ b/src/CRON.go @@ -1,21 +1,46 @@ package main import ( - "github.com/jasonlvhit/gocron" "io/ioutil" "net/http" "github.com/azukaar/cosmos-server/src/utils" - // "github.com/azukaar/cosmos-server/src/docker" "os" "path/filepath" "encoding/json" + + "github.com/jasonlvhit/gocron" + "github.com/Masterminds/semver" ) type Version struct { Version string `json:"version"` } +// compareSemver compares two semantic version strings. +// Returns: +// 0 if v1 == v2 +// 1 if v1 > v2 +// -1 if v1 < v2 +// error if there's a problem parsing either version string +func compareSemver(v1, v2 string) (int, error) { + ver1, err := semver.NewVersion(v1) + if err != nil { + utils.Error("compareSemver 1 " + v1, err) + return 0, err + } + + ver2, err := semver.NewVersion(v2) + if err != nil { + utils.Error("compareSemver 2 " + v2, err) + return 0, err + } + + return ver1.Compare(ver2), nil +} + + func checkVersion() { + utils.NewVersionAvailable = false ex, err := os.Executable() if err != nil { @@ -56,9 +81,16 @@ func checkVersion() { return } - if string(body) != myVersion { + cp, errc := compareSemver(myVersion, string(body)) + + if errc != nil { + utils.Error("checkVersion", errc) + return + } + + if cp == -1 { utils.Log("New version available: " + string(body)) - // update + utils.NewVersionAvailable = true } else { utils.Log("No new version available") } diff --git a/src/configapi/get.go b/src/configapi/get.go index cff6670..9d3ce77 100644 --- a/src/configapi/get.go +++ b/src/configapi/get.go @@ -7,9 +7,11 @@ import ( ) func ConfigApiGet(w http.ResponseWriter, req *http.Request) { - if utils.AdminOnly(w, req) != nil { + if utils.LoggedInOnly(w, req) != nil { return - } + } + + isAdmin := utils.IsAdmin(req) if(req.Method == "GET") { config := utils.ReadConfigFromFile() @@ -18,6 +20,22 @@ func ConfigApiGet(w http.ResponseWriter, req *http.Request) { config.HTTPConfig.AuthPrivateKey = "" config.HTTPConfig.TLSKey = "" + if !isAdmin { + config.MongoDB = "***" + config.EmailConfig.Password = "***" + config.EmailConfig.Username = "***" + config.EmailConfig.Host = "***" + + // filter admin only routes + filteredRoutes := make([]utils.ProxyRouteConfig, 0) + for _, route := range config.HTTPConfig.ProxyConfig.Routes { + if !route.AdminOnly { + filteredRoutes = append(filteredRoutes, route) + } + } + config.HTTPConfig.ProxyConfig.Routes = filteredRoutes + } + json.NewEncoder(w).Encode(map[string]interface{}{ "status": "OK", "data": config, diff --git a/src/index.go b/src/index.go index 31fd3c6..de1ac3b 100644 --- a/src/index.go +++ b/src/index.go @@ -16,6 +16,8 @@ func main() { LoadConfig() + checkVersion() + go CRON() docker.Test() diff --git a/src/proxy/routerGen.go b/src/proxy/routerGen.go index ac4ab7b..ccc31ad 100644 --- a/src/proxy/routerGen.go +++ b/src/proxy/routerGen.go @@ -12,7 +12,7 @@ import ( "github.com/gorilla/mux" ) -func tokenMiddleware(enabled bool) func(next http.Handler) http.Handler { +func tokenMiddleware(enabled bool, adminOnly 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.Del("x-cosmos-user") @@ -25,6 +25,7 @@ func tokenMiddleware(enabled bool) func(next http.Handler) http.Handler { return } + r.Header.Set("x-cosmos-user", u.Nickname) r.Header.Set("x-cosmos-role", strconv.Itoa((int)(u.Role))) r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState))) @@ -37,7 +38,9 @@ func tokenMiddleware(enabled bool) func(next http.Handler) http.Handler { // Replace the token with a application speicfic one r.Header.Set("x-cosmos-token", "1234567890") - if enabled { + if enabled && adminOnly { + utils.AdminOnlyWithRedirect(w, r) + } else if enabled { utils.LoggedInOnlyWithRedirect(w, r) } @@ -116,7 +119,7 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination htt destination = utils.BlockPostWithoutReferer(destination) } - destination = tokenMiddleware(route.AuthEnabled)(utils.CORSHeader(originCORS)((destination))) + destination = tokenMiddleware(route.AuthEnabled, route.AdminOnly)(utils.CORSHeader(originCORS)((destination))) origin.Handler(destination) diff --git a/src/status.go b/src/status.go index 81bec10..cfe5155 100644 --- a/src/status.go +++ b/src/status.go @@ -9,7 +9,7 @@ import ( ) func StatusRoute(w http.ResponseWriter, req *http.Request) { - if !utils.GetMainConfig().NewInstall && (utils.AdminOnly(w, req) != nil) { + if !utils.GetMainConfig().NewInstall && (utils.LoggedInOnly(w, req) != nil) { return } @@ -44,6 +44,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) { "domain": utils.GetMainConfig().HTTPConfig.Hostname == "localhost" || utils.GetMainConfig().HTTPConfig.Hostname == "0.0.0.0", "HTTPSCertificateMode": utils.GetMainConfig().HTTPConfig.HTTPSCertificateMode, "needsRestart": utils.NeedsRestart, + "newVersionAvailable": utils.NewVersionAvailable, }, }) } else { diff --git a/src/utils/loggedIn.go b/src/utils/loggedIn.go index 2e45571..747be51 100644 --- a/src/utils/loggedIn.go +++ b/src/utils/loggedIn.go @@ -29,6 +29,36 @@ func LoggedInOnlyWithRedirect(w http.ResponseWriter, req *http.Request) error { return nil } +func AdminOnlyWithRedirect(w http.ResponseWriter, req *http.Request) error { + userNickname := req.Header.Get("x-cosmos-user") + role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role")) + mfa, _ := strconv.Atoi(req.Header.Get("x-cosmos-mfa")) + isUserLoggedIn := role > 0 + isUserAdmin := role > 1 + + if !isUserLoggedIn || userNickname == "" { + Error("AdminLoggedInOnlyWithRedirect: User is not logged in", nil) + http.Redirect(w, req, "/ui/login?notlogged=1&redirect="+req.URL.Path, http.StatusFound) + return errors.New("User is not logged") + } + + if isUserLoggedIn && !isUserAdmin { + Error("AdminLoggedInOnly: User is not Authorized", nil) + HTTPError(w, "User not Authorized", http.StatusUnauthorized, "HTTP004") + return errors.New("User is not Admin") + } + + if(mfa == 1) { + http.Redirect(w, req, "/ui/loginmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect) + return errors.New("User requires MFA") + } else if(mfa == 2) { + http.Redirect(w, req, "/ui/newmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect) + return errors.New("User requires MFA Setup") + } + + return nil +} + func LoggedInWeakOnly(w http.ResponseWriter, req *http.Request) error { userNickname := req.Header.Get("x-cosmos-user") role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role")) @@ -97,6 +127,11 @@ func AdminOnly(w http.ResponseWriter, req *http.Request) error { return nil } +func IsAdmin(req *http.Request) bool { + role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role")) + return role > 1 +} + func AdminOrItselfOnly(w http.ResponseWriter, req *http.Request, nickname string) error { userNickname := req.Header.Get("x-cosmos-user") role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role")) diff --git a/src/utils/types.go b/src/utils/types.go index 082568f..7476bc6 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -84,6 +84,7 @@ type Config struct { BlockedCountries []string ServerCountry string RequireMFA bool + AutoUpdate bool } type HTTPConfig struct { @@ -139,6 +140,7 @@ type ProxyRouteConfig struct { StripPathPrefix bool MaxBandwith int64 AuthEnabled bool + AdminOnly bool Target string `validate:"required"` SmartShield SmartShieldPolicy Mode ProxyMode diff --git a/src/utils/utils.go b/src/utils/utils.go index 2ffb1c3..76522e8 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -19,12 +19,14 @@ import ( var BaseMainConfig Config var MainConfig Config var IsHTTPS = false +var NewVersionAvailable = false var NeedsRestart = false var DefaultConfig = Config{ LoggingLevel: "INFO", NewInstall: true, + AutoUpdate: true, // By default we block all countries that have a high amount of attacks // Note that Cosmos wont block the country of origin of the server even if it is in this list BlockedCountries: []string{