Browse Source

[release] v0.10.0-unstable15

Yann Stepienik 1 year ago
parent
commit
a6b96bc42a

+ 5 - 1
changelog.md

@@ -6,7 +6,11 @@
 ## Version 0.10.0
 ## Version 0.10.0
  - Added Constellation
  - Added Constellation
  - DNS Challenge is now used for all certificates when enabled
  - DNS Challenge is now used for all certificates when enabled
->>>>>>> b8a9e71 ([release] v0.10.0-unstable)
+ - Rework headers for better compatibility
+ 
+## Version 0.9.20 - 0.9.21
+ - Add option to disable CORS hardening (with empty value)
+
 ## Version 0.9.19
 ## Version 0.9.19
  - Add country whitelist option to geoblocker
  - Add country whitelist option to geoblocker
  - No countries blocked by default anymore
  - No countries blocked by default anymore

+ 22 - 0
client/src/api/constellation.tsx

@@ -28,6 +28,16 @@ function restart() {
   }))
   }))
 }
 }
 
 
+
+function reset() {
+  return wrap(fetch('/cosmos/api/constellation/reset', {
+    method: 'GET',
+    headers: {
+        'Content-Type': 'application/json'
+    }
+  }))
+}
+
 function getConfig() {
 function getConfig() {
   return wrap(fetch('/cosmos/api/constellation/config', {
   return wrap(fetch('/cosmos/api/constellation/config', {
     method: 'GET',
     method: 'GET',
@@ -46,10 +56,22 @@ function getLogs() {
   }))
   }))
 }
 }
 
 
+function connect(file) {
+  return wrap(fetch('/cosmos/api/constellation/connect', {
+    method: 'POST',
+    headers: {
+        'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(file),
+  }))
+}
+
 export {
 export {
   list,
   list,
   addDevice,
   addDevice,
   restart,
   restart,
   getConfig,
   getConfig,
   getLogs,
   getLogs,
+  reset,
+  connect,
 };
 };

+ 48 - 0
client/src/components/confirmModal.jsx

@@ -0,0 +1,48 @@
+// material-ui
+import { LoadingButton } from '@mui/lab';
+import { Button } from '@mui/material';
+import Dialog from '@mui/material/Dialog';
+import DialogActions from '@mui/material/DialogActions';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogTitle from '@mui/material/DialogTitle';
+import * as React from 'react';
+import { useEffect, useState } from 'react';
+
+const ConfirmModal = ({ callback, label, content }) => {
+    const [openModal, setOpenModal] = useState(false);
+
+    return <>
+      <Dialog open={openModal} onClose={() => setOpenModal(false)}>
+          <DialogTitle>Are you sure?</DialogTitle>
+          <DialogContent>
+              <DialogContentText>
+                  {content}
+              </DialogContentText>
+          </DialogContent>
+          <DialogActions>
+              <Button onClick={() => {
+                  setOpenModal(false);           
+              }}>Cancel</Button>
+              <LoadingButton
+              onClick={() => {   
+                  callback();     
+                  setOpenModal(false);    
+              }}>Confirm</LoadingButton>
+          </DialogActions>
+      </Dialog>
+
+      <Button
+          disableElevation
+          variant="outlined"
+          color="warning"
+          onClick={() => {
+              setOpenModal(true);
+          }}
+      >
+        {label}
+      </Button>
+    </>
+};
+
+export default ConfirmModal;

+ 3 - 2
client/src/components/fileUpload.jsx

@@ -2,7 +2,7 @@ import React from 'react';
 import { Button } from '@mui/material';
 import { Button } from '@mui/material';
 import { UploadOutlined } from '@ant-design/icons';
 import { UploadOutlined } from '@ant-design/icons';
 
 
-export default function UploadButtons({OnChange, accept, label}) {
+export default function UploadButtons({OnChange, accept, label, variant, fullWidth, size}) {
   return (
   return (
     <div>
     <div>
       <input
       <input
@@ -14,7 +14,8 @@ export default function UploadButtons({OnChange, accept, label}) {
         onChange={OnChange}
         onChange={OnChange}
       />
       />
       <label htmlFor="contained-button-file">
       <label htmlFor="contained-button-file">
-        <Button variant="contained" component="span" startIcon={<UploadOutlined />}>
+        <Button variant={variant || "contained"} component="span"
+        fullWidth={fullWidth} startIcon={<UploadOutlined />}>
           {label || 'Upload'}
           {label || 'Upload'}
         </Button>
         </Button>
       </label>
       </label>

+ 120 - 31
client/src/pages/constellation/addDevice.jsx

@@ -12,10 +12,67 @@ import { PlusCircleFilled } from '@ant-design/icons';
 import { Formik } from 'formik';
 import { Formik } from 'formik';
 import * as yup from 'yup';
 import * as yup from 'yup';
 import * as API from '../../api';
 import * as API from '../../api';
-import { CosmosFormDivider, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
+import { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
 import { DownloadFile } from '../../api/downloadButton';
 import { DownloadFile } from '../../api/downloadButton';
 import QRCode from 'qrcode';
 import QRCode from 'qrcode';
 
 
+const getDocker = (data, isCompose) => {
+  let lighthouses = '';
+
+  for (let i = 0; i < data.LighthousesList.length; i++) {
+    const l = data.LighthousesList[i];
+    lighthouses += l.publicHostname + ";" +  l.ip + ":" + l.port + ";" + l.isRelay + ",";
+  }
+
+  let containerName = "cosmos-constellation-lighthouse";
+  let imageName = "cosmos-constellation-lighthouse:latest";
+
+  let volPath = "/var/lib/cosmos-constellation";
+
+  if (isCompose) {
+    return `
+version: "3.8"
+services:
+  ${containerName}:
+    image: ${imageName}
+    container_name: ${containerName}
+    restart: unless-stopped
+    network_mode: bridge
+    ports:
+      - "${data.Port}:4242"
+    volumes:
+      - ${volPath}:/config
+    environment:
+      - CA=${JSON.stringify(data.CA)}
+      - CERT=${JSON.stringify(data.PrivateKey)}
+      - KEY=${JSON.stringify(data.PublicKey)}
+      - LIGHTHOUSES=${lighthouses}
+      - PUBLIC_HOSTNAME=${data.PublicHostname}
+      - IS_RELAY=${data.IsRelay}
+      - IP=${data.IP}
+`;
+  } else {
+    return `
+docker run -d \\
+  --name ${containerName} \\
+  --restart unless-stopped \\
+  --network bridge \\
+  -v ${volPath}:/config \\
+  -e CA=${JSON.stringify(data.CA)} \\
+  -e CERT=${JSON.stringify(data.PrivateKey)} \\
+  -e KEY=${JSON.stringify(data.PublicKey)} \\
+  -e LIGHTHOUSES=${lighthouses} \\
+  -e PUBLIC_HOSTNAME=${data.PublicHostname} \\
+  -e IS_RELAY=${data.IsRelay} \\
+  -e IP=${data.IP} \\
+  -p ${data.Port}:4242 \\
+  ${imageName}
+`;
+  }
+
+}
+
+
 const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
 const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
   const [openModal, setOpenModal] = useState(false);
   const [openModal, setOpenModal] = useState(false);
   const [isDone, setIsDone] = useState(null);
   const [isDone, setIsDone] = useState(null);
@@ -63,12 +120,18 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
           deviceName: '',
           deviceName: '',
           ip: firstIP,
           ip: firstIP,
           publicKey: '',
           publicKey: '',
+          Port: "4242",
+          PublicHostname: '',
+          IsRelay: true,
+          isLighthouse: false,
         }}
         }}
 
 
         validationSchema={yup.object({
         validationSchema={yup.object({
         })}
         })}
 
 
         onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
         onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
+          if(values.isLighthouse) values.nickname = null;
+
           return API.constellation.addDevice(values).then(({data}) => {
           return API.constellation.addDevice(values).then(({data}) => {
             setIsDone(data);
             setIsDone(data);
             refreshConfig();
             refreshConfig();
@@ -85,52 +148,55 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
             {isDone ? <DialogContent>
             {isDone ? <DialogContent>
               <DialogContentText>
               <DialogContentText>
                 <p>
                 <p>
-                  Device added successfully!
+                Device added successfully!
                   Download scan the QR Code from the Cosmos app or download the relevant
                   Download scan the QR Code from the Cosmos app or download the relevant
                   files to your device along side the config and network certificate to
                   files to your device along side the config and network certificate to
                   connect:
                   connect:
                 </p>
                 </p>
 
 
                 <Stack spacing={2} direction={"column"}>
                 <Stack spacing={2} direction={"column"}>
-                <CosmosFormDivider title={"QR Code"} />
-                <div style={{textAlign: 'center'}}>
-                <canvas style={{borderRadius: '15px'}} ref={canvasRef} />
-                </div>
-                {/* <CosmosFormDivider title={"Cosmos Client (File)"} />
-                  <DownloadFile 
-                    filename={isDone.DeviceName + `.constellation`}
-                    content={JSON.stringify(isDone, null, 2)}
-                    label={"Download " + isDone.DeviceName + `.constellation`}
-                  /> */}
+                {/* {isDone.isLighthouse ? <>
+                  <CosmosFormDivider title={"Docker"} />
+                  <TextField
+                    fullWidth
+                    multiline
+                    value={getDocker(isDone, false)}
+                    variant="outlined"
+                    size="small"
+                    disabled
+                  />
+                  <CosmosFormDivider title={"File (Docker-Compose)"} />
+                  <DownloadFile
+                    filename={`docker-compose.yml`}
+                    content={getDocker(isDone, true)}
+                    label={"Download docker-compose.yml"}
+                  />
+                </> : <> */}
+                  <CosmosFormDivider title={"QR Code"} />
+                  <div style={{textAlign: 'center'}}>
+                  <canvas style={{borderRadius: '15px'}} ref={canvasRef} />
+                  </div>
+                {/* </>} */}
+                
                 <CosmosFormDivider title={"File"} />
                 <CosmosFormDivider title={"File"} />
-
                   <DownloadFile 
                   <DownloadFile 
                     filename={`constellation.yml`}
                     filename={`constellation.yml`}
                     content={isDone.Config}
                     content={isDone.Config}
                     label={"Download constellation.yml"}
                     label={"Download constellation.yml"}
                   />
                   />
-                  {/* <DownloadFile
-                    filename={isDone.DeviceName + `.key`}
-                    content={isDone.PublicKey}
-                    label={"Download " + isDone.DeviceName + `.key`}
-                  />
-                  <DownloadFile
-                    filename={isDone.DeviceName + `.crt`}
-                    content={isDone.PrivateKey}
-                    label={"Download " + isDone.DeviceName + `.crt`}
-                  />
-                  <DownloadFile
-                    filename={`ca.crt`}
-                    content={isDone.CA}
-                    label={"Download ca.crt"}
-                  /> */}
                 </Stack>
                 </Stack>
               </DialogContentText>
               </DialogContentText>
             </DialogContent> : <DialogContent>
             </DialogContent> : <DialogContent>
               <DialogContentText>
               <DialogContentText>
-                <p>Add a device to the constellation using either the Cosmos or Nebula client</p>
+                <p>Add a Device to the constellation using either the Cosmos or Nebula client</p>
                 <div>
                 <div>
                   <Stack spacing={2} style={{}}>
                   <Stack spacing={2} style={{}}>
+                  <CosmosCheckbox
+                    name="isLighthouse"
+                    label="Lighthouse"
+                    formik={formik}
+                  />
+                  {!formik.values.isLighthouse &&
                     <CosmosSelect
                     <CosmosSelect
                       name="nickname"
                       name="nickname"
                       label="Owner"
                       label="Owner"
@@ -141,7 +207,7 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
                           return [u.nickname, u.nickname]
                           return [u.nickname, u.nickname]
                         })
                         })
                       }
                       }
-                    />
+                    />}
 
 
                     <CosmosInputText
                     <CosmosInputText
                       name="deviceName"
                       name="deviceName"
@@ -155,12 +221,33 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
                       formik={formik}
                       formik={formik}
                     />
                     />
 
 
+                    {/* <CosmosInputText
+                      name="Port"
+                      label="VPN Port (default: 4242)"
+                      formik={formik}
+                    /> */}
+
                     <CosmosInputText
                     <CosmosInputText
                       multiline
                       multiline
                       name="publicKey"
                       name="publicKey"
                       label="Public Key (Optional)"
                       label="Public Key (Optional)"
                       formik={formik}
                       formik={formik}
                     />
                     />
+                    {formik.values.isLighthouse && <>
+                      <CosmosFormDivider title={"Lighthouse Setup"} />
+
+                      <CosmosInputText
+                        name="PublicHostname"
+                        label="Public Hostname"
+                        formik={formik}
+                      />
+
+                      <CosmosCheckbox
+                        name="IsRelay"
+                        label="Can Relay Traffic"
+                        formik={formik}
+                      />
+                    </>}
                     <div>
                     <div>
                       {formik.errors && formik.errors.length > 0 && <Stack spacing={2} direction={"column"}>
                       {formik.errors && formik.errors.length > 0 && <Stack spacing={2} direction={"column"}>
                         <Alert severity="error">{formik.errors.map((err) => {
                         <Alert severity="error">{formik.errors.map((err) => {
@@ -189,7 +276,9 @@ const AddDeviceModal = ({ users, config, isAdmin, refreshConfig, devices }) => {
         setIsDone(null);
         setIsDone(null);
         setOpenModal(true);
         setOpenModal(true);
       }}
       }}
-      variant="contained"
+      variant={
+        "contained"
+      }
       startIcon={<PlusCircleFilled />}
       startIcon={<PlusCircleFilled />}
     >
     >
       Add Device
       Add Device

+ 65 - 11
client/src/pages/constellation/index.jsx

@@ -4,14 +4,26 @@ import * as API  from "../../api";
 import AddDeviceModal from "./addDevice";
 import AddDeviceModal from "./addDevice";
 import PrettyTableView from "../../components/tableView/prettyTableView";
 import PrettyTableView from "../../components/tableView/prettyTableView";
 import { DeleteButton } from "../../components/delete";
 import { DeleteButton } from "../../components/delete";
-import { CloudOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined } from "@ant-design/icons";
+import { CloudOutlined, CloudServerOutlined, CompassOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined } from "@ant-design/icons";
 import IsLoggedIn from "../../isLoggedIn";
 import IsLoggedIn from "../../isLoggedIn";
-import { Button, CircularProgress, Stack } from "@mui/material";
-import { CosmosCheckbox, CosmosFormDivider } from "../config/users/formShortcuts";
+import { Alert, Button, CircularProgress, Stack } from "@mui/material";
+import { CosmosCheckbox, CosmosFormDivider, CosmosInputText } from "../config/users/formShortcuts";
 import MainCard from "../../components/MainCard";
 import MainCard from "../../components/MainCard";
 import { Formik } from "formik";
 import { Formik } from "formik";
 import { LoadingButton } from "@mui/lab";
 import { LoadingButton } from "@mui/lab";
 import ApiModal from "../../components/apiModal";
 import ApiModal from "../../components/apiModal";
+import { isDomain } from "../../utils/indexs";
+import ConfirmModal from "../../components/confirmModal";
+import UploadButtons from "../../components/fileUpload";
+
+const getDefaultConstellationHostname = (config) => {
+  // if domain is set, use it
+  if(isDomain(config.HTTPConfig.Hostname)) {
+    return "vpn." + config.HTTPConfig.Hostname;
+  } else {
+    return config.HTTPConfig.Hostname;
+  }
+}
 
 
 export const ConstellationIndex = () => {
 export const ConstellationIndex = () => {
   const [isAdmin, setIsAdmin] = useState(false);
   const [isAdmin, setIsAdmin] = useState(false);
@@ -41,6 +53,8 @@ export const ConstellationIndex = () => {
       return <DesktopOutlined />
       return <DesktopOutlined />
     } else if (r.deviceName.toLowerCase().includes("tablet")) {
     } else if (r.deviceName.toLowerCase().includes("tablet")) {
       return <TabletOutlined />
       return <TabletOutlined />
+    } else if (r.deviceName.toLowerCase().includes("lighthouse") || r.deviceName.toLowerCase().includes("server")) {
+      return <CompassOutlined />
     } else {
     } else {
       return <CloudOutlined />
       return <CloudOutlined />
     }
     }
@@ -53,22 +67,30 @@ export const ConstellationIndex = () => {
       <div>
       <div>
         <MainCard title={"Constellation Setup"} content={config.constellationIP}>
         <MainCard title={"Constellation Setup"} content={config.constellationIP}>
           <Stack spacing={2}>
           <Stack spacing={2}>
+          {config.ConstellationConfig.Enabled && config.ConstellationConfig.SlaveMode && <>
+            <Alert severity="info">
+              You are currently connected to an external constellation network. Use your main Cosmos server to manage your constellation network and devices.
+            </Alert>
+          </>}  
           <Formik
           <Formik
             initialValues={{
             initialValues={{
               Enabled: config.ConstellationConfig.Enabled,
               Enabled: config.ConstellationConfig.Enabled,
               IsRelay: config.ConstellationConfig.NebulaConfig.Relay.AMRelay,
               IsRelay: config.ConstellationConfig.NebulaConfig.Relay.AMRelay,
+              ConstellationHostname: (config.ConstellationConfig.ConstellationHostname && config.ConstellationConfig.ConstellationHostname != "") ? config.ConstellationConfig.ConstellationHostname :
+                getDefaultConstellationHostname(config)
             }}
             }}
             onSubmit={(values) => {
             onSubmit={(values) => {
               let newConfig = { ...config };
               let newConfig = { ...config };
               newConfig.ConstellationConfig.Enabled = values.Enabled;
               newConfig.ConstellationConfig.Enabled = values.Enabled;
               newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay;
               newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay;
+              newConfig.ConstellationConfig.ConstellationHostname = values.ConstellationHostname;
               return API.config.set(newConfig);
               return API.config.set(newConfig);
             }}
             }}
           >
           >
             {(formik) => (
             {(formik) => (
               <form onSubmit={formik.handleSubmit}>
               <form onSubmit={formik.handleSubmit}>
                 <Stack spacing={2}>        
                 <Stack spacing={2}>        
-                <Stack spacing={2} direction="row">          
+                {formik.values.Enabled && <Stack spacing={2} direction="row">    
                   <Button
                   <Button
                       disableElevation
                       disableElevation
                       variant="outlined"
                       variant="outlined"
@@ -77,14 +99,40 @@ export const ConstellationIndex = () => {
                         await API.constellation.restart();
                         await API.constellation.restart();
                       }}
                       }}
                     >
                     >
-                      Restart Nebula
+                      Restart VPN Service
                   </Button>
                   </Button>
-                  <ApiModal callback={API.constellation.getLogs} label={"Show Nebula logs"} />
-                  <ApiModal callback={API.constellation.getConfig} label={"Render Nebula Config"} />
-                  </Stack>
+                  <ApiModal callback={API.constellation.getLogs} label={"Show VPN logs"} />
+                  <ApiModal callback={API.constellation.getConfig} label={"Show VPN Config"} />
+                  <ConfirmModal
+                    variant="outlined"
+                    color="warning"
+                    label={"Reset Network"}
+                    content={"This will completely reset the network, and disconnect all the clients. You will need to reconnect them. This cannot be undone."}
+                    callback={async () => {
+                      await API.constellation.reset();
+                      refreshConfig();
+                    }}
+                  />
+                  </Stack>}
                   <CosmosCheckbox formik={formik} name="Enabled" label="Constellation Enabled" />
                   <CosmosCheckbox formik={formik} name="Enabled" label="Constellation Enabled" />
-                  <CosmosCheckbox formik={formik} name="IsRelay" label="Relay requests via this Node" />
-
+                  {config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
+                    {formik.values.Enabled && <>
+                      <CosmosCheckbox formik={formik} name="IsRelay" label="Relay requests via this Node" />
+                      <Alert severity="info">This is your Constellation hostname, that you will use to connect. If you are using a domain name, this needs to be different from your server's hostname. Whatever the domain you choose, it is very important that you make sure there is a A entry in your domain DNS pointing to this server. <strong>If you change this value, you will need to reset your network and reconnect all the clients!</strong></Alert>
+                      <CosmosInputText formik={formik} name="ConstellationHostname" label="Constellation Hostname" />
+                    </>}
+                  </>}
+                  <UploadButtons
+                    accept=".yml,.yaml"
+                    label={"Upload Nebula Config"}
+                    variant="outlined"
+                    fullWidth
+                    OnChange={async (e) => {
+                      let file = e.target.files[0];
+                      await API.constellation.connect(file);
+                      refreshConfig();
+                    }}
+                  />
                   <LoadingButton
                   <LoadingButton
                       disableElevation
                       disableElevation
                       loading={formik.isSubmitting}
                       loading={formik.isSubmitting}
@@ -101,12 +149,13 @@ export const ConstellationIndex = () => {
           </Stack>
           </Stack>
         </MainCard>
         </MainCard>
       </div>
       </div>
+      {config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
       <CosmosFormDivider title={"Devices"} />
       <CosmosFormDivider title={"Devices"} />
       <PrettyTableView 
       <PrettyTableView 
           data={devices}
           data={devices}
           getKey={(r) => r.deviceName}
           getKey={(r) => r.deviceName}
           buttons={[
           buttons={[
-            <AddDeviceModal isAdmin={isAdmin} users={users} config={config} refreshConfig={refreshConfig} devices={devices} />
+            <AddDeviceModal isAdmin={isAdmin} users={users} config={config} refreshConfig={refreshConfig} devices={devices} />,
           ]}
           ]}
           columns={[
           columns={[
               {
               {
@@ -121,6 +170,10 @@ export const ConstellationIndex = () => {
                   title: 'Owner',
                   title: 'Owner',
                   field: (r) => <strong>{r.nickname}</strong>,
                   field: (r) => <strong>{r.nickname}</strong>,
               },
               },
+              {
+                  title: 'Type',
+                  field: (r) => <strong>{r.isLighthouse ? "Lighthouse" : "Client"}</strong>,
+              },
               {
               {
                   title: 'Constellation IP',
                   title: 'Constellation IP',
                   screenMin: 'md', 
                   screenMin: 'md', 
@@ -137,6 +190,7 @@ export const ConstellationIndex = () => {
               }
               }
           ]}
           ]}
         />
         />
+      </>}
         </Stack>
         </Stack>
     </> : <center>
     </> : <center>
       <CircularProgress color="inherit" size={20} />
       <CircularProgress color="inherit" size={20} />

+ 1 - 1
package.json

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

+ 41 - 2
src/constellation/api_devices_create.go

@@ -9,10 +9,18 @@ import (
 )
 )
 
 
 type DeviceCreateRequestJSON struct {
 type DeviceCreateRequestJSON struct {
-	Nickname string `json:"nickname",validate:"required,min=3,max=32,alphanum"`
 	DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
 	DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
 	IP string `json:"ip",validate:"required,ipv4"`
 	IP string `json:"ip",validate:"required,ipv4"`
 	PublicKey string `json:"publicKey",omitempty`
 	PublicKey string `json:"publicKey",omitempty`
+	
+	// for devices only
+	Nickname string `json:"nickname",validate:"max=32,alphanum",omitempty`
+	
+	// for lighthouse only
+	IsLighthouse bool `json:"isLighthouse",omitempty`
+	IsRelay bool `json:"isRelay",omitempty`
+	PublicHostname string `json:"PublicHostname",omitempty`
+	Port string `json:"port",omitempty`
 }
 }
 
 
 func DeviceCreate(w http.ResponseWriter, req *http.Request) {
 func DeviceCreate(w http.ResponseWriter, req *http.Request) {
@@ -67,11 +75,22 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
 				return
 				return
 			}
 			}
 
 
+			if request.IsLighthouse && request.Nickname != "" {
+				utils.Error("DeviceCreation: Lighthouse cannot belong to a user", nil)
+				utils.HTTPError(w, "Device Creation Error: Lighthouse cannot have a nickname",
+					http.StatusInternalServerError, "DC003")
+				return
+			}
+
 			_, err3 := c.InsertOne(nil, map[string]interface{}{
 			_, err3 := c.InsertOne(nil, map[string]interface{}{
 				"Nickname": nickname,
 				"Nickname": nickname,
 				"DeviceName": deviceName,
 				"DeviceName": deviceName,
 				"PublicKey": key,
 				"PublicKey": key,
 				"IP": request.IP,
 				"IP": request.IP,
+				"IsLighthouse": request.IsLighthouse,
+				"IsRelay": request.IsRelay,
+				"PublicHostname": request.PublicHostname,
+				"Port": request.Port,
 			})
 			})
 
 
 			if err3 != nil {
 			if err3 != nil {
@@ -88,9 +107,24 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
 					http.StatusInternalServerError, "DC006")
 					http.StatusInternalServerError, "DC006")
 				return
 				return
 			}
 			}
+
+			lightHousesList := []utils.ConstellationDevice{}
+			if request.IsLighthouse {
+				lightHousesList, err = GetAllLightHouses()
+			}
 			
 			
 			// read configYml from config/nebula.yml
 			// read configYml from config/nebula.yml
-			configYml, err := getYAMLClientConfig(deviceName, utils.CONFIGFOLDER + "nebula.yml", capki, cert, key)
+			configYml, err := getYAMLClientConfig(deviceName, utils.CONFIGFOLDER + "nebula.yml", capki, cert, key, utils.ConstellationDevice{
+				Nickname: nickname,
+				DeviceName: deviceName,
+				PublicKey: key,
+				IP: request.IP,
+				IsLighthouse: request.IsLighthouse,
+				IsRelay: request.IsRelay,
+				PublicHostname: request.PublicHostname,
+				Port: request.Port,
+			})
+
 			if err != nil {
 			if err != nil {
 				utils.Error("DeviceCreation: Error while reading config", err)
 				utils.Error("DeviceCreation: Error while reading config", err)
 				utils.HTTPError(w, "Device Creation Error: " + err.Error(),
 				utils.HTTPError(w, "Device Creation Error: " + err.Error(),
@@ -108,6 +142,11 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) {
 					"IP": request.IP,
 					"IP": request.IP,
 					"Config": configYml,
 					"Config": configYml,
 					"CA": capki,
 					"CA": capki,
+					"IsLighthouse": request.IsLighthouse,
+					"IsRelay": request.IsRelay,
+					"PublicHostname": request.PublicHostname,
+					"Port": request.Port,
+					"LighthousesList": lightHousesList,
 				},
 				},
 			})
 			})
 		} else if err2 == nil {
 		} else if err2 == nil {

+ 1 - 11
src/constellation/api_devices_list.go

@@ -29,7 +29,7 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
 		return
 		return
 	}
 	}
 	
 	
-	var devices []utils.Device
+	var devices []utils.ConstellationDevice
 	
 	
 	// Check if user is an admin
 	// Check if user is an admin
 	if isAdmin {
 	if isAdmin {
@@ -47,11 +47,6 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
 			utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL002")
 			utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL002")
 			return
 			return
 		}
 		}
-
-		// Remove the private key from the response
-		for i := range devices {
-			devices[i].PrivateKey = ""
-		}
 	} else {
 	} else {
 		// If not admin, get user's devices based on their nickname
 		// If not admin, get user's devices based on their nickname
 		nickname := req.Header.Get("x-cosmos-user")
 		nickname := req.Header.Get("x-cosmos-user")
@@ -68,11 +63,6 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
 			utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL004")
 			utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL004")
 			return
 			return
 		}
 		}
-		
-		// Remove the private key from the response
-		for i := range devices {
-			devices[i].PrivateKey = ""
-		}
 	}
 	}
 	
 	
 	// Respond with the list of devices
 	// Respond with the list of devices

+ 20 - 0
src/constellation/api_nebula.go

@@ -55,6 +55,26 @@ func API_Restart(w http.ResponseWriter, req *http.Request) {
 	}
 	}
 }
 }
 
 
+func API_Reset(w http.ResponseWriter, req *http.Request) {
+	if utils.AdminOnly(w, req) != nil {
+		return
+	}
+
+	if(req.Method == "GET") {
+		ResetNebula()
+
+		utils.Log("Constellation: nebula reset")
+		
+		json.NewEncoder(w).Encode(map[string]interface{}{
+			"status": "OK",
+		})
+	} else {
+		utils.Error("SettingGet: Method not allowed" + req.Method, nil)
+		utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
+		return
+	}
+}
+
 func API_GetLogs(w http.ResponseWriter, req *http.Request) {
 func API_GetLogs(w http.ResponseWriter, req *http.Request) {
 	if utils.AdminOnly(w, req) != nil {
 	if utils.AdminOnly(w, req) != nil {
 		return
 		return

+ 47 - 0
src/constellation/api_nebula_connect.go

@@ -0,0 +1,47 @@
+package constellation
+
+import (
+	"net/http"
+	"encoding/json"
+	"io/ioutil"
+
+	
+	"github.com/azukaar/cosmos-server/src/utils" 
+)
+
+func API_ConnectToExisting(w http.ResponseWriter, req *http.Request) {
+	if utils.AdminOnly(w, req) != nil {
+		return
+	}
+
+	if(req.Method == "POST") {
+		body, err := ioutil.ReadAll(req.Body)
+		if err != nil {
+			utils.Error("API_Restart: Invalid User Request", err)
+			utils.HTTPError(w, "API_Restart Error",
+				http.StatusInternalServerError, "AR001")
+			return	
+		}
+
+		config := utils.ReadConfigFromFile()
+		config.ConstellationConfig.Enabled = true
+		config.ConstellationConfig.SlaveMode = true
+		config.ConstellationConfig.DNS = false
+		// ConstellationHostname = 
+
+		// output utils.CONFIGFOLDER + "nebula.yml"
+		err = ioutil.WriteFile(utils.CONFIGFOLDER + "nebula.yml", body, 0644)
+		
+		utils.SetBaseMainConfig(config)
+		
+		RestartNebula()
+		
+		json.NewEncoder(w).Encode(map[string]interface{}{
+			"status": "OK",
+		})
+	} else {
+		utils.Error("SettingGet: Method not allowed" + req.Method, nil)
+		utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
+		return
+	}
+}

+ 24 - 20
src/constellation/index.go

@@ -6,32 +6,36 @@ import (
 )
 )
 
 
 func Init() {
 func Init() {
+	var err error
+	
 	// if Constellation is enabled
 	// if Constellation is enabled
 	if utils.GetMainConfig().ConstellationConfig.Enabled {
 	if utils.GetMainConfig().ConstellationConfig.Enabled {
-		InitConfig()
-		
-		utils.Log("Initializing Constellation module...")
+		if !utils.GetMainConfig().ConstellationConfig.SlaveMode {
+			InitConfig()
+			
+			utils.Log("Initializing Constellation module...")
 
 
-		// check if ca.crt exists
-		if _, err := os.Stat(utils.CONFIGFOLDER + "ca.crt"); os.IsNotExist(err) {
-			utils.Log("Constellation: ca.crt not found, generating...")
-			// generate ca.crt
-			generateNebulaCACert("Cosmos - " + utils.GetMainConfig().HTTPConfig.Hostname)
-		}
+			// check if ca.crt exists
+			if _, err = os.Stat(utils.CONFIGFOLDER + "ca.crt"); os.IsNotExist(err) {
+				utils.Log("Constellation: ca.crt not found, generating...")
+				// generate ca.crt
+				generateNebulaCACert("Cosmos - " + utils.GetMainConfig().ConstellationConfig.ConstellationHostname)
+			}
 
 
-		// check if cosmos.crt exists
-		if _, err := os.Stat(utils.CONFIGFOLDER + "cosmos.crt"); os.IsNotExist(err) {
-			utils.Log("Constellation: cosmos.crt not found, generating...")
-			// generate cosmos.crt
-			generateNebulaCert("cosmos", "192.168.201.1/24", "", true)
-		}
+			// check if cosmos.crt exists
+			if _, err := os.Stat(utils.CONFIGFOLDER + "cosmos.crt"); os.IsNotExist(err) {
+				utils.Log("Constellation: cosmos.crt not found, generating...")
+				// generate cosmos.crt
+				generateNebulaCert("cosmos", "192.168.201.1/24", "", true)
+			}
 
 
-		// export nebula.yml
-		utils.Log("Constellation: exporting nebula.yml...")
-		err := ExportConfigToYAML(utils.GetMainConfig().ConstellationConfig, utils.CONFIGFOLDER + "nebula.yml")
+			// export nebula.yml
+			utils.Log("Constellation: exporting nebula.yml...")
+			err := ExportConfigToYAML(utils.GetMainConfig().ConstellationConfig, utils.CONFIGFOLDER + "nebula.yml")
 
 
-		if err != nil {
-			utils.Error("Constellation: error while exporting nebula.yml", err)
+			if err != nil {
+				utils.Error("Constellation: error while exporting nebula.yml", err)
+			}
 		}
 		}
 		
 		
 		// start nebula
 		// start nebula

+ 120 - 8
src/constellation/nebula.go

@@ -80,18 +80,92 @@ func RestartNebula() {
 	Init()
 	Init()
 }
 }
 
 
+func ResetNebula() error {
+	stop()
+	utils.Log("Resetting nebula...")
+	os.RemoveAll(utils.CONFIGFOLDER + "nebula.yml")
+	os.RemoveAll(utils.CONFIGFOLDER + "ca.crt")
+	os.RemoveAll(utils.CONFIGFOLDER + "ca.key")
+	os.RemoveAll(utils.CONFIGFOLDER + "cosmos.crt")
+	os.RemoveAll(utils.CONFIGFOLDER + "cosmos.key")
+	// remove everything in db
+
+	c, err := utils.GetCollection(utils.GetRootAppId(), "devices")
+	if err != nil {
+			return err
+	}
+
+	_, err = c.DeleteMany(nil, map[string]interface{}{})
+	if err != nil {
+		return err
+	}
+
+	Init()
+
+	return nil
+}
+
+func GetAllLightHouses() ([]utils.ConstellationDevice, error) {
+	c, err := utils.GetCollection(utils.GetRootAppId(), "devices")
+	if err != nil {
+		return []utils.ConstellationDevice{}, err
+	}
+
+	var devices []utils.ConstellationDevice
+
+	cursor, err := c.Find(nil, map[string]interface{}{
+		"IsLighthouse": true,
+	})
+	cursor.All(nil, &devices)
+
+	if err != nil {
+		return []utils.ConstellationDevice{}, err
+	}
+
+	return devices, nil
+}
+
+func cleanIp(ip string) string {
+	return strings.Split(ip, "/")[0]
+}
+
 func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath string) error {
 func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath string) error {
 	// Combine defaultConfig and overwriteConfig
 	// Combine defaultConfig and overwriteConfig
 	finalConfig := NebulaDefaultConfig
 	finalConfig := NebulaDefaultConfig
 
 
 	finalConfig.StaticHostMap = map[string][]string{
 	finalConfig.StaticHostMap = map[string][]string{
 		"192.168.201.1": []string{
 		"192.168.201.1": []string{
-			utils.GetMainConfig().HTTPConfig.Hostname + ":4242",
+			utils.GetMainConfig().ConstellationConfig.ConstellationHostname + ":4242",
 		},
 		},
 	}
 	}
 
 
+	// for each lighthouse
+	lh, err := GetAllLightHouses()
+	if err != nil {
+		return err
+	}
+
+	for _, l := range lh {
+		finalConfig.StaticHostMap[cleanIp(l.IP)] = []string{
+			l.PublicHostname + ":" + l.Port,
+		}
+	}
+	
+	// add other lighthouses 
+	finalConfig.Lighthouse.Hosts = []string{}
+	for _, l := range lh {
+		finalConfig.Lighthouse.Hosts = append(finalConfig.Lighthouse.Hosts, cleanIp(l.IP))
+	}
+
 	finalConfig.Relay.AMRelay = overwriteConfig.NebulaConfig.Relay.AMRelay
 	finalConfig.Relay.AMRelay = overwriteConfig.NebulaConfig.Relay.AMRelay
 
 
+	finalConfig.Relay.Relays = []string{}
+	for _, l := range lh {
+		if l.IsRelay && l.IsLighthouse {
+			finalConfig.Relay.Relays = append(finalConfig.Relay.Relays, cleanIp(l.IP))
+		}
+	}
+
 	// Marshal the combined config to YAML
 	// Marshal the combined config to YAML
 	yamlData, err := yaml.Marshal(finalConfig)
 	yamlData, err := yaml.Marshal(finalConfig)
 	if err != nil {
 	if err != nil {
@@ -118,7 +192,7 @@ func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath st
 	return nil
 	return nil
 }
 }
 
 
-func getYAMLClientConfig(name, configPath, capki, cert, key string) (string, error) {
+func getYAMLClientConfig(name, configPath, capki, cert, key string, device utils.ConstellationDevice) (string, error) {
 	utils.Log("Exporting YAML config for " + name + " with file " + configPath)
 	utils.Log("Exporting YAML config for " + name + " with file " + configPath)
 
 
 	// Read the YAML config file
 	// Read the YAML config file
@@ -134,21 +208,38 @@ func getYAMLClientConfig(name, configPath, capki, cert, key string) (string, err
 		return "", err
 		return "", err
 	}
 	}
 
 
+	lh, err := GetAllLightHouses()
+	if err != nil {
+		return "", err
+	}
+
 	if staticHostMap, ok := configMap["static_host_map"].(map[interface{}]interface{}); ok {
 	if staticHostMap, ok := configMap["static_host_map"].(map[interface{}]interface{}); ok {
 		staticHostMap["192.168.201.1"] = []string{
 		staticHostMap["192.168.201.1"] = []string{
-			utils.GetMainConfig().HTTPConfig.Hostname + ":4242",
+			utils.GetMainConfig().ConstellationConfig.ConstellationHostname + ":4242",
+		}
+		
+		for _, l := range lh {
+			staticHostMap[cleanIp(l.IP)] = []string{
+				l.PublicHostname + ":" + l.Port,
+			}
 		}
 		}
 	} else {
 	} else {
 		return "", errors.New("static_host_map not found in nebula.yml")
 		return "", errors.New("static_host_map not found in nebula.yml")
 	}
 	}
 
 
-	// set lightHouse to false
+	// set lightHouse
 	if lighthouseMap, ok := configMap["lighthouse"].(map[interface{}]interface{}); ok {
 	if lighthouseMap, ok := configMap["lighthouse"].(map[interface{}]interface{}); ok {
-		lighthouseMap["am_lighthouse"] = false
-
+		lighthouseMap["am_lighthouse"] = device.IsLighthouse
+		
 		lighthouseMap["hosts"] = []string{
 		lighthouseMap["hosts"] = []string{
 			"192.168.201.1",
 			"192.168.201.1",
 		}
 		}
+		
+		for _, l := range lh {
+			if cleanIp(l.IP) != cleanIp(device.IP) {
+				lighthouseMap["hosts"] = append(lighthouseMap["hosts"].([]string), cleanIp(l.IP))
+			}
+		}
 	} else {
 	} else {
 		return "", errors.New("lighthouse not found in nebula.yml")
 		return "", errors.New("lighthouse not found in nebula.yml")
 	}
 	}
@@ -162,13 +253,34 @@ func getYAMLClientConfig(name, configPath, capki, cert, key string) (string, err
 	}
 	}
 
 
 	if relayMap, ok := configMap["relay"].(map[interface{}]interface{}); ok {
 	if relayMap, ok := configMap["relay"].(map[interface{}]interface{}); ok {
-		relayMap["am_relay"] = false
-		relayMap["relays"] = []string{"192.168.201.1"}
+		relayMap["am_relay"] = device.IsRelay && device.IsLighthouse
+		relayMap["relays"] = []string{}
+		if utils.GetMainConfig().ConstellationConfig.NebulaConfig.Relay.AMRelay {
+			relayMap["relays"] = append(relayMap["relays"].([]string), "192.168.201.1")
+		}
+
+		for _, l := range lh {
+			if l.IsRelay && l.IsLighthouse && cleanIp(l.IP) != cleanIp(device.IP) {
+				relayMap["relays"] = append(relayMap["relays"].([]string), cleanIp(l.IP))
+			}
+		}
 	} else {
 	} else {
 		return "", errors.New("relay not found in nebula.yml")
 		return "", errors.New("relay not found in nebula.yml")
 	}
 	}
+	
+	if listen, ok := configMap["listen"].(map[interface{}]interface{}); ok {
+		if device.Port != "" {
+			listen["port"] = device.Port
+		} else {
+			listen["port"] = "4242"
+		}
+	} else {
+		return "", errors.New("listen not found in nebula.yml")
+	}
 
 
 	configMap["deviceName"] = name
 	configMap["deviceName"] = name
+	configMap["local_dns_overwrite"] = "192.168.201.1"
+	configMap["public_hostname"] = device.PublicHostname
 
 
 	// export configMap as YML
 	// export configMap as YML
 	yamlData, err = yaml.Marshal(configMap)
 	yamlData, err = yaml.Marshal(configMap)

+ 2 - 0
src/httpServer.go

@@ -334,6 +334,8 @@ func InitServer() *mux.Router {
 
 
 	srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
 	srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
 	srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart)
 	srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart)
+	srapi.HandleFunc("/api/constellation/reset", constellation.API_Reset)
+	srapi.HandleFunc("/api/constellation/connect", constellation.API_ConnectToExisting)
 	srapi.HandleFunc("/api/constellation/config", constellation.API_GetConfig)
 	srapi.HandleFunc("/api/constellation/config", constellation.API_GetConfig)
 	srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs)
 	srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs)
 
 

+ 19 - 8
src/proxy/routeTo.go

@@ -46,7 +46,7 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
 
 
 
 
 // NewProxy takes target host and creates a reverse proxy
 // NewProxy takes target host and creates a reverse proxy
-func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardHeader bool, DisableHeaderHardening bool, CORSOrigin string) (*httputil.ReverseProxy, error) {
+func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardHeader bool, DisableHeaderHardening bool, CORSOrigin string, route utils.ProxyRouteConfig) (*httputil.ReverseProxy, error) {
 	url, err := url.Parse(targetHost)
 	url, err := url.Parse(targetHost)
 	if err != nil {
 	if err != nil {
 			return nil, err
 			return nil, err
@@ -76,15 +76,28 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH
 			req.Header.Set("X-Forwarded-Ssl", "on")
 			req.Header.Set("X-Forwarded-Ssl", "on")
 		}
 		}
 
 
-		if CORSOrigin != "" {
-			req.Header.Set("X-Forwarded-Host", url.Host)
+		req.Header.Del("X-Origin-Host")
+		req.Header.Del("X-Forwarded-Host")
+		req.Header.Del("X-Forwarded-For")
+		req.Header.Del("X-Real-Ip")
+		req.Header.Del("Host")
+
+		hostname := utils.GetMainConfig().HTTPConfig.Hostname
+		if route.Host != "" && route.UseHost {
+			hostname = route.Host
+		}
+		if route.UsePathPrefix {
+			hostname = hostname + route.PathPrefix
 		}
 		}
 
 
 		if VerboseForwardHeader {
 		if VerboseForwardHeader {
-			req.Header.Set("X-Origin-Host", url.Host)
-			req.Header.Set("Host", url.Host)
+			req.Header.Set("X-Origin-Host", hostname)
+			req.Header.Set("Host", hostname)
+			req.Header.Set("X-Forwarded-Host", hostname)
 			req.Header.Set("X-Forwarded-For", utils.GetClientIP(req))
 			req.Header.Set("X-Forwarded-For", utils.GetClientIP(req))
 			req.Header.Set("X-Real-IP", utils.GetClientIP(req))
 			req.Header.Set("X-Real-IP", utils.GetClientIP(req))
+		} else {
+			req.Host = url.Host
 		}
 		}
 	}
 	}
 
 
@@ -100,8 +113,6 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH
 
 
 		if CORSOrigin != "" {
 		if CORSOrigin != "" {
 			resp.Header.Del("Access-Control-Allow-Origin")
 			resp.Header.Del("Access-Control-Allow-Origin")
-			resp.Header.Del("Access-Control-Allow-Methods")
-			resp.Header.Del("Access-Control-Allow-Headers")
 			resp.Header.Del("Access-Control-Allow-Credentials")
 			resp.Header.Del("Access-Control-Allow-Credentials")
 		}
 		}
 		
 		
@@ -126,7 +137,7 @@ func RouteTo(route utils.ProxyRouteConfig) http.Handler {
 	routeType := route.Mode
 	routeType := route.Mode
 
 
 	if(routeType == "SERVAPP" || routeType == "PROXY") {
 	if(routeType == "SERVAPP" || routeType == "PROXY") {
-		proxy, err := NewProxy(destination, route.AcceptInsecureHTTPSTarget, route.VerboseForwardHeader, route.DisableHeaderHardening, route.CORSOrigin)
+		proxy, err := NewProxy(destination, route.AcceptInsecureHTTPSTarget, route.VerboseForwardHeader, route.DisableHeaderHardening, route.CORSOrigin, route)
 		if err != nil {
 		if err != nil {
 				utils.Error("Create Route", err)
 				utils.Error("Create Route", err)
 		}
 		}

+ 2 - 2
src/proxy/routerGen.go

@@ -30,12 +30,12 @@ func tokenMiddleware(enabled bool, adminOnly bool) func(next http.Handler) http.
 			r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState)))
 			r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState)))
 
 
 			ogcookies := r.Header.Get("Cookie")
 			ogcookies := r.Header.Get("Cookie")
-			cookieRemoveRegex := regexp.MustCompile(`jwttoken=[^;]*;`)
+			cookieRemoveRegex := regexp.MustCompile(`\s?jwttoken=[^;]*;?\s?`)
 			cookies := cookieRemoveRegex.ReplaceAllString(ogcookies, "")
 			cookies := cookieRemoveRegex.ReplaceAllString(ogcookies, "")
 			r.Header.Set("Cookie", cookies)
 			r.Header.Set("Cookie", cookies)
 
 
 			// Replace the token with a application speicfic one
 			// Replace the token with a application speicfic one
-			r.Header.Set("x-cosmos-token", "1234567890")
+			//r.Header.Set("x-cosmos-token", "1234567890")
 
 
 			if enabled && adminOnly {
 			if enabled && adminOnly {
 				if errT := utils.AdminOnlyWithRedirect(w, r); errT != nil {
 				if errT := utils.AdminOnlyWithRedirect(w, r); errT != nil {

+ 0 - 2
src/utils/middleware.go

@@ -82,8 +82,6 @@ func CORSHeader(origin string) func(next http.Handler) http.Handler {
 
 
 			if origin != "" {
 			if origin != "" {
 				w.Header().Set("Access-Control-Allow-Origin", origin)
 				w.Header().Set("Access-Control-Allow-Origin", origin)
-				w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
-				w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
 				w.Header().Set("Access-Control-Allow-Credentials", "true")
 				w.Header().Set("Access-Control-Allow-Credentials", "true")
 			}
 			}
 
 

+ 13 - 0
src/utils/types.go

@@ -211,6 +211,7 @@ type MarketSource struct {
 
 
 type ConstellationConfig struct {
 type ConstellationConfig struct {
 	Enabled bool
 	Enabled bool
+	SlaveMode bool
 	DNS bool
 	DNS bool
 	DNSPort string
 	DNSPort string
 	DNSFallback string
 	DNSFallback string
@@ -218,6 +219,18 @@ type ConstellationConfig struct {
 	DNSAdditionalBlocklists []string
 	DNSAdditionalBlocklists []string
 	CustomDNSEntries map[string]string
 	CustomDNSEntries map[string]string
 	NebulaConfig NebulaConfig
 	NebulaConfig NebulaConfig
+	ConstellationHostname string
+}
+
+type ConstellationDevice struct {
+	Nickname string `json:"nickname"`
+	DeviceName string `json:"deviceName"`
+	PublicKey string `json:"publicKey"`
+	IP string `json:"ip"`
+	IsLighthouse bool `json:"isLighthouse"`
+	IsRelay bool `json:"isRelay"`
+	PublicHostname string `json:"publicHostname"`
+	Port string `json:"port"`
 }
 }
 
 
 type NebulaFirewallRule struct {
 type NebulaFirewallRule struct {

+ 17 - 0
src/utils/utils.go

@@ -214,6 +214,15 @@ func LoadBaseMainConfig(config Config) {
 	if MainConfig.DockerConfig.DefaultDataPath == "" {
 	if MainConfig.DockerConfig.DefaultDataPath == "" {
 		MainConfig.DockerConfig.DefaultDataPath = "/usr"
 		MainConfig.DockerConfig.DefaultDataPath = "/usr"
 	}
 	}
+	
+	if MainConfig.ConstellationConfig.ConstellationHostname == "" {
+		// if hostname is a domain add vpn. suffix otherwise use hostname
+		if IsDomain(MainConfig.HTTPConfig.Hostname) {
+			MainConfig.ConstellationConfig.ConstellationHostname = "vpn." + MainConfig.HTTPConfig.Hostname
+		} else {
+			MainConfig.ConstellationConfig.ConstellationHostname = MainConfig.HTTPConfig.Hostname
+		}
+	}
 }
 }
 
 
 func GetMainConfig() Config {
 func GetMainConfig() Config {
@@ -577,4 +586,12 @@ func GetClientIP(req *http.Request) string {
 		ip = req.RemoteAddr
 		ip = req.RemoteAddr
 	}*/
 	}*/
 	return req.RemoteAddr
 	return req.RemoteAddr
+}
+
+func IsDomain(domain string) bool {
+	// contains . and at least a letter and no special characters invalid in a domain
+	if strings.Contains(domain, ".") && strings.ContainsAny(domain, "abcdefghijklmnopqrstuvwxyz") && !strings.ContainsAny(domain, " !@#$%^&*()+=[]{}\\|;:'\",/<>?") {
+		return true
+	}
+	return false
 }
 }