mirror of
https://github.com/plankanban/planka.git
synced 2025-07-22 14:49:43 +02:00
parent
543a992d98
commit
3df07c10fa
14 changed files with 134 additions and 65 deletions
13
client/package-lock.json
generated
13
client/package-lock.json
generated
|
@ -44,7 +44,8 @@
|
|||
"semantic-ui-react": "^2.1.3",
|
||||
"socket.io-client": "^2.3.1",
|
||||
"validator": "^13.7.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
"whatwg-fetch": "^3.6.2",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.7",
|
||||
|
@ -25310,6 +25311,11 @@
|
|||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/zxcvbn": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
|
||||
"integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -43805,6 +43811,11 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
||||
"integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA=="
|
||||
},
|
||||
"zxcvbn": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
|
||||
"integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,8 @@
|
|||
"semantic-ui-react": "^2.1.3",
|
||||
"socket.io-client": "^2.3.1",
|
||||
"validator": "^13.7.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
"whatwg-fetch": "^3.6.2",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.7",
|
||||
|
|
|
@ -142,19 +142,16 @@ const UserAddStep = React.memo(
|
|||
onChange={handleFieldChange}
|
||||
/>
|
||||
<div className={styles.text}>{t('common.password')}</div>
|
||||
<div className={styles.field}>
|
||||
<Input.Password
|
||||
withStrengthBar
|
||||
fluid
|
||||
ref={passwordField}
|
||||
name="password"
|
||||
value={data.password}
|
||||
readOnly={isSubmitting}
|
||||
className={styles.field}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
<div className={styles.note}>
|
||||
{t('common.mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber')}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.text}>{t('common.name')}</div>
|
||||
<Input
|
||||
fluid
|
||||
|
|
|
@ -113,18 +113,15 @@ const UserPasswordEditStep = React.memo(
|
|||
)}
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div className={styles.text}>{t('common.newPassword')}</div>
|
||||
<div className={styles.field}>
|
||||
<Input.Password
|
||||
withStrengthBar
|
||||
fluid
|
||||
ref={passwordField}
|
||||
name="password"
|
||||
value={data.password}
|
||||
className={styles.field}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
<div className={styles.note}>
|
||||
{t('common.mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber')}
|
||||
</div>
|
||||
</div>
|
||||
{usePasswordConfirmation && (
|
||||
<>
|
||||
<div className={styles.text}>{t('common.currentPassword')}</div>
|
||||
|
|
|
@ -1,22 +1,74 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { Icon, Input } from 'semantic-ui-react';
|
||||
import zxcvbn from 'zxcvbn';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon, Input, Progress } from 'semantic-ui-react';
|
||||
import { useToggle } from '../../../hooks';
|
||||
|
||||
const InputPassword = React.forwardRef((props, ref) => {
|
||||
import styles from './InputPassword.module.css';
|
||||
|
||||
const STRENGTH_SCORE_COLORS = ['red', 'orange', 'yellow', 'olive', 'green'];
|
||||
|
||||
const InputPassword = React.forwardRef(
|
||||
({ value, withStrengthBar, minStrengthScore, className, ...props }, ref) => {
|
||||
const [isVisible, toggleVisible] = useToggle();
|
||||
|
||||
const strengthScore = useMemo(() => {
|
||||
if (!withStrengthBar) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return zxcvbn(value).score;
|
||||
}, [value, withStrengthBar]);
|
||||
|
||||
const handleToggleClick = useCallback(() => {
|
||||
toggleVisible();
|
||||
}, [toggleVisible]);
|
||||
|
||||
const inputProps = {
|
||||
...props,
|
||||
ref,
|
||||
type: isVisible ? 'text' : 'password',
|
||||
icon: <Icon link name={isVisible ? 'eye' : 'eye slash'} onClick={handleToggleClick} />,
|
||||
};
|
||||
|
||||
if (!withStrengthBar) {
|
||||
return (
|
||||
<Input
|
||||
{...props} // eslint-disable-line react/jsx-props-no-spreading
|
||||
ref={ref}
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
icon={<Icon link name={isVisible ? 'eye' : 'eye slash'} onClick={handleToggleClick} />}
|
||||
{...inputProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Input
|
||||
{...inputProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
error={!!value && strengthScore < minStrengthScore}
|
||||
/>
|
||||
<Progress
|
||||
value={value ? strengthScore + 1 : 0}
|
||||
total={5}
|
||||
color={STRENGTH_SCORE_COLORS[strengthScore]}
|
||||
size="tiny"
|
||||
className={styles.strengthBar}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
InputPassword.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
withStrengthBar: PropTypes.bool,
|
||||
minStrengthScore: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
InputPassword.defaultProps = {
|
||||
withStrengthBar: false,
|
||||
minStrengthScore: 2,
|
||||
className: undefined,
|
||||
};
|
||||
|
||||
export default React.memo(InputPassword);
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.strengthBar {
|
||||
margin: 4px 0 0 !important;
|
||||
opacity: 0.5;
|
||||
}
|
10
client/src/lib/custom-ui/styles.css
vendored
10
client/src/lib/custom-ui/styles.css
vendored
|
@ -14453,11 +14453,11 @@ img.ui.bordered.image {
|
|||
---------------------*/
|
||||
|
||||
.ui.input.error > input {
|
||||
background-color: #fff6f6;
|
||||
border-color: #e0b4b4;
|
||||
color: #9f3a38;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
background-color: #fff6f6 !important;
|
||||
border-color: #e0b4b4 !important;
|
||||
color: #9f3a38 !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Error Placeholder */
|
||||
|
|
|
@ -105,8 +105,6 @@ export default {
|
|||
members: 'Members',
|
||||
minutes: 'Minutes',
|
||||
moveCard_title: 'Move Card',
|
||||
mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber:
|
||||
'Must be at least 6 characters long and contain at least one letter and number',
|
||||
name: 'Name',
|
||||
newEmail: 'New e-mail',
|
||||
newPassword: 'New password',
|
||||
|
|
|
@ -100,8 +100,6 @@ export default {
|
|||
members: 'Участники',
|
||||
minutes: 'Минуты',
|
||||
moveCard: 'Перемещение карточки',
|
||||
mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber:
|
||||
'Должен быть не менее 6 символов и содержать хотя бы одну букву и цифру',
|
||||
name: 'Имя',
|
||||
newEmail: 'Новый e-mail',
|
||||
newPassword: 'Новый пароль',
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
const PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d).+$/;
|
||||
import zxcvbn from 'zxcvbn';
|
||||
|
||||
const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/;
|
||||
|
||||
export const isPassword = (string) => {
|
||||
return string.length >= 6 && PASSWORD_REGEX.test(string);
|
||||
};
|
||||
export const isPassword = (string) => zxcvbn(string).score >= 2; // TODO: move to config
|
||||
|
||||
export const isUsername = (string) => {
|
||||
return string.length >= 3 && string.length <= 16 && USERNAME_REGEX.test(string);
|
||||
};
|
||||
export const isUsername = (string) =>
|
||||
string.length >= 3 && string.length <= 16 && USERNAME_REGEX.test(string);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const zxcvbn = require('zxcvbn');
|
||||
|
||||
const Errors = {
|
||||
EMAIL_ALREADY_IN_USE: {
|
||||
emailAlreadyInUse: 'Email already in use',
|
||||
|
@ -16,8 +18,7 @@ module.exports = {
|
|||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
minLength: 6,
|
||||
regex: /^(?=.*[A-Za-z])(?=.*\d).+$/,
|
||||
custom: (value) => zxcvbn(value).score >= 2, // TODO: move to config
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const bcrypt = require('bcrypt');
|
||||
const zxcvbn = require('zxcvbn');
|
||||
|
||||
const Errors = {
|
||||
USER_NOT_FOUND: {
|
||||
|
@ -18,8 +19,7 @@ module.exports = {
|
|||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
minLength: 6,
|
||||
regex: /^(?=.*[A-Za-z])(?=.*\d).+$/,
|
||||
custom: (value) => zxcvbn(value).score >= 2, // TODO: move to config
|
||||
required: true,
|
||||
},
|
||||
currentPassword: {
|
||||
|
|
13
server/package-lock.json
generated
13
server/package-lock.json
generated
|
@ -24,7 +24,8 @@
|
|||
"stream-to-array": "^2.3.0",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.7.0",
|
||||
"winston": "^3.8.1"
|
||||
"winston": "^3.8.1",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.6",
|
||||
|
@ -7984,6 +7985,11 @@
|
|||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zxcvbn": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
|
||||
"integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -14244,6 +14250,11 @@
|
|||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"zxcvbn": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
|
||||
"integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,8 @@
|
|||
"stream-to-array": "^2.3.0",
|
||||
"uuid": "^8.3.2",
|
||||
"validator": "^13.7.0",
|
||||
"winston": "^3.8.1"
|
||||
"winston": "^3.8.1",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.6",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue