Skip to content

Commit

Permalink
feat: current password is required when changing own password (#597)
Browse files Browse the repository at this point in the history
  • Loading branch information
ymarcon authored Feb 7, 2025
1 parent a643df4 commit 1bed3aa
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 4 deletions.
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

0 comments on commit 1bed3aa

Please sign in to comment.