Skip to content

Commit 218f556

Browse files
authored
Fix forgot password endpoint , fix password inputs (#970)
1 parent 6fdfbef commit 218f556

File tree

6 files changed

+78
-20
lines changed

6 files changed

+78
-20
lines changed

api/src/Feature.Auth/ForgotPassword/Endpoint.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,24 @@ public override void Configure()
2727
public override async Task<Results<Ok, ProblemHttpResult>> ExecuteAsync(Request request, CancellationToken ct)
2828
{
2929
var user = await userManager.FindByEmailAsync(request.Email.Trim());
30-
if (user is null || !await userManager.IsEmailConfirmedAsync(user))
30+
if (user is null)
3131
{
3232
logger.LogWarning("Possible user enumeration. Unknown email received {email}", request.Email);
3333
// Don't reveal that the user does not exist or is not confirmed
3434
return TypedResults.Ok();
3535
}
3636

37+
var mail = await userManager.IsEmailConfirmedAsync(user)
38+
? await GetResetPasswordEmail(user)
39+
: GetAcceptInviteEmail(user);
40+
41+
jobService.EnqueueSendEmail(request.Email, mail.Subject, mail.Body);
42+
43+
return TypedResults.Ok();
44+
}
45+
46+
private async Task<EmailModel> GetResetPasswordEmail(ApplicationUser user)
47+
{
3748
// For more information on how to enable account confirmation and password reset please
3849
// visit https://go.microsoft.com/fwlink/?LinkID=532713
3950
var code = await userManager.GeneratePasswordResetTokenAsync(user);
@@ -45,8 +56,21 @@ public override async Task<Results<Ok, ProblemHttpResult>> ExecuteAsync(Request
4556
var emailProps = new ResetPasswordEmailProps(FullName: user.DisplayName, CdnUrl: _apiConfig.WebAppUrl, ResetPasswordUrl: passwordResetUrl);
4657
var mail = emailFactory.GenerateResetPasswordEmail(emailProps);
4758

48-
jobService.EnqueueSendEmail(request.Email, mail.Subject, mail.Body);
59+
return mail;
60+
}
4961

50-
return TypedResults.Ok();
62+
private EmailModel GetAcceptInviteEmail(ApplicationUser user)
63+
{
64+
var endpointUri = new Uri(Path.Combine($"{_apiConfig.WebAppUrl}", "accept-invite"));
65+
string acceptInviteUrl =
66+
QueryHelpers.AddQueryString(endpointUri.ToString(), "invitationToken", user.InvitationToken);
67+
68+
var confirmEmailProps = new ConfirmEmailProps(
69+
CdnUrl: _apiConfig.WebAppUrl,
70+
FullName: user.DisplayName,
71+
ConfirmUrl: acceptInviteUrl);
72+
73+
var mail = emailFactory.GenerateConfirmAccountEmail(confirmEmailProps);
74+
return mail;
5175
}
5276
}

web/src/components/ui/password-input.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
import { EyeIcon, EyeOffIcon } from 'lucide-react';
2+
import * as React from 'react';
3+
14
import { Button } from '@/components/ui/button';
2-
import { Input, InputProps } from '@/components/ui/input';
5+
import { Input, type InputProps } from '@/components/ui/input';
36
import { cn } from '@/lib/utils';
4-
import { EyeIcon, EyeOffIcon } from 'lucide-react';
5-
import { forwardRef, useState } from 'react';
67

7-
const PasswordInput = forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
8-
const [showPassword, setShowPassword] = useState(false);
9-
const disabled = props['value'] === '' || props['value'] === undefined || props['disabled'];
8+
const PasswordInput = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
9+
const [showPassword, setShowPassword] = React.useState(false);
10+
const disabled = props.value === '' || props.value === undefined || props.disabled;
1011

1112
return (
1213
<div className='relative'>

web/src/features/auth/AcceptInvite.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Route as AcceptInviteRoute } from '@/routes/accept-invite/index';
1212
import { useMutation } from '@tanstack/react-query';
1313
import { noAuthApi } from '@/common/no-auth-api';
1414
import { toast } from '@/components/ui/use-toast';
15+
import { PasswordInput } from '@/components/ui/password-input';
1516

1617
const formSchema = z
1718
.object({
@@ -39,7 +40,7 @@ function AcceptInvite() {
3940

4041
const form = useForm<z.infer<typeof formSchema>>({
4142
resolver: zodResolver(formSchema),
42-
mode: 'all',
43+
mode: 'onChange',
4344
defaultValues: {
4445
password: '',
4546
confirmPassword: '',
@@ -99,7 +100,13 @@ function AcceptInvite() {
99100
<FormItem>
100101
<FormLabel>Password</FormLabel>
101102
<FormControl>
102-
<Input type='password' autoCorrect='off' autoCapitalize='none' autoComplete='off' {...field} />
103+
<PasswordInput
104+
autoCorrect='off'
105+
autoCapitalize='off'
106+
autoComplete='off'
107+
spellCheck='false'
108+
{...field}
109+
/>
103110
</FormControl>
104111
<FormMessage />
105112
</FormItem>
@@ -113,7 +120,13 @@ function AcceptInvite() {
113120
<FormItem>
114121
<FormLabel>Confirm password</FormLabel>
115122
<FormControl>
116-
<Input type='password' {...field} />
123+
<PasswordInput
124+
autoCorrect='off'
125+
autoCapitalize='off'
126+
autoComplete='off'
127+
spellCheck='false'
128+
{...field}
129+
/>
117130
</FormControl>
118131
<FormMessage />
119132
</FormItem>

web/src/features/auth/ForgotPassword.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface ForgotPasswordRequest {
2727
function ForgotPassword() {
2828
const form = useForm<z.infer<typeof formSchema>>({
2929
resolver: zodResolver(formSchema),
30-
mode: 'all',
30+
mode: 'onChange',
3131
defaultValues: {
3232
email: '',
3333
},
@@ -72,7 +72,7 @@ function ForgotPassword() {
7272
name='email'
7373
render={({ field }) => (
7474
<FormItem>
75-
<FormLabel>Password</FormLabel>
75+
<FormLabel>Email</FormLabel>
7676
<FormControl>
7777
<Input type='email' {...field} />
7878
</FormControl>
@@ -83,7 +83,7 @@ function ForgotPassword() {
8383
</CardContent>
8484
<CardFooter>
8585
<Button type='submit' className='w-full'>
86-
Reset password
86+
Request password reset
8787
</Button>
8888
</CardFooter>
8989
</Card>

web/src/features/auth/Login.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { LoginDTO } from '@/common/auth-api';
1111
import { useNavigate } from '@tanstack/react-router';
1212
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
1313
import Logo from '@/components/layout/Header/Logo';
14+
import { PasswordInput } from '@/components/ui/password-input';
1415

1516
const formSchema = z.object({
1617
email: z
@@ -27,7 +28,7 @@ function Login() {
2728
const navigate = useNavigate();
2829
const form = useForm<z.infer<typeof formSchema>>({
2930
resolver: zodResolver(formSchema),
30-
mode: 'all',
31+
mode: 'onChange',
3132
defaultValues: {
3233
email: '',
3334
password: '',
@@ -77,7 +78,13 @@ function Login() {
7778
<FormItem>
7879
<FormLabel>Password</FormLabel>
7980
<FormControl>
80-
<Input type='password' {...field} />
81+
<PasswordInput
82+
autoCorrect='off'
83+
autoCapitalize='off'
84+
autoComplete='off'
85+
spellCheck='false'
86+
{...field}
87+
/>
8188
</FormControl>
8289
<FormMessage />
8390
</FormItem>

web/src/features/auth/ResetPassword.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { noAuthApi } from '@/common/no-auth-api';
1414
import { toast } from '@/components/ui/use-toast';
1515
import { useNavigate } from '@tanstack/react-router';
1616
import type { FunctionComponent } from '@/common/types';
17+
import { PasswordInput } from '@/components/ui/password-input';
1718

1819
interface ResetPasswordRequest {
1920
password: string;
@@ -48,7 +49,7 @@ function ResetPassword(): FunctionComponent {
4849

4950
const form = useForm<z.infer<typeof formSchema>>({
5051
resolver: zodResolver(formSchema),
51-
mode: 'all',
52+
mode: 'onChange',
5253
defaultValues: {
5354
email: '',
5455
password: '',
@@ -113,7 +114,13 @@ function ResetPassword(): FunctionComponent {
113114
<FormItem>
114115
<FormLabel>Password</FormLabel>
115116
<FormControl>
116-
<Input type='password' {...field} />
117+
<PasswordInput
118+
autoCorrect='off'
119+
autoCapitalize='off'
120+
autoComplete='off'
121+
spellCheck='false'
122+
{...field}
123+
/>
117124
</FormControl>
118125
<FormMessage />
119126
</FormItem>
@@ -127,7 +134,13 @@ function ResetPassword(): FunctionComponent {
127134
<FormItem>
128135
<FormLabel>Confirm your password</FormLabel>
129136
<FormControl>
130-
<Input type='password' autoCorrect='off' autoCapitalize='none' autoComplete='off' {...field} />
137+
<PasswordInput
138+
autoCorrect='off'
139+
autoCapitalize='off'
140+
autoComplete='off'
141+
spellCheck='false'
142+
{...field}
143+
/>
131144
</FormControl>
132145
<FormMessage />
133146
</FormItem>

0 commit comments

Comments
 (0)