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

Commit 2aade85

Browse files
authored
Implementing zxcvbn instead of stupid password rules (#272)
* Small refactorings * Refactored ruleChecks in Profile * Added basic wrapper component and dependency * More refactoring * Refactorings, Removed useless rules * Fixed submit function in Profile * Renaming works again * Profile now checks for updates * Removed Todos, added fixmes * Removed useless asyncs * Updated Snapshots * Comments * Prettier * Implemented changes requested by @qvalentin * Updated Snapshots * Prettier * Fixed password strength check * Fixed folderContentMock.json formatting * Updated Snapshot * Automaticly add new snapshots to pre-push hook
1 parent 110c68c commit 2aade85

File tree

15 files changed

+20080
-11919
lines changed

15 files changed

+20080
-11919
lines changed

.husky/pre-push

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33

44
eslint --max-warnings 0 src --ext .ts --ext .tsx --cache
55
npm test -- --watchAll=false
6+
git add -A src

package-lock.json

Lines changed: 19616 additions & 11441 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"react-bootstrap": "2.1.1",
2222
"react-dom": "17.0.2",
2323
"react-dropzone": "11.4.2",
24+
"react-password-strength-bar": "^0.4.0",
2425
"react-redux": "7.2.6",
2526
"react-router-dom": "6.2.1",
2627
"react-scripts": "4.0.3",

src/__tests__/__snapshots__/storybook.test.ts.snap

Lines changed: 17 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -991,74 +991,10 @@ exports[`Storyshots Registration default 1`] = `
991991
className="form-control"
992992
id="formBasicPassword"
993993
onChange={[Function]}
994-
placeholder="Must contain one number, uppercase & lowercase letter each"
994+
placeholder="Enter your super secret strong password password."
995995
type="password"
996996
value=""
997997
/>
998-
<div>
999-
<img
1000-
alt="status icon password length"
1001-
src="info-24px.svg"
1002-
/>
1003-
<span
1004-
className="sr-only"
1005-
>
1006-
Missing:
1007-
</span>
1008-
<span
1009-
className="text-muted"
1010-
>
1011-
Passwords must be between 8 and 20 characters.
1012-
</span>
1013-
</div>
1014-
<div>
1015-
<img
1016-
alt="status icon password contains uppercase character"
1017-
src="info-24px.svg"
1018-
/>
1019-
<span
1020-
className="sr-only"
1021-
>
1022-
Missing:
1023-
</span>
1024-
<span
1025-
className="text-muted"
1026-
>
1027-
Passwords must be at least contain 1 uppercase character.
1028-
</span>
1029-
</div>
1030-
<div>
1031-
<img
1032-
alt="status icon password contains lowercase character"
1033-
src="info-24px.svg"
1034-
/>
1035-
<span
1036-
className="sr-only"
1037-
>
1038-
Missing:
1039-
</span>
1040-
<span
1041-
className="text-muted"
1042-
>
1043-
Passwords must be at least contain 1 lowercase character.
1044-
</span>
1045-
</div>
1046-
<div>
1047-
<img
1048-
alt="status icon password contains number"
1049-
src="info-24px.svg"
1050-
/>
1051-
<span
1052-
className="sr-only"
1053-
>
1054-
Missing:
1055-
</span>
1056-
<span
1057-
className="text-muted"
1058-
>
1059-
Passwords must be at least contain 1 number.
1060-
</span>
1061-
</div>
1062998
</div>
1063999
<div>
10641000
<label
@@ -1074,6 +1010,22 @@ exports[`Storyshots Registration default 1`] = `
10741010
type="password"
10751011
value=""
10761012
/>
1013+
<div>
1014+
<img
1015+
alt="status icon password length"
1016+
src="info-24px.svg"
1017+
/>
1018+
<span
1019+
className="sr-only"
1020+
>
1021+
Missing:
1022+
</span>
1023+
<span
1024+
className="text-muted"
1025+
>
1026+
Passwords must be at least strong.
1027+
</span>
1028+
</div>
10771029
<div>
10781030
<img
10791031
alt="status icon passwords match"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Interface describing the standart return value of the backend
3+
*/
4+
// FIXME implement it if needed.
5+
export interface ApiStatusResponse {
6+
responseCode: number
7+
responseStatus: { statusMessage: string; message: string }
8+
}

src/background/api/userInformation.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import { hostname, userPath } from "./api"
55
import store from "../redux/store"
66
import { updateUser } from "../redux/actions/user"
77
import { UserState } from "../redux/actions/userTypes"
8+
import { ApiStatusResponse } from "../api/sharedApiTypes"
89

910
export interface UserInformation {
1011
userId: number | null
1112
username?: string | null
1213
groups?: string[] | null
1314
password?: string
14-
confirmationPassword?: string
15+
confirmationPassword?: string // FIXME remove this in the backend
1516
}
1617

1718
export const changeUserInformation = (
@@ -29,7 +30,14 @@ export const changeUserInformation = (
2930
resolve(response.data)
3031
})
3132
.catch((error) => {
32-
reject(error.response?.data?.message)
33+
const errorResponse: ApiStatusResponse = {
34+
responseCode: error.response.status,
35+
responseStatus: {
36+
statusMessage: error.response.data.status,
37+
message: error.response.data.message,
38+
},
39+
}
40+
reject(errorResponse)
3341
})
3442
})
3543
}

