Add hyphen and underscore as valid password symbols, increase valid password to 6 chars, add password validity and strength indicator

This commit is contained in:
artur 2024-02-14 09:55:11 +03:00
parent 8d205d9d93
commit 0b56813ece
2 changed files with 49 additions and 19 deletions

View File

@ -16,6 +16,7 @@ import { dbi } from '@/modules/db'
import { usePassword } from '@/hooks/usePassword' import { usePassword } from '@/hooks/usePassword'
import { useAppSelector } from '@/store/hooks/redux' import { useAppSelector } from '@/store/hooks/redux'
import { selectKeys } from '@/store' import { selectKeys } from '@/store'
import { isValidPassphase, isWeakPassphase } from '@/modules/keys'
type ModalSettingsProps = { type ModalSettingsProps = {
isSynced: boolean isSynced: boolean
@ -58,8 +59,9 @@ export const ModalSettings: FC<ModalSettingsProps> = ({ isSynced }) => {
} }
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => { const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
setIsPasswordInvalid(false) const password = e.target.value
setEnteredPassword(e.target.value) setIsPasswordInvalid(!!password && !isValidPassphase(password))
setEnteredPassword(password)
} }
const onClose = () => { const onClose = () => {
@ -76,7 +78,7 @@ export const ModalSettings: FC<ModalSettingsProps> = ({ isSynced }) => {
e.preventDefault() e.preventDefault()
setIsPasswordInvalid(false) setIsPasswordInvalid(false)
if (enteredPassword.trim().length < 6) { if (!isValidPassphase(enteredPassword)) {
return setIsPasswordInvalid(true) return setIsPasswordInvalid(true)
} }
try { try {
@ -114,18 +116,30 @@ export const ModalSettings: FC<ModalSettingsProps> = ({ isSynced }) => {
{...inputProps} {...inputProps}
onChange={handlePasswordChange} onChange={handlePasswordChange}
value={enteredPassword} value={enteredPassword}
helperText={isPasswordInvalid ? 'Invalid password' : ''} // helperText={isPasswordInvalid ? 'Invalid password' : ''}
placeholder="Enter a password" placeholder="Enter a password"
helperTextProps={{ // helperTextProps={{
sx: { // sx: {
'&.helper_text': { // '&.helper_text': {
color: 'red', // color: 'red',
}, // },
}, // },
}} // }}
disabled={!isChecked} disabled={!isChecked}
/> />
{isSynced ? ( {isPasswordInvalid ? (
<Typography variant="body2" color={'red'}>
Password must include 6+ English letters, numbers or punctuation marks.
</Typography>
) : !!enteredPassword && isWeakPassphase(enteredPassword) ? (
<Typography variant="body2" color={'orange'}>
Weak password
</Typography>
) : !!enteredPassword && !isPasswordInvalid ? (
<Typography variant="body2" color={'green'}>
Good password
</Typography>
) : isSynced ? (
<Typography variant="body2" color={'GrayText'}> <Typography variant="body2" color={'GrayText'}>
To change your password, type a new one and sync. To change your password, type a new one and sync.
</Typography> </Typography>
@ -139,7 +153,7 @@ export const ModalSettings: FC<ModalSettingsProps> = ({ isSynced }) => {
Sync {isLoading && <CircularProgress sx={{ marginLeft: '0.5rem' }} size={'1rem'} />} Sync {isLoading && <CircularProgress sx={{ marginLeft: '0.5rem' }} size={'1rem'} />}
</StyledButton> </StyledButton>
</StyledSettingContainer> </StyledSettingContainer>
<Button onClick={onClose}>Done</Button> {/* <Button onClick={onClose}>Done</Button> */}
</Stack> </Stack>
</Modal> </Modal>
) )

View File

@ -21,11 +21,31 @@ const ALGO = 'aes-256-cbc'
const IV_SIZE = 16 const IV_SIZE = 16
// valid passwords are a limited ASCII only, see notes below // valid passwords are a limited ASCII only, see notes below
const ASCII_REGEX = /^[A-Za-z0-9!@#$%^&*()]{4,}$/ const ASCII_REGEX = /^[A-Za-z0-9!@#$%^&*()\-_]{6,}$/
const ALGO_LOCAL = 'AES-CBC' const ALGO_LOCAL = 'AES-CBC'
const KEY_SIZE_LOCAL = 256 const KEY_SIZE_LOCAL = 256
export function isValidPassphase(passphrase: string): boolean {
return ASCII_REGEX.test(passphrase)
}
export function isWeakPassphase(passphrase: string): boolean {
const BIG_LETTER_REGEX = /[A-Z]+/
const SMALL_LETTER_REGEX = /[a-z]+/
const NUMBER_REGEX = /[0-9]+/
const PUNCT_REGEX = /[!@#$%^&*()\-_]+/
const big = BIG_LETTER_REGEX.test(passphrase) ? 1 : 0
const small = SMALL_LETTER_REGEX.test(passphrase) ? 1 : 0
const number = NUMBER_REGEX.test(passphrase) ? 1 : 0
const punct = PUNCT_REGEX.test(passphrase) ? 1 : 0
const base = big * 26 + small * 26 + number * 10 + punct * 12
const compl = Math.pow(base, passphrase.length)
const thresh = Math.pow(11, 14)
// console.log({ big, small, number, punct, base, compl, thresh });
return compl < thresh;
}
export class Keys { export class Keys {
subtle: any subtle: any
@ -33,10 +53,6 @@ export class Keys {
this.subtle = cryptoSubtle this.subtle = cryptoSubtle
} }
public isValidPassphase(passphrase: string): boolean {
return ASCII_REGEX.test(passphrase)
}
public async generatePassKey(pubkey: string, passphrase: string): Promise<{ passkey: Buffer; pwh: string }> { public async generatePassKey(pubkey: string, passphrase: string): Promise<{ passkey: Buffer; pwh: string }> {
const salt = Buffer.from(pubkey, 'hex') const salt = Buffer.from(pubkey, 'hex')
@ -45,7 +61,7 @@ export class Keys {
// We could use string.normalize() to make sure all JS implementations // We could use string.normalize() to make sure all JS implementations
// are compatible, but since we're looking to make this thing a standard // are compatible, but since we're looking to make this thing a standard
// then the simplest way is to exclude unicode and only work with ASCII // then the simplest way is to exclude unicode and only work with ASCII
if (!this.isValidPassphase(passphrase)) throw new Error('Password must be 4+ ASCII chars') if (!isValidPassphase(passphrase)) throw new Error('Password must be 4+ ASCII chars')
return new Promise((ok, fail) => { return new Promise((ok, fail) => {
// NOTE: we should use Argon2 or scrypt later, for now // NOTE: we should use Argon2 or scrypt later, for now