Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: current password is required when changing own password #597

Merged
merged 1 commit into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2019 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.obiba.agate.service;

public class CurrentPasswordInvalidException extends RuntimeException {

public CurrentPasswordInvalidException() {
super("The current password is invalid");
}

}
15 changes: 15 additions & 0 deletions agate-core/src/main/java/org/obiba/agate/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.crypto.hash.Sha512Hash;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.realm.Realm;
Expand Down Expand Up @@ -268,6 +269,17 @@ public void updateUserPassword(@Nonnull User user, @Nonnull String password) {
save(userCredentials);
}

public void updateUserPassword(@Nonnull User user, @Nonnull String password0, @Nonnull String password) {
if (user == null) throw new BadRequestException("Invalid User");
UserCredentials userCredentials = findUserCredentials(user.getName());
if (userCredentials == null) throw new BadRequestException("Invalid User");
String hashedPassword0 = hashPassword(password0);
if (!userCredentials.getPassword().equals(hashedPassword0)) {
throw new CurrentPasswordInvalidException();
}
updateUserPassword(user, password);
}

public User createUser(@Nonnull User user, @Nullable String password) {
checkNameOrEmail(user.getName());
checkName(user.getFirstName());
Expand Down Expand Up @@ -625,6 +637,9 @@ public void updateUserProfile(User user, JSONObject profile) throws JSONExceptio
* @throws org.obiba.agate.service.NoSuchUserException
*/
public User getCurrentUser() {
if (SecurityUtils.getSubject().getPrincipal() == null) {
throw new UnauthenticatedException();
}
String username = SecurityUtils.getSubject().getPrincipal().toString();
User currentUser = findUser(username);
if (currentUser == null) throw NoSuchUserException.withName(username);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.obiba.agate.web.rest;

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import org.obiba.agate.service.CurrentPasswordInvalidException;
import org.obiba.agate.service.PasswordNotChangedException;
import org.obiba.jersey.exceptionmapper.AbstractErrorDtoExceptionMapper;
import org.obiba.web.model.ErrorDtos;


@Provider
public class CurrentPasswordInvalidExceptionMapper extends AbstractErrorDtoExceptionMapper<CurrentPasswordInvalidException> {

@Override
protected Response.Status getStatus() {
return Response.Status.BAD_REQUEST;
}

@Override
protected ErrorDtos.ClientErrorDto getErrorDto(CurrentPasswordInvalidException e) {
return ErrorDtos.ClientErrorDto.newBuilder() //
.setCode(getStatus().getStatusCode()) //
.setMessageTemplate("server.error.password.current-invalid") //
.setMessage(e.getMessage()) //
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ public Agate.UserDto get() {

@PUT
@Path("/password")
public Response updatePassword(@FormParam("password") String password) {
userService.updateUserPassword(getUser(), password);
public Response updatePassword(@FormParam("password0") String password0, @FormParam("password") String password) {
// provide current user password and new password
userService.updateUserPassword(getUser(), password0, password);

return Response.noContent().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
];

const errorMessages = {
'server.error.password.current-invalid': "<@message "server.error.password.current-invalid"/>",
'server.error.password.not-changed': "<@message "server.error.password.not-changed"/>",
'server.error.password.too-weak': "<@message "server.error.password.too-weak"/>"
};
Expand Down
12 changes: 12 additions & 0 deletions agate-webapp/src/main/resources/_templates/profile.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@
<small><@message "password-no-match"/></small>
</div>

<div id="alertPasswordNotModified" class="alert alert-danger d-none">
<small><@message "password-not-modified"/></small>
</div>

<div id="alertFailure" class="alert alert-danger d-none">
<small><@message "update-password-failed"/></small>
</div>
Expand All @@ -126,6 +130,14 @@
</div>

<form id="password-form" method="post">
<div class="input-group mb-3">
<input name="password0" type="password" class="form-control" placeholder="<@message "current-password"/>">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-lock"></span>
</div>
</div>
</div>
<div class="input-group mb-3">
<input name="password" type="password" class="form-control" placeholder="<@message "new-password"/>">
<div class="input-group-append">
Expand Down
3 changes: 3 additions & 0 deletions agate-webapp/src/main/resources/i18n/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ keep-signed-in = Keep me signed in
server.error.bad-request = Bad parameters.
server.error.bad-captcha = Bad captcha.
server.error.email-already-assigned = Email is already taken.
server.error.password.current-invalid = The current password is invalid.
server.error.password.not-changed = New password is identical to the current one.
server.error.password.too-weak = Password must contain at least one digit, one upper case alphabet, one lower case alphabet, one special character (which includes @#$%^&+=!) and no white space.
server.error.unhandledException = Unexpected server error, please try again later.
Expand All @@ -75,12 +76,14 @@ reset = Reset
update-password = Update password
reset-key-missing = The password reset key is missing
reset-password-failed = Unable to update password, make sure the reset key is valid.
current-password = Current password
new-password = New password
repeat-new-password = Repeat new password
update = Update
password-missing = A password is required.
password-too-short = Password is too short (minimum is 8 characters).
password-no-match = The passwords do not match.
password-not-modified = The new password must be different from the current one.
2fa-caption = Enter 6-digits PIN code
validate = Validate
invalid-redirect = Invalid redirection
Expand Down
3 changes: 3 additions & 0 deletions agate-webapp/src/main/resources/i18n/messages_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ keep-signed-in = Garder ma connexion
server.error.bad-request = Paramètres invalides.
server.error.bad-captcha = Le captcha a échoué.
server.error.email-already-assigned = Le courriel est déjà enregistré.
server.error.password.current-invalid = Le mot de passe courant est non valide.
server.error.password.not-changed = Le nouveau mot de passe est identique au mot de passe courant.
server.error.password.too-weak = Le mot de passe doit contenir au moins un chiffre, une lettre minuscule, une lettre majuscule, un caractère spécial (tel que @#$%^&+=!) et aucun espace.
server.error.unhandledException = Erreur inattendue, veuillez réessayer plus tard.
Expand All @@ -75,12 +76,14 @@ reset = Réinitialiser
update-password = Mise à jour du mot de passe
reset-key-missing = La clé de réinitialisation du mot de passe est manquante.
reset-password-failed = La mise à jour du mot de passe a échoué. Veuillez vérifier que la clé de réinitialisation est valide.
current-password = Mot de passe actuel
new-password = Nouveau mot de passe
repeat-new-password = Répéter le nouveau mot de passe
update = Mise à jour
password-missing = Un mot de passe est requis.
password-too-short = Le mot de passe est trop court (minimum de 8 caractères).
password-no-match = Les mots de passe ne correspondent pas.
password-not-modified = Le nouveau mot de passe doit être différent du mot de passe courant.
2fa-caption = Entrer le code NIP à 6 chiffres
validate = Valider
invalid-redirect = Redirection non valide
Expand Down
16 changes: 14 additions & 2 deletions agate-webapp/src/main/webapp/assets/js/agate.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,15 +267,27 @@ var agatejs = (function() {

var formData = form.serializeArray();

// current password
if (formData[0].value.trim() === '') {
onFailure('PasswordMissing');
return;
}
if (formData[0].value.length < 8) {
// new password
if (formData[1].value.trim() === '') {
onFailure('PasswordMissing');
return;
}
// compare current and new password
if (formData[0].value === formData[1].value) {
onFailure('PasswordNotModified');
return;
}
if (formData[1].value.length < 8) {
onFailure('PasswordTooShort');
return;
}
if (formData[0].value !== formData[1].value) {
// compare repeated new password
if (formData[1].value !== formData[2].value) {
onFailure('PasswordNoMatch');
return;
}
Expand Down