Skip to content

Commit 788e582

Browse files
committed
Add ability to change user password
Fixes: #21
1 parent e971bbb commit 788e582

File tree

6 files changed

+125
-2
lines changed

6 files changed

+125
-2
lines changed

apps/api/src/auth/auth.controller.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ import {
1010
} from '@nestjs/common';
1111
import { ApiTags } from '@nestjs/swagger';
1212
import { Request } from 'express';
13+
import { Types } from 'mongoose';
1314
import { PrivateUserDto } from 'shared-types';
15+
import { AuthEmailsService } from '../auth-emails/auth-emails.service';
1416
import { DemoService } from '../demo/demo.service';
17+
import { NotDemoGuard } from '../models/users/guards/not-demo.guard';
1518
import { User } from '../models/users/schemas/user.schema';
1619
import { AuthService } from './auth.service';
20+
import { ChangePasswordDto } from './dto/change-password.dto';
1721
import { UserRegisterDto } from './dto/user-register.dto';
1822
import { AuthenticatedGuard } from './guards/authenticated.guard';
1923
import { LocalAuthGuard } from './guards/local-auth.guard';
20-
import { AuthEmailsService } from '../auth-emails/auth-emails.service';
2124

2225
@Controller('auth')
2326
@ApiTags('auth')
@@ -70,4 +73,16 @@ export class AuthController {
7073
const dto = User.toPrivateDto(user);
7174
return dto;
7275
}
76+
77+
@Post('change-password')
78+
@UseGuards(NotDemoGuard)
79+
async changePassword(
80+
@Req() request: Request,
81+
@Body() body: ChangePasswordDto,
82+
): Promise<PrivateUserDto> {
83+
const userId = new Types.ObjectId(request.user.id);
84+
const user = await this.authService.validateUserByUserId(userId, body.oldPassword);
85+
await this.authService.updateUserPassword(user._id, body.newPassword);
86+
return User.toPrivateDto(user);
87+
}
7388
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Length } from 'class-validator';
2+
import { IChangePasswordDto } from 'shared-types';
3+
4+
export class ChangePasswordDto implements IChangePasswordDto {
5+
@Length(4, 32)
6+
oldPassword: string;
7+
8+
@Length(4, 32)
9+
newPassword: string;
10+
}

apps/client/src/components/Pages/User/UserPasswordChangeTab.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import UserChangePasswordForm from '../../User/UserChangePasswordForm';
2+
13
function UserPasswordChangeTab() {
24
return (
35
<div className="mt-8">
46
<h2 className="mb-4 text-3xl">Change password</h2>
7+
<UserChangePasswordForm />
58
</div>
69
);
710
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import axios from 'axios';
2+
import { useContext, useState } from 'react';
3+
import { useForm } from 'react-hook-form';
4+
import toast from 'react-hot-toast';
5+
import { IChangePasswordDto } from 'shared-types';
6+
import { UserContext } from '../../context/UserContext';
7+
import { Utils } from '../../utils/utils';
8+
import Form from '../Form/Form';
9+
import FormField from '../Form/FormField';
10+
import FormInput from '../Form/FormInput';
11+
import FormSubmitButton from '../Form/FormSubmitButton';
12+
import Alert from '../Helpers/Alert';
13+
14+
type Inputs = {
15+
oldPassword: string;
16+
newPassword: string;
17+
newPasswordConfirm: string;
18+
};
19+
20+
function UserChangePasswordForm() {
21+
const { register, handleSubmit } = useForm<Inputs>();
22+
const [loading, setLoading] = useState(false);
23+
const [error, setError] = useState<string | null>(null);
24+
25+
const { logout } = useContext(UserContext);
26+
27+
function onSubmit(inputs: Inputs) {
28+
setLoading(true);
29+
setError(null);
30+
31+
const dto: IChangePasswordDto = inputs;
32+
33+
axios
34+
.post<void>(`/api/auth/change-password`, dto)
35+
.then(async () => {
36+
await logout();
37+
toast.success(`Successfully changed password`);
38+
})
39+
.catch((err) => setError(Utils.requestErrorToString(err)))
40+
.finally(() => setLoading(false));
41+
}
42+
43+
return (
44+
<Form
45+
onSubmit={handleSubmit(onSubmit)}
46+
loading={loading}
47+
>
48+
{error && <Alert>{error}</Alert>}
49+
50+
<FormField
51+
label="Current password"
52+
hint="Confirm your current password"
53+
required
54+
>
55+
<FormInput
56+
type="password"
57+
required
58+
{...register('oldPassword', { required: true })}
59+
/>
60+
</FormField>
61+
62+
<FormField
63+
label="New password"
64+
required
65+
>
66+
<FormInput
67+
type="password"
68+
required
69+
{...register('newPassword', { required: true })}
70+
/>
71+
</FormField>
72+
73+
<FormField
74+
label="Re-type new password"
75+
required
76+
>
77+
<FormInput
78+
type="password"
79+
required
80+
{...register('newPasswordConfirm', { required: true })}
81+
/>
82+
</FormField>
83+
84+
<FormSubmitButton>Change password</FormSubmitButton>
85+
<span className="ms-4 text-muted">(You will be logged out)</span>
86+
</Form>
87+
);
88+
}
89+
export default UserChangePasswordForm;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface IChangePasswordDto {
2+
oldPassword: string;
3+
newPassword: string;
4+
}

packages/shared-types/src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IImageDto } from './ImageDto';
22
import { OrganizationSecurityRole } from './OrganizationSecurityRole';
33
import { SimpleResponseDto } from './SimpleResponseDto';
4+
import { IChangePasswordDto } from './auth/IChangePasswordDto';
45
import { IResetPasswordDto } from './auth/IResetPasswordDto';
56
import { IUserRegisterDto } from './auth/IUserRegisterDto';
67
import { UserLoginDto } from './auth/UserLoginDto';
@@ -67,6 +68,7 @@ export {
6768
UserDto,
6869
UserLoginDto,
6970
WarehouseDto,
70-
IResetPasswordDto
71+
IResetPasswordDto,
72+
IChangePasswordDto
7173
};
7274

0 commit comments

Comments
 (0)