[REPO REFACTOR]: changed to a better git repository structure with branches

This commit is contained in:
2025-11-01 06:22:17 +01:00
parent 786322b6cd
commit 6ab8bccdd8
70 changed files with 4858 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
package net.miarma.byodsec;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.icons.google_material_design_icons.GoogleMaterialDesignIcons;
import jiconfont.swing.IconFontSwing;
import net.miarma.byodsec.common.ConfigManager;
import net.miarma.byodsec.common.db.DBPopulator;
import net.miarma.byodsec.client.ui.MainWindow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
@SuppressWarnings("all")
public class Byodsec {
private static final Logger logger = LoggerFactory.getLogger(Byodsec.class);
private static ConfigManager configManager = ConfigManager.getInstance();
public static void main(String[] args) {
initConfig();
initDB();
initProperties();
initJKS();
initUI();
}
private static void initConfig() {
File configFile = configManager.getConfigFile();
File parentDir = configFile.getParentFile();
if (!parentDir.exists()) {
if (parentDir.mkdirs()) {
logger.info("Created app directory: " + parentDir.getAbsolutePath());
} else {
logger.error("Failed to create app directory: " + parentDir.getAbsolutePath());
return;
}
}
if (!configFile.exists()) {
try (InputStream in = Byodsec.class.getClassLoader().getResourceAsStream("default.properties")) {
if (in != null) {
Files.copy(in, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
logger.info("Default config file created at: {}", configFile.getAbsolutePath());
} else {
logger.error("Resource default.properties not found!");
}
} catch (IOException e) {
logger.error("Error creating config file: ", e);
}
} else {
logger.info("Config file already exists at: {}", configFile.getAbsolutePath());
}
configManager.loadConfig();
}
private static void initDB() {
if(DBPopulator.getInstance().isFirstRun())
DBPopulator.getInstance().populate();
}
private static void initJKS() {
File jksFile = configManager.getJksFile();
if (!jksFile.exists()) {
String path = jksFile.getAbsolutePath();
String password = configManager.getStringProperty("jksPassword");
logger.info("Executing JKS initialization command...");
ProcessBuilder pb = new ProcessBuilder(
"keytool",
"-genkeypair",
"-alias", "byodsec",
"-keyalg", "RSA",
"-keystore", path,
"-storepass", password,
"-keypass", password,
"-dname", "CN=localhost, OU=Byodsec, O=Chat33, L=Sevilla, ST=Andalucia, C=ES",
"-validity", "365"
);
pb.inheritIO();
Process process = null;
try {
process = pb.start();
process.waitFor();
} catch (IOException e) {
logger.error("Error executing JKS initialization command: ", e);
throw new RuntimeException(e);
} catch (InterruptedException e) {
logger.error("Error executing JKS initialization command: ", e);
throw new RuntimeException(e);
}
}
}
private static void initProperties() {
// especificamos mediante propiedades el KS y la contraseña
System.setProperty("javax.net.ssl.keyStore", configManager.getJksFile().getAbsolutePath());
System.setProperty("javax.net.ssl.keyStorePassword", configManager.getStringProperty("jksPassword"));
System.setProperty("javax.net.ssl.trustStore", configManager.getJksFile().getAbsolutePath());
System.setProperty("javax.net.ssl.trustStorePassword", configManager.getStringProperty("jksPassword"));
}
private static void initUI() {
try {
UIManager.setLookAndFeel(new FlatMacDarkLaf());
} catch(UnsupportedLookAndFeelException e) {
logger.error("Error setting LaF. Falling back to default Swing looks.");
}
IconFontSwing.register(FontAwesome.getIconFont());
IconFontSwing.register(GoogleMaterialDesignIcons.getIconFont());
SwingUtilities.invokeLater(() -> {
new MainWindow().setVisible(true);
});
}
}

View File

@@ -0,0 +1,97 @@
package net.miarma.byodsec.client;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import javax.net.ssl.*;
import com.google.gson.Gson;
import net.miarma.byodsec.common.ConfigManager;
import net.miarma.byodsec.common.db.dto.LoginDTO;
import net.miarma.byodsec.common.db.dto.MessageDTO;
import net.miarma.byodsec.common.socket.SocketRequest;
import net.miarma.byodsec.common.socket.SocketResponse;
public class ClientSocket implements AutoCloseable {
private SSLSocket socket;
private PrintWriter output;
private BufferedReader input;
private final Gson gson = new Gson();
private final ConfigManager configManager = ConfigManager.getInstance();
public ClientSocket() throws IOException {
configManager.loadConfig();
// inicializamos el socket SSL, la versión de TLS y los Cipher Suites a utilizar
SSLSocketFactory factory;
try {
factory = initSSL();
} catch (Exception e) {
throw new IOException("Error initializing SSL context.", e);
}
this.socket = (SSLSocket) factory.createSocket("localhost", 6969);
this.socket.setEnabledProtocols(new String[]{ "TLSv1.3" });
this.socket.setEnabledCipherSuites(new String[]{
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256"
});
// buffers
this.output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
this.input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
private SSLSocketFactory initSSL() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(configManager.getJksFile().getAbsolutePath()),
configManager.getStringProperty("jksPassword").toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, configManager.getStringProperty("jksPassword").toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
public SocketResponse login(String username, String password) throws IOException {
SocketRequest msg = new SocketRequest(SocketRequest.Action.LOGIN, new LoginDTO(username, password));
output.println(gson.toJson(msg));
return gson.fromJson(input.readLine(), SocketResponse.class);
}
public SocketResponse register(String username, String password) throws IOException {
SocketRequest msg = new SocketRequest(SocketRequest.Action.REGISTER, new LoginDTO(username, password));
output.println(gson.toJson(msg));
return gson.fromJson(input.readLine(), SocketResponse.class);
}
public SocketResponse logout(String userId) throws IOException {
SocketRequest msg = new SocketRequest(SocketRequest.Action.LOGOUT, userId);
output.println(gson.toJson(msg));
return gson.fromJson(input.readLine(), SocketResponse.class);
}
public SocketResponse sendMessage(MessageDTO message) throws Exception {
SocketRequest msg = new SocketRequest(SocketRequest.Action.SEND_MESSAGE, message);
output.println(gson.toJson(msg));
return gson.fromJson(input.readLine(), SocketResponse.class);
}
@Override
public void close() {
try {
input.close();
output.close();
socket.close();
} catch (Exception ignored) {}
}
}

View File

@@ -0,0 +1,62 @@
package net.miarma.byodsec.client.ui;
import javax.swing.*;
import java.awt.*;
public class AutoShrinkLabel extends JLabel {
public AutoShrinkLabel(String text) {
super(text);
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
protected void paintComponent(Graphics g) {
String text = getText();
if (text == null || text.isEmpty()) {
super.paintComponent(g);
return;
}
Insets insets = getInsets();
int availableHeight = getHeight() - insets.top - insets.bottom;
int availableWidth = getWidth() - insets.left - insets.right;
if (availableWidth <= 0 || availableHeight <= 0) {
return;
}
Graphics2D graphics2d = (Graphics2D) g.create();
Font font = getFont();
int fontSize = font.getSize();
FontMetrics fm;
int textWidth;
int textHeight;
do {
Font testFont = font.deriveFont((float) fontSize);
fm = graphics2d.getFontMetrics(testFont);
textWidth = fm.stringWidth(text);
textHeight = fm.getHeight();
if (textWidth <= availableWidth && textHeight <= availableHeight) {
font = testFont;
break;
}
fontSize--;
} while (fontSize > 5);
graphics2d.setFont(font);
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = (getHeight() + fm.getAscent() - fm.getDescent()) / 2;
graphics2d.drawString(text, x, y);
graphics2d.dispose();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 50);
}
}

View File

@@ -0,0 +1,442 @@
/*
* Created by JFormDesigner on Wed Oct 01 16:21:10 CEST 2025
*/
package net.miarma.byodsec.client.ui;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import javax.swing.*;
import com.google.gson.Gson;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miarma.byodsec.common.Constants;
import net.miarma.byodsec.common.security.IntegrityProvider;
import net.miarma.byodsec.common.socket.SocketResponse;
import net.miarma.byodsec.common.socket.SocketStatus;
import net.miarma.byodsec.common.db.dto.MessageDTO;
import net.miarma.byodsec.common.db.entities.UserEntity;
import net.miarma.byodsec.client.ClientSocket;
import net.miginfocom.swing.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jomaa
*/
public class MainWindow extends JFrame {
public static UserEntity _loggedUser;
private static final Logger logger = LoggerFactory.getLogger(ClientSocket.class);
public MainWindow() {
initComponents();
this.setTitle(Constants.APP_NAME);
versionLabel.setText(Constants.APP_NAME + " v" + Constants.APP_VERSION + " by Security Team 33");
setIcons();
}
private void setIcons() {
userLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.USER, 18, Color.WHITE));
passwordLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.KEY, 18, Color.WHITE));
loginBtn.setIcon(IconFontSwing.buildIcon(FontAwesome.SIGN_IN, 18, Color.WHITE));
registerBtn.setIcon(IconFontSwing.buildIcon(FontAwesome.USER_PLUS, 18, Color.WHITE));
clientLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.USER_CIRCLE, 16, Color.WHITE));
logoutBtn.setIcon(IconFontSwing.buildIcon(FontAwesome.SIGN_OUT, 16, Color.WHITE));
}
private void clearLoginInputs() {
userTextField.setText("");
passwordTextField.setText("");
}
private void clearMainInputs() {
clientTextField.setText("");
}
private void appendChatMessage(String message) {
chatTextArea.append(message + "\r\n");
chatTextArea.setCaretPosition(chatTextArea.getDocument().getLength());
}
private void setView(String name) {
((CardLayout)getContentPane().getLayout()).show(getContentPane(), name);
}
private void loginBtn(ActionEvent e) {
String username = userTextField.getText();
String password = new String(passwordTextField.getPassword());
try (ClientSocket client = new ClientSocket()) {
SocketResponse res = client.login(username, password);
if (res.getStatus() == SocketStatus.OK) {
_loggedUser = new Gson().fromJson(
new Gson().toJson(res.getData()),
UserEntity.class
);
logger.info("El usuario '{}' ha iniciado sesión correctamente.", username);
JOptionPane.showMessageDialog(this,
res.getMessage().getMessage(),
res.getStatus().toString(),
JOptionPane.INFORMATION_MESSAGE
);
clientLabel.setText("Cliente: " + _loggedUser.getUserName());
this.setTitle(Constants.APP_NAME + " @" + _loggedUser.getUserName());
clearLoginInputs();
setView("mainPanel");
} else if(res.getStatus() == SocketStatus.ALREADY_LOGGED_IN) {
logger.info("El usuario '{}' ya tiene una sesión activa.", username);
JOptionPane.showMessageDialog(this,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje",
res.getStatus().toString(),
JOptionPane.WARNING_MESSAGE
);
} else {
logger.warn("Intento de login fallido para '{}': {}", username,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje");
JOptionPane.showMessageDialog(this,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje",
res.getStatus().toString(),
JOptionPane.ERROR_MESSAGE
);
}
} catch (IOException ex) {
logger.error("Error de conexión con el servidor al intentar loguear '{}': {}", username, ex.getMessage());
JOptionPane.showMessageDialog(this,
"Error de conexión con el servidor",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
}
private void registerBtn(ActionEvent e) {
String username = userTextField.getText();
String password = new String(passwordTextField.getPassword());
try (ClientSocket client = new ClientSocket()) {
SocketResponse res = client.register(username, password);
if(res.getStatus() == SocketStatus.OK) {
logger.info("Usuario '{}' registrado correctamente.", username);
} else {
logger.warn("Error al registrar '{}': {}", username,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje");
}
JOptionPane.showMessageDialog(this,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje",
res.getStatus().toString(),
res.getStatus() == SocketStatus.OK ?
JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE
);
clearLoginInputs();
} catch (IOException ex) {
logger.error("Error de conexión con el servidor al registrar '{}': {}", username, ex.getMessage());
JOptionPane.showMessageDialog(this,
"Error de conexión con el servidor",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
}
private void logout() {
if (_loggedUser != null) {
try (ClientSocket client = new ClientSocket()) {
SocketResponse res = client.logout(_loggedUser.getUserId());
if (res != null && res.getMessage() != null) {
if (res.getStatus() == SocketStatus.OK) {
logger.info("Usuario '{}' ha cerrado sesión correctamente.", _loggedUser.getUserName());
this.setTitle(Constants.APP_NAME);
} else {
logger.warn("Intento de logout de '{}' con estado: {}", _loggedUser.getUserName(), res.getStatus());
}
JOptionPane.showMessageDialog(this,
res.getMessage().getMessage(),
res.getStatus().toString(),
res.getStatus() == SocketStatus.OK ?
JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE
);
}
} catch (IOException ex) {
logger.error("Error de conexión con el servidor al hacer logout de '{}': {}", _loggedUser.getUserName(), ex.getMessage());
JOptionPane.showMessageDialog(this,
"Error de conexión con el servidor",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
}
clearMainInputs();
_loggedUser = null;
}
private void thisWindowClosing(WindowEvent e) {
logout();
}
private void send(ActionEvent e) {
try (ClientSocket client = new ClientSocket()) {
String message = clientTextField.getText();
MessageDTO dto = new MessageDTO(_loggedUser.getUserName(), message);
SocketResponse res = client.sendMessage(dto);
appendChatMessage(String.format("%s: %s", _loggedUser.getUserName(), message));
appendChatMessage("Servidor: " + res.getData());
if (res.getStatus() == SocketStatus.OK) {
logger.info("Mensaje enviado: {} -> Servidor", _loggedUser.getUserName());
} else if (res.getStatus() == SocketStatus.SESSION_EXPIRED) {
logger.warn("Sesión expirada para '{}'. Se cerrará sesión.", _loggedUser.getUserName());
_loggedUser = null;
setView("loginPanel");
clearMainInputs();
} else {
logger.warn("Error enviando mensaje de {} a Servidor: {}", _loggedUser.getUserName(),
res.getMessage() != null ? res.getMessage().getMessage() : "Sin mensaje");
}
clearMainInputs();
} catch (Exception ex) {
logger.error("Error de conexión con el servidor enviando mensaje de {} a Servidor: {}",
_loggedUser.getUserName(), ex.getMessage());
JOptionPane.showMessageDialog(this,
"Error de conexión con el servidor",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
}
private void logoutBtn(ActionEvent e) {
logout();
setView("loginPanel");
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
// Generated using JFormDesigner Educational license - José Manuel Amador Gallardo (José Manuel Amador)
loginPanel = new JPanel();
logo = new JLabel();
userPanel = new JPanel();
userLabel = new JLabel();
userTextField = new JTextField();
passwordPanel = new JPanel();
passwordLabel = new JLabel();
passwordTextField = new JPasswordField();
loginBtn = new JButton();
btnLoginPanel = new JPanel();
noAccountLabel = new JLabel();
registerBtn = new JButton();
versionLabel = new JLabel();
mainPanel = new JPanel();
chatScrollPane = new JScrollPane();
chatTextArea = new JTextArea();
clientPanel = new JPanel();
clientLabel = new JLabel();
logoutBtn = new JButton();
clientTextField = new JTextField();
//======== this ========
setResizable(false);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
thisWindowClosing(e);
}
});
var contentPane = getContentPane();
contentPane.setLayout(new CardLayout(6, 6));
//======== loginPanel ========
{
loginPanel.setLayout(new MigLayout(
"hidemode 3,gapy 12",
// columns
"[grow,fill]",
// rows
"[]para" +
"[fill]" +
"[fill]para" +
"[]" +
"[grow,fill]"));
//---- logo ----
logo.setIcon(new ImageIcon(getClass().getResource("/images/logo.png")));
logo.setMaximumSize(new Dimension(256, 256));
logo.setMinimumSize(new Dimension(256, 256));
logo.setPreferredSize(new Dimension(256, 256));
logo.setHorizontalTextPosition(SwingConstants.CENTER);
loginPanel.add(logo, "cell 0 0,alignx center,growx 0");
//======== userPanel ========
{
userPanel.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[fill]" +
"[grow,fill]",
// rows
"[grow,fill]"));
//---- userLabel ----
userLabel.setText("Usuario:");
userLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
userLabel.setMaximumSize(new Dimension(130, 23));
userLabel.setMinimumSize(new Dimension(130, 23));
userLabel.setPreferredSize(new Dimension(130, 23));
userPanel.add(userLabel, "cell 0 0");
//---- userTextField ----
userTextField.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
userPanel.add(userTextField, "cell 1 0");
}
loginPanel.add(userPanel, "cell 0 1");
//======== passwordPanel ========
{
passwordPanel.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[fill]" +
"[grow,fill]",
// rows
"[grow,fill]"));
//---- passwordLabel ----
passwordLabel.setText("Contrase\u00f1a:");
passwordLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
passwordLabel.setMaximumSize(new Dimension(130, 23));
passwordLabel.setMinimumSize(new Dimension(130, 23));
passwordLabel.setPreferredSize(new Dimension(130, 23));
passwordPanel.add(passwordLabel, "cell 0 0");
//---- passwordTextField ----
passwordTextField.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
passwordPanel.add(passwordTextField, "cell 1 0");
}
loginPanel.add(passwordPanel, "cell 0 2");
//---- loginBtn ----
loginBtn.setText("Iniciar sesi\u00f3n");
loginBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
loginBtn.addActionListener(e -> loginBtn(e));
loginPanel.add(loginBtn, "cell 0 3,alignx trailing,growx 0");
//======== btnLoginPanel ========
{
btnLoginPanel.setLayout(new MigLayout(
"hidemode 3,aligny bottom",
// columns
"[grow,fill]",
// rows
"[]" +
"[fill]para" +
"[]"));
//---- noAccountLabel ----
noAccountLabel.setText("\u00bfNo tiene cuenta?");
btnLoginPanel.add(noAccountLabel, "cell 0 0,alignx center,growx 0");
//---- registerBtn ----
registerBtn.setText("Registrarse");
registerBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
registerBtn.addActionListener(e -> registerBtn(e));
btnLoginPanel.add(registerBtn, "cell 0 1,alignx center,growx 0");
//---- versionLabel ----
versionLabel.setForeground(new Color(0xa0a0a0));
btnLoginPanel.add(versionLabel, "cell 0 2,alignx center,growx 0");
}
loginPanel.add(btnLoginPanel, "cell 0 4");
}
contentPane.add(loginPanel, "loginPanel");
//======== mainPanel ========
{
mainPanel.setLayout(new MigLayout(
"hidemode 3",
// columns
"[grow,fill]",
// rows
"[grow,fill]" +
"[fill]" +
"[fill]"));
//======== chatScrollPane ========
{
//---- chatTextArea ----
chatTextArea.setEditable(false);
chatTextArea.setFocusable(false);
chatTextArea.setFont(new Font("Adwaita Mono", Font.PLAIN, 16));
chatScrollPane.setViewportView(chatTextArea);
}
mainPanel.add(chatScrollPane, "cell 0 0");
//======== clientPanel ========
{
clientPanel.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[grow,fill]" +
"[fill]",
// rows
"[]"));
//---- clientLabel ----
clientLabel.setText("Cliente: $c");
clientLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 16));
clientPanel.add(clientLabel, "cell 0 0");
//---- logoutBtn ----
logoutBtn.setText("Cerrar sesi\u00f3n");
logoutBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 16));
logoutBtn.addActionListener(e -> logoutBtn(e));
clientPanel.add(logoutBtn, "cell 1 0");
}
mainPanel.add(clientPanel, "cell 0 1");
//---- clientTextField ----
clientTextField.addActionListener(e -> send(e));
mainPanel.add(clientTextField, "cell 0 2");
}
contentPane.add(mainPanel, "mainPanel");
setSize(400, 600);
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
// Generated using JFormDesigner Educational license - José Manuel Amador Gallardo (José Manuel Amador)
private JPanel loginPanel;
private JLabel logo;
private JPanel userPanel;
private JLabel userLabel;
private JTextField userTextField;
private JPanel passwordPanel;
private JLabel passwordLabel;
private JPasswordField passwordTextField;
private JButton loginBtn;
private JPanel btnLoginPanel;
private JLabel noAccountLabel;
private JButton registerBtn;
private JLabel versionLabel;
private JPanel mainPanel;
private JScrollPane chatScrollPane;
private JTextArea chatTextArea;
private JPanel clientPanel;
private JLabel clientLabel;
private JButton logoutBtn;
private JTextField clientTextField;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
}

