[release] version 0.5.0-unstable11
This commit is contained in:
parent
e3503f4345
commit
467f84187f
|
@ -3,6 +3,7 @@
|
|||
- Add Create Container
|
||||
- Add support for importing Docker Compose
|
||||
- Fixed 2 bugs with the smart shield, that made it too strict
|
||||
- Fixed issues that prevented from login in with different hostnames
|
||||
- Added more infoon the shield when blocking someone
|
||||
- Fixed home background image
|
||||
|
||||
|
|
|
@ -268,6 +268,15 @@ function pullImage(imageName, onProgress, ifMissing) {
|
|||
});
|
||||
}
|
||||
|
||||
function autoUpdate(id, toggle) {
|
||||
return wrap(fetch('/cosmos/api/servapps/' + id + '/auto-update/'+toggle, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export {
|
||||
list,
|
||||
get,
|
||||
|
@ -289,4 +298,5 @@ export {
|
|||
createTerminal,
|
||||
createService,
|
||||
pullImage,
|
||||
autoUpdate,
|
||||
};
|
|
@ -1,24 +1,39 @@
|
|||
import React from 'react';
|
||||
import { IconButton, Tooltip, useMediaQuery } from '@mui/material';
|
||||
import { Box, IconButton, LinearProgress, Stack, Tooltip, useMediaQuery } from '@mui/material';
|
||||
import { CheckCircleOutlined, CloseSquareOutlined, DeleteOutlined, PauseCircleOutlined, PlaySquareOutlined, ReloadOutlined, RollbackOutlined, StopOutlined, UpCircleOutlined } from '@ant-design/icons';
|
||||
import * as API from '../../api';
|
||||
import LogsInModal from '../../components/logsInModal';
|
||||
|
||||
const GetActions = ({
|
||||
Id,
|
||||
state,
|
||||
image,
|
||||
refreshServeApps,
|
||||
setIsUpdatingId,
|
||||
updateAvailable
|
||||
}) => {
|
||||
const [confirmDelete, setConfirmDelete] = React.useState(false);
|
||||
const isMiniMobile = useMediaQuery((theme) => theme.breakpoints.down('xsm'));
|
||||
const [pullRequest, setPullRequest] = React.useState(null);
|
||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||
console.log(isMiniMobile)
|
||||
|
||||
const doTo = (action) => {
|
||||
setIsUpdating(true);
|
||||
|
||||
if(action === 'pull') {
|
||||
setPullRequest(() => ((cb) => {
|
||||
API.docker.pullImage(image, cb, true)
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUpdatingId(Id, true);
|
||||
API.docker.manageContainer(Id, action).then((res) => {
|
||||
return API.docker.manageContainer(Id, action).then((res) => {
|
||||
setIsUpdating(false);
|
||||
refreshServeApps();
|
||||
}).catch((err) => {
|
||||
setIsUpdating(false);
|
||||
refreshServeApps();
|
||||
});
|
||||
};
|
||||
|
@ -27,7 +42,7 @@ const GetActions = ({
|
|||
{
|
||||
t: 'Update Available',
|
||||
if: ['update_available'],
|
||||
e: <IconButton className="shinyButton" color='primary' onClick={() => {doTo('update')}} size={isMiniMobile ? 'medium' : 'large'}>
|
||||
e: <IconButton className="shinyButton" color='primary' onClick={() => {doTo('pull')}} size={isMiniMobile ? 'medium' : 'large'}>
|
||||
<UpCircleOutlined />
|
||||
</IconButton>
|
||||
},
|
||||
|
@ -93,11 +108,28 @@ const GetActions = ({
|
|||
}
|
||||
];
|
||||
|
||||
return actions.filter((action) => {
|
||||
return action.if.includes(state) || (updateAvailable && action.if.includes('update_available'));
|
||||
}).map((action) => {
|
||||
return <Tooltip title={action.t}>{action.e}</Tooltip>
|
||||
});
|
||||
return <>
|
||||
<LogsInModal
|
||||
request={pullRequest}
|
||||
title="Updating ServeApp..."
|
||||
OnSuccess={() => {
|
||||
doTo('update')
|
||||
}}
|
||||
/>
|
||||
|
||||
{!isUpdating && actions.filter((action) => {
|
||||
updateAvailable = true;
|
||||
return action.if.includes(state) || (updateAvailable && action.if.includes('update_available'));
|
||||
}).map((action) => {
|
||||
return <Tooltip title={action.t}>{action.e}</Tooltip>
|
||||
})}
|
||||
|
||||
{isUpdating && <Stack sx={{
|
||||
width: '100%', height: '44px',
|
||||
}}
|
||||
justifyContent={'center'}
|
||||
><LinearProgress /></Stack>}
|
||||
</>
|
||||
}
|
||||
|
||||
export default GetActions;
|
|
@ -91,6 +91,7 @@ const ContainerOverview = ({ containerInfo, config, refresh }) => {
|
|||
<Stack spacing={2} direction={'row'} >
|
||||
<GetActions
|
||||
Id={containerInfo.Name}
|
||||
image={Image}
|
||||
state={State.Status}
|
||||
refreshServeApps={() => {
|
||||
refreshAll()
|
||||
|
@ -130,6 +131,20 @@ const ContainerOverview = ({ containerInfo, config, refresh }) => {
|
|||
}}
|
||||
/> Force Secure Network
|
||||
</Stack>
|
||||
<Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
|
||||
<Checkbox
|
||||
checked={Config.Labels['cosmos-auto-update'] === 'true'}
|
||||
disabled={isUpdating}
|
||||
onChange={(e) => {
|
||||
setIsUpdating(true);
|
||||
API.docker.autoUpdate(Name, e.target.checked).then(() => {
|
||||
setTimeout(() => {
|
||||
refreshAll();
|
||||
}, 3000);
|
||||
})
|
||||
}}
|
||||
/> Auto Update Container
|
||||
</Stack>
|
||||
<strong><NodeExpandOutlined /> URLs</strong>
|
||||
<div>
|
||||
{routes.map((route) => {
|
||||
|
|
|
@ -188,16 +188,14 @@ const ServeApps = () => {
|
|||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2}>
|
||||
{/* <Button variant="contained" size="small" onClick={() => {}}>
|
||||
Update
|
||||
</Button> */}
|
||||
<Stack direction="row" spacing={2} width='100%'>
|
||||
<GetActions
|
||||
Id={app.Names[0].replace('/', '')}
|
||||
image={app.Image}
|
||||
state={app.State}
|
||||
setIsUpdatingId={setIsUpdatingId}
|
||||
refreshServeApps={refreshServeApps}
|
||||
updateAvailable={updatesAvailable[app.Names[0]]}
|
||||
updateAvailable={updatesAvailable && updatesAvailable[app.Names[0]]}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
@ -242,10 +240,31 @@ const ServeApps = () => {
|
|||
setIsUpdatingId(app.Id, false);
|
||||
refreshServeApps();
|
||||
}, 3000);
|
||||
}).catch(() => {
|
||||
setIsUpdatingId(app.Id, false);
|
||||
refreshServeApps();
|
||||
})
|
||||
}}
|
||||
/> Force Secure Network
|
||||
</Stack>
|
||||
<Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
|
||||
<Checkbox
|
||||
checked={app.Labels['cosmos-auto-update'] === 'true'}
|
||||
disabled={app.State !== 'running'}
|
||||
onChange={(e) => {
|
||||
setIsUpdatingId(app.Id, true);
|
||||
API.docker.autoUpdate(app.Id, e.target.checked).then(() => {
|
||||
setTimeout(() => {
|
||||
setIsUpdatingId(app.Id, false);
|
||||
refreshServeApps();
|
||||
}, 3000);
|
||||
}).catch(() => {
|
||||
setIsUpdatingId(app.Id, false);
|
||||
refreshServeApps();
|
||||
})
|
||||
}}
|
||||
/> Auto Update Container
|
||||
</Stack>
|
||||
</Stack>
|
||||
}
|
||||
<Stack margin={1} direction="column" spacing={1} alignItems="flex-start">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.5.0-unstable10",
|
||||
"version": "0.5.0-unstable11",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
|
57
src/docker/api_autoupdate.go
Normal file
57
src/docker/api_autoupdate.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func AutoUpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
containerName := utils.SanitizeSafe(vars["containerId"])
|
||||
status := utils.Sanitize(vars["status"])
|
||||
|
||||
if os.Getenv("HOSTNAME") != "" && containerName == os.Getenv("HOSTNAME") {
|
||||
utils.Error("AutoUpdateContainerRoute - Container cannot update itself", nil)
|
||||
utils.HTTPError(w, "Container cannot update itself", http.StatusBadRequest, "DS003")
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
container, err := DockerClient.ContainerInspect(DockerContext, containerName)
|
||||
if err != nil {
|
||||
utils.Error("AutoUpdateContainer Inscpect", err)
|
||||
utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS002")
|
||||
return
|
||||
}
|
||||
|
||||
AddLabels(container, map[string]string{
|
||||
"cosmos-auto-update": status,
|
||||
});
|
||||
|
||||
utils.Log("API: Set Auto Update "+status+" : " + containerName)
|
||||
|
||||
_, errEdit := EditContainer(container.ID, container)
|
||||
if errEdit != nil {
|
||||
utils.Error("AutoUpdateContainer Edit", errEdit)
|
||||
utils.HTTPError(w, "Internal server error: " + errEdit.Error(), http.StatusInternalServerError, "DS003")
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
} else {
|
||||
utils.Error("AutoUpdateContainer: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
|
@ -161,7 +161,7 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
errD := Connect()
|
||||
if errD != nil {
|
||||
utils.Error("CreateService", errD)
|
||||
utils.Error("CreateService - connect - ", errD)
|
||||
utils.HTTPError(w, "Internal server error: " + errD.Error(), http.StatusInternalServerError, "DS002")
|
||||
return
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
|
|||
var serviceRequest DockerServiceCreateRequest
|
||||
err := decoder.Decode(&serviceRequest)
|
||||
if err != nil {
|
||||
utils.Error("CreateService", err)
|
||||
utils.Error("CreateService - decode - ", err)
|
||||
utils.HTTPError(w, "Bad request: "+err.Error(), http.StatusBadRequest, "DS003")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -230,6 +230,7 @@ func StartServer() {
|
|||
|
||||
srapi.HandleFunc("/api/servapps/{containerId}/manage/{action}", docker.ManageContainerRoute)
|
||||
srapi.HandleFunc("/api/servapps/{containerId}/secure/{status}", docker.SecureContainerRoute)
|
||||
srapi.HandleFunc("/api/servapps/{containerId}/auto-update/{status}", docker.AutoUpdateContainerRoute)
|
||||
srapi.HandleFunc("/api/servapps/{containerId}/logs", docker.GetContainerLogsRoute)
|
||||
srapi.HandleFunc("/api/servapps/{containerId}/terminal/{action}", docker.TerminalRoute)
|
||||
srapi.HandleFunc("/api/servapps/{containerId}/update", docker.UpdateContainerRoute)
|
||||
|
|
|
@ -24,6 +24,10 @@ func main() {
|
|||
|
||||
docker.BootstrapAllContainersFromTags()
|
||||
|
||||
// TODO DELET THIS BEFORE RELEASE
|
||||
|
||||
docker.CheckUpdatesAvailable()
|
||||
|
||||
version, err := docker.DockerClient.ServerVersion(context.Background())
|
||||
if err == nil {
|
||||
utils.Log("Docker API version: " + version.APIVersion)
|
||||
|
|
|
@ -75,7 +75,7 @@ func Check2FA(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
SendUserToken(w, userInBase, true)
|
||||
SendUserToken(w, req, userInBase, true)
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
|
|
|
@ -70,7 +70,7 @@ func UserLogin(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
SendUserToken(w, user, false)
|
||||
SendUserToken(w, req, user, false)
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
|
|
|
@ -10,7 +10,7 @@ func UserLogout(w http.ResponseWriter, req *http.Request) {
|
|||
if(req.Method == "GET") {
|
||||
utils.Debug("UserLogout: Logging out user")
|
||||
|
||||
logOutUser(w);
|
||||
logOutUser(w, req);
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
func quickLoggout(w http.ResponseWriter, req *http.Request, err error) (utils.User, error) {
|
||||
utils.Error("UserToken: Token likely falsified", err)
|
||||
logOutUser(w)
|
||||
logOutUser(w, req)
|
||||
redirectToReLogin(w, req)
|
||||
return utils.User{}, errors.New("Token likely falsified")
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func RefreshUserToken(w http.ResponseWriter, req *http.Request) (utils.User, err
|
|||
|
||||
if errP != nil {
|
||||
utils.Error("UserToken: token is not valid", nil)
|
||||
logOutUser(w)
|
||||
logOutUser(w, req)
|
||||
redirectToReLogin(w, req)
|
||||
return utils.User{}, errors.New("Token not valid")
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ func RefreshUserToken(w http.ResponseWriter, req *http.Request) (utils.User, err
|
|||
passwordCycle int
|
||||
mfaDone bool
|
||||
ok bool
|
||||
forDomain string
|
||||
)
|
||||
|
||||
if nickname, ok = claims["nickname"].(string); !ok {
|
||||
|
@ -107,6 +108,22 @@ func RefreshUserToken(w http.ResponseWriter, req *http.Request) (utils.User, err
|
|||
}
|
||||
}
|
||||
|
||||
if forDomain, ok = claims["forDomain"].(string); !ok {
|
||||
if _, e := quickLoggout(w, req, nil); e != nil {
|
||||
return utils.User{}, e
|
||||
}
|
||||
}
|
||||
|
||||
reqHostname := req.Host
|
||||
reqHostNoPort := strings.Split(reqHostname, ":")[0]
|
||||
|
||||
if !strings.HasSuffix(reqHostNoPort, forDomain) {
|
||||
utils.Error("UserToken: token is not valid for this domain", nil)
|
||||
logOutUser(w, req)
|
||||
redirectToReLogin(w, req)
|
||||
return utils.User{}, errors.New("JWT Token not valid for this domain")
|
||||
}
|
||||
|
||||
userInBase := utils.User{}
|
||||
|
||||
c, errCo := utils.GetCollection(utils.GetRootAppId(), "users")
|
||||
|
@ -122,14 +139,14 @@ func RefreshUserToken(w http.ResponseWriter, req *http.Request) (utils.User, err
|
|||
|
||||
if errDB != nil {
|
||||
utils.Error("UserToken: User not found", errDB)
|
||||
logOutUser(w)
|
||||
logOutUser(w, req)
|
||||
redirectToReLogin(w, req)
|
||||
return utils.User{}, errors.New("User not found")
|
||||
}
|
||||
|
||||
if userInBase.PasswordCycle != passwordCycle {
|
||||
utils.Error("UserToken: Password cycle changed, token is too old", nil)
|
||||
logOutUser(w)
|
||||
logOutUser(w, req)
|
||||
redirectToReLogin(w, req)
|
||||
return utils.User{}, errors.New("Password cycle changed, token is too old")
|
||||
}
|
||||
|
@ -148,7 +165,7 @@ func RefreshUserToken(w http.ResponseWriter, req *http.Request) (utils.User, err
|
|||
}
|
||||
|
||||
if time.Now().Unix() - int64(claims["iat"].(float64)) > 3600 {
|
||||
SendUserToken(w, userInBase, mfaDone)
|
||||
SendUserToken(w, req, userInBase, mfaDone)
|
||||
}
|
||||
|
||||
return userInBase, nil
|
||||
|
@ -159,7 +176,10 @@ func GetUserR(req *http.Request) (string, string) {
|
|||
}
|
||||
|
||||
|
||||
func logOutUser(w http.ResponseWriter) {
|
||||
func logOutUser(w http.ResponseWriter, req *http.Request) {
|
||||
reqHostname := req.Host
|
||||
reqHostNoPort := strings.Split(reqHostname, ":")[0]
|
||||
|
||||
cookie := http.Cookie{
|
||||
Name: "jwttoken",
|
||||
Value: "",
|
||||
|
@ -167,11 +187,12 @@ func logOutUser(w http.ResponseWriter) {
|
|||
Path: "/",
|
||||
Secure: utils.IsHTTPS,
|
||||
HttpOnly: true,
|
||||
Domain: utils.GetMainConfig().HTTPConfig.Hostname,
|
||||
}
|
||||
|
||||
if(utils.GetMainConfig().HTTPConfig.Hostname == "localhost" || utils.GetMainConfig().HTTPConfig.Hostname == "0.0.0.0") {
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" {
|
||||
cookie.Domain = ""
|
||||
} else {
|
||||
cookie.Domain = "." + reqHostNoPort
|
||||
}
|
||||
|
||||
http.SetCookie(w, &cookie)
|
||||
|
@ -191,7 +212,10 @@ func redirectToNewMFA(w http.ResponseWriter, req *http.Request) {
|
|||
http.Redirect(w, req, "/ui/newmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func SendUserToken(w http.ResponseWriter, user utils.User, mfaDone bool) {
|
||||
func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mfaDone bool) {
|
||||
reqHostname := req.Host
|
||||
reqHostNoPort := strings.Split(reqHostname, ":")[0]
|
||||
|
||||
expiration := time.Now().Add(3 * 24 * time.Hour)
|
||||
|
||||
token := jwt.New(jwt.SigningMethodEdDSA)
|
||||
|
@ -203,6 +227,7 @@ func SendUserToken(w http.ResponseWriter, user utils.User, mfaDone bool) {
|
|||
claims["iat"] = time.Now().Unix()
|
||||
claims["nbf"] = time.Now().Unix()
|
||||
claims["mfaDone"] = mfaDone
|
||||
claims["forDomain"] = reqHostNoPort
|
||||
|
||||
key, err5 := jwt.ParseEdPrivateKeyFromPEM([]byte(utils.GetPrivateAuthKey()))
|
||||
|
||||
|
@ -227,11 +252,20 @@ func SendUserToken(w http.ResponseWriter, user utils.User, mfaDone bool) {
|
|||
Path: "/",
|
||||
Secure: utils.IsHTTPS,
|
||||
HttpOnly: true,
|
||||
Domain: utils.GetMainConfig().HTTPConfig.Hostname,
|
||||
}
|
||||
|
||||
if(utils.GetMainConfig().HTTPConfig.Hostname == "localhost" || utils.GetMainConfig().HTTPConfig.Hostname == "0.0.0.0") {
|
||||
utils.Log("UserLogin: Setting cookie for " + reqHostNoPort)
|
||||
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" {
|
||||
cookie.Domain = ""
|
||||
} else {
|
||||
if utils.IsValidHostname(reqHostNoPort) {
|
||||
cookie.Domain = "." + reqHostNoPort
|
||||
} else {
|
||||
utils.Error("UserLogin: Invalid hostname", nil)
|
||||
utils.HTTPError(w, "User Logging Error", http.StatusInternalServerError, "UL001")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.SetCookie(w, &cookie)
|
||||
|
|
|
@ -202,4 +202,42 @@ func EnsureHostname(next http.Handler) http.Handler {
|
|||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func IsValidHostname(hostname string) bool {
|
||||
og := GetMainConfig().HTTPConfig.Hostname
|
||||
ni := GetMainConfig().NewInstall
|
||||
|
||||
if ni || og == "0.0.0.0" {
|
||||
return true
|
||||
}
|
||||
|
||||
hostnames := GetAllHostnames()
|
||||
|
||||
reqHostNoPort := strings.Split(hostname, ":")[0]
|
||||
|
||||
reqHostNoPortNoSubdomain := ""
|
||||
|
||||
if parts := strings.Split(reqHostNoPort, "."); len(parts) < 2 {
|
||||
reqHostNoPortNoSubdomain = reqHostNoPort
|
||||
} else {
|
||||
reqHostNoPortNoSubdomain = parts[len(parts)-2] + "." + parts[len(parts)-1]
|
||||
}
|
||||
|
||||
for _, hostname := range hostnames {
|
||||
hostnameNoPort := strings.Split(hostname, ":")[0]
|
||||
hostnameNoPortNoSubdomain := ""
|
||||
|
||||
if parts := strings.Split(hostnameNoPort, "."); len(parts) < 2 {
|
||||
hostnameNoPortNoSubdomain = hostnameNoPort
|
||||
} else {
|
||||
hostnameNoPortNoSubdomain = parts[len(parts)-2] + "." + parts[len(parts)-1]
|
||||
}
|
||||
|
||||
if reqHostNoPortNoSubdomain == hostnameNoPortNoSubdomain {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
Loading…
Reference in a new issue