Skip to content

Commit f33eac2

Browse files
committed
feat(password): add details field to validate password endpoint
1 parent 175b4bf commit f33eac2

File tree

11 files changed

+71
-28
lines changed

11 files changed

+71
-28
lines changed

coderd/users_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1109,8 +1109,6 @@ func TestUpdateUserPassword(t *testing.T) {
11091109
require.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[numLogs-1].Action)
11101110
})
11111111

1112-
// FIXME: Re-enable the tests once real logic changed
1113-
// Currently there's no check in code to validate that users have to put the old password
11141112
t.Run("MemberCantUpdateOwnPasswordWithoutOldPassword", func(t *testing.T) {
11151113
t.Parallel()
11161114
client := coderdtest.New(t, nil)

site/src/api/api.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1322,11 +1322,13 @@ class ApiMethods {
13221322
await this.axios.put(`/api/v2/users/${userId}/password`, updatePassword);
13231323
};
13241324

1325-
validateUserPassword = async (password: string): Promise<boolean> => {
1325+
validateUserPassword = async (
1326+
password: string,
1327+
): Promise<TypesGen.ValidateUserPasswordResponse> => {
13261328
const response = await this.axios.post("/api/v2/users/validate-password", {
13271329
password,
13281330
});
1329-
return response.data.valid;
1331+
return response.data;
13301332
};
13311333

13321334
getRoles = async (): Promise<Array<TypesGen.AssignableRoles>> => {

site/src/pages/CreateUserPage/CreateUserForm.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export interface CreateUserFormProps {
6464
onSubmit: (user: TypesGen.CreateUserRequestWithOrgs) => void;
6565
onCancel: () => void;
6666
onPasswordChange: (password: string) => void;
67-
passwordIsValid: boolean;
67+
passwordValidator: TypesGen.ValidateUserPasswordResponse;
6868
error?: unknown;
6969
isLoading: boolean;
7070
authMethods?: TypesGen.AuthMethods;
@@ -91,7 +91,7 @@ export const CreateUserForm: FC<
9191
onSubmit,
9292
onCancel,
9393
onPasswordChange,
94-
passwordIsValid,
94+
passwordValidator,
9595
error,
9696
isLoading,
9797
authMethods,
@@ -206,15 +206,15 @@ export const CreateUserForm: FC<
206206
(form.values.login_type !== "password" &&
207207
"No password required for this login type") ||
208208
(form.values.password !== "" &&
209-
!passwordIsValid &&
210-
"password is not strong enough."),
209+
!passwordValidator.valid &&
210+
passwordValidator.details),
211211
})}
212212
autoComplete="current-password"
213213
fullWidth
214214
id="password"
215215
data-testid="password-input"
216216
disabled={form.values.login_type !== "password"}
217-
error={!!(form.values.password !== "" && !passwordIsValid)}
217+
error={!!(form.values.password !== "" && !passwordValidator.valid)}
218218
label={Language.passwordLabel}
219219
type="password"
220220
/>

site/src/pages/CreateUserPage/CreateUserPage.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ export const CreateUserPage: FC = () => {
2020
const authMethodsQuery = useQuery(authMethods());
2121
const validatePasswordMutation = useMutation(validatePassword());
2222

23-
const [passwordIsValid, setPasswordIsValid] = useState(false);
23+
const [passwordValidator, setPasswordValidator] = useState({
24+
valid: false,
25+
details: "",
26+
});
2427

2528
const validateUserPassword = async (password: string) => {
2629
validatePasswordMutation.mutate(password, {
2730
onSuccess: (data) => {
28-
setPasswordIsValid(data);
31+
setPasswordValidator({ valid: data.valid, details: data.details });
2932
},
3033
});
3134
};
@@ -53,7 +56,7 @@ export const CreateUserPage: FC = () => {
5356
navigate("..", { relative: "path" });
5457
}}
5558
onPasswordChange={debouncedValidateUserPassword}
56-
passwordIsValid={passwordIsValid}
59+
passwordValidator={passwordValidator}
5760
isLoading={createUserMutation.isLoading}
5861
/>
5962
</Margins>

site/src/pages/SetupPage/SetupPage.test.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,32 @@ describe("Setup Page", () => {
4949
);
5050
});
5151