View File

@@ -0,0 +1,176 @@
JFDML JFormDesigner: "8.2.4.0.393" Java: "21.0.8" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormWindow( "javax.swing.JFrame", new FormLayoutManager( class java.awt.CardLayout ) {
"hgap": 6
"vgap": 6
} ) {
name: "this"
"$sizePolicy": 1
"resizable": false
"defaultCloseOperation": 3
addEvent( new FormEvent( "java.awt.event.WindowListener", "windowClosing", "thisWindowClosing", true ) )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3,gapy 12"
"$columnConstraints": "[grow,fill]"
"$rowConstraints": "[]para[fill][fill]para[][grow,fill]"
} ) {
name: "loginPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "logo"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/images/logo.png" )
"maximumSize": new java.awt.Dimension( 256, 256 )
"minimumSize": new java.awt.Dimension( 256, 256 )
"preferredSize": new java.awt.Dimension( 256, 256 )
"horizontalTextPosition": 0
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0,alignx center,growx 0"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[fill][grow,fill]"
"$rowConstraints": "[grow,fill]"
} ) {
name: "userPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "userLabel"
"text": "Usuario:"
"font": &Font0 new java.awt.Font( "Adwaita Sans", 0, 18 )
"maximumSize": new java.awt.Dimension( 130, 23 )
"minimumSize": new java.awt.Dimension( 130, 23 )
"preferredSize": new java.awt.Dimension( 130, 23 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "userTextField"
"font": &Font1 new java.awt.Font( "Adwaita Sans", 0, 18 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[fill][grow,fill]"
"$rowConstraints": "[grow,fill]"
} ) {
name: "passwordPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "passwordLabel"
"text": "Contraseña:"
"font": #Font0
"maximumSize": new java.awt.Dimension( 130, 23 )
"minimumSize": new java.awt.Dimension( 130, 23 )
"preferredSize": new java.awt.Dimension( 130, 23 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JPasswordField" ) {
name: "passwordTextField"
"font": #Font1
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "loginBtn"
"text": "Iniciar sesión"
"font": &Font2 new java.awt.Font( "Adwaita Sans", 0, 18 )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "loginBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3,alignx trailing,growx 0"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3,aligny bottom"
"$columnConstraints": "[grow,fill]"
"$rowConstraints": "[][fill]para[]"
} ) {
name: "btnLoginPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "noAccountLabel"
"text": "¿No tiene cuenta?"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0,alignx center,growx 0"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "registerBtn"
"text": "Registrarse"
"font": #Font2
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "registerBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1,alignx center,growx 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "versionLabel"
"foreground": new java.awt.Color( 160, 160, 160, 255 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2,alignx center,growx 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4"
} )
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "loginPanel"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[grow,fill]"
"$rowConstraints": "[grow,fill][fill][fill]"
} ) {
name: "mainPanel"
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "chatScrollPane"
add( new FormComponent( "javax.swing.JTextArea" ) {
name: "chatTextArea"
"editable": false
"focusable": false
"font": new java.awt.Font( "Adwaita Mono", 0, 16 )
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[grow,fill][fill]"
"$rowConstraints": "[]"
} ) {
name: "clientPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "clientLabel"
"text": "Cliente: $c"
"font": &Font3 new java.awt.Font( "Adwaita Sans", 0, 16 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "logoutBtn"
"text": "Cerrar sesión"
"font": #Font3
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "logoutBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "clientTextField"
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "send", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "mainPanel"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 400, 600 )
} )
}
}

View File

@@ -0,0 +1,105 @@
package net.miarma.byodsec.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
public class ConfigManager {
private static ConfigManager INSTANCE;
private final File jksFile;
private final File configFile;
private final Properties config;
private static final String JKS_FILE_NAME = "byodsec.jks";
private static final String CONFIG_FILE_NAME = "config.properties";
private final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
private ConfigManager() {
String jksPath = getBaseDir() + JKS_FILE_NAME;
String configPath = getBaseDir() + CONFIG_FILE_NAME;
this.jksFile = new File(jksPath);
this.configFile = new File(configPath);
this.config = new Properties();
}
public static ConfigManager getInstance() {
if(INSTANCE == null) {
INSTANCE = new ConfigManager();
}
return INSTANCE;
}
public void loadConfig() {
try (FileInputStream fis = new FileInputStream(configFile);
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
config.load(isr);
} catch (IOException e) {
logger.error("Error loading configuration file: ", e);
}
}
public File getJksFile() {
return jksFile;
}
public File getConfigFile() {
return configFile;
}
public String getHomeDir() {
return getOS() == OSType.WINDOWS ?
"C:/Users/" + System.getProperty("user.name") + "/" :
getOS() == OSType.LINUX ?
(System.getProperty("user.home").contains("root") ? "/root/" :
"/home/" + System.getProperty("user.name") + "/") :
getOS() == OSType.MACOS ? "/Users/" + System.getProperty("user.name") + "/" : System.getProperty("user.home");
}
public String getBaseDir() {
return getHomeDir() +
(getOS() == OSType.WINDOWS || getOS() == OSType.MACOS ? ".byodsec/" :
getOS() == OSType.LINUX ? ".config/byodsec/" :
".byodsec/");
}
public static OSType getOS() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
return OSType.WINDOWS;
} else if (os.contains("nix") || os.contains("nux")) {
return OSType.LINUX;
} else if (os.contains("mac")) {
return OSType.MACOS;
} else {
return OSType.INVALID_OS;
}
}
public String getStringProperty(String key) {
return config.getProperty(key);
}
public int getIntProperty(String key) {
String value = config.getProperty(key);
return value != null ? Integer.parseInt(value) : 10;
}
public boolean getBooleanProperty(String key) {
return Boolean.parseBoolean(config.getProperty(key));
}
public void setProperty(String key, String value) {
config.setProperty(key, value);
saveConfig();
}
private void saveConfig() {
try (FileOutputStream fos = new FileOutputStream(configFile)) {
config.store(fos, "Configuration for: " + Constants.APP_NAME);
} catch (IOException e) {
logger.error("Error saving configuration file: ", e);
}
}
}

