Skip to content

Commit 1dc0249

Browse files
author
Emile Frey
committed
password change now requires user to provide old password
1 parent d048021 commit 1dc0249

File tree

6 files changed

+80
-9
lines changed

6 files changed

+80
-9
lines changed

backend/users/serializers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from rest_framework import serializers
2+
from django.contrib.auth.models import User
3+
4+
class ChangePasswordSerializer(serializers.Serializer):
5+
model = User
6+
7+
"""
8+
Serializer for password change endpoint.
9+
"""
10+
old_password = serializers.CharField(required=True)
11+
new_password1 = serializers.CharField(required=True)
12+
new_password2 = serializers.CharField(required=True)

backend/users/urls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from django.urls import path, include
2+
from users.views import APILoginView, APILogoutView, ChangePasswordView
23

3-
from users.views import APILoginView, APILogoutView, APIPasswordUpdateView
44
urlpatterns = [
55
path('login/', APILoginView.as_view(), name='api_login'),
66
path('logout/', APILogoutView.as_view(), name='api_logout'),
7-
path('update_password/', APIPasswordUpdateView.as_view(), name='api_update_password'),
7+
path('change_password/', ChangePasswordView.as_view(), name='change_password'),
88
]

backend/users/views.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
from rest_auth.views import (LoginView, LogoutView, PasswordChangeView)
1+
from rest_auth.views import (LoginView, LogoutView)
22
from rest_framework.authentication import TokenAuthentication
33
from rest_framework.permissions import IsAuthenticated
4+
from rest_framework import status
5+
from rest_framework import generics
6+
from rest_framework.response import Response
7+
from django.contrib.auth.models import User
8+
from .serializers import ChangePasswordSerializer
9+
from rest_framework.permissions import IsAuthenticated
410

511
# Create your views here.
612
class APILogoutView(LogoutView):
@@ -10,5 +16,44 @@ class APILogoutView(LogoutView):
1016
class APILoginView(LoginView):
1117
pass
1218

13-
class APIPasswordUpdateView(PasswordChangeView):
14-
authentication_classes = [TokenAuthentication]
19+
class ChangePasswordView(generics.UpdateAPIView):
20+
"""
21+
An endpoint for changing password.
22+
"""
23+
serializer_class = ChangePasswordSerializer
24+
model = User
25+
authentication_classes = [TokenAuthentication]
26+
permission_classes = (IsAuthenticated,)
27+
28+
def get_object(self, queryset=None):
29+
obj = self.request.user
30+
return obj
31+
32+
def post(self, request, *args, **kwargs):
33+
self.object = self.get_object()
34+
serializer = self.get_serializer(data=request.data)
35+
36+
if serializer.is_valid():
37+
# Check old password
38+
if not self.object.check_password(serializer.data.get("old_password")):
39+
response = {
40+
'data': [{'status': 'The old password is incorrect.'}]
41+
}
42+
return Response({"old_password": ["The old password you provided is incorrect."]}, status=status.HTTP_400_BAD_REQUEST)
43+
new_password1 = serializer.data.get('new_password1')
44+
new_password2 = serializer.data.get('new_password2')
45+
if new_password1 != new_password2:
46+
return Response({"error": ["Passwords do not match."]})
47+
# set_password also hashes the password that the user will get
48+
self.object.set_password(new_password1)
49+
self.object.save()
50+
response = {
51+
'status': 'success',
52+
'code': status.HTTP_200_OK,
53+
'message': 'Password updated successfully',
54+
'data': [{'status': 'Password updated successfully!'}]
55+
}
56+
57+
return Response(response)
58+
59+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

frontend/src/components/Layout/TopBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function TopBar(props: AppProps) {
3131
{props.isAuthenticated && (
3232
<DropdownMenu dropdownButtonIcon={<AccountCircle />}>
3333
<div>
34-
<MenuItem component='a' href='/update_password'>Change Password</MenuItem>
34+
<MenuItem component='a' href='/change_password'>Change Password</MenuItem>
3535
<MenuItem onClick={() => props.logout()}>Logout</MenuItem>
3636
</div>
3737
</DropdownMenu>

frontend/src/components/Login/PasswordUpdate.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function PasswordUpdate(props: AuthProps) {
3737
const classes = useStyles();
3838
const [new_password1, setNewPassword1] = useState("");
3939
const [new_password2, setNewPassword2] = useState("");
40+
const [oldPassword, setOldPassword] = useState("");
4041
const [success, setSuccess] = useState(false);
4142
const [validationErrors, setValidationErrors] = useState<Record<string, string[]>>({})
4243
const [isLoading, setIsLoading] = useState(false)
@@ -46,6 +47,7 @@ function PasswordUpdate(props: AuthProps) {
4647
switch (event.target.id) {
4748
case 'new_password1': setNewPassword1(event.target.value); break;
4849
case 'new_password2': setNewPassword2(event.target.value); break;
50+
case 'old_password': setOldPassword(event.target.value); break;
4951
default: return null;
5052
}
5153
setValidationErrors({})
@@ -62,12 +64,13 @@ function PasswordUpdate(props: AuthProps) {
6264
else {
6365
let headers = { 'Authorization': `Token ${props.token}` };
6466
const method = 'POST';
65-
let url = settings.API_SERVER + '/api/auth/update_password/';
67+
let url = settings.API_SERVER + '/api/auth/change_password/';
6668
let passwordFormData = new FormData();
69+
passwordFormData.append("old_password", oldPassword);
6770
passwordFormData.append("new_password1", new_password1);
6871
passwordFormData.append("new_password2", new_password2);
6972
let config: AxiosRequestConfig = { headers, method, url, data: passwordFormData };
70-
//Axios update_password API call
73+
//Axios change_password API call
7174
axios(config).then((res: any) => {
7275
setSuccess(true);
7376
}).catch(
@@ -94,6 +97,17 @@ function PasswordUpdate(props: AuthProps) {
9497
Update Password
9598
</Typography>
9699
<form className={classes.form} noValidate onSubmit={handleSubmit}>
100+
<TextField
101+
variant="outlined"
102+
margin="normal"
103+
required
104+
fullWidth
105+
name="old_password"
106+
label="Old Password"
107+
type="password"
108+
id="old_password"
109+
onChange={handleFormFieldChange}
110+
/>
97111
<TextField
98112
variant="outlined"
99113
margin="normal"

frontend/src/routes/Router.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default function Router(props: AppProps) {
1111
return (
1212
<Switch>
1313
<Route exact path="/login/"> <Login {...props} /></Route>
14-
<PrivateRoute exact path="/update_password/" isAuthenticated={props.isAuthenticated}><PasswordUpdate {...props} /></PrivateRoute>
14+
<PrivateRoute exact path="/change_password/" isAuthenticated={props.isAuthenticated}><PasswordUpdate {...props} /></PrivateRoute>
1515
{privateRoutes.map((route, index) =>
1616
<PrivateRoute
1717
key={index}

0 commit comments

Comments
 (0)