mirror of
https://github.com/NekoMonci12/Git-Craft.git
synced 2025-12-19 14:59:22 +00:00
Git Credentials Manager
This commit is contained in:
@@ -18,6 +18,7 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
|
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
|
||||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||||
|
implementation("com.h2database:h2:2.2.224")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable default JAR task
|
// Disable default JAR task
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.yuemi.commands;
|
package org.yuemi.commands;
|
||||||
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.yuemi.git.H2PasswordProvider;
|
||||||
|
import org.yuemi.commands.GitRootCommand;
|
||||||
|
|
||||||
public class CommandRegistrar {
|
public class CommandRegistrar {
|
||||||
|
|
||||||
@@ -11,6 +13,16 @@ public class CommandRegistrar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void registerAll() {
|
public void registerAll() {
|
||||||
plugin.getCommand("git").setExecutor(new GitRootCommand(plugin));
|
try {
|
||||||
|
H2PasswordProvider provider = new H2PasswordProvider(plugin.getDataFolder());
|
||||||
|
String[] passwords = provider.getOrGeneratePasswords();
|
||||||
|
String filePassword = passwords[0];
|
||||||
|
String dbPassword = passwords[1];
|
||||||
|
|
||||||
|
plugin.getCommand("git").setExecutor(new GitRootCommand(plugin, filePassword, dbPassword));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("§cFailed to initialize Git command: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ public class GitRootCommand implements CommandExecutor {
|
|||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final SubcommandHandler handler;
|
private final SubcommandHandler handler;
|
||||||
|
|
||||||
public GitRootCommand(JavaPlugin plugin) {
|
public GitRootCommand(JavaPlugin plugin, String filePassword, String dbPassword) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.handler = new SubcommandHandler(plugin);
|
this.handler = new SubcommandHandler(plugin, filePassword, dbPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -12,14 +12,21 @@ import org.yuemi.commands.subcommands.GitFetchSubcommand;
|
|||||||
import org.yuemi.commands.subcommands.GitPullSubcommand;
|
import org.yuemi.commands.subcommands.GitPullSubcommand;
|
||||||
import org.yuemi.commands.subcommands.GitStatusSubcommand;
|
import org.yuemi.commands.subcommands.GitStatusSubcommand;
|
||||||
import org.yuemi.commands.subcommands.GitHelpSubcommand;
|
import org.yuemi.commands.subcommands.GitHelpSubcommand;
|
||||||
|
import org.yuemi.commands.subcommands.GitLoginSubcommand;
|
||||||
|
import org.yuemi.commands.subcommands.GitWhoamiSubcommand;
|
||||||
|
import org.yuemi.commands.subcommands.GitLogoutSubcommand;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class SubcommandHandler {
|
public class SubcommandHandler {
|
||||||
|
|
||||||
private final Map<String, SubcommandExecutor> commands = new HashMap<>();
|
private final Map<String, SubcommandExecutor> commands = new HashMap<>();
|
||||||
|
private final String filePassword;
|
||||||
public SubcommandHandler(JavaPlugin plugin) {
|
private final String dbPassword;
|
||||||
|
|
||||||
|
public SubcommandHandler(JavaPlugin plugin, String filePassword, String dbPassword) {
|
||||||
|
this.filePassword = filePassword;
|
||||||
|
this.dbPassword = dbPassword;
|
||||||
commands.put("add", new GitAddSubcommand(plugin));
|
commands.put("add", new GitAddSubcommand(plugin));
|
||||||
commands.put("commit", new GitCommitSubcommand(plugin));
|
commands.put("commit", new GitCommitSubcommand(plugin));
|
||||||
commands.put("push", new GitPushSubcommand(plugin));
|
commands.put("push", new GitPushSubcommand(plugin));
|
||||||
@@ -30,6 +37,9 @@ public class SubcommandHandler {
|
|||||||
commands.put("pull", new GitPullSubcommand(plugin));
|
commands.put("pull", new GitPullSubcommand(plugin));
|
||||||
commands.put("status", new GitStatusSubcommand(plugin));
|
commands.put("status", new GitStatusSubcommand(plugin));
|
||||||
commands.put("help", new GitHelpSubcommand(plugin));
|
commands.put("help", new GitHelpSubcommand(plugin));
|
||||||
|
commands.put("login", new GitLoginSubcommand(plugin, filePassword, dbPassword));
|
||||||
|
commands.put("whoami", new GitWhoamiSubcommand(plugin, filePassword, dbPassword));
|
||||||
|
commands.put("logout", new GitLogoutSubcommand(plugin, filePassword, dbPassword));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handle(CommandSender sender, String name, String[] args) {
|
public void handle(CommandSender sender, String name, String[] args) {
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.yuemi.commands.subcommands;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.yuemi.commands.SubcommandExecutor;
|
||||||
|
import org.yuemi.git.GitCredentialDatabaseManager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class GitLoginSubcommand implements SubcommandExecutor {
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final GitCredentialDatabaseManager credentialDB;
|
||||||
|
|
||||||
|
public GitLoginSubcommand(JavaPlugin plugin, String filePassword, String dbPassword) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
File pluginFolder = plugin.getDataFolder();
|
||||||
|
this.credentialDB = new GitCredentialDatabaseManager(pluginFolder, filePassword, dbPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(CommandSender sender, String[] args) {
|
||||||
|
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
String username = null;
|
||||||
|
String token = null;
|
||||||
|
|
||||||
|
for (String arg : args) {
|
||||||
|
if (arg.startsWith("--username=")) {
|
||||||
|
username = arg.substring("--username=".length());
|
||||||
|
} else if (arg.startsWith("--token=")) {
|
||||||
|
token = arg.substring("--token=".length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username == null || token == null) {
|
||||||
|
sender.sendMessage("§cUsage: /git login --username=<user> --token=<token>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean replaced = credentialDB.saveCredentials(username, token, true);
|
||||||
|
sender.sendMessage("§aGit credentials " + (replaced ? "updated" : "saved") + " securely.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
sender.sendMessage("§cFailed to save credentials: " + e.getMessage());
|
||||||
|
plugin.getLogger().warning("Git login error: " + e.getMessage()); // No token or username in logs
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.yuemi.commands.subcommands;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.yuemi.commands.SubcommandExecutor;
|
||||||
|
import org.yuemi.git.GitCredentialDatabaseManager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class GitLogoutSubcommand implements SubcommandExecutor {
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final GitCredentialDatabaseManager credentialDB;
|
||||||
|
|
||||||
|
public GitLogoutSubcommand(JavaPlugin plugin, String filePassword, String dbPassword) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.credentialDB = new GitCredentialDatabaseManager(plugin.getDataFolder(), filePassword, dbPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(CommandSender sender, String[] args) {
|
||||||
|
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
try {
|
||||||
|
boolean deleted = credentialDB.deleteCredentials();
|
||||||
|
sender.sendMessage(deleted ? "§aLogged out successfully." : "§eNo credentials to delete.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
sender.sendMessage("§cFailed to delete credentials: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.yuemi.commands.subcommands;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.yuemi.commands.SubcommandExecutor;
|
||||||
|
import org.yuemi.git.GitCredentialDatabaseManager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class GitWhoamiSubcommand implements SubcommandExecutor {
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final GitCredentialDatabaseManager credentialDB;
|
||||||
|
|
||||||
|
public GitWhoamiSubcommand(JavaPlugin plugin, String filePassword, String dbPassword) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.credentialDB = new GitCredentialDatabaseManager(plugin.getDataFolder(), filePassword, dbPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(CommandSender sender, String[] args) {
|
||||||
|
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
try {
|
||||||
|
String[] creds = credentialDB.getCredentials();
|
||||||
|
if (creds != null) {
|
||||||
|
sender.sendMessage("§aLogged in as: §f" + creds[0]);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage("§eNo Git credentials saved.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
sender.sendMessage("§cError fetching credentials: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package org.yuemi.git;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class GitCredentialDatabaseManager {
|
||||||
|
|
||||||
|
private final String dbPath;
|
||||||
|
private final String filePassword;
|
||||||
|
private final String dbPassword;
|
||||||
|
|
||||||
|
public GitCredentialDatabaseManager(File pluginFolder, String filePassword, String dbPassword) {
|
||||||
|
this.dbPath = new File(pluginFolder, "gitcreds").getAbsolutePath();
|
||||||
|
this.filePassword = filePassword;
|
||||||
|
this.dbPassword = dbPassword;
|
||||||
|
try {
|
||||||
|
Class.forName("org.h2.Driver"); // Ensure the H2 JDBC driver is registered
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException("H2 Driver not found. Make sure it's shaded correctly.", e);
|
||||||
|
}
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Connection getConnection() throws SQLException {
|
||||||
|
String jdbcUrl = "jdbc:h2:file:" + dbPath + ";CIPHER=AES";
|
||||||
|
String user = "sa";
|
||||||
|
String password = filePassword + " " + dbPassword;
|
||||||
|
return DriverManager.getConnection(jdbcUrl, user, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialize() {
|
||||||
|
try (Connection conn = getConnection()) {
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.executeUpdate("""
|
||||||
|
CREATE TABLE IF NOT EXISTS credentials (
|
||||||
|
id IDENTITY PRIMARY KEY,
|
||||||
|
username VARCHAR NOT NULL,
|
||||||
|
token VARCHAR NOT NULL,
|
||||||
|
salt VARCHAR
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean saveCredentials(String username, String token, boolean withSalt) throws SQLException {
|
||||||
|
String salt = withSalt ? generateSalt() : null;
|
||||||
|
String tokenToStore = withSalt ? encodeWithSalt(token, salt) : token;
|
||||||
|
boolean replaced = false;
|
||||||
|
|
||||||
|
try (Connection conn = getConnection()) {
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM credentials");
|
||||||
|
if (rs.next() && rs.getInt(1) > 0) {
|
||||||
|
replaced = true;
|
||||||
|
stmt.executeUpdate("DELETE FROM credentials");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement("""
|
||||||
|
INSERT INTO credentials (username, token, salt)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
""")) {
|
||||||
|
ps.setString(1, username);
|
||||||
|
ps.setString(2, tokenToStore);
|
||||||
|
ps.setString(3, salt);
|
||||||
|
ps.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return replaced;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getCredentials() throws SQLException {
|
||||||
|
try (Connection conn = getConnection()) {
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
try (ResultSet rs = stmt.executeQuery("SELECT username, token, salt FROM credentials LIMIT 1")) {
|
||||||
|
if (rs.next()) {
|
||||||
|
String username = rs.getString("username");
|
||||||
|
String token = rs.getString("token");
|
||||||
|
String salt = rs.getString("salt");
|
||||||
|
if (salt != null) {
|
||||||
|
token = decodeWithSalt(token, salt);
|
||||||
|
}
|
||||||
|
return new String[]{username, token};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean deleteCredentials() throws SQLException {
|
||||||
|
try (Connection conn = getConnection()) {
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
return stmt.executeUpdate("DELETE FROM credentials") > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateSalt() {
|
||||||
|
byte[] salt = new byte[16];
|
||||||
|
new SecureRandom().nextBytes(salt);
|
||||||
|
return Base64.getEncoder().encodeToString(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encodeWithSalt(String token, String salt) {
|
||||||
|
byte[] tokenBytes = token.getBytes();
|
||||||
|
byte[] saltBytes = Base64.getDecoder().decode(salt);
|
||||||
|
byte[] result = new byte[tokenBytes.length];
|
||||||
|
for (int i = 0; i < tokenBytes.length; i++) {
|
||||||
|
result[i] = (byte) (tokenBytes[i] ^ saltBytes[i % saltBytes.length]);
|
||||||
|
}
|
||||||
|
return Base64.getEncoder().encodeToString(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decodeWithSalt(String encoded, String salt) {
|
||||||
|
byte[] tokenBytes = Base64.getDecoder().decode(encoded);
|
||||||
|
byte[] saltBytes = Base64.getDecoder().decode(salt);
|
||||||
|
byte[] result = new byte[tokenBytes.length];
|
||||||
|
for (int i = 0; i < tokenBytes.length; i++) {
|
||||||
|
result[i] = (byte) (tokenBytes[i] ^ saltBytes[i % saltBytes.length]);
|
||||||
|
}
|
||||||
|
return new String(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/src/main/java/org/yuemi/git/H2PasswordProvider.java
Normal file
50
app/src/main/java/org/yuemi/git/H2PasswordProvider.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package org.yuemi.git;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class H2PasswordProvider {
|
||||||
|
|
||||||
|
private final File configFile;
|
||||||
|
|
||||||
|
public H2PasswordProvider(File pluginFolder) {
|
||||||
|
if (!pluginFolder.exists()) {
|
||||||
|
pluginFolder.mkdirs();
|
||||||
|
}
|
||||||
|
this.configFile = new File(pluginFolder, "credentials.properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getOrGeneratePasswords() throws IOException {
|
||||||
|
if (configFile.exists()) {
|
||||||
|
Properties props = new Properties();
|
||||||
|
try (FileInputStream in = new FileInputStream(configFile)) {
|
||||||
|
props.load(in);
|
||||||
|
return new String[] {
|
||||||
|
props.getProperty("filePassword"),
|
||||||
|
props.getProperty("dbPassword")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String filePassword = generateRandomPassword();
|
||||||
|
String dbPassword = generateRandomPassword();
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.setProperty("filePassword", filePassword);
|
||||||
|
props.setProperty("dbPassword", dbPassword);
|
||||||
|
|
||||||
|
try (FileOutputStream out = new FileOutputStream(configFile)) {
|
||||||
|
props.store(out, "GitCraft H2 Passwords - Do not share");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String[] { filePassword, dbPassword };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateRandomPassword() {
|
||||||
|
byte[] randomBytes = new byte[16];
|
||||||
|
new SecureRandom().nextBytes(randomBytes);
|
||||||
|
return Base64.getEncoder().encodeToString(randomBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user