diff --git a/docs/config.md b/docs/config.md
index 4b3396b5..ff2d54fb 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1,5 +1,5 @@
-
+
## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
@@ -422,6 +422,8 @@ Security:
maxLoginTry: 5
# Captcha length
captchaLength: 5
+ # Minutes after which login attempts count is reset for a player
+ captchaCountReset: 60
tempban:
# Tempban a user's IP address if they enter the wrong password too many times
enableTempban: false
@@ -438,6 +440,10 @@ Security:
length: 8
# How many hours is a recovery code valid for?
validForHours: 4
+ emailRecovery:
+ # Seconds a user has to wait for before a password recovery mail may be sent again
+ # This prevents an attacker from abusing AuthMe's email feature.
+ cooldown: 60
BackupSystem:
# Enable or disable automatic backup
ActivateBackup: false
@@ -454,4 +460,4 @@ To change settings on a running server, save your changes to config.yml and use
---
-This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Feb 05 13:46:19 CET 2017
+This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Feb 25 21:59:18 CET 2017
diff --git a/docs/translations.md b/docs/translations.md
index dee35ed7..a4bc104b 100644
--- a/docs/translations.md
+++ b/docs/translations.md
@@ -1,5 +1,5 @@
-
+
# AuthMe Translations
The following translations are available in AuthMe. Set `messagesLanguage` to the language code
@@ -8,35 +8,34 @@ in your config.yml to use the language, or use another language code to start a
Code | Language | Translated |
---- | -------- | ---------: | ------
[en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% |
-[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 69% |
-[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 100% |
-[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 100% |
-[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 100% |
-[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% |
-[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 62% |
-[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 66% |
-[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% |
-[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 70% |
-[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 99% |
-[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 70% |
-[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% |
-[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 72% |
-[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 53% |
-[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 77% |
-[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% |
-[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 86% |
-[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 99% |
-[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 99% |
-[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 46% |
-[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 81% |
-[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 93% |
-[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 100% |
-[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 81% |
-[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 81% |
-[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 96% |
-[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 81% |
-
+[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 61% |
+[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 89% |
+[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 89% |
+[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 89% |
+[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 89% |
+[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 55% |
+[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 59% |
+[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 89% |
+[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 63% |
+[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 88% |
+[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 63% |
+[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 89% |
+[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 64% |
+[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 47% |
+[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 89% |
+[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 89% |
+[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 77% |
+[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 88% |
+[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 89% |
+[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 41% |
+[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 72% |
+[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 83% |
+[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 89% |
+[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 89% |
+[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 72% |
+[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 86% |
+[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 72% |
---
-This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Jan 11 21:24:50 CET 2017
+This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Feb 25 21:59:17 CET 2017
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 eb188f79..b3fb3b62 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
@@ -5,24 +5,31 @@ import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
+import fr.xephi.authme.message.Messages;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.RecoveryCodeService;
+import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.RandomStringUtils;
+import fr.xephi.authme.util.expiring.Duration;
+import fr.xephi.authme.util.expiring.ExpiringSet;
import org.bukkit.entity.Player;
+import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/**
* Command for password recovery by email.
*/
-public class RecoverEmailCommand extends PlayerCommand {
+public class RecoverEmailCommand extends PlayerCommand implements Reloadable {
@Inject
private PasswordSecurity passwordSecurity;
@@ -42,8 +49,19 @@ public class RecoverEmailCommand extends PlayerCommand {
@Inject
private RecoveryCodeService recoveryCodeService;
+ @Inject
+ private Messages messages;
+
+ private ExpiringSet emailCooldown;
+
+ @PostConstruct
+ private void initEmailCooldownSet() {
+ emailCooldown = new ExpiringSet<>(
+ commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
+ }
+
@Override
- public void runCommand(Player player, List arguments) {
+ protected void runCommand(Player player, List arguments) {
final String playerMail = arguments.get(0);
final String playerName = player.getName();
@@ -78,15 +96,29 @@ public class RecoverEmailCommand extends PlayerCommand {
processRecoveryCode(player, arguments.get(1), email);
}
} else {
- generateAndSendNewPassword(player, email);
+ boolean maySendMail = checkEmailCooldown(player);
+ if (maySendMail) {
+ generateAndSendNewPassword(player, email);
+ }
}
}
+ @Override
+ public void reload() {
+ emailCooldown.setExpiration(
+ commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
+ }
+
private void createAndSendRecoveryCode(Player player, String email) {
+ if (!checkEmailCooldown(player)) {
+ return;
+ }
+
String recoveryCode = recoveryCodeService.generateCode(player.getName());
boolean couldSendMail = emailService.sendRecoveryCode(player.getName(), email, recoveryCode);
if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_CODE_SENT);
+ emailCooldown.add(player.getName().toLowerCase());
} else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
}
@@ -111,8 +143,19 @@ public class RecoverEmailCommand extends PlayerCommand {
boolean couldSendMail = emailService.sendPasswordMail(name, email, thePass);
if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
+ emailCooldown.add(player.getName().toLowerCase());
} else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
}
}
+
+ private boolean checkEmailCooldown(Player player) {
+ Duration waitDuration = emailCooldown.getExpiration(player.getName().toLowerCase());
+ if (waitDuration.getDuration() > 0) {
+ String durationText = messages.formatDuration(waitDuration);
+ messages.send(player, MessageKey.EMAIL_COOLDOWN_ERROR, durationText);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java
index 7546a89c..e8c3939a 100644
--- a/src/main/java/fr/xephi/authme/message/MessageKey.java
+++ b/src/main/java/fr/xephi/authme/message/MessageKey.java
@@ -225,7 +225,35 @@ public enum MessageKey {
RECOVERY_CODE_SENT("recovery_code_sent"),
/** The recovery code is not correct! Use "/email recovery [email]" to generate a new one */
- INCORRECT_RECOVERY_CODE("recovery_code_incorrect");
+ INCORRECT_RECOVERY_CODE("recovery_code_incorrect"),
+
+ /** An email was already sent recently. You must wait %time before you can send a new one. */
+ EMAIL_COOLDOWN_ERROR("email_cooldown_error", "%time"),
+
+ /** second */
+ SECOND("second"),
+
+ /** seconds */
+ SECONDS("seconds"),
+
+ /** minute */
+ MINUTE("minute"),
+
+ /** minutes */
+ MINUTES("minutes"),
+
+ /** hour */
+ HOUR("hour"),
+
+ /** hours */
+ HOURS("hours"),
+
+ /** day */
+ DAY("day"),
+
+ /** days */
+ DAYS("days");
+
private String key;
private String[] tags;
diff --git a/src/main/java/fr/xephi/authme/message/Messages.java b/src/main/java/fr/xephi/authme/message/Messages.java
index 5d777ab8..d7fdcec8 100644
--- a/src/main/java/fr/xephi/authme/message/Messages.java
+++ b/src/main/java/fr/xephi/authme/message/Messages.java
@@ -1,11 +1,15 @@
package fr.xephi.authme.message;
+import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.initialization.Reloadable;
+import fr.xephi.authme.util.expiring.Duration;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* Class for retrieving and sending translatable messages to players.
@@ -15,6 +19,20 @@ public class Messages implements Reloadable {
// Custom Authme tag replaced to new line
private static final String NEWLINE_TAG = "%nl%";
+ /** Contains the keys of the singular messages for time units. */
+ private static final Map TIME_UNIT_SINGULARS = ImmutableMap.builder()
+ .put(TimeUnit.SECONDS, MessageKey.SECOND)
+ .put(TimeUnit.MINUTES, MessageKey.MINUTE)
+ .put(TimeUnit.HOURS, MessageKey.HOUR)
+ .put(TimeUnit.DAYS, MessageKey.DAY).build();
+
+ /** Contains the keys of the plural messages for time units. */
+ private static final Map TIME_UNIT_PLURALS = ImmutableMap.builder()
+ .put(TimeUnit.SECONDS, MessageKey.SECONDS)
+ .put(TimeUnit.MINUTES, MessageKey.MINUTES)
+ .put(TimeUnit.HOURS, MessageKey.HOURS)
+ .put(TimeUnit.DAYS, MessageKey.DAYS).build();
+
private final MessageFileHandlerProvider messageFileHandlerProvider;
private MessageFileHandler messageFileHandler;
@@ -71,6 +89,22 @@ public class Messages implements Reloadable {
return message.split("\n");
}
+ /**
+ * Returns the textual representation for the given duration.
+ * Note that this class only supports the time units days, hour, minutes and seconds.
+ *
+ * @param duration the duration to build a text of
+ * @return text of the duration
+ */
+ public String formatDuration(Duration duration) {
+ long value = duration.getDuration();
+ MessageKey timeUnitKey = value == 1
+ ? TIME_UNIT_SINGULARS.get(duration.getTimeUnit())
+ : TIME_UNIT_PLURALS.get(duration.getTimeUnit());
+
+ return value + " " + retrieveMessage(timeUnitKey);
+ }
+
/**
* Retrieve the message from the text file.
*
diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java
index daed1a8c..496fb3b2 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java
@@ -114,6 +114,13 @@ public class SecuritySettings implements SettingsHolder {
public static final Property RECOVERY_CODE_HOURS_VALID =
newProperty("Security.recoveryCode.validForHours", 4);
+ @Comment({
+ "Seconds a user has to wait for before a password recovery mail may be sent again",
+ "This prevents an attacker from abusing AuthMe's email feature."
+ })
+ public static final Property EMAIL_RECOVERY_COOLDOWN_SECONDS =
+ newProperty("Security.emailRecovery.cooldown", 60);
+
private SecuritySettings() {
}
diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java
index e7ec1657..b7628147 100644
--- a/src/main/java/fr/xephi/authme/util/Utils.java
+++ b/src/main/java/fr/xephi/authme/util/Utils.java
@@ -3,7 +3,6 @@ package fr.xephi.authme.util;
import fr.xephi.authme.ConsoleLogger;
import java.util.Collection;
-import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
@@ -71,36 +70,4 @@ public final class Utils {
return Runtime.getRuntime().availableProcessors();
}
- public static Duration convertMillisToSuitableUnit(long duration) {
- TimeUnit targetUnit;
- if (duration > 1000L * 60L * 60L * 24L) {
- targetUnit = TimeUnit.DAYS;
- } else if (duration > 1000L * 60L * 60L) {
- targetUnit = TimeUnit.HOURS;
- } else if (duration > 1000L * 60L) {
- targetUnit = TimeUnit.MINUTES;
- } else if (duration > 1000L) {
- targetUnit = TimeUnit.SECONDS;
- } else {
- targetUnit = TimeUnit.MILLISECONDS;
- }
-
- return new Duration(targetUnit, duration);
- }
-
- public static final class Duration {
-
- private final long duration;
- private final TimeUnit unit;
-
- Duration(TimeUnit targetUnit, long durationMillis) {
- this(targetUnit, durationMillis, TimeUnit.MILLISECONDS);
- }
-
- Duration(TimeUnit targetUnit, long sourceDuration, TimeUnit sourceUnit) {
- this.duration = targetUnit.convert(sourceDuration, sourceUnit);
- this.unit = targetUnit;
- }
- }
-
}
diff --git a/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java b/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java
index 894b6486..e920805c 100644
--- a/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java
+++ b/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java
@@ -46,7 +46,13 @@ public class ExpiringMap {
*/
public V get(K key) {
ExpiringEntry value = entries.get(key);
- return value == null ? null : value.getValue();
+ if (value == null) {
+ return null;
+ } else if (System.currentTimeMillis() > value.getExpiration()) {
+ entries.remove(key);
+ return null;
+ }
+ return value.getValue();
}
/**
@@ -115,7 +121,7 @@ public class ExpiringMap {
}
V getValue() {
- return System.currentTimeMillis() > expiration ? null : value;
+ return value;
}
long getExpiration() {
diff --git a/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java b/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java
index 7e711673..fea8fb31 100644
--- a/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java
+++ b/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java
@@ -83,23 +83,22 @@ public class ExpiringSet {
/**
* Returns the duration of the entry until it expires (provided it is not removed or re-added).
- * If the entry does not exist, -1 is returned.
+ * If the entry does not exist, a duration of -1 seconds is returned.
*
* @param entry the entry whose duration before it expires should be returned
- * @param unit the unit in which to return the duration
* @return duration the entry will remain in the set (if there are not modifications)
*/
- public long getExpiration(E entry, TimeUnit unit) {
+ public Duration getExpiration(E entry) {
Long expiration = entries.get(entry);
if (expiration == null) {
- return -1;
+ return new Duration(-1, TimeUnit.SECONDS);
}
long stillPresentMillis = expiration - System.currentTimeMillis();
if (stillPresentMillis < 0) {
entries.remove(entry);
- return -1;
+ return new Duration(-1, TimeUnit.SECONDS);
}
- return unit.convert(stillPresentMillis, TimeUnit.MILLISECONDS);
+ return Duration.createWithSuitableUnit(stillPresentMillis, TimeUnit.MILLISECONDS);
}
/**
diff --git a/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java
index 67839294..c3ae908c 100644
--- a/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java
+++ b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java
@@ -1,6 +1,5 @@
package fr.xephi.authme.util.expiring;
-import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
@@ -42,9 +41,10 @@ public class TimedCounter extends ExpiringMap {
* @return the total of all valid entries
*/
public int total() {
+ long currentTime = System.currentTimeMillis();
return entries.values().stream()
+ .filter(entry -> currentTime <= entry.getExpiration())
.map(ExpiringEntry::getValue)
- .filter(Objects::nonNull)
.reduce(0, Integer::sum);
}
}
diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml
index 977e8eaf..38e79fac 100644
--- a/src/main/resources/messages/messages_bg.yml
+++ b/src/main/resources/messages/messages_bg.yml
@@ -45,7 +45,7 @@ unregistered: '&cУспешно от-регистриран!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&fТвоята регистрация не е активирана, моля провери своя Имейл!'
usage_unreg: '&cКоманда: /unregister парола'
pwd_changed: '&cПаролата е променена!'
@@ -87,8 +87,19 @@ email_send: '[AuthMe] Изпраен е имейл !'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&cМоля добави своя имейл с : /email add имейл имейл'
recovery_email: '&cЗабравихте своята парола? Моля използвай /email recovery <имейл>'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cYou need to type a captcha, please type: /captcha '
wrong_captcha: '&cГрешен код, използвай : /captcha THE_CAPTCHA'
valid_captcha: '&cТвоя код е валиден!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml
index a6117b3b..c213f49c 100644
--- a/src/main/resources/messages/messages_br.yml
+++ b/src/main/resources/messages/messages_br.yml
@@ -90,8 +90,19 @@ email_send_failure: '&cO e-mail não pôde ser enviado, reporte isso a um admini
show_no_email: '&2Você atualmente não têm endereço de e-mail associado a esta conta.'
add_email: '&3Por favor, adicione seu e-mail para a sua conta com o comando "/email add "'
recovery_email: '&3Esqueceu sua senha? Por favor, use o comando "/email recovery "'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3Para iniciar sessão você tem que resolver um código captcha, utilize o comando "/captcha "'
wrong_captcha: '&cCaptcha errado, por favor, escreva "/captcha THE_CAPTCHA" no chat!'
valid_captcha: '&2Código Captcha resolvido corretamente!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml
index 8b653fb4..586a7376 100644
--- a/src/main/resources/messages/messages_cz.yml
+++ b/src/main/resources/messages/messages_cz.yml
@@ -86,8 +86,19 @@ email_send_failure: 'Email nemohl být odeslán. Kontaktujte prosím admina.'
show_no_email: '&2K tomuto účtu nemáte přidanou žádnou emailovou adresu.'
add_email: '&cPřidej prosím svůj email pomocí : /email add TvůjEmail TvůjEmail'
recovery_email: '&cZapomněl jsi heslo? Napiš: /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cPoužij: /captcha '
wrong_captcha: '&cŠpatné opsana Captcha, pouzij prosim: /captcha THE_CAPTCHA'
valid_captcha: '&cZadaná captcha je v pořádku!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml
index 474c8237..648a5153 100644
--- a/src/main/resources/messages/messages_de.yml
+++ b/src/main/resources/messages/messages_de.yml
@@ -86,8 +86,19 @@ email_send_failure: 'Die E-Mail konnte nicht gesendet werden. Bitte kontaktiere
show_no_email: '&2Du hast zur Zeit keine E-Mail-Adresse für deinen Account hinterlegt.'
add_email: '&3Bitte hinterlege deine E-Mail-Adresse: /email add '
recovery_email: '&3Passwort vergessen? Nutze "/email recovery " für ein neues Passwort'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3Um dich einzuloggen, tippe dieses Captcha so ein: /captcha '
wrong_captcha: '&cFalsches Captcha, bitte nutze: /captcha THE_CAPTCHA'
valid_captcha: '&2Das Captcha ist korrekt!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml
index d1b14700..c7190234 100644
--- a/src/main/resources/messages/messages_en.yml
+++ b/src/main/resources/messages/messages_en.yml
@@ -86,8 +86,19 @@ email_send_failure: 'The email could not be sent. Please contact an administrato
show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&3Please add your email to your account with the command: /email add '
recovery_email: '&3Forgot your password? Please use the command: /email recovery '
+email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3To login you have to solve a captcha code, please use the command: /captcha '
wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!'
valid_captcha: '&2Captcha code solved correctly!'
+
+# Time units
+second: 'second'
+seconds: 'seconds'
+minute: 'minute'
+minutes: 'minutes'
+hour: 'hour'
+hours: 'hours'
+day: 'day'
+days: 'days'
diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml
index e0c0c96d..ac741571 100644
--- a/src/main/resources/messages/messages_es.yml
+++ b/src/main/resources/messages/messages_es.yml
@@ -89,8 +89,19 @@ email_send_failure: 'No se ha podido enviar el correo electrónico. Por favor, c
show_no_email: '&2No tienes ningun E-Mail asociado en esta cuenta.'
add_email: '&cPor favor agrega tu e-mail con: /email add tuEmail confirmarEmail'
recovery_email: '&c¿Olvidaste tu contraseña? Por favor usa /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cUso: /captcha '
wrong_captcha: '&cCaptcha incorrecto, por favor usa: /captcha THE_CAPTCHA'
valid_captcha: '&c¡ Captcha ingresado correctamente !'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml
index 53b0704d..05473e68 100644
--- a/src/main/resources/messages/messages_eu.yml
+++ b/src/main/resources/messages/messages_eu.yml
@@ -45,7 +45,7 @@ unregistered: '&cZure erregistroa ezabatu duzu!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&fZure kontua aktibatu gabe dago, konfirmatu zure emaila!'
usage_unreg: '&cErabili: /unregister password'
pwd_changed: '&cPasahitza aldatu duzu!'
@@ -87,8 +87,19 @@ email_send: '[AuthMe] Berreskuratze emaila bidalita !'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&cMesedez gehitu zure emaila : /email add yourEmail confirmEmail'
recovery_email: '&cPasahitza ahaztu duzu? Erabili /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
-# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "'
+# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command: /captcha '
# TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!'
# TODO valid_captcha: '&2Captcha code solved correctly!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml
index bf359e8c..a48ac74d 100644
--- a/src/main/resources/messages/messages_fi.yml
+++ b/src/main/resources/messages/messages_fi.yml
@@ -45,7 +45,7 @@ unregistered: '&cPelaajatili poistettu onnistuneesti!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&fKäyttäjäsi ei ole vahvistettu!'
usage_unreg: '&cKäyttötapa: /unregister password'
pwd_changed: '&cSalasana vaihdettu!!'
@@ -87,8 +87,19 @@ email_send: '[AuthMe] Palautus sähköposti lähetetty!'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&cLisää sähköpostisi: /email add sähköpostisi sähköpostisiUudelleen'
recovery_email: '&cUnohtuiko salasana? Käytä komentoa: /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cKäyttötapa: /captcha '
wrong_captcha: '&cVäärä varmistus, käytä : /captcha THE_CAPTCHA'
valid_captcha: '&cSinun varmistus onnistui.!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml
index 1bd88456..dd01ccc6 100644
--- a/src/main/resources/messages/messages_fr.yml
+++ b/src/main/resources/messages/messages_fr.yml
@@ -91,8 +91,19 @@ email_send_failure: '&cL''email n''a pas pu être envoyé. Veuillez contacter un
show_no_email: 'Vous n''avez aucune adresse email enregistré sur votre compte.'
add_email: '&cMerci d''ajouter votre email : /email add '
recovery_email: '&cVous avez oublié votre Mot de Passe? Utilisez /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cTrop de tentatives de connexion échouées, utilisez: /captcha '
wrong_captcha: '&cCaptcha incorrect, écrivez de nouveau : /captcha THE_CAPTCHA'
valid_captcha: '&aCaptché validé! Veuillez maintenant vous connecter.'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml
index 2739a1a8..1c057030 100644
--- a/src/main/resources/messages/messages_gl.yml
+++ b/src/main/resources/messages/messages_gl.yml
@@ -45,7 +45,7 @@ unregistered: '&cFeito! Xa non estás rexistrado!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&fA túa conta aínda non está activada, comproba a túa bandexa de correo!!'
usage_unreg: '&cUso: /unregister '
pwd_changed: '&cCambiouse o contrasinal!'
@@ -87,8 +87,19 @@ email_send: '[AuthMe] Enviouse o correo de confirmación!'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&cPor favor, engade o teu correo electrónico con: /email add '
recovery_email: '&cOlvidaches o contrasinal? Por favor, usa /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cNecesitas escribir un captcha, por favor escribe: /captcha '
wrong_captcha: '&cCaptcha equivocado, por favor usa: /captcha THE_CAPTCHA'
valid_captcha: '&cO teu captcha é válido !'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml
index f5667eb8..dc62f8c1 100644
--- a/src/main/resources/messages/messages_hu.yml
+++ b/src/main/resources/messages/messages_hu.yml
@@ -86,8 +86,19 @@ email_already_used: '&4Ez az email cím már használatban van!'
show_no_email: '&2Ehhez a felhasználóhoz jelenleg még nincs email hozzárendelve.'
add_email: '&3Kérlek rendeld hozzá a felhasználódhoz az email címedet "/email add "'
recovery_email: '&3Ha elfelejtetted a jelszavad, használd az: "/email recovery "'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3A bejelentkezéshez CAPTCHA szükséges, kérlek használd a következő parancsot "/captcha "'
wrong_captcha: '&cHibás CAPTCHA, kérlek írd be a következő parancsot: "/captcha THE_CAPTCHA"!'
valid_captcha: '&2CAPTCHA sikeresen feloldva!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml
index b4fde4ab..90db87bd 100644
--- a/src/main/resources/messages/messages_id.yml
+++ b/src/main/resources/messages/messages_id.yml
@@ -45,7 +45,7 @@ unregistered: '&cUnregister berhasil!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&cAkunmu belum diaktifkan, silahkan periksa email kamu!'
# TODO usage_unreg: '&cUsage: /unregister '
pwd_changed: '&2Berhasil mengubah password!'
@@ -87,8 +87,19 @@ email_exists: '&cEmail pemulihan sudah dikirim! kamu bisa membatalkan dan mengir
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&3Silahkan tambahkan email ke akunmu menggunakan command "/email add "'
recovery_email: '&3Lupa password? silahkan gunakan command "/email recovery "'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3Kamu harus menyelesaikan kode captcha untuk login, silahkan gunakan command "/captcha "'
wrong_captcha: '&cCaptcha salah, gunakan command "/captcha THE_CAPTCHA" pada chat!'
valid_captcha: '&2Kode captcha terselesaikan!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml
index cdf6fed8..ede27690 100644
--- a/src/main/resources/messages/messages_it.yml
+++ b/src/main/resources/messages/messages_it.yml
@@ -88,8 +88,19 @@ email_send_failure: 'Non è stato possibile inviare l''email contenente la tua n
show_no_email: '&2Al momento non hai nessun indirizzo email associato al tuo account.'
add_email: '&3Per poter recuperare la password in futuro, aggiungi un indirizzo email al tuo account con il comando: /email add '
recovery_email: '&3Hai dimenticato la tua password? Puoi recuperarla eseguendo il comando: /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3Per poterti autenticare devi risolvere un captcha, per favore scrivi: /captcha '
wrong_captcha: '&cCaptcha sbagliato, per favore riprova scrivendo: "/captcha THE_CAPTCHA" in chat!'
valid_captcha: '&2Il captcha inserito è valido!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml
index db7bc8aa..a65d9352 100644
--- a/src/main/resources/messages/messages_ko.yml
+++ b/src/main/resources/messages/messages_ko.yml
@@ -49,7 +49,7 @@ unregistered: '&c성공적으로 탈퇴했습니다!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&f당신의 계정은 아직 활성화되어있지 않습니다, 당신의 이메일을 확인해보세요!'
usage_unreg: '&c사용법: /unregister 비밀번호'
pwd_changed: '&c비밀번호를 변경했습니다!'
@@ -91,8 +91,19 @@ email_exists: '[AuthMe] 당신의 계정에 이미 이메일이 존재합니다.
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&c당신의 이메일을 추가해주세요 : /email add 당신의이메일 이메일재입력'
recovery_email: '&c비밀번호를 잊어버리셨다고요? /email recovery <당신의이메일>을 사용하세요'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&c보안문자 입력이 필요합니다, 입력해주세요: /captcha '
wrong_captcha: '&c잘못된 보안문자, 사용해주세요 : /captcha THE_CAPTCHA'
valid_captcha: '&c당신의 보안문자는 적합합니다!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml
index a2bcbe25..d75dec4a 100644
--- a/src/main/resources/messages/messages_lt.yml
+++ b/src/main/resources/messages/messages_lt.yml
@@ -45,7 +45,7 @@ unregistered: '&aSekmingai issiregistravote!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&aJusu vartotojas nera patvirtintas, patikrinkite el.pasta.'
usage_unreg: '&ePanaikinti registracija: "/unregister slaptazodis"'
pwd_changed: '&aSlaptazodis pakeistas'
@@ -87,8 +87,19 @@ same_nick: '&cKazkas situo vardu jau zaidzia.'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&ePrasau pridekite savo el.pasta : /email add Email confirmEmail'
recovery_email: '&cPamirsote slaptazodi? Rasykite: /email recovery el.pastas'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cPanaudojimas: /captcha '
wrong_captcha: '&cNeteisinga Captcha, naudokite : /captcha THE_CAPTCHA'
valid_captcha: '&cJusu captcha Teisinga!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml
index 2ee68f95..630fadd6 100644
--- a/src/main/resources/messages/messages_nl.yml
+++ b/src/main/resources/messages/messages_nl.yml
@@ -86,8 +86,19 @@ email_send_failure: 'De E-mail kon niet verzonden worden. Neem contact op met ee
show_no_email: '&2Je hebt nog geen E-mailadres toegevoegd aan dit account.'
add_email: '&3Voeg jouw E-mailadres alsjeblieft toe met: /email add '
recovery_email: '&3Wachtwoord vergeten? Gebruik alsjeblieft het commando: /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3Om in te loggen moet je een captcha-code oplossen, gebruik het commando: /captcha '
wrong_captcha: '&cVerkeerde captcha-code, typ alsjeblieft "/captcha THE_CAPTCHA" in de chat!'
valid_captcha: '&2De captcha-code is geldig!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml
index 3f9e37ae..549efb9e 100644
--- a/src/main/resources/messages/messages_pl.yml
+++ b/src/main/resources/messages/messages_pl.yml
@@ -87,8 +87,19 @@ email_send_failure: 'Nie mozna wyslac emaila. Skontaktuj sie z administracja.'
show_no_email: '&2Nie posiadasz adresu email przypisanego do tego konta.'
add_email: '&cProsze dodac swoj email: /email add twojEmail powtorzEmail'
recovery_email: '&cZapomniales hasla? Prosze uzyj komendy /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cWpisz: /captcha '
wrong_captcha: '&cZly kod, prosze wpisac: /captcha THE_CAPTCHA'
valid_captcha: '&cTwoj kod jest nieprawidlowy!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml
index 7254f8bb..fef35f74 100644
--- a/src/main/resources/messages/messages_pt.yml
+++ b/src/main/resources/messages/messages_pt.yml
@@ -45,7 +45,7 @@ unregistered: '&cRegisto eliminado com sucesso!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
two_factor_create: '&2O seu código secreto é o %code. Você pode verificá-lo a partir daqui %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&fA sua conta não foi ainda activada, verifique o seu email onde irá receber indicações para activação de conta. '
usage_unreg: '&cUse: /unregister password'
pwd_changed: '&cPassword alterada!'
@@ -87,8 +87,19 @@ email_already_used: '&4O endereço de e-mail já está sendo usado'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&cPor favor adicione o seu email com : /email add seuEmail confirmarSeuEmail'
recovery_email: '&cPerdeu a sua password? Para a recuperar escreva /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha '
wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha THE_CAPTCHA'
valid_captcha: '&cO seu captcha é válido!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml
index d1726d69..703b93f5 100644
--- a/src/main/resources/messages/messages_ro.yml
+++ b/src/main/resources/messages/messages_ro.yml
@@ -86,8 +86,19 @@ email_already_used: '&4Email-ul a fost deja folosit'
show_no_email: '&2Nu ai nici-o adresa de email asociat cu acest cont.'
add_email: '&3Te rugam adaugati email-ul la contul tau folosind comanda "/email add "'
recovery_email: '&3Ti-ai uitat parola? Te rugam foloseste comanda "/email recovery "'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3Pentru a te autentifica trebuie sa folosesti codul de la captcha, te rugam foloseste comanda "/captcha "'
wrong_captcha: '&cCod-ul captcha este gresit, te rugam foloseste comanda "/captcha THE_CAPTCHA"!'
valid_captcha: '&2Cod-ul captcha a fost scris corect!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml
index 873b6397..11f55835 100644
--- a/src/main/resources/messages/messages_ru.yml
+++ b/src/main/resources/messages/messages_ru.yml
@@ -86,8 +86,19 @@ email_send_failure: 'Письмо не може быть отправлено.
show_no_email: '&2В данный момент к вашему аккаунте не привязана электронная почта.'
add_email: '&cДобавьте свой email: &e/email add <Ваш Email> <Ваш Email>'
recovery_email: '&cЗабыли пароль? Используйте &e/email recovery <Ваш Email>'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Каптча
usage_captcha: '&cВы должны ввести код, используйте: &e/captcha '
wrong_captcha: '&cНеверный код, используйте: &e/captcha THE_CAPTCHA'
valid_captcha: '&2Вы успешно ввели код!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_sk.yml b/src/main/resources/messages/messages_sk.yml
index 5eb97d92..b49f8cf5 100644
--- a/src/main/resources/messages/messages_sk.yml
+++ b/src/main/resources/messages/messages_sk.yml
@@ -49,7 +49,7 @@ unregistered: '&cUcet bol vymazany!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&fUcet nie je aktivny. Prezri si svoj e-mail!'
usage_unreg: '&cPríkaz: /unregister heslo'
pwd_changed: '&cHeslo zmenené!'
@@ -91,8 +91,19 @@ same_nick: '&fHrác s tymto nickom uz hrá!'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&cPridaj svoj e-mail príkazom "/email add email zopakujEmail"'
recovery_email: '&cZabudol si heslo? Pouzi príkaz /email recovery '
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
-# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "'
+# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command: /captcha '
# TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!'
# TODO valid_captcha: '&2Captcha code solved correctly!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml
index 480e0e5b..a3ba1f26 100644
--- a/src/main/resources/messages/messages_tr.yml
+++ b/src/main/resources/messages/messages_tr.yml
@@ -44,7 +44,7 @@ unregistered: '&cKayit basariyla kaldirildi!'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
two_factor_create: '&2Gizli kodunuz %code. Buradan test edebilirsin, %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&cHeabiniz henuz aktif edilmemis, e-postanizi kontrol edin!'
usage_unreg: '&cKullanim: /unregister '
pwd_changed: '&2Sifre basariyla degistirildi!'
@@ -86,8 +86,19 @@ email_already_used: '&4Eposta adresi zaten kullaniliyor.'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&3Lutfen hesabinize eposta adresinizi komut ile ekleyin "/email add "'
recovery_email: '&3Sifreni mi unuttun ? Komut kullanarak ogrenebilirsin "/email recovery "'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3Giris yapmak icin guvenlik kodunu komut yazarak girin "/captcha "'
wrong_captcha: '&cYanlis guvenlik kodu, kullanim sekli "/captcha THE_CAPTCHA" sohbete yazin!'
valid_captcha: '&2Guvenlik kodu dogrulandi!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_uk.yml b/src/main/resources/messages/messages_uk.yml
index d1d53187..35dbbffe 100644
--- a/src/main/resources/messages/messages_uk.yml
+++ b/src/main/resources/messages/messages_uk.yml
@@ -44,7 +44,7 @@ accounts_owned_self: 'Кількість ваших твінк‒акаунті
accounts_owned_other: 'Кількість твінк‒акаунтів гравця %name: %count'
two_factor_create: '&2Ваш секретний код — %code %nl%&2Можете зкопіювати його за цим посиланням — %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&cВаш акаунт ще не активовано. Будь ласка, провірте свою електронну пошту!'
usage_unreg: '&cСинтаксис: /unregister <пароль>'
pwd_changed: '&2Пароль успішно змінено!'
@@ -86,8 +86,19 @@ email_already_used: '&4До цієї електронної пошти прив
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&3Не забудьте прив’язати електронну пошту до свого акаунта, за допомогою команди "/email add "'
recovery_email: 'Забули пароль? Можете скористатись командою &9/email recovery &f<&9ваш e-mail&f>'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3Для продовження доведеться ввести капчу — "/captcha "'
wrong_captcha: '&cНевірно введена капча! Спробуйте ще раз — "/captcha THE_CAPTCHA"'
valid_captcha: '&2Капчу прийнято.'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_vn.yml b/src/main/resources/messages/messages_vn.yml
index 4cb86ae8..7f68be06 100644
--- a/src/main/resources/messages/messages_vn.yml
+++ b/src/main/resources/messages/messages_vn.yml
@@ -86,8 +86,19 @@ email_send_failure: 'Không thể gửi thư. Vui lòng liên hệ với ban qu
show_no_email: '&2Hiện tại bạn chưa liên kết bất kỳ email nào với tài khoản này.'
add_email: '&eVui lòng thêm email của bạn với lệnh "/email add "'
recovery_email: '&aBạn quên mật khẩu? Vui lòng gõ lệnh "/email recovery "'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&eĐể đăng nhập vui lòng hãy gõ mã Captcha, gõ lệnh "/captcha "'
wrong_captcha: '&cSai mã captcha, Vui lòng nhấn "/captcha THE_CAPTCHA" trong kênh chát!'
valid_captcha: '&2Mã captcha đã được xác nhận!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml
index e6c5e755..63922334 100644
--- a/src/main/resources/messages/messages_zhcn.yml
+++ b/src/main/resources/messages/messages_zhcn.yml
@@ -87,8 +87,19 @@ email_send_failure: '邮件发送失败,请联系管理员'
show_no_email: '&2您当前并没有任何邮箱与该账号绑定'
add_email: '&8[&6玩家系统&8] &c请输入“/email add <你的邮箱> <再输入一次以确认>”以把你的邮箱添加到此帐号'
recovery_email: '&8[&6玩家系统&8] &c忘了你的密码?请输入:“/email recovery <你的邮箱>”'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&8[&6玩家系统&8] &c正确用法:/captcha '
wrong_captcha: '&8[&6玩家系统&8] &c错误的验证码,请输入:“/captcha THE_CAPTCHA”'
valid_captcha: '&8[&6玩家系统&8] &c你的验证码是有效的!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_zhhk.yml b/src/main/resources/messages/messages_zhhk.yml
index a07d5249..896003d4 100644
--- a/src/main/resources/messages/messages_zhhk.yml
+++ b/src/main/resources/messages/messages_zhhk.yml
@@ -49,7 +49,7 @@ unregistered: '&8[&6用戶系統&8] &c你已成功刪除會員註冊記錄。'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
two_factor_create: '&8[&6用戶系統 - 兩步驗證碼&8] &b你的登入金鑰為&9「%c%code&9」&b,掃描連結為:&c %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&8[&6用戶系統&8] &f你的帳戶還沒有經過電郵驗證 !'
usage_unreg: '&8[&6用戶系統&8] &f用法: 《 /unregister <密碼> 》'
pwd_changed: '&8[&6用戶系統&8] &c你成功更換了你的密碼 !'
@@ -91,8 +91,19 @@ email_already_used: '&8[&6用戶系統&8] &4這個電郵地址已被使用。'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&8[&6用戶系統&8] &b請為你的帳戶立即添加電郵地址: 《 /email add <電郵地址> <重覆電郵地址> 》'
recovery_email: '&8[&6用戶系統&8] &b忘記密碼?請使用 /email recovery <電郵地址> 來更新密碼。'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&8[&6用戶系統&8] &f用法: 《 /captcha 》'
wrong_captcha: '&8[&6用戶系統&8] &c你所輸入的驗證碼無效,請使用 《 /captcha THE_CAPTCHA 》 再次輸入。'
valid_captcha: '&8[&6用戶系統&8] &c你所輸入的驗證碼無效 !'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_zhmc.yml b/src/main/resources/messages/messages_zhmc.yml
index 1d9876ad..92f9113e 100644
--- a/src/main/resources/messages/messages_zhmc.yml
+++ b/src/main/resources/messages/messages_zhmc.yml
@@ -86,8 +86,19 @@ email_already_used: '&4此電子郵件地址已被使用'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&3請使用命令: /email add [你的電郵地址] [重覆確認你的電郵地址] 將您的電子郵件添加到您的帳戶"'
recovery_email: '&3忘記密碼了嗎? 請使用命令: "/email recovery [你的電郵地址]"'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&3T要登錄您必須使用captcha驗證碼,請使用命令: "/captcha "'
wrong_captcha: '&c驗證碼錯誤!請按T在聊天中輸入 "/captcha THE_CAPTCHA"'
valid_captcha: '&2驗證碼正確!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
diff --git a/src/main/resources/messages/messages_zhtw.yml b/src/main/resources/messages/messages_zhtw.yml
index b5dc97cf..b2cff038 100644
--- a/src/main/resources/messages/messages_zhtw.yml
+++ b/src/main/resources/messages/messages_zhtw.yml
@@ -49,7 +49,7 @@ unregistered: '&b【AuthMe】&6你已經成功取消註冊。'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
two_factor_create: '&b【AuthMe - 兩步驗證碼】&b你的登入金鑰為&9「%c%code&9」&b,掃描連結為:&c %url'
# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
-# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
+# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one'
vb_nonActiv: '&b【AuthMe】&6你的帳號還沒有經過驗證! 檢查看看你的電子信箱 (Email) 吧!'
usage_unreg: '&b【AuthMe】&6用法: &c"/unregister <密碼>"'
pwd_changed: '&b【AuthMe】&6密碼變更成功!'
@@ -91,8 +91,19 @@ email_already_used: '&b【AuthMe】&4這個電郵地址已被使用。'
# TODO show_no_email: '&2You currently don''t have email address associated with this account.'
add_email: '&b【AuthMe】&6請使用 &c"/email add <你的Email> <再次輸入你的Email>" &6來添加 Email'
recovery_email: '&b【AuthMe】&6忘記密碼了嗎? 使用 &c"/email recovery <你的Email>"'
+# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
# Captcha
usage_captcha: '&b【AuthMe】&6請用 &c"/captcha " &6來輸入你的驗證碼'
wrong_captcha: '&b【AuthMe】&6錯誤的驗證碼,請使用 《 /captcha THE_CAPTCHA 》 再試一次吧。'
valid_captcha: '&b【AuthMe】&6驗證碼無效!'
+
+# Time units
+# TODO second: 'second'
+# TODO seconds: 'seconds'
+# TODO minute: 'minute'
+# TODO minutes: 'minutes'
+# TODO hour: 'hour'
+# TODO hours: 'hours'
+# TODO day: 'day'
+# TODO days: 'days'
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 556a1033..5b102078 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,29 +1,39 @@
package fr.xephi.authme.command.executable.email;
+import ch.jalu.injector.testing.BeforeInjecting;
+import ch.jalu.injector.testing.DelayedInjectionRunner;
+import ch.jalu.injector.testing.InjectDelayed;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
+import fr.xephi.authme.message.Messages;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.RecoveryCodeService;
import fr.xephi.authme.settings.properties.EmailSettings;
+import fr.xephi.authme.settings.properties.SecuritySettings;
+import fr.xephi.authme.util.expiring.Duration;
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.junit.MockitoJUnitRunner;
+import org.mockito.Mockito;
import java.util.Arrays;
import java.util.Collections;
+import java.util.concurrent.TimeUnit;
import static fr.xephi.authme.AuthMeMatchers.stringWithLength;
+import static org.hamcrest.Matchers.both;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -38,19 +48,19 @@ import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Test for {@link RecoverEmailCommand}.
*/
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(DelayedInjectionRunner.class)
public class RecoverEmailCommandTest {
private static final String DEFAULT_EMAIL = "your@email.com";
- @InjectMocks
+ @InjectDelayed
private RecoverEmailCommand command;
@Mock
private PasswordSecurity passwordSecurity;
@Mock
- private CommonService commandService;
+ private CommonService commonService;
@Mock
private DataSource dataSource;
@@ -64,11 +74,19 @@ public class RecoverEmailCommandTest {
@Mock
private RecoveryCodeService recoveryCodeService;
+ @Mock
+ private Messages messages;
+
@BeforeClass
public static void initLogger() {
TestHelper.setupLogger();
}
+ @BeforeInjecting
+ public void initSettings() {
+ given(commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS)).willReturn(40);
+ }
+
@Test
public void shouldHandleMissingMailProperties() {
// given
@@ -79,7 +97,7 @@ public class RecoverEmailCommandTest {
command.executeCommand(sender, Collections.singletonList("some@email.tld"));
// then
- verify(commandService).send(sender, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
+ verify(commonService).send(sender, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
verifyZeroInteractions(dataSource, passwordSecurity);
}
@@ -98,7 +116,7 @@ public class RecoverEmailCommandTest {
// then
verify(emailService).hasAllInformation();
verifyZeroInteractions(dataSource);
- verify(commandService).send(sender, MessageKey.ALREADY_LOGGED_IN_ERROR);
+ verify(commonService).send(sender, MessageKey.ALREADY_LOGGED_IN_ERROR);
}
@Test
@@ -118,7 +136,7 @@ public class RecoverEmailCommandTest {
verify(emailService).hasAllInformation();
verify(dataSource).getAuth(name);
verifyNoMoreInteractions(dataSource);
- verify(commandService).send(sender, MessageKey.USAGE_REGISTER);
+ verify(commonService).send(sender, MessageKey.USAGE_REGISTER);
}
@Test
@@ -138,7 +156,7 @@ public class RecoverEmailCommandTest {
verify(emailService).hasAllInformation();
verify(dataSource).getAuth(name);
verifyNoMoreInteractions(dataSource);
- verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
+ verify(commonService).send(sender, MessageKey.INVALID_EMAIL);
}
@Test
@@ -158,7 +176,7 @@ public class RecoverEmailCommandTest {
verify(emailService).hasAllInformation();
verify(dataSource).getAuth(name);
verifyNoMoreInteractions(dataSource);
- verify(commandService).send(sender, MessageKey.INVALID_EMAIL);
+ verify(commonService).send(sender, MessageKey.INVALID_EMAIL);
}
@Test
@@ -183,7 +201,7 @@ public class RecoverEmailCommandTest {
verify(emailService).hasAllInformation();
verify(dataSource).getAuth(name);
verify(recoveryCodeService).generateCode(name);
- verify(commandService).send(sender, MessageKey.RECOVERY_CODE_SENT);
+ verify(commonService).send(sender, MessageKey.RECOVERY_CODE_SENT);
verify(emailService).sendRecoveryCode(name, email, code);
}
@@ -207,7 +225,7 @@ public class RecoverEmailCommandTest {
// then
verify(emailService).hasAllInformation();
verify(dataSource, only()).getAuth(name);
- verify(commandService).send(sender, MessageKey.INCORRECT_RECOVERY_CODE);
+ verify(commonService).send(sender, MessageKey.INCORRECT_RECOVERY_CODE);
verifyNoMoreInteractions(emailService);
}
@@ -224,7 +242,7 @@ public class RecoverEmailCommandTest {
String code = "A6EF3AC8";
PlayerAuth auth = newAuthWithEmail(email);
given(dataSource.getAuth(name)).willReturn(auth);
- given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20);
+ given(commonService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20);
given(passwordSecurity.computeHash(anyString(), eq(name)))
.willAnswer(invocation -> new HashedPassword(invocation.getArgument(0)));
given(recoveryCodeService.isRecoveryCodeNeeded()).willReturn(true);
@@ -243,7 +261,7 @@ public class RecoverEmailCommandTest {
verify(dataSource).updatePassword(eq(name), any(HashedPassword.class));
verify(recoveryCodeService).removeCode(name);
verify(emailService).sendPasswordMail(name, email, generatedPassword);
- verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
+ verify(commonService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
}
@Test
@@ -258,7 +276,7 @@ public class RecoverEmailCommandTest {
String email = "shark@example.org";
PlayerAuth auth = newAuthWithEmail(email);
given(dataSource.getAuth(name)).willReturn(auth);
- given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20);
+ given(commonService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20);
given(passwordSecurity.computeHash(anyString(), eq(name)))
.willAnswer(invocation -> new HashedPassword(invocation.getArgument(0)));
given(recoveryCodeService.isRecoveryCodeNeeded()).willReturn(false);
@@ -275,7 +293,40 @@ public class RecoverEmailCommandTest {
assertThat(generatedPassword, stringWithLength(20));
verify(dataSource).updatePassword(eq(name), any(HashedPassword.class));
verify(emailService).sendPasswordMail(name, email, generatedPassword);
- verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
+ verify(commonService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
+ }
+
+ @Test
+ public void shouldNotSendEmailIfCooldownCheckFails() {
+ // given
+ String name = "feverRay";
+ Player sender = mock(Player.class);
+ given(sender.getName()).willReturn(name);
+ given(emailService.hasAllInformation()).willReturn(true);
+ given(emailService.sendRecoveryCode(anyString(), anyString(), anyString())).willReturn(true);
+ given(playerCache.isAuthenticated(name)).willReturn(false);
+ String email = "mymail@example.org";
+ PlayerAuth auth = newAuthWithEmail(email);
+ given(dataSource.getAuth(name)).willReturn(auth);
+ given(recoveryCodeService.isRecoveryCodeNeeded()).willReturn(true);
+ given(recoveryCodeService.generateCode(anyString())).willReturn("Code");
+ // Trigger sending of recovery code
+ command.executeCommand(sender, Collections.singletonList(email));
+
+ Mockito.reset(emailService, commonService);
+ given(emailService.hasAllInformation()).willReturn(true);
+ given(messages.formatDuration(any(Duration.class))).willReturn("8 minutes");
+
+ // when
+ command.executeCommand(sender, Collections.singletonList(email));
+
+ // then
+ verify(emailService, only()).hasAllInformation();
+ ArgumentCaptor durationCaptor = ArgumentCaptor.forClass(Duration.class);
+ verify(messages).formatDuration(durationCaptor.capture());
+ assertThat(durationCaptor.getValue().getDuration(), both(lessThan(41L)).and(greaterThan(36L)));
+ assertThat(durationCaptor.getValue().getTimeUnit(), equalTo(TimeUnit.SECONDS));
+ verify(messages).send(sender, MessageKey.EMAIL_COOLDOWN_ERROR, "8 minutes");
}
diff --git a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java
index 94fe9a0d..8f48fffe 100644
--- a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java
+++ b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java
@@ -1,7 +1,9 @@
package fr.xephi.authme.message;
+import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.TestHelper;
+import fr.xephi.authme.util.expiring.Duration;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.junit.Before;
@@ -11,6 +13,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.io.File;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Logger;
@@ -230,6 +234,26 @@ public class MessagesIntegrationTest {
assertThat(result, equalTo("Use /captcha 24680 to solve the captcha"));
}
+ @Test
+ public void shouldFormatDurationObjects() {
+ // given
+ Map expectedTexts = ImmutableMap.builder()
+ .put(new Duration(1, TimeUnit.SECONDS), "1 second")
+ .put(new Duration(12, TimeUnit.SECONDS), "12 seconds")
+ .put(new Duration(1, TimeUnit.MINUTES), "1 minute")
+ .put(new Duration(0, TimeUnit.MINUTES), "0 minutes")
+ .put(new Duration(1, TimeUnit.HOURS), "1 hour")
+ .put(new Duration(-4, TimeUnit.HOURS), "-4 hours")
+ .put(new Duration(1, TimeUnit.DAYS), "1 day")
+ .put(new Duration(44, TimeUnit.DAYS), "44 days")
+ .build();
+
+ // when / then
+ for (Map.Entry entry : expectedTexts.entrySet()) {
+ assertThat(messages.formatDuration(entry.getKey()), equalTo(entry.getValue()));
+ }
+ }
+
@SuppressWarnings("unchecked")
private static MessageFileHandlerProvider providerReturning(File file, String defaultFile) {
MessageFileHandlerProvider handler = mock(MessageFileHandlerProvider.class);
diff --git a/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java b/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java
index 15a55aa0..42180ab2 100644
--- a/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java
+++ b/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java
@@ -4,7 +4,6 @@ import org.junit.Test;
import java.util.concurrent.TimeUnit;
-import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@@ -97,14 +96,31 @@ public class ExpiringSetTest {
set.add("my entry");
// when
- long expiresInHours = set.getExpiration("my entry", TimeUnit.HOURS);
- long expiresInMinutes = set.getExpiration("my entry", TimeUnit.MINUTES);
- long unknownExpires = set.getExpiration("bogus", TimeUnit.SECONDS);
+ Duration expiration = set.getExpiration("my entry");
+ Duration unknownExpiration = set.getExpiration("bogus");
// then
- assertThat(expiresInHours, equalTo(2L));
- assertThat(expiresInMinutes, either(equalTo(122L)).or(equalTo(123L)));
- assertThat(unknownExpires, equalTo(-1L));
+ assertIsDuration(expiration, 2, TimeUnit.HOURS);
+ assertIsDuration(unknownExpiration, -1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void shouldReturnExpirationInSuitableUnits() {
+ // given
+ ExpiringSet set = new ExpiringSet<>(601, TimeUnit.SECONDS);
+ set.add(12);
+ set.setExpiration(49, TimeUnit.HOURS);
+ set.add(23);
+
+ // when
+ Duration expiration12 = set.getExpiration(12);
+ Duration expiration23 = set.getExpiration(23);
+ Duration expectedUnknown = set.getExpiration(-100);
+
+ // then
+ assertIsDuration(expiration12, 10, TimeUnit.MINUTES);
+ assertIsDuration(expiration23, 2, TimeUnit.DAYS);
+ assertIsDuration(expectedUnknown, -1, TimeUnit.SECONDS);
}
@Test
@@ -114,9 +130,14 @@ public class ExpiringSetTest {
set.add(23);
// when
- long expiresInSeconds = set.getExpiration(23, TimeUnit.SECONDS);
+ Duration expiration = set.getExpiration(23);
// then
- assertThat(expiresInSeconds, equalTo(-1L));
+ assertIsDuration(expiration, -1, TimeUnit.SECONDS);
+ }
+
+ private static void assertIsDuration(Duration duration, long expectedDuration, TimeUnit expectedUnit) {
+ assertThat(duration.getTimeUnit(), equalTo(expectedUnit));
+ assertThat(duration.getDuration(), equalTo(expectedDuration));
}
}