-
Notifications
You must be signed in to change notification settings - Fork 926
feat(site): add forgot password link #15108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
dd9cfae
Add request OTP flow
BrunoQuaresma 648935a
Add change password flow
BrunoQuaresma c125320
Update email template
BrunoQuaresma 893efec
Fix lint
BrunoQuaresma 7276781
Add tests
BrunoQuaresma 1c4bdee
Fix fmt
BrunoQuaresma 4d7ec36
Remove unecessary files
BrunoQuaresma 4b22b4d
Fix migration numbers
BrunoQuaresma 7ef3df8
Fix styles
BrunoQuaresma c0d3a6c
Update golden files
BrunoQuaresma c279704
Update message with @stirby copy
BrunoQuaresma File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
10 changes: 10 additions & 0 deletions
10
coderd/database/migrations/000266_update_forgot_password_notification.up.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
UPDATE notification_templates | ||
SET | ||
title_template = E'Reset your password for Coder', | ||
body_template = E'Hi {{.UserName}},\n\nUse the link below to reset your password.\n\nIf you did not make this request, you can ignore this message.', | ||
actions = '[{ | ||
"label": "Reset password", | ||
"url": "{{ base_url }}/reset-password/change?otp={{.Labels.one_time_passcode}}&email={{ .UserEmail }}" | ||
}]'::jsonb | ||
WHERE | ||
id = '62f86a30-2330-4b61-a26d-311ff3b608cf' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import type { Interpolation, Theme } from "@emotion/react"; | ||
import { CoderIcon } from "components/Icons/CoderIcon"; | ||
import type { FC } from "react"; | ||
import { getApplicationName, getLogoURL } from "utils/appearance"; | ||
|
||
/** | ||
* Enterprise customers can set a custom logo for their Coder application. Use | ||
* the custom logo wherever the Coder logo is used, if a custom one is provided. | ||
*/ | ||
export const CustomLogo: FC<{ css?: Interpolation<Theme> }> = (props) => { | ||
const applicationName = getApplicationName(); | ||
const logoURL = getLogoURL(); | ||
|
||
return logoURL ? ( | ||
<img | ||
{...props} | ||
alt={applicationName} | ||
src={logoURL} | ||
// This prevent browser to display the ugly error icon if the | ||
// image path is wrong or user didn't finish typing the url | ||
onError={(e) => { | ||
e.currentTarget.style.display = "none"; | ||
}} | ||
onLoad={(e) => { | ||
e.currentTarget.style.display = "inline"; | ||
}} | ||
css={{ maxWidth: 200 }} | ||
className="application-logo" | ||
/> | ||
) : ( | ||
<CoderIcon {...props} css={[{ fontSize: 64, fill: "white" }, props.css]} /> | ||
); | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
site/src/pages/ResetPasswordPage/ChangePasswordPage.stories.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { expect, spyOn, userEvent, within } from "@storybook/test"; | ||
import { API } from "api/api"; | ||
import { mockApiError } from "testHelpers/entities"; | ||
import { withGlobalSnackbar } from "testHelpers/storybook"; | ||
import ChangePasswordPage from "./ChangePasswordPage"; | ||
|
||
const meta: Meta<typeof ChangePasswordPage> = { | ||
title: "pages/ResetPasswordPage/ChangePasswordPage", | ||
component: ChangePasswordPage, | ||
args: { redirect: false }, | ||
decorators: [withGlobalSnackbar], | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof ChangePasswordPage>; | ||
|
||
export const Default: Story = {}; | ||
|
||
export const Success: Story = { | ||
play: async ({ canvasElement }) => { | ||
spyOn(API, "changePasswordWithOTP").mockResolvedValueOnce(); | ||
const canvas = within(canvasElement); | ||
const user = userEvent.setup(); | ||
const newPasswordInput = await canvas.findByLabelText("Password *"); | ||
await user.type(newPasswordInput, "password"); | ||
const confirmPasswordInput = | ||
await canvas.findByLabelText("Confirm password *"); | ||
await user.type(confirmPasswordInput, "password"); | ||
await user.click(canvas.getByRole("button", { name: /reset password/i })); | ||
await canvas.findByText("Password reset successfully"); | ||
}, | ||
}; | ||
|
||
export const WrongConfirmationPassword: Story = { | ||
play: async ({ canvasElement }) => { | ||
spyOn(API, "changePasswordWithOTP").mockRejectedValueOnce( | ||
mockApiError({ | ||
message: "New password should be different from the old password", | ||
}), | ||
); | ||
const canvas = within(canvasElement); | ||
const user = userEvent.setup(); | ||
const newPasswordInput = await canvas.findByLabelText("Password *"); | ||
await user.type(newPasswordInput, "password"); | ||
const confirmPasswordInput = | ||
await canvas.findByLabelText("Confirm password *"); | ||
await user.type(confirmPasswordInput, "different-password"); | ||
await user.click(canvas.getByRole("button", { name: /reset password/i })); | ||
await canvas.findByText("Passwords must match"); | ||
}, | ||
}; | ||
|
||
export const ServerError: Story = { | ||
play: async ({ canvasElement }) => { | ||
const serverError = | ||
"New password should be different from the old password"; | ||
spyOn(API, "changePasswordWithOTP").mockRejectedValueOnce( | ||
mockApiError({ | ||
message: serverError, | ||
}), | ||
); | ||
const canvas = within(canvasElement); | ||
const user = userEvent.setup(); | ||
const newPasswordInput = await canvas.findByLabelText("Password *"); | ||
await user.type(newPasswordInput, "password"); | ||
const confirmPasswordInput = | ||
await canvas.findByLabelText("Confirm password *"); | ||
await user.type(confirmPasswordInput, "password"); | ||
await user.click(canvas.getByRole("button", { name: /reset password/i })); | ||
await canvas.findByText(serverError); | ||
}, | ||
}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Could we pass the function directly as below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, when we do that, we are not able to spy on the method. I'm not sure why, but we have encountered this issue in jest as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weeeeird.