v0.3.0-unstable14

This commit is contained in:
Yann Stepienik 2023-05-01 12:59:46 +01:00
parent 29fc924475
commit e93e45d4df
27 changed files with 362 additions and 31 deletions

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 KiB

View file

@ -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) => <div style={{verticalAlign: 'middle'}}><img
loading="lazy"

View file

@ -62,7 +62,6 @@ const PrettyTableView = ({ getKey, data, columns, onRowClick, linkTo }) => {
})
.map((row, key) => (
<TableRow
onClick={() => 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]) && <TableCell
component={(linkTo && !column.clickable) ? Link : 'td'}
onClick={() => !column.clickable && onRowClick && onRowClick(row, key)}
to={linkTo && linkTo(row, key)}
className={column.underline ? 'emphasis' : ''}
sx={{

View file

@ -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
},
]
};

View file

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

View file

@ -51,6 +51,7 @@ const AuthLogin = () => {
// TODO: Extract ?redirect=<URL> 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 = () => {
<br />
</Grid>}
{ notLoggedAdmin &&<Grid container spacing={2} justifyContent="center">
<Alert severity="error">You need to be Admin</Alert>
<br />
</Grid>}
{ invalid &&<Grid container spacing={2} justifyContent="center">
<Alert severity="error">You have been disconnected. Please login to continue</Alert>
<br />

View file

@ -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}
/>
<CosmosCheckbox
name="AdminOnly"
label="Admin only"
formik={formik}
/>
<CosmosFormDivider title={'Smart Shield'} />
<CosmosCheckbox

View file