View File

@@ -0,0 +1,10 @@
package net.miarma.byodsec.common;
public class Constants {
public static final String APP_NAME = "BYODSEC";
public static final String APP_VERSION = "1.0.0";
private Constants() {
throw new AssertionError("Utility class cannot be instantiated.");
}
}

View File

@@ -0,0 +1,9 @@
package net.miarma.byodsec.common;
/**
* Enum que representa los diferentes tipos de sistemas operativos soportados
* @author José Manuel Amador Gallardo
*/
public enum OSType {
LINUX, WINDOWS, MACOS, INVALID_OS
}

View File

@@ -0,0 +1,39 @@
package net.miarma.byodsec.common.db;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
public class DBConnector {
private static DBConnector instance;
private EntityManagerFactory emf;
private final ThreadLocal<EntityManager> threadLocal = new ThreadLocal<>();
private DBConnector() {
this.emf = Persistence.createEntityManagerFactory("ssii-pai2");
}
public static DBConnector getInstance() {
if (instance == null) {
instance = new DBConnector();
}
return instance;
}
public EntityManager getEntityManager() {
EntityManager em = threadLocal.get();
if (em == null || !em.isOpen()) {
em = emf.createEntityManager();
threadLocal.set(em);
}
return em;
}
public void close() {
EntityManager em = threadLocal.get();
if (em != null && em.isOpen()) {
em.close();
}
threadLocal.remove();
}
}

