Browse Source

current hostname as cookie domain, group edit member bugfix

Roman 3 weeks ago
parent
commit
72d2850e83

+ 1 - 8
Core/Configuration/Settings.class.php

@@ -84,15 +84,8 @@ class Settings {
   }
 
   public static function loadDefaults(): Settings {
-    $hostname = $_SERVER["SERVER_NAME"] ?? null;
-    if (empty($hostname)) {
-      $hostname = $_SERVER["HTTP_HOST"] ?? null;
-      if (empty($hostname)) {
-        $hostname = "localhost";
-      }
-    }
-
     $protocol = getProtocol();
+    $hostname = getHostName();
     $settings = new Settings();
 
     // General

+ 3 - 2
Core/Objects/Context.class.php

@@ -92,7 +92,8 @@ class Context {
 
   public function sendCookies(): void {
     // TODO: what will we do, when there is a domain mismatch? forbid access or just send cookies for the current domain? or should we send a redirect?
-    $domain = $this->getSettings()->getDomain();
+    // $domain = $this->getSettings()->getDomain();
+    $domain = getCurrentHostName();
     $this->language->sendCookie($domain);
     $this->session?->sendCookie($domain);
     $this->session?->update();
@@ -202,7 +203,7 @@ class Context {
     return $this->language;
   }
 
-  public function invalidateSessions(bool $keepCurrent = true): bool {
+  public function invalidateSessions(bool $keepCurrent = false): bool {
     $query = $this->sql->update("Session")
       ->set("active", false)
       ->whereEq("user_id", $this->user->getId());

+ 12 - 0
Core/core.php

@@ -36,6 +36,18 @@ function getProtocol(): string {
   return $isSecure ? 'https' : 'http';
 }
 
+function getCurrentHostName(): string {
+  $hostname = $_SERVER["SERVER_NAME"] ?? null;
+  if (empty($hostname)) {
+    $hostname = $_SERVER["HTTP_HOST"] ?? null;
+    if (empty($hostname)) {
+      $hostname = gethostname();
+    }
+  }
+
+  return $hostname;
+}
+
 function uuidv4(): string {
   $data = random_bytes(16);
   $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100

+ 2 - 2
react/admin-panel/src/AdminDashboard.jsx

@@ -1,5 +1,5 @@
 import React, {lazy, Suspense, useCallback, useState} from "react";
-import {BrowserRouter, Route, Routes} from "react-router-dom";
+import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom";
 import Dialog from "shared/elements/dialog";
 import Sidebar from "./elements/sidebar";
 import Footer from "./elements/footer";
@@ -73,7 +73,7 @@ export default function AdminDashboard(props) {
                 <section className={"content"}>
                     <Suspense fallback={<div>{L("general.loading")}... </div>}>
                         <Routes>
-                            <Route path={"/admin"} element={<Overview {...controlObj} />}/>
+                            <Route path={"/admin"} element={<Navigate to={"/admin/dashboard"} />}/>
                             <Route path={"/admin/dashboard"} element={<Overview {...controlObj} />}/>
                             <Route path={"/admin/users"} element={<UserListView {...controlObj} />}/>
                             <Route path={"/admin/user/:userId"} element={<UserEditView {...controlObj} />}/>

+ 16 - 12
react/admin-panel/src/views/group/group-edit.js

@@ -1,4 +1,4 @@
-import {useCallback, useContext, useEffect, useState} from "react";
+import {useCallback, useContext, useEffect, useRef, useState} from "react";
 import {Link, useNavigate, useParams} from "react-router-dom";
 import {LocaleContext} from "shared/locale";
 import SearchField from "shared/elements/search-field";
@@ -34,7 +34,7 @@ export default function EditGroupView(props) {
     const [fetchGroup, setFetchGroup] = useState(!isNewGroup);
     const [group, setGroup] = useState(isNewGroup ? defaultGroupData : null);
     const [members, setMembers] = useState([]);
-    const [selectedUser, setSelectedUser] = useState(null);
+    const selectedUserRef = useRef(null);
 
     // ui
     const [dialogData, setDialogData] = useState({open: false});
@@ -86,19 +86,19 @@ export default function EditGroupView(props) {
     }, [api, showDialog, groupId, members]);
 
     const onAddMember = useCallback(() => {
-        if (selectedUser) {
-            api.addGroupMember(groupId, selectedUser.id).then(data => {
+        if (selectedUserRef.current) {
+            api.addGroupMember(groupId, selectedUserRef.current.id).then(data => {
                 if (!data.success) {
                     showDialog(data.msg, L("account.add_group_member_error"));
                 } else {
                     let newMembers = [...members];
-                    newMembers.push(selectedUser);
+                    newMembers.push(selectedUserRef.current);
                     setMembers(newMembers);
                 }
-                setSelectedUser(null);
+                selectedUserRef.current = null;
             });
         }
-    }, [api, showDialog, groupId, selectedUser, members])
+    }, [api, showDialog, groupId, selectedUserRef, members])
 
     const onSave = useCallback(() => {
         setSaving(true);
@@ -152,15 +152,19 @@ export default function EditGroupView(props) {
                     size: "small", key: "search",
                     element: SearchField,
                     onSearch: v => onSearchUser(v),
-                    onSelect: u => setSelectedUser(u),
+                    onSelect: u => { selectedUserRef.current = u },
                     getOptionLabel: u => u.fullName || u.name
                 }
             ],
-            onOption: (option) => option === 0 ?
-                onAddMember() :
-                setSelectedUser(null)
+            onOption: (option) => {
+                if(option === 0) {
+                    onAddMember()
+                    } else {
+                    selectedUserRef.current = null
+                }
+            }
         });
-    }, [onAddMember, onSearchUser, setSelectedUser, setDialogData]);
+    }, [onAddMember, onSearchUser, selectedUserRef, setDialogData]);
 
     useEffect(() => {
         onFetchGroup();

+ 5 - 3
react/admin-panel/src/views/profile/profile.js

@@ -7,7 +7,7 @@ import {
     CircularProgress,
     FormControl,
     FormGroup,
-    FormLabel, Paper, styled,
+    FormLabel, styled,
     TextField
 } from "@mui/material";
 import {
@@ -26,6 +26,7 @@ import ButtonBar from "../../elements/button-bar";
 import MfaTotp from "./mfa-totp";
 import MfaFido from "./mfa-fido";
 import Dialog from "shared/elements/dialog";
+import PasswordStrength from "shared/elements/password-strength";
 
 const GpgKeyField = styled(TextField)((props) => ({
     "& > div": {
@@ -211,8 +212,6 @@ export default function ProfileView(props) {
         reader.readAsText(file);
     }, [showDialog]);
 
-    console.log("SELECTED USER:", profile.twoFactorToken);
-
     return <>
         <div className={"content-header"}>
             <div className={"container-fluid"}>
@@ -285,6 +284,9 @@ export default function ProfileView(props) {
                                    onChange={e => setChangePassword({...changePassword, confirm: e.target.value })} />
                     </FormControl>
                 </ProfileFormGroup>
+                <Box className={"w-50"}>
+                    <PasswordStrength password={changePassword.new} minLength={6} />
+                </Box>
             </CollapseBox>
 
             <CollapseBox title={L("account.gpg_key")} open={openedTab === "gpg"}

+ 58 - 0
react/shared/elements/password-strength.js

@@ -0,0 +1,58 @@
+import {Box, styled} from "@mui/material";
+import {useContext} from "react";
+import {LocaleContext} from "../locale";
+import {sprintf} from "sprintf-js";
+
+const PasswordStrengthBox = styled(Box)((props) => ({
+    textAlign: "center",
+    borderRadius: 5,
+    borderStyle: "solid",
+    borderWidth: 1,
+    borderColor: props.theme.palette.action,
+    padding: props.theme.spacing(0.5),
+    position: "relative",
+    "& > div": {
+        zIndex: 0,
+        position: "absolute",
+        top: 0,
+        left: 0,
+        height: "100%",
+    },
+    "& > span": {
+        zIndex: 1,
+        position: "relative",
+    }
+}));
+
+export default function PasswordStrength(props) {
+
+    const {password, ...other} = props;
+    const {translate: L} = useContext(LocaleContext);
+
+    const ref = 14;
+    let strength = password.length >= ref ? 100 : Math.round(password.length / ref * 100.0);
+    let label = "account.password_very_weak";
+    let bgColor = "red";
+
+    if (strength >= 85) {
+        label = "account.password_very_strong";
+        bgColor = "darkgreen";
+    } else if (strength >= 65) {
+        label = "account.password_strong";
+        bgColor = "green";
+    } else if (strength >= 50) {
+        label = "account.password_ok";
+        bgColor = "yellow";
+    } else if (strength >= 25) {
+        label = "account.password_weak";
+        bgColor = "orange";
+    }
+
+    return <PasswordStrengthBox {...other}>
+        <Box position={"absolute"} sx={{
+            backgroundColor: bgColor,
+            width: sprintf("%d%%", strength),
+        }} />
+        <span>{L(label)}</span>
+    </PasswordStrengthBox>
+}

+ 1 - 0
react/shared/views/login.jsx

@@ -129,6 +129,7 @@ export default function LoginForm(props) {
             return;
         }
 
+        console.log("navigator.credentials.get")
         set2FAToken({ ...tfaToken, step: 1, error: "" });
         navigator.credentials.get({
             publicKey: {