mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-12-02 15:13:55 +01:00
#472 Require recovery code before resetting password
- /email recovery generates recovery code and resets password only if recovery code is also given - Change data source method to return email and recovery code
This commit is contained in:
parent
3b723bbbe9
commit
c5f5c0d2fd
38
src/main/java/fr/xephi/authme/cache/auth/EmailRecoveryData.java
vendored
Normal file
38
src/main/java/fr/xephi/authme/cache/auth/EmailRecoveryData.java
vendored
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -384,6 +384,7 @@ public class CommandInitializer {
|
|||||||
.detailedDescription("Recover your account using an Email address by sending a mail containing " +
|
.detailedDescription("Recover your account using an Email address by sending a mail containing " +
|
||||||
"a new password.")
|
"a new password.")
|
||||||
.withArgument("email", "Email address", false)
|
.withArgument("email", "Email address", false)
|
||||||
|
.withArgument("code", "Recovery code", true)
|
||||||
.permission(PlayerPermission.RECOVER_EMAIL)
|
.permission(PlayerPermission.RECOVER_EMAIL)
|
||||||
.executableCommand(RecoverEmailCommand.class)
|
.executableCommand(RecoverEmailCommand.class)
|
||||||
.build();
|
.build();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package fr.xephi.authme.command.executable.email;
|
package fr.xephi.authme.command.executable.email;
|
||||||
|
|
||||||
import fr.xephi.authme.ConsoleLogger;
|
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.cache.auth.PlayerCache;
|
||||||
import fr.xephi.authme.command.CommandService;
|
import fr.xephi.authme.command.CommandService;
|
||||||
import fr.xephi.authme.command.PlayerCommand;
|
import fr.xephi.authme.command.PlayerCommand;
|
||||||
@ -17,6 +17,9 @@ import org.bukkit.entity.Player;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command for password recovery by email.
|
||||||
|
*/
|
||||||
public class RecoverEmailCommand extends PlayerCommand {
|
public class RecoverEmailCommand extends PlayerCommand {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@ -49,22 +52,47 @@ public class RecoverEmailCommand extends PlayerCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerAuth auth = dataSource.getAuth(playerName);
|
EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(playerName);
|
||||||
if (auth == null) {
|
if (recoveryData == null) {
|
||||||
commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE);
|
commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE);
|
||||||
return;
|
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);
|
commandService.send(player, MessageKey.INVALID_EMAIL);
|
||||||
return;
|
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));
|
String thePass = RandomString.generate(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH));
|
||||||
HashedPassword hashNew = passwordSecurity.computeHash(thePass, playerName);
|
HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
|
||||||
auth.setPassword(hashNew);
|
dataSource.updatePassword(name, hashNew);
|
||||||
dataSource.updatePassword(auth);
|
dataSource.removeRecoveryCode(name);
|
||||||
sendMailSsl.sendPasswordMail(auth, thePass);
|
sendMailSsl.sendPasswordMail(name, recoveryData.getEmail(), thePass);
|
||||||
commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
|
commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import com.google.common.util.concurrent.MoreExecutors;
|
|||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
import fr.xephi.authme.ConsoleLogger;
|
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.PlayerAuth;
|
||||||
import fr.xephi.authme.cache.auth.PlayerCache;
|
import fr.xephi.authme.cache.auth.PlayerCache;
|
||||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||||
@ -242,9 +243,8 @@ public class CacheDataSource implements DataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRecoveryCode(String name) {
|
public EmailRecoveryData getEmailRecoveryData(String name) {
|
||||||
// TODO #472: can probably get it from the cached Auth?
|
return source.getEmailRecoveryData(name);
|
||||||
return source.getRecoveryCode(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package fr.xephi.authme.datasource;
|
package fr.xephi.authme.datasource;
|
||||||
|
|
||||||
|
import fr.xephi.authme.cache.auth.EmailRecoveryData;
|
||||||
import fr.xephi.authme.cache.auth.PlayerAuth;
|
import fr.xephi.authme.cache.auth.PlayerAuth;
|
||||||
import fr.xephi.authme.initialization.Reloadable;
|
import fr.xephi.authme.initialization.Reloadable;
|
||||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||||
@ -204,12 +205,12 @@ public interface DataSource extends Reloadable {
|
|||||||
void setRecoveryCode(String name, String code, long expiration);
|
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
|
* @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.
|
* Remove the recovery code of a given user.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package fr.xephi.authme.datasource;
|
package fr.xephi.authme.datasource;
|
||||||
|
|
||||||
import fr.xephi.authme.ConsoleLogger;
|
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.PlayerAuth;
|
||||||
import fr.xephi.authme.cache.auth.PlayerCache;
|
import fr.xephi.authme.cache.auth.PlayerCache;
|
||||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||||
@ -474,7 +475,7 @@ public class FlatFile implements DataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRecoveryCode(String name) {
|
public EmailRecoveryData getEmailRecoveryData(String name) {
|
||||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
throw new UnsupportedOperationException("Flat file no longer supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import com.google.common.annotations.VisibleForTesting;
|
|||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
|
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
|
||||||
import fr.xephi.authme.ConsoleLogger;
|
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.PlayerAuth;
|
||||||
import fr.xephi.authme.security.HashAlgorithm;
|
import fr.xephi.authme.security.HashAlgorithm;
|
||||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||||
@ -881,15 +882,16 @@ public class MySQL implements DataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRecoveryCode(String name) {
|
public EmailRecoveryData getEmailRecoveryData(String name) {
|
||||||
String sql = "SELECT " + col.RECOVERY_CODE + " FROM " + tableName
|
String sql = "SELECT " + col.EMAIL + ", " + col.RECOVERY_CODE + ", " + col.RECOVERY_EXPIRATION
|
||||||
+ " WHERE " + col.NAME + " = ? AND " + col.RECOVERY_EXPIRATION + " > ?;";
|
+ " FROM " + tableName
|
||||||
|
+ " WHERE " + col.NAME + " = ?;";
|
||||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||||
pst.setString(1, name.toLowerCase());
|
pst.setString(1, name.toLowerCase());
|
||||||
pst.setLong(2, System.currentTimeMillis());
|
|
||||||
try (ResultSet rs = pst.executeQuery()) {
|
try (ResultSet rs = pst.executeQuery()) {
|
||||||
if (rs.next()) {
|
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) {
|
} catch (SQLException e) {
|
||||||
|
@ -2,6 +2,7 @@ package fr.xephi.authme.datasource;
|
|||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import fr.xephi.authme.ConsoleLogger;
|
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.PlayerAuth;
|
||||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||||
import fr.xephi.authme.settings.Settings;
|
import fr.xephi.authme.settings.Settings;
|
||||||
@ -611,15 +612,16 @@ public class SQLite implements DataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRecoveryCode(String name) {
|
public EmailRecoveryData getEmailRecoveryData(String name) {
|
||||||
String sql = "SELECT " + col.RECOVERY_CODE + " FROM " + tableName
|
String sql = "SELECT " + col.EMAIL + ", " + col.RECOVERY_CODE + ", " + col.RECOVERY_EXPIRATION
|
||||||
+ " WHERE " + col.NAME + " = ? AND " + col.RECOVERY_EXPIRATION + " > ?;";
|
+ " FROM " + tableName
|
||||||
|
+ " WHERE " + col.NAME + " = ?;";
|
||||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||||
pst.setString(1, name.toLowerCase());
|
pst.setString(1, name.toLowerCase());
|
||||||
pst.setLong(2, System.currentTimeMillis());
|
|
||||||
try (ResultSet rs = pst.executeQuery()) {
|
try (ResultSet rs = pst.executeQuery()) {
|
||||||
if (rs.next()) {
|
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) {
|
} catch (SQLException e) {
|
||||||
|
@ -2,7 +2,6 @@ package fr.xephi.authme.mail;
|
|||||||
|
|
||||||
import fr.xephi.authme.AuthMe;
|
import fr.xephi.authme.AuthMe;
|
||||||
import fr.xephi.authme.ConsoleLogger;
|
import fr.xephi.authme.ConsoleLogger;
|
||||||
import fr.xephi.authme.cache.auth.PlayerAuth;
|
|
||||||
import fr.xephi.authme.settings.Settings;
|
import fr.xephi.authme.settings.Settings;
|
||||||
import fr.xephi.authme.settings.properties.EmailSettings;
|
import fr.xephi.authme.settings.properties.EmailSettings;
|
||||||
import fr.xephi.authme.util.BukkitService;
|
import fr.xephi.authme.util.BukkitService;
|
||||||
@ -53,16 +52,17 @@ public class SendMailSSL {
|
|||||||
/**
|
/**
|
||||||
* Sends an email to the user with his new password.
|
* 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
|
* @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()) {
|
if (!hasAllInformation()) {
|
||||||
ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete");
|
ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String mailText = replaceMailTags(settings.getEmailMessage(), auth, newPass);
|
final String mailText = replaceMailTags(settings.getEmailMessage(), name, newPass);
|
||||||
bukkitService.runTaskAsynchronously(new Runnable() {
|
bukkitService.runTaskAsynchronously(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -70,7 +70,7 @@ public class SendMailSSL {
|
|||||||
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
|
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
|
||||||
HtmlEmail email;
|
HtmlEmail email;
|
||||||
try {
|
try {
|
||||||
email = initializeMail(auth.getEmail());
|
email = initializeMail(mailAddress);
|
||||||
} catch (EmailException e) {
|
} catch (EmailException e) {
|
||||||
ConsoleLogger.logException("Failed to create email with the given settings:", e);
|
ConsoleLogger.logException("Failed to create email with the given settings:", e);
|
||||||
return;
|
return;
|
||||||
@ -81,11 +81,11 @@ public class SendMailSSL {
|
|||||||
File file = null;
|
File file = null;
|
||||||
if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) {
|
if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) {
|
||||||
try {
|
try {
|
||||||
file = generateImage(auth.getNickname(), plugin, newPass);
|
file = generateImage(name, plugin, newPass);
|
||||||
content = embedImageIntoEmailContent(file, email, content);
|
content = embedImageIntoEmailContent(file, email, content);
|
||||||
} catch (IOException | EmailException e) {
|
} catch (IOException | EmailException e) {
|
||||||
ConsoleLogger.logException(
|
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 {
|
private static File generateImage(String name, AuthMe plugin, String newPass) throws IOException {
|
||||||
ImageGenerator gen = new ImageGenerator(newPass);
|
ImageGenerator gen = new ImageGenerator(newPass);
|
||||||
File file = new File(plugin.getDataFolder(), name + "_new_pass.jpg");
|
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
|
return mailText
|
||||||
.replace("<playername />", auth.getNickname())
|
.replace("<playername />", name)
|
||||||
.replace("<servername />", plugin.getServer().getServerName())
|
.replace("<servername />", plugin.getServer().getServerName())
|
||||||
.replace("<generatedpass />", newPass);
|
.replace("<generatedpass />", newPass);
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ public class AsyncRegister implements AsynchronousProcess {
|
|||||||
}
|
}
|
||||||
database.updateEmail(auth);
|
database.updateEmail(auth);
|
||||||
database.updateSession(auth);
|
database.updateSession(auth);
|
||||||
sendMailSsl.sendPasswordMail(auth, password);
|
sendMailSsl.sendPasswordMail(name, email, password);
|
||||||
syncProcessManager.processSyncEmailRegister(player);
|
syncProcessManager.processSyncEmailRegister(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package fr.xephi.authme.command.executable.email;
|
package fr.xephi.authme.command.executable.email;
|
||||||
|
|
||||||
import fr.xephi.authme.TestHelper;
|
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.cache.auth.PlayerCache;
|
||||||
import fr.xephi.authme.command.CommandService;
|
import fr.xephi.authme.command.CommandService;
|
||||||
import fr.xephi.authme.datasource.DataSource;
|
import fr.xephi.authme.datasource.DataSource;
|
||||||
@ -14,21 +14,25 @@ import org.bukkit.entity.Player;
|
|||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static fr.xephi.authme.AuthMeMatchers.stringWithLength;
|
import static fr.xephi.authme.AuthMeMatchers.stringWithLength;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.mockito.BDDMockito.given;
|
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.anyString;
|
||||||
import static org.mockito.Matchers.argThat;
|
import static org.mockito.Matchers.argThat;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.only;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
@ -104,14 +108,14 @@ public class RecoverEmailCommandTest {
|
|||||||
given(sender.getName()).willReturn(name);
|
given(sender.getName()).willReturn(name);
|
||||||
given(sendMailSsl.hasAllInformation()).willReturn(true);
|
given(sendMailSsl.hasAllInformation()).willReturn(true);
|
||||||
given(playerCache.isAuthenticated(name)).willReturn(false);
|
given(playerCache.isAuthenticated(name)).willReturn(false);
|
||||||
given(dataSource.getAuth(name)).willReturn(null);
|
given(dataSource.getEmailRecoveryData(name)).willReturn(null);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
command.executeCommand(sender, Collections.singletonList("someone@example.com"));
|
command.executeCommand(sender, Collections.singletonList("someone@example.com"));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(sendMailSsl).hasAllInformation();
|
verify(sendMailSsl).hasAllInformation();
|
||||||
verify(dataSource).getAuth(name);
|
verify(dataSource).getEmailRecoveryData(name);
|
||||||
verifyNoMoreInteractions(dataSource);
|
verifyNoMoreInteractions(dataSource);
|
||||||
verify(commandService).send(sender, MessageKey.REGISTER_EMAIL_MESSAGE);
|
verify(commandService).send(sender, MessageKey.REGISTER_EMAIL_MESSAGE);
|
||||||
}
|
}
|
||||||
@ -124,14 +128,14 @@ public class RecoverEmailCommandTest {
|
|||||||
given(sender.getName()).willReturn(name);
|
given(sender.getName()).willReturn(name);
|
||||||
given(sendMailSsl.hasAllInformation()).willReturn(true);
|
given(sendMailSsl.hasAllInformation()).willReturn(true);
|
||||||
given(playerCache.isAuthenticated(name)).willReturn(false);
|
given(playerCache.isAuthenticated(name)).willReturn(false);
|
||||||
given(dataSource.getAuth(name)).willReturn(authWithEmail(DEFAULT_EMAIL));
|
given(dataSource.getEmailRecoveryData(name)).willReturn(newEmailRecoveryData(DEFAULT_EMAIL));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
command.executeCommand(sender, Collections.singletonList(DEFAULT_EMAIL));
|
command.executeCommand(sender, Collections.singletonList(DEFAULT_EMAIL));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(sendMailSsl).hasAllInformation();
|
verify(sendMailSsl).hasAllInformation();
|
||||||
verify(dataSource).getAuth(name);
|
verify(dataSource).getEmailRecoveryData(name);
|
||||||
verifyNoMoreInteractions(dataSource);
|
verifyNoMoreInteractions(dataSource);
|
||||||
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
|
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
|
||||||
}
|
}
|
||||||
@ -144,18 +148,67 @@ public class RecoverEmailCommandTest {
|
|||||||
given(sender.getName()).willReturn(name);
|
given(sender.getName()).willReturn(name);
|
||||||
given(sendMailSsl.hasAllInformation()).willReturn(true);
|
given(sendMailSsl.hasAllInformation()).willReturn(true);
|
||||||
given(playerCache.isAuthenticated(name)).willReturn(false);
|
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
|
// when
|
||||||
command.executeCommand(sender, Collections.singletonList("wrong-email@example.com"));
|
command.executeCommand(sender, Collections.singletonList("wrong-email@example.com"));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(sendMailSsl).hasAllInformation();
|
verify(sendMailSsl).hasAllInformation();
|
||||||
verify(dataSource).getAuth(name);
|
verify(dataSource).getEmailRecoveryData(name);
|
||||||
verifyNoMoreInteractions(dataSource);
|
verifyNoMoreInteractions(dataSource);
|
||||||
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
|
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<String> 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
|
@Test
|
||||||
public void shouldResetPasswordAndSendEmail() {
|
public void shouldResetPasswordAndSendEmail() {
|
||||||
// given
|
// given
|
||||||
@ -165,36 +218,36 @@ public class RecoverEmailCommandTest {
|
|||||||
given(sendMailSsl.hasAllInformation()).willReturn(true);
|
given(sendMailSsl.hasAllInformation()).willReturn(true);
|
||||||
given(playerCache.isAuthenticated(name)).willReturn(false);
|
given(playerCache.isAuthenticated(name)).willReturn(false);
|
||||||
String email = "vulture@example.com";
|
String email = "vulture@example.com";
|
||||||
PlayerAuth auth = authWithEmail(email);
|
String code = "A6EF3AC8";
|
||||||
given(dataSource.getAuth(name)).willReturn(auth);
|
EmailRecoveryData recoveryData = newEmailRecoveryData(email, code);
|
||||||
|
given(dataSource.getEmailRecoveryData(name)).willReturn(recoveryData);
|
||||||
given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20);
|
given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20);
|
||||||
given(passwordSecurity.computeHash(anyString(), eq(name)))
|
given(passwordSecurity.computeHash(anyString(), eq(name)))
|
||||||
.willAnswer(new Answer<HashedPassword>() {
|
.willAnswer(invocation -> new HashedPassword((String) invocation.getArguments()[0]));
|
||||||
@Override
|
|
||||||
public HashedPassword answer(InvocationOnMock invocationOnMock) {
|
|
||||||
return new HashedPassword((String) invocationOnMock.getArguments()[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
command.executeCommand(sender, Collections.singletonList(email.toUpperCase()));
|
command.executeCommand(sender, Arrays.asList(email, code));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(sendMailSsl).hasAllInformation();
|
verify(sendMailSsl).hasAllInformation();
|
||||||
verify(dataSource).getAuth(name);
|
verify(dataSource).getEmailRecoveryData(name);
|
||||||
verify(passwordSecurity).computeHash(anyString(), eq(name));
|
ArgumentCaptor<String> passwordCaptor = ArgumentCaptor.forClass(String.class);
|
||||||
verify(dataSource).updatePassword(auth);
|
verify(passwordSecurity).computeHash(passwordCaptor.capture(), eq(name));
|
||||||
assertThat(auth.getPassword().getHash(), stringWithLength(20));
|
String generatedPassword = passwordCaptor.getValue();
|
||||||
verify(sendMailSsl).sendPasswordMail(eq(auth), argThat(stringWithLength(20)));
|
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);
|
verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static PlayerAuth authWithEmail(String email) {
|
private static EmailRecoveryData newEmailRecoveryData(String email) {
|
||||||
return PlayerAuth.builder()
|
return new EmailRecoveryData(email, null, 0L);
|
||||||
.name("tester")
|
}
|
||||||
.email(email)
|
|
||||||
.build();
|
private static EmailRecoveryData newEmailRecoveryData(String email, String code) {
|
||||||
|
return new EmailRecoveryData(email, code, System.currentTimeMillis() + 10_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package fr.xephi.authme.datasource;
|
package fr.xephi.authme.datasource;
|
||||||
|
|
||||||
|
import fr.xephi.authme.cache.auth.EmailRecoveryData;
|
||||||
import fr.xephi.authme.cache.auth.PlayerAuth;
|
import fr.xephi.authme.cache.auth.PlayerAuth;
|
||||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -393,7 +394,7 @@ public abstract class AbstractDataSourceIntegrationTest {
|
|||||||
dataSource.setRecoveryCode(name, code, System.currentTimeMillis() + 100_000L);
|
dataSource.setRecoveryCode(name, code, System.currentTimeMillis() + 100_000L);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(dataSource.getRecoveryCode(name), equalTo(code));
|
assertThat(dataSource.getEmailRecoveryData(name).getRecoveryCode(), equalTo(code));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -407,8 +408,10 @@ public abstract class AbstractDataSourceIntegrationTest {
|
|||||||
dataSource.removeRecoveryCode(name);
|
dataSource.removeRecoveryCode(name);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(dataSource.getRecoveryCode(name), nullValue());
|
EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(name);
|
||||||
assertThat(dataSource.getRecoveryCode("bobby"), nullValue());
|
assertThat(recoveryData.getRecoveryCode(), nullValue());
|
||||||
|
assertThat(recoveryData.getEmail(), equalTo("user@example.org"));
|
||||||
|
assertThat(dataSource.getEmailRecoveryData("bobby").getRecoveryCode(), nullValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -419,10 +422,23 @@ public abstract class AbstractDataSourceIntegrationTest {
|
|||||||
dataSource.setRecoveryCode(name, "123456", System.currentTimeMillis() - 2_000L);
|
dataSource.setRecoveryCode(name, "123456", System.currentTimeMillis() - 2_000L);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String code = dataSource.getRecoveryCode(name);
|
EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(name);
|
||||||
|
|
||||||
// then
|
// 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user