View File

@@ -0,0 +1,51 @@
package net.miarma.byodsec.common.db;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import net.miarma.byodsec.common.security.PasswordHasher;
import net.miarma.byodsec.common.db.entities.UserEntity;
import java.util.UUID;
public class DBPopulator {
private final DBConnector conn = DBConnector.getInstance();
private EntityManager em;
private static DBPopulator instance;
private DBPopulator() {
em = conn.getEntityManager();
}
public static DBPopulator getInstance() {
if (instance == null) {
instance = new DBPopulator();
}
return instance;
}
public boolean isFirstRun() {
TypedQuery<Long> q = em.createQuery("SELECT COUNT(u) FROM UserEntity u", Long.class);
Long count = q.getSingleResult();
return count == 0;
}
public void populate() {
UserEntity u1 = new UserEntity(UUID.randomUUID().toString(),
"ricolinad", PasswordHasher.hash("ricardo2025"));
UserEntity u2 = new UserEntity(UUID.randomUUID().toString(),
"alvgulveg", PasswordHasher.hash("alcalde_dimision"));
UserEntity u3 = new UserEntity(UUID.randomUUID().toString(),
"smt4497", PasswordHasher.hash("quemenlaus"));
try {
em.getTransaction().begin();
em.persist(u1);
em.persist(u2);
em.persist(u3);
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
} finally {
em.close();
}
}
}

View File

@@ -0,0 +1,20 @@
package net.miarma.byodsec.common.db.dao;
import jakarta.persistence.EntityManager;
import net.miarma.byodsec.common.db.entities.SessionEntity;
public class SessionDAO {
public SessionEntity getByUserId(EntityManager em, String userId) {
return em.createQuery("SELECT s FROM SessionEntity s WHERE s.userId = :userId", SessionEntity.class)
.setParameter("userId", userId)
.getSingleResult();
}
public boolean isLoggedIn(EntityManager em, String userId) {
Long count = em.createQuery(
"SELECT COUNT(s) FROM SessionEntity s WHERE s.userId = :userId", Long.class)
.setParameter("userId", userId)
.getSingleResult();
return count > 0;
}
}

View File

@@ -0,0 +1,16 @@
package net.miarma.byodsec.common.db.dao;
import jakarta.persistence.EntityManager;
import net.miarma.byodsec.common.db.entities.UserEntity;
public class UserDAO {
public UserEntity getByUserName(EntityManager em, String userName) {
try {
return em.createQuery("SELECT u FROM UserEntity u WHERE u.userName = :userName", UserEntity.class)
.setParameter("userName", userName)
.getSingleResult();
} catch (jakarta.persistence.NoResultException e) {
return null; // No se encontró, devolvemos null
}
}
}

View File

