Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitaaa1223

Browse files
1 parent413928b commitaaa1223

File tree

14 files changed

+603
-44
lines changed

14 files changed

+603
-44
lines changed

‎coderd/database/migrations/000266_update_forgot_password_notification.down.sql‎

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
UPDATE notification_templates
2+
SET
3+
title_template= E'Reset your password for Coder',
4+
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.',
5+
actions='[{
6+
"label": "Reset password",
7+
"url": "{{ base_url }}/reset-password/change?otp={{.Labels.one_time_passcode}}&email={{ .UserEmail }}"
8+
}]'::jsonb
9+
WHERE
10+
id='62f86a30-2330-4b61-a26d-311ff3b608cf'

‎coderd/notifications/testdata/rendered-templates/smtp/TemplateUserRequestedOneTimePasscode.html.golden‎

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
From: system@coder.com
22
To: bobby@coder.com
3-
Subject:Your One-Time Passcode for Coder.
3+
Subject:Reset your password for Coder
44
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
55
Date: Fri, 11 Oct 2024 09:03:06 +0000
66
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
@@ -12,13 +12,13 @@ Content-Type: text/plain; charset=UTF-8
1212

1313
Hi Bobby,
1414

15-
A request to reset the password for your Coder account has been made. Your =
16-
one-time passcode is:
15+
Use the link below to reset your password.
1716

18-
fad9020b-6562-4cdb-87f1-0486f1bea415
17+
If you did not make this request, you can ignore this message.
1918

20-
If you did not request to reset your password, you can ignore this message.
2119

20+
Reset password: http://test.com/reset-password/change?otp=3Dfad9020b-6562-4=
21+
cdb-87f1-0486f1bea415&email=3Dbobby@coder.com
2222

2323
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
2424
Content-Transfer-Encoding: quoted-printable
@@ -30,7 +30,7 @@ Content-Type: text/html; charset=UTF-8
3030
<meta charset=3D"UTF-8" />
3131
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
3232
=3D1.0" />
33-
<title>Your One-Time Passcode for Coder.</title>
33+
<title>Reset your password for Coder</title>
3434
</head>
3535
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
3636
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
@@ -45,21 +45,24 @@ er Logo" style=3D"height: 40px;" />
4545
</div>
4646
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
4747
argin: 8px 0 32px; line-height: 1.5;">
48-
Your One-Time Passcode for Coder.
48+
Reset your password for Coder
4949
</h1>
5050
<div style=3D"line-height: 1.5;">
5151
<p>Hi Bobby,</p>
5252

53-
<p>A request to reset the password for your Coder account has been made. Yo=
54-
ur one-time passcode is:</p>
53+
<p>Use the link below to reset your password.</p>
5554

56-
<p><strong>fad9020b-6562-4cdb-87f1-0486f1bea415</strong></p>
57-
58-
<p>If you did not request to reset your password, you can ignore this messa=
59-
ge.</p>
55+
<p>If you did not make this request, you can ignore this message.</p>
6056
</div>
6157
<div style=3D"text-align: center; margin-top: 32px;">
6258
=20
59+
<a href=3D"http://test.com/reset-password/change?otp=3Dfad9020b-656=
60+
2-4cdb-87f1-0486f1bea415&email=3Dbobby@coder.com" style=3D"display: inline-=
61+
block; padding: 13px 24px; background-color: #020617; color: #f8fafc; text-=
62+
decoration: none; border-radius: 8px; margin: 0 4px;">
63+
Reset password
64+
</a>
65+
=20
6366
</div>
6467
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
6568
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">

‎coderd/notifications/testdata/rendered-templates/webhook/TemplateUserRequestedOneTimePasscode.json.golden‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@
99
"user_email": "bobby@coder.com",
1010
"user_name": "Bobby",
1111
"user_username": "bobby",
12-
"actions": [],
12+
"actions": [
13+
{
14+
"label": "Reset password",
15+
"url": "http://test.com/reset-password/change?otp=00000000-0000-0000-0000-000000000000\u0026email=bobby@coder.com"
16+
}
17+
],
1318
"labels": {
1419
"one_time_passcode": "00000000-0000-0000-0000-000000000000"
1520
},
1621
"data": null
1722
},
18-
"title": "Your One-Time Passcode for Coder.",
19-
"title_markdown": "Your One-Time Passcode for Coder.",
20-
"body": "Hi Bobby,\n\nA requestto resetthe password foryourCoder account has been made. Your one-time passcode is:\n\n00000000-0000-0000-0000-000000000000\n\nIf you did notrequest to reset your password, you can ignore this message.",
21-
"body_markdown": "Hi Bobby,\n\nA requestto resetthe password foryourCoder account has been made. Your one-time passcode is:\n\n**00000000-0000-0000-0000-000000000000**\n\nIf you did notrequest to reset your password, you can ignore this message."
23+
"title": "Reset your password for Coder",
24+
"title_markdown": "Reset your password for Coder",
25+
"body": "Hi Bobby,\n\nUse the link belowto reset yourpassword.\n\nIf you did notmake this request, you can ignore this message.",
26+
"body_markdown": "Hi Bobby,\n\nUse the link belowto reset yourpassword.\n\nIf you did notmake this request, you can ignore this message."
2227
}

