diff --git a/README.md b/README.md index 9e467de..c0482ff 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ You can use the current version and give us a feedback about what needs to be ch ## 👥 Contributors urhatedjack - logo design +[KotreQ](https://github.com/KotreQ) - help with the QR code generation Thanks to **Discord support team**: - [Blavez](https://github.com/Blavezz) @@ -67,7 +68,7 @@ NavAuth is licensed under the GNU AGPL v3. See the license file for more informa [![GNU AGPL Logo](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html) ## 💡 TODO List -More planned features are described in [Documentation](https://navio1430.github.io/NavAuth/offer.html#%F0%9F%9A%80-planned-features) +More planned features are described in [Documentation](https://navio1430.github.io/NavAuth/docs/offer.html#%F0%9F%9A%80-planned-features) - readme: - add banner - github/gh actions: diff --git a/build.gradle.kts b/build.gradle.kts index cf3fd86..09f0dbd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ repositories { allprojects { group = "pl.spcode.navauth" - version = "0.1.3-SNAPSHOT" + version = "0.1.4-SNAPSHOT" } tasks.register("formatAll") { diff --git a/docs/docs/index.md b/docs/docs/index.md index ccf4f75..8d9b4cb 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -25,7 +25,7 @@ features: - icon: ⚡ title: Setup & Run the plugin details: Go through the detailed description about how to configure and run NavAuth. - link: /setup # todo + link: /setup - icon: 🚀 title: Migrate now details: Check how to migrate from other plugins like LibreLogin, AuthMe and FastLogin. @@ -33,7 +33,7 @@ features: - icon: 🧩 title: Requirements details: Check the requirements needed to run NavAuth. - link: /requirements # todo + link: /requirements - icon: src: https://pngimg.com/d/discord_PNG3.png title: Need support? diff --git a/docs/docs/navauth-release.md b/docs/docs/navauth-release.md index 3b26824..eba8aa9 100644 --- a/docs/docs/navauth-release.md +++ b/docs/docs/navauth-release.md @@ -1,4 +1,3 @@ # NavAuth Release -First release of NavAuth is still under development. -Please join our discord server to be notified about version 1.0 +NavAuth version 1.0 is still under development. As of now you can use versions **0.X** and give us your feedback on our **Discord** server. \ No newline at end of file diff --git a/docs/docs/requirements.md b/docs/docs/requirements.md new file mode 100644 index 0000000..93a23bf --- /dev/null +++ b/docs/docs/requirements.md @@ -0,0 +1,13 @@ +# Requirements + +## What is required to run NavAuth? +- Velocity (preferably the newest versions) +- Limbo server e.g., NanoLimbo, PicoLimbo +- You can use [PicoLimbo velocity plugin](https://modrinth.com/plugin/picolimbo-java-wrapper) for simplicity. +::: danger +DO NOT USE NORMAL PAPER/LOBBY SERVER AS LIMBO. IT CAN CAUSE A MAJOR SECURITY ISSUE. +::: + +## Modern forwarding is required +Please set **Velocity** forwarding to `MODERN`. +All backend servers must support `MODERN` forward too (**1.13.1+ only**). diff --git a/docs/docs/setup.md b/docs/docs/setup.md new file mode 100644 index 0000000..3449de5 --- /dev/null +++ b/docs/docs/setup.md @@ -0,0 +1,6 @@ +# Setup + +Detailed setup tutorial is still under construction. +Follow [Requirements page](/requirements) for requirements details. +If you have basic knowledge of how **Velocity** proxy works, then it shouldn't +be a problem for you to figure it out how to make things work. \ No newline at end of file diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/application/auth/session/AuthSessionService.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/application/auth/session/AuthSessionService.kt index b812187..10f7089 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/application/auth/session/AuthSessionService.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/application/auth/session/AuthSessionService.kt @@ -52,9 +52,7 @@ open class AuthSessionService

