Implement /email setpassword

This commit is contained in:
Gnat008
2017-03-14 18:26:32 -04:00
parent 62c053d5cb
commit 2214fa5839
35 changed files with 267 additions and 2 deletions
@@ -32,6 +32,7 @@ import fr.xephi.authme.command.executable.email.ChangeEmailCommand;
import fr.xephi.authme.command.executable.email.EmailBaseCommand;
import fr.xephi.authme.command.executable.email.ProcessCodeCommand;
import fr.xephi.authme.command.executable.email.RecoverEmailCommand;
import fr.xephi.authme.command.executable.email.SetPasswordCommand;
import fr.xephi.authme.command.executable.email.ShowEmailCommand;
import fr.xephi.authme.command.executable.login.LoginCommand;
import fr.xephi.authme.command.executable.logout.LogoutCommand;
@@ -435,6 +436,17 @@ public class CommandInitializer {
.executableCommand(ProcessCodeCommand.class)
.register();
// Register the change password after recovery command
CommandDescription.builder()
.parent(EMAIL_BASE)
.labels("setpassword")
.description("Set new password after recovery")
.detailedDescription("Set a new password after successfully recovering your account.")
.withArgument("password", "New password", false)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(SetPasswordCommand.class)
.register();
// Register the base captcha command
CommandDescription CAPTCHA_BASE = CommandDescription.builder()
.parent(null)
@@ -0,0 +1,53 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.PasswordRecoveryService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.service.ValidationService.ValidationResult;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command for changing password following successful recovery.
*/
public class SetPasswordCommand extends PlayerCommand {
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Inject
private PasswordRecoveryService recoveryService;
@Inject
private PasswordSecurity passwordSecurity;
@Inject
private ValidationService validationService;
@Override
protected void runCommand(Player player, List<String> arguments) {
if (recoveryService.canChangePassword(player)) {
String name = player.getName();
String password = arguments.get(0);
ValidationResult result = validationService.validatePassword(password, name);
if (!result.hasError()) {
HashedPassword hashedPassword = passwordSecurity.computeHash(password, name);
dataSource.updatePassword(name, hashedPassword);
commonService.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS);
} else {
commonService.send(player, result.getMessageKey(), result.getArgs());
}
}
}
}
@@ -230,6 +230,12 @@ public enum MessageKey {
/** You have exceeded the maximum number of attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one. */
RECOVERY_TRIES_EXCEEDED("recovery_tries_exceeded"),
/** Please use the command /email setpassword to change your password immediately. */
RECOVERY_CHANGE_PASSWORD("recovery_change_password"),
/** You cannot change your password using this command anymore. */
CHANGE_PASSWORD_EXPIRED("change_password_expired"),
/** An email was already sent recently. You must wait %time before you can send a new one. */
EMAIL_COOLDOWN_ERROR("email_cooldown_error", "%time"),
@@ -8,6 +8,7 @@ import fr.xephi.authme.message.Messages;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.util.expiring.Duration;
import fr.xephi.authme.util.expiring.ExpiringSet;
@@ -46,11 +47,14 @@ public class PasswordRecoveryService implements Reloadable {
private Messages messages;
private ExpiringSet<String> emailCooldown;
private ExpiringSet<String> successfulRecovers;
@PostConstruct
private void initEmailCooldownSet() {
emailCooldown = new ExpiringSet<>(
commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
successfulRecovers = new ExpiringSet<>(
commonService.getProperty(SecuritySettings.PASSWORD_CHANGE_TIMEOUT), TimeUnit.MINUTES);
}
/**
@@ -96,6 +100,11 @@ public class PasswordRecoveryService implements Reloadable {
if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
emailCooldown.add(player.getName().toLowerCase());
String address = PlayerUtils.getPlayerIp(player);
successfulRecovers.add(address);
commonService.send(player, MessageKey.RECOVERY_CHANGE_PASSWORD);
} else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
}
@@ -117,6 +126,23 @@ public class PasswordRecoveryService implements Reloadable {
return true;
}
/**
* Checks if a player can change their password after recovery
* using the /email setpassword command.
*
* @param player The player to check.
* @return True if the player can change their password.
*/
public boolean canChangePassword(Player player) {
String address = PlayerUtils.getPlayerIp(player);
Duration waitDuration = successfulRecovers.getExpiration(address);
if (waitDuration.getDuration() > 0) {
messages.send(player, MessageKey.EMAIL_COOLDOWN_ERROR);
return false;
}
return true;
}
@Override
public void reload() {
emailCooldown.setExpiration(
@@ -118,6 +118,12 @@ public class SecuritySettings implements SettingsHolder {
public static final Property<Integer> RECOVERY_CODE_MAX_TRIES =
newProperty("Security.recoveryCode.maxTries", 3);
@Comment({"How long a player has after password recovery to change their password",
"without logging in. This is in minutes.",
"Default: 2 minutes"})
public static final Property<Integer> PASSWORD_CHANGE_TIMEOUT =
newProperty("Security.recoveryCode.passwordChangeTimeout", 2);
@Comment({
"Seconds a user has to wait for before a password recovery mail may be sent again",
"This prevents an attacker from abusing AuthMe's email feature."