src/background/constants.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,5 @@ const dev: constantsdef = {
2121
}
2222
export const constants = process.env.NODE_ENV === "development" ? dev : prod
2323

24-
export const MIN_PASSWORD_LENGTH = 8
25-
export const MAX_PASSWORD_LENGTH = 20
24+
export const REQUIRED_PASSWORD_STRENGTH = 3 // 3/4 (starting at 0)
2625
export const DEFAULT_ALERT_DURATION = 3500

src/background/methods/checkInput.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, { Suspense } from "react"
2+
import { PasswordFeedback } from "react-password-strength-bar"
3+
4+
// lazy load the lib
5+
const PasswordStrengthBar = React.lazy(
6+
() => import("react-password-strength-bar")
7+
)
8+
9+
type PasswordStrengthBarWrapperArgs = {
10+
currentPassword: string
11+
scoreChangeCallback: (score: number, feedback: PasswordFeedback) => void
12+
}
13+
14+
// a small component wrapping the password strength checks by lazy loading the component if necessary.
15+
const PasswordStrengthBarWrapper = ({
16+
currentPassword,
17+
scoreChangeCallback,
18+
}: PasswordStrengthBarWrapperArgs): JSX.Element | null => {
19+
// if the user typed something show the component
20+
if (currentPassword.length > 0) {
21+
return (
22+
<Suspense fallback={""}>
23+
<PasswordStrengthBar
24+
password={currentPassword}
25+
onChangeScore={(score, feedback) =>
26+
scoreChangeCallback(score, feedback)
27+
}
28+
scoreWords={["weak", "weak", "ok", "strong", "epic"]}
29+
/>
30+
</Suspense>
31+
)
32+
} else {
33+
return null
34+
}
35+
}
36+
37+
export { PasswordStrengthBarWrapper }

src/components/pages/User/Profile.tsx

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@ import UserInformationInput, {
55
} from "./UserInformationInput"
66
import { useSelector } from "react-redux"
77
import { RootState } from "../../../background/redux/store"
8-
import {
9-
DEFAULT_ALERT_DURATION,
10-
MIN_PASSWORD_LENGTH,
11-
} from "../../../background/constants"
8+
import { DEFAULT_ALERT_DURATION } from "../../../background/constants"
129
import {
1310
changeUserInformation,
1411
UserInformation,
1512
} from "../../../background/api/userInformation"
16-
import { notMinStrLength } from "../../../background/methods/checkInput"
13+
import { ApiStatusResponse } from "../../../background/api/sharedApiTypes"
1714
import edit_svg from "../../../assets/images/icons/material.io/edit_white_24dp.svg"
1815
import { hashPassword } from "../../../background/methods/passwords"
1916

@@ -59,75 +56,81 @@ export default function Profile(): ReactElement {
5956
}
6057

