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);
|
return this.authService.resetPasswordWithToken(token, newPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Post("logout")
|
@Post("validate-token")
|
||||||
// async logout(@Request() req) {
|
async validateToken(@Body() { token }: { token: string }) {
|
||||||
// await this.authService.logout(req.user.userId);
|
if (!token || typeof token !== "string") {
|
||||||
// return { message: "Logged out successfully" };
|
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) {
|
if (!resetRecord) {
|
||||||
this.logger.warn("Invalid or expired reset token used");
|
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
|
// Hash new password
|
||||||
@ -365,7 +366,7 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { message: "Password reset successful" };
|
return { isValid: true, message: "Password reset successful" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Error in resetPasswordWithToken:", {
|
this.logger.error("Error in resetPasswordWithToken:", {
|
||||||
error: error.message,
|
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> {
|
async logout(userId: string): Promise<void> {
|
||||||
return console.log("User logged out:", userId);
|
|
||||||
this.logger.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) {
|
async sendPasswordResetEmail(email: string, name: string, token: string) {
|
||||||
this.logger.log("Sending password reset email to:", email);
|
this.logger.log("Sending password reset email to:", email);
|
||||||
const frontendUrl =
|
const frontendUrl = "http://localhost:5173";
|
||||||
this.configService.get<string>("FRONTEND_URL") || "https://placebo.mk";
|
// this.configService.get<string>("FRONTEND_URL") || "http://localhost:5147";
|
||||||
const resetLink = `${frontendUrl}/forgot-password?token=${token}`;
|
const resetLink = `${frontendUrl}/reset-password?token=${token}`;
|
||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: this.from,
|
from: this.from,
|
||||||
to: email,
|
to: email,
|
||||||
|
|||||||
@ -1,64 +1,97 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
|
import { useNavigate, useSearchParams, Link } from "react-router-dom";
|
||||||
import { resetPassword } from '../../services/api';
|
import { resetPassword } from "../../services/api";
|
||||||
|
import { useAuth } from "../../hooks/useAuth";
|
||||||
|
|
||||||
export default function ResetPassword() {
|
export default function ResetPassword() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { validateToken } = useAuth(); // Use validateToken from useAuth
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
password: '',
|
password: "",
|
||||||
confirmPassword: '',
|
confirmPassword: "",
|
||||||
});
|
});
|
||||||
const [status, setStatus] = useState({ type: '', message: '' });
|
const [status, setStatus] = useState({ type: "", message: "" });
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const token = searchParams.get('token');
|
const token = searchParams.get("token");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) {
|
const checkToken = async () => {
|
||||||
navigate('/forgot-password');
|
console.log("token received from validate-token ", token);
|
||||||
}
|
if (!token) {
|
||||||
}, [token, navigate]);
|
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) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (formData.password !== formData.confirmPassword) {
|
if (formData.password !== formData.confirmPassword) {
|
||||||
setStatus({
|
setStatus({
|
||||||
type: 'error',
|
type: "error",
|
||||||
message: 'Passwords do not match'
|
message: "Passwords do not match",
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formData.password.length < 6) {
|
|
||||||
setStatus({
|
|
||||||
type: 'error',
|
|
||||||
message: 'Password must be at least 6 characters long'
|
|
||||||
});
|
});
|
||||||
return;
|
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);
|
setLoading(true);
|
||||||
setStatus({ type: '', message: '' });
|
setStatus({ type: "", message: "" });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await resetPassword(token, formData.password);
|
await resetPassword(token, formData.password);
|
||||||
setStatus({
|
setStatus({
|
||||||
type: 'success',
|
type: "success",
|
||||||
message: 'Password reset successful. You can now login with your new password.'
|
message:
|
||||||
|
"Password reset successful. You can now login with your new password.",
|
||||||
});
|
});
|
||||||
setFormData({ password: '', confirmPassword: '' });
|
setFormData({ password: "", confirmPassword: "" });
|
||||||
// Redirect to login after 3 seconds
|
setTimeout(() => navigate("/login"), 3000);
|
||||||
setTimeout(() => navigate('/login'), 3000);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Password reset error:", error);
|
||||||
setStatus({
|
setStatus({
|
||||||
type: 'error',
|
type: "error",
|
||||||
message: error.response?.data?.message || 'Failed to reset password. Please try again.'
|
message:
|
||||||
|
error.response?.data?.message ||
|
||||||
|
"Failed to reset password. Please try again.",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isStrongPassword = (password) => {
|
||||||
|
const strongPasswordRegex =
|
||||||
|
/^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/;
|
||||||
|
return strongPasswordRegex.test(password);
|
||||||
|
};
|
||||||
|
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -77,9 +110,9 @@ export default function ResetPassword() {
|
|||||||
{status.message && (
|
{status.message && (
|
||||||
<div
|
<div
|
||||||
className={`p-4 rounded-lg ${
|
className={`p-4 rounded-lg ${
|
||||||
status.type === 'success'
|
status.type === "success"
|
||||||
? 'bg-green-500/10 border border-green-500/20 text-green-200'
|
? "bg-green-500/10 border border-green-500/20 text-green-200"
|
||||||
: 'bg-red-500/10 border border-red-500/20 text-red-200'
|
: "bg-red-500/10 border border-red-500/20 text-red-200"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{status.message}
|
{status.message}
|
||||||
@ -97,7 +130,9 @@ export default function ResetPassword() {
|
|||||||
type="password"
|
type="password"
|
||||||
required
|
required
|
||||||
value={formData.password}
|
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
|
className="appearance-none relative block w-full px-3 py-2 border
|
||||||
border-primary-600 bg-primary-700/30 placeholder-neutral-400
|
border-primary-600 bg-primary-700/30 placeholder-neutral-400
|
||||||
text-white rounded-lg focus:outline-none focus:ring-primary-500
|
text-white rounded-lg focus:outline-none focus:ring-primary-500
|
||||||
@ -117,7 +152,9 @@ export default function ResetPassword() {
|
|||||||
type="password"
|
type="password"
|
||||||
required
|
required
|
||||||
value={formData.confirmPassword}
|
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
|
className="appearance-none relative block w-full px-3 py-2 border
|
||||||
border-primary-600 bg-primary-700/30 placeholder-neutral-400
|
border-primary-600 bg-primary-700/30 placeholder-neutral-400
|
||||||
text-white rounded-lg focus:outline-none focus:ring-primary-500
|
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
|
focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50
|
||||||
disabled:cursor-not-allowed transition-colors"
|
disabled:cursor-not-allowed transition-colors"
|
||||||
>
|
>
|
||||||
{loading ? 'Resetting...' : 'Reset Password'}
|
{loading ? "Resetting..." : "Reset Password"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -154,4 +191,4 @@ export default function ResetPassword() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,8 +55,21 @@ export const AuthProvider = ({ children }) => {
|
|||||||
setUser(null);
|
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 (
|
return (
|
||||||
<AuthContext.Provider value={{ user, isLoading, error, login, logout }}>
|
<AuthContext.Provider
|
||||||
|
value={{ user, isLoading, error, login, logout, validateToken }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</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