diff --git a/docs/commands.md b/docs/commands.md index 9e2176ee9..81b323716 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,5 +1,5 @@ - + ## AuthMe Commands You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >` @@ -32,7 +32,7 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
Requires `authme.admin.firstspawn` - **/authme setfirstspawn**: Change the first player's spawn to your current position.
Requires `authme.admin.setfirstspawn` -- **/authme purge** <days> [all]: Purge old AuthMeReloaded data longer than the specified number of days ago. +- **/authme purge** <days>: Purge old AuthMeReloaded data longer than the specified number of days ago.
Requires `authme.admin.purge` - **/authme purgeplayer** <player> [options]: Purges data of the given player.
Requires `authme.admin.purgeplayer` @@ -86,8 +86,11 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`). - **/captcha** <captcha>: Captcha command for AuthMeReloaded.
Requires `authme.player.captcha` - **/captcha help** [query]: View detailed help for /captcha commands. +- **/verification** <code>: Command to complete the verification process for AuthMeReloaded. +
Requires `authme.player.security.verificationcode` +- **/verification help** [query]: View detailed help for /verification commands. --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Oct 08 19:56:41 CEST 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Oct 28 10:39:36 CEST 2017 diff --git a/docs/config.md b/docs/config.md index a49a8f839..ed5a2e2fb 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,5 +1,5 @@ - + ## AuthMe Configuration The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder, @@ -43,10 +43,14 @@ DataSource: mySQLColumnLogged: 'isLogged' # Column for storing if a player has a valid session or not mySQLColumnHasSession: 'hasSession' - # Column for storing players ips + # Column for storing the player's last IP mySQLColumnIp: 'ip' # Column for storing players lastlogins mySQLColumnLastLogin: 'lastlogin' + # Column storing the registration date + mySQLColumnRegisterDate: 'regdate' + # Column for storing the IP address at the time of registration + mySQLColumnRegisterIp: 'regip' # Column for storing player LastLocation - X mySQLlastlocX: 'x' # Column for storing player LastLocation - Y @@ -226,10 +230,11 @@ settings: minPasswordLength: 5 # Maximum length of password passwordMaxLength: 30 - # Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL, + # Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, # MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB, - # PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at + # PBKDF2DJANGO, WORDPRESS, ROYALAUTH, ARGON2, CUSTOM (for developers only). See full list at # https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/hash_algorithms.md + # If you use ARGON2, check that you have the argon2 c library on your system passwordHash: 'SHA256' # If a password check fails, AuthMe will also try to check with the following hash methods. # Use this setting when you change from one hash method to another. @@ -470,6 +475,8 @@ Security: # original email: my.email@example.com # hidden email: my.***@***mple.com enableEmailMasking: false + # Minutes after which a verification code will expire + verificationCodeExpiration: 10 # Before a user logs in, various properties are temporarily removed from the player, # such as OP status, ability to fly, and walk/fly speed. # Once the user is logged in, we add back the properties we previously saved. @@ -547,4 +554,4 @@ To change settings on a running server, save your changes to config.yml and use --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Oct 10 13:51:56 CEST 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Oct 28 10:39:36 CEST 2017 diff --git a/docs/permission_nodes.md b/docs/permission_nodes.md index 4325fdfda..1444ee4ed 100644 --- a/docs/permission_nodes.md +++ b/docs/permission_nodes.md @@ -1,5 +1,5 @@ - + ## AuthMe Permission Nodes The following are the permission nodes that are currently supported by the latest dev builds. @@ -40,6 +40,7 @@ The following are the permission nodes that are currently supported by the lates - **authme.debug.group** – Permission to view permission groups. - **authme.debug.limbo** – Permission to use the limbo data viewer. - **authme.debug.mail** – Permission to use the test email sender. +- **authme.debug.mysqldef** – Permission to change nullable status of MySQL columns. - **authme.debug.perm** – Permission to use the permission checker. - **authme.debug.spawn** – Permission to view spawn information. - **authme.debug.stats** – Permission to use the stats section. @@ -56,6 +57,7 @@ The following are the permission nodes that are currently supported by the lates - **authme.player.login** – Command permission to login. - **authme.player.logout** – Command permission to logout. - **authme.player.register** – Command permission to register. +- **authme.player.security.verificationcode** – Permission to use the email verification codes feature. - **authme.player.seeownaccounts** – Permission to use to see own other accounts. - **authme.player.unregister** – Command permission to unregister. - **authme.vip** – When the server is full and someone with this permission joins the server, someone will be kicked. @@ -63,4 +65,4 @@ The following are the permission nodes that are currently supported by the lates --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Sep 02 12:24:17 CEST 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Oct 28 12:12:40 CEST 2017 diff --git a/docs/translations.md b/docs/translations.md index c85b33ee4..c92124baf 100644 --- a/docs/translations.md +++ b/docs/translations.md @@ -1,5 +1,5 @@ - + # AuthMe Translations The following translations are available in AuthMe. Set `messagesLanguage` to the language code @@ -8,37 +8,37 @@ in your config.yml to use the language, or use another language code to start a Code | Language | Translated |   ---- | -------- | ---------: | ------ [en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | bar -[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 95% | bar -[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 100% | bar -[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 100% | bar -[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 100% | bar -[eo](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eo.yml) | Esperanto | 100% | bar -[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% | bar -[et](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_et.yml) | Estonian | 100% | bar -[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 53% | bar -[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 56% | bar -[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% | bar -[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 60% | bar -[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 100% | bar -[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 59% | bar -[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% | bar -[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 85% | bar -[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 45% | bar -[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 100% | bar -[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% | bar -[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 100% | bar -[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 100% | bar -[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 100% | bar -[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 100% | bar -[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 95% | bar -[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 79% | bar -[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 96% | bar -[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 100% | bar -[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 68% | bar -[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 81% | bar -[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 68% | bar +[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 88% | bar +[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 92% | bar +[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 92% | bar +[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 92% | bar +[eo](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eo.yml) | Esperanto | 92% | bar +[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 92% | bar +[et](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_et.yml) | Estonian | 92% | bar +[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 49% | bar +[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 52% | bar +[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 92% | bar +[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 55% | bar +[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 91% | bar +[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 54% | bar +[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 92% | bar +[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 78% | bar +[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 41% | bar +[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 92% | bar +[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 92% | bar +[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 92% | bar +[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 92% | bar +[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 92% | bar +[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 92% | bar +[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 88% | bar +[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 73% | bar +[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 89% | bar +[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 92% | bar +[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 92% | bar +[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 75% | bar +[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 63% | bar --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Sep 17 11:29:08 CEST 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Oct 28 11:30:40 CEST 2017 diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 8c96894bb..dbce87332 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -40,6 +40,7 @@ import fr.xephi.authme.command.executable.login.LoginCommand; import fr.xephi.authme.command.executable.logout.LogoutCommand; import fr.xephi.authme.command.executable.register.RegisterCommand; import fr.xephi.authme.command.executable.unregister.UnregisterCommand; +import fr.xephi.authme.command.executable.verification.VerificationCommand; import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.DebugSectionPermissions; import fr.xephi.authme.permission.PlayerPermission; @@ -136,13 +137,24 @@ public class CommandInitializer { CommandDescription captchaBase = CommandDescription.builder() .parent(null) .labels("captcha") - .description("Captcha Command") + .description("Captcha command") .detailedDescription("Captcha command for AuthMeReloaded.") .withArgument("captcha", "The Captcha", false) .permission(PlayerPermission.CAPTCHA) .executableCommand(CaptchaCommand.class) .register(); + // Register the base verification code command + CommandDescription verificationBase = CommandDescription.builder() + .parent(null) + .labels("verification") + .description("Verification command") + .detailedDescription("Command to complete the verification process for AuthMeReloaded.") + .withArgument("code", "The code", false) + .permission(PlayerPermission.VERIFICATION_CODE) + .executableCommand(VerificationCommand.class) + .register(); + List baseCommands = ImmutableList.of( authMeBase, emailBase, @@ -151,7 +163,8 @@ public class CommandInitializer { registerBase, unregisterBase, changePasswordBase, - captchaBase); + captchaBase, + verificationBase); setHelpOnAllBases(baseCommands); commands = baseCommands; diff --git a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java index 7c4b53c35..a1cc4f034 100644 --- a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java @@ -1,6 +1,7 @@ package fr.xephi.authme.command.executable.changepassword; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; @@ -29,17 +30,28 @@ public class ChangePasswordCommand extends PlayerCommand { @Inject private Management management; + @Inject + private VerificationCodeManager codeManager; + @Override public void runCommand(Player player, List arguments) { - String oldPassword = arguments.get(0); - String newPassword = arguments.get(1); - String name = player.getName().toLowerCase(); + if (!playerCache.isAuthenticated(name)) { commonService.send(player, MessageKey.NOT_LOGGED_IN); return; } + // Check if the user has been verified or not + if (codeManager.isVerificationRequired(player)) { + codeManager.codeExistOrGenerateNew(name); + commonService.send(player, MessageKey.VERIFICATION_CODE_REQUIRED); + return; + } + + String oldPassword = arguments.get(0); + String newPassword = arguments.get(1); + // Make sure the password is allowed ValidationResult passwordValidation = validationService.validatePassword(newPassword, name); if (passwordValidation.hasError()) { diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java index aabe99bc3..a6d52e0a4 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java @@ -1,8 +1,10 @@ package fr.xephi.authme.command.executable.email; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; +import fr.xephi.authme.service.CommonService; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -16,11 +18,24 @@ public class ChangeEmailCommand extends PlayerCommand { @Inject private Management management; + @Inject + private CommonService commonService; + + @Inject + private VerificationCodeManager codeManager; + @Override public void runCommand(Player player, List arguments) { + final String playerName = player.getName(); + // Check if the user has been verified or not + if (codeManager.isVerificationRequired(player)) { + codeManager.codeExistOrGenerateNew(playerName); + commonService.send(player, MessageKey.VERIFICATION_CODE_REQUIRED); + return; + } + String playerMailOld = arguments.get(0); String playerMailNew = arguments.get(1); - management.performChangeEmail(player, playerMailOld, playerMailNew); } diff --git a/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java index dd747e0e4..91088f1b3 100644 --- a/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java @@ -1,6 +1,7 @@ package fr.xephi.authme.command.executable.unregister; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; @@ -24,6 +25,9 @@ public class UnregisterCommand extends PlayerCommand { @Inject private PlayerCache playerCache; + @Inject + private VerificationCodeManager codeManager; + @Override public void runCommand(Player player, List arguments) { String playerPass = arguments.get(0); @@ -35,6 +39,13 @@ public class UnregisterCommand extends PlayerCommand { return; } + // Check if the user has been verified or not + if (codeManager.isVerificationRequired(player)) { + codeManager.codeExistOrGenerateNew(playerName); + commonService.send(player, MessageKey.VERIFICATION_CODE_REQUIRED); + return; + } + // Unregister the player management.performUnregister(player, playerPass); } diff --git a/src/main/java/fr/xephi/authme/command/executable/verification/VerificationCommand.java b/src/main/java/fr/xephi/authme/command/executable/verification/VerificationCommand.java new file mode 100644 index 000000000..6aeeda282 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/verification/VerificationCommand.java @@ -0,0 +1,58 @@ +package fr.xephi.authme.command.executable.verification; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.data.VerificationCodeManager; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.service.CommonService; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.List; + +/** + * Used to complete the email verification process. + */ +public class VerificationCommand extends PlayerCommand { + + @Inject + private CommonService commonService; + + @Inject + private VerificationCodeManager codeManager; + + @Override + public void runCommand(Player player, List arguments) { + final String playerName = player.getName(); + + if (!codeManager.canSendMail()) { + ConsoleLogger.warning("Mail API is not set"); + commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); + return; + } + + if (codeManager.isVerificationRequired(player)) { + if (codeManager.isCodeRequired(playerName)) { + if (codeManager.checkCode(playerName, arguments.get(0))) { + commonService.send(player, MessageKey.VERIFICATION_CODE_VERIFIED); + } else { + commonService.send(player, MessageKey.INCORRECT_VERIFICATION_CODE); + } + } else { + commonService.send(player, MessageKey.VERIFICATION_CODE_EXPIRED); + } + } else { + if (codeManager.hasEmail(playerName)) { + commonService.send(player, MessageKey.VERIFICATION_CODE_ALREADY_VERIFIED); + } else { + commonService.send(player, MessageKey.VERIFICATION_CODE_EMAIL_NEEDED); + commonService.send(player, MessageKey.ADD_EMAIL_MESSAGE); + } + } + } + + @Override + public MessageKey getArgumentsMismatchMessage() { + return MessageKey.USAGE_VERIFICATION_CODE; + } +} diff --git a/src/main/java/fr/xephi/authme/data/VerificationCodeManager.java b/src/main/java/fr/xephi/authme/data/VerificationCodeManager.java new file mode 100644 index 000000000..a6ba75c6e --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/VerificationCodeManager.java @@ -0,0 +1,189 @@ +package fr.xephi.authme.data; + +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.datasource.DataSourceResult; +import fr.xephi.authme.initialization.HasCleanup; +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.mail.EmailService; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerPermission; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.RandomStringUtils; +import fr.xephi.authme.util.Utils; +import fr.xephi.authme.util.expiring.ExpiringMap; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class VerificationCodeManager implements SettingsDependent, HasCleanup { + + private final EmailService emailService; + private final DataSource dataSource; + private final PermissionsManager permissionsManager; + + private final ExpiringMap verificationCodes; + private final Set verifiedPlayers; + + private boolean canSendMail; + + @Inject + VerificationCodeManager(Settings settings, DataSource dataSource, EmailService emailService, + PermissionsManager permissionsManager) { + this.emailService = emailService; + this.dataSource = dataSource; + this.permissionsManager = permissionsManager; + verifiedPlayers = new HashSet<>(); + long countTimeout = settings.getProperty(SecuritySettings.VERIFICATION_CODE_EXPIRATION_MINUTES); + verificationCodes = new ExpiringMap<>(countTimeout, TimeUnit.MINUTES); + reload(settings); + } + + /** + * Returns if it is possible to send emails + * + * @return true if the service is enabled, false otherwise + */ + public boolean canSendMail() { + return canSendMail; + } + + /** + * Returns whether the given player is able to verify his identity + * + * @param player the player to verify + * @return true if the player has not been verified yet, false otherwise + */ + public boolean isVerificationRequired(Player player) { + final String name = player.getName(); + return canSendMail + && !isPlayerVerified(name) + && permissionsManager.hasPermission(player, PlayerPermission.VERIFICATION_CODE) + && hasEmail(name); + } + + /** + * Returns whether the given player is required to verify his identity through a command + * + * @param name the name of the player to verify + * @return true if the player has an existing code and has not been verified yet, false otherwise + */ + public boolean isCodeRequired(String name) { + return canSendMail && hasCode(name) && !isPlayerVerified(name); + } + + /** + * Returns whether the given player has been verified or not + * + * @param name the name of the player to verify + * @return true if the player has been verified, false otherwise + */ + private boolean isPlayerVerified(String name) { + return verifiedPlayers.contains(name.toLowerCase()); + } + + /** + * Returns if a code exists for the player + * + * @param name the name of the player to verify + * @return true if the code exists, false otherwise + */ + public boolean hasCode(String name) { + return (verificationCodes.get(name.toLowerCase()) != null); + } + + /** + * Returns whether the given player is able to receive emails + * + * @param name the name of the player to verify + * @return true if the player is able to receive emails, false otherwise + */ + public boolean hasEmail(String name) { + boolean result = false; + DataSourceResult emailResult = dataSource.getEmail(name); + if (emailResult.playerExists()) { + final String email = emailResult.getValue(); + if (!Utils.isEmailEmpty(email)) { + result = true; + } + } + return result; + } + + /** + * Check if a code exists for the player or generates and saves a new one. + * + * @param name the player's name + */ + public void codeExistOrGenerateNew(String name) { + if (!hasCode(name)) { + generateCode(name); + } + } + + /** + * Generates a code for the player and returns it. + * + * @param name the name of the player to generate a code for + */ + private void generateCode(String name) { + DataSourceResult emailResult = dataSource.getEmail(name); + if (emailResult.playerExists()) { + final String email = emailResult.getValue(); + if (!Utils.isEmailEmpty(email)) { + String code = RandomStringUtils.generateNum(6); // 6 digits code + verificationCodes.put(name.toLowerCase(), code); + emailService.sendVerificationMail(name, email, code); + } + } + } + + /** + * Checks the given code against the existing one. + * + * @param name the name of the player to check + * @param code the supplied code + * @return true if the code matches, false otherwise + */ + public boolean checkCode(String name, String code) { + boolean correct = false; + if (code.equals(verificationCodes.get(name.toLowerCase()))) { + verify(name); + correct = true; + } + return correct; + } + + /** + * Add the user to the set of verified users + * + * @param name the name of the player to generate a code for + */ + public void verify(String name){ + verifiedPlayers.add(name.toLowerCase()); + } + + /** + * Remove the user from the set of verified users + * + * @param name the name of the player to generate a code for + */ + public void unverify(String name){ + verifiedPlayers.remove(name.toLowerCase()); + } + + @Override + public void reload(Settings settings) { + canSendMail = emailService.hasAllInformation(); + long countTimeout = settings.getProperty(SecuritySettings.VERIFICATION_CODE_EXPIRATION_MINUTES); + verificationCodes.setExpiration(countTimeout, TimeUnit.MINUTES); + } + + @Override + public void performCleanup() { + verificationCodes.removeExpiredEntries(); + } +} diff --git a/src/main/java/fr/xephi/authme/mail/EmailService.java b/src/main/java/fr/xephi/authme/mail/EmailService.java index cfa2f9274..c63d7d6f8 100644 --- a/src/main/java/fr/xephi/authme/mail/EmailService.java +++ b/src/main/java/fr/xephi/authme/mail/EmailService.java @@ -67,7 +67,7 @@ public class EmailService { File file = null; if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) { try { - file = generateImage(name, newPass); + file = generatePasswordImage(name, newPass); mailText = embedImageIntoEmailContent(file, email, mailText); } catch (IOException | EmailException e) { ConsoleLogger.logException( @@ -80,6 +80,33 @@ public class EmailService { return couldSendEmail; } + /** + * Sends an email to the user with the temporary verification code. + * + * @param name the name of the player + * @param mailAddress the player's email + * @param code the verification code + * @return true if email could be sent, false otherwise + */ + public boolean sendVerificationMail(String name, String mailAddress, String code) { + if (!hasAllInformation()) { + ConsoleLogger.warning("Cannot send verification email: not all email settings are complete"); + return false; + } + + HtmlEmail email; + try { + email = sendMailSsl.initializeMail(mailAddress); + } catch (EmailException e) { + ConsoleLogger.logException("Failed to create verification email with the given settings:", e); + return false; + } + + String mailText = replaceTagsForVerificationEmail(settings.getVerificationEmailMessage(), name, code, + settings.getProperty(SecuritySettings.VERIFICATION_CODE_EXPIRATION_MINUTES)); + return sendMailSsl.sendEmail(mailText, email); + } + /** * Sends an email to the user with a recovery code for the password recovery process. * @@ -102,7 +129,7 @@ public class EmailService { return sendMailSsl.sendEmail(message, htmlEmail); } - private File generateImage(String name, String newPass) throws IOException { + private File generatePasswordImage(String name, String newPass) throws IOException { ImageGenerator gen = new ImageGenerator(newPass); File file = new File(dataFolder, name + "_new_pass.jpg"); ImageIO.write(gen.generateImage(), "jpg", file); @@ -123,6 +150,14 @@ public class EmailService { .replace("", newPass); } + private String replaceTagsForVerificationEmail(String mailText, String name, String code, int minutesValid) { + return mailText + .replace("", name) + .replace("", serverName) + .replace("", code) + .replace("", String.valueOf(minutesValid)); + } + private String replaceTagsForRecoveryCodeMail(String mailText, String name, String code, int hoursValid) { return mailText .replace("", name) diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index f962ba4e1..07710a508 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -239,6 +239,36 @@ public enum MessageKey { /** An email was already sent recently. You must wait %time before you can send a new one. */ EMAIL_COOLDOWN_ERROR("email_cooldown_error", "%time"), + /** + * The command you are trying to execute is sensitive and requires a verification! + * A verification code has been sent to your email, + * run the command "/verification [code]" to verify your identity. + */ + VERIFICATION_CODE_REQUIRED("verification_code_required"), + + /** Usage: /verification <code> */ + USAGE_VERIFICATION_CODE("usage_verification_code"), + + /** Incorrect code, please type "/verification <code>" into the chat! */ + INCORRECT_VERIFICATION_CODE("incorrect_verification_code"), + + /** + * Your identity has been verified! + * You can now execute every sensitive command within the current session! + */ + VERIFICATION_CODE_VERIFIED("verification_code_verified"), + + /** + * You can already execute every sensitive command within the current session! + */ + VERIFICATION_CODE_ALREADY_VERIFIED("verification_code_already_verified"), + + /** Your code has expired! Execute another sensitive command to get a new code! */ + VERIFICATION_CODE_EXPIRED("verification_code_expired"), + + /** To verify your identity you need to link an email address with your account! */ + VERIFICATION_CODE_EMAIL_NEEDED("verification_code_email_needed"), + /** second */ SECOND("second"), diff --git a/src/main/java/fr/xephi/authme/permission/PlayerPermission.java b/src/main/java/fr/xephi/authme/permission/PlayerPermission.java index 5b3fcb569..1a89490f2 100644 --- a/src/main/java/fr/xephi/authme/permission/PlayerPermission.java +++ b/src/main/java/fr/xephi/authme/permission/PlayerPermission.java @@ -63,7 +63,12 @@ public enum PlayerPermission implements PermissionNode { /** * Permission to use to see own other accounts. */ - SEE_OWN_ACCOUNTS("authme.player.seeownaccounts"); + SEE_OWN_ACCOUNTS("authme.player.seeownaccounts"), + + /** + * Permission to use the email verification codes feature. + */ + VERIFICATION_CODE("authme.player.security.verificationcode"); /** * The permission node. diff --git a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java index 6e60bc87f..01784b9d4 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java @@ -1,5 +1,6 @@ package fr.xephi.authme.process.logout; +import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; @@ -26,6 +27,9 @@ public class AsynchronousLogout implements AsynchronousProcess { @Inject private PlayerCache playerCache; + @Inject + private VerificationCodeManager codeManager; + @Inject private SyncProcessManager syncProcessManager; @@ -52,6 +56,7 @@ public class AsynchronousLogout implements AsynchronousProcess { } playerCache.removePlayer(name); + codeManager.unverify(name); database.setUnlogged(name); database.revokeSession(name); syncProcessManager.processSyncPlayerLogout(player); diff --git a/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java b/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java index 534b754b0..58b626a5a 100644 --- a/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java @@ -1,6 +1,7 @@ package fr.xephi.authme.process.quit; import fr.xephi.authme.AuthMe; +import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.CacheDataSource; @@ -44,6 +45,9 @@ public class AsynchronousQuit implements AsynchronousProcess { @Inject private ValidationService validationService; + @Inject + private VerificationCodeManager codeManager; + AsynchronousQuit() { } @@ -80,6 +84,7 @@ public class AsynchronousQuit implements AsynchronousProcess { //always unauthenticate the player - use session only for auto logins on the same ip playerCache.removePlayer(name); + codeManager.unverify(name); //always update the database when the player quit the game (if sessions are disabled) if (wasLoggedIn) { diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index f2a722510..01bf25801 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -20,6 +20,7 @@ public class Settings extends SettingsManager { private final File pluginFolder; private String passwordEmailMessage; + private String verificationEmailMessage; private String recoveryCodeEmailMessage; /** @@ -46,6 +47,15 @@ public class Settings extends SettingsManager { return passwordEmailMessage; } + /** + * Return the text for verification emails (before sensitive commands can be used). + * + * @return The email message + */ + public String getVerificationEmailMessage() { + return verificationEmailMessage; + } + /** * Return the text to use when someone requests to receive a recovery code. * @@ -57,6 +67,7 @@ public class Settings extends SettingsManager { private void loadSettingsFromFiles() { passwordEmailMessage = readFile("email.html"); + verificationEmailMessage = readFile("verification_code_email.html"); recoveryCodeEmailMessage = readFile("recovery_code_email.html"); } diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index 737995168..f58f8b6b5 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -147,6 +147,10 @@ public final class SecuritySettings implements SettingsHolder { public static final Property USE_EMAIL_MASKING = newProperty("Security.privacy.enableEmailMasking", false); + @Comment("Minutes after which a verification code will expire") + public static final Property VERIFICATION_CODE_EXPIRATION_MINUTES = + newProperty("Security.privacy.verificationCodeExpiration", 10); + private SecuritySettings() { } diff --git a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java index 2dce3c64a..58018477c 100644 --- a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java +++ b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java @@ -10,8 +10,9 @@ public final class RandomStringUtils { private static final char[] CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static final Random RANDOM = new SecureRandom(); - private static final int HEX_MAX_INDEX = 16; + private static final int NUM_INDEX = 10; private static final int LOWER_ALPHANUMERIC_INDEX = 36; + private static final int HEX_MAX_INDEX = 16; // Utility class private RandomStringUtils() { @@ -38,6 +39,17 @@ public final class RandomStringUtils { return generateString(length, HEX_MAX_INDEX); } + /** + * Generate a random numbers string of the given length. In other words, the generated string + * contains characters only within the range [0-9]. + * + * @param length The length of the random string to generate + * @return The random numbers string + */ + public static String generateNum(int length) { + return generateString(length, NUM_INDEX); + } + /** * Generate a random string with digits and lowercase and uppercase letters. The result of this * method matches the pattern [0-9a-zA-Z]. diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index 183f279c0..1964559e1 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -96,6 +96,15 @@ usage_captcha: '&3Моля въведе цифрите/буквите от ка wrong_captcha: '&cКода е грешен, използвайте: "/captcha THE_CAPTCHA" в чата!' valid_captcha: '&2Кода е валиден!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'секунда' seconds: 'секунди' diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml index c617d8b95..9c4557bf6 100644 --- a/src/main/resources/messages/messages_br.yml +++ b/src/main/resources/messages/messages_br.yml @@ -100,6 +100,15 @@ usage_captcha: '&3Para iniciar sessão você tem que resolver um código captcha wrong_captcha: '&cCaptcha errado, por favor, escreva "/captcha THE_CAPTCHA" no chat!' valid_captcha: '&2Código Captcha resolvido corretamente!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'segundo' seconds: 'segundos' diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml index 5b60ffdaf..4e9ed8dd3 100644 --- a/src/main/resources/messages/messages_cz.yml +++ b/src/main/resources/messages/messages_cz.yml @@ -95,6 +95,15 @@ usage_captcha: '&cPoužij: /captcha ' wrong_captcha: '&cŠpatné opsana Captcha, pouzij prosim: /captcha THE_CAPTCHA' valid_captcha: '&cZadaná captcha je v pořádku!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'sekundy' seconds: 'sekund' diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml index 874b1d3c8..fc48a9f6b 100644 --- a/src/main/resources/messages/messages_de.yml +++ b/src/main/resources/messages/messages_de.yml @@ -95,6 +95,15 @@ usage_captcha: '&3Um dich einzuloggen, tippe dieses Captcha so ein: /captcha ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'Sekunde' seconds: 'Sekunden' diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index fc815663f..8879f0ca6 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -95,6 +95,15 @@ usage_captcha: '&3To login you have to solve a captcha code, please use the comm wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' valid_captcha: '&2Captcha code solved correctly!' +# Verification code +verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +usage_verification_code: '&cUsage: /verification ' +incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'second' seconds: 'seconds' diff --git a/src/main/resources/messages/messages_eo.yml b/src/main/resources/messages/messages_eo.yml index 162bc3638..938c0362b 100644 --- a/src/main/resources/messages/messages_eo.yml +++ b/src/main/resources/messages/messages_eo.yml @@ -95,6 +95,15 @@ usage_captcha: '&3Ensaluti vi devas solvi captcha kodo, bonvolu uzi la komando: wrong_captcha: '&cMalĝusta captcha, bonvolu tajpi "/captcha THE_CAPTCHA" en la babilejo!' valid_captcha: '&2Captcha kodo solvita ĝuste!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'sekundo' seconds: 'sekundoj' diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index c5f2fe071..cdef5a651 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -99,6 +99,15 @@ usage_captcha: '&cUso: /captcha ' wrong_captcha: '&cCaptcha incorrecto, por favor usa: /captcha THE_CAPTCHA' valid_captcha: '&c¡ Captcha ingresado correctamente !' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'segundo' seconds: 'segundos' diff --git a/src/main/resources/messages/messages_et.yml b/src/main/resources/messages/messages_et.yml index 044522cc2..56ea26277 100644 --- a/src/main/resources/messages/messages_et.yml +++ b/src/main/resources/messages/messages_et.yml @@ -95,6 +95,15 @@ usage_captcha: '&3Sisselogimiseks pead lahendama captcha. Tee seda kasutades kä wrong_captcha: '&cVale captcha, kirjuta "/captcha THE_CAPTCHA" chatti!' valid_captcha: '&2Captcha lahendatud!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'sekund' seconds: 'sekundit' diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml index 28ba966ab..a55565645 100644 --- a/src/main/resources/messages/messages_eu.yml +++ b/src/main/resources/messages/messages_eu.yml @@ -96,6 +96,15 @@ recovery_email: '&cPasahitza ahaztu duzu? Erabili /email recovery ' # TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' # TODO valid_captcha: '&2Captcha code solved correctly!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units # TODO second: 'second' # TODO seconds: 'seconds' diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml index 0f491ce62..31ef37aa5 100644 --- a/src/main/resources/messages/messages_fi.yml +++ b/src/main/resources/messages/messages_fi.yml @@ -96,6 +96,15 @@ usage_captcha: '&cKäyttötapa: /captcha ' wrong_captcha: '&cVäärä varmistus, käytä : /captcha THE_CAPTCHA' valid_captcha: '&cSinun varmistus onnistui.!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units # TODO second: 'second' # TODO seconds: 'seconds' diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml index 53fef0728..32e4a0ca9 100644 --- a/src/main/resources/messages/messages_fr.yml +++ b/src/main/resources/messages/messages_fr.yml @@ -101,6 +101,15 @@ usage_captcha: '&cTrop de tentatives de connexion échouées, utilisez: /captcha wrong_captcha: '&cCaptcha incorrect, écrivez de nouveau: /captcha THE_CAPTCHA' valid_captcha: '&aCaptché validé! Veuillez maintenant vous connecter.' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Unités de temps second: 'seconde' seconds: 'secondes' diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml index cdf5ddbe4..8c03f366c 100644 --- a/src/main/resources/messages/messages_gl.yml +++ b/src/main/resources/messages/messages_gl.yml @@ -96,6 +96,15 @@ usage_captcha: '&cNecesitas escribir un captcha, por favor escribe: /captcha ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units # TODO second: 'second' # TODO seconds: 'seconds' diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml index ef627aeb4..63972b468 100644 --- a/src/main/resources/messages/messages_hu.yml +++ b/src/main/resources/messages/messages_hu.yml @@ -80,7 +80,6 @@ email_added: '&2Az email címed rögzítése sikeresen megtörtént!' email_confirm: '&cKérlek, ellenőrízd az email címedet!' email_changed: '&2Az email cím cseréje sikeresen megtörtént!' email_send: '&2A jelszó visszaállításhoz szükséges emailt elküldtük! Ellenőrizd a leveleidet!' -email_exists: '&cA visszaállító emailt elküldtük! Hiba esetén újra kérheted az alábbi parancs segítségével:' email_show: '&2A jelenlegi email-ed a következő: &f%email' incomplete_email_settings: 'Hiba: nem lett beállítva az összes szükséges beállítás az email küldéshez. Vedd fel a kapcsolatot egy adminnal.' email_already_used: '&4Ez az email cím már használatban van!' @@ -88,6 +87,7 @@ email_send_failure: 'Nem sikerült elküldeni az emailt. Lépj kapcsolatba egy a show_no_email: '&2Ehhez a felhasználóhoz jelenleg még nincs email hozzárendelve.' add_email: '&3Kérlek, rendeld hozzá a felhasználódhoz az email címedet "&7/email add &3".' recovery_email: '&3Ha elfelejtetted a jelszavad, használd az "&7/email recovery &3".' +# TODO change_password_expired: 'You cannot change your password using this command anymore.' email_cooldown_error: '&cEgy emailt már kiküldtünk. Következő email küldése előtt várnod kell: %time.' # Captcha @@ -95,6 +95,15 @@ usage_captcha: '&3A bejelentkezéshez CAPTCHA szükséges, kérlek, használd a wrong_captcha: '&cHibás CAPTCHA, kérlek, írd be a következő parancsot: "&7/captcha THE_CAPTCHA&c"!' valid_captcha: '&2A CAPTCHA sikeresen feloldva!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'másodperc' seconds: 'másodperc' diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml index 79472486a..e36004344 100644 --- a/src/main/resources/messages/messages_id.yml +++ b/src/main/resources/messages/messages_id.yml @@ -96,6 +96,15 @@ usage_captcha: '&3Kamu harus menyelesaikan kode captcha untuk login, silahkan gu wrong_captcha: '&cCaptcha salah, gunakan command "/captcha THE_CAPTCHA" pada chat!' valid_captcha: '&2Kode captcha terselesaikan!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units # TODO second: 'second' # TODO seconds: 'seconds' diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml index 75c6d9e4c..7261c54cb 100644 --- a/src/main/resources/messages/messages_it.yml +++ b/src/main/resources/messages/messages_it.yml @@ -97,6 +97,15 @@ usage_captcha: '&3Per poterti autenticare devi risolvere un captcha, per favore wrong_captcha: '&cCaptcha sbagliato, per favore riprova scrivendo: "/captcha THE_CAPTCHA" in chat!' valid_captcha: '&2Il captcha inserito è valido!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Unità di tempo second: 'secondo' seconds: 'secondi' diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml index 79f93fbca..caced5202 100644 --- a/src/main/resources/messages/messages_ko.yml +++ b/src/main/resources/messages/messages_ko.yml @@ -34,7 +34,6 @@ tempban_max_logins: '&c너무 많이 로그인에 실패하여 잠시 서버에 max_reg: '&c당신은 가입할 수 있는 계정 한도를 초과했습니다 (%reg_count/%max_acc %reg_names)!' no_perm: '&4이 작업을 수행할 수 있는 권한이 없습니다!' error: '&4예기치 않은 오류가 발생했습니다, 관리자에게 알려주세요!' -unsafe_spawn: '&c마지막으로 접속한 위치가 안전하지 않습니다, 이 세계의 스폰 지점으로 텔레포트 됩니다.' kick_forvip: '&3서버가 꽉 차있을땐 VIP 플레이어만 접속이 가능합니다!' # AntiBot @@ -48,7 +47,11 @@ accounts_owned_self: '%count 개의 계정을 소유하고 있습니다:' accounts_owned_other: '플레이어 %name 는 %count 개의 계정을 소유하고 있습니다:' two_factor_create: '&2당신의 비밀 코드는 %code 입니다. %url 에서 스캔할 수 있습니다' recovery_code_sent: '비밀번호 재설정을 위한 복구 코드가 이메일로 전송되었습니다.' +# TODO: Missing tags %count recovery_code_incorrect: '복구 코드가 올바르지 않습니다! "/email recovery [이메일 주소]"를 이용하여 새로 생성하세요' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&c계정이 아직 활성화되지 않았습니다, 이메일을 확인해보세요!' usage_unreg: '&c사용법: /unregister <비밀번호>' pwd_changed: '&2비밀번호가 변경되었습니다!' @@ -82,7 +85,6 @@ email_added: '&2계정에 이메일 주소를 추가했습니다!' email_confirm: '&c이메일 주소를 확인해주세요!' email_changed: '&2이메일 주소가 변경되었습니다!' email_send: '&2복구 이메일을 보냈습니다! 메일함을 확인해보세요!' -email_exists: '&c복구 이메일을 이미 보냈습니다! 아래 명령어를 사용하여 취소하고 다시 보낼 수 있습니다:' email_show: '&2현재 이메일 주소: &f%email' incomplete_email_settings: '오류: 메일을 보내기 위해 필요한 설정이 되어 있지 않습니다. 관리자에게 알려주세요.' email_already_used: '&4이메일 주소가 이미 사용 중입니다.' @@ -90,8 +92,29 @@ email_send_failure: '이메일을 보낼 수 없습니다. 관리자에게 알 show_no_email: '&2현재 이 계정과 연결된 이메일 주소가 없습니다.' add_email: '&3다음 명령어로 계정에 이메일 주소를 추가하세요: /email add <이메일 주소> <이메일 주소 확인>' recovery_email: '&3비밀번호를 잊으셨나요? 이 명령어를 사용해보세요: /email recovery <이메일 주소>' +# TODO change_password_expired: 'You cannot change your password using this command anymore.' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3로그인 하려면 CAPTCHA 코드를 입력해야 합니다, 이 명령어를 사용하세요: /captcha ' wrong_captcha: '&c잘못된 CAPTCHA 코드 입니다, "/captcha THE_CAPTCHA"을 입력해주세요!' valid_captcha: '&2CAPTCHA 코드가 확인되었습니다!' + +# Verification Code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml index 297510879..ec67058f9 100644 --- a/src/main/resources/messages/messages_lt.yml +++ b/src/main/resources/messages/messages_lt.yml @@ -96,6 +96,15 @@ usage_captcha: '&cPanaudojimas: /captcha ' wrong_captcha: '&cNeteisinga Captcha, naudokite : /captcha THE_CAPTCHA' valid_captcha: '&cJusu captcha Teisinga!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units # TODO second: 'second' # TODO seconds: 'seconds' diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml index 18a3e4d7b..84aa01259 100644 --- a/src/main/resources/messages/messages_nl.yml +++ b/src/main/resources/messages/messages_nl.yml @@ -95,6 +95,15 @@ usage_captcha: '&3Om in te loggen moet je een captcha-code oplossen, gebruik het wrong_captcha: '&cVerkeerde captcha-code, typ alsjeblieft "/captcha THE_CAPTCHA" in de chat!' valid_captcha: '&2De captcha-code is geldig!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'seconde' seconds: 'seconden' diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml index 3ccc03cf3..5dfca5b12 100644 --- a/src/main/resources/messages/messages_pl.yml +++ b/src/main/resources/messages/messages_pl.yml @@ -95,6 +95,15 @@ usage_captcha: '&cWpisz: /captcha ' wrong_captcha: '&cZły kod, proszę wpisać: /captcha THE_CAPTCHA' valid_captcha: '&cTwój kod jest nieprawidłowy!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'sekundy' seconds: 'sekund' diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index 2ce053240..d895ca0e7 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -97,6 +97,15 @@ usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha ' wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha THE_CAPTCHA' valid_captcha: '&cO seu captcha é válido!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'segundo' seconds: 'segundos' diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml index c7ae75160..43a92170d 100644 --- a/src/main/resources/messages/messages_ro.yml +++ b/src/main/resources/messages/messages_ro.yml @@ -95,6 +95,15 @@ usage_captcha: '&3Pentru a te autentifica, te rugam sa folosesti comanda "/captc wrong_captcha: '&cCodul de verificare este gresit, te rugam foloseste comanda "/captcha THE_CAPTCHA"!' valid_captcha: '&2Codul de verificare a fost scris corect!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'secunda' seconds: 'secunde' diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index cde26ecf2..dd9e7a703 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -95,6 +95,15 @@ usage_captcha: '&3Необходимо ввести текст с каптчи. wrong_captcha: '&cНеверно! Используйте «/captcha THE_CAPTCHA».' valid_captcha: '&2Вы успешно решили каптчу!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Единицы времени second: 'с.' seconds: 'с.' diff --git a/src/main/resources/messages/messages_sk.yml b/src/main/resources/messages/messages_sk.yml index 8c0d689f4..04d335e72 100644 --- a/src/main/resources/messages/messages_sk.yml +++ b/src/main/resources/messages/messages_sk.yml @@ -102,6 +102,15 @@ usage_captcha: '&3Pre prihlásenie musíš vyriešiť captcha kód, prosím pou wrong_captcha: '&cNesprávny kód captcha, prosím napíš "/captcha THE_CAPTCHA" do chatu!' valid_captcha: '&2Správne si vyriešil captcha kód!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'sek.' seconds: 'sek.' diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index 2997ddd5b..f8ed0b780 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -96,6 +96,15 @@ usage_captcha: '&3Giris yapmak icin guvenlik kodunu komut yazarak girin "/captch wrong_captcha: '&cYanlis guvenlik kodu, kullanim sekli "/captcha THE_CAPTCHA" sohbete yazin!' valid_captcha: '&2Guvenlik kodu dogrulandi!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Zaman birimleri second: 'saniye' seconds: 'saniye' diff --git a/src/main/resources/messages/messages_uk.yml b/src/main/resources/messages/messages_uk.yml index fdd80bb47..45e0565d8 100644 --- a/src/main/resources/messages/messages_uk.yml +++ b/src/main/resources/messages/messages_uk.yml @@ -95,6 +95,15 @@ usage_captcha: '&3Для продовження доведеться ввест wrong_captcha: '&cНевірно введена капча! Спробуйте ще раз — "/captcha THE_CAPTCHA"' valid_captcha: '&2Капчу прийнято.' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units # TODO second: 'second' # TODO seconds: 'seconds' diff --git a/src/main/resources/messages/messages_vn.yml b/src/main/resources/messages/messages_vn.yml index ff16c030d..e5df437fe 100644 --- a/src/main/resources/messages/messages_vn.yml +++ b/src/main/resources/messages/messages_vn.yml @@ -96,6 +96,15 @@ usage_captcha: '&eĐể đăng nhập vui lòng hãy nhập mã Captcha, gõ l wrong_captcha: '&cSai mã captcha, Vui lòng gõ lệnh "/captcha THE_CAPTCHA"!' valid_captcha: '&2Mã captcha đã được xác nhận!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: 'giây' seconds: 'giây' diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index 01a2571c6..c769e438e 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -97,6 +97,15 @@ usage_captcha: '&8[&6玩家系统&8] &c正确用法:/captcha ' wrong_captcha: '&8[&6玩家系统&8] &c错误的验证码,请输入:“/captcha THE_CAPTCHA”' valid_captcha: '&8[&6玩家系统&8] &c你的验证码是有效的!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # 时间单位 second: '秒' seconds: '秒' diff --git a/src/main/resources/messages/messages_zhhk.yml b/src/main/resources/messages/messages_zhhk.yml index 0e27f3a71..71d48e7e0 100644 --- a/src/main/resources/messages/messages_zhhk.yml +++ b/src/main/resources/messages/messages_zhhk.yml @@ -99,6 +99,15 @@ usage_captcha: '&8[&6用戶系統&8] &f用法:《 /captcha 》' wrong_captcha: '&8[&6用戶系統&8] &c你所輸入的驗證碼無效,請使用 《 /captcha THE_CAPTCHA 》 再次輸入。' valid_captcha: '&8[&6用戶系統&8] &c你所輸入的驗證碼無效 !' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units second: '秒' seconds: '秒' diff --git a/src/main/resources/messages/messages_zhmc.yml b/src/main/resources/messages/messages_zhmc.yml index c4d8897eb..56ae46463 100644 --- a/src/main/resources/messages/messages_zhmc.yml +++ b/src/main/resources/messages/messages_zhmc.yml @@ -96,6 +96,15 @@ usage_captcha: '&3T要登錄您必須使用captcha驗證碼,請使用命令: "/c wrong_captcha: '&c驗證碼錯誤!請按T在聊天中輸入 "/captcha THE_CAPTCHA"' valid_captcha: '&2驗證碼正確!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units # TODO second: 'second' # TODO seconds: 'seconds' diff --git a/src/main/resources/messages/messages_zhtw.yml b/src/main/resources/messages/messages_zhtw.yml index 747357678..3df7d1b28 100644 --- a/src/main/resources/messages/messages_zhtw.yml +++ b/src/main/resources/messages/messages_zhtw.yml @@ -100,6 +100,15 @@ usage_captcha: '&b【AuthMe】&6請用 &c"/captcha " &6來輸入你 wrong_captcha: '&b【AuthMe】&6錯誤的驗證碼,請使用 《 /captcha THE_CAPTCHA 》 再試一次吧。' valid_captcha: '&b【AuthMe】&6驗證碼無效!' +# Verification code +# TODO verification_code_required: '&3This command is sensitive and requires an email verification! Check your inbox and follow the email''s instructions.' +# TODO usage_verification_code: '&cUsage: /verification ' +# TODO incorrect_verification_code: '&cIncorrect code, please type "/verification " into the chat, using the code you received by email' +# TODO verification_code_verified: '&2Your identity has been verified! You can now execute all commands within the current session!' +# TODO verification_code_already_verified: '&2You can already execute every sensitive command within the current session!' +# TODO verification_code_expired: '&3Your code has expired! Execute an other sensitive command to get a new code!' +# TODO verification_code_email_needed: '&3To verify your identity you need to link an email address with your account!!' + # Time units # TODO second: 'second' # TODO seconds: 'seconds' diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9f24120b9..9014476f6 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -47,8 +47,11 @@ commands: - changepass - cp captcha: - description: Captcha Command + description: Captcha command usage: /captcha + verification: + description: Verification command + usage: /verification permissions: authme.admin.*: description: Gives access to all admin commands @@ -224,6 +227,7 @@ permissions: authme.player.login: true authme.player.logout: true authme.player.register: true + authme.player.security.verificationcode: true authme.player.seeownaccounts: true authme.player.unregister: true authme.player.canbeforced: @@ -263,6 +267,9 @@ permissions: authme.player.register: description: Command permission to register. default: true + authme.player.security.verificationcode: + description: Permission to use the email verification codes feature. + default: true authme.player.seeownaccounts: description: Permission to use to see own other accounts. default: true diff --git a/src/main/resources/verification_code_email.html b/src/main/resources/verification_code_email.html new file mode 100644 index 000000000..c12ae946c --- /dev/null +++ b/src/main/resources/verification_code_email.html @@ -0,0 +1,20 @@ +

+ Dear , +

+ +

+ This is your temporary verification code for the server : +

+

+ +

+ +

+ This code will be valid for the next mins!
+ Use the command + /verification + to complete the verification process. +

+

+ See you on ! +

diff --git a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java index 8fb2dcad9..2b8215baa 100644 --- a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java @@ -44,7 +44,7 @@ public class CommandInitializerTest { // It obviously doesn't make sense to test much of the concrete data // that is being initialized; we just want to guarantee with this test // that data is indeed being initialized and we take a few "probes" - assertThat(commands, hasSize(8)); + assertThat(commands, hasSize(9)); assertThat(commandsIncludeLabel(commands, "authme"), equalTo(true)); assertThat(commandsIncludeLabel(commands, "register"), equalTo(true)); assertThat(commandsIncludeLabel(commands, "help"), equalTo(false)); diff --git a/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java index cee384041..4c5488cb8 100644 --- a/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.executable.changepassword; +import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; @@ -40,11 +41,14 @@ public class ChangePasswordCommandTest { private ChangePasswordCommand command; @Mock - private CommonService commandService; + private CommonService commonService; @Mock private PlayerCache playerCache; + @Mock + private VerificationCodeManager codeManager; + @Mock private ValidationService validationService; @@ -72,23 +76,24 @@ public class ChangePasswordCommandTest { command.executeCommand(sender, Arrays.asList("pass", "pass")); // then - verify(commandService).send(sender, MessageKey.NOT_LOGGED_IN); + verify(commonService).send(sender, MessageKey.NOT_LOGGED_IN); } @Test public void shouldRejectInvalidPassword() { // given - CommandSender sender = initPlayerWithName("abc12", true); + Player sender = initPlayerWithName("abc12", true); String password = "newPW"; - given(validationService.validatePassword(password, "abc12")) - .willReturn(new ValidationResult(MessageKey.INVALID_PASSWORD_LENGTH)); + given(validationService.validatePassword(password, "abc12")).willReturn(new ValidationResult(MessageKey.INVALID_PASSWORD_LENGTH)); + given(codeManager.isVerificationRequired(sender)).willReturn(false); // when command.executeCommand(sender, Arrays.asList("tester", password)); // then verify(validationService).validatePassword(password, "abc12"); - verify(commandService).send(sender, MessageKey.INVALID_PASSWORD_LENGTH, new String[0]); + verify(commonService).send(sender, MessageKey.INVALID_PASSWORD_LENGTH, new String[0]); + verify(codeManager).isVerificationRequired(sender); } @Test @@ -98,14 +103,16 @@ public class ChangePasswordCommandTest { String newPass = "abc123"; Player player = initPlayerWithName("parker", true); given(validationService.validatePassword("abc123", "parker")).willReturn(new ValidationResult()); + given(codeManager.isVerificationRequired(player)).willReturn(false); // when command.executeCommand(player, Arrays.asList(oldPass, newPass)); // then verify(validationService).validatePassword(newPass, "parker"); - verify(commandService, never()).send(eq(player), any(MessageKey.class)); + verify(commonService, never()).send(eq(player), any(MessageKey.class)); verify(management).performPasswordChange(player, oldPass, newPass); + verify(codeManager).isVerificationRequired(player); } @Test diff --git a/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java index b4cb80a96..c990c81ef 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java @@ -1,7 +1,9 @@ package fr.xephi.authme.command.executable.email; +import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; +import fr.xephi.authme.service.CommonService; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -16,9 +18,11 @@ import java.util.Collections; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; /** * Test for {@link ChangeEmailCommand}. @@ -32,6 +36,12 @@ public class ChangeEmailCommandTest { @Mock private Management management; + @Mock + private CommonService commonService; + + @Mock + private VerificationCodeManager codeManager; + @Test public void shouldRejectNonPlayerSender() { @@ -45,16 +55,34 @@ public class ChangeEmailCommandTest { verifyZeroInteractions(management); } + @Test + public void shouldStopIfVerificationIsRequired() { + // given + String name = "Testeroni"; + Player player = initPlayerWithName(name); + given(codeManager.isVerificationRequired(player)).willReturn(true); + + // when + command.executeCommand(player, Arrays.asList("mail@example.org", "otherMail@example.com")); + + // then + verify(codeManager).codeExistOrGenerateNew(name); + verify(commonService).send(player, MessageKey.VERIFICATION_CODE_REQUIRED); + verifyZeroInteractions(management); + } + @Test public void shouldForwardData() { // given - Player sender = mock(Player.class); + Player sender = initPlayerWithName("AmATest"); + given(codeManager.isVerificationRequired(sender)).willReturn(false); // when command.executeCommand(sender, Arrays.asList("new.mail@example.org", "old_mail@example.org")); // then verify(management).performChangeEmail(sender, "new.mail@example.org", "old_mail@example.org"); + verify(codeManager).isVerificationRequired(sender); } @Test @@ -62,4 +90,10 @@ public class ChangeEmailCommandTest { // given / when / then assertThat(command.getArgumentsMismatchMessage(), equalTo(MessageKey.USAGE_CHANGE_EMAIL)); } + + private Player initPlayerWithName(String name) { + Player player = mock(Player.class); + when(player.getName()).thenReturn(name); + return player; + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java index 2adc66d51..5ee573a2b 100644 --- a/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.executable.unregister; +import fr.xephi.authme.data.VerificationCodeManager; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; @@ -17,11 +18,11 @@ import java.util.Collections; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; -import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; /** * Test for {@link UnregisterCommand}. @@ -36,11 +37,14 @@ public class UnregisterCommandTest { private Management management; @Mock - private CommonService commandService; + private CommonService commonService; @Mock private PlayerCache playerCache; + @Mock + private VerificationCodeManager codeManager; + @Test public void shouldCatchUnauthenticatedUser() { // given @@ -55,7 +59,26 @@ public class UnregisterCommandTest { // then verify(playerCache).isAuthenticated(name); - verify(commandService).send(player, MessageKey.NOT_LOGGED_IN); + verify(commonService).send(player, MessageKey.NOT_LOGGED_IN); + verifyZeroInteractions(management); + } + + @Test + public void shouldStopForMissingVerificationCode() { + // given + String name = "asldjf"; + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + given(playerCache.isAuthenticated(name)).willReturn(true); + given(codeManager.isVerificationRequired(player)).willReturn(true); + + // when + command.executeCommand(player, Collections.singletonList("blergh")); + + // then + verify(playerCache).isAuthenticated(name); + verify(codeManager).codeExistOrGenerateNew(name); + verify(commonService).send(player, MessageKey.VERIFICATION_CODE_REQUIRED); verifyZeroInteractions(management); } @@ -67,6 +90,7 @@ public class UnregisterCommandTest { Player player = mock(Player.class); given(player.getName()).willReturn(name); given(playerCache.isAuthenticated(name)).willReturn(true); + given(codeManager.isVerificationRequired(player)).willReturn(false); // when command.executeCommand(player, Collections.singletonList(password)); @@ -74,6 +98,7 @@ public class UnregisterCommandTest { // then verify(playerCache).isAuthenticated(name); verify(management).performUnregister(player, password); + verify(codeManager).isVerificationRequired(player); } @Test diff --git a/src/test/java/fr/xephi/authme/command/executable/verification/VerificationCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/verification/VerificationCommandTest.java new file mode 100644 index 000000000..53ede2d89 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/verification/VerificationCommandTest.java @@ -0,0 +1,161 @@ +package fr.xephi.authme.command.executable.verification; + +import fr.xephi.authme.data.VerificationCodeManager; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.service.CommonService; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link VerificationCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class VerificationCommandTest { + + @InjectMocks + private VerificationCommand command; + + @Mock + private CommonService commonService; + + @Mock + private VerificationCodeManager codeManager; + + @Test + public void shouldDetectIfMailHasASetup() { + // given + String name = "Alligator"; + Player player = mockPlayerWithName(name); + given(codeManager.canSendMail()).willReturn(false); + + // when + command.executeCommand(player, Collections.singletonList("code")); + + // then + verify(commonService).send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); + } + + @Test + public void shouldRequireAndAcceptCode() { + // given + String name = "Duck"; + String code = "123932"; + Player player = mockPlayerWithName(name); + given(codeManager.canSendMail()).willReturn(true); + given(codeManager.isVerificationRequired(player)).willReturn(true); + given(codeManager.isCodeRequired(name)).willReturn(true); + given(codeManager.checkCode(name, code)).willReturn(true); + + // when + command.executeCommand(player, Collections.singletonList(code)); + + // then + verify(codeManager).isVerificationRequired(player); + verify(codeManager).isCodeRequired(name); + verify(codeManager).checkCode(name, code); + verify(commonService).send(player, MessageKey.VERIFICATION_CODE_VERIFIED); + } + + @Test + public void shouldRejectCode() { + // given + String name = "Spider"; + String code = "98345222"; // more than 6 digits + Player player = mockPlayerWithName(name); + given(codeManager.canSendMail()).willReturn(true); + given(codeManager.isVerificationRequired(player)).willReturn(true); + given(codeManager.isCodeRequired(name)).willReturn(true); + given(codeManager.checkCode(name, code)).willReturn(false); + + // when + command.executeCommand(player, Collections.singletonList(code)); + + // then + verify(codeManager).isVerificationRequired(player); + verify(codeManager).isCodeRequired(name); + verify(codeManager).checkCode(name, code); + verify(commonService).send(player, MessageKey.INCORRECT_VERIFICATION_CODE); + } + + @Test + public void shouldRejectVerificationDueToExpiration() { + // given + String name = "Dog"; + String code = "131552"; + Player player = mockPlayerWithName(name); + given(codeManager.canSendMail()).willReturn(true); + given(codeManager.isVerificationRequired(player)).willReturn(true); + given(codeManager.isCodeRequired(name)).willReturn(false); + + // when + command.executeCommand(player, Collections.singletonList(code)); + + // then + verify(codeManager).isVerificationRequired(player); + verify(codeManager).isCodeRequired(name); + verify(commonService).send(player, MessageKey.VERIFICATION_CODE_EXPIRED); + } + + @Test + public void shouldRejectVerificationDueToVerifiedIdentity() { + // given + String name = "Cow"; + String code = "973583"; + Player player = mockPlayerWithName(name); + given(codeManager.canSendMail()).willReturn(true); + given(codeManager.isVerificationRequired(player)).willReturn(false); + given(codeManager.hasEmail(name)).willReturn(true); + + // when + command.executeCommand(player, Collections.singletonList(code)); + + // then + verify(codeManager).isVerificationRequired(player); + verify(codeManager).hasEmail(name); + verify(commonService).send(player, MessageKey.VERIFICATION_CODE_ALREADY_VERIFIED); + } + + @Test + public void shouldRejectVerificationDueToUndefinedEmail() { + // given + String name = "Frog"; + String code = "774543"; + Player player = mockPlayerWithName(name); + given(codeManager.canSendMail()).willReturn(true); + given(codeManager.isVerificationRequired(player)).willReturn(false); + given(codeManager.hasEmail(name)).willReturn(false); + + // when + command.executeCommand(player, Collections.singletonList(code)); + + // then + verify(codeManager).isVerificationRequired(player); + verify(codeManager).hasEmail(name); + verify(commonService).send(player, MessageKey.VERIFICATION_CODE_EMAIL_NEEDED); + verify(commonService).send(player, MessageKey.ADD_EMAIL_MESSAGE); + } + + @Test + public void shouldDefineArgumentMismatchMessage() { + // given / when / then + assertThat(command.getArgumentsMismatchMessage(), equalTo(MessageKey.USAGE_VERIFICATION_CODE)); + } + + private static Player mockPlayerWithName(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } +} diff --git a/src/test/java/fr/xephi/authme/data/VerificationCodeManagerTest.java b/src/test/java/fr/xephi/authme/data/VerificationCodeManagerTest.java new file mode 100644 index 000000000..b99d6a373 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/VerificationCodeManagerTest.java @@ -0,0 +1,171 @@ +package fr.xephi.authme.data; + +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.datasource.DataSourceResult; +import fr.xephi.authme.mail.EmailService; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerPermission; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link VerificationCodeManager}. + */ +@RunWith(MockitoJUnitRunner.class) +public class VerificationCodeManagerTest { + + @Mock + private Settings settings; + + @Mock + private DataSource dataSource; + + @Mock + private EmailService emailService; + + @Mock + private PermissionsManager permissionsManager; + + @Before + public void setUpBasicBehavior() { + given(emailService.hasAllInformation()).willReturn(true); + given(settings.getProperty(SecuritySettings.VERIFICATION_CODE_EXPIRATION_MINUTES)).willReturn(1); + } + + @Test + public void shouldRequireVerification() { + // given + String name1 = "ILoveTests"; + Player player1 = mockPlayerWithName(name1); + given(dataSource.getEmail(name1)).willReturn(DataSourceResult.of("ilovetests@test.com")); + given(permissionsManager.hasPermission(player1, PlayerPermission.VERIFICATION_CODE)).willReturn(true); + String name2 = "StillLovingTests"; + Player player2 = mockPlayerWithName(name2); + + VerificationCodeManager codeManager = createCodeManager(); + codeManager.verify(name2); + + // when + boolean test1 = codeManager.isVerificationRequired(player1); + boolean test2 = codeManager.isVerificationRequired(player2); + + // then + assertThat(test1, equalTo(true)); + assertThat(test2, equalTo(false)); + verify(dataSource, only()).getEmail(name1); + verify(permissionsManager, only()).hasPermission(player1, PlayerPermission.VERIFICATION_CODE); + } + + @Test + public void shouldNotRequireVerificationIfEmailSettingsAreIncomplete() { + // given + given(emailService.hasAllInformation()).willReturn(false); + VerificationCodeManager codeManager = createCodeManager(); + Player player = mock(Player.class); + + // when + boolean result = codeManager.isVerificationRequired(player); + + // then + assertThat(result, equalTo(false)); + verifyZeroInteractions(permissionsManager, dataSource); + } + + @Test + public void shouldNotRequireVerificationForMissingPermission() { + // given + Player player = mockPlayerWithName("ILoveTests"); + given(permissionsManager.hasPermission(player, PlayerPermission.VERIFICATION_CODE)).willReturn(false); + VerificationCodeManager codeManager = createCodeManager(); + + // when + boolean result = codeManager.isVerificationRequired(player); + + // then + assertThat(result, equalTo(false)); + verify(permissionsManager).hasPermission(player, PlayerPermission.VERIFICATION_CODE); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldGenerateCode() { + // given + String player = "ILoveTests"; + String email = "ilovetests@test.com"; + given(dataSource.getEmail(player)).willReturn(DataSourceResult.of(email)); + VerificationCodeManager codeManager1 = createCodeManager(); + VerificationCodeManager codeManager2 = createCodeManager(); + codeManager2.codeExistOrGenerateNew(player); + + // when + boolean test1 = codeManager1.hasCode(player); + boolean test2 = codeManager2.hasCode(player); + + // then + assertThat(test1, equalTo(false)); + assertThat(test2, equalTo(true)); + } + + @Test + public void shouldRequireCode() { + // given + String player = "ILoveTests"; + String email = "ilovetests@test.com"; + given(dataSource.getEmail(player)).willReturn(DataSourceResult.of(email)); + VerificationCodeManager codeManager1 = createCodeManager(); + VerificationCodeManager codeManager2 = createCodeManager(); + codeManager2.codeExistOrGenerateNew(player); + + // when + boolean test1 = codeManager1.isCodeRequired(player); + boolean test2 = codeManager2.isCodeRequired(player); + + // then + assertThat(test1, equalTo(false)); + assertThat(test2, equalTo(true)); + } + + @Test + public void shouldVerifyCode() { + // given + String player = "ILoveTests"; + String code = "193458"; + String email = "ilovetests@test.com"; + given(dataSource.getEmail(player)).willReturn(DataSourceResult.of(email)); + VerificationCodeManager codeManager1 = createCodeManager(); + VerificationCodeManager codeManager2 = createCodeManager(); + codeManager1.codeExistOrGenerateNew(player); + + // when + boolean test1 = codeManager1.checkCode(player, code); + boolean test2 = codeManager2.checkCode(player, code); + + // then + assertThat(test1, equalTo(false)); + assertThat(test2, equalTo(false)); + } + + private VerificationCodeManager createCodeManager() { + return new VerificationCodeManager(settings, dataSource, emailService, permissionsManager); + } + + private static Player mockPlayerWithName(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } +} diff --git a/src/test/java/fr/xephi/authme/settings/SettingsTest.java b/src/test/java/fr/xephi/authme/settings/SettingsTest.java index f7d2604c2..9482c09a3 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsTest.java @@ -77,6 +77,24 @@ public class SettingsTest { assertThat(result, equalTo(emailMessage)); } + @Test + public void shouldLoadVerificationMessage() throws IOException { + // given + String emailMessage = "Please verify your identity with ."; + File emailFile = new File(testPluginFolder, "verification_code_email.html"); + createFile(emailFile); + Files.write(emailFile.toPath(), emailMessage.getBytes()); + + PropertyResource resource = mock(PropertyResource.class); + Settings settings = new Settings(testPluginFolder, resource, null, CONFIG_DATA); + + // when + String result = settings.getVerificationEmailMessage(); + + // then + assertThat(result, equalTo(emailMessage)); + } + private static void createFile(File file) { try { file.getParentFile().mkdirs(); diff --git a/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java b/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java index 556a604c3..1af9851a6 100644 --- a/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java @@ -61,6 +61,22 @@ public class RandomStringUtilsTest { } } + @Test + public void shouldGenerateRandomNumberString() { + // given + int[] lengths = {0, 1, 18, 147, 1833}; + Pattern badChars = Pattern.compile(".*[^0-9].*"); + + // when / then + for (int length : lengths) { + String result = RandomStringUtils.generateNum(length); + assertThat("Result '" + result + "' should have length " + length, + result.length(), equalTo(length)); + assertThat("Result '" + result + "' should only have characters 0-9", + badChars.matcher(result).matches(), equalTo(false)); + } + } + @Test(expected = IllegalArgumentException.class) public void shouldThrowForInvalidLength() { // given/when