6158
function changeEditMode(): void {
62-
console.log("[PROFILE] changedEditMode")
59+
console.log("[Profile] changedEditMode")
6360
setIsEditing(!isEditing)
6461
}
6562

66-
const handleSubmit = async (inputUser: UserInformationInputInterface) => {
67-
console.log("[PROFILE] handleSubmit")
68-
let newUser: UserInformation = {
69-
groups: user.groups,
70-
userId: user.userId,
63+
const handleSubmit = async (userInput: UserInformationInputInterface) => {
64+
console.log("[Profile] handleSubmit")
65+
66+
let updatedUser: UserInformation = {
67+
...user,
68+
username: userInput.username,
7169
}
72-
if (!inputUser.username) {
70+
71+
if (userInput.password) {
72+
// if the user updated the password
73+
const hashedPassword = await hashPassword(userInput.password)
74+
updatedUser.password = hashedPassword
75+
updatedUser.confirmationPassword = hashedPassword
76+
} else if (user.username === userInput.username) {
77+
// if the new username is the old one show erorr instead of calling the backend
78+
// FIXME should we even show something here?
7379
handleAlertVisibility(
7480
DEFAULT_ALERT_DURATION,
7581
"danger",
76-
"Error: Please choose an username."
82+
"Error: No Changes."
7783
)
7884
return
7985
}
80-
newUser["username"] = inputUser.username
81-
if (inputUser.password || inputUser.passwordConfirmation) {
82-
if (inputUser.password !== inputUser.passwordConfirmation) {
83-
handleAlertVisibility(
84-
DEFAULT_ALERT_DURATION,
85-
"danger",
86-
"Error: Password and password confirmation must match."
87-
)
88-
return
89-
}
90-
if (
91-
inputUser.password.match(/\d/) == null ||
92-
inputUser.password.match(/[a-z]/) == null ||
93-
inputUser.password.match(/[A-Z]/) == null ||
94-
notMinStrLength(inputUser.password, MIN_PASSWORD_LENGTH)
95-
) {
96-
handleAlertVisibility(
97-
DEFAULT_ALERT_DURATION,
98-
"danger",
99-
"Error: Please pay attention to the notes below the input fields."
100-
)
101-
return
102-
}
103-
newUser["password"] = await hashPassword(inputUser.password)
104-
newUser["confirmationPassword"] = newUser["password"]
105-
}
10686

107-
await changeUserInformation(newUser)
87+
// trigger api call
88+
await changeUserInformation(updatedUser)
10889
.then((res) => {
10990
changeEditMode()
91+
// FIXME this does never appear, because we rerender it and this gets lost
11092
handleAlertVisibility(
11193
DEFAULT_ALERT_DURATION,
11294
"success",
11395
"Worked: " + res
11496
)
11597
})
116-
.catch((err) => {
117-
console.log("Error:" + err)
118-
handleAlertVisibility(
119-
DEFAULT_ALERT_DURATION,
120-
"danger",
121-
"Error: " + err
98+
.catch(({ responseStatus, responseCode }: ApiStatusResponse) => {
99+
console.log(
100+
"[Profile] Error: (" +
101+
responseCode +
102+
") - " +
103+
responseStatus.message
122104
)
105+
106+
// 409 === Username already taken
107+
if (responseCode === 409) {
108+
handleAlertVisibility(
109+
DEFAULT_ALERT_DURATION,
110+
"danger",
111+
"Error: Username already taken"
112+
)
113+
} else {
114+
handleAlertVisibility(
115+
DEFAULT_ALERT_DURATION,
116+
"danger",
117+
"Error: " + responseStatus.message
118+
)
119+
}
123120
})
124121
}
125122

126123
function EditProfile(): ReactElement {
127124
return (
128125
<>
129126
<UserInformationInput
130-
triggerAlert={handleAlertVisibility}
127+
triggerAlert={(errorMessage: string) =>
128+
handleAlertVisibility(
129+
DEFAULT_ALERT_DURATION,
130+
"danger",
131+
errorMessage
132+
)
133+
}
131134
submitFunction={handleSubmit}
132135
presets={{ username: user.username ?? "", password: "" }}
133136
/>

0 commit comments

Comments
 (0)