From 76cd92ee0ea50dc4d3237ac7188bac56020f179b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 3 May 2024 19:05:43 +0200 Subject: [PATCH] Captcha trait --- Core/API/Traits/Captcha.trait.php | 30 ++++++ Core/API/UserAPI.class.php | 70 +++++++------ Core/Localization/de_DE/logs.php | 2 +- react/admin-panel/src/views/user/user-edit.js | 99 ++++++++++++++++++- 4 files changed, 162 insertions(+), 39 deletions(-) create mode 100644 Core/API/Traits/Captcha.trait.php diff --git a/Core/API/Traits/Captcha.trait.php b/Core/API/Traits/Captcha.trait.php new file mode 100644 index 0000000..23eb4c3 --- /dev/null +++ b/Core/API/Traits/Captcha.trait.php @@ -0,0 +1,30 @@ +context->getSettings(); + if ($settings->isCaptchaEnabled()) { + $parameters["captcha"] = new StringType("captcha"); + } + } + + function checkCaptcha(string $action): bool { + $settings = $this->context->getSettings(); + if ($settings->isCaptchaEnabled()) { + $captcha = $this->getParam("captcha"); + $req = new VerifyCaptcha($this->context); + if (!$req->execute(array("captcha" => $captcha, "action" => $action))) { + return $this->createError($req->getLastError()); + } + } + + return true; + } +} \ No newline at end of file diff --git a/Core/API/UserAPI.class.php b/Core/API/UserAPI.class.php index 17def1d..c0b3d11 100644 --- a/Core/API/UserAPI.class.php +++ b/Core/API/UserAPI.class.php @@ -124,9 +124,9 @@ namespace Core\API\User { use Core\API\Parameter\Parameter; use Core\API\Parameter\StringType; use Core\API\Template\Render; + use Core\API\Traits\Captcha; use Core\API\Traits\Pagination; use Core\API\UserAPI; - use Core\API\VerifyCaptcha; use Core\Driver\SQL\Condition\CondBool; use Core\Driver\SQL\Condition\CondLike; use Core\Driver\SQL\Condition\CondOr; @@ -716,6 +716,8 @@ namespace Core\API\User { class Register extends UserAPI { + use Captcha; + public function __construct(Context $context, bool $externalCall = false) { $parameters = array( "username" => new StringType("username", 32), @@ -724,10 +726,7 @@ namespace Core\API\User { "confirmPassword" => new StringType("confirmPassword"), ); - $settings = $context->getSettings(); - if ($settings->isCaptchaEnabled()) { - $parameters["captcha"] = new StringType("captcha"); - } + $this->addCaptchaParameters($parameters); parent::__construct($context, $externalCall, $parameters); $this->csrfTokenRequired = false; @@ -745,12 +744,8 @@ namespace Core\API\User { return $this->createError("User Registration is not enabled."); } - if ($settings->isCaptchaEnabled()) { - $captcha = $this->getParam("captcha"); - $req = new VerifyCaptcha($this->context); - if (!$req->execute(array("captcha" => $captcha, "action" => "register"))) { - return $this->createError($req->getLastError()); - } + if (!$this->checkCaptcha("register")) { + return false; } $username = $this->getParam("username"); @@ -840,7 +835,8 @@ namespace Core\API\User { 'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL), 'password' => new StringType('password', -1, true, NULL), 'groups' => new ArrayType('groups', Parameter::TYPE_INT, true, true, NULL), - 'confirmed' => new Parameter('confirmed', Parameter::TYPE_BOOLEAN, true, NULL) + 'confirmed' => new Parameter('confirmed', Parameter::TYPE_BOOLEAN, true, NULL), + 'active' => new Parameter('active', Parameter::TYPE_BOOLEAN, true, NULL) )); $this->loginRequired = true; @@ -865,6 +861,7 @@ namespace Core\API\User { $password = $this->getParam("password"); $groups = $this->getParam("groups"); $confirmed = $this->getParam("confirmed"); + $active = $this->getParam("active"); $email = (!is_null($email) && empty($email)) ? null : $email; @@ -918,13 +915,22 @@ namespace Core\API\User { if (!is_null($confirmed)) { if ($id === $currentUser->getId() && $confirmed === false) { - return $this->createError("Cannot make own account unconfirmed."); + return $this->createError("Cannot change confirmed flag on own account."); } else { $user->confirmed = $confirmed; $columnsToUpdate[] = "confirmed"; } } + if (!is_null($active)) { + if ($id === $currentUser->getId() && $active === false) { + return $this->createError("Cannot change active flag on own account."); + } else { + $user->active = $active; + $columnsToUpdate[] = "active"; + } + } + if (empty($columnsToUpdate) || $user->save($sql, $columnsToUpdate)) { $deleteQuery = $sql->delete("UserGroup")->whereEq("user_id", $id); @@ -995,16 +1001,15 @@ namespace Core\API\User { } class RequestPasswordReset extends UserAPI { + + use Captcha; + public function __construct(Context $context, $externalCall = false) { - $parameters = array( + $parameters = [ 'email' => new Parameter('email', Parameter::TYPE_EMAIL), - ); - - $settings = $context->getSettings(); - if ($settings->isCaptchaEnabled()) { - $parameters["captcha"] = new StringType("captcha"); - } + ]; + $this->addCaptchaParameters($parameters); parent::__construct($context, $externalCall, $parameters); } @@ -1019,12 +1024,8 @@ namespace Core\API\User { return $this->createError("The mail service is not enabled, please contact the server administration."); } - if ($settings->isCaptchaEnabled()) { - $captcha = $this->getParam("captcha"); - $req = new VerifyCaptcha($this->context); - if (!$req->execute(array("captcha" => $captcha, "action" => "resetPassword"))) { - return $this->createError($req->getLastError()); - } + if (!$this->checkCaptcha("resetPassword")) { + return false; } $sql = $this->context->getSQL(); @@ -1088,16 +1089,15 @@ namespace Core\API\User { } class ResendConfirmEmail extends UserAPI { + + use Captcha; + public function __construct(Context $context, $externalCall = false) { $parameters = array( 'email' => new Parameter('email', Parameter::TYPE_EMAIL), ); - $settings = $context->getSettings(); - if ($settings->isCaptchaEnabled()) { - $parameters["captcha"] = new StringType("captcha"); - } - + $this->addCaptchaParameters($parameters); parent::__construct($context, $externalCall, $parameters); } @@ -1108,12 +1108,8 @@ namespace Core\API\User { } $settings = $this->context->getSettings(); - if ($settings->isCaptchaEnabled()) { - $captcha = $this->getParam("captcha"); - $req = new VerifyCaptcha($this->context); - if (!$req->execute(array("captcha" => $captcha, "action" => "resendConfirmation"))) { - return $this->createError($req->getLastError()); - } + if (!$this->checkCaptcha("resendConfirmation")) { + return false; } $email = $this->getParam("email"); diff --git a/Core/Localization/de_DE/logs.php b/Core/Localization/de_DE/logs.php index d6df6c5..fd3eddc 100644 --- a/Core/Localization/de_DE/logs.php +++ b/Core/Localization/de_DE/logs.php @@ -9,7 +9,7 @@ return [ "search" => "Suche", "search_query" => "Suchanfrage", "no_entries_placeholder" => "Keine Log-Einträge zum Anzeigen", - "timestamp_placeholder" => "Datum und Zeitpunk Auswählen zum Filtern", + "timestamp_placeholder" => "Datum und Zeitpunkt auswählen zum Filtern", "hide_details" => "Details verstecken", "show_details" => "Details zeigen", diff --git a/react/admin-panel/src/views/user/user-edit.js b/react/admin-panel/src/views/user/user-edit.js index 08f46b9..0ab6b86 100644 --- a/react/admin-panel/src/views/user/user-edit.js +++ b/react/admin-panel/src/views/user/user-edit.js @@ -1,15 +1,32 @@ import {Link, useNavigate, useParams} from "react-router-dom"; import {useCallback, useContext, useEffect, useState} from "react"; -import {CircularProgress} from "@mui/material"; +import { + Box, + Button, + Checkbox, + CircularProgress, + FormControl, + FormControlLabel, + FormLabel, + TextField +} from "@mui/material"; import {LocaleContext} from "shared/locale"; import * as React from "react"; import ViewContent from "../../elements/view-content"; +import FormGroup from "../../elements/form-group"; +import ButtonBar from "../../elements/button-bar"; +import {RestartAlt, Save} from "@mui/icons-material"; +import {parseBool} from "shared/util"; +import SpacedFormGroup from "../../elements/form-group"; export default function UserEditView(props) { + // meta const { api, showDialog } = props; const { userId } = useParams(); const navigate = useNavigate(); + + // data const isNewUser = userId === "new"; const {translate: L, requestModules, currentLocale} = useContext(LocaleContext); const [fetchUser, setFetchUser] = useState(!isNewUser); @@ -20,8 +37,13 @@ export default function UserEditView(props) { password: "", groups: [], confirmed: false, + active: true, } : null); + // ui + const [hasChanged, setChanged] = useState(isNewUser); + const [isSaving, setSaving] = useState(false); + useEffect(() => { requestModules(props.api, ["general", "account"], currentLocale).then(data => { if (!data.success) { @@ -30,6 +52,14 @@ export default function UserEditView(props) { }); }, [currentLocale]); + const onReset = useCallback(() => { + + }, []); + + const onSaveUser = useCallback(() => { + + }, []); + const onFetchUser = useCallback((force = false) => { if (!isNewUser && (force || fetchUser)) { setFetchUser(false); @@ -46,6 +76,10 @@ export default function UserEditView(props) { } }, [api, showDialog, fetchUser, isNewUser, userId, user]); + const onChangeValue = useCallback((name, value) => { + + }, []); + useEffect(() => { if (!isNewUser) { onFetchUser(true); @@ -61,6 +95,69 @@ export default function UserEditView(props) { User, {isNewUser ? "New" : "Edit"} ]}> + + + {L("account.name")} + + setUser({...user, name: e.target.value})} /> + + + + {L("account.full_name")} + + setUser({...user, fullName: e.target.value})} /> + + + + {L("account.email")} + + setUser({...user, email: e.target.value})} /> + + + { !isNewUser ? + <> + + onChangeValue("active", v)} />} + label={L("account.active")} /> + + + onChangeValue("confirmed", v)} />} + label={L("account.confirmed")} /> + + : <> + + + } + + + + + } \ No newline at end of file