‎site/src/api/api.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,18 @@ class ApiMethods {
21672167
);
21682168
returnres.data;
21692169
};
2170+
2171+
requestOneTimePassword=async(
2172+
req:TypesGen.RequestOneTimePasscodeRequest,
2173+
)=>{
2174+
awaitthis.axios.post<void>("/api/v2/users/otp/request",req);
2175+
};
2176+
2177+
changePasswordWithOTP=async(
2178+
req:TypesGen.ChangePasswordWithOneTimePasscodeRequest,
2179+
)=>{
2180+
awaitthis.axios.post<void>("/api/v2/users/otp/change-password",req);
2181+
};
21702182
}
21712183

21722184
// This is a hard coded CSRF token/cookie pair for local development. In prod,

‎site/src/api/queries/users.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
AuthorizationRequest,
44
GenerateAPIKeyResponse,
55
GetUsersResponse,
6+
RequestOneTimePasscodeRequest,
67
UpdateUserAppearanceSettingsRequest,
78
UpdateUserPasswordRequest,
89
UpdateUserProfileRequest,
@@ -253,3 +254,16 @@ export const updateAppearanceSettings = (
253254
},
254255
};
255256
};
257+
258+
exportconstrequestOneTimePassword=()=>{
259+
return{
260+
mutationFn:(req:RequestOneTimePasscodeRequest)=>
261+
API.requestOneTimePassword(req),
262+
};
263+
};
264+
265+
exportconstchangePasswordWithOTP=()=>{
266+
return{
267+
mutationFn:API.changePasswordWithOTP,
268+
};
269+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
importtype{Interpolation,Theme}from"@emotion/react";
2+
import{CoderIcon}from"components/Icons/CoderIcon";
3+
importtype{FC}from"react";
4+
import{getApplicationName,getLogoURL}from"utils/appearance";
5+
6+
/**
7+
* Enterprise customers can set a custom logo for their Coder application. Use
8+
* the custom logo wherever the Coder logo is used, if a custom one is provided.
9+
*/
10+
exportconstCustomLogo:FC<{css?:Interpolation<Theme>}>=(props)=>{
11+
constapplicationName=getApplicationName();
12+
constlogoURL=getLogoURL();
13+
14+
returnlogoURL ?(
15+
<img
16+
{...props}
17+
alt={applicationName}
18+
src={logoURL}
19+
// This prevent browser to display the ugly error icon if the
20+
// image path is wrong or user didn't finish typing the url
21+
onError={(e)=>{
22+
e.currentTarget.style.display="none";
23+
}}
24+
onLoad={(e)=>{
25+
e.currentTarget.style.display="inline";
26+
}}
27+
css={{maxWidth:200}}
28+
className="application-logo"
29+
/>
30+
) :(
31+
<CoderIcon{...props}css={[{fontSize:64,fill:"white"},props.css]}/>
32+
);
33+
};

‎site/src/pages/LoginPage/LoginPageView.tsx‎

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
importtype{Interpolation,Theme}from"@emotion/react";
22
importButtonfrom"@mui/material/Button";
33
importtype{AuthMethods,BuildInfoResponse}from"api/typesGenerated";
4-
import{CoderIcon}from"components/Icons/CoderIcon";
4+
import{CustomLogo}from"components/CustomLogo/CustomLogo";
55
import{Loader}from"components/Loader/Loader";
66
import{typeFC,useState}from"react";
77
import{useLocation}from"react-router-dom";
8-
import{getApplicationName,getLogoURL}from"utils/appearance";
98
import{retrieveRedirect}from"utils/redirect";
109
import{SignInForm}from"./SignInForm";
1110
import{TermsOfServiceLink}from"./TermsOfServiceLink";
@@ -32,37 +31,14 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
3231
// This allows messages to be displayed at the top of the sign in form.
3332
// Helpful for any redirects that want to inform the user of something.
3433
constmessage=newURLSearchParams(location.search).get("message");
35-
constapplicationName=getApplicationName();
36-
constlogoURL=getLogoURL();
37-
constapplicationLogo=logoURL ?(
38-
<img
39-
alt={applicationName}
40-
src={logoURL}
41-
// This prevent browser to display the ugly error icon if the
42-
// image path is wrong or user didn't finish typing the url
43-
onError={(e)=>{
44-
e.currentTarget.style.display="none";
45-
}}
46-
onLoad={(e)=>{
47-
e.currentTarget.style.display="inline";
48-
}}
49-
css={{
50-
maxWidth:"200px",
51-
}}
52-
className="application-logo"
53-
/>
54-
) :(
55-
<CoderIconfill="white"opacity={1}css={styles.icon}/>
56-
);
57-
5834
const[tosAccepted,setTosAccepted]=useState(false);
5935
consttosAcceptanceRequired=
6036
authMethods?.terms_of_service_url&&!tosAccepted;
6137

6238
return(
6339
<divcss={styles.root}>
6440
<divcss={styles.container}>
65-
{applicationLogo}
41+
<CustomLogo/>
6642
{isLoading ?(
6743
<Loader/>
6844
) :tosAcceptanceRequired ?(

‎site/src/pages/LoginPage/PasswordSignInForm.tsx‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
importLoadingButtonfrom"@mui/lab/LoadingButton";
2+
importLinkfrom"@mui/material/Link";
23
importTextFieldfrom"@mui/material/TextField";
34
import{Stack}from"components/Stack/Stack";
45
import{useFormik}from"formik";
56
importtype{FC}from"react";
7+
import{LinkasRouterLink}from"react-router-dom";
68
import{getFormHelpers,onChangeTrimmed}from"utils/formUtils";
79
import*asYupfrom"yup";
810
import{Language}from"./SignInForm";
@@ -65,6 +67,17 @@ export const PasswordSignInForm: FC<PasswordSignInFormProps> = ({
6567
>
6668
{Language.passwordSignIn}
6769
</LoadingButton>
70+
<Link
71+
component={RouterLink}
72+
to="/reset-password"
73+
css={{
74+
fontSize:12,
75+
fontWeight:500,
76+
lineHeight:"16px",
77+
}}
78+
>
79+
Forgot password?
80+
</Link>
6881
</Stack>
6982
</form>
7083
);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{expect,spyOn,userEvent,within}from"@storybook/test";
3+
import{API}from"api/api";
4+
import{mockApiError}from"testHelpers/entities";
5+
import{withGlobalSnackbar}from"testHelpers/storybook";
6+
importChangePasswordPagefrom"./ChangePasswordPage";
7+
8+
constmeta:Meta<typeofChangePasswordPage>={
9+
title:"pages/ResetPasswordPage/ChangePasswordPage",
10+
component:ChangePasswordPage,
11+
args:{redirect:false},
12+
decorators:[withGlobalSnackbar],
13+
};
14+
15+
exportdefaultmeta;
16+
typeStory=StoryObj<typeofChangePasswordPage>;
17+
18+
exportconstDefault:Story={};
19+
20+
exportconstSuccess:Story={
21+
play:async({ canvasElement})=>{
22+
spyOn(API,"changePasswordWithOTP").mockResolvedValueOnce();
23+
constcanvas=within(canvasElement);
24+
constuser=userEvent.setup();
25+
constnewPasswordInput=awaitcanvas.findByLabelText("Password *");
26+
awaituser.type(newPasswordInput,"password");
27+
constconfirmPasswordInput=
28+
awaitcanvas.findByLabelText("Confirm password *");
29+
awaituser.type(confirmPasswordInput,"password");
30+
awaituser.click(canvas.getByRole("button",{name:/resetpassword/i}));
31+
awaitcanvas.findByText("Password reset successfully");
32+
},
33+
};
34+
35+
exportconstWrongConfirmationPassword:Story={
36+
play:async({ canvasElement})=>{
37+
spyOn(API,"changePasswordWithOTP").mockRejectedValueOnce(
38+
mockApiError({
39+
message:"New password should be different from the old password",
40+
}),
41+
);
42+
constcanvas=within(canvasElement);
43+
constuser=userEvent.setup();
44+
constnewPasswordInput=awaitcanvas.findByLabelText("Password *");
45+
awaituser.type(newPasswordInput,"password");
46+
constconfirmPasswordInput=
47+
awaitcanvas.findByLabelText("Confirm password *");
48+
awaituser.type(confirmPasswordInput,"different-password");
49+
awaituser.click(canvas.getByRole("button",{name:/resetpassword/i}));
50+
awaitcanvas.findByText("Passwords must match");
51+
},
52+
};
53+
54+
exportconstServerError:Story={
55+
play:async({ canvasElement})=>{
56+
constserverError=
57+
"New password should be different from the old password";
58+
spyOn(API,"changePasswordWithOTP").mockRejectedValueOnce(
59+
mockApiError({
60+
message:serverError,
61+
}),
62+
);
63+
constcanvas=within(canvasElement);
64+
constuser=userEvent.setup();
65+
constnewPasswordInput=awaitcanvas.findByLabelText("Password *");
66+
awaituser.type(newPasswordInput,"password");
67+
constconfirmPasswordInput=
68+
awaitcanvas.findByLabelText("Confirm password *");
69+
awaituser.type(confirmPasswordInput,"password");
70+
awaituser.click(canvas.getByRole("button",{name:/resetpassword/i}));
71+
awaitcanvas.findByText(serverError);
72+
},
73+
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp