forgot password fix
This commit is contained in:
parent
3338209fe5
commit
1e38bd67a0
@ -181,9 +181,13 @@ export class AuthController {
|
||||
return this.authService.resetPasswordWithToken(token, newPassword);
|
||||
}
|
||||
|
||||
// @Post("logout")
|
||||
// async logout(@Request() req) {
|
||||
// await this.authService.logout(req.user.userId);
|
||||
// return { message: "Logged out successfully" };
|
||||
// }
|
||||
@Post("validate-token")
|
||||
async validateToken(@Body() { token }: { token: string }) {
|
||||
if (!token || typeof token !== "string") {
|
||||
this.logger.warn("Invalid token input received");
|
||||
throw new BadRequestException("A valid token is required");
|
||||
}
|
||||
|
||||
return this.authService.validateToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,7 +327,8 @@ export class AuthService {
|
||||
|
||||
if (!resetRecord) {
|
||||
this.logger.warn("Invalid or expired reset token used");
|
||||
throw new UnauthorizedException("Invalid or expired reset token");
|
||||
// throw new UnauthorizedException("Invalid or expired reset token");
|
||||
return { isValid: false, message: "Invalid or expired token" };
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
@ -365,7 +366,7 @@ export class AuthService {
|
||||
});
|
||||
}
|
||||
|
||||
return { message: "Password reset successful" };
|
||||
return { isValid: true, message: "Password reset successful" };
|
||||
} catch (error) {
|
||||
this.logger.error("Error in resetPasswordWithToken:", {
|
||||
error: error.message,
|
||||
@ -376,8 +377,28 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
async validateToken(token: string): Promise<{ isValid: boolean }> {
|
||||
try {
|
||||
const resetRecord = await this.prisma.passwordReset.findFirst({
|
||||
where: {
|
||||
token,
|
||||
expiresAt: {
|
||||
gt: new Date(),
|
||||
},
|
||||
used: false,
|
||||
},
|
||||
});
|
||||
if (!resetRecord) {
|
||||
return { isValid: false };
|
||||
}
|
||||
return { isValid: true };
|
||||
} catch (error) {
|
||||
return { isValid: false };
|
||||
}
|
||||
}
|
||||
|
||||
async logout(userId: string): Promise<void> {
|
||||
return console.log("User logged out:", userId);
|
||||
this.logger.log("User logged out:", userId);
|
||||
return console.log("User logged out:", userId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,9 +227,9 @@ export class EmailService {
|
||||
|
||||
async sendPasswordResetEmail(email: string, name: string, token: string) {
|
||||
this.logger.log("Sending password reset email to:", email);
|
||||
const frontendUrl =
|
||||
this.configService.get<string>("FRONTEND_URL") || "https://placebo.mk";
|
||||
const resetLink = `${frontendUrl}/forgot-password?token=${token}`;
|
||||
const frontendUrl = "http://localhost:5173";
|
||||
// this.configService.get<string>("FRONTEND_URL") || "http://localhost:5147";
|
||||
const resetLink = `${frontendUrl}/reset-password?token=${token}`;
|
||||
const mailOptions = {
|
||||
from: this.from,
|
||||
to: email,
|
||||
|
||||
@ -1,64 +1,97 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
|
||||
import { resetPassword } from '../../services/api';
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useSearchParams, Link } from "react-router-dom";
|
||||
import { resetPassword } from "../../services/api";
|
||||
import { useAuth } from "../../hooks/useAuth";
|
||||
|
||||
export default function ResetPassword() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const { validateToken } = useAuth(); // Use validateToken from useAuth
|
||||
const [formData, setFormData] = useState({
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
const [status, setStatus] = useState({ type: '', message: '' });
|
||||
const [status, setStatus] = useState({ type: "", message: "" });
|
||||
const [loading, setLoading] = useState(false);
|
||||
const token = searchParams.get('token');
|
||||
const token = searchParams.get("token");
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
navigate('/forgot-password');
|
||||
}
|
||||
}, [token, navigate]);
|
||||
const checkToken = async () => {
|
||||
console.log("token received from validate-token ", token);
|
||||
if (!token) {
|
||||
console.log("no token found");
|
||||
navigate("/forgot-password");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const isValid = await validateToken(token); // Validate token using useAuth
|
||||
console.log("token validity", isValid);
|
||||
if (!isValid) {
|
||||
setStatus({
|
||||
type: "error",
|
||||
message:
|
||||
"Invalid or expired token. Please request a new reset link.",
|
||||
});
|
||||
setTimeout(() => navigate("/forgot-password"), 3000);
|
||||
}
|
||||
} catch {
|
||||
console.error("error during token validation", error);
|
||||
}
|
||||
};
|
||||
|
||||
checkToken();
|
||||
}, [token, navigate, validateToken]);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setStatus({
|
||||
type: 'error',
|
||||
message: 'Passwords do not match'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.password.length < 6) {
|
||||
setStatus({
|
||||
type: 'error',
|
||||
message: 'Password must be at least 6 characters long'
|
||||
type: "error",
|
||||
message: "Passwords do not match",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!isStrongPassword(formData.password)) {
|
||||
// setStatus({
|
||||
// type: "error",
|
||||
// message:
|
||||
// "Password must be at least 6 characters long and include uppercase, numbers, and special characters.",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
setLoading(true);
|
||||
setStatus({ type: '', message: '' });
|
||||
setStatus({ type: "", message: "" });
|
||||
|
||||
try {
|
||||
await resetPassword(token, formData.password);
|
||||
setStatus({
|
||||
type: 'success',
|
||||
message: 'Password reset successful. You can now login with your new password.'
|
||||
type: "success",
|
||||
message:
|
||||
"Password reset successful. You can now login with your new password.",
|
||||
});
|
||||
setFormData({ password: '', confirmPassword: '' });
|
||||
// Redirect to login after 3 seconds
|
||||
setTimeout(() => navigate('/login'), 3000);
|
||||
setFormData({ password: "", confirmPassword: "" });
|
||||
setTimeout(() => navigate("/login"), 3000);
|
||||
} catch (error) {
|
||||
console.error("Password reset error:", error);
|
||||
setStatus({
|
||||
type: 'error',
|
||||
message: error.response?.data?.message || 'Failed to reset password. Please try again.'
|
||||
type: "error",
|
||||
message:
|
||||
error.response?.data?.message ||
|
||||
"Failed to reset password. Please try again.",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isStrongPassword = (password) => {
|
||||
const strongPasswordRegex =
|
||||
/^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/;
|
||||
return strongPasswordRegex.test(password);
|
||||
};
|
||||
|
||||
if (!token) return null;
|
||||
|
||||
return (
|
||||
@ -77,9 +110,9 @@ export default function ResetPassword() {
|
||||
{status.message && (
|
||||
<div
|
||||
className={`p-4 rounded-lg ${
|
||||
status.type === 'success'
|
||||
? 'bg-green-500/10 border border-green-500/20 text-green-200'
|
||||
: 'bg-red-500/10 border border-red-500/20 text-red-200'
|
||||
status.type === "success"
|
||||
? "bg-green-500/10 border border-green-500/20 text-green-200"
|
||||
: "bg-red-500/10 border border-red-500/20 text-red-200"
|
||||
}`}
|
||||
>
|
||||
{status.message}
|
||||
@ -97,7 +130,9 @@ export default function ResetPassword() {
|
||||
type="password"
|
||||
required
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, password: e.target.value })
|
||||
}
|
||||
className="appearance-none relative block w-full px-3 py-2 border
|
||||
border-primary-600 bg-primary-700/30 placeholder-neutral-400
|
||||
text-white rounded-lg focus:outline-none focus:ring-primary-500
|
||||
@ -117,7 +152,9 @@ export default function ResetPassword() {
|
||||
type="password"
|
||||
required
|
||||
value={formData.confirmPassword}
|
||||
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, confirmPassword: e.target.value })
|
||||
}
|
||||
className="appearance-none relative block w-full px-3 py-2 border
|
||||
border-primary-600 bg-primary-700/30 placeholder-neutral-400
|
||||
text-white rounded-lg focus:outline-none focus:ring-primary-500
|
||||
@ -138,7 +175,7 @@ export default function ResetPassword() {
|
||||
focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50
|
||||
disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
{loading ? 'Resetting...' : 'Reset Password'}
|
||||
{loading ? "Resetting..." : "Reset Password"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -55,8 +55,21 @@ export const AuthProvider = ({ children }) => {
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
const validateToken = async (token) => {
|
||||
try {
|
||||
const response = await api.post("/auth/validate-token", { token });
|
||||
console.log(response.data.isValid);
|
||||
return response.data.isValid; // Assuming the API returns { isValid: true/false }
|
||||
} catch (error) {
|
||||
console.error("Token validation error:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, isLoading, error, login, logout }}>
|
||||
<AuthContext.Provider
|
||||
value={{ user, isLoading, error, login, logout, validateToken }}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
|
||||
0
frontend/src/pages/reset/Reset.jsx
Normal file
0
frontend/src/pages/reset/Reset.jsx
Normal file
Loading…
Reference in New Issue
Block a user