Implement password change for employee profile

This commit is contained in:
Alan Cell
2020-11-06 19:38:48 +01:00
parent d986a2b5bb
commit 5cd7963f6f
4 changed files with 307 additions and 14 deletions

View File

@@ -56,9 +56,9 @@ class EmployeesActionManager extends SubActionManager
if (!empty($childCompaniesIds)) { if (!empty($childCompaniesIds)) {
$childStructureSubordinates $childStructureSubordinates
= $obj->Find( = $obj->Find(
"department in (" . implode(',', $childCompaniesIds) . ") and id != ?", "department in (" . implode(',', $childCompaniesIds) . ") and id != ?",
array($cemp) array($cemp)
); );
$subordinates = array_merge($subordinates, $childStructureSubordinates); $subordinates = array_merge($subordinates, $childStructureSubordinates);
} }
@@ -147,6 +147,10 @@ class EmployeesActionManager extends SubActionManager
return new IceResponse(IceResponse::ERROR, "Error occurred while changing password"); 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); $passwordStrengthResponse = PasswordManager::isQualifiedPassword($req->pwd);
if ($passwordStrengthResponse->getStatus() === IceResponse::ERROR) { if ($passwordStrengthResponse->getStatus() === IceResponse::ERROR) {
return $passwordStrengthResponse; return $passwordStrengthResponse;
@@ -158,6 +162,6 @@ class EmployeesActionManager extends SubActionManager
return new IceResponse(IceResponse::ERROR, $user->ErrorMsg()); return new IceResponse(IceResponse::ERROR, $user->ErrorMsg());
} }
return new IceResponse(IceResponse::SUCCESS, $user); return new IceResponse(IceResponse::SUCCESS, []);
} }
} }

26
web/api/CustomAction.js Normal file
View File

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

View File

@@ -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 (
<Modal
visible={this.props.visible}
title="Update Password"
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
{this.props.adapter.gt('Cancel')}
</Button>,
<Button key="submit" type="primary" loading={this.state.loading} onClick={this.handleOk}>
{this.props.adapter.gt('Update')}
</Button>,
]}
>
<Form {...layout} ref={this.formRef}>
<Form.Item label="Current Password" key="current" name="current" >
<Input.Password placeholder="current password"/>
</Form.Item>
{ this.state.passwordHasError &&
<Form.Item label="New Password" key="new" name="new" {...this.state.passwordState}>
<Input.Password placeholder="new password" onChange={(event) => this.updatePasswordState(event.target.value)}/>
</Form.Item>
}
{ !this.state.passwordHasError &&
<Form.Item label="New Password" key="new" name="new" {...this.state.passwordState}>
<Input.Password placeholder="new password" onChange={(event) => this.updatePasswordState(event.target.value)}/>
</Form.Item>
}
{ this.state.confirmationHasError &&
<Form.Item label="Confirm Password" key="confirm" name="confirm" {...this.state.confirmationState}>
<Input.Password placeholder="confirm password" onChange={(event) => this.clearConfirmFeedback()}/>
</Form.Item>
}
{ !this.state.confirmationHasError &&
<Form.Item label="Confirm Password" key="confirm" name="confirm" >
<Input.Password placeholder="confirm password" onChange={(event) => this.clearConfirmFeedback()}/>
</Form.Item>
}
</Form>
</Modal>
)
}
}
export default UpdatePasswordModal;

View File

@@ -1,37 +1,32 @@
import React, {Component} from 'react'; import React from 'react';
import { import {
Col, Col,
Card, Card,
Badge,
Avatar, Avatar,
Input,
Row, Row,
Descriptions, Descriptions,
Typography, Typography,
Table,
Space, Space,
Button,
Tag, Tag,
message,
Tabs, Tabs,
Spin,
Skeleton Skeleton
} from 'antd'; } from 'antd';
import { import {
FilterOutlined,
EditOutlined, EditOutlined,
PhoneTwoTone, PhoneTwoTone,
MailTwoTone, MailTwoTone,
SyncOutlined, SyncOutlined,
LockOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import TagList from "../../../../components/TagList"; import TagList from "../../../../components/TagList";
const { Search } = Input; import UpdatePasswordModal from "../../../../components/UpdatePasswordModal";
const { Title, Text } = Typography; const { Title, Text } = Typography;
const { TabPane } = Tabs; const { TabPane } = Tabs;
class EmployeeProfile extends React.Component { class EmployeeProfile extends React.Component {
state = { state = {
loading: true, loading: true,
showPasswordResetModal: false,
}; };
constructor(props) { constructor(props) {
@@ -42,6 +37,10 @@ class EmployeeProfile extends React.Component {
this.setState({ loading: value }); this.setState({ loading: value });
} }
setShowPasswordUpdate(value) {
this.setState({ showPasswordResetModal: value });
}
updateProfileImage() { updateProfileImage() {
showUploadDialog( showUploadDialog(
`profile_image_${this.props.element.id}_${(new Date()).getTime()}`, `profile_image_${this.props.element.id}_${(new Date()).getTime()}`,
@@ -71,6 +70,33 @@ class EmployeeProfile extends React.Component {
</>); </>);
} }
getEditButtonJsxWithPassword() {
return (<>
{this.state.loading &&
<Tag icon={<SyncOutlined spin/>} color="processing">
{this.props.adapter.gt('Edit')}
</Tag>
}
{!this.state.loading &&
<Tag icon={<EditOutlined/>} color="processing"
onClick={() => modJs.edit(this.props.element.id)}>
{this.props.adapter.gt('Edit')}
</Tag>
}
<Tag icon={<LockOutlined/>} color="volcano" onClick={() => this.setShowPasswordUpdate(true)}>
{this.props.adapter.gt('Update Password')}
</Tag>
</>);
}
getUpdatePasswordButtonJsx() {
return (<>
<Tag icon={<SyncOutlined spin/>} color="processing">
{this.props.adapter.gt('Update Password')}
</Tag>
</>);
}
getTabViewEmployeeFilterButtonJsx(tab) { getTabViewEmployeeFilterButtonJsx(tab) {
return ( return (
<Tag icon={<EditOutlined/>} color="processing" <Tag icon={<EditOutlined/>} color="processing"
@@ -86,10 +112,15 @@ class EmployeeProfile extends React.Component {
} }
return ( return (
<> <>
<UpdatePasswordModal
visible={this.state.showPasswordResetModal}
closeModal={() => {this.setState({ showPasswordResetModal: false })}}
adapter={this.props.adapter}
/>
<Row direction="vertical" style={{width: '100%', padding: '10px'}} gutter={24}> <Row direction="vertical" style={{width: '100%', padding: '10px'}} gutter={24}>
<Col span={24}> <Col span={24}>
<Card title={this.props.adapter.gt('Employee Profile')} <Card title={this.props.adapter.gt('Employee Profile')}
extra={this.getEditButtonJsx()} extra={this.getEditButtonJsxWithPassword()}
style={{ width: '100%' }} style={{ width: '100%' }}
> >
<Space size={'large'}> <Space size={'large'}>