#472 Store recovery codes in memory instead of in data source
This commit is contained in:
parent
bff344ba8f
commit
e30d7220bd
@ -1,41 +0,0 @@
|
|||||||
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 expiration timestamp of the recovery code
|
|
||||||
*/
|
|
||||||
public EmailRecoveryData(String email, String recoveryCode, Long codeExpiration) {
|
|
||||||
this.email = email;
|
|
||||||
|
|
||||||
if (codeExpiration == null || System.currentTimeMillis() > codeExpiration) {
|
|
||||||
this.recoveryCode = null;
|
|
||||||
} else {
|
|
||||||
this.recoveryCode = recoveryCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the email address
|
|
||||||
*/
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the recovery code, if available and not expired
|
|
||||||
*/
|
|
||||||
public String getRecoveryCode() {
|
|
||||||
return recoveryCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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.EmailRecoveryData;
|
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.command.CommandService;
|
import fr.xephi.authme.command.CommandService;
|
||||||
import fr.xephi.authme.command.PlayerCommand;
|
import fr.xephi.authme.command.PlayerCommand;
|
||||||
@ -11,15 +11,13 @@ import fr.xephi.authme.output.MessageKey;
|
|||||||
import fr.xephi.authme.security.PasswordSecurity;
|
import fr.xephi.authme.security.PasswordSecurity;
|
||||||
import fr.xephi.authme.security.RandomString;
|
import fr.xephi.authme.security.RandomString;
|
||||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||||
import fr.xephi.authme.settings.properties.EmailSettings;
|
import fr.xephi.authme.service.RecoveryCodeManager;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID;
|
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
|
||||||
import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_LENGTH;
|
|
||||||
import static fr.xephi.authme.util.Utils.MILLIS_PER_HOUR;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command for password recovery by email.
|
* Command for password recovery by email.
|
||||||
@ -41,6 +39,9 @@ public class RecoverEmailCommand extends PlayerCommand {
|
|||||||
@Inject
|
@Inject
|
||||||
private SendMailSSL sendMailSsl;
|
private SendMailSSL sendMailSsl;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private RecoveryCodeManager recoveryCodeManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void runCommand(Player player, List<String> arguments) {
|
public void runCommand(Player player, List<String> arguments) {
|
||||||
final String playerMail = arguments.get(0);
|
final String playerMail = arguments.get(0);
|
||||||
@ -56,48 +57,54 @@ public class RecoverEmailCommand extends PlayerCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(playerName);
|
PlayerAuth auth = dataSource.getAuth(playerName); // TODO: Create method to get email only
|
||||||
if (recoveryData == null) {
|
if (auth == null) {
|
||||||
commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE);
|
commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String email = recoveryData.getEmail();
|
final String email = auth.getEmail();
|
||||||
if (email == null || !email.equalsIgnoreCase(playerMail) || "your@email.com".equalsIgnoreCase(email)) {
|
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) {
|
if (recoveryCodeManager.isRecoveryCodeNeeded()) {
|
||||||
// Process /email recover addr@example.com
|
// Process /email recovery addr@example.com
|
||||||
createAndSendRecoveryCode(playerName, recoveryData);
|
if (arguments.size() == 1) {
|
||||||
|
createAndSendRecoveryCode(playerName, email);
|
||||||
|
} else {
|
||||||
|
// Process /email recovery addr@example.com 12394
|
||||||
|
processRecoveryCode(player, arguments.get(1), email);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Process /email recover addr@example.com 12394
|
generateAndSendNewPassword(player, email);
|
||||||
processRecoveryCode(player, arguments.get(1), recoveryData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAndSendRecoveryCode(String name, EmailRecoveryData recoveryData) {
|
private void createAndSendRecoveryCode(String name, String email) {
|
||||||
String recoveryCode = RandomString.generateHex(commandService.getProperty(RECOVERY_CODE_LENGTH));
|
String recoveryCode = recoveryCodeManager.generateCode(name);
|
||||||
long expiration = System.currentTimeMillis()
|
sendMailSsl.sendRecoveryCode(name, email, recoveryCode);
|
||||||
+ commandService.getProperty(RECOVERY_CODE_HOURS_VALID) * MILLIS_PER_HOUR;
|
|
||||||
|
|
||||||
dataSource.setRecoveryCode(name, recoveryCode, expiration);
|
|
||||||
sendMailSsl.sendRecoveryCode(name, recoveryData.getEmail(), recoveryCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRecoveryCode(Player player, String code, EmailRecoveryData recoveryData) {
|
private void processRecoveryCode(Player player, String code, String email) {
|
||||||
if (!code.equals(recoveryData.getRecoveryCode())) {
|
final String name = player.getName();
|
||||||
|
if (!recoveryCodeManager.isCodeValid(name, code)) {
|
||||||
player.sendMessage("The recovery code is not correct! Use /email recovery [email] to generate a new one");
|
player.sendMessage("The recovery code is not correct! Use /email recovery [email] to generate a new one");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String name = player.getName();
|
generateAndSendNewPassword(player, email);
|
||||||
String thePass = RandomString.generate(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH));
|
recoveryCodeManager.removeCode(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateAndSendNewPassword(Player player, String email) {
|
||||||
|
String name = player.getName();
|
||||||
|
String thePass = RandomString.generate(commandService.getProperty(RECOVERY_PASSWORD_LENGTH));
|
||||||
HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
|
HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
|
||||||
|
|
||||||
dataSource.updatePassword(name, hashNew);
|
dataSource.updatePassword(name, hashNew);
|
||||||
dataSource.removeRecoveryCode(name);
|
sendMailSsl.sendPasswordMail(name, email, thePass);
|
||||||
sendMailSsl.sendPasswordMail(name, recoveryData.getEmail(), thePass);
|
|
||||||
commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
|
commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
|||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
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;
|
||||||
@ -236,23 +234,6 @@ public class CacheDataSource implements DataSource {
|
|||||||
return source.getAllAuths();
|
return source.getAllAuths();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRecoveryCode(String name, String code, long expiration) {
|
|
||||||
source.setRecoveryCode(name, code, expiration);
|
|
||||||
cachedAuths.refresh(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EmailRecoveryData getEmailRecoveryData(String name) {
|
|
||||||
return source.getEmailRecoveryData(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRecoveryCode(String name) {
|
|
||||||
source.removeRecoveryCode(name);
|
|
||||||
cachedAuths.refresh(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PlayerAuth> getLoggedPlayers() {
|
public List<PlayerAuth> getLoggedPlayers() {
|
||||||
return new ArrayList<>(PlayerCache.getInstance().getCache().values());
|
return new ArrayList<>(PlayerCache.getInstance().getCache().values());
|
||||||
|
|||||||
@ -22,8 +22,6 @@ public final class Columns {
|
|||||||
public final String EMAIL;
|
public final String EMAIL;
|
||||||
public final String ID;
|
public final String ID;
|
||||||
public final String IS_LOGGED;
|
public final String IS_LOGGED;
|
||||||
public final String RECOVERY_CODE;
|
|
||||||
public final String RECOVERY_EXPIRATION;
|
|
||||||
|
|
||||||
public Columns(Settings settings) {
|
public Columns(Settings settings) {
|
||||||
NAME = settings.getProperty(DatabaseSettings.MYSQL_COL_NAME);
|
NAME = settings.getProperty(DatabaseSettings.MYSQL_COL_NAME);
|
||||||
@ -40,8 +38,6 @@ public final class Columns {
|
|||||||
EMAIL = settings.getProperty(DatabaseSettings.MYSQL_COL_EMAIL);
|
EMAIL = settings.getProperty(DatabaseSettings.MYSQL_COL_EMAIL);
|
||||||
ID = settings.getProperty(DatabaseSettings.MYSQL_COL_ID);
|
ID = settings.getProperty(DatabaseSettings.MYSQL_COL_ID);
|
||||||
IS_LOGGED = settings.getProperty(DatabaseSettings.MYSQL_COL_ISLOGGED);
|
IS_LOGGED = settings.getProperty(DatabaseSettings.MYSQL_COL_ISLOGGED);
|
||||||
RECOVERY_CODE = settings.getProperty(DatabaseSettings.MYSQL_COL_RECOVERY_CODE);
|
|
||||||
RECOVERY_EXPIRATION = settings.getProperty(DatabaseSettings.MYSQL_COL_RECOVERY_EXPIRATION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@ -195,30 +194,6 @@ public interface DataSource extends Reloadable {
|
|||||||
*/
|
*/
|
||||||
List<PlayerAuth> getAllAuths();
|
List<PlayerAuth> getAllAuths();
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the password recovery code for a user.
|
|
||||||
*
|
|
||||||
* @param name The name of the user
|
|
||||||
* @param code The recovery code
|
|
||||||
* @param expiration Recovery code expiration (milliseconds timestamp)
|
|
||||||
*/
|
|
||||||
void setRecoveryCode(String name, String code, long expiration);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the information necessary for performing a password recovery by email.
|
|
||||||
*
|
|
||||||
* @param name The name of the user
|
|
||||||
* @return The data of the player, or null if player doesn't exist
|
|
||||||
*/
|
|
||||||
EmailRecoveryData getEmailRecoveryData(String name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the recovery code of a given user.
|
|
||||||
*
|
|
||||||
* @param name The name of the user
|
|
||||||
*/
|
|
||||||
void removeRecoveryCode(String name);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload the data source.
|
* Reload the data source.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@ -469,21 +468,6 @@ public class FlatFile implements DataSource {
|
|||||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
throw new UnsupportedOperationException("Flat file no longer supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRecoveryCode(String name, String code, long expiration) {
|
|
||||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EmailRecoveryData getEmailRecoveryData(String name) {
|
|
||||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRecoveryCode(String name) {
|
|
||||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PlayerAuth buildAuthFromArray(String[] args) {
|
private static PlayerAuth buildAuthFromArray(String[] args) {
|
||||||
// Format allows 2, 3, 4, 7, 8, 9 fields. Anything else is unknown
|
// Format allows 2, 3, 4, 7, 8, 9 fields. Anything else is unknown
|
||||||
if (args.length >= 2 && args.length <= 9 && args.length != 5 && args.length != 6) {
|
if (args.length >= 2 && args.length <= 9 && args.length != 5 && args.length != 6) {
|
||||||
|
|||||||
@ -4,7 +4,6 @@ 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;
|
||||||
@ -209,14 +208,6 @@ public class MySQL implements DataSource {
|
|||||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN "
|
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN "
|
||||||
+ col.IS_LOGGED + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.EMAIL);
|
+ col.IS_LOGGED + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.EMAIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isColumnMissing(md, col.RECOVERY_CODE)) {
|
|
||||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.RECOVERY_CODE + " VARCHAR(20);");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isColumnMissing(md, col.RECOVERY_EXPIRATION)) {
|
|
||||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.RECOVERY_EXPIRATION + " BIGINT;");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ConsoleLogger.info("MySQL setup finished");
|
ConsoleLogger.info("MySQL setup finished");
|
||||||
}
|
}
|
||||||
@ -865,55 +856,6 @@ public class MySQL implements DataSource {
|
|||||||
return auths;
|
return auths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRecoveryCode(String name, String code, long expiration) {
|
|
||||||
String sql = "UPDATE " + tableName
|
|
||||||
+ " SET " + col.RECOVERY_CODE + " = ?, "
|
|
||||||
+ col.RECOVERY_EXPIRATION + " = ?"
|
|
||||||
+ " WHERE " + col.NAME + " = ?;";
|
|
||||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
|
||||||
pst.setString(1, code);
|
|
||||||
pst.setLong(2, expiration);
|
|
||||||
pst.setString(3, name.toLowerCase());
|
|
||||||
pst.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logSqlException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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());
|
|
||||||
try (ResultSet rs = pst.executeQuery()) {
|
|
||||||
if (rs.next()) {
|
|
||||||
return new EmailRecoveryData(
|
|
||||||
rs.getString(col.EMAIL), rs.getString(col.RECOVERY_CODE), rs.getLong(col.RECOVERY_EXPIRATION));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logSqlException(e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRecoveryCode(String name) {
|
|
||||||
String sql = "UPDATE " + tableName
|
|
||||||
+ " SET " + col.RECOVERY_CODE + " = NULL, "
|
|
||||||
+ col.RECOVERY_EXPIRATION + " = NULL"
|
|
||||||
+ " WHERE " + col.NAME + " = ?;";
|
|
||||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
|
||||||
pst.setString(1, name.toLowerCase());
|
|
||||||
pst.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logSqlException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
||||||
String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT);
|
String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT);
|
||||||
int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP);
|
int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP);
|
||||||
|
|||||||
@ -2,7 +2,6 @@ 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;
|
||||||
@ -128,14 +127,6 @@ public class SQLite implements DataSource {
|
|||||||
if (isColumnMissing(md, col.IS_LOGGED)) {
|
if (isColumnMissing(md, col.IS_LOGGED)) {
|
||||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.IS_LOGGED + " INT DEFAULT '0';");
|
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.IS_LOGGED + " INT DEFAULT '0';");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isColumnMissing(md, col.RECOVERY_CODE)) {
|
|
||||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.RECOVERY_CODE + " VARCHAR(20);");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isColumnMissing(md, col.RECOVERY_EXPIRATION)) {
|
|
||||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.RECOVERY_EXPIRATION + " BIGINT;");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ConsoleLogger.info("SQLite Setup finished");
|
ConsoleLogger.info("SQLite Setup finished");
|
||||||
}
|
}
|
||||||
@ -595,55 +586,6 @@ public class SQLite implements DataSource {
|
|||||||
return auths;
|
return auths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRecoveryCode(String name, String code, long expiration) {
|
|
||||||
String sql = "UPDATE " + tableName
|
|
||||||
+ " SET " + col.RECOVERY_CODE + " = ?, "
|
|
||||||
+ col.RECOVERY_EXPIRATION + " = ?"
|
|
||||||
+ " WHERE " + col.NAME + " = ?;";
|
|
||||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
|
||||||
pst.setString(1, code);
|
|
||||||
pst.setLong(2, expiration);
|
|
||||||
pst.setString(3, name.toLowerCase());
|
|
||||||
pst.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logSqlException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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());
|
|
||||||
try (ResultSet rs = pst.executeQuery()) {
|
|
||||||
if (rs.next()) {
|
|
||||||
return new EmailRecoveryData(
|
|
||||||
rs.getString(col.EMAIL), rs.getString(col.RECOVERY_CODE), rs.getLong(col.RECOVERY_EXPIRATION));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logSqlException(e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRecoveryCode(String name) {
|
|
||||||
String sql = "UPDATE " + tableName
|
|
||||||
+ " SET " + col.RECOVERY_CODE + " = NULL, "
|
|
||||||
+ col.RECOVERY_EXPIRATION + " = NULL"
|
|
||||||
+ " WHERE " + col.NAME + " = ?;";
|
|
||||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
|
||||||
pst.setString(1, name.toLowerCase());
|
|
||||||
pst.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logSqlException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
||||||
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;
|
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,73 @@
|
|||||||
|
package fr.xephi.authme.service;
|
||||||
|
|
||||||
|
import fr.xephi.authme.initialization.SettingsDependent;
|
||||||
|
import fr.xephi.authme.security.RandomString;
|
||||||
|
import fr.xephi.authme.settings.Settings;
|
||||||
|
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID;
|
||||||
|
import static fr.xephi.authme.util.Utils.MILLIS_PER_HOUR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager for recovery codes.
|
||||||
|
*/
|
||||||
|
public class RecoveryCodeManager implements SettingsDependent {
|
||||||
|
|
||||||
|
private Map<String, TimedEntry> recoveryCodes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private int recoveryCodeLength;
|
||||||
|
private long recoveryCodeExpirationMillis;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RecoveryCodeManager(Settings settings) {
|
||||||
|
reload(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRecoveryCodeNeeded() {
|
||||||
|
return recoveryCodeExpirationMillis > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateCode(String player) {
|
||||||
|
String code = RandomString.generateHex(recoveryCodeLength);
|
||||||
|
recoveryCodes.put(player, new TimedEntry(code, System.currentTimeMillis() + recoveryCodeExpirationMillis));
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCodeValid(String player, String code) {
|
||||||
|
TimedEntry entry = recoveryCodes.get(player);
|
||||||
|
if (entry != null) {
|
||||||
|
return code != null && code.equals(entry.getCode());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCode(String player) {
|
||||||
|
recoveryCodes.remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reload(Settings settings) {
|
||||||
|
recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH);
|
||||||
|
recoveryCodeExpirationMillis = settings.getProperty(RECOVERY_CODE_HOURS_VALID) * MILLIS_PER_HOUR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TimedEntry {
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final long expiration;
|
||||||
|
|
||||||
|
TimedEntry(String code, long expiration) {
|
||||||
|
this.code = code;
|
||||||
|
this.expiration = expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return System.currentTimeMillis() < expiration ? code : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -98,14 +98,6 @@ public class DatabaseSettings implements SettingsHolder {
|
|||||||
public static final Property<String> MYSQL_COL_GROUP =
|
public static final Property<String> MYSQL_COL_GROUP =
|
||||||
newProperty("ExternalBoardOptions.mySQLColumnGroup", "");
|
newProperty("ExternalBoardOptions.mySQLColumnGroup", "");
|
||||||
|
|
||||||
@Comment("Column for storing recovery code (when password lost)")
|
|
||||||
public static final Property<String> MYSQL_COL_RECOVERY_CODE =
|
|
||||||
newProperty("DataSource.mySQLrecoveryCode", "recoverycode");
|
|
||||||
|
|
||||||
@Comment("Column for storing recovery code expiration")
|
|
||||||
public static final Property<String> MYSQL_COL_RECOVERY_EXPIRATION =
|
|
||||||
newProperty("DataSource.mySQLrecoveryExpiration", "recoveryexpiration");
|
|
||||||
|
|
||||||
private DatabaseSettings() {
|
private DatabaseSettings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,10 +40,6 @@ DataSource:
|
|||||||
mySQLlastlocWorld: world
|
mySQLlastlocWorld: world
|
||||||
# Column for RealName
|
# Column for RealName
|
||||||
mySQLRealName: realname
|
mySQLRealName: realname
|
||||||
# Column for storing recovery code (when password lost)
|
|
||||||
mySQLrecoveryCode: recoverycode
|
|
||||||
# Column for storing recovery code expiration
|
|
||||||
mySQLrecoveryExpiration: recoveryexpiration
|
|
||||||
settings:
|
settings:
|
||||||
# The name shown in the help messages.
|
# The name shown in the help messages.
|
||||||
helpHeader: AuthMeReloaded
|
helpHeader: AuthMeReloaded
|
||||||
|
|||||||
@ -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.EmailRecoveryData;
|
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.command.CommandService;
|
import fr.xephi.authme.command.CommandService;
|
||||||
import fr.xephi.authme.datasource.DataSource;
|
import fr.xephi.authme.datasource.DataSource;
|
||||||
@ -9,6 +9,7 @@ import fr.xephi.authme.mail.SendMailSSL;
|
|||||||
import fr.xephi.authme.output.MessageKey;
|
import fr.xephi.authme.output.MessageKey;
|
||||||
import fr.xephi.authme.security.PasswordSecurity;
|
import fr.xephi.authme.security.PasswordSecurity;
|
||||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||||
|
import fr.xephi.authme.service.RecoveryCodeManager;
|
||||||
import fr.xephi.authme.settings.properties.EmailSettings;
|
import fr.xephi.authme.settings.properties.EmailSettings;
|
||||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -24,11 +25,7 @@ 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 fr.xephi.authme.util.Utils.MILLIS_PER_HOUR;
|
|
||||||
import static org.hamcrest.Matchers.allOf;
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
|
||||||
import static org.hamcrest.Matchers.lessThan;
|
|
||||||
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.any;
|
||||||
@ -66,6 +63,9 @@ public class RecoverEmailCommandTest {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private SendMailSSL sendMailSsl;
|
private SendMailSSL sendMailSsl;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RecoveryCodeManager recoveryCodeManager;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void initLogger() {
|
public static void initLogger() {
|
||||||
@ -112,14 +112,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.getEmailRecoveryData(name)).willReturn(null);
|
given(dataSource.getAuth(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).getEmailRecoveryData(name);
|
verify(dataSource).getAuth(name);
|
||||||
verifyNoMoreInteractions(dataSource);
|
verifyNoMoreInteractions(dataSource);
|
||||||
verify(commandService).send(sender, MessageKey.REGISTER_EMAIL_MESSAGE);
|
verify(commandService).send(sender, MessageKey.REGISTER_EMAIL_MESSAGE);
|
||||||
}
|
}
|
||||||
@ -132,14 +132,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.getEmailRecoveryData(name)).willReturn(newEmailRecoveryData(DEFAULT_EMAIL));
|
given(dataSource.getAuth(name)).willReturn(newAuthWithEmail(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).getEmailRecoveryData(name);
|
verify(dataSource).getAuth(name);
|
||||||
verifyNoMoreInteractions(dataSource);
|
verifyNoMoreInteractions(dataSource);
|
||||||
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
|
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
|
||||||
}
|
}
|
||||||
@ -152,14 +152,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.getEmailRecoveryData(name)).willReturn(newEmailRecoveryData("raptor@example.org"));
|
given(dataSource.getAuth(name)).willReturn(newAuthWithEmail("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).getEmailRecoveryData(name);
|
verify(dataSource).getAuth(name);
|
||||||
verifyNoMoreInteractions(dataSource);
|
verifyNoMoreInteractions(dataSource);
|
||||||
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
|
verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
|
||||||
}
|
}
|
||||||
@ -173,26 +173,23 @@ 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 = "v@example.com";
|
String email = "v@example.com";
|
||||||
given(dataSource.getEmailRecoveryData(name)).willReturn(newEmailRecoveryData(email));
|
given(dataSource.getAuth(name)).willReturn(newAuthWithEmail(email));
|
||||||
int codeLength = 7;
|
int codeLength = 7;
|
||||||
given(commandService.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH)).willReturn(codeLength);
|
given(commandService.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH)).willReturn(codeLength);
|
||||||
int hoursValid = 12;
|
int hoursValid = 12;
|
||||||
given(commandService.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(hoursValid);
|
given(commandService.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(hoursValid);
|
||||||
|
String code = "a94f37";
|
||||||
|
given(recoveryCodeManager.isRecoveryCodeNeeded()).willReturn(true);
|
||||||
|
given(recoveryCodeManager.generateCode(name)).willReturn(code);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
command.executeCommand(sender, Collections.singletonList(email.toUpperCase()));
|
command.executeCommand(sender, Collections.singletonList(email.toUpperCase()));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(sendMailSsl).hasAllInformation();
|
verify(sendMailSsl).hasAllInformation();
|
||||||
verify(dataSource).getEmailRecoveryData(name);
|
verify(dataSource).getAuth(name);
|
||||||
ArgumentCaptor<String> codeCaptor = ArgumentCaptor.forClass(String.class);
|
verify(recoveryCodeManager).generateCode(name);
|
||||||
ArgumentCaptor<Long> expirationCaptor = ArgumentCaptor.forClass(Long.class);
|
verify(sendMailSsl).sendRecoveryCode(name, email, code);
|
||||||
verify(dataSource).setRecoveryCode(eq(name), codeCaptor.capture(), expirationCaptor.capture());
|
|
||||||
assertThat(codeCaptor.getValue(), stringWithLength(codeLength));
|
|
||||||
// Check expiration with a tolerance
|
|
||||||
assertThat(expirationCaptor.getValue() - System.currentTimeMillis(),
|
|
||||||
allOf(lessThan(12L * MILLIS_PER_HOUR), greaterThan((long) (11.9 * MILLIS_PER_HOUR))));
|
|
||||||
verify(sendMailSsl).sendRecoveryCode(name, email, codeCaptor.getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -204,19 +201,18 @@ 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";
|
||||||
String code = "A6EF3AC8";
|
PlayerAuth auth = newAuthWithEmail(email);
|
||||||
EmailRecoveryData recoveryData = newEmailRecoveryData(email, code);
|
given(dataSource.getAuth(name)).willReturn(auth);
|
||||||
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(recoveryCodeManager.isRecoveryCodeNeeded()).willReturn(true);
|
||||||
.willAnswer(invocation -> new HashedPassword((String) invocation.getArguments()[0]));
|
given(recoveryCodeManager.isCodeValid(name, "bogus")).willReturn(false);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
command.executeCommand(sender, Arrays.asList(email, "bogus"));
|
command.executeCommand(sender, Arrays.asList(email, "bogus"));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(sendMailSsl).hasAllInformation();
|
verify(sendMailSsl).hasAllInformation();
|
||||||
verify(dataSource, only()).getEmailRecoveryData(name);
|
verify(dataSource, only()).getAuth(name);
|
||||||
verify(sender).sendMessage(argThat(containsString("The recovery code is not correct")));
|
verify(sender).sendMessage(argThat(containsString("The recovery code is not correct")));
|
||||||
verifyNoMoreInteractions(sendMailSsl);
|
verifyNoMoreInteractions(sendMailSsl);
|
||||||
}
|
}
|
||||||
@ -231,35 +227,35 @@ public class RecoverEmailCommandTest {
|
|||||||
given(playerCache.isAuthenticated(name)).willReturn(false);
|
given(playerCache.isAuthenticated(name)).willReturn(false);
|
||||||
String email = "vulture@example.com";
|
String email = "vulture@example.com";
|
||||||
String code = "A6EF3AC8";
|
String code = "A6EF3AC8";
|
||||||
EmailRecoveryData recoveryData = newEmailRecoveryData(email, code);
|
PlayerAuth auth = newAuthWithEmail(email);
|
||||||
given(dataSource.getEmailRecoveryData(name)).willReturn(recoveryData);
|
given(dataSource.getAuth(name)).willReturn(auth);
|
||||||
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(invocation -> new HashedPassword((String) invocation.getArguments()[0]));
|
.willAnswer(invocation -> new HashedPassword((String) invocation.getArguments()[0]));
|
||||||
|
given(recoveryCodeManager.isRecoveryCodeNeeded()).willReturn(true);
|
||||||
|
given(recoveryCodeManager.isCodeValid(name, code)).willReturn(true);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
command.executeCommand(sender, Arrays.asList(email, code));
|
command.executeCommand(sender, Arrays.asList(email, code));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(sendMailSsl).hasAllInformation();
|
verify(sendMailSsl).hasAllInformation();
|
||||||
verify(dataSource).getEmailRecoveryData(name);
|
verify(dataSource).getAuth(name);
|
||||||
ArgumentCaptor<String> passwordCaptor = ArgumentCaptor.forClass(String.class);
|
ArgumentCaptor<String> passwordCaptor = ArgumentCaptor.forClass(String.class);
|
||||||
verify(passwordSecurity).computeHash(passwordCaptor.capture(), eq(name));
|
verify(passwordSecurity).computeHash(passwordCaptor.capture(), eq(name));
|
||||||
String generatedPassword = passwordCaptor.getValue();
|
String generatedPassword = passwordCaptor.getValue();
|
||||||
assertThat(generatedPassword, stringWithLength(20));
|
assertThat(generatedPassword, stringWithLength(20));
|
||||||
verify(dataSource).updatePassword(eq(name), any(HashedPassword.class));
|
verify(dataSource).updatePassword(eq(name), any(HashedPassword.class));
|
||||||
verify(dataSource).removeRecoveryCode(name);
|
verify(recoveryCodeManager).removeCode(name);
|
||||||
verify(sendMailSsl).sendPasswordMail(name, email, generatedPassword);
|
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 EmailRecoveryData newEmailRecoveryData(String email) {
|
private static PlayerAuth newAuthWithEmail(String email) {
|
||||||
return new EmailRecoveryData(email, null, 0L);
|
return PlayerAuth.builder()
|
||||||
|
.name("name")
|
||||||
|
.email(email)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EmailRecoveryData newEmailRecoveryData(String email, String code) {
|
|
||||||
return new EmailRecoveryData(email, code, System.currentTimeMillis() + 10_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@ -382,63 +381,4 @@ public abstract class AbstractDataSourceIntegrationTest {
|
|||||||
// then
|
// then
|
||||||
assertThat(dataSource.getAllAuths(), empty());
|
assertThat(dataSource.getAllAuths(), empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldSetRecoveryCode() {
|
|
||||||
// given
|
|
||||||
DataSource dataSource = getDataSource();
|
|
||||||
String name = "Bobby";
|
|
||||||
String code = "A123BC";
|
|
||||||
|
|
||||||
// when
|
|
||||||
dataSource.setRecoveryCode(name, code, System.currentTimeMillis() + 100_000L);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertThat(dataSource.getEmailRecoveryData(name).getRecoveryCode(), equalTo(code));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldRemoveRecoveryCode() {
|
|
||||||
// given
|
|
||||||
String name = "User";
|
|
||||||
DataSource dataSource = getDataSource();
|
|
||||||
dataSource.setRecoveryCode(name, "code", System.currentTimeMillis() + 20_000L);
|
|
||||||
|
|
||||||
// when
|
|
||||||
dataSource.removeRecoveryCode(name);
|
|
||||||
|
|
||||||
// then
|
|
||||||
EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(name);
|
|
||||||
assertThat(recoveryData.getRecoveryCode(), nullValue());
|
|
||||||
assertThat(recoveryData.getEmail(), equalTo("user@example.org"));
|
|
||||||
assertThat(dataSource.getEmailRecoveryData("bobby").getRecoveryCode(), nullValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldNotReturnRecoveryCodeIfExpired() {
|
|
||||||
// given
|
|
||||||
String name = "user";
|
|
||||||
DataSource dataSource = getDataSource();
|
|
||||||
dataSource.setRecoveryCode(name, "123456", System.currentTimeMillis() - 2_000L);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EmailRecoveryData recoveryData = dataSource.getEmailRecoveryData(name);
|
|
||||||
|
|
||||||
// then
|
|
||||||
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…
x
Reference in New Issue
Block a user