ソースを参照

[release] v0.9.15

Yann Stepienik 1 年間 前
コミット
0801799600

+ 6 - 0
changelog.md

@@ -1,3 +1,9 @@
+## Version 0.9.15
+ - Check background extension on upload is an image
+ - Update Docker for security patch
+ - Check redirect target is local
+ - Improve OpenID client secret generation
+
 ## Version 0.9.14
 ## Version 0.9.14
  - Check network mode before pruning networks
  - Check network mode before pruning networks
 
 

+ 6 - 5
client/src/api/index.jsx

@@ -12,6 +12,7 @@ import * as indexDemo from './index.demo';
 import * as marketDemo from './market.demo';
 import * as marketDemo from './market.demo';
 
 
 import wrap from './wrap';
 import wrap from './wrap';
+import { redirectToLocal } from '../utils/indexs';
 
 
 export let CPU_ARCH = 'amd64';
 export let CPU_ARCH = 'amd64';
 export let CPU_AVX = true;
 export let CPU_AVX = true;
@@ -39,7 +40,7 @@ let getStatus = (initial) => {
     return response
     return response
   }).catch((response) => {
   }).catch((response) => {
     const urlSearch = encodeURIComponent(window.location.search);
     const urlSearch = encodeURIComponent(window.location.search);
-    const redirectTo = (window.location.pathname + urlSearch);
+    const redirectToURL = (window.location.pathname + urlSearch);
 
 
     if(response.status != 'OK') {
     if(response.status != 'OK') {
       if( 
       if( 
@@ -50,13 +51,13 @@ let getStatus = (initial) => {
           window.location.href.indexOf('/cosmos-ui/register') == -1 &&
           window.location.href.indexOf('/cosmos-ui/register') == -1 &&
           window.location.href.indexOf('/cosmos-ui/forgot-password') == -1) {
           window.location.href.indexOf('/cosmos-ui/forgot-password') == -1) {
         if(response.status == 'NEW_INSTALL') {
         if(response.status == 'NEW_INSTALL') {
-            window.location.href = '/cosmos-ui/newInstall';
+            redirectToLocal('/cosmos-ui/newInstall');
         } else if (response.status == 'error' && response.code == "HTTP004") {
         } else if (response.status == 'error' && response.code == "HTTP004") {
-            window.location.href = '/cosmos-ui/login?redirect=' + redirectTo;
+            redirectToLocal('/cosmos-ui/login?redirect=' + redirectToURL);
         } else if (response.status == 'error' && response.code == "HTTP006") {
         } else if (response.status == 'error' && response.code == "HTTP006") {
-            window.location.href = '/cosmos-ui/loginmfa?redirect=' + redirectTo;
+            redirectToLocal('/cosmos-ui/loginmfa?redirect=' + redirectToURL);
         } else if (response.status == 'error' && response.code == "HTTP007") {
         } else if (response.status == 'error' && response.code == "HTTP007") {
-            window.location.href = '/cosmos-ui/newmfa?redirect=' + redirectTo;
+            redirectToLocal('/cosmos-ui/newmfa?redirect=' + redirectToURL);
         }
         }
       } else {
       } else {
         return "nothing";
         return "nothing";

+ 6 - 5
client/src/isLoggedIn.jsx

@@ -1,22 +1,23 @@
 
 
 import * as API from './api';
 import * as API from './api';
 import { useEffect } from 'react';
 import { useEffect } from 'react';
+import { redirectToLocal } from './utils/indexs';
 
 
 const IsLoggedIn = () => useEffect(() => {
 const IsLoggedIn = () => useEffect(() => {
     console.log("CHECK LOGIN")
     console.log("CHECK LOGIN")
     const urlSearch = encodeURIComponent(window.location.search);
     const urlSearch = encodeURIComponent(window.location.search);
-    const redirectTo = (window.location.pathname + urlSearch);
+    const redirectToURL = (window.location.pathname + urlSearch);
 
 
     API.auth.me().then((data) => {
     API.auth.me().then((data) => {
         if(data.status != 'OK') {
         if(data.status != 'OK') {
             if(data.status == 'NEW_INSTALL') {
             if(data.status == 'NEW_INSTALL') {
-                window.location.href = '/cosmos-ui/newInstall';
+                redirectToLocal('/cosmos-ui/newInstall');
             } else if (data.status == 'error' && data.code == "HTTP004") {
             } else if (data.status == 'error' && data.code == "HTTP004") {
-                window.location.href = '/cosmos-ui/login?redirect=' + redirectTo;
+                redirectToLocal('/cosmos-ui/login?redirect=' + redirectToURL);
             } else if (data.status == 'error' && data.code == "HTTP006") {
             } else if (data.status == 'error' && data.code == "HTTP006") {
-                window.location.href = '/cosmos-ui/loginmfa?redirect=' + redirectTo;
+                redirectToLocal('/cosmos-ui/loginmfa?redirect=' + redirectToURL);
             } else if (data.status == 'error' && data.code == "HTTP007") {
             } else if (data.status == 'error' && data.code == "HTTP007") {
-                window.location.href = '/cosmos-ui/newmfa?redirect=' + redirectTo;
+                redirectToLocal('/cosmos-ui/newmfa?redirect=' + redirectToURL);
             }
             }
         }
         }
     })
     })

+ 2 - 1
client/src/pages/authentication/Logoff.jsx

@@ -9,6 +9,7 @@ import AuthWrapper from './AuthWrapper';
 import { useEffect } from 'react';
 import { useEffect } from 'react';
 
 
 import * as API from '../../api';
 import * as API from '../../api';
+import { redirectTo } from '../../utils/indexs';
 
 
 // ================================|| REGISTER ||================================ //
 // ================================|| REGISTER ||================================ //
 
 
@@ -17,7 +18,7 @@ const Logout = () => {
       API.auth.logout()
       API.auth.logout()
        .then(() => {
        .then(() => {
           setTimeout(() => {
           setTimeout(() => {
-            window.location.href = '/cosmos-ui/login';
+            redirectToLocal('/cosmos-ui/login');
           }, 2000);
           }, 2000);
         });
         });
     },[]);
     },[]);

+ 5 - 4
client/src/pages/authentication/auth-forms/AuthLogin.jsx

@@ -31,6 +31,7 @@ import AnimateButton from '../../../components/@extended/AnimateButton';
 // assets
 // assets
 import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
 import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
 import { LoadingButton } from '@mui/lab';
 import { LoadingButton } from '@mui/lab';
+import { redirectToLocal } from '../../../utils/indexs';
 
 
 // ============================|| FIREBASE - LOGIN ||============================ //
 // ============================|| FIREBASE - LOGIN ||============================ //
 
 
@@ -53,14 +54,14 @@ const AuthLogin = () => {
     const notLogged = urlSearchParams.get('notlogged') == 1;
     const notLogged = urlSearchParams.get('notlogged') == 1;
     const notLoggedAdmin = urlSearchParams.get('notlogged') == 2;
     const notLoggedAdmin = urlSearchParams.get('notlogged') == 2;
     const invalid = urlSearchParams.get('invalid') == 1;
     const invalid = urlSearchParams.get('invalid') == 1;
-    const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/cosmos-ui';
+    const redirectToURL = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/cosmos-ui';
 
 
     useEffect(() => {
     useEffect(() => {
         API.auth.me().then((data) => {
         API.auth.me().then((data) => {
             if(data.status == 'OK') {
             if(data.status == 'OK') {
-                window.location.href = redirectTo;
+                redirectToLocal(redirectToURL);
             } else if(data.status == 'NEW_INSTALL') {
             } else if(data.status == 'NEW_INSTALL') {
-                window.location.href = '/cosmos-ui/newInstall';
+                redirectToLocal('/cosmos-ui/newInstall');
             }
             }
         });
         });
 
 
@@ -103,7 +104,7 @@ const AuthLogin = () => {
                     return API.auth.login(values).then((data) => {
                     return API.auth.login(values).then((data) => {
                         setStatus({ success: true });
                         setStatus({ success: true });
                         setSubmitting(false);
                         setSubmitting(false);
-                        window.location.href = redirectTo;
+                        redirectToLocal(redirectToURL);
                     }).catch((err) => {
                     }).catch((err) => {
                         setStatus({ success: false });
                         setStatus({ success: false });
                         if(err.code == 'UL001') {
                         if(err.code == 'UL001') {

+ 2 - 1
client/src/pages/authentication/auth-forms/AuthRegister.jsx

@@ -33,6 +33,7 @@ import { strengthColor, strengthIndicator } from '../../../utils/password-streng
 // assets
 // assets
 import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
 import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
 import { LoadingButton } from '@mui/lab';
 import { LoadingButton } from '@mui/lab';
+import { redirectTo } from '../../../utils/indexs';
 
 
 // ============================|| FIREBASE - REGISTER ||============================ //
 // ============================|| FIREBASE - REGISTER ||============================ //
 
 
@@ -85,7 +86,7 @@ const AuthRegister = ({nickname, isRegister, isInviteLink, regkey}) => {
                     }).then((res) => {
                     }).then((res) => {
                         setStatus({ success: true });
                         setStatus({ success: true });
                         setSubmitting(false);
                         setSubmitting(false);
-                        window.location.href = '/cosmos-ui/login';
+                        redirectToLocal('/cosmos-ui/login');
                     }).catch((err) => {
                     }).catch((err) => {
                         setStatus({ success: false });
                         setStatus({ success: false });
                         setErrors({ submit: err.message });
                         setErrors({ submit: err.message });

+ 5 - 4
client/src/pages/authentication/newMFA.jsx

@@ -32,17 +32,18 @@ import { useTheme } from '@mui/material/styles';
 import { Formik } from 'formik';
 import { Formik } from 'formik';
 import { LoadingButton } from '@mui/lab';
 import { LoadingButton } from '@mui/lab';
 import { CosmosCollapse } from '../config/users/formShortcuts';
 import { CosmosCollapse } from '../config/users/formShortcuts';
+import { redirectToLocal } from '../../utils/indexs';
 
 
 const MFALoginForm = () => {
 const MFALoginForm = () => {
   const urlSearchParams = new URLSearchParams(window.location.search);
   const urlSearchParams = new URLSearchParams(window.location.search);
-  const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/cosmos-ui';
+  const redirectToURL = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/cosmos-ui';
 
 
   useEffect(() => {
   useEffect(() => {
     API.auth.me().then((data) => {
     API.auth.me().then((data) => {
         if(data.status == 'OK') {
         if(data.status == 'OK') {
-            window.location.href = redirectTo;
+          redirectToLocal(redirectToURL);
         } else if(data.status == 'NEW_INSTALL') {
         } else if(data.status == 'NEW_INSTALL') {
-            window.location.href = '/cosmos-ui/newInstall';
+          redirectToLocal('/cosmos-ui/newInstall');
         }
         }
     });
     });
   });  
   });  
@@ -56,7 +57,7 @@ const MFALoginForm = () => {
     })}
     })}
     onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
     onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
       API.users.check2FA(values.token).then((data) => {
       API.users.check2FA(values.token).then((data) => {
-        window.location.href = redirectTo;
+        redirectToLocal(redirectToURL);
       }).catch((error) => {
       }).catch((error) => {
         console.log(error)
         console.log(error)
         setStatus({ success: false });
         setStatus({ success: false });

+ 2 - 1
client/src/pages/config/routes/routeoverview.jsx

@@ -8,6 +8,7 @@ import { getFaviconURL } from '../../../utils/routes';
 import * as API from '../../../api';
 import * as API from '../../../api';
 import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
 import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
 import IsLoggedIn from '../../../isLoggedIn';
 import IsLoggedIn from '../../../isLoggedIn';
+import { redirectTo } from '../../../utils/indexs';
 
 
 const info = {
 const info = {
   backgroundColor: 'rgba(0, 0, 0, 0.1)',
   backgroundColor: 'rgba(0, 0, 0, 0.1)',
@@ -22,7 +23,7 @@ const RouteOverview = ({ routeConfig }) => {
   function deleteRoute(event) {
   function deleteRoute(event) {
     event.stopPropagation();
     event.stopPropagation();
     API.config.deleteRoute(routeConfig.Name).then(() => {
     API.config.deleteRoute(routeConfig.Name).then(() => {
-      window.location.href = '/cosmos-ui/config-url';
+      redirectToLocal('/cosmos-ui/config-url');
     });
     });
   }
   }
 
 

+ 3 - 3
client/src/pages/newInstall/newInstall.jsx

@@ -19,7 +19,7 @@ import { CosmosCheckbox, CosmosInputPassword, CosmosInputText, CosmosSelect } fr
 import AnimateButton from '../../components/@extended/AnimateButton';
 import AnimateButton from '../../components/@extended/AnimateButton';
 import { Box } from '@mui/system';
 import { Box } from '@mui/system';
 import { pull } from 'lodash';
 import { pull } from 'lodash';
-import { isDomain } from '../../utils/indexs';
+import { isDomain, redirectTo } from '../../utils/indexs';
 import { DnsChallengeComp } from '../../utils/dns-challenge-comp';
 import { DnsChallengeComp } from '../../utils/dns-challenge-comp';
 // ================================|| LOGIN ||================================ //
 // ================================|| LOGIN ||================================ //
 
 
@@ -67,7 +67,7 @@ const NewInstall = () => {
             setStatus(res.data);
             setStatus(res.data);
         } catch(error) {
         } catch(error) {
             if(error.status == 401)
             if(error.status == 401)
-                window.location.href = "/cosmos-ui/login";
+            redirectToLocal("/cosmos-ui/login");
         }
         }
         if (typeof status !== 'undefined') {
         if (typeof status !== 'undefined') {
             setTimeout(() => {
             setTimeout(() => {
@@ -618,7 +618,7 @@ const NewInstall = () => {
                                     step: "5",
                                     step: "5",
                                 })
                                 })
                                 setTimeout(() => {
                                 setTimeout(() => {
-                                    window.location.href = hostname + "/cosmos-ui/login";
+                                    redirectTo(hostname + "/cosmos-ui/login");
                                 }, 500);
                                 }, 500);
                             } else {
                             } else {
                                 setActiveStep(activeStep + 1)
                                 setActiveStep(activeStep + 1)

+ 5 - 1
client/src/pages/openid/openid-list.jsx

@@ -114,7 +114,11 @@ const OpenIdList = () => {
   }, []);
   }, []);
 
 
   const generateNewSecret = (clientIdToUpdate) => {
   const generateNewSecret = (clientIdToUpdate) => {
-    let newSecret = Math.random().toString(36).substring(2, 24) + Math.random().toString(36).substring(2, 15);
+    let newSecretSeed = window.crypto.getRandomValues(new Uint32Array(4));
+    let newSecret = "";
+    newSecretSeed.forEach((r) => {
+      newSecret += r.toString(36);
+    });
     let encryptedSecret = bcrypt.hashSync(newSecret, 10);
     let encryptedSecret = bcrypt.hashSync(newSecret, 10);
     let index = clients.findIndex((r) => r.id === clientIdToUpdate);
     let index = clients.findIndex((r) => r.id === clientIdToUpdate);
     clients[index].secret = encryptedSecret;
     clients[index].secret = encryptedSecret;

+ 12 - 1
client/src/utils/indexs.js

@@ -34,4 +34,15 @@ export const debounce = (func, wait) => {
       clearTimeout(timeout);
       clearTimeout(timeout);
       timeout = setTimeout(() => func.apply(context, args), wait);
       timeout = setTimeout(() => func.apply(context, args), wait);
     };
     };
-  };
+  };
+
+export const redirectTo = (url) => {
+  window.location.href = url;
+}
+
+export const redirectToLocal = (url) => {
+  if(url.startsWith("http://") || url.startsWith("https://")) {
+    throw new Error("URL must be local");
+  }
+  window.location.href = url;
+}

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "cosmos-server",
   "name": "cosmos-server",
-  "version": "0.9.14",
+  "version": "0.9.15",
   "description": "",
   "description": "",
   "main": "test-server.js",
   "main": "test-server.js",
   "bugs": {
   "bugs": {

+ 19 - 14
src/background.go

@@ -12,6 +12,18 @@ import (
 	"github.com/azukaar/cosmos-server/src/utils" 
 	"github.com/azukaar/cosmos-server/src/utils" 
 )
 )
 
 
+var validExtensions = map[string]bool{
+	".jpg":  true,
+	".jpeg": true,
+	".png":  true,
+	".gif":  true,
+	".bmp":  true,
+	".svg":  true,
+	".webp": true,
+	".tiff": true,
+	".avif": true,
+}
+
 func UploadBackground(w http.ResponseWriter, req *http.Request) {
 func UploadBackground(w http.ResponseWriter, req *http.Request) {
 	if utils.AdminOnly(w, req) != nil {
 	if utils.AdminOnly(w, req) != nil {
 		return
 		return
@@ -32,9 +44,14 @@ func UploadBackground(w http.ResponseWriter, req *http.Request) {
 			return
 			return
 		}
 		}
 		defer file.Close()
 		defer file.Close()
-
+		
 		// get the file extension
 		// get the file extension
 		ext := filepath.Ext(header.Filename)
 		ext := filepath.Ext(header.Filename)
+		
+		if !validExtensions[ext] {
+			utils.HTTPError(w, "Invalid file extension " + ext, http.StatusBadRequest, "FILE001")
+			return
+		}
 
 
 		// create a new file in the config directory
 		// create a new file in the config directory
 		dst, err := os.Create("/config/background" + ext)
 		dst, err := os.Create("/config/background" + ext)
@@ -75,19 +92,7 @@ func GetBackground(w http.ResponseWriter, req *http.Request) {
 	vars := mux.Vars(req)
 	vars := mux.Vars(req)
 	ext := vars["ext"]
 	ext := vars["ext"]
 
 
-	validExtensions := map[string]bool{
-		"jpg":  true,
-		"jpeg": true,
-		"png":  true,
-		"gif":  true,
-		"bmp":  true,
-		"svg":  true,
-		"webp": true,
-		"tiff": true,
-		"avif": true,
-	}
-
-	if !validExtensions[ext] {
+	if !validExtensions["." + ext] {
 		utils.HTTPError(w, "Invalid file extension", http.StatusBadRequest, "FILE001")
 		utils.HTTPError(w, "Invalid file extension", http.StatusBadRequest, "FILE001")
 		return
 		return
 	}
 	}