diff --git a/core/src/Employees/User/Api/EmployeesActionManager.php b/core/src/Employees/User/Api/EmployeesActionManager.php index 06b6f0ca..a1779107 100644 --- a/core/src/Employees/User/Api/EmployeesActionManager.php +++ b/core/src/Employees/User/Api/EmployeesActionManager.php @@ -56,9 +56,9 @@ class EmployeesActionManager extends SubActionManager if (!empty($childCompaniesIds)) { $childStructureSubordinates = $obj->Find( - "department in (" . implode(',', $childCompaniesIds) . ") and id != ?", - array($cemp) - ); + "department in (" . implode(',', $childCompaniesIds) . ") and id != ?", + array($cemp) + ); $subordinates = array_merge($subordinates, $childStructureSubordinates); } @@ -147,6 +147,10 @@ class EmployeesActionManager extends SubActionManager return new IceResponse(IceResponse::ERROR, "Error occurred while changing password"); } + if (!PasswordManager::verifyPassword($req->current, $user->password)) { + return new IceResponse(IceResponse::ERROR, "Current password is incorrect"); + } + $passwordStrengthResponse = PasswordManager::isQualifiedPassword($req->pwd); if ($passwordStrengthResponse->getStatus() === IceResponse::ERROR) { return $passwordStrengthResponse; @@ -158,6 +162,6 @@ class EmployeesActionManager extends SubActionManager return new IceResponse(IceResponse::ERROR, $user->ErrorMsg()); } - return new IceResponse(IceResponse::SUCCESS, $user); + return new IceResponse(IceResponse::SUCCESS, []); } } diff --git a/web/api/CustomAction.js b/web/api/CustomAction.js new file mode 100644 index 00000000..d021a9b4 --- /dev/null +++ b/web/api/CustomAction.js @@ -0,0 +1,26 @@ +const axios = require('axios'); + +class CustomAction { + constructor(adapter) { + this.adapter = adapter; + } + + execute(subAction, module, request, isPost) { + if (!isPost) { + return axios.get( + this.adapter.moduleRelativeURL, + { + params: { + t: this.adapter.table, a: 'ca', sa: subAction, mod: module, req: request, + }, + }, + ); + } + + return axios.post(this.moduleRelativeURL, { + t: this.adapter.table, a: 'ca', sa: subAction, mod: module, req: request, + }); + } +} + +export default CustomAction; diff --git a/web/components/UpdatePasswordModal.js b/web/components/UpdatePasswordModal.js new file mode 100644 index 00000000..5c648f25 --- /dev/null +++ b/web/components/UpdatePasswordModal.js @@ -0,0 +1,232 @@ +import React, {Component} from 'react'; +import { + Form, + Modal, + Input, + Button, + message, +} from 'antd'; +import CustomAction from '../api/CustomAction'; + +class UpdatePasswordModal extends React.Component { + + state = { + loading: false, + passwordHasError: false, + passwordState: { hasFeedback: false, validateStatus:'', help:'Password must include at least one number, one lowercase letter, one uppercase letter and a symbol' }, + confirmationHasError: false, + confirmationState: { hasFeedback: false, validateStatus:'', help:'' }, + }; + + constructor(props) { + super(props); + this.formRef = React.createRef(); + this.customAction = new CustomAction(this.props.adapter); + } + + componentDidMount() { + message.config({ + top: 40, + }); + } + + clearConfirmFeedback = () => { + this.setState({confirmationHasError: false}); + this.setState({ + confirmationState: { + hasFeedback : false, + validateStatus:'', + help:'', + } + }); + } + + updatePasswordState(value) { + const passwordValidationResult = this.validatePassword(value); + if (passwordValidationResult !== null) { + this.setState({passwordHasError: true}); + this.setState({ + passwordState: { + hasFeedback : true, + validateStatus:'error', + help:passwordValidationResult, + } + }); + + return false; + } else { + this.setState({passwordHasError: false}); + this.setState({ + passwordState: { + hasFeedback : true, + validateStatus:'success', + help:'', + } + }); + } + + return true; + } + + updateConfirmPasswordState(values) { + if (values.confirm !== values.new) { + this.setState({confirmationHasError: true}); + this.setState({ + confirmationState: { + hasFeedback : true, + validateStatus:'error', + help:'Passwords don\'t match', + } + }); + + return false; + } else { + this.setState({confirmationHasError: false}); + this.setState({ + confirmationState: { + hasFeedback : false, + validateStatus:'', + help:'', + } + }); + } + + return true; + } + + handleOk = () => { + const from = this.formRef.current; + from + .validateFields() + .then((values) => { + if (this.updatePasswordState(values.new) && this.updateConfirmPasswordState(values)) { + this.updatePassword(values.current, values.new) + .then((response) => { + const data = response.data; + console.log(data); + if (data.status === 'SUCCESS') { + this.handleCancel(); + message.success(this.props.adapter.gt('Password updated')); + } else { + message.error( + `${this.props.adapter.gt('Error updating password')}: ${this.props.adapter.gt(data.data)}` + ); + } + }).catch((error) => { + message.error( + `${this.props.adapter.gt('Error updating password')}` + ); + console.log(error.message); + }); + } + }) + .catch((info) => { + this.setState({ loading: false }); + }); + } + + handleCancel = () => { + if (this.formRef.current) { + this.formRef.current.resetFields(); + } + this.props.closeModal(); + } + + updatePassword = (oldPassword, newPassword) => { + const req = { current: oldPassword ? oldPassword : '', pwd: newPassword }; + const reqJson = JSON.stringify(req); + + const callBackData = []; + callBackData.callBackData = []; + callBackData.callBackSuccess = 'changePasswordSuccessCallBack'; + callBackData.callBackFail = 'changePasswordFailCallBack'; + + return this.customAction.execute('changePassword', 'modules=employees', reqJson); + + } + + validatePassword = (password) => { + if (password.length < 8) { + return this.props.adapter.gt('Password too short'); + } + + if (password.length > 20) { + return this.props.adapter.gt('Password too long'); + } + + const numberTester = /.*[0-9]+.*$/; + if (!password.match(numberTester)) { + return this.props.adapter.gt('Password must include at least one number'); + } + + const lowerTester = /.*[a-z]+.*$/; + if (!password.match(lowerTester)) { + return this.props.adapter.gt('Password must include at least one lowercase letter'); + } + + const upperTester = /.*[A-Z]+.*$/; + if (!password.match(upperTester)) { + return this.props.adapter.gt('Password must include at least one uppercase letter'); + } + + const symbolTester = /.*[\W]+.*$/; + if (!password.match(symbolTester)) { + return this.props.adapter.gt('Password must include at least one symbol'); + } + + return null; + } + + render() { + const layout = { + labelCol: { span: 8 }, + wrapperCol: { span: 16 }, + }; + + return ( + + {this.props.adapter.gt('Cancel')} + , + , + ]} + > +
+ + + + { this.state.passwordHasError && + + this.updatePasswordState(event.target.value)}/> + + } + { !this.state.passwordHasError && + + this.updatePasswordState(event.target.value)}/> + + } + { this.state.confirmationHasError && + + this.clearConfirmFeedback()}/> + + } + { !this.state.confirmationHasError && + + this.clearConfirmFeedback()}/> + + } + +
+
+ ) + } +} + +export default UpdatePasswordModal; diff --git a/web/modules/src/employees/components/EmployeeProfile.js b/web/modules/src/employees/components/EmployeeProfile.js index 6c0fa360..19f18b49 100644 --- a/web/modules/src/employees/components/EmployeeProfile.js +++ b/web/modules/src/employees/components/EmployeeProfile.js @@ -1,37 +1,32 @@ -import React, {Component} from 'react'; +import React from 'react'; import { Col, Card, - Badge, Avatar, - Input, Row, Descriptions, Typography, - Table, Space, - Button, Tag, - message, Tabs, - Spin, Skeleton } from 'antd'; import { - FilterOutlined, EditOutlined, PhoneTwoTone, MailTwoTone, SyncOutlined, + LockOutlined, } from '@ant-design/icons'; import TagList from "../../../../components/TagList"; -const { Search } = Input; +import UpdatePasswordModal from "../../../../components/UpdatePasswordModal"; const { Title, Text } = Typography; const { TabPane } = Tabs; class EmployeeProfile extends React.Component { state = { loading: true, + showPasswordResetModal: false, }; constructor(props) { @@ -42,6 +37,10 @@ class EmployeeProfile extends React.Component { this.setState({ loading: value }); } + setShowPasswordUpdate(value) { + this.setState({ showPasswordResetModal: value }); + } + updateProfileImage() { showUploadDialog( `profile_image_${this.props.element.id}_${(new Date()).getTime()}`, @@ -71,6 +70,33 @@ class EmployeeProfile extends React.Component { ); } + getEditButtonJsxWithPassword() { + return (<> + {this.state.loading && + } color="processing"> + {this.props.adapter.gt('Edit')} + + } + {!this.state.loading && + } color="processing" + onClick={() => modJs.edit(this.props.element.id)}> + {this.props.adapter.gt('Edit')} + + } + } color="volcano" onClick={() => this.setShowPasswordUpdate(true)}> + {this.props.adapter.gt('Update Password')} + + ); + } + + getUpdatePasswordButtonJsx() { + return (<> + } color="processing"> + {this.props.adapter.gt('Update Password')} + + ); + } + getTabViewEmployeeFilterButtonJsx(tab) { return ( } color="processing" @@ -86,10 +112,15 @@ class EmployeeProfile extends React.Component { } return ( <> + {this.setState({ showPasswordResetModal: false })}} + adapter={this.props.adapter} + />