{ fun closeSession(uniqueSessionId: UniqueSessionId): Boolean { val session = sessionsMap.remove(uniqueSessionId) if (session != null) { - if (session.playerAdapter.isOnline()) { - session.playerAdapter.disconnect(DisconnectReason.AUTH_SESSION_CLOSED) - } + session.playerAdapter.disconnect(DisconnectReason.AUTH_SESSION_CLOSED) session.onInvalidate() logger.debug( "invalidated auth session (type='{}') with ID {}", diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/config/MessagesConfig.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/config/MessagesConfig.kt index 08fad16..14f8175 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/config/MessagesConfig.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/config/MessagesConfig.kt @@ -32,7 +32,9 @@ import pl.spcode.navauth.common.component.TextComponent open class MessagesConfig : OkaeriConfig() { var supportFooter = - TextComponent("

For support please join our discord: https://dc.spcode.pl/") + TextComponent("

For support please join our discord: https://dc.yourwebsite.net/") + + var invalidUsernameError = TextComponent("Invalid username.") var usernameRequiredError = TextComponent( @@ -59,21 +61,50 @@ open class MessagesConfig : OkaeriConfig() { var registerTimeExceededError = TextComponent("You've exceeded register time, please try again") + var loginTooManyAttemptsError = + TextComponent("Too many login attempts. Please try again later.") + + var adminCopyPasswordText = + "CLICK HERE TO COPY" + + var yourAccountDataHasBeenMigrated = + TextComponent("Your account data has been migrated to '%USERNAME%'.") + @Comment( "Notifications which use multification library.", "Here you can use chat messages, action bars, sounds etc. combined.", - "To learn more about multification please read https://navio1430.github.io/NavAuth/configuration/multification.html", + "To learn more about multification please read https://navio1430.github.io/NavAuth/docs/configuration/multification.html", ) var multification = NoticesConfig() class NoticesConfig : OkaeriConfig() { + var passwordRequiredError: Notice = Notice.chat("Please provide your current password.") + var twoFactorAlreadyEnabledError: Notice = + Notice.chat("Your account has 2FA enabled already!") + var missingPermissionError: Notice = Notice.chat("You don't have permission to execute this command.") var invalidUsageError: Notice = Notice.chat("Invalid command usage!") @Comment("Invalid usage scheme line (single text component only).") var invalidUsageLine: TextComponent = TextComponent(" • %SCHEME%") + var cantUseThisCommandNowError: Notice = Notice.chat("Can't use this command right now.") + var commandPasswordNotSetForAccountError: Notice = + Notice.chat("Can't execute this command right now: your account has no password set.") + var commandNoPremiumAccountWithUsername: Notice = + Notice.chat( + "Can't set this account as premium because there's no premium account with username '%USERNAME%'." + ) + var accountAlreadyPremiumError: Notice = + Notice.chat("Account is already set as a premium one.") + + var registerPasswordInvalidError: Notice = + Notice.chat( + "The password is invalid. It must be at least 5 characters long and contain at least one uppercase letter and one digit." + ) + var registerPasswordsMustMatchError: Notice = Notice.chat("Both passwords must match.") + var loginPasswordOnlyInstruction: Notice = Notice.chat("Please login using \"/login \" command.") var loginTwoFactorOnlyInstruction: Notice = @@ -86,11 +117,26 @@ open class MessagesConfig : OkaeriConfig() { var loginSuccess: Notice = Notice.chat("You have been authenticated successfully.") - var registerSuccess: Notice = Notice.chat("Successfully registered") - var premiumAuthSuccess: Notice = Notice.chat("Auto-logged in") + var accountMigrationSuccess: Notice = Notice.chat("Account migrated successfully!") + var newPasswordSetSuccess: Notice = Notice.chat("Success! New password set.") + + var wrongCredentialsError: Notice = Notice.chat("Wrong credentials provided!") + + var twoFactorDisabledSuccess: Notice = Notice.chat("2FA is now disabled!") + var twoFactorEnabledSuccess: Notice = Notice.chat("2FA is now enabled!") + var twoFactorCodeRequiredError: Notice = + Notice.chat("Please provide two-factor authentication code.") + var twoFactorSessionNotFound: Notice = + Notice.chat( + "2FA setup session not found. Please try again using /setup2fa command first." + ) + var twoFactorWrongCodeError: Notice = Notice.chat("Wrong 2FA code!") + var twoFactorAlreadyDisabledError: Notice = + Notice.chat("Your account has 2FA disabled already.") + var twoFactorSetupInstruction: Notice = Notice.chat( """ @@ -112,6 +158,39 @@ open class MessagesConfig : OkaeriConfig() { """ .trimIndent() ) + + var adminCmdUsernameIsInvalid: Notice = + Notice.chat("Provided username '%USERNAME%' is invalid.") + var adminCmdAccountIsPremiumError: Notice = + Notice.chat("Can't execute the command! Account '%USERNAME%' is set to premium mode.") + var adminCmdAccountIsAlreadyNonPremiumError: Notice = + Notice.chat("Account '%USERNAME%' is already a non-premium account.") + var adminCmdUseForceCrackedFirst: Notice = Notice.chat("Use /forcecracked command first.") + var adminCmdCantMigrateToExistingPremiumAccount: Notice = + Notice.chat( + "Provided username '%USERNAME%' is found as Mojang premium profile. Can't migrate to premium account." + ) + var adminCmdUsernameAlreadyTakenError: Notice = + Notice.chat( + "Username '%USERNAME%' is already taken. Please try again with a different username." + ) + var adminCmdUsernameNotPremiumError: Notice = + Notice.chat( + "Can't find '%USERNAME%' user in Mojang database. This player can't be migrated to premium mode." + ) + + var adminCmdPasswordSetSuccess: Notice = + Notice.chat("Success! User '%USERNAME%' password was set.") + var adminCmdAccountMigratedToNonPremiumSuccess: Notice = + Notice.chat( + "User '%USERNAME%' has been successfully migrated to non-premium mode. Their new password is: %PASSWORD_TEXT%" + ) + var adminCmdUserDataMigratedSuccess: Notice = + Notice.chat( + "Success! User '%OLD_USERNAME%' data has been migrated to '%NEW_USERNAME%'." + ) + var adminCmdUserPremiumMigrationSuccess: Notice = + Notice.chat("User '%USERNAME%' successfully migrated to premium mode.") } @Variable("CONFIG_VERSION") diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/config/MigrationConfig.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/config/MigrationConfig.kt index 9fa5c77..91142f4 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/config/MigrationConfig.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/config/MigrationConfig.kt @@ -27,7 +27,7 @@ import pl.spcode.navauth.common.migrate.MigrationOriginPluginType @Header( "Database migration config", "This file contains database migration settings.", - "Check https://navio1430.github.io/NavAuth/migration/migration.html for more information", + "Check https://navio1430.github.io/NavAuth/docs/migration/migration.html for more information", ) class MigrationConfig : OkaeriConfig() { diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/auth/LoginAuthSession.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/auth/LoginAuthSession.kt index 07b58f9..15d3879 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/auth/LoginAuthSession.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/auth/LoginAuthSession.kt @@ -42,6 +42,10 @@ open class LoginAuthSession( override fun onInvalidate() {} + open fun onTooManyLoginAttempts() { + playerAdapter.disconnect(DisconnectReason.TOO_MANY_LOGIN_ATTEMPTS) + } + /** * Authenticates a user using a combination of password and two-factor authentication code if * required. @@ -58,7 +62,7 @@ open class LoginAuthSession( if (!result) { attemptsLeft -= 1 if (attemptsLeft <= 0) { - playerAdapter.disconnect(DisconnectReason.TOO_MANY_LOGIN_ATTEMPTS) + onTooManyLoginAttempts() } } return result diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/shared/data/OrmLiteCrudRepository.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/OrmLiteCrudRepository.kt similarity index 95% rename from navauth-common/src/main/kotlin/pl/spcode/navauth/common/shared/data/OrmLiteCrudRepository.kt rename to navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/OrmLiteCrudRepository.kt index a2eaac7..86b6829 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/shared/data/OrmLiteCrudRepository.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/OrmLiteCrudRepository.kt @@ -16,7 +16,7 @@ * */ -package pl.spcode.navauth.common.shared.data +package pl.spcode.navauth.common.infra.persistence.ormlite import com.j256.ormlite.dao.Dao diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/shared/data/OrmLiteCrudRepositoryImpl.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/OrmLiteCrudRepositoryImpl.kt similarity index 97% rename from navauth-common/src/main/kotlin/pl/spcode/navauth/common/shared/data/OrmLiteCrudRepositoryImpl.kt rename to navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/OrmLiteCrudRepositoryImpl.kt index 2a601eb..6946afd 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/shared/data/OrmLiteCrudRepositoryImpl.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/OrmLiteCrudRepositoryImpl.kt @@ -16,7 +16,7 @@ * */ -package pl.spcode.navauth.common.shared.data +package pl.spcode.navauth.common.infra.persistence.ormlite import com.j256.ormlite.dao.Dao import com.j256.ormlite.stmt.DeleteBuilder diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/credentials/UserCredentialsRepositoryImpl.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/credentials/UserCredentialsRepositoryImpl.kt index 40e9809..8ba9e14 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/credentials/UserCredentialsRepositoryImpl.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/credentials/UserCredentialsRepositoryImpl.kt @@ -27,7 +27,7 @@ import pl.spcode.navauth.common.domain.user.User import pl.spcode.navauth.common.infra.database.DatabaseManager import pl.spcode.navauth.common.infra.persistence.mapper.toDomain import pl.spcode.navauth.common.infra.persistence.mapper.toRecord -import pl.spcode.navauth.common.shared.data.OrmLiteCrudRepositoryImpl +import pl.spcode.navauth.common.infra.persistence.ormlite.OrmLiteCrudRepositoryImpl class UserCredentialsRepositoryImpl @Inject constructor(databaseManager: DatabaseManager) : OrmLiteCrudRepositoryImpl( diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/user/UserActivitySessionRepositoryImpl.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/user/UserActivitySessionRepositoryImpl.kt index 571315f..bb48545 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/user/UserActivitySessionRepositoryImpl.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/user/UserActivitySessionRepositoryImpl.kt @@ -27,7 +27,7 @@ import pl.spcode.navauth.common.infra.database.DatabaseManager import pl.spcode.navauth.common.infra.persistence.Paginator import pl.spcode.navauth.common.infra.persistence.mapper.toDomain import pl.spcode.navauth.common.infra.persistence.mapper.toRecord -import pl.spcode.navauth.common.shared.data.OrmLiteCrudRepositoryImpl +import pl.spcode.navauth.common.infra.persistence.ormlite.OrmLiteCrudRepositoryImpl class UserActivitySessionRepositoryImpl @Inject constructor(databaseManager: DatabaseManager) : OrmLiteCrudRepositoryImpl( diff --git a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/user/UserRepositoryImpl.kt b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/user/UserRepositoryImpl.kt index 8918b7b..156f8da 100644 --- a/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/user/UserRepositoryImpl.kt +++ b/navauth-common/src/main/kotlin/pl/spcode/navauth/common/infra/persistence/ormlite/user/UserRepositoryImpl.kt @@ -28,7 +28,7 @@ import pl.spcode.navauth.common.domain.user.UserUuid import pl.spcode.navauth.common.infra.database.DatabaseManager import pl.spcode.navauth.common.infra.persistence.mapper.toDomain import pl.spcode.navauth.common.infra.persistence.mapper.toRecord -import pl.spcode.navauth.common.shared.data.OrmLiteCrudRepositoryImpl +import pl.spcode.navauth.common.infra.persistence.ormlite.OrmLiteCrudRepositoryImpl class UserRepositoryImpl @Inject constructor(databaseManager: DatabaseManager) : OrmLiteCrudRepositoryImpl(databaseManager, UserRecord::class), UserRepository { diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForceChangePasswordAdminCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForceChangePasswordAdminCommand.kt index 3200601..6b3c7f7 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForceChangePasswordAdminCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForceChangePasswordAdminCommand.kt @@ -26,14 +26,13 @@ import dev.rollczi.litecommands.annotations.command.Command import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute import dev.rollczi.litecommands.annotations.permission.Permission -import net.kyori.adventure.text.Component import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.credentials.UserCredentialsService import pl.spcode.navauth.common.application.user.UserService import pl.spcode.navauth.common.command.user.UserArgumentResolver import pl.spcode.navauth.common.command.user.UsernameOrUuidRaw -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.velocity.command.Permissions +import pl.spcode.navauth.velocity.multification.VelocityMultification @Command(name = "forcesetpassword") @Permission(Permissions.ADMIN_FORCE_SET_PASSWORD) @@ -43,6 +42,7 @@ constructor( val userService: UserService, val userCredentialsService: UserCredentialsService, val userArgumentResolver: UserArgumentResolver, + val multification: VelocityMultification, ) { @Async @@ -58,18 +58,17 @@ constructor( val user = userArgumentResolver.resolve(usernameOrUuidRaw) if (user.isPremium) { - sender.sendMessage( - Component.text( - "Can't execute the command! Account '${user.username}' is set to premium mode.", - TextColors.RED, - ) - ) + multification + .create(sender) { it.multification.adminCmdAccountIsPremiumError } + .placeholder("%USERNAME%", user.username.value) + .send() return } userCredentialsService.updatePassword(user, password) - sender.sendMessage( - Component.text("Success! User '${user.username}' credentials set.", TextColors.GREEN) - ) + multification + .create(sender) { it.multification.adminCmdPasswordSetSuccess } + .placeholder("%USERNAME%", user.username.value) + .send() } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForceCrackedAdminCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForceCrackedAdminCommand.kt index 312670e..369c084 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForceCrackedAdminCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForceCrackedAdminCommand.kt @@ -29,16 +29,15 @@ import dev.rollczi.litecommands.annotations.execute.Execute import dev.rollczi.litecommands.annotations.permission.Permission import java.util.Optional import me.uniodex.velocityrcon.commandsource.IRconCommandSource -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.minimessage.MiniMessage import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.credentials.UserCredentialsService import pl.spcode.navauth.common.application.user.UserService import pl.spcode.navauth.common.command.user.UserArgumentResolver import pl.spcode.navauth.common.command.user.UsernameOrUuidRaw -import pl.spcode.navauth.common.component.TextColors +import pl.spcode.navauth.common.extension.StringExtensions.Companion.applyPlaceholders import pl.spcode.navauth.common.shared.utils.StringUtils.Companion.generateRandomString import pl.spcode.navauth.velocity.command.Permissions +import pl.spcode.navauth.velocity.multification.VelocityMultification @Command(name = "forcecracked") @Permission(Permissions.ADMIN_FORCE_CRACKED) @@ -48,6 +47,7 @@ constructor( val userService: UserService, val userArgumentResolver: UserArgumentResolver, val userCredentialsService: UserCredentialsService, + val multification: VelocityMultification, ) { @Async @@ -64,9 +64,10 @@ constructor( val user = userArgumentResolver.resolve(usernameOrUuidRaw) if (!user.isPremium) { - sender.sendMessage( - Component.text("User '${user.username}' is already non-premium account.", TextColors.RED) - ) + multification + .create(sender) { it.multification.adminCmdAccountIsAlreadyNonPremiumError } + .placeholder("%USERNAME%", user.username.value) + .send() return } @@ -79,13 +80,14 @@ constructor( if (sender is ConsoleCommandSource || sender is IRconCommandSource) { "$newPassword" } else { - "CLICK HERE TO COPY" + val placeholders = mapOf(Pair("PASSWORD", newPassword)) + multification.config.adminCopyPasswordText.applyPlaceholders(placeholders) } - sender.sendMessage( - MiniMessage.miniMessage() - .deserialize( - "<${TextColors.GREEN.asHexString()}>User '${user.username}' has been successfully migrated to non-premium mode. Their new password is: $passwordText" - ) - ) + + multification + .create(sender) { it.multification.adminCmdAccountMigratedToNonPremiumSuccess } + .placeholder("%USERNAME%", user.username.value) + .placeholder("%PASSWORD_TEXT%", passwordText) + .send() } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForcePremiumAdminCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForcePremiumAdminCommand.kt index 39ecc3b..9edb997 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForcePremiumAdminCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/ForcePremiumAdminCommand.kt @@ -26,14 +26,13 @@ import dev.rollczi.litecommands.annotations.command.Command import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute import dev.rollczi.litecommands.annotations.permission.Permission -import net.kyori.adventure.text.Component import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.mojang.MojangProfileService import pl.spcode.navauth.common.application.user.UserService import pl.spcode.navauth.common.command.user.UserArgumentResolver import pl.spcode.navauth.common.command.user.UsernameOrUuidRaw -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.velocity.command.Permissions +import pl.spcode.navauth.velocity.multification.VelocityMultification @Command(name = "forcepremium") @Permission(Permissions.ADMIN_FORCE_PREMIUM) @@ -43,6 +42,7 @@ constructor( val userService: UserService, val profileService: MojangProfileService, val userArgumentResolver: UserArgumentResolver, + val multification: VelocityMultification, ) { @Execute @@ -55,27 +55,26 @@ constructor( val user = userArgumentResolver.resolve(usernameOrUuidRaw) if (user.isPremium) { - sender.sendMessage(Component.text("User is already premium.", TextColors.RED)) + multification + .create(sender) { it.multification.adminCmdAccountIsPremiumError } + .placeholder("%USERNAME%", user.username.value) + .send() return } val profile = profileService.fetchProfileInfo(user.username) if (profile == null) { - sender.sendMessage( - Component.text( - "Can't find '${user.username}' user in Mojang database. This player can't be migrated to premium mode.", - TextColors.RED, - ) - ) + multification + .create(sender) { it.multification.adminCmdUsernameNotPremiumError } + .placeholder("%USERNAME%", user.username.value) + .send() return } userService.migrateToPremium(user, profile.uuid) - sender.sendMessage( - Component.text( - "User '${user.username}' successfully migrated to premium mode.", - TextColors.GREEN, - ) - ) + multification + .create(sender) { it.multification.adminCmdUserPremiumMigrationSuccess } + .placeholder("%USERNAME%", user.username.value) + .send() } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/MigrateUserDataAdminCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/MigrateUserDataAdminCommand.kt index 2de059b..ef0ab45 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/MigrateUserDataAdminCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/admin/MigrateUserDataAdminCommand.kt @@ -27,7 +27,6 @@ import dev.rollczi.litecommands.annotations.command.Command import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute import dev.rollczi.litecommands.annotations.permission.Permission -import net.kyori.adventure.text.Component import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.mojang.MojangProfileService import pl.spcode.navauth.common.application.user.UserService @@ -35,9 +34,9 @@ import pl.spcode.navauth.common.application.user.UsernameAlreadyTakenException import pl.spcode.navauth.common.application.validator.UsernameValidator import pl.spcode.navauth.common.command.user.UserArgumentResolver import pl.spcode.navauth.common.command.user.UsernameOrUuidRaw -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.common.domain.user.Username import pl.spcode.navauth.velocity.command.Permissions +import pl.spcode.navauth.velocity.multification.VelocityMultification @Command(name = "migrateuser") @Permission(Permissions.ADMIN_MIGRATE_USER_DATA) @@ -49,6 +48,7 @@ constructor( val profileService: MojangProfileService, val userArgumentResolver: UserArgumentResolver, val usernameValidator: UsernameValidator, + val multification: VelocityMultification, ) { @Async @@ -67,56 +67,54 @@ constructor( val user = userArgumentResolver.resolve(usernameOrUuidRaw) if (user.isPremium) { - sender.sendMessage( - Component.text( - "Can't execute the command! Account '${user.username}' is set to premium mode. Use /forcecracked command first.", - TextColors.RED, - ) - ) + multification + .create(sender) { it.multification.adminCmdAccountIsPremiumError } + .placeholder("%USERNAME%", user.username.value) + .send() + multification.send(sender) { it.multification.adminCmdUseForceCrackedFirst } return } if (!usernameValidator.isValid(newUsername)) { - sender.sendMessage( - Component.text("Provided username '${newUsername}' is invalid.", TextColors.RED) - ) + multification + .create(sender) { it.multification.adminCmdUsernameIsInvalid } + .placeholder("%USERNAME%", newUsername) + .send() return } val premiumMojangProfile = profileService.fetchProfileInfo(Username(newUsername)) if (premiumMojangProfile != null) { - sender.sendMessage( - Component.text( - "Provided username '${user.username}' is found as Mojang premium profile. Can't migrate to premium account.", - TextColors.RED, - ) - ) + multification + .create(sender) { it.multification.adminCmdCantMigrateToExistingPremiumAccount } + .placeholder("%USERNAME%", user.username.value) + .send() return } try { userService.migrateData(user, Username(newUsername)) } catch (e: UsernameAlreadyTakenException) { - sender.sendMessage( - Component.text( - "Username '${newUsername}' is already taken. Please try again with different username.", - TextColors.RED, - ) - ) + multification + .create(sender) { it.multification.adminCmdUsernameAlreadyTakenError } + .placeholder("%USERNAME%", newUsername) + .send() return } proxyServer.getPlayer(user.username.value).ifPresent { - it.disconnect( - Component.text("Your account data has been migrated to '${newUsername}'.", TextColors.GREEN) - ) + val disconnectReason = + multification.config.yourAccountDataHasBeenMigrated + .withPlaceholders() + .placeholder("USERNAME", newUsername) + .toComponent() + it.disconnect(disconnectReason) } - sender.sendMessage( - Component.text( - "Success! User '${user.username}' data has been migrated to '${newUsername}'.", - TextColors.GREEN, - ) - ) + multification + .create(sender) { it.multification.adminCmdUserDataMigratedSuccess } + .placeholder("%OLD_USERNAME%", user.username.value) + .placeholder("%NEW_USERNAME%", newUsername) + .send() } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/ChangePasswordCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/ChangePasswordCommand.kt index f5cab2b..a9fde53 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/ChangePasswordCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/ChangePasswordCommand.kt @@ -26,18 +26,21 @@ import dev.rollczi.litecommands.annotations.command.Command import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute import dev.rollczi.litecommands.annotations.permission.Permission -import net.kyori.adventure.text.Component import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.credentials.UserCredentialsService import pl.spcode.navauth.common.application.user.UserService -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.velocity.command.Permissions +import pl.spcode.navauth.velocity.multification.VelocityMultification @Command(name = "changepassword") @Permission(Permissions.USER_CHANGE_PASSWORD) class ChangePasswordCommand @Inject -constructor(val userService: UserService, val userCredentialsService: UserCredentialsService) { +constructor( + val userService: UserService, + val userCredentialsService: UserCredentialsService, + val multification: VelocityMultification, +) { @Async @Execute @@ -48,23 +51,20 @@ constructor(val userService: UserService, val userCredentialsService: UserCreden @Arg(value = "new_password") newPassword: String, ) { val user = userService.findUserByExactUsername(sender.username)!! - if (user.isPremium) { - sender.sendMessage( - Component.text( - "Can't execute this command right now: your account is set to premium mode.", - TextColors.RED, - ) - ) + val credentials = userCredentialsService.findCredentials(user)!! + + if (!credentials.isPasswordRequired) { + multification.send(sender) { it.multification.commandPasswordNotSetForAccountError } return } - val credentials = userCredentialsService.findCredentials(user)!! + val isCorrectPassword = userCredentialsService.verifyPassword(credentials, currentPassword) if (!isCorrectPassword) { - sender.sendMessage(Component.text("Wrong password!", TextColors.RED)) + multification.send(sender) { it.multification.wrongCredentialsError } return } userCredentialsService.updatePassword(user, newPassword) - sender.sendMessage(Component.text("Success! New password set.", TextColors.GREEN)) + multification.send(sender) { it.multification.newPasswordSetSuccess } } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/LoginCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/LoginCommand.kt index b704303..7fa402c 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/LoginCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/LoginCommand.kt @@ -28,22 +28,24 @@ import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute import java.util.Optional import kotlin.jvm.optionals.getOrNull -import net.kyori.adventure.text.Component import pl.spcode.navauth.api.domain.auth.AuthSessionType import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.auth.session.AuthSessionService import pl.spcode.navauth.common.command.exception.MissingPermissionException -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.velocity.command.Permissions import pl.spcode.navauth.velocity.infra.auth.VelocityLoginAuthSession import pl.spcode.navauth.velocity.infra.auth.VelocityUniqueSessionId import pl.spcode.navauth.velocity.infra.player.VelocityPlayerAdapter +import pl.spcode.navauth.velocity.multification.VelocityMultification // we use inverted permission in this command @RootCommand class LoginCommand @Inject -constructor(val authSessionService: AuthSessionService) { +constructor( + val authSessionService: AuthSessionService, + val multification: VelocityMultification, +) { @Async @Execute(name = "login") @@ -66,7 +68,7 @@ constructor(val authSessionService: AuthSessionService) { val uniqueSessionId = VelocityUniqueSessionId(sender) val session = authSessionService.findSession(uniqueSessionId) if (session?.getSessionType() != AuthSessionType.LOGIN || session.isAuthenticated) { - sender.sendMessage(Component.text("Can't use this command right now.", TextColors.RED)) + multification.send(sender) { it.multification.cantUseThisCommandNowError } return } @@ -74,9 +76,7 @@ constructor(val authSessionService: AuthSessionService) { if (session.userCredentials.isTwoFactorEnabled) { if (!twoFactorCode.isPresent) { - sender.sendMessage( - Component.text("Please provide two-factor authentication code.", TextColors.RED) - ) + multification.send(sender) { it.multification.twoFactorCodeRequiredError } return } } @@ -101,16 +101,14 @@ constructor(val authSessionService: AuthSessionService) { val uniqueSessionId = VelocityUniqueSessionId(sender) val session = authSessionService.findSession(uniqueSessionId) if (session?.getSessionType() != AuthSessionType.LOGIN || session.isAuthenticated) { - sender.sendMessage(Component.text("Can't use this command right now.", TextColors.RED)) + multification.send(sender) { it.multification.cantUseThisCommandNowError } return } session as VelocityLoginAuthSession if (!session.userCredentials.isTwoFactorEnabled || session.userCredentials.isPasswordRequired) { - sender.sendMessage( - Component.text("Can't use 2fa command right now. Please use /login command.") - ) + multification.send(sender) { it.multification.cantUseThisCommandNowError } return } @@ -124,10 +122,9 @@ constructor(val authSessionService: AuthSessionService) { code: String?, ) { if (session.auth(password, code)) { - sender.sendMessage(Component.text("Logged in!", TextColors.GREEN)) return } - sender.sendMessage(Component.text("Wrong credentials!", TextColors.RED)) + multification.send(sender) { it.multification.wrongCredentialsError } } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/PremiumAccountCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/PremiumAccountCommand.kt index 82f67d7..46edf00 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/PremiumAccountCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/PremiumAccountCommand.kt @@ -25,20 +25,23 @@ import dev.rollczi.litecommands.annotations.async.Async import dev.rollczi.litecommands.annotations.command.Command import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute -import net.kyori.adventure.text.Component import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.mojang.MojangProfileService import pl.spcode.navauth.common.application.user.UserService import pl.spcode.navauth.common.command.exception.MissingPermissionException -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.common.domain.user.Username import pl.spcode.navauth.velocity.command.Permissions +import pl.spcode.navauth.velocity.multification.VelocityMultification // inverted permission @Command(name = "premium") class PremiumAccountCommand @Inject -constructor(val userService: UserService, val mojangProfileService: MojangProfileService) { +constructor( + val userService: UserService, + val mojangProfileService: MojangProfileService, + val multification: VelocityMultification, +) { @Async @Execute @@ -56,22 +59,20 @@ constructor(val userService: UserService, val mojangProfileService: MojangProfil val user = userService.findUserByExactUsername(sender.username)!! if (user.isPremium) { - sender.sendMessage(Component.text("Account is already set as a premium one.", TextColors.RED)) + multification.send(sender) { it.multification.accountAlreadyPremiumError } return } val mojangProfile = mojangProfileService.fetchProfileInfo(Username(sender.username)) if (mojangProfile == null) { - sender.sendMessage( - Component.text( - "Can't set this account as premium because there's no premium account with username '${sender.username}'.", - TextColors.RED, - ) - ) + multification + .create(sender) { it.multification.commandNoPremiumAccountWithUsername } + .placeholder("%USERNAME%", sender.username) + .send() return } userService.migrateToPremium(user, mojangProfile.uuid) - sender.sendMessage(Component.text("Account migrated successfully!", TextColors.GREEN)) + multification.create(sender) { it.multification.accountMigrationSuccess } } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/RegisterCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/RegisterCommand.kt index 6b278d8..0760f16 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/RegisterCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/RegisterCommand.kt @@ -26,15 +26,12 @@ import dev.rollczi.litecommands.annotations.async.Async import dev.rollczi.litecommands.annotations.command.Command import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.TextColor import pl.spcode.navauth.api.domain.auth.AuthSessionType import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.auth.session.AuthSessionService import pl.spcode.navauth.common.application.user.UserService import pl.spcode.navauth.common.application.validator.PasswordValidator import pl.spcode.navauth.common.command.exception.MissingPermissionException -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.common.domain.user.User import pl.spcode.navauth.common.domain.user.UserUuid import pl.spcode.navauth.common.domain.user.Username @@ -42,6 +39,7 @@ import pl.spcode.navauth.common.infra.crypto.hasher.BCryptCredentialsHasher import pl.spcode.navauth.velocity.command.Permissions import pl.spcode.navauth.velocity.infra.auth.VelocityUniqueSessionId import pl.spcode.navauth.velocity.infra.player.VelocityPlayerAdapter +import pl.spcode.navauth.velocity.multification.VelocityMultification // we use inverted permission in this command @Command(name = "register") @@ -51,6 +49,7 @@ constructor( val authSessionService: AuthSessionService, val userService: UserService, val passwordValidator: PasswordValidator, + val multification: VelocityMultification, ) { @Async @@ -73,17 +72,17 @@ constructor( val session = authSessionService.findSession(VelocityUniqueSessionId(sender)) if (session?.getSessionType() != AuthSessionType.REGISTER || session.isAuthenticated) { - sender.sendMessage(Component.text("Can't use this command right now.", TextColors.RED)) + multification.send(sender) { it.multification.cantUseThisCommandNowError } return } if (!passwordValidator.isValid(password)) { - sender.sendMessage(Component.text("Password is invalid.", TextColors.RED)) + multification.send(sender) { it.multification.registerPasswordInvalidError } return } if (password != repeatPassword) { - sender.sendMessage(Component.text("Both passwords must match.", TextColors.RED)) + multification.send(sender) { it.multification.registerPasswordsMustMatchError } return } @@ -91,8 +90,7 @@ constructor( User.nonPremium(UserUuid(sender.uniqueId), Username(sender.username)), BCryptCredentialsHasher().hash(password), ) + // register session will send success message session.authenticate() - - sender.sendMessage(Component.text("Account created", TextColor.color(0, 200, 0))) } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/TwoFactorDisableCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/TwoFactorDisableCommand.kt index 8eea233..c51627f 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/TwoFactorDisableCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/TwoFactorDisableCommand.kt @@ -26,20 +26,23 @@ import dev.rollczi.litecommands.annotations.async.Async import dev.rollczi.litecommands.annotations.command.RootCommand import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute -import net.kyori.adventure.text.Component import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.credentials.UserCredentialsService import pl.spcode.navauth.common.application.user.UserService import pl.spcode.navauth.common.command.exception.MissingPermissionException -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.common.domain.user.UserUuid import pl.spcode.navauth.velocity.command.Permissions +import pl.spcode.navauth.velocity.multification.VelocityMultification // we use inverted permission in this command @RootCommand class TwoFactorDisableCommand @Inject -constructor(val userService: UserService, val userCredentialsService: UserCredentialsService) { +constructor( + val userService: UserService, + val userCredentialsService: UserCredentialsService, + val multification: VelocityMultification, +) { @Async @Execute(name = "disable2fa") @@ -53,17 +56,17 @@ constructor(val userService: UserService, val userCredentialsService: UserCreden val user = userService.findUserByUuid(UserUuid(sender.uniqueId))!! val credentials = userCredentialsService.findCredentials(user) if (credentials == null || !credentials.isTwoFactorEnabled) { - sender.sendMessage(Component.text("Your account has 2FA disabled already.", TextColors.RED)) + multification.send(sender) { it.multification.twoFactorAlreadyDisabledError } return } if (!userCredentialsService.verifyCode(credentials, code)) { - sender.sendMessage(Component.text("Wrong code!", TextColors.RED)) + multification.send(sender) { it.multification.twoFactorWrongCodeError } return } userService.disableTwoFactorAuth(user) - sender.sendMessage(Component.text("2FA disabled!", TextColors.GREEN)) + multification.send(sender) { it.multification.twoFactorDisabledSuccess } return } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/TwoFactorSetupCommand.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/TwoFactorSetupCommand.kt index 2845ce5..8aa7f6f 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/TwoFactorSetupCommand.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/command/user/TwoFactorSetupCommand.kt @@ -27,13 +27,11 @@ import dev.rollczi.litecommands.annotations.command.RootCommand import dev.rollczi.litecommands.annotations.context.Context import dev.rollczi.litecommands.annotations.execute.Execute import java.util.Optional -import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage import pl.spcode.navauth.common.annotation.Description import pl.spcode.navauth.common.application.credentials.UserCredentialsService import pl.spcode.navauth.common.application.user.UserService import pl.spcode.navauth.common.command.exception.MissingPermissionException -import pl.spcode.navauth.common.component.TextColors import pl.spcode.navauth.common.config.MessagesConfig import pl.spcode.navauth.common.config.TwoFactorAuthConfig import pl.spcode.navauth.common.domain.user.UserUuid @@ -57,7 +55,7 @@ constructor( val totpSetupSessionService: TotpSetupSessionService, val twoFactorAuthConfig: TwoFactorAuthConfig, val messages: MessagesConfig, - val velocityMultification: VelocityMultification, + val multification: VelocityMultification, ) { @Async @@ -75,25 +73,20 @@ constructor( val user = userService.findUserByUuid(UserUuid(sender.uniqueId))!! val credentials = userCredentialsService.findCredentials(user) if (credentials?.isTwoFactorEnabled == true) { - sender.sendMessage( - Component.text( - "Can't execute this command right now: your account has 2FA enabled already.", - TextColors.RED, - ) - ) + multification.send(sender) { it.multification.twoFactorAlreadyEnabledError } return } if (credentials?.isPasswordRequired == true) { if (currentPassword.isEmpty) { - sender.sendMessage(Component.text("Please provide your current password.", TextColors.RED)) + multification.send(sender) { it.multification.passwordRequiredError } return } val isCorrectPassword = userCredentialsService.verifyPassword(credentials, currentPassword.get()) if (!isCorrectPassword) { - sender.sendMessage(Component.text("Wrong password!", TextColors.RED)) + multification.send(sender) { it.multification.wrongCredentialsError } return } } @@ -103,7 +96,7 @@ constructor( totpSetupSessionService.registerSession(UserUuid(sender.uniqueId), session) val remainingSeconds = TotpSetupSessionService.SESSION_LIFETIME_SECONDS - velocityMultification + multification .create() .player(sender.uniqueId) .notice(messages.multification.twoFactorSetupInstruction) @@ -121,29 +114,24 @@ constructor( val user = userService.findUserByUuid(UserUuid(sender.uniqueId))!! if (userCredentialsService.findCredentials(user)?.isTwoFactorEnabled == true) { - sender.sendMessage(Component.text("2FA is already enabled for this account.", TextColors.RED)) + multification.send(sender) { it.multification.twoFactorAlreadyEnabledError } return } val session = totpSetupSessionService.findSession(UserUuid(sender.uniqueId)) if (session == null) { - sender.sendMessage( - Component.text( - "2FA setup session not found. Please try again using /setup2fa command first.", - TextColors.RED, - ) - ) + multification.send(sender) { it.multification.twoFactorSessionNotFound } return } if (!TOTP2FA().verifyTOTP(session.secret, code)) { - sender.sendMessage(Component.text("Invalid 2FA code!", TextColors.RED)) + multification.send(sender) { it.multification.twoFactorWrongCodeError } return } userService.enableTwoFactorAuth(user, session.secret) totpSetupSessionService.closeSession(UserUuid(sender.uniqueId)) - sender.sendMessage(Component.text("2FA successfully enabled!", TextColors.GREEN)) + multification.send(sender) { it.multification.twoFactorEnabledSuccess } } @Execute(name = "generate2faqr") @@ -154,12 +142,7 @@ constructor( fun generateQrCode(@Context sender: Player) { val session = totpSetupSessionService.findSession(UserUuid(sender.uniqueId)) if (session == null) { - sender.sendMessage( - Component.text( - "2FA setup session not found. Please try again using /setup2fa command first.", - TextColors.RED, - ) - ) + multification.send(sender) { it.multification.twoFactorSessionNotFound } return } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/extension/PlayerDisconnectExtension.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/extension/PlayerDisconnectExtension.kt new file mode 100644 index 0000000..7285797 --- /dev/null +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/extension/PlayerDisconnectExtension.kt @@ -0,0 +1,32 @@ +/* + * NavAuth + * Copyright © 2026 Oliwier Fijas (Navio1430) + * + * NavAuth is free software; You can redistribute it and/or modify it under the terms of: + * the GNU Affero General Public License version 3 as published by the Free Software Foundation. + * + * NavAuth is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with NavAuth. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + * + */ + +package pl.spcode.navauth.velocity.extension + +import com.velocitypowered.api.proxy.Player +import net.kyori.adventure.text.Component + +class PlayerDisconnectExtension { + companion object { + fun Player.disconnectIfActive(reason: Component) { + if (this.isActive) { + this.disconnect(reason) + } + } + } +} diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityAutoLoginAuthSession.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityAutoLoginAuthSession.kt index f514147..b7d55ae 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityAutoLoginAuthSession.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityAutoLoginAuthSession.kt @@ -46,11 +46,13 @@ class VelocityAutoLoginAuthSession( scheduler .buildTask( Runnable { - multification - .create() - .player(player.uniqueId) - .notice(messagesConfig.multification.premiumAuthSuccess) - .send() + if (player.isActive) { + multification + .create() + .player(player.uniqueId) + .notice(messagesConfig.multification.premiumAuthSuccess) + .send() + } } ) .delay(Duration.ofSeconds(1)) diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityLoginAuthSession.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityLoginAuthSession.kt index 7faa4d3..d444e3b 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityLoginAuthSession.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityLoginAuthSession.kt @@ -28,6 +28,7 @@ import pl.spcode.navauth.common.config.MessagesConfig import pl.spcode.navauth.common.domain.credentials.UserCredentials import pl.spcode.navauth.common.infra.auth.LoginAuthSession import pl.spcode.navauth.velocity.application.event.VelocityEventDispatcher +import pl.spcode.navauth.velocity.extension.PlayerDisconnectExtension.Companion.disconnectIfActive import pl.spcode.navauth.velocity.infra.player.VelocityPlayerAdapter import pl.spcode.navauth.velocity.multification.VelocityMultification import pl.spcode.navauth.velocity.scheduler.NavAuthScheduler @@ -58,7 +59,9 @@ class VelocityLoginAuthSession( disconnectPlayerTask = scheduler .buildTask( - Runnable { player.disconnect(messagesConfig.loginTimeExceededError.toComponent()) } + Runnable { + player.disconnectIfActive(messagesConfig.loginTimeExceededError.toComponent()) + } ) .delay(generalConfig.maxLoginDuration) .schedule() @@ -96,6 +99,10 @@ class VelocityLoginAuthSession( cancelTasks() } + override fun onTooManyLoginAttempts() { + player.disconnectIfActive(messagesConfig.loginTooManyAttemptsError.toComponent()) + } + fun cancelTasks() { notifyMessageTask.cancel() disconnectPlayerTask.cancel() diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityRegisterAuthSession.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityRegisterAuthSession.kt index 969d8ed..732795c 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityRegisterAuthSession.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/auth/VelocityRegisterAuthSession.kt @@ -26,6 +26,7 @@ import pl.spcode.navauth.common.config.GeneralConfig import pl.spcode.navauth.common.config.MessagesConfig import pl.spcode.navauth.common.infra.auth.RegisterAuthSession import pl.spcode.navauth.velocity.application.event.VelocityEventDispatcher +import pl.spcode.navauth.velocity.extension.PlayerDisconnectExtension.Companion.disconnectIfActive import pl.spcode.navauth.velocity.infra.player.VelocityPlayerAdapter import pl.spcode.navauth.velocity.multification.VelocityMultification import pl.spcode.navauth.velocity.scheduler.NavAuthScheduler @@ -47,7 +48,9 @@ class VelocityRegisterAuthSession( disconnectPlayerTask = scheduler .buildTask( - Runnable { player.disconnect(messagesConfig.registerTimeExceededError.toComponent()) } + Runnable { + player.disconnectIfActive(messagesConfig.registerTimeExceededError.toComponent()) + } ) .delay(generalConfig.maxRegistrationDuration) .schedule() diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/player/VelocityPlayerAdapter.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/player/VelocityPlayerAdapter.kt index 1b62a25..64921f1 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/player/VelocityPlayerAdapter.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/infra/player/VelocityPlayerAdapter.kt @@ -37,6 +37,9 @@ class VelocityPlayerAdapter(val velocityPlayer: Player) : PlayerAdapter { } override fun disconnect(reason: DisconnectReason) { + if (!isOnline()) { + return + } when (reason) { DisconnectReason.AUTH_SESSION_CLOSED -> velocityPlayer.disconnect( @@ -46,8 +49,12 @@ class VelocityPlayerAdapter(val velocityPlayer: Player) : PlayerAdapter { ) ) DisconnectReason.TOO_MANY_LOGIN_ATTEMPTS -> + // this is handled by VelocityLoginAuthSession itself velocityPlayer.disconnect( - Component.text("Too many login attempts. Please try again later.", TextColors.RED) + Component.text( + "NavAuth: Too many login attempts. Please try again later.", + TextColors.RED, + ) ) } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/listener/velocity/ConnectionListeners.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/listener/velocity/ConnectionListeners.kt index 298a674..a5714e4 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/listener/velocity/ConnectionListeners.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/listener/velocity/ConnectionListeners.kt @@ -35,6 +35,7 @@ import pl.spcode.navauth.common.config.GeneralConfig import pl.spcode.navauth.common.domain.auth.session.AuthSession import pl.spcode.navauth.common.domain.auth.session.AuthSessionState import pl.spcode.navauth.velocity.application.server.VelocityServerSelectionService +import pl.spcode.navauth.velocity.extension.PlayerDisconnectExtension.Companion.disconnectIfActive import pl.spcode.navauth.velocity.infra.auth.VelocityUniqueSessionId import pl.spcode.navauth.velocity.infra.player.VelocityPlayerAdapter @@ -66,7 +67,7 @@ constructor( logger.warn( "OnServerConnect: player ${player.username} tried to connect without an auth session, disconnecting the player..." ) - player.disconnect( + player.disconnectIfActive( Component.text( "NavAuth: user tried to connect into a server without an auth session", TextColors.RED, @@ -84,7 +85,7 @@ constructor( player.uniqueId, authSession.toString(), ) - player.disconnect( + player.disconnectIfActive( Component.text("NavAuth: bad auth state on server connect", TextColors.RED) ) event.result = ServerPreConnectEvent.ServerResult.denied() @@ -92,7 +93,7 @@ constructor( } if (!generalConfig.limboServers.contains(event.originalServer.serverInfo.name)) { - player.disconnect( + player.disconnectIfActive( Component.text( "NavAuth: Tried to connect into different server than limbo, while waiting for limbo handler.", TextColors.RED, @@ -116,7 +117,7 @@ constructor( logger.warn( "PlayerChooseInitialServer: server tried to pick initial server for ${player.username} user without an auth session, disconnecting the player..." ) - player.disconnect( + player.disconnectIfActive( Component.text( "NavAuth: server tried to choose an initial server for you without an auth session", TextColors.RED, @@ -173,7 +174,7 @@ constructor( player.uniqueId, authSession.toString(), ) - player.disconnect( + player.disconnectIfActive( Component.text( "NavAuth: can't choose an initial limbo with a bad auth state", TextColors.RED, @@ -188,7 +189,9 @@ constructor( "PlayerChooseInitialServerEvent: no initial limbo was found for unauthenticated user '{}'", player.username, ) - player.disconnect(Component.text("NavAuth: initial limbo server not found.", TextColors.RED)) + player.disconnectIfActive( + Component.text("NavAuth: initial limbo server not found.", TextColors.RED) + ) return } @@ -206,7 +209,7 @@ constructor( try { block() } catch (e: Throwable) { - player.disconnect(Component.text("NavAuth: internal error", TextColors.RED)) + player.disconnectIfActive(Component.text("NavAuth: internal error", TextColors.RED)) logger.error("unexpected internal error occurred", e) } } diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/listener/velocity/LoginListeners.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/listener/velocity/LoginListeners.kt index f836a04..92093fc 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/listener/velocity/LoginListeners.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/listener/velocity/LoginListeners.kt @@ -45,6 +45,7 @@ import pl.spcode.navauth.common.domain.user.User import pl.spcode.navauth.common.domain.user.UserUuid import pl.spcode.navauth.common.domain.user.Username import pl.spcode.navauth.velocity.application.auth.session.VelocityAuthSessionFactory +import pl.spcode.navauth.velocity.extension.PlayerDisconnectExtension.Companion.disconnectIfActive import pl.spcode.navauth.velocity.infra.auth.VelocityUniqueSessionId class LoginListeners @@ -67,9 +68,8 @@ constructor( val connUsername = event.username if (usernameValidator.isValid(connUsername).not()) { - // todo send error message - event.result = - PreLoginEvent.PreLoginComponentResult.denied(Component.text("Invalid username")) + val reason = messagesConfig.invalidUsernameError.toComponent() + event.result = PreLoginEvent.PreLoginComponentResult.denied(reason) return } @@ -139,7 +139,7 @@ constructor( player.uniqueId, ) // there must be an auth session for specified user, otherwise abort - player.disconnect( + player.disconnectIfActive( Component.text("NavAuth: Auth session expired, please try again", TextColors.RED) ) return @@ -213,7 +213,7 @@ constructor( player.uniqueId, handshakeSession.toString(), ) - player.disconnect(Component.text("NavAuth: Bad auth state", TextColors.RED)) + player.disconnectIfActive(Component.text("NavAuth: Bad auth state", TextColors.RED)) } private fun createAndStorePremiumUser(player: Player) { diff --git a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/multification/VelocityMultification.kt b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/multification/VelocityMultification.kt index d38e491..dab67c1 100644 --- a/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/multification/VelocityMultification.kt +++ b/navauth-velocity/src/main/kotlin/pl/spcode/navauth/velocity/multification/VelocityMultification.kt @@ -20,6 +20,8 @@ package pl.spcode.navauth.velocity.multification import com.eternalcode.multification.Multification import com.eternalcode.multification.adventure.AudienceConverter +import com.eternalcode.multification.notice.Notice +import com.eternalcode.multification.notice.NoticeBroadcast import com.eternalcode.multification.translation.TranslationProvider import com.eternalcode.multification.viewer.ViewerProvider import com.google.inject.Inject @@ -32,9 +34,25 @@ import pl.spcode.navauth.common.config.MessagesConfig open class VelocityMultification @Inject -constructor(private val config: MessagesConfig, private val provider: VelocityViewerProvider) : +constructor(val config: MessagesConfig, private val provider: VelocityViewerProvider) : Multification() { + fun send(sender: CommandSource, notice: (config: MessagesConfig) -> Notice) { + create(sender, notice).send() + } + + fun create( + sender: CommandSource, + notice: (config: MessagesConfig) -> Notice, + ): NoticeBroadcast<*, *, *> { + val notification = notice(config) + return if (sender !is Player) { + create().notice(notification).console() + } else { + create().notice(notification).player(sender.uniqueId) + } + } + override fun viewerProvider(): ViewerProvider { return provider }