@@ -0,0 +1,30 @@
package net.miarma.byodsec.common.db.dto;
public class LoginDTO {
private String username;
private String password;
public LoginDTO() {
}
public LoginDTO(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,31 @@
package net.miarma.byodsec.common.db.dto;
public class MessageDTO {
private String fromUserName;
private String messageText;
public MessageDTO() {}
public MessageDTO(String fromUserName, String messageText) {
this.fromUserName = fromUserName;
this.messageText = messageText;
}
public String getFromUserName() {
return fromUserName;
}
public void setFromUserName(String fromUserName) {
this.fromUserName = fromUserName;
}
public String getMessageText() {
return messageText;
}
public void setMessageText(String messageText) {
this.messageText = messageText;
}
}

View File

@@ -0,0 +1,66 @@
package net.miarma.byodsec.common.db.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
@Entity
@Table(name = "sessions")
public class SessionEntity {
@Id
@Column(name = "sessionId", nullable = false, updatable = false)
private String sessionId;
@Column(name = "userId", nullable = false)
private String userId;
@Column(name = "createdAt", nullable = false, updatable = false, insertable = false)
private LocalDateTime createdAt;
@Column(name = "expiresAt", nullable = false, updatable = false, insertable = false)
private LocalDateTime expiresAt;
public SessionEntity() {}
public SessionEntity(String sessionId, String userId, LocalDateTime createdAt, LocalDateTime expiresAt) {
this.sessionId = sessionId;
this.userId = userId;
this.createdAt = createdAt;
this.expiresAt = expiresAt;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(LocalDateTime expiresAt) {
this.expiresAt = expiresAt;
}
}

View File

@@ -0,0 +1,52 @@
package net.miarma.byodsec.common.db.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@Column(name = "userId", nullable = false, updatable = false, length = 36)
private String userId;
@Column(name = "userName", unique = true, nullable = false, length = 64)
private String userName;
@Column(name = "password", nullable = false, length = 256)
private String password;
public UserEntity() {}
public UserEntity(String userId, String userName, String password) {
this.userId = userId;
this.userName = userName;
this.password = password;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,30 @@
package net.miarma.byodsec.common.security;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class IntegrityProvider {
public static String generateHMAC(String key, String data) throws Exception {
Mac sha256HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256HMAC.init(secretKey);
return byteArrayToHex(sha256HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
public static String generateNonce() {
byte[] nonce = new byte[16];
new SecureRandom().nextBytes(nonce);
return Base64.getEncoder().encodeToString(nonce);
}
public static String byteArrayToHex(byte[] arr) {
StringBuilder sb = new StringBuilder(arr.length * 2);
for(byte b : arr) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,22 @@
package net.miarma.byodsec.common.security;
import org.mindrot.jbcrypt.BCrypt;
/**
* Clase de utilidad para el hash de contraseñas.
* Utiliza BCrypt para generar y verificar hashes de contraseñas.
*
* @author José Manuel Amador Gallardo
*/
public class PasswordHasher {
private static final int SALT_ROUNDS = 12;
public static String hash(String plainPassword) {
return BCrypt.hashpw(plainPassword, BCrypt.gensalt(SALT_ROUNDS));
}
public static boolean verify(String plainPassword, String hashedPassword) {
return BCrypt.checkpw(plainPassword, hashedPassword);
}
}

View File

@@ -0,0 +1,41 @@
package net.miarma.byodsec.common.socket;
public class SocketRequest {
private SocketRequest.Action action;
private Object payload;
public SocketRequest() {}
public SocketRequest(SocketRequest.Action action, Object payload) {
this.action = action;
this.payload = payload;
}
public SocketRequest.Action getAction() {
return action;
}
public void setAction(SocketRequest.Action action) {
this.action = action;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
@Override
public String toString() {
return "SocketRequest{" +
"action='" + action.name() + '\'' +
", payload=" + (payload != null ? payload.toString() : "null") +
'}';
}
public enum Action {
LOGIN, REGISTER, SEND_MESSAGE, LOGOUT
}
}

View File

@@ -0,0 +1,67 @@
package net.miarma.byodsec.common.socket;
public class SocketResponse {
private SocketStatus status;
private Message message;
private Object data;
public SocketResponse(SocketStatus status, Message message) {
this(status, message, message != null ? message.getMessage() : null);
}
public SocketResponse(SocketStatus status, Message message, Object data) {
this.status = status;
this.message = message;
this.data = data;
}
public SocketStatus getStatus() {
return status;
}
public Message getMessage() {
return message;
}
public Object getData() {
return data;
}
@Override
public String toString() {
return "SocketResponse{" +
"status=" + status +
", message=" + (message != null ? message.name() : "null") +
", data=" + (data != null ? data.toString() : "null") +
'}';
}
public enum Message {
LOGGED_OUT("Ha cerrado sesión correctamente"),
LOGGED_IN("Ha iniciado sesión correctamente"),
MESSAGE_SENT("Su mensaje se ha enviado"),
USER_NOT_FOUND("El usuario no existe."),
WRONG_PASSWORD("Contraseña incorrecta."),
USER_ALREADY_EXISTS("El usuario ya está registrado."),
CONNECTION_ERROR("Error de conexión con el servidor."),
UNKNOWN_ERROR("Ha ocurrido un error inesperado."),
SESSION_EXPIRED("Tu sesión ha expirado."),
INTEGRITY_FAIL("Fallo de integridad en el mensaje."),
INVALID_AMOUNT("Cantidad no válida."),
REPLAY_ATTACK("Intento de repetición detectado."),
RECIPIENT_NOT_FOUND("El destinatario no existe"),
REGISTERED("Se ha registrado correctamente"),
SESSION_ACTIVE("Ya hay una sesión activa para este usuario.");
private final String message;
Message(String userMessage) {
this.message = userMessage;
}
public String getMessage() {
return message;
}
}
}

View File

@@ -0,0 +1,15 @@
package net.miarma.byodsec.common.socket;
public enum SocketStatus {
OK,
ERROR,
SESSION_EXPIRED,
ALREADY_LOGGED_IN,
INTEGRITY_FAIL,
UNAUTHORIZED,
INVALID_REQUEST;
public static boolean isOk(String status) {
return OK.name().equalsIgnoreCase(status);
}
}

View File

@@ -0,0 +1,156 @@
package net.miarma.byodsec.server;
import com.google.gson.Gson;
import jakarta.persistence.EntityManager;
import net.miarma.byodsec.common.db.DBConnector;
import net.miarma.byodsec.common.db.dao.SessionDAO;
import net.miarma.byodsec.common.db.dao.UserDAO;
import net.miarma.byodsec.common.db.dto.LoginDTO;
import net.miarma.byodsec.common.db.dto.MessageDTO;
import net.miarma.byodsec.common.db.entities.SessionEntity;
import net.miarma.byodsec.common.db.entities.UserEntity;
import net.miarma.byodsec.common.security.PasswordHasher;
import net.miarma.byodsec.common.socket.SocketRequest;
import net.miarma.byodsec.common.socket.SocketResponse;
import net.miarma.byodsec.common.socket.SocketStatus;
import org.slf4j.Logger;
import javax.net.ssl.SSLSocket;
import java.io.*;
import java.util.UUID;
public class ClientHandler implements Runnable {
private final SSLSocket socket;
private final Gson gson;
private final UserDAO userDAO;
private final SessionDAO sessionDAO;
private final DBConnector conn;
private final Logger logger;
public ClientHandler(SSLSocket socket, Gson gson, UserDAO userDAO, SessionDAO sessionDAO, DBConnector conn, Logger logger) {
this.socket = socket;
this.gson = gson;
this.userDAO = userDAO;
this.sessionDAO = sessionDAO;
this.conn = conn;
this.logger = logger;
}
@Override
public void run() {
try (
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
) {
logger.info("Nueva conexión desde {}:{}", socket.getInetAddress(), socket.getPort());
SocketRequest msg = gson.fromJson(input.readLine(), SocketRequest.class);
SocketResponse res;
switch (msg.getAction()) {
case REGISTER -> {
LoginDTO dto = gson.fromJson(gson.toJson(msg.getPayload()), LoginDTO.class);
logger.info("Procesando REGISTER para {}", dto.getUsername());
EntityManager em = conn.getEntityManager();
try {
if (userDAO.getByUserName(em, dto.getUsername()) != null) {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.USER_ALREADY_EXISTS);
} else {
em.getTransaction().begin();
UserEntity u = new UserEntity(UUID.randomUUID().toString(),
dto.getUsername(), PasswordHasher.hash(dto.getPassword()));
em.persist(u);
em.getTransaction().commit();
res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.REGISTERED, u);
}
} catch (Exception e) {
if (em.getTransaction().isActive()) em.getTransaction().rollback();
logger.error(e.getMessage(), e);
res = new SocketResponse(SocketStatus.ERROR, SocketResponse.Message.UNKNOWN_ERROR, e.getMessage());
} finally {
conn.close();
}
}
case LOGIN -> {
LoginDTO dto = gson.fromJson(gson.toJson(msg.getPayload()), LoginDTO.class);
logger.info("Procesando LOGIN para {}", dto.getUsername());
try (EntityManager em = conn.getEntityManager()) {
UserEntity user = userDAO.getByUserName(em, dto.getUsername());
if (user == null) {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.USER_NOT_FOUND);
} else if (PasswordHasher.verify(dto.getPassword(), user.getPassword())) {
if(sessionDAO.isLoggedIn(em, user.getUserId())) {
res = new SocketResponse(SocketStatus.ALREADY_LOGGED_IN, SocketResponse.Message.SESSION_ACTIVE);
} else {
res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.LOGGED_IN, user);
SessionEntity session = new SessionEntity();
session.setSessionId(UUID.randomUUID().toString());
session.setUserId(user.getUserId());
em.getTransaction().begin();
em.persist(session);
em.getTransaction().commit();
}
} else {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.WRONG_PASSWORD);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
res = new SocketResponse(SocketStatus.ERROR, SocketResponse.Message.UNKNOWN_ERROR, e.getMessage());
}
}
case LOGOUT -> {
String userId = gson.fromJson(gson.toJson(msg.getPayload()), String.class);
logger.info("Procesando LOGOUT para {}", userId);
try (EntityManager em = conn.getEntityManager()) {
SessionEntity session = sessionDAO.getByUserId(em, userId);
if (session != null) {
em.getTransaction().begin();
em.remove(session);
em.getTransaction().commit();
res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.LOGGED_OUT);
} else {
res = new SocketResponse(SocketStatus.SESSION_EXPIRED, SocketResponse.Message.SESSION_EXPIRED);
}
}
}
case SEND_MESSAGE -> {
MessageDTO dto = gson.fromJson(gson.toJson(msg.getPayload()), MessageDTO.class);
logger.info("Procesando SEND_MESSAGE de {} a Servidor | {}", dto.getFromUserName(), dto.getMessageText());
EntityManager em = conn.getEntityManager();
try {
UserEntity fromUser = userDAO.getByUserName(em, dto.getFromUserName());
if (fromUser == null || !sessionDAO.isLoggedIn(em, fromUser.getUserId())) {
res = new SocketResponse(SocketStatus.SESSION_EXPIRED, SocketResponse.Message.SESSION_EXPIRED);
} else if (dto.getMessageText().isBlank()) {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.INVALID_AMOUNT);
} else {
res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.MESSAGE_SENT, dto.getMessageText());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
res = new SocketResponse(SocketStatus.ERROR, SocketResponse.Message.UNKNOWN_ERROR, e.getMessage());
} finally {
conn.close();
}
}
default -> {
logger.warn("Acción desconocida recibida: {}", msg.getAction());
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.UNKNOWN_ERROR);
}
}
output.println(gson.toJson(res));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,55 @@
package net.miarma.byodsec.server;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import net.miarma.byodsec.common.ConfigManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import net.miarma.byodsec.common.db.DBConnector;
import net.miarma.byodsec.common.db.dao.SessionDAO;
import net.miarma.byodsec.common.db.dao.UserDAO;
public class RemoteSocket {
static void main(String[] args) throws IOException {
ConfigManager configManager = ConfigManager.getInstance();
configManager.loadConfig();
DBConnector conn = DBConnector.getInstance();
UserDAO userDAO = new UserDAO();
SessionDAO sessionDAO = new SessionDAO();
Gson gson = new Gson();
final Logger logger = LoggerFactory.getLogger(RemoteSocket.class);
// especificamos mediante propiedades el KS y la contraseña
System.setProperty("javax.net.ssl.keyStore", configManager.getJksFile().getAbsolutePath());
System.setProperty("javax.net.ssl.keyStorePassword", configManager.getStringProperty("jksPassword"));
System.setProperty("javax.net.ssl.trustStore", configManager.getJksFile().getAbsolutePath());
System.setProperty("javax.net.ssl.trustStorePassword", configManager.getStringProperty("jksPassword"));
// inicializamos el socket SSL, la versión de TLS y los Cipher Suites a utilizar
SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(6969);
serverSocket.setEnabledProtocols(new String[]{ "TLSv1.3" });
serverSocket.setEnabledCipherSuites(new String[]{
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256"
});
serverSocket.setNeedClientAuth(true);
// main loop
Executor executor = Executors.newFixedThreadPool(300);
while (true) {
SSLSocket socket = (SSLSocket) serverSocket.accept();
executor.execute(new ClientHandler(socket, gson, userDAO, sessionDAO, conn, logger));
}
}
}

View File

@@ -0,0 +1,29 @@
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="ssii-pai2">
<description>
Persistence unit for PAI-2
</description>
<class>net.miarma.byodsec.common.db.entities.UserEntity</class>
<class>net.miarma.byodsec.common.db.entities.SessionEntity</class>
<properties>
<property name="jakarta.persistence.jdbc.url" value="jdbc:mariadb://miarma.net:3307/pai2" />
<property name="jakarta.persistence.jdbc.user" value="admin" />
<property name="jakarta.persistence.jdbc.password" value="55ii.P4I.1;" />
<property name="jakarta.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MariaDBDialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.highlight_sql" value="true" />
</properties>
</persistence-unit>
</persistence>

View File

@@ -0,0 +1 @@
jksPassword=

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,53 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="CLIENT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/client.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/client-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<appender name="SERVER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/server.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/server-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<logger name="net.miarma.byodsec.ui" level="INFO" additivity="false">
<appender-ref ref="CLIENT"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="net.miarma.byodsec.client.ClientSocket" level="INFO" additivity="false">
<appender-ref ref="CLIENT"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="net.miarma.byodsec.server.RemoteSocket" level="DEBUG" additivity="false">
<appender-ref ref="SERVER"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="net.miarma.byodsec.db" level="INFO" additivity="false">
<appender-ref ref="SERVER"/>
<appender-ref ref="STDOUT"/>
</logger>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -0,0 +1,27 @@
USE pai2;
-- Cleanup
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS users;
-- END Cleanup
-- DDL
CREATE TABLE users (
userId UUID NOT NULL PRIMARY KEY,
userName VARCHAR(64) NOT NULL UNIQUE,
password VARCHAR(256) NOT NULL
);
CREATE TABLE sessions (
sessionId UUID NOT NULL PRIMARY KEY,
userId UUID NOT NULL UNIQUE,
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expiresAt TIMESTAMP AS (createdAt + INTERVAL 15 MINUTE) STORED,
FOREIGN KEY (userId) REFERENCES users(userId) ON DELETE CASCADE
);
CREATE EVENT IF NOT EXISTS clear_expired_sessions
ON SCHEDULE EVERY 1 MINUTE
DO
DELETE FROM sessions WHERE expiresAt <= NOW();
-- END DDL