v0.3.0-unstable14
This commit is contained in:
parent
29fc924475
commit
e93e45d4df
17
changelog.md
17
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
|
||||
|
|
BIN
client/src/assets/images/wallpaper(1).png
Normal file
BIN
client/src/assets/images/wallpaper(1).png
Normal file
Binary file not shown.
After Width: | Height: | Size: 410 KiB |
BIN
client/src/assets/images/wallpaper.png
Normal file
BIN
client/src/assets/images/wallpaper.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 684 KiB |
|
@ -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"
|
||||
|
|
|
@ -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={{
|
||||
|
|
|
@ -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
|
||||
},
|
||||
]
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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={[
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
151
client/src/pages/home/index.jsx
Normal file
151
client/src/pages/home/index.jsx
Normal 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;
|
|
@ -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';
|
||||
|
|
|
@ -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 />
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
1
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
|
||||
|
|
2
go.sum
2
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=
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.3.0-unstable13",
|
||||
"version": "0.3.0-unstable14",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
|
40
src/CRON.go
40
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")
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -16,6 +16,8 @@ func main() {
|
|||
|
||||
LoadConfig()
|
||||
|
||||
checkVersion()
|
||||
|
||||
go CRON()
|
||||
|
||||
docker.Test()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Reference in a new issue