diff --git a/src/main/java/fr/xephi/authme/cache/auth/EmailRecoveryData.java b/src/main/java/fr/xephi/authme/cache/auth/EmailRecoveryData.java
new file mode 100644
index 000000000..392b14c61
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/cache/auth/EmailRecoveryData.java
@@ -0,0 +1,38 @@
+package fr.xephi.authme.cache.auth;
+
+/**
+ * Stored data for email recovery.
+ */
+public class EmailRecoveryData {
+
+ private final String email;
+ private final String recoveryCode;
+
+ /**
+ * Constructor.
+ *
+ * @param email the email address
+ * @param recoveryCode the recovery code, or null if not available
+ * @param codeExpiration
+ */
+ public EmailRecoveryData(String email, String recoveryCode, Long codeExpiration) {
+ this.email = email;
+ this.recoveryCode = codeExpiration == null || System.currentTimeMillis() > codeExpiration
+ ? null
+ : recoveryCode;
+ }
+
+ /**
+ * @return the email address
+ */
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ * @return the recovery code, if available and not expired
+ */
+ public String getRecoveryCode() {
+ return recoveryCode;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
index 0d5c6da07..b29fd2150 100644
--- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java
+++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
@@ -384,6 +384,7 @@ public class CommandInitializer {
.detailedDescription("Recover your account using an Email address by sending a mail containing " +
"a new password.")
.withArgument("email", "Email address", false)
+ .withArgument("code", "Recovery code", true)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(RecoverEmailCommand.class)
.build();
diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java
index 8965c96fa..671a31bde 100644
--- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java
@@ -1,7 +1,7 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.cache.auth.PlayerAuth;
+import fr.xephi.authme.cache.auth.EmailRecoveryData;
import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.PlayerCommand;
@@ -17,6 +17,9 @@ import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
+/**
+ * Command for password recovery by email.
+ */
public class RecoverEmailCommand extends PlayerCommand {
@Inject
@@ -49,22 +52,47 @@ public class RecoverEmailCommand extends PlayerCommand {
return;
}
- PlayerAuth auth = dataSource.getAuth(playerName);
- if (auth == null) {
+ EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(playerName);
+ if (recoveryData == null) {
commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE);
return;
}
- if (!playerMail.equalsIgnoreCase(auth.getEmail()) || "your@email.com".equalsIgnoreCase(auth.getEmail())) {
+ final String email = recoveryData.getEmail();
+ if (email == null || !email.equalsIgnoreCase(playerMail) || "your@email.com".equalsIgnoreCase(email)) {
commandService.send(player, MessageKey.INVALID_EMAIL);
return;
}
+ if (arguments.size() == 1) {
+ // Process /email recover addr@example.com
+ createAndSendRecoveryCode(playerName, recoveryData);
+ } else {
+ // Process /email recover addr@example.com 12394
+ processRecoveryCode(player, arguments.get(1), recoveryData);
+ }
+ }
+
+ private void createAndSendRecoveryCode(String name, EmailRecoveryData recoveryData) {
+ // TODO #472: Add configurations
+ String recoveryCode = RandomString.generateHex(8);
+ long expiration = System.currentTimeMillis() + (3 * 60 * 60_000L); // 3 hours
+ dataSource.setRecoveryCode(name, recoveryCode, expiration);
+ sendMailSsl.sendRecoveryCode(recoveryData.getEmail(), recoveryCode);
+ }
+
+ private void processRecoveryCode(Player player, String code, EmailRecoveryData recoveryData) {
+ if (!code.equals(recoveryData.getRecoveryCode())) {
+ player.sendMessage("The recovery code is not correct! Use /email recovery [email] to generate a new one");
+ return;
+ }
+
+ final String name = player.getName();
String thePass = RandomString.generate(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH));
- HashedPassword hashNew = passwordSecurity.computeHash(thePass, playerName);
- auth.setPassword(hashNew);
- dataSource.updatePassword(auth);
- sendMailSsl.sendPasswordMail(auth, thePass);
+ HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
+ dataSource.updatePassword(name, hashNew);
+ dataSource.removeRecoveryCode(name);
+ sendMailSsl.sendPasswordMail(name, recoveryData.getEmail(), thePass);
commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
}
}
diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
index 36372430c..a4f642d00 100644
--- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
+++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
@@ -10,6 +10,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.cache.auth.EmailRecoveryData;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.security.crypts.HashedPassword;
@@ -242,9 +243,8 @@ public class CacheDataSource implements DataSource {
}
@Override
- public String getRecoveryCode(String name) {
- // TODO #472: can probably get it from the cached Auth?
- return source.getRecoveryCode(name);
+ public EmailRecoveryData getEmailRecoveryData(String name) {
+ return source.getEmailRecoveryData(name);
}
@Override
diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java
index 96788d839..b069cf4bc 100644
--- a/src/main/java/fr/xephi/authme/datasource/DataSource.java
+++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.datasource;
+import fr.xephi.authme.cache.auth.EmailRecoveryData;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.security.crypts.HashedPassword;
@@ -204,12 +205,12 @@ public interface DataSource extends Reloadable {
void setRecoveryCode(String name, String code, long expiration);
/**
- * Get the recovery code of a user if available and not yet expired.
+ * Get the information necessary for performing a password recovery by email.
*
* @param name The name of the user
- * @return The recovery code, or null if no current code available
+ * @return The data of the player, or null if player doesn't exist
*/
- String getRecoveryCode(String name);
+ EmailRecoveryData getEmailRecoveryData(String name);
/**
* Remove the recovery code of a given user.
diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java
index d600a3e67..f2490a844 100644
--- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java
+++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java
@@ -1,6 +1,7 @@
package fr.xephi.authme.datasource;
import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.cache.auth.EmailRecoveryData;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.security.crypts.HashedPassword;
@@ -474,7 +475,7 @@ public class FlatFile implements DataSource {
}
@Override
- public String getRecoveryCode(String name) {
+ public EmailRecoveryData getEmailRecoveryData(String name) {
throw new UnsupportedOperationException("Flat file no longer supported");
}
diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java
index 0e9c64669..ff771bb2b 100644
--- a/src/main/java/fr/xephi/authme/datasource/MySQL.java
+++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java
@@ -4,6 +4,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.cache.auth.EmailRecoveryData;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.HashedPassword;
@@ -881,15 +882,16 @@ public class MySQL implements DataSource {
}
@Override
- public String getRecoveryCode(String name) {
- String sql = "SELECT " + col.RECOVERY_CODE + " FROM " + tableName
- + " WHERE " + col.NAME + " = ? AND " + col.RECOVERY_EXPIRATION + " > ?;";
+ public EmailRecoveryData getEmailRecoveryData(String name) {
+ String sql = "SELECT " + col.EMAIL + ", " + col.RECOVERY_CODE + ", " + col.RECOVERY_EXPIRATION
+ + " FROM " + tableName
+ + " WHERE " + col.NAME + " = ?;";
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
pst.setString(1, name.toLowerCase());
- pst.setLong(2, System.currentTimeMillis());
try (ResultSet rs = pst.executeQuery()) {
if (rs.next()) {
- return rs.getString(1);
+ return new EmailRecoveryData(
+ rs.getString(col.EMAIL), rs.getString(col.RECOVERY_CODE), rs.getLong(col.RECOVERY_EXPIRATION));
}
}
} catch (SQLException e) {
diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java
index 5f08e0765..f6ae8f905 100644
--- a/src/main/java/fr/xephi/authme/datasource/SQLite.java
+++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java
@@ -2,6 +2,7 @@ package fr.xephi.authme.datasource;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.cache.auth.EmailRecoveryData;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings;
@@ -611,15 +612,16 @@ public class SQLite implements DataSource {
}
@Override
- public String getRecoveryCode(String name) {
- String sql = "SELECT " + col.RECOVERY_CODE + " FROM " + tableName
- + " WHERE " + col.NAME + " = ? AND " + col.RECOVERY_EXPIRATION + " > ?;";
+ public EmailRecoveryData getEmailRecoveryData(String name) {
+ String sql = "SELECT " + col.EMAIL + ", " + col.RECOVERY_CODE + ", " + col.RECOVERY_EXPIRATION
+ + " FROM " + tableName
+ + " WHERE " + col.NAME + " = ?;";
try (PreparedStatement pst = con.prepareStatement(sql)) {
pst.setString(1, name.toLowerCase());
- pst.setLong(2, System.currentTimeMillis());
try (ResultSet rs = pst.executeQuery()) {
if (rs.next()) {
- return rs.getString(1);
+ return new EmailRecoveryData(
+ rs.getString(col.EMAIL), rs.getString(col.RECOVERY_CODE), rs.getLong(col.RECOVERY_EXPIRATION));
}
}
} catch (SQLException e) {
diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java
index 6088a5bca..8b8f8b7e4 100644
--- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java
+++ b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java
@@ -2,7 +2,6 @@ package fr.xephi.authme.mail;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.util.BukkitService;
@@ -53,16 +52,17 @@ public class SendMailSSL {
/**
* Sends an email to the user with his new password.
*
- * @param auth the player auth of the player
+ * @param name the name of the player
+ * @param mailAddress the player's email
* @param newPass the new password
*/
- public void sendPasswordMail(final PlayerAuth auth, final String newPass) {
+ public void sendPasswordMail(String name, String mailAddress, String newPass) {
if (!hasAllInformation()) {
ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete");
return;
}
- final String mailText = replaceMailTags(settings.getEmailMessage(), auth, newPass);
+ final String mailText = replaceMailTags(settings.getEmailMessage(), name, newPass);
bukkitService.runTaskAsynchronously(new Runnable() {
@Override
@@ -70,7 +70,7 @@ public class SendMailSSL {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
HtmlEmail email;
try {
- email = initializeMail(auth.getEmail());
+ email = initializeMail(mailAddress);
} catch (EmailException e) {
ConsoleLogger.logException("Failed to create email with the given settings:", e);
return;
@@ -81,11 +81,11 @@ public class SendMailSSL {
File file = null;
if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) {
try {
- file = generateImage(auth.getNickname(), plugin, newPass);
+ file = generateImage(name, plugin, newPass);
content = embedImageIntoEmailContent(file, email, content);
} catch (IOException | EmailException e) {
ConsoleLogger.logException(
- "Unable to send new password as image for email " + auth.getEmail() + ":", e);
+ "Unable to send new password as image for email " + mailAddress + ":", e);
}
}
@@ -97,6 +97,20 @@ public class SendMailSSL {
});
}
+ public void sendRecoveryCode(String email, String code) {
+ // TODO #472: Create a configurable, more verbose message
+ String message = String.format("Use /email recovery %s %s to reset your password", email, code);
+
+ HtmlEmail htmlEmail;
+ try {
+ htmlEmail = initializeMail(email);
+ } catch (EmailException e) {
+ ConsoleLogger.logException("Failed to create email for recovery code:", e);
+ return;
+ }
+ sendEmail(message, htmlEmail);
+ }
+
private static File generateImage(String name, AuthMe plugin, String newPass) throws IOException {
ImageGenerator gen = new ImageGenerator(newPass);
File file = new File(plugin.getDataFolder(), name + "_new_pass.jpg");
@@ -149,9 +163,9 @@ public class SendMailSSL {
}
}
- private String replaceMailTags(String mailText, PlayerAuth auth, String newPass) {
+ private String replaceMailTags(String mailText, String name, String newPass) {
return mailText
- .replace("", auth.getNickname())
+ .replace("", name)
.replace("", plugin.getServer().getServerName())
.replace("", newPass);
}
diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
index 57390a54a..a7cf57949 100644
--- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
@@ -149,7 +149,7 @@ public class AsyncRegister implements AsynchronousProcess {
}
database.updateEmail(auth);
database.updateSession(auth);
- sendMailSsl.sendPasswordMail(auth, password);
+ sendMailSsl.sendPasswordMail(name, email, password);
syncProcessManager.processSyncEmailRegister(player);
}
diff --git a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java
index 2f57443ae..4d60877e1 100644
--- a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java
+++ b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java
@@ -1,7 +1,7 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.TestHelper;
-import fr.xephi.authme.cache.auth.PlayerAuth;
+import fr.xephi.authme.cache.auth.EmailRecoveryData;
import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.datasource.DataSource;
@@ -14,21 +14,25 @@ import org.bukkit.entity.Player;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
+import java.util.Arrays;
import java.util.Collections;
import static fr.xephi.authme.AuthMeMatchers.stringWithLength;
+import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -104,14 +108,14 @@ public class RecoverEmailCommandTest {
given(sender.getName()).willReturn(name);
given(sendMailSsl.hasAllInformation()).willReturn(true);
given(playerCache.isAuthenticated(name)).willReturn(false);
- given(dataSource.getAuth(name)).willReturn(null);
+ given(dataSource.getEmailRecoveryData(name)).willReturn(null);
// when
command.executeCommand(sender, Collections.singletonList("someone@example.com"));
// then
verify(sendMailSsl).hasAllInformation();
- verify(dataSource).getAuth(name);
+ verify(dataSource).getEmailRecoveryData(name);
verifyNoMoreInteractions(dataSource);
verify(commandService).send(sender, MessageKey.REGISTER_EMAIL_MESSAGE);
}
@@ -124,14 +128,14 @@ public class RecoverEmailCommandTest {
given(sender.getName()).willReturn(name);
given(sendMailSsl.hasAllInformation()).willReturn(true);
given(playerCache.isAuthenticated(name)).willReturn(false);
- given(dataSource.getAuth(name)).willReturn(authWithEmail(DEFAULT_EMAIL));
+ given(dataSource.getEmailRecoveryData(name)).willReturn(newEmailRecoveryData(DEFAULT_EMAIL));
// when
command.executeCommand(sender, Collections.singletonList(DEFAULT_EMAIL));
// then
verify(sendMailSsl).hasAllInformation();
- verify(dataSource).getAuth(name);
+ verify(dataSource).getEmailRecoveryData(name);
verifyNoMoreInteractions(dataSource);
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
}
@@ -144,18 +148,67 @@ public class RecoverEmailCommandTest {
given(sender.getName()).willReturn(name);
given(sendMailSsl.hasAllInformation()).willReturn(true);
given(playerCache.isAuthenticated(name)).willReturn(false);
- given(dataSource.getAuth(name)).willReturn(authWithEmail("raptor@example.org"));
+ given(dataSource.getEmailRecoveryData(name)).willReturn(newEmailRecoveryData("raptor@example.org"));
// when
command.executeCommand(sender, Collections.singletonList("wrong-email@example.com"));
// then
verify(sendMailSsl).hasAllInformation();
- verify(dataSource).getAuth(name);
+ verify(dataSource).getEmailRecoveryData(name);
verifyNoMoreInteractions(dataSource);
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
}
+ @Test
+ public void shouldGenerateRecoveryCode() {
+ // given
+ String name = "Vultur3";
+ Player sender = mock(Player.class);
+ given(sender.getName()).willReturn(name);
+ given(sendMailSsl.hasAllInformation()).willReturn(true);
+ given(playerCache.isAuthenticated(name)).willReturn(false);
+ String email = "v@example.com";
+ given(dataSource.getEmailRecoveryData(name)).willReturn(newEmailRecoveryData(email));
+
+ // when
+ command.executeCommand(sender, Collections.singletonList(email.toUpperCase()));
+
+ // then
+ verify(sendMailSsl).hasAllInformation();
+ verify(dataSource).getEmailRecoveryData(name);
+ ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(String.class);
+ verify(dataSource).setRecoveryCode(eq(name), codeCaptor.capture(), anyLong());
+ assertThat(codeCaptor.getValue(), stringWithLength(8));
+ verify(sendMailSsl).sendRecoveryCode(email, codeCaptor.getValue());
+ }
+
+ @Test
+ public void shouldSendErrorForInvalidRecoveryCode() {
+ // given
+ String name = "Vultur3";
+ Player sender = mock(Player.class);
+ given(sender.getName()).willReturn(name);
+ given(sendMailSsl.hasAllInformation()).willReturn(true);
+ given(playerCache.isAuthenticated(name)).willReturn(false);
+ String email = "vulture@example.com";
+ String code = "A6EF3AC8";
+ EmailRecoveryData recoveryData = newEmailRecoveryData(email, code);
+ given(dataSource.getEmailRecoveryData(name)).willReturn(recoveryData);
+ given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20);
+ given(passwordSecurity.computeHash(anyString(), eq(name)))
+ .willAnswer(invocation -> new HashedPassword((String) invocation.getArguments()[0]));
+
+ // when
+ command.executeCommand(sender, Arrays.asList(email, "bogus"));
+
+ // then
+ verify(sendMailSsl).hasAllInformation();
+ verify(dataSource, only()).getEmailRecoveryData(name);
+ verify(sender).sendMessage(argThat(containsString("The recovery code is not correct")));
+ verifyNoMoreInteractions(sendMailSsl);
+ }
+
@Test
public void shouldResetPasswordAndSendEmail() {
// given
@@ -165,36 +218,36 @@ public class RecoverEmailCommandTest {
given(sendMailSsl.hasAllInformation()).willReturn(true);
given(playerCache.isAuthenticated(name)).willReturn(false);
String email = "vulture@example.com";
- PlayerAuth auth = authWithEmail(email);
- given(dataSource.getAuth(name)).willReturn(auth);
+ String code = "A6EF3AC8";
+ EmailRecoveryData recoveryData = newEmailRecoveryData(email, code);
+ given(dataSource.getEmailRecoveryData(name)).willReturn(recoveryData);
given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20);
given(passwordSecurity.computeHash(anyString(), eq(name)))
- .willAnswer(new Answer() {
- @Override
- public HashedPassword answer(InvocationOnMock invocationOnMock) {
- return new HashedPassword((String) invocationOnMock.getArguments()[0]);
- }
- });
+ .willAnswer(invocation -> new HashedPassword((String) invocation.getArguments()[0]));
// when
- command.executeCommand(sender, Collections.singletonList(email.toUpperCase()));
+ command.executeCommand(sender, Arrays.asList(email, code));
// then
verify(sendMailSsl).hasAllInformation();
- verify(dataSource).getAuth(name);
- verify(passwordSecurity).computeHash(anyString(), eq(name));
- verify(dataSource).updatePassword(auth);
- assertThat(auth.getPassword().getHash(), stringWithLength(20));
- verify(sendMailSsl).sendPasswordMail(eq(auth), argThat(stringWithLength(20)));
+ verify(dataSource).getEmailRecoveryData(name);
+ ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class);
+ verify(passwordSecurity).computeHash(passwordCaptor.capture(), eq(name));
+ String generatedPassword = passwordCaptor.getValue();
+ assertThat(generatedPassword, stringWithLength(20));
+ verify(dataSource).updatePassword(eq(name), any(HashedPassword.class));
+ verify(dataSource).removeRecoveryCode(name);
+ verify(sendMailSsl).sendPasswordMail(name, email, generatedPassword);
verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
}
- private static PlayerAuth authWithEmail(String email) {
- return PlayerAuth.builder()
- .name("tester")
- .email(email)
- .build();
+ private static EmailRecoveryData newEmailRecoveryData(String email) {
+ return new EmailRecoveryData(email, null, 0L);
+ }
+
+ private static EmailRecoveryData newEmailRecoveryData(String email, String code) {
+ return new EmailRecoveryData(email, code, System.currentTimeMillis() + 10_000);
}
}
diff --git a/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java b/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java
index 9c417f622..59c6ce22a 100644
--- a/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java
+++ b/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.datasource;
+import fr.xephi.authme.cache.auth.EmailRecoveryData;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.security.crypts.HashedPassword;
import org.junit.Test;
@@ -393,7 +394,7 @@ public abstract class AbstractDataSourceIntegrationTest {
dataSource.setRecoveryCode(name, code, System.currentTimeMillis() + 100_000L);
// then
- assertThat(dataSource.getRecoveryCode(name), equalTo(code));
+ assertThat(dataSource.getEmailRecoveryData(name).getRecoveryCode(), equalTo(code));
}
@Test
@@ -407,8 +408,10 @@ public abstract class AbstractDataSourceIntegrationTest {
dataSource.removeRecoveryCode(name);
// then
- assertThat(dataSource.getRecoveryCode(name), nullValue());
- assertThat(dataSource.getRecoveryCode("bobby"), nullValue());
+ EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(name);
+ assertThat(recoveryData.getRecoveryCode(), nullValue());
+ assertThat(recoveryData.getEmail(), equalTo("user@example.org"));
+ assertThat(dataSource.getEmailRecoveryData("bobby").getRecoveryCode(), nullValue());
}
@Test
@@ -419,10 +422,23 @@ public abstract class AbstractDataSourceIntegrationTest {
dataSource.setRecoveryCode(name, "123456", System.currentTimeMillis() - 2_000L);
// when
- String code = dataSource.getRecoveryCode(name);
+ EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(name);
// then
- assertThat(code, nullValue());
+ assertThat(recoveryData.getEmail(), equalTo("user@example.org"));
+ assertThat(recoveryData.getRecoveryCode(), nullValue());
+ }
+
+ @Test
+ public void shouldReturnNullForNoAvailableUser() {
+ // given
+ DataSource dataSource = getDataSource();
+
+ // when
+ EmailRecoveryData result = dataSource.getEmailRecoveryData("does-not-exist");
+
+ // then
+ assertThat(result, nullValue());
}
}