1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-04 16:31:13 +00:00

set-password-strength (#6477)

* set-password-strength

* update-uni-test

* Update views.py

* update

* remove userless code

* Update settings.py
This commit is contained in:
Ranjiwei
2024-08-05 12:10:36 +08:00
committed by GitHub
parent 8a3470e45f
commit 9b3492088c
31 changed files with 1008 additions and 181 deletions

View File

@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { FormGroup, Label, InputGroup, Input, InputGroupAddon, Button } from 'reactstrap';
import classnames from 'classnames';
import PasswordStrengthChecker from './password-strength-checker';
import { isMobile } from '../../../utils/utils';
import '../../../css/password-input.css';
const propTypes = {
value: PropTypes.string,
labelValue: PropTypes.string,
enableCheckStrength: PropTypes.bool,
onChangeValue: PropTypes.func,
};
const PasswordInput = ({ value, labelValue, enableCheckStrength, onChangeValue }) => {
const [passwordValue, setPasswordValue] = useState(value || '');
const [isShowPassword, setIsShowPassword] = useState(false);
const [isShowChecker, setIsShowChecker] = useState(false);
const changePasswordValue = (e) => {
const updatedValue = e.target.value;
onChangeValue(updatedValue);
setPasswordValue(updatedValue);
};
const handleFocus = () => {
if (enableCheckStrength) {
setIsShowChecker(true);
}
};
const handleBlur = () => {
if (enableCheckStrength) {
setIsShowChecker(false);
}
};
return (
<FormGroup className={classnames('password-input-container position-relative', { 'mobile': isMobile })}>
<Label>{labelValue}</Label>
<InputGroup className='password'>
<Input
type={isShowPassword ? 'text' : 'password'}
value={value}
onChange={changePasswordValue}
onFocus={handleFocus}
onBlur={handleBlur}
/>
{isShowChecker && enableCheckStrength && (
<PasswordStrengthChecker
passwordValue={passwordValue}
/>
)}
<InputGroupAddon addonType="append">
<Button onClick={() => setIsShowPassword(!isShowPassword)}>
<i className={`password-icon sf3-font sf3-font-eye${isShowPassword ? '' : '-slash'}`} />
</Button>
</InputGroupAddon>
</InputGroup>
</FormGroup>
);
};
PasswordInput.propTypes = propTypes;
PasswordInput.defaultProps = { enableCheckStrength: true };
export default PasswordInput;

View File

@@ -0,0 +1,51 @@
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { Progress } from 'reactstrap';
import { gettext } from '../../../utils/constants';
import { evaluatePasswordStrength } from '../../../utils/utils';
const propTypes = {
passwordValue: PropTypes.string.isRequired,
};
const PASSWORD_STRENGTH_VALUES = {
empty: { classNames: ['default', 'default', 'default', 'default'], textValue: '' },
too_short: { classNames: ['too-short', 'default', 'default', 'default'], textValue: 'too short' },
weak: { classNames: ['weak', 'default', 'default', 'default'], textValue: 'weak' },
medium: { classNames: ['medium', 'medium', 'default', 'default'], textValue: 'medium' },
strong: { classNames: ['strong', 'strong', 'strong', 'default'], textValue: 'strong' },
very_strong: { classNames: ['very-strong', 'very-strong', 'very-strong', 'very-strong'], textValue: 'very strong' },
};
const PasswordStrengthChecker = ({ passwordValue }) => {
const { classNames: progressClassNames = [], textValue = '' } = useMemo(() => PASSWORD_STRENGTH_VALUES[evaluatePasswordStrength(passwordValue)] || {}, [passwordValue]);
const labelClassName = Array.isArray(progressClassNames) && progressClassNames.length > 0 ? progressClassNames[0] : '';
return (
<div className="password-strength-check-container">
<div className='password-strength-check-box'>
<div className="password-strength-value">
<span>{gettext('Password strength')}: </span>
<span className={labelClassName}>{gettext(textValue)}</span>
</div>
<Progress multi>
{progressClassNames.map((className, index) => (
<Progress
bar
key={index}
className={className}
value='25'
/>
))}
</Progress>
<div className='password-strength-description'>
<span>{gettext('Password must be at least 8 characters long and contain different characters: uppercase letters, lowercase letters, numbers, and special symbols')}</span>
</div>
</div>
</div>
);
};
PasswordStrengthChecker.propTypes = propTypes;
export default PasswordStrengthChecker;

View File

@@ -0,0 +1,81 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, Alert } from 'reactstrap';
import toaster from '../../toast';
import PasswordInput from './password-input';
import { userAPI } from '../../../utils/user-api';
import { gettext } from '../../../utils/constants';
import { Utils, validatePassword } from '../../../utils/utils';
const propTypes = {
toggle: PropTypes.func,
};
const UserSetPassword = ({ toggle }) => {
const [password, setPassword] = useState('');
const [confirmedPassword, setConfirmedPassword] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [canSubmit, setCanSubmit] = useState(true);
const submitPassword = () => {
if (!password) {
setErrorMessage(gettext('Password cannot be blank'));
return;
}
if (!confirmedPassword) {
setErrorMessage(gettext('Please enter the password again'));
return;
}
if (password !== confirmedPassword) {
setErrorMessage(gettext('Passwords don\'t match'));
return;
}
if (!validatePassword(password)) {
setErrorMessage(gettext('Insufficient password strength'));
return;
}
setErrorMessage('');
setCanSubmit(false);
userAPI.resetPassword(null, password).then(() => {
toaster.success('Password set');
location.reload();
toggle();
}).catch(error => {
const errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
setCanSubmit(true);
});
};
return (
<Modal centered={true} isOpen={true} toggle={toggle}>
<ModalHeader toggle={toggle}>{gettext('Set password')}</ModalHeader>
<ModalBody>
<Form>
<PasswordInput
value={password}
labelValue={gettext('Password')}
onChangeValue={setPassword}
/>
<PasswordInput
value={confirmedPassword}
labelValue={gettext('Confirm password')}
onChangeValue={setConfirmedPassword}
/>
</Form>
{errorMessage && (
<Alert color='danger'>{errorMessage}</Alert>
)}
</ModalBody>
<ModalFooter>
<Button color='secondary' onClick={toggle}>{gettext('Cancel')}</Button>
<Button color="primary" disabled={!canSubmit} onClick={submitPassword}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
};
UserSetPassword.propTypes = propTypes;
export default UserSetPassword;

View File

@@ -0,0 +1,93 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, Alert } from 'reactstrap';
import toaster from '../../toast';
import PasswordInput from './password-input';
import { userAPI } from '../../../utils/user-api';
import { gettext } from '../../../utils/constants';
import { Utils, validatePassword } from '../../../utils/utils';
const propTypes = {
toggle: PropTypes.func,
};
const UserUpdatePassword = ({ toggle }) => {
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmedNewPassword, setConfirmedNewPassword] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [canSubmit, setCanSubmit] = useState(true);
const updatePassword = () => {
if (!currentPassword) {
setErrorMessage(gettext('Current password cannot be blank'));
return;
}
if (!newPassword) {
setErrorMessage(gettext('Password cannot be blank'));
return;
}
if (!confirmedNewPassword) {
setErrorMessage(gettext('Please enter the password again'));
return;
}
if (newPassword !== confirmedNewPassword) {
setErrorMessage(gettext('Passwords don\'t match'));
return;
}
if (currentPassword === newPassword) {
setErrorMessage(gettext('New password cannot be the same as old password'));
}
if (!validatePassword(newPassword)) {
setErrorMessage(gettext('Insufficient password strength'));
return;
}
setErrorMessage('');
setCanSubmit(false);
userAPI.resetPassword(currentPassword, newPassword).then(() => {
toaster.success('Password updated');
toggle();
}).catch(error => {
const errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
setCanSubmit(true);
});
};
return (
<Modal centered={true} isOpen={true} toggle={toggle}>
<ModalHeader toggle={toggle}>{gettext('Update password')}</ModalHeader>
<ModalBody>
<Form>
<PasswordInput
value={currentPassword}
labelValue={gettext('Current password')}
enableCheckStrength={false}
onChangeValue={setCurrentPassword}
/>
<PasswordInput
value={newPassword}
labelValue={gettext('New password')}
onChangeValue={setNewPassword}
/>
<PasswordInput
value={confirmedNewPassword}
labelValue={gettext('Confirm password')}
onChangeValue={setConfirmedNewPassword}
/>
</Form>
{errorMessage && (
<Alert color='danger'>{errorMessage}</Alert>
)}
</ModalBody>
<ModalFooter>
<Button color='secondary' onClick={toggle}>{gettext('Cancel')}</Button>
<Button color="primary" disabled={!canSubmit} onClick={updatePassword}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
};
UserUpdatePassword.propTypes = propTypes;
export default UserUpdatePassword;