current hostname as cookie domain, group edit member bugfix

This commit is contained in:
Roman 2024-04-09 14:59:19 +02:00
parent 6c551b08d8
commit 72d2850e83
8 changed files with 98 additions and 27 deletions

@ -84,15 +84,8 @@ class Settings {
} }
public static function loadDefaults(): 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(); $protocol = getProtocol();
$hostname = getHostName();
$settings = new Settings(); $settings = new Settings();
// General // General

@ -92,7 +92,8 @@ class Context {
public function sendCookies(): void { 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? // 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->language->sendCookie($domain);
$this->session?->sendCookie($domain); $this->session?->sendCookie($domain);
$this->session?->update(); $this->session?->update();
@ -202,7 +203,7 @@ class Context {
return $this->language; return $this->language;
} }
public function invalidateSessions(bool $keepCurrent = true): bool { public function invalidateSessions(bool $keepCurrent = false): bool {
$query = $this->sql->update("Session") $query = $this->sql->update("Session")
->set("active", false) ->set("active", false)
->whereEq("user_id", $this->user->getId()); ->whereEq("user_id", $this->user->getId());

@ -36,6 +36,18 @@ function getProtocol(): string {
return $isSecure ? 'https' : 'http'; 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 { function uuidv4(): string {
$data = random_bytes(16); $data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100 $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100

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

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

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

@ -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>
}

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