Skip to content
This repository has been archived by the owner on Apr 5, 2024. It is now read-only.

Implementing zxcvbn instead of stupid rules #272

Merged
merged 23 commits into from
Jan 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

eslint --max-warnings 0 src --ext .ts --ext .tsx --cache
npm test -- --watchAll=false
git add -A src
qvalentin marked this conversation as resolved.
Show resolved Hide resolved
31,057 changes: 19,616 additions & 11,441 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"react-bootstrap": "2.1.1",
"react-dom": "17.0.2",
"react-dropzone": "11.4.2",
"react-password-strength-bar": "^0.4.0",
"react-redux": "7.2.6",
"react-router-dom": "6.2.1",
"react-scripts": "4.0.3",
Expand Down
82 changes: 17 additions & 65 deletions src/__tests__/__snapshots__/storybook.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -991,74 +991,10 @@ exports[`Storyshots Registration default 1`] = `
className="form-control"
id="formBasicPassword"
onChange={[Function]}
placeholder="Must contain one number, uppercase & lowercase letter each"
placeholder="Enter your super secret strong password password."
type="password"
value=""
/>
<div>
<img
alt="status icon password length"
src="info-24px.svg"
/>
<span
className="sr-only"
>
Missing:
</span>
<span
className="text-muted"
>
Passwords must be between 8 and 20 characters.
</span>
</div>
<div>
<img
alt="status icon password contains uppercase character"
src="info-24px.svg"
/>
<span
className="sr-only"
>
Missing:
</span>
<span
className="text-muted"
>
Passwords must be at least contain 1 uppercase character.
</span>
</div>
<div>
<img
alt="status icon password contains lowercase character"
src="info-24px.svg"
/>
<span
className="sr-only"
>
Missing:
</span>
<span
className="text-muted"
>
Passwords must be at least contain 1 lowercase character.
</span>
</div>
<div>
<img
alt="status icon password contains number"
src="info-24px.svg"
/>
<span
className="sr-only"
>
Missing:
</span>
<span
className="text-muted"
>
Passwords must be at least contain 1 number.
</span>
</div>
</div>
<div>
<label
Expand All @@ -1074,6 +1010,22 @@ exports[`Storyshots Registration default 1`] = `
type="password"
value=""
/>
<div>
<img
alt="status icon password length"
src="info-24px.svg"
/>
<span
className="sr-only"
>
Missing:
</span>
<span
className="text-muted"
>
Passwords must be at least strong.
</span>
</div>
<div>
<img
alt="status icon passwords match"
Expand Down
8 changes: 8 additions & 0 deletions src/background/api/sharedApiTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Interface describing the standart return value of the backend
*/
// FIXME implement it if needed.
export interface ApiStatusResponse {
responseCode: number
responseStatus: { statusMessage: string; message: string }
}
12 changes: 10 additions & 2 deletions src/background/api/userInformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { hostname, userPath } from "./api"
import store from "../redux/store"
import { updateUser } from "../redux/actions/user"
import { UserState } from "../redux/actions/userTypes"
import { ApiStatusResponse } from "../api/sharedApiTypes"

export interface UserInformation {
userId: number | null
username?: string | null
groups?: string[] | null
password?: string
confirmationPassword?: string
confirmationPassword?: string // FIXME remove this in the backend
}

export const changeUserInformation = (
Expand All @@ -29,7 +30,14 @@ export const changeUserInformation = (
resolve(response.data)
})
.catch((error) => {
reject(error.response?.data?.message)
const errorResponse: ApiStatusResponse = {
responseCode: error.response.status,
responseStatus: {
statusMessage: error.response.data.status,
message: error.response.data.message,
},
}
reject(errorResponse)
})
})
}
3 changes: 1 addition & 2 deletions src/background/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,5 @@ const dev: constantsdef = {
}
export const constants = process.env.NODE_ENV === "development" ? dev : prod

export const MIN_PASSWORD_LENGTH = 8
export const MAX_PASSWORD_LENGTH = 20
export const REQUIRED_PASSWORD_STRENGTH = 3 // 3/4 (starting at 0)
export const DEFAULT_ALERT_DURATION = 3500
7 changes: 0 additions & 7 deletions src/background/methods/checkInput.ts

This file was deleted.

37 changes: 37 additions & 0 deletions src/components/pages/User/PasswordStrengthBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { Suspense } from "react"
import { PasswordFeedback } from "react-password-strength-bar"

// lazy load the lib
const PasswordStrengthBar = React.lazy(
() => import("react-password-strength-bar")
)

type PasswordStrengthBarWrapperArgs = {
currentPassword: string
scoreChangeCallback: (score: number, feedback: PasswordFeedback) => void
}

