Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package xyz.kyngs.librelogin.api;

import xyz.kyngs.librelogin.api.authorization.AuthorizationProvider;
import xyz.kyngs.librelogin.api.configuration.CorruptedConfigurationException;
import xyz.kyngs.librelogin.api.configuration.Messages;
import xyz.kyngs.librelogin.api.crypto.CryptoProvider;
import xyz.kyngs.librelogin.api.crypto.HashedPassword;
Expand All @@ -24,6 +25,7 @@
import xyz.kyngs.librelogin.api.util.ThrowableFunction;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Timestamp;
import java.util.Map;
Expand Down Expand Up @@ -291,4 +293,9 @@ User createUser(
String lastServer,
String email
);

/**
* Reloads the configurations and registers/unregisters new handlers (if possible).
*/
void reloadConfiguration() throws CorruptedConfigurationException, IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ public interface AuthorizationProvider<P> {
*/
boolean isAwaiting2FA(P player);

/**
* Checks whether the player is in the process of disabling 2FA.
*
* @param player The player.
* @return True if the player needs to confirm the action of disabling 2FA, false otherwise.
*/
boolean isAwaiting2FADisable(P player);

/**
* Authorizes the player, if the player is not already authorized. Implementation must make sure that {@link #isAuthorized(P)} returns false.
*
Expand All @@ -51,4 +59,14 @@ public interface AuthorizationProvider<P> {
* @return whether the code is valid.
*/
boolean confirmTwoFactorAuth(P player, Integer code, User user);

/**
* Finishes the 2FA disabling process.
*
* @param player The player.
* @param code The code.
* @param user The user.
* @return whether the code is valid.
*/
boolean confirmTwoFactorAuthDisable(P player, Integer code, User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,9 @@ default void registerLobbyServer(S server) {
*/
void registerLimboServer(S server);

/**
* Shuts down server handler.
*/
void shutdown();

}
Original file line number Diff line number Diff line change
Expand Up @@ -290,26 +290,12 @@ protected void enable() {

connectToDB();

serverHandler = new AuthenticServerHandler<>(this);

this.loginTryListener = new LoginTryListener<>(this);

// Moved to a different class to avoid class loading issues
GeneralUtil.checkAndMigrate(configuration, logger, this);

imageProjector = provideImageProjector();

if (imageProjector != null) {
if (!configuration.get(TOTP_ENABLED)) {
imageProjector = null;
logger.warn("2FA is disabled in the configuration, aborting...");
} else {
imageProjector.enable();
}
}

totpProvider = imageProjector == null ? null : new AuthenticTOTPProvider(this);
eMailHandler = configuration.get(MAIL_ENABLED) ? new AuthenticEMailHandler(this) : null;
reloadComponents();

authorizationProvider = new AuthenticAuthorizationProvider<>(this);
commandProvider = new CommandProvider<>(this);
Expand Down Expand Up @@ -717,6 +703,30 @@ public void checkDataFolder() {
if (!folder.exists()) if (!folder.mkdir()) throw new RuntimeException("Failed to create datafolder");
}

@Override
public void reloadConfiguration() throws CorruptedConfigurationException, IOException {
this.getConfiguration().reload(this);
if (serverHandler != null) serverHandler.shutdown();
reloadComponents();
}

private void reloadComponents() {
serverHandler = new AuthenticServerHandler<>(this);

imageProjector = provideImageProjector();

if (imageProjector != null) {
if (!configuration.get(TOTP_ENABLED)) {
imageProjector = null;
} else {
imageProjector.enable();
}
}

totpProvider = configuration.get(TOTP_ENABLED) ? new AuthenticTOTPProvider(this) : null;
eMailHandler = configuration.get(MAIL_ENABLED) ? new AuthenticEMailHandler(this) : null;
}

protected abstract Logger provideLogger();

public abstract CommandManager<?, ?, ?, ?, ?, ?> provideManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ public class AuthenticAuthorizationProvider<P, S> extends AuthenticHandler<P, S>

private final Map<P, Boolean> unAuthorized;
private final Map<P, String> awaiting2FA;
private final Map<P, String> awaiting2FADisable;
private final Cache<UUID, EmailVerifyData> emailConfirmCache;
private final Cache<UUID, String> passwordResetCache;

public AuthenticAuthorizationProvider(AuthenticLibreLogin<P, S> plugin) {
super(plugin);
unAuthorized = new ConcurrentHashMap<>();
awaiting2FA = new ConcurrentHashMap<>();
awaiting2FADisable = new ConcurrentHashMap<>();

var millis = plugin.getConfiguration().get(ConfigurationKeys.MILLISECONDS_TO_REFRESH_NOTIFICATION);

Expand Down Expand Up @@ -69,6 +71,7 @@ public Cache<UUID, String> getPasswordResetCache() {
public void onExit(P player) {
stopTracking(player);
awaiting2FA.remove(player);
awaiting2FADisable.remove(player);
emailConfirmCache.invalidate(platformHandle.getUUIDForPlayer(player));
passwordResetCache.invalidate(platformHandle.getUUIDForPlayer(player));
}
Expand All @@ -83,6 +86,11 @@ public boolean isAwaiting2FA(P player) {
return awaiting2FA.containsKey(player);
}

@Override
public boolean isAwaiting2FADisable(P player) {
return awaiting2FADisable.containsKey(player);
}

@Override
public void authorize(User user, P player, AuthenticatedEvent.AuthenticationReason reason) {
if (isAuthorized(player)) {
Expand Down Expand Up @@ -113,6 +121,17 @@ public boolean confirmTwoFactorAuth(P player, Integer code, User user) {
return false;
}

@Override
public boolean confirmTwoFactorAuthDisable(P player, Integer code, User user) {
var secret = awaiting2FADisable.get(player);
if (plugin.getTOTPProvider().verify(code, secret)) {
user.setSecret(null);
plugin.getDatabaseProvider().updateUser(user);
return true;
}
return false;
}

public void startTracking(User user, P player) {
var audience = platformHandle.getAudienceForPlayer(player);

Expand Down Expand Up @@ -215,4 +234,8 @@ public void beginTwoFactorAuth(User user, P player, TOTPData data) {
if (t != null || e != null) awaiting2FA.remove(player);
});
}

public void beginTwoFactorAuthDisable(User user, P player) {
awaiting2FADisable.put(player, user.getSecret());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import xyz.kyngs.librelogin.common.command.commands.premium.PremiumEnableCommand;
import xyz.kyngs.librelogin.common.command.commands.staff.LibreLoginCommand;
import xyz.kyngs.librelogin.common.command.commands.tfa.TwoFactorAuthCommand;
import xyz.kyngs.librelogin.common.command.commands.tfa.TwoFactorAuthDisableCommand;
import xyz.kyngs.librelogin.common.command.commands.tfa.TwoFactorConfirmCommand;
import xyz.kyngs.librelogin.common.util.RateLimiter;

Expand Down Expand Up @@ -110,6 +111,7 @@ public CommandProvider(AuthenticLibreLogin<P, S> plugin) {
if (plugin.getTOTPProvider() != null) {
manager.registerCommand(new TwoFactorAuthCommand<>(plugin));
manager.registerCommand(new TwoFactorConfirmCommand<>(plugin));
manager.registerCommand(new TwoFactorAuthDisableCommand<>(plugin));
}

if (plugin.getEmailHandler() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public CompletionStage<Void> onReloadConfiguration(Audience audience) {
audience.sendMessage(getMessage("info-reloading"));

try {
plugin.getConfiguration().reload(plugin);
plugin.reloadConfiguration();
} catch (IOException e) {
e.printStackTrace();
throw new InvalidCommandArgument(getMessage("error-unknown"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,23 @@ public CompletionStage<Void> onTwoFactorAuth(Audience sender, P player) {
throw new InvalidCommandArgument(getMessage("totp-show-info"));
}

if (!plugin.getImageProjector().canProject(player)) {
throw new InvalidCommandArgument(getMessage("totp-wrong-version",
"%low%", "1.13",
"%high%", "1.21.1"
));
}

sender.sendMessage(getMessage("totp-generating"));

var data = plugin.getTOTPProvider().generate(user);

auth.beginTwoFactorAuth(user, player, data);

plugin.cancelOnExit(plugin.delay(() -> {
plugin.getImageProjector().project(data.qr(), player);
if (plugin.getImageProjector() != null && plugin.getImageProjector().canProject(player)) {
plugin.cancelOnExit(plugin.delay(() -> {
plugin.getImageProjector().project(data.qr(), player);

sender.sendMessage(getMessage("totp-show-info"));
}, plugin.getConfiguration().get(ConfigurationKeys.TOTP_DELAY)), player);
sender.sendMessage(getMessage("totp-show-info"));
}, plugin.getConfiguration().get(ConfigurationKeys.TOTP_DELAY)), player);
} else {
sender.sendMessage(getMessage("totp-show-info-fallback",
"%totp_secret%", data.secret()
));
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

package xyz.kyngs.librelogin.common.command.commands.tfa;

import co.aikar.commands.annotation.*;
import net.kyori.adventure.audience.Audience;
import xyz.kyngs.librelogin.common.AuthenticLibreLogin;
import xyz.kyngs.librelogin.common.command.Command;
import xyz.kyngs.librelogin.common.command.InvalidCommandArgument;

import java.util.concurrent.CompletionStage;

@CommandAlias("2fadisable")
public class TwoFactorAuthDisableCommand<P> extends Command<P> {
public TwoFactorAuthDisableCommand(AuthenticLibreLogin<P, ?> plugin) {
super(plugin);
}

@Default
@Syntax("{@@syntax.2fa-disable}")
@CommandCompletion("%autocomplete.2fa-disable")
public CompletionStage<Void> onTwoFactorAuthDisable(Audience sender, P player, @Optional String code) {
return runAsync(() -> {
checkAuthorized(player);
var user = getUser(player);
var auth = plugin.getAuthorizationProvider();
int parsedCode;

if (auth.isAwaiting2FADisable(player) && code != null) {
try {
parsedCode = Integer.parseInt(code.trim().replace(" ", ""));
} catch (NumberFormatException e) {
throw new InvalidCommandArgument(getMessage("totp-wrong"));
}

if (!auth.confirmTwoFactorAuthDisable(player, parsedCode, user)) {
throw new InvalidCommandArgument(getMessage("totp-wrong"));
}

sender.sendMessage(getMessage("totp-disable-confirm"));
}else{
auth.beginTwoFactorAuthDisable(user, player);

sender.sendMessage(getMessage("totp-disable"));
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,36 @@ public class MessageKeys {
ConfigurateHelper::getString
);

public static final ConfigurationKey<String> TOTP_SHOW_INFO_FALLBACK = new ConfigurationKey<>(
"totp-show-info-fallback",
"""
It is currently not possible to display the 2FA QR code on screen.
Please open your 2FA app (e.g., Google Authenticator or Authy) and manually add a new entry using the following secret key:
%totp_secret%
Once done, execute the /2faconfirm <code> command to complete the process.
Disconnect to abort.""",
"This message is displayed when the player is prompted to scan the 2FA QR code but can't show in game graphic.",
ConfigurateHelper::getString
);

public static final ConfigurationKey<String> TOTP_DISABLE = new ConfigurationKey<>(
"totp-disable",
"""
Please confirm that you want to disable 2FA by executing the command again:
/2fadisable <code>
Once 2FA is disabled, you will be able to log in without a 2FA code.
This action is irreversible. Once disabled, old codes will no longer work.""",
"This message is displayed when the player is prompted to confirm he wants to disable 2fa.",
ConfigurateHelper::getString
);

public static final ConfigurationKey<String> TOTP_DISABLE_CONFIRM = new ConfigurationKey<>(
"totp-disable-confirm",
"2FA has been disabled! You can now log in without a 2FA code.",
"This message is displayed when the 2fa is disabled.",
ConfigurateHelper::getString
);

public static final ConfigurationKey<String> TOTP_GENERATING = new ConfigurationKey<>(
"totp-generating",
"Generating 2FA code...",
Expand Down Expand Up @@ -964,6 +994,13 @@ public class MessageKeys {
ConfigurateHelper::getString
);

public static final ConfigurationKey<String> SYNTAX_2FA_DISABLE = new ConfigurationKey<>(
"syntax.2fa-disable",
"<code>",
"This message is displayed when the player attempts to disable 2FA with wrong syntax.",
ConfigurateHelper::getString
);

public static final ConfigurationKey<String> SYNTAX_CHANGE_PASSWORD = new ConfigurationKey<>(
"syntax.change-password",
"<oldPassword> <newPassword>",
Expand Down Expand Up @@ -1133,7 +1170,14 @@ public class MessageKeys {
public static final ConfigurationKey<String> AUTOCOMPLETE_2FA_CONFIRM = new ConfigurationKey<>(
"autocomplete.2fa-confirm",
"code",
"This hint is displayed when the player starts typing the /2fa-confirm command.",
"This hint is displayed when the player starts typing the /2faconfirm command.",
ConfigurateHelper::getString
);

public static final ConfigurationKey<String> AUTOCOMPLETE_2FA_DISABLE = new ConfigurationKey<>(
"autocomplete.2fa-disable",
"code",
"This hint is displayed when the player starts typing the /2fadisable command.",
ConfigurateHelper::getString
);

Expand Down
Loading