@ -59,6 +59,7 @@ const ConfigManagement = () => {
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"
/>
<CosmosCheckbox
label="Auto Update Cosmos"
name="AutoUpdate"
formik={formik}
/>
<Grid item xs={12}>
<Stack spacing={1}>

View file

@ -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</Button>
</DialogActions>
</Dialog>
<Dialog open={openEditEmail} onClose={() => setOpenEditEmail(false)}>
<DialogTitle>Edit Email</DialogTitle>
<DialogContent>
<DialogContentText>
Use this form to invite edit {openEditEmail}'s Email.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="c-email-edit"
label="Email Address"
type="email"
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenEditEmail(false)}>Cancel</Button>
<Button onClick={() => {
API.users.edit(openEditEmail, {
email: document.getElementById('c-email-edit').value,
}).then(() => {
setOpenEditEmail(false);
refresh();
});
}}>Edit</Button>
</DialogActions>
</Dialog>
<Dialog open={openCreateForm} onClose={() => setOpenCreateForm(false)}>
<DialogTitle>Create User</DialogTitle>
@ -174,6 +204,9 @@ const UserManagement = () => {
{!isLoading && rows && (<PrettyTableView
data={rows}
onRowClick = {(r) => {
setOpenEditEmail(r.nickname);
}}
getKey={(r) => r.nickname}
columns={[
{

View file

@ -116,6 +116,12 @@ const DashboardDefault = () => {
</Alert>
)}
{coStatus && coStatus.newVersionAvailable && (
<Alert severity="warning">
A new version of Cosmos is available! Please update to the latest version to get the latest features and bug fixes.
</Alert>
)}
{coStatus && coStatus.needsRestart && (
<Alert severity="warning">
You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes.

View file

@ -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 (
<Box sx={{ position: 'fixed', float: 'left', overflow: 'hidden', zIndex: 0, top: 0, left: 0, right: 0, bottom: 0 }}>
<img src={wallpaper} style={{ display:'inline'}} alt="Cosmos" width="100%" height="100%" />
</Box>
);
};
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 <Stack spacing={2} >
<IsLoggedIn />
<HomeBackground />
<style>
{`header {
background: rgba(0.2,0.2,0.2,0.2) !important;
border-bottom-color: rgba(0.4,0.4,0.4,0.4) !important;
color: white !important;
}
header .MuiChip-label {
color: #eee !important;
}
header .MuiButtonBase-root {
color: #eee !important;
background: rgba(0.2,0.2,0.2,0.2) !important;
}
.app {
transition: background 0.1s ease-in-out;
transition: transform 0.1s ease-in-out;
}
.app:hover {
cursor: pointer;
background: rgba(0.4,0.4,0.4,0.4) !important;
transform: scale(1.05);
}
`}
</style>
<Stack style={{zIndex: 2}} spacing={1}>
{coStatus && !coStatus.database && (
<Alert severity="error">
No Database is setup for Cosmos! User Management and Authentication will not work.<br />
You can either setup the database, or disable user management in the configuration panel.<br />
</Alert>
)}
{coStatus && coStatus.letsencrypt && (
<Alert severity="error">
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.
</Alert>
)}
{coStatus && coStatus.newVersionAvailable && (
<Alert severity="warning">
A new version of Cosmos is available! Please update to the latest version to get the latest features and bug fixes.
</Alert>
)}
{coStatus && coStatus.needsRestart && (
<Alert severity="warning">
You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes.
</Alert>
)}
{coStatus && coStatus.domain && (
<Alert severity="error">
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.
</Alert>
)}
{coStatus && !coStatus.docker && (
<Alert severity="error">
Docker is not connected! Please check your docker connection.<br/>
Did you forget to add <pre>-v /var/run/docker.sock:/var/run/docker.sock</pre> to your docker run command?<br />
if your docker daemon is running somewhere else, please add <pre>-e DOCKER_HOST=...</pre> to your docker run command.
</Alert>
)}
</Stack>
<Grid2 container spacing={2} style={{zIndex: 2}}>
{config && config.HTTPConfig.ProxyConfig.Routes.map((route) => {
return <Grid2 item xs={12} sm={6} md={4} lg={3} xl={2} key={route.Name}>
<Box className='app' style={{padding: 10, color: 'white', background: 'rgba(0,0,0,0.35)', borderRadius: 5}}>
<Link to={getFullOrigin(route)} target="_blank" style={{textDecoration: 'none', color: 'white'}}>
<Stack direction="row" spacing={2} alignItems="center">
<img src={getFaviconURL(route)} width="64px" />
<div style={{width: '100%'}}>
<h3 style={blockStyle}>{route.Name}</h3>
<p style={blockStyle}>{route.Description}</p>
<p style={blockStyle}>{route.Target}</p>
</div>
</Stack>
</Link>
</Box>
</Grid2>
})}
</Grid2>
</Stack>
}
export default HomePage;

View file

@ -63,7 +63,7 @@ const NewInstall = () => {
label: 'Docker 🐋 (step 1/4)',
component: <Stack item xs={12} spacing={2}>
<div>
<QuestionCircleOutlined /> 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.
<QuestionCircleOutlined /> 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.
</div>
{(status && status.docker) ?
<Alert severity="success">
@ -98,7 +98,7 @@ const NewInstall = () => {
label: 'Database 🗄️ (step 2/4)',
component: <Stack item xs={12} spacing={2}>
<div>
<QuestionCircleOutlined /> 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.
<QuestionCircleOutlined /> 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.
</div>
{(status && status.database) ?
<Alert severity="success">
@ -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 <a href="https://discord.gg/PwMWwsrwHA">Discord server</a> and we'll be happy to help!
If you still don't manage, please join our <a target="_blank" href="https://discord.gg/PwMWwsrwHA">Discord server</a> and we'll be happy to help!
</div>,
nextButtonLabel: () => {
return 'Apply and Restart';

View file

@ -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: <HomePage />
},
{
path: '/ui/dashboard',
element: <DashboardDefault />
},
{

View file

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

1
go.mod
View file

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

2
go.sum
View file

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

View file

@ -1,6 +1,6 @@
{
"name": "cosmos-server",
"version": "0.3.0-unstable13",
"version": "0.3.0-unstable14",
"description": "",
"main": "test-server.js",
"bugs": {

View file

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

View file

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

View file

@ -16,6 +16,8 @@ func main() {
LoadConfig()
checkVersion()
go CRON()
docker.Test()

View file

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

View file

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

View file

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

View file

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

View file

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