mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-12-25 10:07:35 +01:00
#1141 Move TOTP code during login as separate step: /2fa code
Rough version. - Introduces a limbo player state on the LimboPlayer, allowing us to add further mandatory actions between successful (password) authentication and the ability to play on the server
This commit is contained in:
parent
af6bee59bd
commit
495cfc69a9
@ -44,6 +44,7 @@ import fr.xephi.authme.command.executable.totp.AddTotpCommand;
|
|||||||
import fr.xephi.authme.command.executable.totp.ConfirmTotpCommand;
|
import fr.xephi.authme.command.executable.totp.ConfirmTotpCommand;
|
||||||
import fr.xephi.authme.command.executable.totp.RemoveTotpCommand;
|
import fr.xephi.authme.command.executable.totp.RemoveTotpCommand;
|
||||||
import fr.xephi.authme.command.executable.totp.TotpBaseCommand;
|
import fr.xephi.authme.command.executable.totp.TotpBaseCommand;
|
||||||
|
import fr.xephi.authme.command.executable.totp.TotpCodeCommand;
|
||||||
import fr.xephi.authme.command.executable.unregister.UnregisterCommand;
|
import fr.xephi.authme.command.executable.unregister.UnregisterCommand;
|
||||||
import fr.xephi.authme.command.executable.verification.VerificationCommand;
|
import fr.xephi.authme.command.executable.verification.VerificationCommand;
|
||||||
import fr.xephi.authme.permission.AdminPermission;
|
import fr.xephi.authme.permission.AdminPermission;
|
||||||
@ -92,7 +93,6 @@ public class CommandInitializer {
|
|||||||
.description("Login command")
|
.description("Login command")
|
||||||
.detailedDescription("Command to log in using AuthMeReloaded.")
|
.detailedDescription("Command to log in using AuthMeReloaded.")
|
||||||
.withArgument("password", "Login password", MANDATORY)
|
.withArgument("password", "Login password", MANDATORY)
|
||||||
.withArgument("2facode", "TOTP code", OPTIONAL)
|
|
||||||
.permission(PlayerPermission.LOGIN)
|
.permission(PlayerPermission.LOGIN)
|
||||||
.executableCommand(LoginCommand.class)
|
.executableCommand(LoginCommand.class)
|
||||||
.register();
|
.register();
|
||||||
@ -561,6 +561,15 @@ public class CommandInitializer {
|
|||||||
.executableCommand(TotpBaseCommand.class)
|
.executableCommand(TotpBaseCommand.class)
|
||||||
.register();
|
.register();
|
||||||
|
|
||||||
|
// Register the base totp code
|
||||||
|
CommandDescription.builder()
|
||||||
|
.parent(null)
|
||||||
|
.labels("code", "c")
|
||||||
|
.description("Command for logging in")
|
||||||
|
.detailedDescription("Processes the two-factor authentication code during login.")
|
||||||
|
.executableCommand(TotpCodeCommand.class)
|
||||||
|
.register();
|
||||||
|
|
||||||
// Register totp add
|
// Register totp add
|
||||||
CommandDescription.builder()
|
CommandDescription.builder()
|
||||||
.parent(totpBase)
|
.parent(totpBase)
|
||||||
|
@ -4,6 +4,7 @@ import fr.xephi.authme.command.PlayerCommand;
|
|||||||
import fr.xephi.authme.data.auth.PlayerCache;
|
import fr.xephi.authme.data.auth.PlayerCache;
|
||||||
import fr.xephi.authme.data.captcha.LoginCaptchaManager;
|
import fr.xephi.authme.data.captcha.LoginCaptchaManager;
|
||||||
import fr.xephi.authme.data.captcha.RegistrationCaptchaManager;
|
import fr.xephi.authme.data.captcha.RegistrationCaptchaManager;
|
||||||
|
import fr.xephi.authme.data.limbo.LimboMessageType;
|
||||||
import fr.xephi.authme.data.limbo.LimboService;
|
import fr.xephi.authme.data.limbo.LimboService;
|
||||||
import fr.xephi.authme.datasource.DataSource;
|
import fr.xephi.authme.datasource.DataSource;
|
||||||
import fr.xephi.authme.message.MessageKey;
|
import fr.xephi.authme.message.MessageKey;
|
||||||
@ -80,6 +81,6 @@ public class CaptchaCommand extends PlayerCommand {
|
|||||||
String newCode = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(player.getName());
|
String newCode = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(player.getName());
|
||||||
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
|
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
|
||||||
}
|
}
|
||||||
limboService.resetMessageTask(player, false);
|
limboService.resetMessageTask(player, LimboMessageType.REGISTER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,7 @@ public class LoginCommand extends PlayerCommand {
|
|||||||
@Override
|
@Override
|
||||||
public void runCommand(Player player, List<String> arguments) {
|
public void runCommand(Player player, List<String> arguments) {
|
||||||
String password = arguments.get(0);
|
String password = arguments.get(0);
|
||||||
String totpCode = arguments.size() > 1 ? arguments.get(1) : null;
|
management.performLogin(player, password);
|
||||||
management.performLogin(player, password, totpCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package fr.xephi.authme.command.executable.totp;
|
||||||
|
|
||||||
|
import fr.xephi.authme.command.PlayerCommand;
|
||||||
|
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||||
|
import fr.xephi.authme.data.auth.PlayerCache;
|
||||||
|
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||||
|
import fr.xephi.authme.data.limbo.LimboPlayerState;
|
||||||
|
import fr.xephi.authme.data.limbo.LimboService;
|
||||||
|
import fr.xephi.authme.datasource.DataSource;
|
||||||
|
import fr.xephi.authme.message.MessageKey;
|
||||||
|
import fr.xephi.authme.message.Messages;
|
||||||
|
import fr.xephi.authme.process.login.AsynchronousLogin;
|
||||||
|
import fr.xephi.authme.security.totp.TotpService;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TOTP code command for processing the 2FA code during the login process.
|
||||||
|
*/
|
||||||
|
public class TotpCodeCommand extends PlayerCommand {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private LimboService limboService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private PlayerCache playerCache;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Messages messages;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private TotpService totpService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private AsynchronousLogin asynchronousLogin;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runCommand(Player player, List<String> arguments) {
|
||||||
|
if (playerCache.isAuthenticated(player.getName())) {
|
||||||
|
messages.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerAuth auth = dataSource.getAuth(player.getName());
|
||||||
|
if (auth == null) {
|
||||||
|
messages.send(player, MessageKey.REGISTER_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LimboPlayer limbo = limboService.getLimboPlayer(player.getName());
|
||||||
|
if (limbo.getState() == LimboPlayerState.TOTP_REQUIRED) {
|
||||||
|
processCode(player, limbo, auth, arguments.get(0));
|
||||||
|
} else {
|
||||||
|
messages.send(player, MessageKey.LOGIN_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processCode(Player player, LimboPlayer limbo, PlayerAuth auth, String inputCode) {
|
||||||
|
boolean isCodeValid = totpService.verifyCode(auth, inputCode);
|
||||||
|
if (isCodeValid) {
|
||||||
|
limbo.setState(LimboPlayerState.FINISHED);
|
||||||
|
asynchronousLogin.performLogin(player, auth);
|
||||||
|
} else {
|
||||||
|
player.sendMessage("Invalid code!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package fr.xephi.authme.data.limbo;
|
||||||
|
|
||||||
|
public enum LimboMessageType {
|
||||||
|
|
||||||
|
REGISTER,
|
||||||
|
|
||||||
|
LOG_IN,
|
||||||
|
|
||||||
|
TOTP_CODE
|
||||||
|
|
||||||
|
}
|
@ -23,6 +23,7 @@ public class LimboPlayer {
|
|||||||
private final float flySpeed;
|
private final float flySpeed;
|
||||||
private BukkitTask timeoutTask = null;
|
private BukkitTask timeoutTask = null;
|
||||||
private MessageTask messageTask = null;
|
private MessageTask messageTask = null;
|
||||||
|
private LimboPlayerState state = LimboPlayerState.PASSWORD_REQUIRED;
|
||||||
|
|
||||||
public LimboPlayer(Location loc, boolean operator, Collection<String> groups, boolean fly, float walkSpeed,
|
public LimboPlayer(Location loc, boolean operator, Collection<String> groups, boolean fly, float walkSpeed,
|
||||||
float flySpeed) {
|
float flySpeed) {
|
||||||
@ -124,4 +125,12 @@ public class LimboPlayer {
|
|||||||
setMessageTask(null);
|
setMessageTask(null);
|
||||||
setTimeoutTask(null);
|
setTimeoutTask(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LimboPlayerState getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(LimboPlayerState state) {
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package fr.xephi.authme.data.limbo;
|
||||||
|
|
||||||
|
public enum LimboPlayerState {
|
||||||
|
|
||||||
|
PASSWORD_REQUIRED,
|
||||||
|
|
||||||
|
TOTP_REQUIRED,
|
||||||
|
|
||||||
|
FINISHED
|
||||||
|
|
||||||
|
}
|
@ -45,11 +45,11 @@ class LimboPlayerTaskManager {
|
|||||||
*
|
*
|
||||||
* @param player the player
|
* @param player the player
|
||||||
* @param limbo the associated limbo player of the player
|
* @param limbo the associated limbo player of the player
|
||||||
* @param isRegistered whether the player is registered or not (needed to determine the message in the task)
|
* @param messageType message type
|
||||||
*/
|
*/
|
||||||
void registerMessageTask(Player player, LimboPlayer limbo, boolean isRegistered) {
|
void registerMessageTask(Player player, LimboPlayer limbo, LimboMessageType messageType) {
|
||||||
int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL);
|
int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL);
|
||||||
MessageResult result = getMessageKey(player.getName(), isRegistered);
|
MessageResult result = getMessageKey(player.getName(), messageType);
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
String[] joinMessage = messages.retrieveSingle(player, result.messageKey, result.args).split("\n");
|
String[] joinMessage = messages.retrieveSingle(player, result.messageKey, result.args).split("\n");
|
||||||
MessageTask messageTask = new MessageTask(player, joinMessage);
|
MessageTask messageTask = new MessageTask(player, joinMessage);
|
||||||
@ -89,12 +89,14 @@ class LimboPlayerTaskManager {
|
|||||||
* Returns the appropriate message key according to the registration status and settings.
|
* Returns the appropriate message key according to the registration status and settings.
|
||||||
*
|
*
|
||||||
* @param name the player's name
|
* @param name the player's name
|
||||||
* @param isRegistered whether or not the username is registered
|
* @param messageType the message to show
|
||||||
* @return the message key to display to the user
|
* @return the message key to display to the user
|
||||||
*/
|
*/
|
||||||
private MessageResult getMessageKey(String name, boolean isRegistered) {
|
private MessageResult getMessageKey(String name, LimboMessageType messageType) {
|
||||||
if (isRegistered) {
|
if (messageType == LimboMessageType.LOG_IN) {
|
||||||
return new MessageResult(MessageKey.LOGIN_MESSAGE);
|
return new MessageResult(MessageKey.LOGIN_MESSAGE);
|
||||||
|
} else if (messageType == LimboMessageType.TOTP_CODE) {
|
||||||
|
return new MessageResult(MessageKey.TWO_FACTOR_CODE_REQUIRED);
|
||||||
} else if (registrationCaptchaManager.isCaptchaRequired(name)) {
|
} else if (registrationCaptchaManager.isCaptchaRequired(name)) {
|
||||||
final String captchaCode = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(name);
|
final String captchaCode = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(name);
|
||||||
return new MessageResult(MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, captchaCode);
|
return new MessageResult(MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, captchaCode);
|
||||||
|
@ -69,7 +69,8 @@ public class LimboService {
|
|||||||
LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
|
LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
|
||||||
limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered, location), limboPlayer);
|
limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered, location), limboPlayer);
|
||||||
|
|
||||||
taskManager.registerMessageTask(player, limboPlayer, isRegistered);
|
taskManager.registerMessageTask(player, limboPlayer,
|
||||||
|
isRegistered ? LimboMessageType.LOG_IN : LimboMessageType.REGISTER);
|
||||||
taskManager.registerTimeoutTask(player, limboPlayer);
|
taskManager.registerTimeoutTask(player, limboPlayer);
|
||||||
helper.revokeLimboStates(player);
|
helper.revokeLimboStates(player);
|
||||||
authGroupHandler.setGroup(player, limboPlayer,
|
authGroupHandler.setGroup(player, limboPlayer,
|
||||||
@ -134,7 +135,7 @@ public class LimboService {
|
|||||||
Optional<LimboPlayer> limboPlayer = getLimboOrLogError(player, "reset tasks");
|
Optional<LimboPlayer> limboPlayer = getLimboOrLogError(player, "reset tasks");
|
||||||
limboPlayer.ifPresent(limbo -> {
|
limboPlayer.ifPresent(limbo -> {
|
||||||
taskManager.registerTimeoutTask(player, limbo);
|
taskManager.registerTimeoutTask(player, limbo);
|
||||||
taskManager.registerMessageTask(player, limbo, true);
|
taskManager.registerMessageTask(player, limbo, LimboMessageType.LOG_IN);
|
||||||
});
|
});
|
||||||
authGroupHandler.setGroup(player, limboPlayer.orElse(null), AuthGroupType.REGISTERED_UNAUTHENTICATED);
|
authGroupHandler.setGroup(player, limboPlayer.orElse(null), AuthGroupType.REGISTERED_UNAUTHENTICATED);
|
||||||
}
|
}
|
||||||
@ -143,11 +144,11 @@ public class LimboService {
|
|||||||
* Resets the message task associated with the player's LimboPlayer.
|
* Resets the message task associated with the player's LimboPlayer.
|
||||||
*
|
*
|
||||||
* @param player the player to set a new message task for
|
* @param player the player to set a new message task for
|
||||||
* @param isRegistered whether or not the player is registered
|
* @param messageType the message to show for the limbo player
|
||||||
*/
|
*/
|
||||||
public void resetMessageTask(Player player, boolean isRegistered) {
|
public void resetMessageTask(Player player, LimboMessageType messageType) {
|
||||||
getLimboOrLogError(player, "reset message task")
|
getLimboOrLogError(player, "reset message task")
|
||||||
.ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, isRegistered));
|
.ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, messageType));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,6 +197,9 @@ public enum MessageKey {
|
|||||||
/** Your secret code is %code. You can scan it from here %url */
|
/** Your secret code is %code. You can scan it from here %url */
|
||||||
TWO_FACTOR_CREATE("misc.two_factor_create", "%code", "%url"),
|
TWO_FACTOR_CREATE("misc.two_factor_create", "%code", "%url"),
|
||||||
|
|
||||||
|
/** Please submit your two-factor authentication code with /2fa code <code>. */
|
||||||
|
TWO_FACTOR_CODE_REQUIRED("two_factor.code_required"),
|
||||||
|
|
||||||
/** You are not the owner of this account. Please choose another name! */
|
/** You are not the owner of this account. Please choose another name! */
|
||||||
NOT_OWNER_ERROR("on_join_validation.not_owner_error"),
|
NOT_OWNER_ERROR("on_join_validation.not_owner_error"),
|
||||||
|
|
||||||
|
@ -131,6 +131,7 @@ public class MessageUpdater {
|
|||||||
.put("captcha", new String[]{"Captcha"})
|
.put("captcha", new String[]{"Captcha"})
|
||||||
.put("verification", new String[]{"Verification code"})
|
.put("verification", new String[]{"Verification code"})
|
||||||
.put("time", new String[]{"Time units"})
|
.put("time", new String[]{"Time units"})
|
||||||
|
.put("two_factor", new String[]{"Two-factor authentication"})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Set<String> addedKeys = new HashSet<>();
|
Set<String> addedKeys = new HashSet<>();
|
||||||
|
@ -49,8 +49,8 @@ public class Management {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void performLogin(Player player, String password, String totpCode) {
|
public void performLogin(Player player, String password) {
|
||||||
runTask(() -> asynchronousLogin.login(player, password, totpCode));
|
runTask(() -> asynchronousLogin.login(player, password));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forceLogin(Player player) {
|
public void forceLogin(Player player) {
|
||||||
|
@ -6,6 +6,8 @@ import fr.xephi.authme.data.TempbanManager;
|
|||||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||||
import fr.xephi.authme.data.auth.PlayerCache;
|
import fr.xephi.authme.data.auth.PlayerCache;
|
||||||
import fr.xephi.authme.data.captcha.LoginCaptchaManager;
|
import fr.xephi.authme.data.captcha.LoginCaptchaManager;
|
||||||
|
import fr.xephi.authme.data.limbo.LimboMessageType;
|
||||||
|
import fr.xephi.authme.data.limbo.LimboPlayerState;
|
||||||
import fr.xephi.authme.data.limbo.LimboService;
|
import fr.xephi.authme.data.limbo.LimboService;
|
||||||
import fr.xephi.authme.datasource.DataSource;
|
import fr.xephi.authme.datasource.DataSource;
|
||||||
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
|
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
|
||||||
@ -18,7 +20,6 @@ import fr.xephi.authme.permission.PlayerStatePermission;
|
|||||||
import fr.xephi.authme.process.AsynchronousProcess;
|
import fr.xephi.authme.process.AsynchronousProcess;
|
||||||
import fr.xephi.authme.process.SyncProcessManager;
|
import fr.xephi.authme.process.SyncProcessManager;
|
||||||
import fr.xephi.authme.security.PasswordSecurity;
|
import fr.xephi.authme.security.PasswordSecurity;
|
||||||
import fr.xephi.authme.security.totp.TotpService;
|
|
||||||
import fr.xephi.authme.service.BukkitService;
|
import fr.xephi.authme.service.BukkitService;
|
||||||
import fr.xephi.authme.service.CommonService;
|
import fr.xephi.authme.service.CommonService;
|
||||||
import fr.xephi.authme.service.SessionService;
|
import fr.xephi.authme.service.SessionService;
|
||||||
@ -79,9 +80,6 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
@Inject
|
@Inject
|
||||||
private BungeeSender bungeeSender;
|
private BungeeSender bungeeSender;
|
||||||
|
|
||||||
@Inject
|
|
||||||
private TotpService totpService;
|
|
||||||
|
|
||||||
AsynchronousLogin() {
|
AsynchronousLogin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,14 +88,19 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
*
|
*
|
||||||
* @param player the player to log in
|
* @param player the player to log in
|
||||||
* @param password the password to log in with
|
* @param password the password to log in with
|
||||||
* @param totpCode the totp code (nullable)
|
|
||||||
*/
|
*/
|
||||||
public void login(Player player, String password, String totpCode) {
|
public void login(Player player, String password) {
|
||||||
PlayerAuth auth = getPlayerAuth(player);
|
PlayerAuth auth = getPlayerAuth(player);
|
||||||
if (auth != null && checkPlayerInfo(player, auth, password, totpCode)) {
|
if (auth != null && checkPlayerInfo(player, auth, password)) {
|
||||||
|
if (auth.getTotpKey() != null) {
|
||||||
|
limboService.resetMessageTask(player, LimboMessageType.TOTP_CODE);
|
||||||
|
limboService.getLimboPlayer(player.getName()).setState(LimboPlayerState.TOTP_REQUIRED);
|
||||||
|
// TODO #1141: Check if we should check limbo state before processing password
|
||||||
|
} else {
|
||||||
performLogin(player, auth);
|
performLogin(player, auth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a player in without requiring a password.
|
* Logs a player in without requiring a password.
|
||||||
@ -130,7 +133,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
if (auth == null) {
|
if (auth == null) {
|
||||||
service.send(player, MessageKey.UNKNOWN_USER);
|
service.send(player, MessageKey.UNKNOWN_USER);
|
||||||
// Recreate the message task to immediately send the message again as response
|
// Recreate the message task to immediately send the message again as response
|
||||||
limboService.resetMessageTask(player, false);
|
limboService.resetMessageTask(player, LimboMessageType.REGISTER);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,11 +164,10 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
* @param player the player requesting to log in
|
* @param player the player requesting to log in
|
||||||
* @param auth the PlayerAuth object of the player
|
* @param auth the PlayerAuth object of the player
|
||||||
* @param password the password supplied by the player
|
* @param password the password supplied by the player
|
||||||
* @param totpCode the input totp code (nullable)
|
|
||||||
* @return true if the password matches and all other conditions are met (e.g. no captcha required),
|
* @return true if the password matches and all other conditions are met (e.g. no captcha required),
|
||||||
* false otherwise
|
* false otherwise
|
||||||
*/
|
*/
|
||||||
private boolean checkPlayerInfo(Player player, PlayerAuth auth, String password, String totpCode) {
|
private boolean checkPlayerInfo(Player player, PlayerAuth auth, String password) {
|
||||||
final String name = player.getName().toLowerCase();
|
final String name = player.getName().toLowerCase();
|
||||||
|
|
||||||
// If captcha is required send a message to the player and deny to log in
|
// If captcha is required send a message to the player and deny to log in
|
||||||
@ -180,17 +182,6 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
loginCaptchaManager.increaseLoginFailureCount(name);
|
loginCaptchaManager.increaseLoginFailureCount(name);
|
||||||
tempbanManager.increaseCount(ip, name);
|
tempbanManager.increaseCount(ip, name);
|
||||||
|
|
||||||
if (auth.getTotpKey() != null) {
|
|
||||||
if (totpCode == null) {
|
|
||||||
player.sendMessage(
|
|
||||||
"You have two-factor authentication enabled. Please provide it: /login <password> <2faCode>");
|
|
||||||
return false;
|
|
||||||
} else if (!totpService.verifyCode(auth, totpCode)) {
|
|
||||||
player.sendMessage("Invalid code for two-factor authentication. Please try again");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordSecurity.comparePassword(password, auth.getPassword(), player.getName())) {
|
if (passwordSecurity.comparePassword(password, auth.getPassword(), player.getName())) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -235,7 +226,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
|||||||
* @param player the player to log in
|
* @param player the player to log in
|
||||||
* @param auth the associated PlayerAuth object
|
* @param auth the associated PlayerAuth object
|
||||||
*/
|
*/
|
||||||
private void performLogin(Player player, PlayerAuth auth) {
|
public void performLogin(Player player, PlayerAuth auth) {
|
||||||
if (player.isOnline()) {
|
if (player.isOnline()) {
|
||||||
final boolean isFirstLogin = (auth.getLastLogin() == null);
|
final boolean isFirstLogin = (auth.getLastLogin() == null);
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ public final class RestrictionSettings implements SettingsHolder {
|
|||||||
@Comment("Allowed commands for unauthenticated players")
|
@Comment("Allowed commands for unauthenticated players")
|
||||||
public static final Property<List<String>> ALLOW_COMMANDS =
|
public static final Property<List<String>> ALLOW_COMMANDS =
|
||||||
newLowercaseListProperty("settings.restrictions.allowCommands",
|
newLowercaseListProperty("settings.restrictions.allowCommands",
|
||||||
"/login", "/register", "/l", "/reg", "/email", "/captcha");
|
"/login", "/register", "/l", "/reg", "/email", "/captcha", "/2fa", "/totp");
|
||||||
|
|
||||||
@Comment({
|
@Comment({
|
||||||
"Max number of allowed registrations per IP",
|
"Max number of allowed registrations per IP",
|
||||||
|
@ -128,6 +128,9 @@ verification:
|
|||||||
code_expired: '&3Your code has expired! Execute another sensitive command to get a new code!'
|
code_expired: '&3Your code has expired! Execute another sensitive command to get a new code!'
|
||||||
email_needed: '&3To verify your identity you need to link an email address with your account!!'
|
email_needed: '&3To verify your identity you need to link an email address with your account!!'
|
||||||
|
|
||||||
|
two_factor:
|
||||||
|
code_required: 'Please submit your two-factor authentication code with /2fa code <code>'
|
||||||
|
|
||||||
# Time units
|
# Time units
|
||||||
time:
|
time:
|
||||||
second: 'second'
|
second: 'second'
|
||||||
|
@ -11,7 +11,6 @@ import org.mockito.InjectMocks;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
@ -57,19 +56,7 @@ public class LoginCommandTest {
|
|||||||
command.executeCommand(sender, Collections.singletonList("password"));
|
command.executeCommand(sender, Collections.singletonList("password"));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(management).performLogin(sender, "password", null);
|
verify(management).performLogin(sender, "password" );
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldCallManagementForPasswordAndTotpCode() {
|
|
||||||
// given
|
|
||||||
Player sender = mock(Player.class);
|
|
||||||
|
|
||||||
// when
|
|
||||||
command.executeCommand(sender, Arrays.asList("pwd", "12345"));
|
|
||||||
|
|
||||||
// then
|
|
||||||
verify(management).performLogin(sender, "pwd", "12345");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -76,7 +76,7 @@ public class LimboPlayerTaskManagerTest {
|
|||||||
given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(interval);
|
given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(interval);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
limboPlayerTaskManager.registerMessageTask(player, limboPlayer, false);
|
limboPlayerTaskManager.registerMessageTask(player, limboPlayer, LimboMessageType.REGISTER);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(limboPlayer).setMessageTask(any(MessageTask.class));
|
verify(limboPlayer).setMessageTask(any(MessageTask.class));
|
||||||
@ -94,7 +94,7 @@ public class LimboPlayerTaskManagerTest {
|
|||||||
given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(0);
|
given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(0);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
limboPlayerTaskManager.registerMessageTask(player, limboPlayer, true);
|
limboPlayerTaskManager.registerMessageTask(player, limboPlayer, LimboMessageType.LOG_IN);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verifyZeroInteractions(limboPlayer, bukkitService);
|
verifyZeroInteractions(limboPlayer, bukkitService);
|
||||||
@ -113,7 +113,7 @@ public class LimboPlayerTaskManagerTest {
|
|||||||
given(messages.retrieveSingle(player, MessageKey.REGISTER_MESSAGE)).willReturn("Please register!");
|
given(messages.retrieveSingle(player, MessageKey.REGISTER_MESSAGE)).willReturn("Please register!");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
limboPlayerTaskManager.registerMessageTask(player, limboPlayer, false);
|
limboPlayerTaskManager.registerMessageTask(player, limboPlayer, LimboMessageType.REGISTER);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(limboPlayer.getMessageTask(), not(nullValue()));
|
assertThat(limboPlayer.getMessageTask(), not(nullValue()));
|
||||||
@ -137,7 +137,7 @@ public class LimboPlayerTaskManagerTest {
|
|||||||
given(messages.retrieveSingle(player, MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, captcha)).willReturn("Need to use captcha");
|
given(messages.retrieveSingle(player, MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, captcha)).willReturn("Need to use captcha");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
limboPlayerTaskManager.registerMessageTask(player, limboPlayer, false);
|
limboPlayerTaskManager.registerMessageTask(player, limboPlayer, LimboMessageType.REGISTER);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(limboPlayer.getMessageTask(), not(nullValue()));
|
assertThat(limboPlayer.getMessageTask(), not(nullValue()));
|
||||||
|
@ -90,7 +90,7 @@ public class LimboServiceTest {
|
|||||||
limboService.createLimboPlayer(player, true);
|
limboService.createLimboPlayer(player, true);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(taskManager).registerMessageTask(eq(player), any(LimboPlayer.class), eq(true));
|
verify(taskManager).registerMessageTask(eq(player), any(LimboPlayer.class), eq(LimboMessageType.LOG_IN));
|
||||||
verify(taskManager).registerTimeoutTask(eq(player), any(LimboPlayer.class));
|
verify(taskManager).registerTimeoutTask(eq(player), any(LimboPlayer.class));
|
||||||
verify(player).setAllowFlight(false);
|
verify(player).setAllowFlight(false);
|
||||||
verify(player).setFlySpeed(0.0f);
|
verify(player).setFlySpeed(0.0f);
|
||||||
@ -121,7 +121,7 @@ public class LimboServiceTest {
|
|||||||
limboService.createLimboPlayer(player, false);
|
limboService.createLimboPlayer(player, false);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(taskManager).registerMessageTask(eq(player), any(LimboPlayer.class), eq(false));
|
verify(taskManager).registerMessageTask(eq(player), any(LimboPlayer.class), eq(LimboMessageType.REGISTER));
|
||||||
verify(taskManager).registerTimeoutTask(eq(player), any(LimboPlayer.class));
|
verify(taskManager).registerTimeoutTask(eq(player), any(LimboPlayer.class));
|
||||||
verify(permissionsManager, only()).hasGroupSupport();
|
verify(permissionsManager, only()).hasGroupSupport();
|
||||||
verify(player).setAllowFlight(false);
|
verify(player).setAllowFlight(false);
|
||||||
@ -209,7 +209,7 @@ public class LimboServiceTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
verify(taskManager).registerTimeoutTask(player, limbo);
|
verify(taskManager).registerTimeoutTask(player, limbo);
|
||||||
verify(taskManager).registerMessageTask(player, limbo, true);
|
verify(taskManager).registerMessageTask(player, limbo, LimboMessageType.LOG_IN);
|
||||||
verify(authGroupHandler).setGroup(player, limbo, AuthGroupType.REGISTERED_UNAUTHENTICATED);
|
verify(authGroupHandler).setGroup(player, limbo, AuthGroupType.REGISTERED_UNAUTHENTICATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user