Skip to content

Commit d8f142e

Browse files
authored
Merge pull request #27 from DevOps-Cloud-Team5/SCRUM-31_pw_reset
Scrum 31 pw reset
2 parents f9cf66b + 1ffd0d6 commit d8f142e

File tree

7 files changed

+442
-90
lines changed

7 files changed

+442
-90
lines changed

src/pages/login/index.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ const SignIn = () => {
127127
</Button>
128128
<Grid container>
129129
<Grid item xs>
130-
<Link href="#" variant="body2">
130+
<Link
131+
href="/reset_password_request"
132+
variant="body2"
133+
>
131134
Forgot password?
132135
</Link>
133136
</Grid>

src/pages/reset_password/index.tsx

+165-82
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import React from "react";
1+
import React, { useEffect, useState } from "react";
22
import { useNavigate } from "react-router-dom";
3-
import Cookies from "js-cookie";
43

54
import Button from "@mui/material/Button";
65
import TextField from "@mui/material/TextField";
@@ -12,104 +11,188 @@ import { Box } from "@mui/material";
1211

1312
import RootPage from "../root";
1413
import "./reset_password.css"; // Import CSS file for additional styling
15-
import { backend_post, expire_time } from "../../utils";
16-
import { TokenResponse } from "../../types/common";
14+
import { useAxiosRequest } from "../../utils";
15+
import { Empty } from "../../types/common";
16+
17+
interface ChangePasswordRequestParams {
18+
token: string;
19+
password: string;
20+
}
21+
interface ValidateTokenRequestParams {
22+
token: string;
23+
}
24+
interface ValidateTokenRequestResponse {
25+
detail?: string;
26+
status?: string;
27+
}
1728

1829
const ResetPassword = () => {
1930
const navigate = useNavigate();
31+
const [newPassword, setNewPassword] = useState("");
32+
const [confirmPassword, setConfirmPassword] = useState("");
33+
const [passwordError, setPasswordError] = useState("");
34+
const [loading, setLoading] = useState(true);
35+
36+
// API Request
37+
const ChangePassword = useAxiosRequest<
38+
ChangePasswordRequestParams,
39+
Empty
40+
>();
41+
const ValidateToken = useAxiosRequest<
42+
ValidateTokenRequestParams,
43+
ValidateTokenRequestResponse
44+
>();
45+
const validateRequest = ValidateToken.sendRequest;
2046

21-
const handleTokenResponse = (data: TokenResponse) => {
22-
const expire_date = new Date(new Date().getTime() + expire_time * 1000);
23-
Cookies.set("token_access", data["access"], { expires: expire_date });
24-
Cookies.set("token_refresh", data["refresh"], { expires: expire_date });
25-
Cookies.set("token_spawned", (Date.now() / 1000).toString(), {
26-
expires: expire_date
47+
// Get the token from query
48+
const queryParams = new URLSearchParams(location.search);
49+
let token = queryParams.get("token");
50+
if (token == null) token = "";
51+
52+
useEffect(() => {
53+
if (token === "") {
54+
navigate("/login");
55+
navigate(0);
56+
}
57+
const tokenParam: ValidateTokenRequestParams = {
58+
token: token
59+
};
60+
validateRequest({
61+
method: "POST",
62+
route: "password_reset/validate_token",
63+
data: tokenParam,
64+
useJWT: false
2765
});
28-
navigate(0);
29-
navigate("/home", { replace: true });
30-
};
66+
}, [navigate, token, validateRequest]);
3167

32-
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
68+
useEffect(() => {
69+
if (ValidateToken.error != null) {
70+
navigate("/login");
71+
navigate(0);
72+
}
73+
}, [navigate, ValidateToken.error]);
74+
75+
useEffect(() => {
76+
if (ValidateToken.loading) {
77+
setLoading(false);
78+
}
79+
}, [ValidateToken.loading]);
80+
81+
const handleSubmit = async (event: React.FormEvent) => {
3382
event.preventDefault();
34-
const data = new FormData(event.currentTarget);
35-
console.log({
36-
username: data.get("username"),
37-
password: data.get("password")
38-
});
3983

40-
backend_post(
41-
"token/",
42-
JSON.stringify({
43-
username: data.get("username"),
44-
password: data.get("password")
84+
// Reset error state
85+
setPasswordError("");
86+
87+
// Basic client-side validations
88+
if (newPassword !== confirmPassword) {
89+
setPasswordError("Passwords do not match.");
90+
return;
91+
}
92+
93+
if (newPassword.length < 8) {
94+
setPasswordError("Password must be at least 8 characters long.");
95+
return;
96+
}
97+
98+
// Basic common password check (for demonstration; consider using a list)
99+
const commonPasswords = ["password", "12345678", "qwerty"];
100+
if (commonPasswords.includes(newPassword.toLowerCase())) {
101+
setPasswordError("Password is too common.");
102+
return;
103+
}
104+
105+
// Check if the password is entirely numeric
106+
if (/^\d+$/.test(newPassword)) {
107+
setPasswordError("Password cannot be entirely numeric.");
108+
return;
109+
}
110+
const requestParams: ChangePasswordRequestParams = {
111+
token: token,
112+
password: newPassword
113+
};
114+
115+
ChangePassword.sendRequest({
116+
method: "POST",
117+
route: "/password_reset/confirm/",
118+
data: requestParams,
119+
useJWT: false
120+
})
121+
.then(() => {
122+
navigate("/login");
123+
navigate(0);
45124
})
46-
)
47-
.then((resp) => resp.json())
48-
.then((data) => handleTokenResponse(data))
49-
.catch((error) => console.log(error));
125+
.catch((error) => {
126+
console.error("Password reset request failed:", error);
127+
setPasswordError("Failed to reset password. Please try again.");
128+
});
50129
};
51130

52131
return (
53132
<RootPage>
54133
<Container component="main" maxWidth="xs">
55-
<Box
56-
sx={{
57-
marginTop: 8,
58-
display: "flex",
59-
flexDirection: "column",
60-
alignItems: "center"
61-
}}
62-
>
63-
<Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
64-
<LockOutlinedIcon />
65-
</Avatar>
66-
<Typography component="h1" variant="h5">
67-
Change Password
68-
</Typography>
134+
{loading || ValidateToken.loading ? (
135+
<div>Loading...</div> // Added loading state feedback
136+
) : (
69137
<Box
70-
component="form"
71-
onSubmit={handleSubmit}
72-
noValidate
73-
sx={{ mt: 1 }}
74-
color={"primary"}
138+
sx={{
139+
marginTop: 8,
140+
display: "flex",
141+
flexDirection: "column",
142+
alignItems: "center"
143+
}}
75144
>
76-
<TextField
77-
margin="normal"
78-
required
79-
fullWidth
80-
name="old_password"
81-
label="Previous Password"
82-
type="password"
83-
id="old_password"
84-
/>
85-
<TextField
86-
margin="normal"
87-
required
88-
fullWidth
89-
name="new_password"
90-
label="New Password"
91-
type="password"
92-
id="new_password"
93-
/>
94-
<TextField
95-
margin="normal"
96-
required
97-
fullWidth
98-
name="confirm_password"
99-
label="Confirm Password"
100-
type="password"
101-
id="confirm_password"
102-
/>
103-
<Button
104-
type="submit"
105-
fullWidth
106-
variant="contained"
107-
sx={{ mt: 3, mb: 2 }}
108-
>
145+
<Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
146+
<LockOutlinedIcon />
147+
</Avatar>
148+
<Typography component="h1" variant="h5">
109149
Change Password
110-
</Button>
150+
</Typography>
151+
<Box
152+
component="form"
153+
onSubmit={handleSubmit}
154+
noValidate
155+
sx={{ mt: 1 }}
156+
color={"primary"}
157+
>
158+
<TextField
159+
margin="normal"
160+
required
161+
fullWidth
162+
name="new_password"
163+
label="New Password"
164+
type="password"
165+
id="new_password"
166+
onChange={(e) => setNewPassword(e.target.value)}
167+
/>
168+
<TextField
169+
margin="normal"
170+
required
171+
fullWidth
172+
name="confirm_password"
173+
label="Confirm Password"
174+
type="password"
175+
id="confirm_password"
176+
onChange={(e) =>
177+
setConfirmPassword(e.target.value)
178+
}
179+
/>
180+
<Button
181+
type="submit"
182+
fullWidth
183+
variant="contained"
184+
sx={{ mt: 3, mb: 2 }}
185+
>
186+
Change Password
187+
</Button>
188+
{passwordError && (
189+
<Typography color="error" textAlign="center">
190+
{passwordError}
191+
</Typography>
192+
)}
193+
</Box>
111194
</Box>
112-
</Box>
195+
)}
113196
</Container>
114197
</RootPage>
115198
);
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React, { useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
4+
import Button from "@mui/material/Button";
5+
import TextField from "@mui/material/TextField";
6+
import Container from "@mui/material/Container";
7+
import Avatar from "@mui/material/Avatar";
8+
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
9+
import Typography from "@mui/material/Typography";
10+
import { Box } from "@mui/material";
11+
12+
import RootPage from "../root";
13+
import "./reset_password_request.css"; // Import CSS file for additional styling
14+
import { useAxiosRequest } from "../../utils";
15+
import { Empty } from "../../types/common";
16+
17+
interface RequestParams {
18+
email: string;
19+
}
20+
const ResetPasswordRequest = () => {
21+
const navigate = useNavigate();
22+
const [email, setEmail] = useState("");
23+
const { sendRequest } = useAxiosRequest<RequestParams, Empty>();
24+
25+
const handleSubmit = async (event: React.FormEvent) => {
26+
event.preventDefault();
27+
28+
const requestParams: RequestParams = {
29+
email: email
30+
};
31+
32+
sendRequest({
33+
method: "POST",
34+
route: "/password_reset/",
35+
data: requestParams,
36+
useJWT: false
37+
})
38+
.then(() => {
39+
navigate("/password_reset_requested");
40+
navigate(0);
41+
})
42+
.catch((error) => {
43+
console.error("Password reset request failed:", error);
44+
});
45+
};
46+
47+
return (
48+
<RootPage>
49+
<Container component="main" maxWidth="xs">
50+
<Box
51+
sx={{
52+
marginTop: 8,
53+
display: "flex",
54+
flexDirection: "column",
55+
alignItems: "center"
56+
}}
57+
>
58+
<Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
59+
<LockOutlinedIcon />
60+
</Avatar>
61+
<Typography component="h1" variant="h5">
62+
Change Password
63+
</Typography>
64+
<Box
65+
component="form"
66+
onSubmit={handleSubmit}
67+
noValidate
68+
sx={{ mt: 1 }}
69+
color={"primary"}
70+
>
71+
<TextField
72+
margin="normal"
73+
required
74+
fullWidth
75+
id="email"
76+
label="Email"
77+
name="email"
78+
autoComplete="email"
79+
autoFocus
80+
color="primary"
81+
onChange={(e) => setEmail(e.target.value)}
82+
/>
83+
<Button
84+
type="submit"
85+
fullWidth
86+
variant="contained"
87+
sx={{ mt: 3, mb: 2 }}
88+
>
89+
Request Password Reset
90+
</Button>
91+
</Box>
92+
</Box>
93+
</Container>
94+
</RootPage>
95+
);
96+
};
97+
98+
export default ResetPasswordRequest;

0 commit comments

Comments
 (0)