/* * The MIT License (MIT) * * Copyright (c) 2015 games647 and contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.github.games647.flexiblelogin; import com.github.games647.flexiblelogin.commands.*; import com.github.games647.flexiblelogin.config.Settings; import com.github.games647.flexiblelogin.hasher.BcryptHasher; import com.github.games647.flexiblelogin.hasher.Hasher; import com.github.games647.flexiblelogin.hasher.TOTP; import com.github.games647.flexiblelogin.listener.ConnectionListener; import com.github.games647.flexiblelogin.listener.PreventListener; import com.google.common.collect.Maps; import com.google.inject.Inject; import ninja.leaping.configurate.commented.CommentedConfigurationNode; import ninja.leaping.configurate.loader.ConfigurationLoader; import org.slf4j.Logger; import org.spongepowered.api.Game; import org.spongepowered.api.command.CommandManager; import org.spongepowered.api.command.args.GenericArguments; import org.spongepowered.api.command.spec.CommandSpec; import org.spongepowered.api.config.DefaultConfig; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.game.state.GameInitializationEvent; import org.spongepowered.api.event.game.state.GamePreInitializationEvent; import org.spongepowered.api.event.game.state.GameStoppedServerEvent; import org.spongepowered.api.plugin.Plugin; import org.spongepowered.api.plugin.PluginContainer; import org.spongepowered.api.text.Text; import java.nio.file.Path; import java.util.Map; @Plugin(id = "flexiblelogin", name = "FlexibleLogin", version = "0.12.1" , url = "https://github.com/games647/FlexibleLogin" , description = "A Sponge minecraft server plugin for second authentication.") public class FlexibleLogin { private static FlexibleLogin instance; public static FlexibleLogin getInstance() { return instance; } private final PluginContainer pluginContainer; private final Logger logger; private final Game game; @Inject @DefaultConfig(sharedRoot = false) //We will place more than one config there (i.e. H2/SQLite database) private Path defaultConfigFile; @Inject @DefaultConfig(sharedRoot = false) private ConfigurationLoader<CommentedConfigurationNode> configManager; private Map<String, Integer> attempts = Maps.newConcurrentMap(); private Settings configuration; private Database database; private ProtectionManager protectionManager; private Hasher hasher; @Inject public FlexibleLogin(Logger logger, PluginContainer pluginContainer, Game game) { instance = this; this.logger = logger; this.pluginContainer = pluginContainer; this.game = game; } @Listener //During this state, the plugin gets ready for initialization. Logger and config public void onPreInit(GamePreInitializationEvent preInitEvent) { configuration = new Settings(configManager, defaultConfigFile); configuration.load(); database = new Database(); database.createTable(); protectionManager = new ProtectionManager(); if (configuration.getConfig().getHashAlgo().equalsIgnoreCase("totp")) { hasher = new TOTP(); } else { //use bcrypt as fallback for now hasher = new BcryptHasher(); } } @Listener //Commands register + events public void onInit(GameInitializationEvent initEvent) { //register commands CommandManager commandDispatcher = game.getCommandManager(); commandDispatcher.register(this, CommandSpec.builder() .executor(new LoginCommand()) .arguments(GenericArguments.onlyOne(GenericArguments.string(Text.of("password")))) .build(), "login", "log"); commandDispatcher.register(this, CommandSpec.builder() .executor(new RegisterCommand()) .arguments(GenericArguments .optional(GenericArguments .repeated(GenericArguments .string(Text.of("password")), 2))) .build(), "register", "reg"); commandDispatcher.register(this, CommandSpec.builder() .executor(new ChangePasswordCommand()) .arguments(GenericArguments .repeated(GenericArguments .string(Text.of("password")), 2)) .build(), "changepassword", "changepw"); commandDispatcher.register(this, CommandSpec.builder() .executor(new SetEmailCommand()) .arguments(GenericArguments.onlyOne(GenericArguments.string(Text.of("email")))) .build(), "setemail", "email"); commandDispatcher.register(this, CommandSpec.builder() .executor(new UnregisterCommand()) .build(), "forgotpassword", "forgot"); commandDispatcher.register(this, CommandSpec.builder() .executor(new LogoutCommand()) .build(), "logout"); //admin commands commandDispatcher.register(this, CommandSpec.builder() .permission(pluginContainer.getName() + ".admin") .child(CommandSpec.builder() .executor(new ReloadCommand()) .build(), "reload", "rl") .child(CommandSpec.builder() .executor(new UnregisterCommand()) .arguments(GenericArguments.onlyOne(GenericArguments.string(Text.of("account")))) .build(), "unregister", "unreg") .child(CommandSpec.builder() .executor(new ForceRegisterCommand()) .arguments( GenericArguments.onlyOne(GenericArguments .string(Text.of("account"))), GenericArguments.string(Text.of("password"))) .build(), "register", "reg") .child(CommandSpec.builder() .executor(new ResetPasswordCommand()) .arguments( GenericArguments.onlyOne(GenericArguments .string(Text.of("account"))), GenericArguments.string(Text.of("password"))) .build(), "resetpw", "resetpassword") .build(), pluginContainer.getName()); //register events game.getEventManager().registerListeners(this, new ConnectionListener()); game.getEventManager().registerListeners(this, new PreventListener()); } @Listener public void onDisable(GameStoppedServerEvent gameStoppedEvent) { //run this task sync in order let it finish before the process ends database.close(); game.getServer().getOnlinePlayers().forEach(protectionManager::unprotect); } public void onReload() { //run this task sync in order let it finish before the process ends database.close(); game.getServer().getOnlinePlayers().forEach(protectionManager::unprotect); configuration.load(); database = new Database(); database.createTable(); if (configuration.getConfig().getHashAlgo().equalsIgnoreCase("totp")) { hasher = new TOTP(); } else { //use bcrypt as fallback for now hasher = new BcryptHasher(); } game.getServer().getOnlinePlayers().forEach(protectionManager::protect); game.getServer().getOnlinePlayers().forEach(database::loadAccount); } public Settings getConfigManager() { return configuration; } public PluginContainer getContainer() { return pluginContainer; } public Logger getLogger() { return logger; } public Game getGame() { return game; } public Database getDatabase() { return database; } public Map<String, Integer> getAttempts() { return attempts; } public ProtectionManager getProtectionManager() { return protectionManager; } public void setDatabase(Database database) { this.database = database; } public void setHasher(Hasher hasher) { this.hasher = hasher; } public Hasher getHasher() { //this is thread-safe because it won't change after config load return hasher; } }