diff --git a/changelog.md b/changelog.md index 08325db..ea5a320 100644 --- a/changelog.md +++ b/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 - Check network mode before pruning networks diff --git a/client/src/api/index.jsx b/client/src/api/index.jsx index 3e44e43..492bc78 100644 --- a/client/src/api/index.jsx +++ b/client/src/api/index.jsx @@ -12,6 +12,7 @@ import * as indexDemo from './index.demo'; import * as marketDemo from './market.demo'; import wrap from './wrap'; +import { redirectToLocal } from '../utils/indexs'; export let CPU_ARCH = 'amd64'; export let CPU_AVX = true; @@ -39,7 +40,7 @@ let getStatus = (initial) => { return response }).catch((response) => { const urlSearch = encodeURIComponent(window.location.search); - const redirectTo = (window.location.pathname + urlSearch); + const redirectToURL = (window.location.pathname + urlSearch); if(response.status != 'OK') { if( @@ -50,13 +51,13 @@ let getStatus = (initial) => { window.location.href.indexOf('/cosmos-ui/register') == -1 && window.location.href.indexOf('/cosmos-ui/forgot-password') == -1) { if(response.status == 'NEW_INSTALL') { - window.location.href = '/cosmos-ui/newInstall'; + redirectToLocal('/cosmos-ui/newInstall'); } 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") { - window.location.href = '/cosmos-ui/loginmfa?redirect=' + redirectTo; + redirectToLocal('/cosmos-ui/loginmfa?redirect=' + redirectToURL); } else if (response.status == 'error' && response.code == "HTTP007") { - window.location.href = '/cosmos-ui/newmfa?redirect=' + redirectTo; + redirectToLocal('/cosmos-ui/newmfa?redirect=' + redirectToURL); } } else { return "nothing"; diff --git a/client/src/isLoggedIn.jsx b/client/src/isLoggedIn.jsx index d15e430..1dcbf35 100644 --- a/client/src/isLoggedIn.jsx +++ b/client/src/isLoggedIn.jsx @@ -1,22 +1,23 @@ import * as API from './api'; import { useEffect } from 'react'; +import { redirectToLocal } from './utils/indexs'; const IsLoggedIn = () => useEffect(() => { console.log("CHECK LOGIN") const urlSearch = encodeURIComponent(window.location.search); - const redirectTo = (window.location.pathname + urlSearch); + const redirectToURL = (window.location.pathname + urlSearch); API.auth.me().then((data) => { if(data.status != 'OK') { if(data.status == 'NEW_INSTALL') { - window.location.href = '/cosmos-ui/newInstall'; + redirectToLocal('/cosmos-ui/newInstall'); } 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") { - window.location.href = '/cosmos-ui/loginmfa?redirect=' + redirectTo; + redirectToLocal('/cosmos-ui/loginmfa?redirect=' + redirectToURL); } else if (data.status == 'error' && data.code == "HTTP007") { - window.location.href = '/cosmos-ui/newmfa?redirect=' + redirectTo; + redirectToLocal('/cosmos-ui/newmfa?redirect=' + redirectToURL); } } }) diff --git a/client/src/pages/authentication/Logoff.jsx b/client/src/pages/authentication/Logoff.jsx index 5422117..635efc0 100644 --- a/client/src/pages/authentication/Logoff.jsx +++ b/client/src/pages/authentication/Logoff.jsx @@ -9,6 +9,7 @@ import AuthWrapper from './AuthWrapper'; import { useEffect } from 'react'; import * as API from '../../api'; +import { redirectTo } from '../../utils/indexs'; // ================================|| REGISTER ||================================ // @@ -17,7 +18,7 @@ const Logout = () => { API.auth.logout() .then(() => { setTimeout(() => { - window.location.href = '/cosmos-ui/login'; + redirectToLocal('/cosmos-ui/login'); }, 2000); }); },[]); diff --git a/client/src/pages/authentication/auth-forms/AuthLogin.jsx b/client/src/pages/authentication/auth-forms/AuthLogin.jsx index 7085c2c..d4ab04a 100644 --- a/client/src/pages/authentication/auth-forms/AuthLogin.jsx +++ b/client/src/pages/authentication/auth-forms/AuthLogin.jsx @@ -31,6 +31,7 @@ import AnimateButton from '../../../components/@extended/AnimateButton'; // assets import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; import { LoadingButton } from '@mui/lab'; +import { redirectToLocal } from '../../../utils/indexs'; // ============================|| FIREBASE - LOGIN ||============================ // @@ -53,14 +54,14 @@ const AuthLogin = () => { 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') : '/cosmos-ui'; + const redirectToURL = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/cosmos-ui'; useEffect(() => { API.auth.me().then((data) => { if(data.status == 'OK') { - window.location.href = redirectTo; + redirectToLocal(redirectToURL); } 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) => { setStatus({ success: true }); setSubmitting(false); - window.location.href = redirectTo; + redirectToLocal(redirectToURL); }).catch((err) => { setStatus({ success: false }); if(err.code == 'UL001') { diff --git a/client/src/pages/authentication/auth-forms/AuthRegister.jsx b/client/src/pages/authentication/auth-forms/AuthRegister.jsx index a46d95b..1144e49 100644 --- a/client/src/pages/authentication/auth-forms/AuthRegister.jsx +++ b/client/src/pages/authentication/auth-forms/AuthRegister.jsx @@ -33,6 +33,7 @@ import { strengthColor, strengthIndicator } from '../../../utils/password-streng // assets import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; import { LoadingButton } from '@mui/lab'; +import { redirectTo } from '../../../utils/indexs'; // ============================|| FIREBASE - REGISTER ||============================ // @@ -85,7 +86,7 @@ const AuthRegister = ({nickname, isRegister, isInviteLink, regkey}) => { }).then((res) => { setStatus({ success: true }); setSubmitting(false); - window.location.href = '/cosmos-ui/login'; + redirectToLocal('/cosmos-ui/login'); }).catch((err) => { setStatus({ success: false }); setErrors({ submit: err.message }); diff --git a/client/src/pages/authentication/newMFA.jsx b/client/src/pages/authentication/newMFA.jsx index c554116..2e8d562 100644 --- a/client/src/pages/authentication/newMFA.jsx +++ b/client/src/pages/authentication/newMFA.jsx @@ -32,17 +32,18 @@ import { useTheme } from '@mui/material/styles'; import { Formik } from 'formik'; import { LoadingButton } from '@mui/lab'; import { CosmosCollapse } from '../config/users/formShortcuts'; +import { redirectToLocal } from '../../utils/indexs'; const MFALoginForm = () => { 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(() => { API.auth.me().then((data) => { if(data.status == 'OK') { - window.location.href = redirectTo; + redirectToLocal(redirectToURL); } 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 }) => { API.users.check2FA(values.token).then((data) => { - window.location.href = redirectTo; + redirectToLocal(redirectToURL); }).catch((error) => { console.log(error) setStatus({ success: false }); diff --git a/client/src/pages/config/routes/routeoverview.jsx b/client/src/pages/config/routes/routeoverview.jsx index 5ca6671..3775155 100644 --- a/client/src/pages/config/routes/routeoverview.jsx +++ b/client/src/pages/config/routes/routeoverview.jsx @@ -8,6 +8,7 @@ import { getFaviconURL } from '../../../utils/routes'; import * as API from '../../../api'; import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons"; import IsLoggedIn from '../../../isLoggedIn'; +import { redirectTo } from '../../../utils/indexs'; const info = { backgroundColor: 'rgba(0, 0, 0, 0.1)', @@ -22,7 +23,7 @@ const RouteOverview = ({ routeConfig }) => { function deleteRoute(event) { event.stopPropagation(); API.config.deleteRoute(routeConfig.Name).then(() => { - window.location.href = '/cosmos-ui/config-url'; + redirectToLocal('/cosmos-ui/config-url'); }); } diff --git a/client/src/pages/newInstall/newInstall.jsx b/client/src/pages/newInstall/newInstall.jsx index dbd0a15..28faaf8 100644 --- a/client/src/pages/newInstall/newInstall.jsx +++ b/client/src/pages/newInstall/newInstall.jsx @@ -19,7 +19,7 @@ import { CosmosCheckbox, CosmosInputPassword, CosmosInputText, CosmosSelect } fr import AnimateButton from '../../components/@extended/AnimateButton'; import { Box } from '@mui/system'; import { pull } from 'lodash'; -import { isDomain } from '../../utils/indexs'; +import { isDomain, redirectTo } from '../../utils/indexs'; import { DnsChallengeComp } from '../../utils/dns-challenge-comp'; // ================================|| LOGIN ||================================ // @@ -67,7 +67,7 @@ const NewInstall = () => { setStatus(res.data); } catch(error) { if(error.status == 401) - window.location.href = "/cosmos-ui/login"; + redirectToLocal("/cosmos-ui/login"); } if (typeof status !== 'undefined') { setTimeout(() => { @@ -618,7 +618,7 @@ const NewInstall = () => { step: "5", }) setTimeout(() => { - window.location.href = hostname + "/cosmos-ui/login"; + redirectTo(hostname + "/cosmos-ui/login"); }, 500); } else { setActiveStep(activeStep + 1) diff --git a/client/src/pages/openid/openid-list.jsx b/client/src/pages/openid/openid-list.jsx index 7d1c60b..305a2e0 100644 --- a/client/src/pages/openid/openid-list.jsx +++ b/client/src/pages/openid/openid-list.jsx @@ -114,7 +114,11 @@ const OpenIdList = () => { }, []); 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 index = clients.findIndex((r) => r.id === clientIdToUpdate); clients[index].secret = encryptedSecret; diff --git a/client/src/utils/indexs.js b/client/src/utils/indexs.js index 38cd773..cf7a756 100644 --- a/client/src/utils/indexs.js +++ b/client/src/utils/indexs.js @@ -34,4 +34,15 @@ export const debounce = (func, wait) => { clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; - }; \ No newline at end of file + }; + +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; +} \ No newline at end of file diff --git a/package.json b/package.json index 1492cc9..4b15a29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.9.14", + "version": "0.9.15", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/background.go b/src/background.go index c66c698..fc5eda4 100644 --- a/src/background.go +++ b/src/background.go @@ -12,6 +12,18 @@ import ( "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) { if utils.AdminOnly(w, req) != nil { return @@ -32,9 +44,14 @@ func UploadBackground(w http.ResponseWriter, req *http.Request) { return } defer file.Close() - + // get the file extension 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 dst, err := os.Create("/config/background" + ext) @@ -75,19 +92,7 @@ func GetBackground(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) 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") return }