registrationExecutorFactory;
@@ -44,9 +49,9 @@ public class AsyncRegister implements AsynchronousProcess {
/**
* Performs the registration process for the given player.
*
- * @param variant the registration method
+ * @param variant the registration method
* @param parameters the parameters
- * @param parameters type
+ * @param
parameters type
*/
public
void register(RegistrationMethod
variant, P parameters) {
if (preRegisterCheck(variant, parameters.getPlayer())) {
@@ -57,6 +62,14 @@ public class AsyncRegister implements AsynchronousProcess {
}
}
+ /**
+ * Checks if the player is able to register, in that case the {@link AuthMeAsyncPreRegisterEvent} is invoked.
+ *
+ * @param variant the registration type variant.
+ * @param player the player which is trying to register.
+ *
+ * @return true if the checks are successful and the event hasn't marked the action as denied, false otherwise.
+ */
private boolean preRegisterCheck(RegistrationMethod> variant, Player player) {
final String name = player.getName().toLowerCase();
if (playerCache.isAuthenticated(name)) {
@@ -70,6 +83,12 @@ public class AsyncRegister implements AsynchronousProcess {
return false;
}
+ AuthMeAsyncPreRegisterEvent event = bukkitService.createAndCallEvent(
+ isAsync -> new AuthMeAsyncPreRegisterEvent(player, isAsync));
+ if (!event.canRegister()) {
+ return false;
+ }
+
return variant == RegistrationMethod.API_REGISTRATION || isPlayerIpAllowedToRegister(player);
}
@@ -77,11 +96,11 @@ public class AsyncRegister implements AsynchronousProcess {
* Executes the registration.
*
* @param parameters the registration parameters
- * @param executor the executor to perform the registration process with
- * @param
registration params type
+ * @param executor the executor to perform the registration process with
+ * @param
registration params type
*/
private
- void executeRegistration(P parameters, RegistrationExecutor
executor) {
+ void executeRegistration(P parameters, RegistrationExecutor
executor) {
PlayerAuth auth = executor.buildPlayerAuth(parameters);
if (database.saveAuth(auth)) {
executor.executePostPersistAction(parameters);
@@ -95,14 +114,14 @@ public class AsyncRegister implements AsynchronousProcess {
* Checks whether the registration threshold has been exceeded for the given player's IP address.
*
* @param player the player to check
+ *
* @return true if registration may take place, false otherwise (IP check failed)
*/
private boolean isPlayerIpAllowedToRegister(Player player) {
final int maxRegPerIp = service.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP);
final String ip = PlayerUtils.getPlayerIp(player);
if (maxRegPerIp > 0
- && !"127.0.0.1".equalsIgnoreCase(ip)
- && !"localhost".equalsIgnoreCase(ip)
+ && !InternetProtocolUtils.isLoopbackAddress(ip)
&& !service.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) {
List otherAccounts = database.getAllAuthsByIp(ip);
if (otherAccounts.size() >= maxRegPerIp) {
diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java
index 6ab4a874a..e740a0ded 100644
--- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java
@@ -2,8 +2,10 @@ package fr.xephi.authme.process.register;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboService;
+import fr.xephi.authme.events.RegisterEvent;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.SynchronousProcess;
+import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player;
@@ -15,6 +17,9 @@ import javax.inject.Inject;
*/
public class ProcessSyncEmailRegister implements SynchronousProcess {
+ @Inject
+ private BukkitService bukkitService;
+
@Inject
private CommonService service;
@@ -34,6 +39,7 @@ public class ProcessSyncEmailRegister implements SynchronousProcess {
limboService.replaceTasksAfterRegistration(player);
player.saveData();
+ bukkitService.callEvent(new RegisterEvent(player));
ConsoleLogger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player));
}
diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java
index 30e7f59ae..dc8aa136f 100644
--- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java
@@ -2,8 +2,10 @@ package fr.xephi.authme.process.register;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboService;
+import fr.xephi.authme.events.RegisterEvent;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.SynchronousProcess;
+import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.bungeecord.BungeeSender;
import fr.xephi.authme.settings.commandconfig.CommandManager;
@@ -31,6 +33,9 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
@Inject
private CommandManager commandManager;
+ @Inject
+ private BukkitService bukkitService;
+
ProcessSyncPasswordRegister() {
}
@@ -60,6 +65,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
}
player.saveData();
+ bukkitService.callEvent(new RegisterEvent(player));
ConsoleLogger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player));
// Kick Player after Registration is enabled, kick the player
diff --git a/src/main/java/fr/xephi/authme/security/HashUtils.java b/src/main/java/fr/xephi/authme/security/HashUtils.java
index 3578c80f3..642081c6d 100644
--- a/src/main/java/fr/xephi/authme/security/HashUtils.java
+++ b/src/main/java/fr/xephi/authme/security/HashUtils.java
@@ -1,6 +1,7 @@
package fr.xephi.authme.security;
import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -78,6 +79,20 @@ public final class HashUtils {
return hash.length() > 3 && hash.substring(0, 2).equals("$2");
}
+ /**
+ * Checks whether the two strings are equal to each other in a time-constant manner.
+ * This helps to avoid timing side channel attacks,
+ * cf. issue #1561.
+ *
+ * @param string1 first string
+ * @param string2 second string
+ * @return true if the strings are equal to each other, false otherwise
+ */
+ public static boolean isEqual(String string1, String string2) {
+ return MessageDigest.isEqual(
+ string1.getBytes(StandardCharsets.UTF_8), string2.getBytes(StandardCharsets.UTF_8));
+ }
+
/**
* Hash the message with the given algorithm and return the hash in its hexadecimal notation.
*
diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java
index 02e12d459..8b454c799 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java
@@ -8,7 +8,7 @@ import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.HooksSettings;
-import fr.xephi.authme.util.StringUtils;
+import fr.xephi.authme.util.ExceptionUtils;
import javax.inject.Inject;
@@ -39,7 +39,7 @@ public class BCrypt implements EncryptionMethod {
try {
return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash());
} catch (IllegalArgumentException e) {
- ConsoleLogger.warning("Bcrypt checkpw() returned " + StringUtils.formatException(e));
+ ConsoleLogger.warning("Bcrypt checkpw() returned " + ExceptionUtils.formatException(e));
}
return false;
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java
index cf4807abc..a22a68906 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java
@@ -3,6 +3,8 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
@Recommendation(Usage.RECOMMENDED)
public class BCrypt2y extends HexSaltedMethod {
@@ -23,7 +25,7 @@ public class BCrypt2y extends HexSaltedMethod {
// The salt is the first 29 characters of the hash
String salt = hash.substring(0, 29);
- return hash.equals(computeHash(password, salt, null));
+ return isEqual(hash, computeHash(password, salt, null));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java
index 762897955..c7bfcd65b 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java
@@ -2,12 +2,12 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.security.HashUtils;
-import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
-import fr.xephi.authme.util.StringUtils;
+import fr.xephi.authme.util.ExceptionUtils;
+import fr.xephi.authme.util.RandomStringUtils;
/**
@@ -37,7 +37,7 @@ public class Ipb4 implements EncryptionMethod {
try {
return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash());
} catch (IllegalArgumentException e) {
- ConsoleLogger.warning("Bcrypt checkpw() returned " + StringUtils.formatException(e));
+ ConsoleLogger.warning("Bcrypt checkpw() returned " + ExceptionUtils.formatException(e));
}
return false;
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Joomla.java b/src/main/java/fr/xephi/authme/security/crypts/Joomla.java
index 462f5cb28..2ecc1d8d3 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Joomla.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Joomla.java
@@ -4,6 +4,8 @@ import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
@Recommendation(Usage.ACCEPTABLE)
public class Joomla extends HexSaltedMethod {
@@ -16,7 +18,7 @@ public class Joomla extends HexSaltedMethod {
public boolean comparePassword(String password, HashedPassword hashedPassword, String unusedName) {
String hash = hashedPassword.getHash();
String[] hashParts = hash.split(":");
- return hashParts.length == 2 && hash.equals(computeHash(password, hashParts[1], null));
+ return hashParts.length == 2 && isEqual(hash, computeHash(password, hashParts[1], null));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java b/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java
index c244ec49d..00656964f 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.security.crypts;
+import static fr.xephi.authme.security.HashUtils.isEqual;
import static fr.xephi.authme.security.HashUtils.md5;
public class Md5vB extends HexSaltedMethod {
@@ -13,7 +14,7 @@ public class Md5vB extends HexSaltedMethod {
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
String hash = hashedPassword.getHash();
String[] line = hash.split("\\$");
- return line.length == 4 && hash.equals(computeHash(password, line[2], name));
+ return line.length == 4 && isEqual(hash, computeHash(password, line[2], name));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2.java b/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2.java
index 5367a2a12..d9695abc5 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.security.crypts;
+import com.google.common.primitives.Ints;
import de.rtner.misc.BinTools;
import de.rtner.security.auth.spi.PBKDF2Engine;
import de.rtner.security.auth.spi.PBKDF2Parameters;
@@ -38,13 +39,12 @@ public class Pbkdf2 extends HexSaltedMethod {
if (line.length != 4) {
return false;
}
- int iterations;
- try {
- iterations = Integer.parseInt(line[1]);
- } catch (NumberFormatException e) {
- ConsoleLogger.logException("Cannot read number of rounds for Pbkdf2", e);
+ Integer iterations = Ints.tryParse(line[1]);
+ if (iterations == null) {
+ ConsoleLogger.warning("Cannot read number of rounds for Pbkdf2: '" + line[1] + "'");
return false;
}
+
String salt = line[2];
byte[] derivedKey = BinTools.hex2bin(line[3]);
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", salt.getBytes(), iterations, derivedKey);
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2Django.java b/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2Django.java
index f5a0abb63..e32930db1 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2Django.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2Django.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.security.crypts;
+import com.google.common.primitives.Ints;
import de.rtner.security.auth.spi.PBKDF2Engine;
import de.rtner.security.auth.spi.PBKDF2Parameters;
import fr.xephi.authme.ConsoleLogger;
@@ -27,13 +28,12 @@ public class Pbkdf2Django extends HexSaltedMethod {
if (line.length != 4) {
return false;
}
- int iterations;
- try {
- iterations = Integer.parseInt(line[1]);
- } catch (NumberFormatException e) {
- ConsoleLogger.logException("Could not read number of rounds for Pbkdf2Django:", e);
+ Integer iterations = Ints.tryParse(line[1]);
+ if (iterations == null) {
+ ConsoleLogger.warning("Cannot read number of rounds for Pbkdf2Django: '" + line[1] + "'");
return false;
}
+
String salt = line[2];
byte[] derivedKey = Base64.getDecoder().decode(line[3]);
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), iterations, derivedKey);
diff --git a/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java
index 70ac322d0..2d641706c 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java
@@ -10,6 +10,8 @@ import fr.xephi.authme.security.crypts.description.Usage;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
/**
* Encryption method compatible with phpBB3.
*
@@ -43,7 +45,7 @@ public class PhpBB implements EncryptionMethod {
} else if (hash.length() == 34) {
return PhpassSaltedMd5.phpbb_check_hash(password, hash);
} else {
- return PhpassSaltedMd5.md5(password).equals(hash);
+ return isEqual(hash, PhpassSaltedMd5.md5(password));
}
}
@@ -153,7 +155,7 @@ public class PhpBB implements EncryptionMethod {
}
private static boolean phpbb_check_hash(String password, String hash) {
- return _hash_crypt_private(password, hash).equals(hash);
+ return isEqual(hash, _hash_crypt_private(password, hash)); // #1561: fix timing issue
}
}
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
index d0dacda4d..c0ec13dd7 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
@@ -1,5 +1,7 @@
package fr.xephi.authme.security.crypts;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
/**
* Common supertype for encryption methods which store their salt separately from the hash.
*/
@@ -19,7 +21,7 @@ public abstract class SeparateSaltMethod implements EncryptionMethod {
@Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
- return hashedPassword.getHash().equals(computeHash(password, hashedPassword.getSalt(), null));
+ return isEqual(hashedPassword.getHash(), computeHash(password, hashedPassword.getSalt(), null));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Sha256.java b/src/main/java/fr/xephi/authme/security/crypts/Sha256.java
index 1b77a2e44..ce6b25492 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Sha256.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Sha256.java
@@ -3,6 +3,7 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
+import static fr.xephi.authme.security.HashUtils.isEqual;
import static fr.xephi.authme.security.HashUtils.sha256;
@Recommendation(Usage.RECOMMENDED)
@@ -14,10 +15,10 @@ public class Sha256 extends HexSaltedMethod {
}
@Override
- public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
+ public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
String hash = hashedPassword.getHash();
String[] line = hash.split("\\$");
- return line.length == 4 && hash.equals(computeHash(password, line[2], ""));
+ return line.length == 4 && isEqual(hash, computeHash(password, line[2], name));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Smf.java b/src/main/java/fr/xephi/authme/security/crypts/Smf.java
index 24d28fe6c..e24c1b83d 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Smf.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Smf.java
@@ -7,6 +7,8 @@ import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
import fr.xephi.authme.util.RandomStringUtils;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
/**
* Hashing algorithm for SMF forums.
*
@@ -32,7 +34,7 @@ public class Smf implements EncryptionMethod {
@Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
- return computeHash(password, null, name).equals(hashedPassword.getHash());
+ return isEqual(hashedPassword.getHash(), computeHash(password, null, name));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java
index a8f2040e5..33815ec77 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java
@@ -5,6 +5,8 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
/**
* Common type for encryption methods which do not use any salt whatsoever.
*/
@@ -26,7 +28,7 @@ public abstract class UnsaltedMethod implements EncryptionMethod {
@Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
- return hashedPassword.getHash().equals(computeHash(password));
+ return isEqual(hashedPassword.getHash(), computeHash(password));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
index 23101e22a..f5930fcf5 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
@@ -5,6 +5,8 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
/**
* Common supertype of encryption methods that use a player's username
* (or something based on it) as embedded salt.
@@ -23,7 +25,7 @@ public abstract class UsernameSaltMethod implements EncryptionMethod {
@Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
- return hashedPassword.getHash().equals(computeHash(password, name).getHash());
+ return isEqual(hashedPassword.getHash(), computeHash(password, name).getHash());
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java
index d1d4953d1..f396c5d84 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java
@@ -3,6 +3,7 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
+import static fr.xephi.authme.security.HashUtils.isEqual;
import static fr.xephi.authme.security.crypts.BCryptService.hashpw;
@Recommendation(Usage.RECOMMENDED)
@@ -14,12 +15,12 @@ public class Wbb4 extends HexSaltedMethod {
}
@Override
- public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
+ public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
if (hashedPassword.getHash().length() != 60) {
return false;
}
String salt = hashedPassword.getHash().substring(0, 29);
- return computeHash(password, salt, null).equals(hashedPassword.getHash());
+ return isEqual(hashedPassword.getHash(), computeHash(password, salt, name));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java b/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java
index 768b92c5d..f70c09496 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java
@@ -12,6 +12,8 @@ import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
@Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 9)
// Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally
@@ -115,7 +117,7 @@ public class Wordpress extends UnsaltedMethod {
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
String hash = hashedPassword.getHash();
String comparedHash = crypt(password, hash);
- return comparedHash.equals(hash);
+ return isEqual(hash, comparedHash);
}
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAuth.java b/src/main/java/fr/xephi/authme/security/crypts/XAuth.java
index 9f921b6ae..62f2e0d71 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/XAuth.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/XAuth.java
@@ -3,6 +3,8 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
+import static fr.xephi.authme.security.HashUtils.isEqual;
+
@Recommendation(Usage.RECOMMENDED)
public class XAuth extends HexSaltedMethod {
@@ -23,14 +25,14 @@ public class XAuth extends HexSaltedMethod {
}
@Override
- public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
+ public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
String hash = hashedPassword.getHash();
int saltPos = password.length() >= hash.length() ? hash.length() - 1 : password.length();
if (saltPos + 12 > hash.length()) {
return false;
}
String salt = hash.substring(saltPos, saltPos + 12);
- return hash.equals(computeHash(password, salt, null));
+ return isEqual(hash, computeHash(password, salt, name));
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java
index 3ef4e4301..846807e6c 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java
@@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.security.HashUtils;
-import fr.xephi.authme.util.StringUtils;
+import fr.xephi.authme.util.ExceptionUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -32,7 +32,7 @@ public class XfBCrypt implements EncryptionMethod {
try {
return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash());
} catch (IllegalArgumentException e) {
- ConsoleLogger.warning("XfBCrypt checkpw() returned " + StringUtils.formatException(e));
+ ConsoleLogger.warning("XfBCrypt checkpw() returned " + ExceptionUtils.formatException(e));
}
return false;
}
diff --git a/src/main/java/fr/xephi/authme/security/totp/GenerateTotpService.java b/src/main/java/fr/xephi/authme/security/totp/GenerateTotpService.java
new file mode 100644
index 000000000..14a6a6bbb
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/totp/GenerateTotpService.java
@@ -0,0 +1,69 @@
+package fr.xephi.authme.security.totp;
+
+import fr.xephi.authme.initialization.HasCleanup;
+import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult;
+import fr.xephi.authme.util.expiring.ExpiringMap;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Handles the generation of new TOTP secrets for players.
+ */
+public class GenerateTotpService implements HasCleanup {
+
+ private static final int NEW_TOTP_KEY_EXPIRATION_MINUTES = 5;
+
+ private final ExpiringMap totpKeys;
+
+ @Inject
+ private TotpAuthenticator totpAuthenticator;
+
+ GenerateTotpService() {
+ this.totpKeys = new ExpiringMap<>(NEW_TOTP_KEY_EXPIRATION_MINUTES, TimeUnit.MINUTES);
+ }
+
+ /**
+ * Generates a new TOTP key and returns the corresponding QR code.
+ *
+ * @param player the player to save the TOTP key for
+ * @return TOTP generation result
+ */
+ public TotpGenerationResult generateTotpKey(Player player) {
+ TotpGenerationResult credentials = totpAuthenticator.generateTotpKey(player);
+ totpKeys.put(player.getName().toLowerCase(), credentials);
+ return credentials;
+ }
+
+ /**
+ * Returns the generated TOTP secret of a player, if available and not yet expired.
+ *
+ * @param player the player to retrieve the TOTP key for
+ * @return TOTP generation result
+ */
+ public TotpGenerationResult getGeneratedTotpKey(Player player) {
+ return totpKeys.get(player.getName().toLowerCase());
+ }
+
+ public void removeGenerateTotpKey(Player player) {
+ totpKeys.remove(player.getName().toLowerCase());
+ }
+
+ /**
+ * Returns whether the given totp code is correct for the generated TOTP key of the player.
+ *
+ * @param player the player to verify the code for
+ * @param totpCode the totp code to verify with the generated secret
+ * @return true if the input code is correct, false if the code is invalid or no unexpired totp key is available
+ */
+ public boolean isTotpCodeCorrectForGeneratedTotpKey(Player player, String totpCode) {
+ TotpGenerationResult totpDetails = totpKeys.get(player.getName().toLowerCase());
+ return totpDetails != null && totpAuthenticator.checkCode(totpDetails.getTotpKey(), totpCode);
+ }
+
+ @Override
+ public void performCleanup() {
+ totpKeys.removeExpiredEntries();
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/security/totp/TotpAuthenticator.java b/src/main/java/fr/xephi/authme/security/totp/TotpAuthenticator.java
new file mode 100644
index 000000000..4905a521e
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/totp/TotpAuthenticator.java
@@ -0,0 +1,75 @@
+package fr.xephi.authme.security.totp;
+
+import com.google.common.primitives.Ints;
+import com.warrenstrange.googleauth.GoogleAuthenticator;
+import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
+import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
+import com.warrenstrange.googleauth.IGoogleAuthenticator;
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.service.BukkitService;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+
+/**
+ * Provides TOTP functions (wrapping a third-party TOTP implementation).
+ */
+public class TotpAuthenticator {
+
+ private final IGoogleAuthenticator authenticator;
+ private final BukkitService bukkitService;
+
+ @Inject
+ TotpAuthenticator(BukkitService bukkitService) {
+ this.authenticator = createGoogleAuthenticator();
+ this.bukkitService = bukkitService;
+ }
+
+ /**
+ * @return new Google Authenticator instance
+ */
+ protected IGoogleAuthenticator createGoogleAuthenticator() {
+ return new GoogleAuthenticator();
+ }
+
+ public boolean checkCode(PlayerAuth auth, String totpCode) {
+ return checkCode(auth.getTotpKey(), totpCode);
+ }
+
+ /**
+ * Returns whether the given input code matches for the provided TOTP key.
+ *
+ * @param totpKey the key to check with
+ * @param inputCode the input code to verify
+ * @return true if code is valid, false otherwise
+ */
+ public boolean checkCode(String totpKey, String inputCode) {
+ Integer totpCode = Ints.tryParse(inputCode);
+ return totpCode != null && authenticator.authorize(totpKey, totpCode);
+ }
+
+ public TotpGenerationResult generateTotpKey(Player player) {
+ GoogleAuthenticatorKey credentials = authenticator.createCredentials();
+ String qrCodeUrl = GoogleAuthenticatorQRGenerator.getOtpAuthURL(
+ bukkitService.getIp(), player.getName(), credentials);
+ return new TotpGenerationResult(credentials.getKey(), qrCodeUrl);
+ }
+
+ public static final class TotpGenerationResult {
+ private final String totpKey;
+ private final String authenticatorQrCodeUrl;
+
+ public TotpGenerationResult(String totpKey, String authenticatorQrCodeUrl) {
+ this.totpKey = totpKey;
+ this.authenticatorQrCodeUrl = authenticatorQrCodeUrl;
+ }
+
+ public String getTotpKey() {
+ return totpKey;
+ }
+
+ public String getAuthenticatorQrCodeUrl() {
+ return authenticatorQrCodeUrl;
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/service/BukkitService.java b/src/main/java/fr/xephi/authme/service/BukkitService.java
index 09b4869fb..75fae29c0 100644
--- a/src/main/java/fr/xephi/authme/service/BukkitService.java
+++ b/src/main/java/fr/xephi/authme/service/BukkitService.java
@@ -393,4 +393,11 @@ public class BukkitService implements SettingsDependent {
return Optional.empty();
}
}
+
+ /**
+ * @return the IP string that this server is bound to, otherwise empty string
+ */
+ public String getIp() {
+ return Bukkit.getServer().getIp();
+ }
}
diff --git a/src/main/java/fr/xephi/authme/service/GeoIpService.java b/src/main/java/fr/xephi/authme/service/GeoIpService.java
index b973b6337..be2746878 100644
--- a/src/main/java/fr/xephi/authme/service/GeoIpService.java
+++ b/src/main/java/fr/xephi/authme/service/GeoIpService.java
@@ -21,8 +21,9 @@ import fr.xephi.authme.util.InternetProtocolUtils;
import java.io.BufferedInputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.OutputStream;
+import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
@@ -33,6 +34,9 @@ import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.time.Duration;
import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.Optional;
import java.util.zip.GZIPInputStream;
@@ -55,6 +59,10 @@ public class GeoIpService {
private static final int UPDATE_INTERVAL_DAYS = 30;
+ // The server for MaxMind doesn't seem to understand RFC1123,
+ // but every HTTP implementation have to support RFC 1023
+ private static final String TIME_RFC_1023 = "EEE, dd-MMM-yy HH:mm:ss zzz";
+
private final Path dataFile;
private final BukkitService bukkitService;
@@ -98,13 +106,12 @@ public class GeoIpService {
try {
FileTime lastModifiedTime = Files.getLastModifiedTime(dataFile);
if (Duration.between(lastModifiedTime.toInstant(), Instant.now()).toDays() <= UPDATE_INTERVAL_DAYS) {
- databaseReader = new Reader(dataFile.toFile(), FileMode.MEMORY, new CHMCache());
- ConsoleLogger.info(LICENSE);
+ startReading();
// don't fire the update task - we are up to date
return true;
} else {
- ConsoleLogger.debug("GEO Ip database is older than " + UPDATE_INTERVAL_DAYS + " Days");
+ ConsoleLogger.debug("GEO IP database is older than " + UPDATE_INTERVAL_DAYS + " Days");
}
} catch (IOException ioEx) {
ConsoleLogger.logException("Failed to load GeoLiteAPI database", ioEx);
@@ -112,54 +119,101 @@ public class GeoIpService {
}
}
+ //set the downloading flag in order to fix race conditions outside
+ downloading = true;
+
// File is outdated or doesn't exist - let's try to download the data file!
- startDownloadTask();
+ // use bukkit's cached threads
+ bukkitService.runTaskAsynchronously(this::updateDatabase);
return false;
}
/**
- * Create a thread which will attempt to download new data from the GeoLite website.
+ * Tries to update the database by downloading a new version from the website.
*/
- private void startDownloadTask() {
- downloading = true;
+ private void updateDatabase() {
+ ConsoleLogger.info("Downloading GEO IP database, because the old database is older than "
+ + UPDATE_INTERVAL_DAYS + " days or doesn't exist");
- // use bukkit's cached threads
- bukkitService.runTaskAsynchronously(() -> {
- ConsoleLogger.info("Downloading GEO IP database, because the old database is outdated or doesn't exist");
-
- Path tempFile = null;
- try {
- // download database to temporarily location
- tempFile = Files.createTempFile(ARCHIVE_FILE, null);
- try (OutputStream out = Files.newOutputStream(tempFile)) {
- Resources.copy(new URL(ARCHIVE_URL), out);
- }
-
- // MD5 checksum verification
- String targetChecksum = Resources.toString(new URL(CHECKSUM_URL), StandardCharsets.UTF_8);
- if (!verifyChecksum(Hashing.md5(), tempFile, targetChecksum)) {
- return;
- }
-
- // tar extract database and copy to target destination
- if (!extractDatabase(tempFile, dataFile)) {
- ConsoleLogger.warning("Cannot find database inside downloaded GEO IP file at " + tempFile);
- return;
- }
-
- ConsoleLogger.info("Successfully downloaded new GEO IP database to " + dataFile);
-
- //only set this value to false on success otherwise errors could lead to endless download triggers
- downloading = false;
- } catch (IOException ioEx) {
- ConsoleLogger.logException("Could not download GeoLiteAPI database", ioEx);
- } finally {
- // clean up
- if (tempFile != null) {
- FileUtils.delete(tempFile.toFile());
- }
+ Path tempFile = null;
+ try {
+ // download database to temporarily location
+ tempFile = Files.createTempFile(ARCHIVE_FILE, null);
+ if (!downloadDatabaseArchive(tempFile)) {
+ ConsoleLogger.info("There is no newer GEO IP database uploaded to MaxMind. Using the old one for now.");
+ startReading();
+ return;
}
- });
+
+ // MD5 checksum verification
+ String expectedChecksum = Resources.toString(new URL(CHECKSUM_URL), StandardCharsets.UTF_8);
+ verifyChecksum(Hashing.md5(), tempFile, expectedChecksum);
+
+ // tar extract database and copy to target destination
+ extractDatabase(tempFile, dataFile);
+
+ //only set this value to false on success otherwise errors could lead to endless download triggers
+ ConsoleLogger.info("Successfully downloaded new GEO IP database to " + dataFile);
+ startReading();
+ } catch (IOException ioEx) {
+ ConsoleLogger.logException("Could not download GeoLiteAPI database", ioEx);
+ } finally {
+ // clean up
+ if (tempFile != null) {
+ FileUtils.delete(tempFile.toFile());
+ }
+ }
+ }
+
+ private void startReading() throws IOException {
+ databaseReader = new Reader(dataFile.toFile(), FileMode.MEMORY, new CHMCache());
+ ConsoleLogger.info(LICENSE);
+
+ // clear downloading flag, because we now have working reader instance
+ downloading = false;
+ }
+
+ /**
+ * Downloads the archive to the destination file if it's newer than the locally version.
+ *
+ * @param lastModified modification timestamp of the already present file
+ * @param destination save file
+ * @return false if we already have the newest version, true if successful
+ * @throws IOException if failed during downloading and writing to destination file
+ */
+ private boolean downloadDatabaseArchive(Instant lastModified, Path destination) throws IOException {
+ HttpURLConnection connection = (HttpURLConnection) new URL(ARCHIVE_URL).openConnection();
+ if (lastModified != null) {
+ // Only download if we actually need a newer version - this field is specified in GMT zone
+ ZonedDateTime zonedTime = lastModified.atZone(ZoneId.of("GMT"));
+ String timeFormat = DateTimeFormatter.ofPattern(TIME_RFC_1023).format(zonedTime);
+ connection.addRequestProperty("If-Modified-Since", timeFormat);
+ }
+
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
+ //we already have the newest version
+ connection.getInputStream().close();
+ return false;
+ }
+
+ Files.copy(connection.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
+ return true;
+ }
+
+ /**
+ * Downloads the archive to the destination file if it's newer than the locally version.
+ *
+ * @param destination save file
+ * @return false if we already have the newest version, true if successful
+ * @throws IOException if failed during downloading and writing to destination file
+ */
+ private boolean downloadDatabaseArchive(Path destination) throws IOException {
+ Instant lastModified = null;
+ if (Files.exists(dataFile)) {
+ lastModified = Files.getLastModifiedTime(dataFile).toInstant();
+ }
+
+ return downloadDatabaseArchive(lastModified, destination);
}
/**
@@ -168,19 +222,15 @@ public class GeoIpService {
* @param function the checksum function like MD5, SHA256 used to generate the checksum from the file
* @param file the file we want to calculate the checksum from
* @param expectedChecksum the expected checksum
- * @return true if equal, false otherwise
- * @throws IOException on I/O error reading the file
+ * @throws IOException on I/O error reading the file or the checksum verification failed
*/
- private boolean verifyChecksum(HashFunction function, Path file, String expectedChecksum) throws IOException {
+ private void verifyChecksum(HashFunction function, Path file, String expectedChecksum) throws IOException {
HashCode actualHash = function.hashBytes(Files.readAllBytes(file));
HashCode expectedHash = HashCode.fromString(expectedChecksum);
- if (Objects.equals(actualHash, expectedHash)) {
- return true;
+ if (!Objects.equals(actualHash, expectedHash)) {
+ throw new IOException("GEO IP Checksum verification failed. " +
+ "Expected: " + expectedChecksum + "Actual:" + actualHash);
}
-
- ConsoleLogger.warning("GEO IP checksum verification failed");
- ConsoleLogger.warning("Expected: " + expectedHash + " Actual: " + actualHash);
- return false;
}
/**
@@ -188,38 +238,37 @@ public class GeoIpService {
*
* @param tarInputFile gzipped tar input file where the database is
* @param outputFile destination file for the database
- * @return true if the database was found, false otherwise
- * @throws IOException on I/O error reading the tar archive or writing the output
+ * @throws IOException on I/O error reading the tar archive, or writing the output
+ * @throws FileNotFoundException if the database cannot be found inside the archive
*/
- private boolean extractDatabase(Path tarInputFile, Path outputFile) throws IOException {
+ private void extractDatabase(Path tarInputFile, Path outputFile) throws FileNotFoundException, IOException {
// .gz -> gzipped file
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(tarInputFile));
TarInputStream tarIn = new TarInputStream(new GZIPInputStream(in))) {
- TarEntry entry;
- while ((entry = tarIn.getNextEntry()) != null) {
- if (!entry.isDirectory()) {
- // filename including folders (absolute path inside the archive)
- String filename = entry.getName();
- if (filename.endsWith(DATABASE_EXT)) {
- // found the database file
- Files.copy(tarIn, outputFile, StandardCopyOption.REPLACE_EXISTING);
-
- // update the last modification date to be same as in the archive
- Files.setLastModifiedTime(outputFile, FileTime.from(entry.getModTime().toInstant()));
- return true;
- }
+ for (TarEntry entry = tarIn.getNextEntry(); entry != null; entry = tarIn.getNextEntry()) {
+ // filename including folders (absolute path inside the archive)
+ String filename = entry.getName();
+ if (entry.isDirectory() || !filename.endsWith(DATABASE_EXT)) {
+ continue;
}
+
+ // found the database file and copy file
+ Files.copy(tarIn, outputFile, StandardCopyOption.REPLACE_EXISTING);
+
+ // update the last modification date to be same as in the archive
+ Files.setLastModifiedTime(outputFile, FileTime.from(entry.getModTime().toInstant()));
+ return;
}
}
- return false;
+ throw new FileNotFoundException("Cannot find database inside downloaded GEO IP file at " + tarInputFile);
}
/**
* Get the country code of the given IP address.
*
* @param ip textual IP address to lookup.
- * @return two-character ISO 3166-1 alpha code for the country.
+ * @return two-character ISO 3166-1 alpha code for the country or "--" if it cannot be fetched.
*/
public String getCountryCode(String ip) {
return getCountry(ip).map(Country::getIsoCode).orElse("--");
@@ -229,7 +278,7 @@ public class GeoIpService {
* Get the country name of the given IP address.
*
* @param ip textual IP address to lookup.
- * @return The name of the country.
+ * @return The name of the country or "N/A" if it cannot be fetched.
*/
public String getCountryName(String ip) {
return getCountry(ip).map(Country::getName).orElse("N/A");
@@ -255,7 +304,7 @@ public class GeoIpService {
try {
InetAddress address = InetAddress.getByName(ip);
- //Reader.getCountry() can be null for unknown addresses
+ // Reader.getCountry() can be null for unknown addresses
return Optional.ofNullable(databaseReader.getCountry(address)).map(CountryResponse::getCountry);
} catch (UnknownHostException e) {
// Ignore invalid ip addresses
diff --git a/src/main/java/fr/xephi/authme/service/HelpTranslationGenerator.java b/src/main/java/fr/xephi/authme/service/HelpTranslationGenerator.java
index 6ecd05490..21407b4f0 100644
--- a/src/main/java/fr/xephi/authme/service/HelpTranslationGenerator.java
+++ b/src/main/java/fr/xephi/authme/service/HelpTranslationGenerator.java
@@ -44,15 +44,17 @@ public class HelpTranslationGenerator {
/**
* Updates the help file to contain entries for all commands.
*
+ * @return the help file that has been updated
* @throws IOException if the help file cannot be written to
*/
- public void updateHelpFile() throws IOException {
+ public File updateHelpFile() throws IOException {
String languageCode = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
File helpFile = new File(dataFolder, "messages/help_" + languageCode + ".yml");
Map helpEntries = generateHelpMessageEntries();
String helpEntriesYaml = exportToYaml(helpEntries);
Files.write(helpFile.toPath(), helpEntriesYaml.getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
+ return helpFile;
}
private static String exportToYaml(Map helpEntries) {
diff --git a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java
index f24b45f35..200aa11c7 100644
--- a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java
+++ b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java
@@ -121,6 +121,16 @@ public class PasswordRecoveryService implements Reloadable, HasCleanup {
commonService.send(player, MessageKey.RECOVERY_CHANGE_PASSWORD);
}
+ /**
+ * Removes a player from the list of successful recovers so that he can
+ * no longer use the /email setpassword command.
+ *
+ * @param player The player to remove.
+ */
+ public void removeFromSuccessfulRecovery(Player player) {
+ successfulRecovers.remove(player.getName());
+ }
+
/**
* Check if a player is able to have emails sent.
*
@@ -149,12 +159,7 @@ public class PasswordRecoveryService implements Reloadable, HasCleanup {
String playerAddress = PlayerUtils.getPlayerIp(player);
String storedAddress = successfulRecovers.get(name);
- if (storedAddress == null || !playerAddress.equals(storedAddress)) {
- messages.send(player, MessageKey.CHANGE_PASSWORD_EXPIRED);
- return false;
- }
-
- return true;
+ return storedAddress != null && playerAddress.equals(storedAddress);
}
@Override
diff --git a/src/main/java/fr/xephi/authme/service/TeleportationService.java b/src/main/java/fr/xephi/authme/service/TeleportationService.java
index 10f9e1178..1588c4404 100644
--- a/src/main/java/fr/xephi/authme/service/TeleportationService.java
+++ b/src/main/java/fr/xephi/authme/service/TeleportationService.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.service;
+import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.limbo.LimboPlayer;
@@ -63,12 +64,13 @@ public class TeleportationService implements Reloadable {
public void teleportOnJoin(final Player player) {
if (!settings.getProperty(RestrictionSettings.NO_TELEPORT)
&& settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) {
+ ConsoleLogger.debug("Teleport on join for player `{0}`", player.getName());
teleportToSpawn(player, playerCache.isAuthenticated(player.getName()));
}
}
/**
- * Returns the player's custom on join location
+ * Returns the player's custom on join location.
*
* @param player the player to process
*
@@ -82,10 +84,11 @@ public class TeleportationService implements Reloadable {
SpawnTeleportEvent event = new SpawnTeleportEvent(player, location,
playerCache.isAuthenticated(player.getName()));
bukkitService.callEvent(event);
- if(!isEventValid(event)) {
+ if (!isEventValid(event)) {
return null;
}
+ ConsoleLogger.debug("Returning custom location for >1.9 join event for player `{0}`", player.getName());
return location;
}
return null;
@@ -107,6 +110,7 @@ public class TeleportationService implements Reloadable {
}
if (!player.hasPlayedBefore() || !dataSource.isAuthAvailable(player.getName())) {
+ ConsoleLogger.debug("Attempting to teleport player `{0}` to first spawn", player.getName());
performTeleportation(player, new FirstSpawnTeleportEvent(player, firstSpawn));
}
}
@@ -130,12 +134,15 @@ public class TeleportationService implements Reloadable {
// The world in LimboPlayer is from where the player comes, before any teleportation by AuthMe
if (mustForceSpawnAfterLogin(worldName)) {
+ ConsoleLogger.debug("Teleporting `{0}` to spawn because of 'force-spawn after login'", player.getName());
teleportToSpawn(player, true);
} else if (settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) {
if (settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION) && auth.getQuitLocY() != 0) {
Location location = buildLocationFromAuth(player, auth);
+ ConsoleLogger.debug("Teleporting `{0}` after login, based on the player auth", player.getName());
teleportBackFromSpawn(player, location);
} else if (limbo != null && limbo.getLocation() != null) {
+ ConsoleLogger.debug("Teleporting `{0}` after login, based on the limbo player", player.getName());
teleportBackFromSpawn(player, limbo.getLocation());
}
}
diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java
index c5ab3fd7a..d95f73ce5 100644
--- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java
+++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java
@@ -10,6 +10,7 @@ import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.security.HashAlgorithm;
+import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
@@ -74,6 +75,7 @@ public class SettingsMigrationService extends PlainMigrationService {
| convertToRegistrationType(resource)
| mergeAndMovePermissionGroupSettings(resource)
| moveDeprecatedHashAlgorithmIntoLegacySection(resource)
+ | moveSaltColumnConfigWithOtherColumnConfigs(resource)
|| hasDeprecatedProperties(resource);
}
@@ -313,6 +315,18 @@ public class SettingsMigrationService extends PlainMigrationService {
return false;
}
+ /**
+ * Moves the property for the password salt column name to the same path as all other column name properties.
+ *
+ * @param resource The property resource
+ * @return True if the configuration has changed, false otherwise
+ */
+ private static boolean moveSaltColumnConfigWithOtherColumnConfigs(PropertyResource resource) {
+ Property oldProperty = newProperty("ExternalBoardOptions.mySQLColumnSalt",
+ DatabaseSettings.MYSQL_COL_SALT.getDefaultValue());
+ return moveProperty(oldProperty, DatabaseSettings.MYSQL_COL_SALT, resource);
+ }
+
/**
* Retrieves the old config to run a command when alt accounts are detected and sets them to this instance
* for further processing.
diff --git a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
index ea235b3cc..d2f2edbf7 100644
--- a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
+++ b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
@@ -198,9 +198,11 @@ public class SpawnLoader implements Reloadable {
// ignore
}
if (spawnLoc != null) {
+ ConsoleLogger.debug("Spawn location determined as `{0}` for world `{1}`", spawnLoc, world.getName());
return spawnLoc;
}
}
+ ConsoleLogger.debug("Fall back to default world spawn location. World: `{0}`", world.getName());
return world.getSpawnLocation(); // return default location
}
diff --git a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
index 66ddd3cd5..0818c2693 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
@@ -65,7 +65,7 @@ public final class DatabaseSettings implements SettingsHolder {
@Comment("Column for storing players passwords salts")
public static final Property MYSQL_COL_SALT =
- newProperty("ExternalBoardOptions.mySQLColumnSalt", "");
+ newProperty("DataSource.mySQLColumnSalt", "");
@Comment("Column for storing players emails")
public static final Property MYSQL_COL_EMAIL =
@@ -79,6 +79,10 @@ public final class DatabaseSettings implements SettingsHolder {
public static final Property MYSQL_COL_HASSESSION =
newProperty("DataSource.mySQLColumnHasSession", "hasSession");
+ @Comment("Column for storing a player's TOTP key (for two-factor authentication)")
+ public static final Property MYSQL_COL_TOTP_KEY =
+ newProperty("DataSource.mySQLtotpKey", "totp");
+
@Comment("Column for storing the player's last IP")
public static final Property MYSQL_COL_LAST_IP =
newProperty("DataSource.mySQLColumnIp", "ip");
diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
index 492af4e6d..e769dd9b0 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
@@ -26,7 +26,7 @@ public final class RestrictionSettings implements SettingsHolder {
@Comment("Allowed commands for unauthenticated players")
public static final Property> ALLOW_COMMANDS =
newLowercaseListProperty("settings.restrictions.allowCommands",
- "/login", "/register", "/l", "/reg", "/email", "/captcha");
+ "/login", "/register", "/l", "/reg", "/email", "/captcha", "/2fa", "/totp");
@Comment({
"Max number of allowed registrations per IP",
diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java b/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
index a3e42f756..36c951ffc 100644
--- a/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
+++ b/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
@@ -49,7 +49,7 @@ public class PurgeExecutor {
* players and names.
*
* @param players the players to purge
- * @param names names to purge
+ * @param names names to purge
*/
public void executePurge(Collection players, Collection names) {
// Purge other data
@@ -212,15 +212,13 @@ public class PurgeExecutor {
}
for (OfflinePlayer offlinePlayer : cleared) {
- try {
- permissionsManager.loadUserData(offlinePlayer.getUniqueId());
- } catch (NoSuchMethodError e) {
- permissionsManager.loadUserData(offlinePlayer.getName());
+ if (!permissionsManager.loadUserData(offlinePlayer)) {
+ ConsoleLogger.warning("Unable to purge the permissions of user " + offlinePlayer + "!");
+ continue;
}
permissionsManager.removeAllGroups(offlinePlayer);
}
ConsoleLogger.info("AutoPurge: Removed permissions from " + cleared.size() + " player(s).");
}
-
}
diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeTask.java b/src/main/java/fr/xephi/authme/task/purge/PurgeTask.java
index 27b424150..686bab86d 100644
--- a/src/main/java/fr/xephi/authme/task/purge/PurgeTask.java
+++ b/src/main/java/fr/xephi/authme/task/purge/PurgeTask.java
@@ -3,6 +3,7 @@ package fr.xephi.authme.task.purge;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PlayerStatePermission;
+import fr.xephi.authme.permission.handlers.PermissionLoadUserException;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
@@ -73,10 +74,9 @@ class PurgeTask extends BukkitRunnable {
OfflinePlayer offlinePlayer = offlinePlayers[nextPosition];
if (offlinePlayer.getName() != null && toPurge.remove(offlinePlayer.getName().toLowerCase())) {
- try {
- permissionsManager.loadUserData(offlinePlayer.getUniqueId());
- } catch (NoSuchMethodError e) {
- permissionsManager.loadUserData(offlinePlayer.getName());
+ if(!permissionsManager.loadUserData(offlinePlayer)) {
+ ConsoleLogger.warning("Unable to check if the user " + offlinePlayer.getName() + " can be purged!");
+ continue;
}
if (!permissionsManager.hasPermissionOffline(offlinePlayer, PlayerStatePermission.BYPASS_PURGE)) {
playerPortion.add(offlinePlayer);
diff --git a/src/main/java/fr/xephi/authme/util/ExceptionUtils.java b/src/main/java/fr/xephi/authme/util/ExceptionUtils.java
index 6a5adde69..fd5ae8852 100644
--- a/src/main/java/fr/xephi/authme/util/ExceptionUtils.java
+++ b/src/main/java/fr/xephi/authme/util/ExceptionUtils.java
@@ -33,4 +33,14 @@ public final class ExceptionUtils {
}
return null;
}
+
+ /**
+ * Format the information from a Throwable as string, retaining the type and its message.
+ *
+ * @param th the throwable to process
+ * @return string with the type of the Throwable and its message, e.g. "[IOException]: Could not open stream"
+ */
+ public static String formatException(Throwable th) {
+ return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage();
+ }
}
diff --git a/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java b/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java
index 548e1e913..039421548 100644
--- a/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java
+++ b/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java
@@ -1,16 +1,13 @@
package fr.xephi.authme.util;
-import java.util.regex.Pattern;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
/**
* Utility class about the InternetProtocol
*/
public final class InternetProtocolUtils {
- private static final Pattern LOCAL_ADDRESS_PATTERN =
- Pattern.compile("(^127\\.)|(^(0)?10\\.)|(^172\\.(0)?1[6-9]\\.)|(^172\\.(0)?2[0-9]\\.)"
- + "|(^172\\.(0)?3[0-1]\\.)|(^169\\.254\\.)|(^192\\.168\\.)");
-
// Utility class
private InternetProtocolUtils() {
}
@@ -19,10 +16,57 @@ public final class InternetProtocolUtils {
* Checks if the specified address is a private or loopback address
*
* @param address address to check
- *
- * @return true if the address is a local or loopback address, false otherwise
+ * @return true if the address is a local (site and link) or loopback address, false otherwise
*/
public static boolean isLocalAddress(String address) {
- return LOCAL_ADDRESS_PATTERN.matcher(address).find();
+ try {
+ InetAddress inetAddress = InetAddress.getByName(address);
+
+ // Examples: 127.0.0.1, localhost or [::1]
+ return isLoopbackAddress(address)
+ // Example: 10.0.0.0, 172.16.0.0, 192.168.0.0, fec0::/10 (deprecated)
+ // Ref: https://en.wikipedia.org/wiki/IP_address#Private_addresses
+ || inetAddress.isSiteLocalAddress()
+ // Example: 169.254.0.0/16, fe80::/10
+ // Ref: https://en.wikipedia.org/wiki/IP_address#Address_autoconfiguration
+ || inetAddress.isLinkLocalAddress()
+ // non deprecated unique site-local that java doesn't check yet -> fc00::/7
+ || isIPv6UniqueSiteLocal(inetAddress);
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the specified address is a loopback address. This can be one of the following:
+ *
+ * - 127.0.0.1
+ * - localhost
+ * - [::1]
+ *
+ *
+ * @param address address to check
+ * @return true if the address is a loopback one
+ */
+ public static boolean isLoopbackAddress(String address) {
+ try {
+ InetAddress inetAddress = InetAddress.getByName(address);
+ return inetAddress.isLoopbackAddress();
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+ private static boolean isLoopbackAddress(InetAddress address) {
+ return address.isLoopbackAddress();
+ }
+
+ private static boolean isIPv6UniqueSiteLocal(InetAddress address) {
+ // ref: https://en.wikipedia.org/wiki/Unique_local_address
+
+ // currently undefined but could be used in the near future fc00::/8
+ return (address.getAddress()[0] & 0xFF) == 0xFC
+ // in use for unique site-local fd00::/8
+ || (address.getAddress()[0] & 0xFF) == 0xFD;
}
}
diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java
index 1f200c0f0..5c8613005 100644
--- a/src/main/java/fr/xephi/authme/util/StringUtils.java
+++ b/src/main/java/fr/xephi/authme/util/StringUtils.java
@@ -66,17 +66,6 @@ public final class StringUtils {
return str == null || str.trim().isEmpty();
}
- /**
- * Format the information from a Throwable as string, retaining the type and its message.
- *
- * @param th The throwable to process
- *
- * @return String with the type of the Throwable and its message, e.g. "[IOException]: Could not open stream"
- */
- public static String formatException(Throwable th) {
- return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage();
- }
-
/**
* Check that the given needle is in the middle of the haystack, i.e. that the haystack
* contains the needle and that it is not at the very start or end.
diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml
index 8b073bd96..4687a8b99 100644
--- a/src/main/resources/messages/messages_bg.yml
+++ b/src/main/resources/messages/messages_bg.yml
@@ -60,7 +60,6 @@ misc:
logout: '&2Излязохте успешно!'
reload: '&2Конфигурацията и база данните бяха презаредени правилно!'
usage_change_password: '&cКоманда: /changepassword Стара-Парола Нова-Парола'
- two_factor_create: '&2Кода е %code. Можеш да го провериш оттука: %url'
accounts_owned_self: 'Претежаваш %count акаунт/а:'
accounts_owned_other: 'Потребителят %name има %count акаунт/а:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '&cСтарият имейл е грешен, опитайте отново!'
invalid: '&cИмейла е невалиден, опитайте с друг!'
added: '&2Имейл адреса е добавен!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cМоля потвърди своя имейл адрес!'
changed: '&2Имейл адреса е сменен!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2Твоят имейл адрес е: &f%email'
no_email_for_account: '&2Няма добавен имейл адрес към акаунта.'
already_used: '&4Имейл адреса вече се използва, опитайте с друг.'
@@ -140,3 +141,16 @@ time:
hours: 'часа'
day: 'ден'
days: 'дена'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Кода е %code. Можеш да го провериш оттука: %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml
index 69f5e3954..40c58fc8f 100644
--- a/src/main/resources/messages/messages_br.yml
+++ b/src/main/resources/messages/messages_br.yml
@@ -63,7 +63,6 @@ misc:
logout: '&2Desconectado com sucesso!'
reload: '&2Configuração e o banco de dados foram recarregados corretamente!'
usage_change_password: '&cUse: /changepassword '
- two_factor_create: '&2O seu código secreto é %code. Você pode verificá-lo a partir daqui %url'
accounts_owned_self: 'Você tem %count contas:'
accounts_owned_other: 'O jogador %name tem %count contas:'
@@ -93,8 +92,10 @@ email:
old_email_invalid: '&cE-mail velho inválido, tente novamente!'
invalid: '&E-mail inválido, tente novamente!'
added: '&2Email adicionado com sucesso à sua conta!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cPor favor confirme seu endereço de email!'
changed: '&2Troca de email com sucesso.!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2O seu endereço de e-mail atual é: &f%email'
no_email_for_account: '&2Você atualmente não têm endereço de e-mail associado a esta conta.'
already_used: '&4O endereço de e-mail já está sendo usado'
@@ -143,3 +144,16 @@ time:
hours: 'horas'
day: 'dia'
days: 'dias'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2O seu código secreto é %code. Você pode verificá-lo a partir daqui %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml
index 5ed2ceb41..025b39f7a 100644
--- a/src/main/resources/messages/messages_cz.yml
+++ b/src/main/resources/messages/messages_cz.yml
@@ -60,7 +60,6 @@ misc:
logout: '&cÚspěšně jsi se odhlásil.'
reload: '&cZnovu načtení nastavení AuthMe proběhlo úspěšně.'
usage_change_password: '&cPoužij: "/changepassword StaréHeslo NovéHeslo".'
- two_factor_create: '&2Tvůj tajný kód je %code. Můžeš ho oskenovat zde %url'
accounts_owned_self: 'Vlastníš tyto účty (%count):'
accounts_owned_other: 'Hráč %name vlastní tyto účty (%count):'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '[AuthMe] Starý email je chybně zadán!'
invalid: '[AuthMe] Nesprávný email'
added: '[AuthMe] Email přidán!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '[AuthMe] Potvrď prosím svůj email!'
changed: '[AuthMe] Email změněn!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2Váš aktuální email je: &f%email'
no_email_for_account: '&2K tomuto účtu nemáte přidanou žádnou emailovou adresu.'
already_used: '&4Tato emailová adresa je již používána'
@@ -140,3 +141,16 @@ time:
hours: 'hodin'
day: 'dny'
days: 'dnu'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Tvůj tajný kód je %code. Můžeš ho oskenovat zde %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml
index 84679c486..006626234 100644
--- a/src/main/resources/messages/messages_de.yml
+++ b/src/main/resources/messages/messages_de.yml
@@ -60,7 +60,6 @@ misc:
logout: '&2Erfolgreich ausgeloggt'
reload: '&2Konfiguration und Datenbank wurden erfolgreich neu geladen.'
usage_change_password: '&cBenutze: /changepassword '
- two_factor_create: '&2Dein geheimer Code ist %code. Du kannst ihn hier abfragen: %url'
accounts_owned_self: 'Du besitzt %count Accounts:'
accounts_owned_other: 'Der Spieler %name hat %count Accounts:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '&cDie alte E-Mail ist ungültig!'
invalid: '&cUngültige E-Mail!'
added: '&2E-Mail hinzugefügt!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cBitte bestätige deine E-Mail!'
changed: '&2E-Mail aktualisiert!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2Deine aktuelle E-Mail-Adresse ist: &f%email'
no_email_for_account: '&2Du hast zur Zeit keine E-Mail-Adresse für deinen Account hinterlegt.'
already_used: '&4Diese E-Mail-Adresse wird bereits genutzt.'
@@ -140,3 +141,16 @@ time:
hours: 'Stunden'
day: 'Tag'
days: 'Tage'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Dein geheimer Code ist %code. Du kannst ihn hier abfragen: %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml
index 6d9d28798..917d57cd1 100644
--- a/src/main/resources/messages/messages_en.yml
+++ b/src/main/resources/messages/messages_en.yml
@@ -56,7 +56,6 @@ unregister:
misc:
accounts_owned_self: 'You own %count accounts:'
accounts_owned_other: 'The player %name has %count accounts:'
- two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
account_not_activated: '&cYour account isn''t activated yet, please check your emails!'
password_changed: '&2Password changed successfully!'
logout: '&2Logged out successfully!'
@@ -98,6 +97,8 @@ email:
add_email_request: '&3Please add your email to your account with the command: /email add '
change_password_expired: 'You cannot change your password using this command anymore.'
email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.'
+ add_not_allowed: '&cAdding email was not allowed'
+ change_not_allowed: '&cChanging email was not allowed'
# Password recovery by email
recovery:
@@ -129,6 +130,18 @@ verification:
code_expired: '&3Your code has expired! Execute another sensitive command to get a new code!'
email_needed: '&3To verify your identity you need to link an email address with your account!!'
+two_factor:
+ code_created: '&2Your secret code is %code. You can scan it from here %url'
+ confirmation_required: 'Please confirm your code with /2fa confirm '
+ code_required: 'Please submit your two-factor authentication code with /2fa code '
+ already_enabled: 'Two-factor authentication is already enabled for your account!'
+ enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ enable_success: 'Successfully enabled two-factor authentication for your account'
+ enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ removed_success: 'Successfully removed two-factor auth from your account'
+ invalid_code: 'Invalid code!'
+
# Time units
time:
second: 'second'
diff --git a/src/main/resources/messages/messages_eo.yml b/src/main/resources/messages/messages_eo.yml
index c5783c831..5636ab729 100644
--- a/src/main/resources/messages/messages_eo.yml
+++ b/src/main/resources/messages/messages_eo.yml
@@ -60,7 +60,6 @@ misc:
logout: '&2Elsalutita sukcese!'
reload: '&2Agordo kaj datumbazo estis larditaj korekte!'
usage_change_password: '&cUzado: /changepassword '
- two_factor_create: '&2Via sekreta kodo estas %code. Vi povas skani ĝin de tie %url'
accounts_owned_self: 'Vi posedas %count kontoj:'
accounts_owned_other: 'La ludanto %name havas %count kontojn::'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '&cNevalida malnovaj retpoŝto, provu denove!'
invalid: '&cNevalida retadreso, provu denove!'
added: '&2Retpoŝtadreso sukcese aldonitaj al via konto!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cBonvolu konfirmi vian retadreson!'
changed: '&2Retpoŝtadreso ŝanĝis ĝuste!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2Via nuna retadreso estas: &f%email'
no_email_for_account: '&2Vi aktuale ne havas retadreson asociita kun ĉi tiu konto.'
already_used: '&4La retpoŝto jam estas uzata'
@@ -140,3 +141,16 @@ time:
hours: 'horoj'
day: 'tago'
days: 'tagoj'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Via sekreta kodo estas %code. Vi povas skani ĝin de tie %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml
index 50363ead1..6070411f5 100644
--- a/src/main/resources/messages/messages_es.yml
+++ b/src/main/resources/messages/messages_es.yml
@@ -61,7 +61,6 @@ misc:
logout: '&cDesconectado correctamente.'
reload: '&fLa configuración y la base de datos han sido recargados'
usage_change_password: '&fUso: /changepw contraseñaActual contraseñaNueva'
- two_factor_create: '&2Tu código secreto es %code. Lo puedes escanear desde aquí %url'
accounts_owned_self: 'Eres propietario de %count cuentas:'
accounts_owned_other: 'El jugador %name tiene %count cuentas:'
@@ -80,7 +79,7 @@ on_join_validation:
country_banned: '¡Tu país ha sido baneado de este servidor!'
not_owner_error: 'No eres el propietario de esta cuenta. ¡Por favor, elije otro nombre!'
invalid_name_case: 'Solo puedes unirte mediante el nombre de usuario %valid, no %invalid.'
- # TODO quick_command: 'You used a command too fast! Please, join the server again and wait more before using any command.'
+ quick_command: 'Has usado el comando demasiado rápido! Porfavor, entra al servidor de nuevo y espera un poco antes de usar cualquier comando.'
# Email
email:
@@ -91,8 +90,10 @@ email:
old_email_invalid: '[AuthMe] Email anterior inválido!'
invalid: '[AuthMe] Email inválido'
added: '[AuthMe] Email agregado !'
+ add_not_allowed: '&cNo se permite añadir un Email'
request_confirmation: '[AuthMe] Confirma tu Email !'
changed: '[AuthMe] Email cambiado !'
+ change_not_allowed: '&cNo se permite el cambio de Email'
email_show: '&2Tu dirección de E-Mail actual es: &f%email'
no_email_for_account: '&2No tienes ningun E-Mail asociado en esta cuenta.'
already_used: '&4La dirección Email ya está siendo usada'
@@ -141,3 +142,16 @@ time:
hours: 'horas'
day: 'día'
days: 'días'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Tu código secreto es %code. Lo puedes escanear desde aquí %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_et.yml b/src/main/resources/messages/messages_et.yml
index 68ce520f5..3484fae69 100644
--- a/src/main/resources/messages/messages_et.yml
+++ b/src/main/resources/messages/messages_et.yml
@@ -60,7 +60,6 @@ misc:
logout: '&2Edukalt välja logitud!!'
reload: '&2Andmebaas uuendatud!'
usage_change_password: '&cKasutus: /changepassword '
- two_factor_create: '&2Su salajane kood on %code. Skänni see siin: %url'
accounts_owned_self: 'Sa omad %count kontot:'
accounts_owned_other: 'Mängijal %name on %count kontot:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '&cVale vana meiliaadress, proovi uuesti.'
invalid: '&cVale meiliaadress, proovi uuesti.'
added: '&2Meiliaadress edukalt vahetatud!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cPalun kinnita oma meiliaadress.'
changed: '&2Meiliaadress edukalt vahetatud.'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2Su meiliaadress on: &f%email'
no_email_for_account: '&2Selle kasutajaga pole seotud ühtegi meiliaadressi.'
already_used: '&4Meiliaadress juba kasutuses.'
@@ -140,3 +141,16 @@ time:
hours: 'tundi'
day: 'päev'
days: 'päeva'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Su salajane kood on %code. Skänni see siin: %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml
index 0507d1042..cfa0f78b3 100644
--- a/src/main/resources/messages/messages_eu.yml
+++ b/src/main/resources/messages/messages_eu.yml
@@ -60,7 +60,6 @@ misc:
logout: '&cAtera zara'
reload: '&fConfiguration and database has been reloaded'
usage_change_password: '&fErabili: /changepassword pasahitzZaharra pasahitzBerria'
- # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO accounts_owned_self: 'You own %count accounts:'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '[AuthMe] Email zaharra okerra!'
invalid: '[AuthMe] Email okerrea'
added: '[AuthMe] Emaila gehitu duzu !'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '[AuthMe] Konfirmatu zure emaila !'
changed: '[AuthMe] Emaila aldatua!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
# TODO email_show: '&2Your current email address is: &f%email'
# TODO no_email_for_account: '&2You currently don''t have email address associated with this account.'
# TODO already_used: '&4The email address is already being used'
@@ -140,3 +141,16 @@ time:
# TODO hours: 'hours'
# TODO day: 'day'
# TODO days: 'days'
+
+# Two-factor authentication
+two_factor:
+ # TODO code_created: '&2Your secret code is %code. You can scan it from here %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml
index b0b4f3032..00a29ba4e 100644
--- a/src/main/resources/messages/messages_fi.yml
+++ b/src/main/resources/messages/messages_fi.yml
@@ -60,7 +60,6 @@ misc:
logout: '&cKirjauduit ulos palvelimelta.'
reload: '&fAsetukset uudelleenladattu'
usage_change_password: '&fKäyttötapa: /changepassword vanhaSalasana uusiSalasana'
- # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO accounts_owned_self: 'You own %count accounts:'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '[AuthMe] Vanha sähköposti on väärä!'
invalid: '[AuthMe] Väärä sähköposti'
added: '[AuthMe] Sähköposti lisätty!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '[AuthMe] Vahvistuta sähköposti!'
changed: '[AuthMe] Sähköposti vaihdettu!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
# TODO email_show: '&2Your current email address is: &f%email'
# TODO no_email_for_account: '&2You currently don''t have email address associated with this account.'
# TODO already_used: '&4The email address is already being used'
@@ -140,3 +141,16 @@ time:
# TODO hours: 'hours'
# TODO day: 'day'
# TODO days: 'days'
+
+# Two-factor authentication
+two_factor:
+ # TODO code_created: '&2Your secret code is %code. You can scan it from here %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml
index 52fd4c882..9a492cc72 100644
--- a/src/main/resources/messages/messages_fr.yml
+++ b/src/main/resources/messages/messages_fr.yml
@@ -63,7 +63,6 @@ misc:
logout: '&cVous avez été déconnecté !'
reload: '&aAuthMe a été relancé avec succès.'
usage_change_password: '&cPour changer de mot de passe, utilisez "/changepassword "'
- two_factor_create: '&aVotre code secret est &2%code&a. Vous pouvez le scanner depuis &2%url'
accounts_owned_self: 'Vous avez %count comptes:'
accounts_owned_other: 'Le joueur %name a %count comptes:'
@@ -93,8 +92,10 @@ email:
old_email_invalid: '&cAncien email invalide !'
invalid: '&cL''email inscrit est invalide !'
added: '&aEmail enregistré. En cas de perte de MDP, faites "/email recover "'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cLa confirmation de l''email est manquante ou éronnée.'
changed: '&aVotre email a été mis à jour.'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&fL''email enregistré pour votre compte est: %email'
no_email_for_account: '&c&oVous n''avez aucun email enregistré sur votre compte.'
already_used: '&cCet email est déjà utilisé !'
@@ -143,3 +144,16 @@ time:
hours: 'heures'
day: 'jour'
days: 'jours'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&aVotre code secret est &2%code&a. Vous pouvez le scanner depuis &2%url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml
index d22caa7cb..6b6f8707e 100644
--- a/src/main/resources/messages/messages_gl.yml
+++ b/src/main/resources/messages/messages_gl.yml
@@ -60,7 +60,6 @@ misc:
logout: '&cSesión pechada con éxito'
reload: '&fRecargáronse a configuración e a base de datos'
usage_change_password: '&fUso: /changepassword '
- # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO accounts_owned_self: 'You own %count accounts:'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '[AuthMe] O correo vello non é válido!'
invalid: '[AuthMe] Correo non válido'
added: '[AuthMe] Correo engadido!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '[AuthMe] Confirma o teu correo!'
changed: '[AuthMe] Cambiouse o correo!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
# TODO email_show: '&2Your current email address is: &f%email'
# TODO no_email_for_account: '&2You currently don''t have email address associated with this account.'
# TODO already_used: '&4The email address is already being used'
@@ -140,3 +141,16 @@ time:
# TODO hours: 'hours'
# TODO day: 'day'
# TODO days: 'days'
+
+# Two-factor authentication
+two_factor:
+ # TODO code_created: '&2Your secret code is %code. You can scan it from here %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml
index 1d420b746..4f2fe6020 100644
--- a/src/main/resources/messages/messages_hu.yml
+++ b/src/main/resources/messages/messages_hu.yml
@@ -60,7 +60,6 @@ misc:
logout: '&cSikeresen kijelentkeztél!'
reload: 'Beállítások és az adatbázis újratöltve!'
usage_change_password: 'Használat: "/changepassword <új jelszó>".'
- two_factor_create: '&2A titkos kódod a következő: %code. Vagy skenneld be a következő oldalról: %url'
accounts_owned_self: '%count db regisztrációd van:'
accounts_owned_other: 'A %name nevű játékosnak, %count db regisztrációja van:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '&cHibás a régi email cím, próbáld újra!'
invalid: '&cHibás az email cím, próbáld újra!'
added: '&2Az email címed rögzítése sikeresen megtörtént!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cKérlek, ellenőrízd az email címedet!'
changed: '&2Az email cím cseréje sikeresen megtörtént!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2A jelenlegi email-ed a következő: &f%email'
no_email_for_account: '&2Ehhez a felhasználóhoz jelenleg még nincs email hozzárendelve.'
already_used: '&4Ez az email cím már használatban van!'
@@ -140,3 +141,16 @@ time:
hours: 'óra'
day: 'nap'
days: 'nap'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2A titkos kódod a következő: %code. Vagy skenneld be a következő oldalról: %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml
index 65e84cd9c..9e9d61215 100644
--- a/src/main/resources/messages/messages_id.yml
+++ b/src/main/resources/messages/messages_id.yml
@@ -60,7 +60,6 @@ misc:
logout: '&2Berhasil logout!'
reload: '&2Konfigurasi dan database telah dimuat ulang!'
usage_change_password: '&cUsage: /changepassword '
- # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO accounts_owned_self: 'You own %count accounts:'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '&cEmail lama tidak valid, coba lagi!'
invalid: '&cAlamat email tidak valid, coba lagi!'
added: '&2Berhasil menambahkan alamat email ke akunmu!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cSilahkan konfirmasi alamat email kamu!'
changed: '&2Alamat email telah diubah dengan benar!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
# TODO email_show: '&2Your current email address is: &f%email'
# TODO no_email_for_account: '&2You currently don''t have email address associated with this account.'
# TODO already_used: '&4The email address is already being used'
@@ -140,3 +141,16 @@ time:
# TODO hours: 'hours'
# TODO day: 'day'
# TODO days: 'days'
+
+# Two-factor authentication
+two_factor:
+ # TODO code_created: '&2Your secret code is %code. You can scan it from here %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml
index 22354cda8..9e9721b96 100644
--- a/src/main/resources/messages/messages_it.yml
+++ b/src/main/resources/messages/messages_it.yml
@@ -4,12 +4,10 @@
# %username% - Sostituisce il nome dell'utente che riceve il messaggio.
# %displayname% - Sostituisce il nickname (e i colori) dell'utente che riceve il messaggio.
-# Registrazione
-
# Registration
registration:
disabled: '&cLa registrazione tramite i comandi di gioco è disabilitata.'
- name_taken: '&cHai già eseguito la registrazione, non puoi eseguirla nuovamente.'
+ name_taken: '&cHai già eseguito la registrazione!'
register_request: '&3Per favore, esegui la registrazione con il comando: /register '
command_usage: '&cUtilizzo: /register '
reg_only: '&4Puoi giocare in questo server solo dopo aver eseguito la registrazione attraverso il sito web! Per favore, vai su http://esempio.it per procedere!'
@@ -41,7 +39,7 @@ error:
no_permission: '&4Non hai il permesso di eseguire questa operazione.'
unexpected_error: '&4Qualcosa è andato storto, riporta questo errore ad un amministratore!'
max_registration: '&cHai raggiunto il numero massimo di registrazioni (%reg_count/%max_acc %reg_names) per questo indirizzo IP!'
- logged_in: '&cHai già eseguito l''autenticazione, non è necessario eseguirla nuovamente!'
+ logged_in: '&cHai già eseguito l''autenticazione!'
kick_for_vip: '&3Un utente VIP è entrato mentre il server era pieno e ha preso il tuo posto!'
tempban_max_logins: '&cSei stato temporaneamente bandito per aver fallito l''autenticazione troppe volte.'
@@ -63,7 +61,6 @@ misc:
logout: '&2Disconnessione avvenuta correttamente!'
reload: '&2La configurazione e il database sono stati ricaricati correttamente!'
usage_change_password: '&cUtilizzo: /changepassword '
- two_factor_create: '&2Il tuo codice segreto è: &f%code%%nl%&2Puoi anche scannerizzare il codice QR da qui: &f%url'
accounts_owned_self: 'Possiedi %count account:'
accounts_owned_other: 'Il giocatore %name possiede %count account:'
@@ -82,7 +79,7 @@ on_join_validation:
country_banned: '&4Il tuo paese è bandito da questo server!'
not_owner_error: 'Non sei il proprietario di questo account. Per favore scegli un altro nome!'
invalid_name_case: 'Dovresti entrare con questo nome utente "%valid", al posto di "%invalid".'
- # TODO quick_command: 'You used a command too fast! Please, join the server again and wait more before using any command.'
+ quick_command: 'Hai usato un comando troppo velocemente dal tuo accesso! Per favore, rientra nel server e aspetta un po'' di più prima di usare un qualsiasi comando.'
# Email
email:
@@ -93,8 +90,10 @@ email:
old_email_invalid: '&cIl vecchio indirizzo email inserito non è valido, riprova!'
invalid: '&cL''indirizzo email inserito non è valido, riprova!'
added: '&2Indirizzo email aggiunto correttamente al tuo account!'
+ add_not_allowed: '&cNon hai il permesso di aggiungere un indirizzo email'
request_confirmation: '&cPer favore, conferma il tuo indirizzo email!'
changed: '&2Indirizzo email cambiato correttamente!'
+ change_not_allowed: '&cNon hai il permesso di cambiare l''indirizzo email'
email_show: '&2Il tuo indirizzo email al momento è: &f%email'
no_email_for_account: '&2Al momento non hai nessun indirizzo email associato al tuo account.'
already_used: '&4L''indirizzo email inserito è già in uso'
@@ -105,7 +104,7 @@ email:
# Password recovery by email
recovery:
- forgot_password_hint: '&3Hai dimenticato la tua password? Puoi recuperarla eseguendo il comando: /email recovery '
+ forgot_password_hint: '&3Hai dimenticato la tua password? Puoi recuperarla usando il comando: /email recovery '
command_usage: '&cUtilizzo: /email recovery '
email_sent: '&2Una email di recupero è stata appena inviata al tuo indirizzo email!'
code:
@@ -143,3 +142,16 @@ time:
hours: 'ore'
day: 'giorno'
days: 'giorni'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Il tuo codice segreto è: &f%code%%nl%&2Puoi anche scannerizzare il codice QR da qui: &f%url'
+ confirmation_required: 'Per favore conferma il tuo codice con: /2fa confirm '
+ code_required: 'Per favore inserisci il tuo codice per l''autenticazione a 2 fattori con: /2fa code '
+ already_enabled: 'Hai già abilitato l''autenticazione a 2 fattori!'
+ enable_error_no_code: 'Non hai ancora generato un codice per l''autenticazione a 2 fattori oppure il tuo codice è scaduto. Per favore scrivi: /2fa add'
+ enable_success: 'Autenticazione a 2 fattori abilitata correttamente'
+ enable_error_wrong_code: 'Hai inserito un codice sbagliato o scaduto. Per favore scrivi: /2fa add'
+ not_enabled_error: 'L''autenticazione a 2 fattori non è ancora abilitata per il tuo account. Scrivi: /2fa add'
+ removed_success: 'Autenticazione a 2 fattori rimossa correttamente'
+ invalid_code: 'Il codice inserito non è valido, riprova!'
diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml
index 41049b884..32b5ffd43 100644
--- a/src/main/resources/messages/messages_ko.yml
+++ b/src/main/resources/messages/messages_ko.yml
@@ -62,7 +62,6 @@ misc:
logout: '&2로그아웃 되었습니다!'
reload: '&2설정과 데이터 베이스가 새로고침 되었습니다!'
usage_change_password: '&c사용법: /changepassword <예전 비밀번호> <새 비밀번호>'
- two_factor_create: '&2당신의 비밀 코드는 %code 입니다. %url 에서 스캔할 수 있습니다'
accounts_owned_self: '%count 개의 계정을 소유하고 있습니다.'
accounts_owned_other: '플레이어 %name 는 %count 개의 계정을 소유하고 있습니다:'
@@ -92,8 +91,10 @@ email:
old_email_invalid: '&c예전 이메일 주소가 잘못되었습니다. 다시 시도해보세요!'
invalid: '&c이메일 주소가 잘못되었습니다. 다시 시도해보세요!'
added: '&2계정에 이메일 주소를 추가했습니다!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&c이메일 주소를 확인해주세요!'
changed: '&2이메일 주소가 변경되었습니다!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2현재 이메일 주소: &f%email'
no_email_for_account: '&2현재 이 계정과 연결된 이메일 주소가 없습니다.'
already_used: '&4이메일 주소가 이미 사용 중입니다.'
@@ -142,3 +143,16 @@ time:
hours: '시간'
day: '일'
days: '일'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2당신의 비밀 코드는 %code 입니다. %url 에서 스캔할 수 있습니다'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml
index 15eea3048..e58a9fd6e 100644
--- a/src/main/resources/messages/messages_lt.yml
+++ b/src/main/resources/messages/messages_lt.yml
@@ -60,7 +60,6 @@ misc:
logout: '&aSekmingai atsijungete'
reload: '&aNustatymai ir duomenu baze buvo perkrauta.'
usage_change_password: '&ePanaudojimas: /changepassword senasSlaptazodis naujasSlaptazodis'
- # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
# TODO accounts_owned_self: 'You own %count accounts:'
# TODO accounts_owned_other: 'The player %name has %count accounts:'
@@ -90,8 +89,10 @@ email:
# TODO old_email_invalid: '&cInvalid old email, try again!'
# TODO invalid: '&cInvalid email address, try again!'
# TODO added: '&2Email address successfully added to your account!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
# TODO request_confirmation: '&cPlease confirm your email address!'
# TODO changed: '&2Email address changed correctly!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
# TODO email_show: '&2Your current email address is: &f%email'
# TODO no_email_for_account: '&2You currently don''t have email address associated with this account.'
# TODO already_used: '&4The email address is already being used'
@@ -140,3 +141,16 @@ time:
# TODO hours: 'hours'
# TODO day: 'day'
# TODO days: 'days'
+
+# Two-factor authentication
+two_factor:
+ # TODO code_created: '&2Your secret code is %code. You can scan it from here %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml
index b8fc40950..00b99161b 100644
--- a/src/main/resources/messages/messages_nl.yml
+++ b/src/main/resources/messages/messages_nl.yml
@@ -60,7 +60,6 @@ misc:
logout: '&2Je bent succesvol uitgelogd!'
reload: '&2De configuratie en database zijn succesvol herladen!'
usage_change_password: '&cGebruik: /changepassword '
- two_factor_create: '&2Je geheime code is %code. Je kunt hem scannen op %url'
accounts_owned_self: 'Je bezit %count accounts:'
accounts_owned_other: 'De speler %name heeft %count accounts:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '&cOngeldig oud e-mailadres, probeer het opnieuw!'
invalid: '&cOngeldig E-mailadres, probeer het opnieuw!'
added: '&2Het e-mailadres is succesvol toegevoegd aan je account!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cVerifiëer je e-mailadres alsjeblieft!'
changed: '&2Het e-mailadres is succesvol veranderd!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2Jouw huidige e-mailadres is: %email'
no_email_for_account: '&2Je hebt nog geen e-mailadres toegevoegd aan dit account.'
already_used: '&4Dit e-mailadres wordt al gebruikt'
@@ -140,3 +141,16 @@ time:
hours: 'uren'
day: 'dag'
days: 'dagen'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Je geheime code is %code. Je kunt hem scannen op %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm '
+ # TODO code_required: 'Please submit your two-factor authentication code with /2fa code '
+ # TODO already_enabled: 'Two-factor authentication is already enabled for your account!'
+ # TODO enable_error_no_code: 'No 2fa key has been generated for you or it has expired. Please run /2fa add'
+ # TODO enable_success: 'Successfully enabled two-factor authentication for your account'
+ # TODO enable_error_wrong_code: 'Wrong code or code has expired. Please run /2fa add'
+ # TODO not_enabled_error: 'Two-factor authentication is not enabled for your account. Run /2fa add'
+ # TODO removed_success: 'Successfully removed two-factor auth from your account'
+ # TODO invalid_code: 'Invalid code!'
diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml
index 4d9061dfd..78f63a831 100644
--- a/src/main/resources/messages/messages_pl.yml
+++ b/src/main/resources/messages/messages_pl.yml
@@ -60,7 +60,6 @@ misc:
logout: '&cPomyślnie wylogowany'
reload: '&fKonfiguracja bazy danych została przeładowana.'
usage_change_password: '&fUżycie: /changepassword '
- two_factor_create: '&2Twój sekretny kod to %code. Możesz zeskanować go tutaj: %url'
accounts_owned_self: '&7Posiadasz %count kont:'
accounts_owned_other: '&7Gracz %name posiada %count kont:'
@@ -79,7 +78,7 @@ on_join_validation:
country_banned: '&4Ten kraj jest zbanowany na tym serwerze'
not_owner_error: '&cNie jesteś właścicielem tego konta, wybierz inny nick!'
invalid_name_case: '&cPowinieneś dołączyć do serwera z nicku %valid, a nie %invalid.'
- # TODO quick_command: 'You used a command too fast! Please, join the server again and wait more before using any command.'
+ quick_command: '&cUżyłeś komendy zbyt szybko! Ponownie dołącz do serwera i poczekaj chwilę, zanim użyjesz dowolnej komendy.'
# Email
email:
@@ -90,8 +89,10 @@ email:
old_email_invalid: '[AuthMe] Stary e-mail niepoprawny!'
invalid: '[AuthMe] Nieprawidłowy adres e-mail.'
added: '[AuthMe] E-mail został dodany do Twojego konta!'
+ add_not_allowed: '&cMożliwość dodania adresu e-mail jest wyłączona.'
request_confirmation: '[AuthMe] Potwierdź swój adres e-mail!'
changed: '[AuthMe] E-mail został zmieniony!'
+ change_not_allowed: '&cMożliwość zmiany adresu e-mail jest wyłączona.'
email_show: '&2Twój aktualny adres e-mail to: &f%email'
no_email_for_account: '&2Nie posiadasz adresu e-mail przypisanego do tego konta.'
already_used: '&4Ten adres e-mail jest aktualnie używany!'
@@ -140,3 +141,16 @@ time:
hours: 'godzin'
day: 'dzień'
days: 'dni'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Twój sekretny kod to %code. Możesz zeskanować go tutaj: %url'
+ confirmation_required: 'Musisz potwierdzić swój kod komendą /2fa confirm '
+ code_required: 'Wpisz swój kod weryfikacji dwuetapowej przy pomocy komendy /2fa code '
+ already_enabled: '&aWeryfikacja dwuetapowa jest już włączona dla Twojego konta.'
+ enable_error_no_code: '&cKod weryfikacji dwuetapowej nie został dla Ciebie wygenerowany lub wygasł. Wpisz komende /2fa add'
+ enable_success: '&aWeryfikacja dwuetapowa została włączona dla Twojego konta.'
+ enable_error_wrong_code: '&cWpisany kod jest nieprawidłowy lub wygasły. Wpisz ponownie /2fa add'
+ not_enabled_error: 'Weryfikacja dwuetapowa nie jest włączona dla twojego konta. Wpisz komende /2fa add'
+ removed_success: '&aPomyślnie usunięto weryfikacje dwuetapową z Twojego konta.'
+ invalid_code: '&cWpisany kod jest nieprawidłowy, spróbuj jeszcze raz.'
diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml
index 2fb0ad2c4..5d5b0ce9b 100644
--- a/src/main/resources/messages/messages_pt.yml
+++ b/src/main/resources/messages/messages_pt.yml
@@ -60,7 +60,6 @@ misc:
logout: '&cSaida com sucesso'
reload: '&fConfiguração e base de dados foram recarregadas'
usage_change_password: '&fUse: /changepassword '
- two_factor_create: '&2O seu código secreto é o %code. Você pode verificá-lo a partir daqui %url'
accounts_owned_self: 'Você possui %count contas:'
accounts_owned_other: 'O jogador %name possui %count contas:'
@@ -79,7 +78,7 @@ on_join_validation:
country_banned: 'O seu país está banido deste servidor'
not_owner_error: 'Não é o proprietário da conta. Por favor, escolha outro nome!'
invalid_name_case: 'Deve se juntar usando nome de usuário %valid, não %invalid.'
- # TODO quick_command: 'You used a command too fast! Please, join the server again and wait more before using any command.'
+ quick_command: 'Você usou o comando demasiado rapido por favor re-entre no servidor e aguarde antes de digitar qualquer comando.'
# Email
email:
@@ -99,6 +98,8 @@ email:
send_failure: 'Não foi possivel enviar o email. Por favor contate um administrador.'
change_password_expired: 'Você não pode mais alterar a sua password usando este comando.'
email_cooldown_error: '&cUm email já foi enviado recentemente.Por favor, espere %time antes de enviar novamente'
+ add_not_allowed: '&cAdicionar e-mail não é permitido'
+ change_not_allowed: '&cAlterar e-mail não é permitido'
# Password recovery by email
recovery:
@@ -117,18 +118,18 @@ captcha:
usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha %captcha_code'
wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha %captcha_code'
valid_captcha: '&cO seu captcha é válido!'
- # TODO captcha_for_registration: 'To register you have to solve a captcha first, please use the command: /captcha %captcha_code'
- # TODO register_captcha_valid: '&2Valid captcha! You may now register with /register'
+ captcha_for_registration: 'Para se registar tem de resolver o captcha primeiro, por favor use: /captcha %captcha_code'
+ register_captcha_valid: '&2Captcha Valido! Agora você pode te registar com /register'
# Verification code
verification:
- # TODO code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.'
- # TODO command_usage: '&cUsage: /verification '
- # TODO incorrect_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email'
- # TODO success: '&2Your identity has been verified! You can now execute all commands within the current session!'
- # TODO already_verified: '&2You can already execute every sensitive command within the current session!'
- # TODO code_expired: '&3Your code has expired! Execute another sensitive command to get a new code!'
- # TODO email_needed: '&3To verify your identity you need to link an email address with your account!!'
+ code_required: '&3Este codigo é sensivel e requer uma verificação por e-mail! Verifique na sua caixa de entrada e siga as instruções do e-mail.'
+ command_usage: '&cUso: /verification '
+ incorrect_code: '&cCodigo incorreto, por favor digite "/verification " no chat, utilizando o codigo que recebeu no e-mail.'
+ success: '&2Sua identidade foi verificada! Agora você pode executar todos os comandos nesta sessão!'
+ already_verified: '&2Você já pode digitar todos os comandos sensiveis nesta sessão!'
+ code_expired: '&3Seu codigo expirou! Execute outro comando sensivel para obter um novo codigo!'
+ email_needed: '&3Para confirmar a sua identidade necessita de associar um endereço de e-mail!!'
# Time units
time:
@@ -140,3 +141,16 @@ time:
hours: 'horas'
day: 'dia'
days: 'dias'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2O seu código secreto é o %code. Você pode verificá-lo a partir daqui %url'
+ confirmation_required: 'Por favor confirme seu codigo de 2 etapas com /2fa confirm '
+ code_required: 'Por favor submita seu codigo de duas etapas com /2fa code '
+ already_enabled: 'Autenticação de duas etapas já se encontra habilitada na sua conta!'
+ enable_error_no_code: 'Nenhuma chave 2fa foi gerada por você ou expirou. Por favor digite /2fa add'
+ enable_success: 'Autenticação de duas etapas habilitada com sucesso na sua conta'
+ enable_error_wrong_code: 'Codigo errado ou o codigo expirou. Por favor digite /2fa add'
+ not_enabled_error: 'Autenticação de duas etapas não está habilitada na sua conta. Digite /2fa add'
+ removed_success: 'Autenticação de duas etapas removida com sucesso da sua conta'
+ invalid_code: 'Codigo invalido!'
diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml
index 6fcacb052..3d9c6cfed 100644
--- a/src/main/resources/messages/messages_ro.yml
+++ b/src/main/resources/messages/messages_ro.yml
@@ -60,7 +60,6 @@ misc:
logout: '&2Te-ai dezautentificat cu succes!'
reload: '&2Configuratiile si baza de date sau reincarcat corect!'
usage_change_password: '&cFoloseste comanda: /changepassword '
- two_factor_create: '&2Codul tau secret este %code. Il poti scana de aici %url'
accounts_owned_self: 'Detii %count conturi:'
accounts_owned_other: 'Jucatorul %name are %count conturi:'
@@ -90,8 +89,10 @@ email:
old_email_invalid: '&cEmail-ul vechi este invalid, incearca din nou!'
invalid: '&cEmail-ul este invalid, incearca din nou!'
added: '&2Email-ul a fost adaugat cu succes la contul tau!'
+ # TODO add_not_allowed: '&cAdding email was not allowed'
request_confirmation: '&cTe rugam sa confirmi adresa ta de email!'
changed: '&2Email-ul a fost schimbat cu succes!'
+ # TODO change_not_allowed: '&cChanging email was not allowed'
email_show: '&2Adresa ta curenta de email este: &f%email'
no_email_for_account: '&2Nu ai nici o adresa de email asociata cu acest cont.'
already_used: '&4Email-ul acesta este deja folosit de altcineva'
@@ -140,3 +141,16 @@ time:
hours: 'ore'
day: 'zi'
days: 'zile'
+
+# Two-factor authentication
+two_factor:
+ code_created: '&2Codul tau secret este %code. Il poti scana de aici %url'
+ # TODO confirmation_required: 'Please confirm your code with /2fa confirm