52+
it("renders the password validation error", async () => {
53+
server.use(
54+
http.post("/api/v2/users/validate-password", () => {
55+
return HttpResponse.json({
56+
valid: false,
57+
details: "Password is too short",
58+
});
59+
}),
60+
);
61+
62+
renderWithRouter(
63+
createMemoryRouter(
64+
[
65+
{
66+
path: "/setup",
67+
element: <SetupPage />,
68+
},
69+
],
70+
{ initialEntries: ["/setup"] },
71+
),
72+
);
73+
await waitForLoaderToBeRemoved();
74+
await fillForm({ password: "short" });
75+
await waitFor(() => screen.findByText("Password is too short"));
76+
});
77+
5278
it("redirects to the app when setup is successful", async () => {
5379
let userHasBeenCreated = false;
5480

@@ -99,6 +125,7 @@ describe("Setup Page", () => {
99125
await fillForm();
100126
await waitFor(() => screen.findByText("Templates"));
101127
});
128+
102129
it("calls sendBeacon with telemetry", async () => {
103130
const sendBeacon = jest.fn();
104131
Object.defineProperty(window.navigator, "sendBeacon", {

site/src/pages/SetupPage/SetupPage.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ export const SetupPage: FC = () => {
2929
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
3030
const navigate = useNavigate();
3131

32-
const [passwordIsValid, setPasswordIsValid] = useState(false);
32+
const [passwordValidator, setPasswordValidator] = useState({
33+
valid: false,
34+
details: "",
35+
});
3336

3437
useEffect(() => {
3538
if (!buildInfoQuery.data) {
@@ -43,7 +46,7 @@ export const SetupPage: FC = () => {
4346
const validateUserPassword = async (password: string) => {
4447
validatePasswordMutation.mutate(password, {
4548
onSuccess: (data) => {
46-
setPasswordIsValid(data);
49+
setPasswordValidator({ valid: data.valid, details: data.details });
4750
},
4851
});
4952
};
@@ -74,7 +77,7 @@ export const SetupPage: FC = () => {
7477
</Helmet>
7578
<SetupPageView
7679
onPasswordChange={debouncedValidateUserPassword}
77-
passwordIsValid={passwordIsValid}
80+
passwordValidator={passwordValidator}
7881
isLoading={isSigningIn || createFirstUserMutation.isLoading}
7982
error={createFirstUserMutation.error}
8083
onSubmit={async (firstUser) => {

site/src/pages/SetupPage/SetupPageView.stories.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export const TrialError: Story = {
3131
},
3232
};
3333

34+
export const PasswordValidation: Story = {
35+
args: {
36+
passwordValidator: { valid: false, details: "Password is too short" },
37+
},
38+
};
39+
3440
export const Loading: Story = {
3541
args: {
3642
isLoading: true,

site/src/pages/SetupPage/SetupPageView.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,15 @@ const numberOfDevelopersOptions = [
8484
export interface SetupPageViewProps {
8585
onSubmit: (firstUser: TypesGen.CreateFirstUserRequest) => void;
8686
onPasswordChange?: (password: string) => void;
87-
passwordIsValid?: boolean;
87+
passwordValidator: TypesGen.ValidateUserPasswordResponse;
8888
error?: unknown;
8989
isLoading?: boolean;
9090
}
9191

9292
export const SetupPageView: FC<SetupPageViewProps> = ({
9393
onSubmit,
9494
onPasswordChange,
95-
passwordIsValid = true,
95+
passwordValidator,
9696
error,
9797
isLoading,
9898
}) => {
@@ -183,9 +183,9 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
183183
id="password"
184184
label={Language.passwordLabel}
185185
type="password"
186-
error={!!(form.values.password !== "" && !passwordIsValid)}
186+
error={!!(form.values.password !== "" && !passwordValidator.valid)}
187187
helperText={
188-
!passwordIsValid ? "Password is not strong enough." : ""
188+
!passwordValidator.valid ? passwordValidator.details : ""
189189
}
190190
/>
191191
<label

site/src/pages/UserSettingsPage/SecurityPage/SecurityForm.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import LoadingButton from "@mui/lab/LoadingButton";
22
import TextField from "@mui/material/TextField";
3+
import type * as TypesGen from "api/typesGenerated";
34
import { Alert } from "components/Alert/Alert";
45
import { ErrorAlert } from "components/Alert/ErrorAlert";
56
import { Form, FormFields } from "components/Form/Form";
@@ -42,7 +43,7 @@ export interface SecurityFormProps {
4243
disabled: boolean;
4344
isLoading: boolean;
4445
onPasswordChange: (password: string) => void;
45-
passwordIsValid: boolean;
46+
passwordValidator: TypesGen.ValidateUserPasswordResponse;
4647
onSubmit: (values: SecurityFormValues) => void;
4748
error?: unknown;
4849
}
@@ -51,7 +52,7 @@ export const SecurityForm: FC<SecurityFormProps> = ({
5152
disabled,
5253
isLoading,
5354
onPasswordChange,
54-
passwordIsValid,
55+
passwordValidator,
5556
onSubmit,
5657
error,
5758
}) => {
@@ -96,10 +97,10 @@ export const SecurityForm: FC<SecurityFormProps> = ({
9697
autoComplete="password"
9798
fullWidth
9899
label={Language.newPasswordLabel}
99-
error={!!(form.values.password !== "" && !passwordIsValid)}
100+
error={!!(form.values.password !== "" && !passwordValidator.valid)}
100101
helperText={
101-
form.values.password !== "" && !passwordIsValid
102-
? "Password is not strong enough."
102+
form.values.password !== "" && !passwordValidator.valid
103+
? passwordValidator.details
103104
: ""
104105
}
105106
type="password"

site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ export const SecurityPage: FC = () => {
2929
});
3030
const singleSignOnSection = useSingleSignOnSection();
3131

32-
const [passwordIsValid, setPasswordIsValid] = useState(false);
32+
const [passwordValidator, setPasswordValidator] = useState({
33+
valid: false,
34+
details: "",
35+
});
3336

3437
const validateUserPassword = async (password: string) => {
3538
validatePasswordMutation.mutate(password, {
3639
onSuccess: (data) => {
37-
setPasswordIsValid(data);
40+
setPasswordValidator({ valid: data.valid, details: data.details });
3841
},
3942
});
4043
};
@@ -56,7 +59,7 @@ export const SecurityPage: FC = () => {
5659
error: updatePasswordMutation.error,
5760
isLoading: updatePasswordMutation.isLoading,
5861
onPasswordChange: debouncedValidateUserPassword,
59-
passwordIsValid: passwordIsValid,
62+
passwordValidator: passwordValidator,
6063
onSubmit: async (data) => {
6164
await updatePasswordMutation.mutateAsync({
6265
userId: me.id,

site/src/pages/UserSettingsPage/SecurityPage/SecurityPageView.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const defaultArgs: ComponentProps<typeof SecurityPageView> = {
1616
isLoading: false,
1717
onSubmit: action("onSubmit"),
1818
onPasswordChange: (password: string) => {},
19-
passwordIsValid: false,
19+
passwordValidator: { valid: false, details: "" },
2020
},
2121
},
2222
oidc: {

0 commit comments

Comments
 (0)