// a small component wrapping the password strength checks by lazy loading the component if necessary.
const PasswordStrengthBarWrapper = ({
currentPassword,
scoreChangeCallback,
}: PasswordStrengthBarWrapperArgs): JSX.Element | null => {
// if the user typed something show the component
if (currentPassword.length > 0) {
return (
<Suspense fallback={""}>
<PasswordStrengthBar
password={currentPassword}
onChangeScore={(score, feedback) =>
scoreChangeCallback(score, feedback)
}
scoreWords={["weak", "weak", "ok", "strong", "epic"]}
/>
</Suspense>
)
} else {
return null
}
}

export { PasswordStrengthBarWrapper }
97 changes: 50 additions & 47 deletions src/components/pages/User/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import UserInformationInput, {
} from "./UserInformationInput"
import { useSelector } from "react-redux"
import { RootState } from "../../../background/redux/store"
import {
DEFAULT_ALERT_DURATION,
MIN_PASSWORD_LENGTH,
} from "../../../background/constants"
import { DEFAULT_ALERT_DURATION } from "../../../background/constants"
import {
changeUserInformation,
UserInformation,
} from "../../../background/api/userInformation"
import { notMinStrLength } from "../../../background/methods/checkInput"
import { ApiStatusResponse } from "../../../background/api/sharedApiTypes"
import edit_svg from "../../../assets/images/icons/material.io/edit_white_24dp.svg"
import { hashPassword } from "../../../background/methods/passwords"

Expand Down Expand Up @@ -59,75 +56,81 @@ export default function Profile(): ReactElement {
}

function changeEditMode(): void {
console.log("[PROFILE] changedEditMode")
console.log("[Profile] changedEditMode")
setIsEditing(!isEditing)
}

const handleSubmit = async (inputUser: UserInformationInputInterface) => {
console.log("[PROFILE] handleSubmit")
let newUser: UserInformation = {
groups: user.groups,
userId: user.userId,
const handleSubmit = async (userInput: UserInformationInputInterface) => {
console.log("[Profile] handleSubmit")

let updatedUser: UserInformation = {
...user,
username: userInput.username,
}
if (!inputUser.username) {

if (userInput.password) {
// if the user updated the password
const hashedPassword = await hashPassword(userInput.password)
updatedUser.password = hashedPassword
updatedUser.confirmationPassword = hashedPassword
} else if (user.username === userInput.username) {
// if the new username is the old one show erorr instead of calling the backend
// FIXME should we even show something here?
handleAlertVisibility(
DEFAULT_ALERT_DURATION,
"danger",
"Error: Please choose an username."
"Error: No Changes."
)
return
}
newUser["username"] = inputUser.username
if (inputUser.password || inputUser.passwordConfirmation) {
if (inputUser.password !== inputUser.passwordConfirmation) {
handleAlertVisibility(
DEFAULT_ALERT_DURATION,
"danger",
"Error: Password and password confirmation must match."
)
return
}
if (
inputUser.password.match(/\d/) == null ||
inputUser.password.match(/[a-z]/) == null ||
inputUser.password.match(/[A-Z]/) == null ||
notMinStrLength(inputUser.password, MIN_PASSWORD_LENGTH)
) {
handleAlertVisibility(
DEFAULT_ALERT_DURATION,
"danger",
"Error: Please pay attention to the notes below the input fields."
)
return
}
newUser["password"] = await hashPassword(inputUser.password)
newUser["confirmationPassword"] = newUser["password"]
}

await changeUserInformation(newUser)
// trigger api call
await changeUserInformation(updatedUser)
.then((res) => {
changeEditMode()
// FIXME this does never appear, because we rerender it and this gets lost
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alerts should be defined global. Would need a lot more refacotring.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Y. Dont know why we are not using something like this: https://www.npmjs.com/package/react-notifications

handleAlertVisibility(
DEFAULT_ALERT_DURATION,
"success",
"Worked: " + res
)
})
.catch((err) => {
console.log("Error:" + err)
handleAlertVisibility(
DEFAULT_ALERT_DURATION,
"danger",
"Error: " + err
.catch(({ responseStatus, responseCode }: ApiStatusResponse) => {
console.log(
"[Profile] Error: (" +
responseCode +
") - " +
responseStatus.message
)

// 409 === Username already taken
if (responseCode === 409) {
handleAlertVisibility(
DEFAULT_ALERT_DURATION,
"danger",
"Error: Username already taken"
)
} else {
handleAlertVisibility(
DEFAULT_ALERT_DURATION,
"danger",
"Error: " + responseStatus.message
)
}
})
}

function EditProfile(): ReactElement {
return (
<>
<UserInformationInput
triggerAlert={handleAlertVisibility}
triggerAlert={(errorMessage: string) =>
handleAlertVisibility(
DEFAULT_ALERT_DURATION,
"danger",
errorMessage
)
}
submitFunction={handleSubmit}
presets={{ username: user.username ?? "", password: "" }}
/>
Expand Down
Loading