Auth #1
@@ -233,6 +233,92 @@ The new password must contain at least one uppercase letter, one lowercase lette
|
||||
|
||||
---
|
||||
|
||||
## Change Password
|
||||
|
||||
Changes the authenticated user's password. After a successful change, all refresh sessions are revoked, authentication cookies are cleared, and the user must log in again.
|
||||
|
||||
**Endpoint:** `POST` [http://localhost:3070/api/auth/change-password](http://localhost:3070/api/auth/change-password)
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
The access token may be supplied through the `access_token` cookie or as a Bearer token:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <access-token>
|
||||
```
|
||||
|
||||
### Request body
|
||||
|
||||
```json
|
||||
{
|
||||
"currentPassword": "CurrentPassword@1234",
|
||||
"newPassword": "NewPassword@5678",
|
||||
"confirmPassword": "NewPassword@5678"
|
||||
}
|
||||
```
|
||||
|
||||
The new password:
|
||||
|
||||
- Must match `confirmPassword`
|
||||
- Must differ from the current password
|
||||
- Must contain at least one uppercase letter
|
||||
- Must contain at least one lowercase letter
|
||||
- Must contain at least one symbol
|
||||
- Must contain at least four digits
|
||||
|
||||
### Success response — `200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Password changed successfully. Please login again."
|
||||
}
|
||||
```
|
||||
|
||||
### Error responses
|
||||
|
||||
#### `400 Bad Request`
|
||||
|
||||
Returned when required fields are missing, the passwords do not match, the new password does not satisfy the password policy, or it matches the current password.
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "New password and confirm password do not match"
|
||||
}
|
||||
```
|
||||
|
||||
#### `401 Unauthorized`
|
||||
|
||||
Returned when authentication fails or the current password is incorrect.
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Current password is incorrect"
|
||||
}
|
||||
```
|
||||
|
||||
#### `404 Not Found`
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "User not found"
|
||||
}
|
||||
```
|
||||
|
||||
#### `500 Internal Server Error`
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Failed to change password"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logout
|
||||
|
||||
Deletes the current refresh session when available and clears both authentication cookies.
|
||||
|
||||
@@ -374,7 +374,119 @@ exports.resetPassword = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
exports.changePassword = async (req, res) => {
|
||||
try {
|
||||
// User ID comes from authenticate middleware
|
||||
const userId = req.user.id;
|
||||
|
||||
const { currentPassword, newPassword, confirmPassword } = req.body;
|
||||
|
||||
// 1. Check required fields
|
||||
if (!currentPassword || !newPassword || !confirmPassword) {
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message:
|
||||
"Current password, new password and confirm password are required",
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Check new password and confirmation
|
||||
if (newPassword !== confirmPassword) {
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message: "New password and confirm password do not match",
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Validate password policy
|
||||
const passwordValid = validatePassword(newPassword);
|
||||
|
||||
if (!passwordValid) {
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message: "New password does not meet the required criteria",
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Get logged-in user from database
|
||||
const user = await User.findByPk(userId);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).send({
|
||||
success: false,
|
||||
message: "User not found",
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Check current password
|
||||
const currentPasswordValid = await checkPassword(
|
||||
currentPassword,
|
||||
user.password,
|
||||
);
|
||||
|
||||
if (!currentPasswordValid) {
|
||||
return res.status(401).send({
|
||||
success: false,
|
||||
message: "Current password is incorrect",
|
||||
});
|
||||
}
|
||||
|
||||
// 6. Make sure new password is different
|
||||
const sameAsOldPassword = await checkPassword(newPassword, user.password);
|
||||
|
||||
if (sameAsOldPassword) {
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message: "New password must be different from current password",
|
||||
});
|
||||
}
|
||||
|
||||
// 7. Hash new password
|
||||
const hashedPassword = await hashPassword(newPassword);
|
||||
|
||||
// 8. Update user
|
||||
user.password = hashedPassword;
|
||||
user.passwordChangedAt = new Date();
|
||||
|
||||
await user.save();
|
||||
|
||||
// 9. Revoke all refresh sessions
|
||||
deleteAllUserSessions(user.id);
|
||||
|
||||
// 10. Clear auth cookies
|
||||
res.clearCookie("access_token", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
||||
});
|
||||
|
||||
res.clearCookie("refresh_token", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
||||
});
|
||||
|
||||
// 11. Send confirmation email
|
||||
try {
|
||||
await sendPasswordChangedEmail(user.email, user.firstName);
|
||||
} catch (mailError) {
|
||||
console.error("PASSWORD CHANGED EMAIL ERROR:", mailError);
|
||||
}
|
||||
|
||||
// 12. Response
|
||||
return res.status(200).send({
|
||||
success: true,
|
||||
message: "Password changed successfully. Please login again.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("CHANGE PASSWORD ERROR:", error);
|
||||
|
||||
return res.status(500).send({
|
||||
success: false,
|
||||
message: "Failed to change password",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Logout: Clear the JWT cookie
|
||||
exports.logout = async (req, res) => {
|
||||
@@ -390,9 +502,7 @@ exports.logout = async (req, res) => {
|
||||
if (session) {
|
||||
deleteRefreshSession(session.sessionId);
|
||||
|
||||
console.log(
|
||||
`Refresh session deleted: ${session.sessionId}`
|
||||
);
|
||||
console.log(`Refresh session deleted: ${session.sessionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,20 +510,14 @@ exports.logout = async (req, res) => {
|
||||
res.clearCookie("access_token", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "None"
|
||||
: "Lax",
|
||||
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
||||
});
|
||||
|
||||
// 5. Clear refresh token cookie
|
||||
res.clearCookie("refresh_token", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "None"
|
||||
: "Lax",
|
||||
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
||||
});
|
||||
|
||||
// 6. Send response
|
||||
@@ -421,7 +525,6 @@ exports.logout = async (req, res) => {
|
||||
success: true,
|
||||
message: "Logged out successfully",
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("LOGOUT ERROR:", error);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ router.post('/login', authController.login);
|
||||
router.post('/refresh', authController.refreshToken);
|
||||
router.post('/forgot-password', authController.forgotPassword);
|
||||
router.post('/reset-password', authController.resetPassword);
|
||||
router.post('/change-password', authenticate, authController.changePassword);
|
||||
router.post('/logout', authController.logout);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user