From 6ab8bccdd854adce5f7b4de5956229a27fecc0e5 Mon Sep 17 00:00:00 2001 From: Jose Date: Sat, 1 Nov 2025 06:22:17 +0100 Subject: [PATCH] [REPO REFACTOR]: changed to a better git repository structure with branches --- BYODSEC/.gitignore | 2 + BYODSEC/log/client.log | 54 ++ BYODSEC/log/server.log | 575 ++++++++++++++++++ BYODSEC/pom.xml | 155 +++++ .../main/java/net/miarma/byodsec/Byodsec.java | 128 ++++ .../miarma/byodsec/client/ClientSocket.java | 97 +++ .../byodsec/client/ui/AutoShrinkLabel.java | 62 ++ .../miarma/byodsec/client/ui/MainWindow.java | 442 ++++++++++++++ .../miarma/byodsec/client/ui/MainWindow.jfd | 176 ++++++ .../miarma/byodsec/common/ConfigManager.java | 105 ++++ .../net/miarma/byodsec/common/Constants.java | 10 + .../net/miarma/byodsec/common/OSType.java | 9 + .../miarma/byodsec/common/db/DBConnector.java | 39 ++ .../miarma/byodsec/common/db/DBPopulator.java | 51 ++ .../byodsec/common/db/dao/SessionDAO.java | 20 + .../miarma/byodsec/common/db/dao/UserDAO.java | 16 + .../byodsec/common/db/dto/LoginDTO.java | 30 + .../byodsec/common/db/dto/MessageDTO.java | 31 + .../common/db/entities/SessionEntity.java | 66 ++ .../common/db/entities/UserEntity.java | 52 ++ .../common/security/IntegrityProvider.java | 30 + .../common/security/PasswordHasher.java | 22 + .../byodsec/common/socket/SocketRequest.java | 41 ++ .../byodsec/common/socket/SocketResponse.java | 67 ++ .../byodsec/common/socket/SocketStatus.java | 15 + .../miarma/byodsec/server/ClientHandler.java | 156 +++++ .../miarma/byodsec/server/RemoteSocket.java | 55 ++ .../main/resources/META-INF/persistence.xml | 29 + BYODSEC/src/main/resources/default.properties | 1 + BYODSEC/src/main/resources/images/banco.png | Bin 0 -> 23172 bytes BYODSEC/src/main/resources/images/logo.png | Bin 0 -> 15551 bytes BYODSEC/src/main/resources/logback.xml | 53 ++ BYODSEC/src/main/resources/scripts/db.sql | 27 + DevSecOps/.gitkeep | 0 Integridos/.gitignore | 2 + Integridos/log/client.log | 15 + Integridos/log/server.log | 46 ++ Integridos/pom.xml | 155 +++++ Integridos/src/main/java/module-info.java | 8 + .../net/miarma/integridos/Integridos.java | 78 +++ .../integridos/client/ClientSocket.java | 63 ++ .../integridos/client/ui/AutoShrinkLabel.java | 62 ++ .../integridos/client/ui/MainWindow.java | 492 +++++++++++++++ .../integridos/client/ui/MainWindow.jfd | 209 +++++++ .../integridos/common/ConfigManager.java | 105 ++++ .../miarma/integridos/common/Constants.java | 10 + .../net/miarma/integridos/common/OSType.java | 9 + .../integridos/common/db/DBConnector.java | 31 + .../integridos/common/db/DBPopulator.java | 51 ++ .../integridos/common/db/dao/SessionDAO.java | 20 + .../common/db/dao/TransactionDAO.java | 13 + .../integridos/common/db/dao/UserDAO.java | 16 + .../integridos/common/db/dto/LoginDTO.java | 30 + .../common/db/dto/TransactionDTO.java | 58 ++ .../common/db/entities/SessionEntity.java | 66 ++ .../common/db/entities/TransactionEntity.java | 85 +++ .../common/db/entities/UserEntity.java | 63 ++ .../common/security/IntegrityProvider.java | 30 + .../common/security/PasswordHasher.java | 22 + .../common/socket/SocketRequest.java | 41 ++ .../common/socket/SocketResponse.java | 80 +++ .../common/socket/SocketStatus.java | 14 + .../integridos/server/RemoteSocket.java | 207 +++++++ .../main/resources/META-INF/persistence.xml | 30 + .../src/main/resources/default.properties | 1 + .../src/main/resources/images/banco.png | Bin 0 -> 23172 bytes Integridos/src/main/resources/logback.xml | 53 ++ Integridos/src/main/resources/scripts/db.sql | 77 +++ RedTeamPro/.gitkeep | 0 Vulnaweb/.gitkeep | 0 70 files changed, 4858 insertions(+) create mode 100644 BYODSEC/.gitignore create mode 100644 BYODSEC/log/client.log create mode 100644 BYODSEC/log/server.log create mode 100644 BYODSEC/pom.xml create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/Byodsec.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/client/ClientSocket.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/client/ui/AutoShrinkLabel.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/client/ui/MainWindow.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/client/ui/MainWindow.jfd create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/ConfigManager.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/Constants.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/OSType.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/db/DBConnector.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/db/DBPopulator.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/db/dao/SessionDAO.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/db/dao/UserDAO.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/db/dto/LoginDTO.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/db/dto/MessageDTO.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/db/entities/SessionEntity.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/db/entities/UserEntity.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/security/IntegrityProvider.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/security/PasswordHasher.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketRequest.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketResponse.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketStatus.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/server/ClientHandler.java create mode 100644 BYODSEC/src/main/java/net/miarma/byodsec/server/RemoteSocket.java create mode 100644 BYODSEC/src/main/resources/META-INF/persistence.xml create mode 100644 BYODSEC/src/main/resources/default.properties create mode 100644 BYODSEC/src/main/resources/images/banco.png create mode 100644 BYODSEC/src/main/resources/images/logo.png create mode 100644 BYODSEC/src/main/resources/logback.xml create mode 100644 BYODSEC/src/main/resources/scripts/db.sql create mode 100644 DevSecOps/.gitkeep create mode 100644 Integridos/.gitignore create mode 100644 Integridos/log/client.log create mode 100644 Integridos/log/server.log create mode 100644 Integridos/pom.xml create mode 100644 Integridos/src/main/java/module-info.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/Integridos.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/client/ClientSocket.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/client/ui/AutoShrinkLabel.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/client/ui/MainWindow.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/client/ui/MainWindow.jfd create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/ConfigManager.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/Constants.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/OSType.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/DBConnector.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/DBPopulator.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/dao/SessionDAO.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/dao/TransactionDAO.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/dao/UserDAO.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/dto/LoginDTO.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/dto/TransactionDTO.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/entities/SessionEntity.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/entities/TransactionEntity.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/db/entities/UserEntity.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/security/IntegrityProvider.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/security/PasswordHasher.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/socket/SocketRequest.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/socket/SocketResponse.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/common/socket/SocketStatus.java create mode 100644 Integridos/src/main/java/net/miarma/integridos/server/RemoteSocket.java create mode 100644 Integridos/src/main/resources/META-INF/persistence.xml create mode 100644 Integridos/src/main/resources/default.properties create mode 100644 Integridos/src/main/resources/images/banco.png create mode 100644 Integridos/src/main/resources/logback.xml create mode 100644 Integridos/src/main/resources/scripts/db.sql create mode 100644 RedTeamPro/.gitkeep create mode 100644 Vulnaweb/.gitkeep diff --git a/BYODSEC/.gitignore b/BYODSEC/.gitignore new file mode 100644 index 0000000..09e3bc9 --- /dev/null +++ b/BYODSEC/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/target/ diff --git a/BYODSEC/log/client.log b/BYODSEC/log/client.log new file mode 100644 index 0000000..713373d --- /dev/null +++ b/BYODSEC/log/client.log @@ -0,0 +1,54 @@ +2025-10-20 03:41:45 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 03:41:50 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: smt4497 -> Servidor +2025-10-20 18:03:23 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:03:34 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'alvgulveg' ha iniciado sesión correctamente. +2025-10-20 18:03:38 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: smt4497 -> Servidor +2025-10-20 18:03:40 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: alvgulveg -> Servidor +2025-10-20 18:04:04 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'ricolinad' ha iniciado sesión correctamente. +2025-10-20 18:04:15 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: ricolinad -> Servidor +2025-10-20 18:04:36 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'ricolinad' ha cerrado sesión correctamente. +2025-10-20 18:05:48 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:09:21 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:10:12 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:12:49 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'psancas' registrado correctamente. +2025-10-20 18:12:54 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 18:12:57 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'psancas' ha cerrado sesión correctamente. +2025-10-20 18:14:49 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:14:57 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'alvgulveg' ha iniciado sesión correctamente. +2025-10-20 18:15:04 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 18:15:08 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: smt4497 -> Servidor +2025-10-20 18:15:09 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: alvgulveg -> Servidor +2025-10-20 18:15:14 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: psancas -> Servidor +2025-10-20 18:16:28 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: psancas -> Servidor +2025-10-20 18:17:38 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'ricolinad' ha iniciado sesión correctamente. +2025-10-20 18:17:43 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: ricolinad -> Servidor +2025-10-20 18:18:16 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:20:11 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:20:24 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:24:49 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 18:25:09 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'smt4497': Ha ocurrido un error inesperado. +2025-10-20 18:25:20 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'smt4497': Ha ocurrido un error inesperado. +2025-10-20 18:25:23 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'smt4497': Ha ocurrido un error inesperado. +2025-10-20 18:25:47 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'smt4497' ha cerrado sesión correctamente. +2025-10-20 18:32:42 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 18:32:49 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'psancas': Ha ocurrido un error inesperado. +2025-10-20 18:32:56 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'psancas' ha cerrado sesión correctamente. +2025-10-20 18:35:05 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 18:35:07 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'psancas': Ha ocurrido un error inesperado. +2025-10-20 18:35:22 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'psancas' ha cerrado sesión correctamente. +2025-10-20 18:35:24 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 18:35:26 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'psancas' ha cerrado sesión correctamente. +2025-10-20 18:37:28 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 18:37:31 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'psancas': Ha ocurrido un error inesperado. +2025-10-20 18:37:40 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'psancas': Ha ocurrido un error inesperado. +2025-10-20 18:37:44 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'psancas' ha cerrado sesión correctamente. +2025-10-20 18:39:08 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 18:39:21 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'psancas': Ha ocurrido un error inesperado. +2025-10-20 18:41:15 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 18:41:25 WARN n.miarma.byodsec.client.ClientSocket - Intento de login fallido para 'psancas': Ha ocurrido un error inesperado. +2025-10-20 22:42:26 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-20 22:42:36 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'smt4497' ya tiene una sesión activa. +2025-10-20 22:43:17 INFO n.miarma.byodsec.client.ClientSocket - El usuario 'psancas' ha iniciado sesión correctamente. +2025-10-20 22:43:20 INFO n.miarma.byodsec.client.ClientSocket - Mensaje enviado: smt4497 -> Servidor +2025-10-20 22:43:24 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'psancas' ha cerrado sesión correctamente. +2025-10-20 22:43:26 INFO n.miarma.byodsec.client.ClientSocket - Usuario 'smt4497' ha cerrado sesión correctamente. diff --git a/BYODSEC/log/server.log b/BYODSEC/log/server.log new file mode 100644 index 0000000..e2265f8 --- /dev/null +++ b/BYODSEC/log/server.log @@ -0,0 +1,575 @@ +2025-10-20 03:41:45 [main] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:58152 +2025-10-20 03:41:45 [main] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 03:41:50 [main] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:35314 +2025-10-20 03:41:50 [main] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de smt4497 a Servidor | hola +2025-10-20 03:42:15 [main] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:45928 +2025-10-20 03:42:15 [main] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a +2025-10-20 03:42:15 [main] ERROR n.miarma.byodsec.server.RemoteSocket - Fallo procesando solicitud +jakarta.persistence.NonUniqueResultException: Query did not return a unique result: 2 results were returned + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:114) + at org.hibernate.query.spi.AbstractSelectionQuery.getSingleResult(AbstractSelectionQuery.java:291) + at net.miarma.byodsec.common.db.dao.SessionDAO.getByUserId(SessionDAO.java:10) + at net.miarma.byodsec.server.RemoteSocket.main(RemoteSocket.java:126) +Caused by: org.hibernate.NonUniqueResultException: Query did not return a unique result: 2 results were returned + at org.hibernate.query.spi.AbstractSelectionQuery.uniqueElement(AbstractSelectionQuery.java:305) + at org.hibernate.query.spi.AbstractSelectionQuery.getSingleResult(AbstractSelectionQuery.java:288) + ... 2 common frames omitted +2025-10-20 18:03:22 [pool-2-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:36678 +2025-10-20 18:03:22 [pool-2-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:03:33 [pool-3-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:50342 +2025-10-20 18:03:33 [pool-3-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para alvgulveg +2025-10-20 18:03:37 [pool-4-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:60208 +2025-10-20 18:03:37 [pool-4-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de smt4497 a Servidor | illo que +2025-10-20 18:03:40 [pool-5-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:60218 +2025-10-20 18:03:40 [pool-5-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de alvgulveg a Servidor | yoyoyo +2025-10-20 18:04:04 [pool-6-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:54898 +2025-10-20 18:04:04 [pool-6-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para ricolinad +2025-10-20 18:04:15 [pool-7-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:35428 +2025-10-20 18:04:15 [pool-7-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de ricolinad a Servidor | we are number one +2025-10-20 18:04:33 [pool-8-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:37304 +2025-10-20 18:04:33 [pool-8-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a +2025-10-20 18:04:35 [pool-9-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:59664 +2025-10-20 18:04:35 [pool-9-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para b176f04e-6b8a-4447-948a-af290d3ebb82 +2025-10-20 18:04:35 [pool-10-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:59676 +2025-10-20 18:04:35 [pool-10-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para 85218286-76fc-46af-b3c0-71bb09896531 +2025-10-20 18:05:47 [pool-11-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:47858 +2025-10-20 18:05:47 [pool-11-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:05:51 [pool-12-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:47864 +2025-10-20 18:05:51 [pool-12-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a +2025-10-20 18:09:20 [pool-2-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:55666 +2025-10-20 18:09:20 [pool-2-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:09:27 [pool-3-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:41602 +2025-10-20 18:09:27 [pool-3-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a +2025-10-20 18:10:12 [pool-4-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:46782 +2025-10-20 18:10:12 [pool-4-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:10:26 [pool-5-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:51828 +2025-10-20 18:10:26 [pool-5-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a +2025-10-20 18:12:48 [pool-6-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34644 +2025-10-20 18:12:48 [pool-6-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando REGISTER para psancas +2025-10-20 18:12:54 [pool-7-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34658 +2025-10-20 18:12:54 [pool-7-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:12:57 [pool-8-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:56042 +2025-10-20 18:12:57 [pool-8-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para 585d98f3-5951-40d6-8d35-f32b65d9d43e +2025-10-20 18:14:48 [pool-9-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:39960 +2025-10-20 18:14:48 [pool-9-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:14:56 [pool-10-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:60056 +2025-10-20 18:14:57 [pool-10-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para alvgulveg +2025-10-20 18:15:03 [pool-11-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:60068 +2025-10-20 18:15:03 [pool-11-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:15:07 [pool-12-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34964 +2025-10-20 18:15:07 [pool-12-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de smt4497 a Servidor | dsadasdas +2025-10-20 18:15:09 [pool-13-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34978 +2025-10-20 18:15:09 [pool-13-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de alvgulveg a Servidor | sfdsgefrgfr +2025-10-20 18:15:14 [pool-14-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34986 +2025-10-20 18:15:14 [pool-14-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de psancas a Servidor | odio a la derecha +2025-10-20 18:16:28 [pool-15-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:54852 +2025-10-20 18:16:28 [pool-15-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de psancas a Servidor | dictadura +2025-10-20 18:17:38 [pool-16-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:43520 +2025-10-20 18:17:38 [pool-16-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para ricolinad +2025-10-20 18:17:43 [pool-17-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:43528 +2025-10-20 18:17:43 [pool-17-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de ricolinad a Servidor | we are number one +2025-10-20 18:18:15 [pool-18-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:60346 +2025-10-20 18:18:16 [pool-18-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:20:10 [pool-19-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34420 +2025-10-20 18:20:10 [pool-19-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:20:23 [pool-20-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:45642 +2025-10-20 18:20:23 [pool-20-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:20:45 [pool-21-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:40882 +2025-10-20 18:20:45 [pool-21-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a +2025-10-20 18:20:55 [pool-22-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:35040 +2025-10-20 18:20:55 [pool-22-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a +2025-10-20 18:24:49 [pool-23-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:52902 +2025-10-20 18:24:49 [pool-23-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:25:08 [pool-24-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:56154 +2025-10-20 18:25:08 [pool-24-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:25:09 [pool-24-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.server.ClientHandler.run(ClientHandler.java:90) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 18:25:20 [pool-25-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:47508 +2025-10-20 18:25:20 [pool-25-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:25:20 [pool-25-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.server.ClientHandler.run(ClientHandler.java:90) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 18:25:22 [pool-26-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:47518 +2025-10-20 18:25:22 [pool-26-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 18:25:23 [pool-26-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.server.ClientHandler.run(ClientHandler.java:90) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=195) Duplicate entry 'cb1465ad-6d6d-4784-a2e9-9670a60e778a' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 18:25:47 [pool-27-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:36890 +2025-10-20 18:25:47 [pool-27-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a +2025-10-20 18:32:42 [pool-28-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:47312 +2025-10-20 18:32:42 [pool-28-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:32:48 [pool-29-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:39420 +2025-10-20 18:32:48 [pool-29-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:32:49 [pool-29-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.client.ClientHandler.run(ClientHandler.java:90) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=195) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=195) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 18:32:55 [pool-30-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:47214 +2025-10-20 18:32:55 [pool-30-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para 585d98f3-5951-40d6-8d35-f32b65d9d43e +2025-10-20 18:35:05 [pool-31-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:36694 +2025-10-20 18:35:05 [pool-31-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:35:06 [pool-32-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:36710 +2025-10-20 18:35:07 [pool-32-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:35:07 [pool-32-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=195) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.client.ClientHandler.run(ClientHandler.java:90) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=195) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=195) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 18:35:22 [pool-33-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:45686 +2025-10-20 18:35:22 [pool-33-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para 585d98f3-5951-40d6-8d35-f32b65d9d43e +2025-10-20 18:35:24 [pool-34-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:45688 +2025-10-20 18:35:24 [pool-34-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:35:26 [pool-35-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:45378 +2025-10-20 18:35:26 [pool-35-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para 585d98f3-5951-40d6-8d35-f32b65d9d43e +2025-10-20 18:37:27 [pool-2-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:39072 +2025-10-20 18:37:27 [pool-2-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:37:31 [pool-3-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:39080 +2025-10-20 18:37:31 [pool-3-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:37:31 [pool-3-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.server.ClientHandler.run(ClientHandler.java:93) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 18:37:39 [pool-4-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:42422 +2025-10-20 18:37:39 [pool-4-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:37:40 [pool-4-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.server.ClientHandler.run(ClientHandler.java:93) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 18:37:44 [pool-5-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:42426 +2025-10-20 18:37:44 [pool-5-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para 585d98f3-5951-40d6-8d35-f32b65d9d43e +2025-10-20 18:39:07 [pool-6-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:42476 +2025-10-20 18:39:07 [pool-6-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:39:20 [pool-7-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:52194 +2025-10-20 18:39:20 [pool-7-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:39:21 [pool-7-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.server.ClientHandler.run(ClientHandler.java:93) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 18:41:14 [pool-8-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:49238 +2025-10-20 18:41:14 [pool-8-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:41:24 [pool-9-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:49240 +2025-10-20 18:41:24 [pool-9-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 18:41:25 [pool-9-thread-1] ERROR n.miarma.byodsec.server.RemoteSocket - Error while committing the transaction [could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] +jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)]] + at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:70) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:93) + at net.miarma.byodsec.server.ClientHandler.run(ClientHandler.java:93) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) + at java.base/java.lang.Thread.run(Thread.java:1474) +Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId'] [insert into sessions (userId,sessionId) values (?,?)] + at org.hibernate.dialect.MariaDBDialect.lambda$buildSQLExceptionConversionDelegate$1(MariaDBDialect.java:379) + at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:34) + at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:115) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:193) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:148) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:53) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:66) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:191) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:129) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:101) + at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:113) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:646) + at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:513) + at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:378) + at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:140) + at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443) + at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:488) + at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2321) + at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2029) + at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:394) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:167) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:249) + at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:243) + at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:90) + ... 4 common frames omitted +Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=241) Duplicate entry '585d98f3-5951-40d6-8d35-f32b65d9d43e' for key 'userId' + at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:301) + at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:386) + at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:187) + at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1376) + at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1315) + at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1234) + at org.mariadb.jdbc.client.impl.StandardClient.execute(StandardClient.java:1158) + at org.mariadb.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:91) + at org.mariadb.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:345) + at org.mariadb.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:322) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:190) + ... 26 common frames omitted +2025-10-20 22:42:25 [pool-2-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:53230 +2025-10-20 22:42:25 [pool-2-thread-1] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 22:42:35 [pool-2-thread-2] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:40972 +2025-10-20 22:42:35 [pool-2-thread-2] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-20 22:43:17 [pool-2-thread-3] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34616 +2025-10-20 22:43:17 [pool-2-thread-3] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGIN para psancas +2025-10-20 22:43:20 [pool-2-thread-4] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34618 +2025-10-20 22:43:20 [pool-2-thread-4] INFO n.miarma.byodsec.server.RemoteSocket - Procesando SEND_MESSAGE de smt4497 a Servidor | hola +2025-10-20 22:43:23 [pool-2-thread-5] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:41190 +2025-10-20 22:43:23 [pool-2-thread-5] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para 585d98f3-5951-40d6-8d35-f32b65d9d43e +2025-10-20 22:43:26 [pool-2-thread-6] INFO n.miarma.byodsec.server.RemoteSocket - Nueva conexión desde /127.0.0.1:41194 +2025-10-20 22:43:26 [pool-2-thread-6] INFO n.miarma.byodsec.server.RemoteSocket - Procesando LOGOUT para cb1465ad-6d6d-4784-a2e9-9670a60e778a diff --git a/BYODSEC/pom.xml b/BYODSEC/pom.xml new file mode 100644 index 0000000..73456ca --- /dev/null +++ b/BYODSEC/pom.xml @@ -0,0 +1,155 @@ + + 4.0.0 + net.miarma + integridos + 1.0.0 + net.miarma.integridos.Integridos + + + 25 + 25 + + + + + + com.google.code.gson + gson + 2.12.1 + + + + + + org.slf4j + slf4j-api + 2.0.12 + + + + ch.qos.logback + logback-classic + 1.5.13 + + + + + + org.mariadb.jdbc + mariadb-java-client + 3.5.6 + + + + jakarta.persistence + jakarta.persistence-api + 3.2.0 + + + + org.hibernate.orm + hibernate-core + 7.1.1.Final + + + + com.zaxxer + HikariCP + 5.1.0 + + + + + + com.auth0 + java-jwt + 4.5.0 + + + + + + org.mindrot + jbcrypt + 0.4 + + + + + + com.miglayout + miglayout-swing + 11.4.2 + + + + com.formdev + flatlaf-intellij-themes + 3.6.1 + + + + com.formdev + flatlaf + 3.6.1 + + + + com.formdev + flatlaf-extras + 3.6.1 + + + + com.github.jiconfont + jiconfont + 1.0.0 + + + + com.github.jiconfont + jiconfont-swing + 1.0.1 + + + + com.github.jiconfont + jiconfont-font_awesome + 4.7.0.1 + + + + com.github.jiconfont + jiconfont-google_material_design_icons + 2.2.0.2 + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.integridos.net.miarma.integridos.Integridos + + + + + + + + + \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/Byodsec.java b/BYODSEC/src/main/java/net/miarma/byodsec/Byodsec.java new file mode 100644 index 0000000..8ee12fc --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/Byodsec.java @@ -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); + }); + } +} \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/client/ClientSocket.java b/BYODSEC/src/main/java/net/miarma/byodsec/client/ClientSocket.java new file mode 100644 index 0000000..5f2d25d --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/client/ClientSocket.java @@ -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) {} + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/AutoShrinkLabel.java b/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/AutoShrinkLabel.java new file mode 100644 index 0000000..c1c3c62 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/AutoShrinkLabel.java @@ -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); + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/MainWindow.java b/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/MainWindow.java new file mode 100644 index 0000000..e76d8eb --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/MainWindow.java @@ -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 +} \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/MainWindow.jfd b/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/MainWindow.jfd new file mode 100644 index 0000000..ed09df0 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/client/ui/MainWindow.jfd @@ -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 ) + } ) + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/ConfigManager.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/ConfigManager.java new file mode 100644 index 0000000..f6fd6a7 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/ConfigManager.java @@ -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); + } + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/Constants.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/Constants.java new file mode 100644 index 0000000..86bd609 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/Constants.java @@ -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."); + } +} \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/OSType.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/OSType.java new file mode 100644 index 0000000..b8fc6e5 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/OSType.java @@ -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 +} \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/db/DBConnector.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/DBConnector.java new file mode 100644 index 0000000..180b1d1 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/DBConnector.java @@ -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 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(); + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/db/DBPopulator.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/DBPopulator.java new file mode 100644 index 0000000..67306e6 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/DBPopulator.java @@ -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 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(); + } + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dao/SessionDAO.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dao/SessionDAO.java new file mode 100644 index 0000000..f01329e --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dao/SessionDAO.java @@ -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; + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dao/UserDAO.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dao/UserDAO.java new file mode 100644 index 0000000..92b3cad --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dao/UserDAO.java @@ -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 + } + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dto/LoginDTO.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dto/LoginDTO.java new file mode 100644 index 0000000..0af78d9 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dto/LoginDTO.java @@ -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; + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dto/MessageDTO.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dto/MessageDTO.java new file mode 100644 index 0000000..020c549 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/dto/MessageDTO.java @@ -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; + } + + +} \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/db/entities/SessionEntity.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/entities/SessionEntity.java new file mode 100644 index 0000000..e4333de --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/entities/SessionEntity.java @@ -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; + } +} \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/db/entities/UserEntity.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/entities/UserEntity.java new file mode 100644 index 0000000..ee04c6c --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/db/entities/UserEntity.java @@ -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; + } +} \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/security/IntegrityProvider.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/security/IntegrityProvider.java new file mode 100644 index 0000000..93b6b74 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/security/IntegrityProvider.java @@ -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(); + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/security/PasswordHasher.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/security/PasswordHasher.java new file mode 100644 index 0000000..6349d63 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/security/PasswordHasher.java @@ -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); + } +} \ No newline at end of file diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketRequest.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketRequest.java new file mode 100644 index 0000000..5209d1a --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketRequest.java @@ -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 + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketResponse.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketResponse.java new file mode 100644 index 0000000..80e77f1 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketResponse.java @@ -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; + } + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketStatus.java b/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketStatus.java new file mode 100644 index 0000000..09b987a --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/common/socket/SocketStatus.java @@ -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); + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/server/ClientHandler.java b/BYODSEC/src/main/java/net/miarma/byodsec/server/ClientHandler.java new file mode 100644 index 0000000..348a3e7 --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/server/ClientHandler.java @@ -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); + } + } +} diff --git a/BYODSEC/src/main/java/net/miarma/byodsec/server/RemoteSocket.java b/BYODSEC/src/main/java/net/miarma/byodsec/server/RemoteSocket.java new file mode 100644 index 0000000..7aeba2e --- /dev/null +++ b/BYODSEC/src/main/java/net/miarma/byodsec/server/RemoteSocket.java @@ -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)); + } + } +} diff --git a/BYODSEC/src/main/resources/META-INF/persistence.xml b/BYODSEC/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..91471a2 --- /dev/null +++ b/BYODSEC/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,29 @@ + + + + + Persistence unit for PAI-2 + + + net.miarma.byodsec.common.db.entities.UserEntity + net.miarma.byodsec.common.db.entities.SessionEntity + + + + + + + + + + + + + + + + + diff --git a/BYODSEC/src/main/resources/default.properties b/BYODSEC/src/main/resources/default.properties new file mode 100644 index 0000000..70ad8e9 --- /dev/null +++ b/BYODSEC/src/main/resources/default.properties @@ -0,0 +1 @@ +jksPassword= \ No newline at end of file diff --git a/BYODSEC/src/main/resources/images/banco.png b/BYODSEC/src/main/resources/images/banco.png new file mode 100644 index 0000000000000000000000000000000000000000..ebf2e60714f09d5829e9c712a1e1d5eb67b07140 GIT binary patch literal 23172 zcmeEt(|4qAv~+BzZF_KmrH)B25tvZ>4m z6Mc38cbmp~gYybr4*!Gs*=8g)n9wk7q~Ya=hF@2z8phKxXx1_omVX?(A1A#Zfl)}5 zw86#lG6|QF{h6atUe|`=exUcR%K$_P5s}`I5@j%GXv7Rra4akaV+3er<+>nREMbve zh|>T2;eYWjiTHhqNkfxiyI8Gf1-R7lKAdhSxgYFZdd$7NZA;ibe1$?lIEb0yJCW;H_f9a%Ck3aOS89+X_x^v%aqjvXhq=!>g2>uYzn~gy= zIv3;hSVGM<_3%WFaVM3+QVqAFI(~k1-TGhP@`gkF6$Ti52J6`fRlltXJY85>b_C80 z)vnM`ZV23m8o*AyuS_skOcB)py(G@OXAhq!2cJ``Y4m?A(b4l2cseKFKK7AVd-4TA zj3@o%5)Z10QZ{TOsJ4Vm!6%XJOXx{Z3l>;-etYh!@r3yAVNNGv-9K0Y*6bdC*>?tT zRJVcP%d+wyuO?grJ*;{ctL>bf+$5}>WN2zaRKw5$D9g+_NC!t-;L?Z*YK(q|!HXhj zmqIclZN;iz`*c79kowK~GjejmoM!+e*km9nC~!mOB5XHdS*l{(zAY;4grJzchjijV z%g^VoyZ@;G3;=u|?8r88(qrg1V85)|a66~7y)FdSp07n>a4?leJ-pnXK6hd0p{th0 zQnZDr>epaVhKy*0Ol#DOr0Ho~2Xym7G0UL+LrnsSouAwcwaJ7T|o>+^rsnzY?Pg!Ys>Dzy4YtW=j# zJ%V0K+__;DbA+<=6pa~y*}K9pu=X+%O_W5Y`~x0Nc~BS`#m41(2vZLY-2PS^q9`0q z$xA7m2*D6bMleP6jC~WUI{qS+e}#Gk0w&9(i4Nu8eT-5nY7u0ZQ3VPWBs>@zJgSgr z8J01Lk)Mv)J~vVlTbJKyuzI{r?dBpl3S>U&EE@Pm?@5)Jh0E4HP+unRPrWr>@_v#A zC@DjeLQ{dbZ(v6ziHS05Z*^R6Uz)-Du|1l7T&Zug&Z{&zBgrJusIB=32+_qP1*L&8 z4prbSPhBhF?T$oh2DM`v1%;J!!*G%c(RJnCti^I8A6Wkp+*8vd+{;(pdYF%(x!3q^Te_|7+@ zBp$)^nYE=_>>5`o`H_gCSO*A7FW?%CyS(=I;5>0BIi~dn$f?^c&8iige^HQ`Ob_|1 zP+BQAB8pl*BH*-?=43(QL5~mL)#b1 zj!R9h9oH5LbBO$eu`F6Rsidq1RJG!4ZkDN7>bH7uw>83L4J{(y-DzF^+glz)n_ruq z0o5G3AM(~WAY#W>@TG|JG_;=p(TX=ck)DK_b*TEo{swzMtZlHomh+ffQ-W@a7;QKJ zLzPxKsroI)ys8z!%%7mJVM#GB+pzHgG-rzo|JUMVDOxpo)1#S>WCuzXPFvFE0v62# z=^G*gkuFsCieLHM+1rS3*0ra{`6~9*!fOK>EmjXdo6G6F5ghmPuYsM*pR+<4R2jS0 zbD9I9h%qJrxOXsd>Az{e99kjez)GaaGID&#q3Riw zO|8xJE9d!f(MXiVL(odYlZNG8=3Et^1t_syXb{lg(`rtiLB687?@X_On4&$bj7@9y z>Z2C+2W3mYiI}BLS;*PNOXNwyLSn^O9P-Uq&)Z!k$TAtp|614j|yHst+B z1cD!a%NK8D5080yjo%lmS`00^Hotvz)cm_J#>Ne!^Adr|mQ|K$7OGJVE~7AdZEO}f zkb@%iK_sHP@0|nPu(4$xg98PIRE>oE^VN0#s&NICc2h&hs!U6h{P0aMff_Nb1`I|- zqK6r__gyB*b}^Z?f!KaQs#gkF{_*H`{w%5`@jVXl&aVSI$SBAb{43K|A_aloPnU`& z5zXmIUZGsE(2Q!^hO+8h9zOj2?*|&qwk`?HaC*q+Veh9I)6{W{9+zQ6NfC25e*mDo zO}V1Gh61S{CRx2Zz^02nWc(W-forhAr+ywndIK_5$l)cMyZ{oS0y{s{152I8{w0}4 zWMifS|A-*Zr}rQtHuQ?b>`Y^<$Xjx%-4XP(^3Ci#W~e%`g7&s zU(5zy*K4q-P7YBxG|Le8Ph26FV%7>sZ5amA&L^G1c~C|Q#*m89;kY;frEWf5GhGtR zd;kZjYzIti-RJK>pWXJoHKhO#rmpcS$Ik89a>Ib$_+_u?MZx#3mZL=%J50^?aB}lCcQ3R9r4oI9_viH_}ksYhjKkP8gGKdtSb{B6|)`S&E<@p-qOO`DpYsc>HY z(DGoO8iFipyHqn~a(oOTP@wk`_O&>x1?8rj9%zH+pE1v4EPJ9s;V)aM3^nZuJ1cdA z+x04yTHRU`Mm(om;__q6ky>)Z+fg(T;jf^jx@lb%6B*U?Lwg0Nz!PrvF{x?0}UBq@=T{sj6tAB7n#QRjKX9;w(F* zPW=iJ_)z*PT;?BckHjzA$2ob<|0n}5vhQ4Mt>Hvkf7$(}s#Cy>qAbSp`4rG(4I6#9 zO3eN|_GTS*>r=ELwJw+pYz}#Y-$S~e>i~FW{=6VP3p5&z|s+q<~+H0G4J3f871b4%Q zO~7-&&G2jVLhE00EirmMM!~GC6=VDLzlTx8n&?p_k%XCK!KDnhe5wX!;=NqXZ%Hd` z&0{1uh-*y@Fyq1dNc9H8$}@j=2a z(0jgkF037lL3#kLJ`;z)SP~%f+24GsYR5n9ogsq{Euuvuf)%TvO<4?bcHKL+Vy&iR zYV%x&DI{0iXKcRr+9L|0XOfR1J4qOqE&`=f6#^V`LCP5l!&iG3ak-)V7M1ITK+N=R7hemH6uxS>@%mH**$FgZz6UR2P z8Q^0c_qc*Fm^7oMLJ{iA8ZB!zX3$7*c>lz)guhMVZeBb$zi+X#VRL3VZkK#`pR{3? z7|A5rrA{QfHiqS`uPcS;N-yAkyV9Q1u(4fQba@bK}BJ_`}!PM`n*}1Z80e?PrEVn;YIB=SO+jTJ0V~d1QUw9_Yt7R(t-=ao z>5m?~2HY+1>^LMcHjjujiH@}2kknaIgg-|Vmf2HZmdRf)KwbU>J)IijjKW_O9!8ze z*Ha!0{oj#%vxyd6`3lN6l~?FcRtsvE-~YJdi1Wbe?Fp@ahC{XTh^F>bXl;7}|4`*l z5=G2ZG31XKuT7xru!uBow6#I!xa{i%e9>jry@~ro32RDHtdZ6;YpUy*@f%*q#19b| zEkC3)6r>ON)Es%J_FpjiWl&>HUu=F0mWW_Xayd7oH=}0%F;pg&NvH7rv+_p|)tf{k zCZ^i9My@g$&A= zChyF%QnJVsw})VI7+745(bN!u&j^5>`LXlKvS0oJp%2V32dn~=T@#hl-(*inDx^a~ z<&OWx5^}m46+#s;y3nc)R%&ZF6D#Vb3m@y#HIXg{B%4DJriadPUc7mcoQvw9yW*kK z5?Z5mMzrdw1%(0#F-tCo=olmpI0-G|uU&KOe9wpLvX4bN28cRhYqeEUv&D1mrAF!h zE{EDff@V2FxC`+H1|m}S5~_9ZSyrmF7ciOIyq`btd>U47G@1MwYkPa{kH@X_5Tp>;w`KI~7~DNytV%1qe|!y>)5@ zfADlb45y@t{<+}|5I?^=m{}5mREabIcil#6xf?kVXBJjQ0JFj7W-d6ze zEB@;~#VF}GEEUbB0~@)~4jOksrQveJGNN+MMc^mjWd@<-RDey7p`^&Z-p{i&v>46{ z@4U93acSts&VHZ-;d-skUi&lu#FMV@MHE+o3PooTeRYr^lUS6e(BKq3LJ&iNroz(Z zdUM{bKdcep%3JKqj{?sTlUKgdk19rEVU(kWJ|-in^NSZiUA}+Vyd~2&Fi*DsXMnO* zj3A5`TO6XP`yELr&N#uY->7mEA+LH#47->)Xbk^imOYSp_EKus;+^%{|&!H|yo zU}SCBE2Zg%hY(ji;g+yz4q~lFvt!W?4)QX?D2aP2SPisRxMip!L$oY&0-r>;m;6*P zb&*yyZpVSBMdWUANJj=%<}RyNp^h}1x;wG3FDSK{53!MMiI>glra>3FKC>#{)^dHi z$vK1^+xI5xK&*bpEev$vwQsWmM++IeHmkFTHE9iPFgyJ#JpJSJuy_${AzYxXssKwm zV6FsG^|)oz1nZcDEKJz95T0DI&dnTYLUZ>v8T8!iztQM>il``305%D}pk&y=({ya5 z?21lT(0CA}+FcQW!^D{Ht(|r^5fYHW*$mNudb&rv@}GDlly$(&8XDwOivKGGiN2Mn zp(mF~P;{{r-&@kC8^;8vv$({@kRs2!nr zHDyZjvKkhGk!gvCuNTXWU<}TO!}Gwk#=lVRwH3xTNNZE0>m7!8?u37gj<>J=@fF zyzgaEvC}CMLLT9M-kD*}9i|`vi{O$ZA*omc3`0cO1VyAWM8+-C|LPb8h04y@Eur}! zn%0ho#;4Tz$I=R%QSdIl{Y%lH`#SWZ7u7Gu=UygSqQf; zK_1lyg~Ofzmzce))1+8CPQkvN4BmU0d8PWTn|sA}rNvu=%+%obFwK&PgsfPz=GUv$z+sH}O}a#Zk#u&jJYcO~n{S;)EhEcAA7(3HR7AyZ(;K zeDTug0KGdmvS7`foc-8+(WJJ2NZ3RBgv@F5Zc!gAqD~%@OvAJk9#0N!49e^bb75-z zZ-}e}GK&q1WX7}@r0|WZnE$+ZFXP(k4t$6(Pa05mID=JDt2|H0Q^#WLSNgl#8m2_| zr>lGc*a5ss^=FBLceAxPBK!IC$XMc&|D9%Wx&=ZqD);$!p zRXzCdRax|8kotGIYe#W(E^?15WeLl)Zk%y|RcA&<+! z6N|ByuPT$nukv6KpjlA={B?idh%CG+8r&g02aR=ZcsTx(NaN-@2R$}kyL9t&&8aOI zucGvMNKIi`$14RzIax*;{MtG}Jnw;Y{+r7&EYB|2AY*PhN`HqPx9P5|^K;7%*7Swj zMba=NXuhcJPTMiG@25W+xg24f21JM9tTz~%ju`r#CkorY11Zu!1xXGtrYwk-6A`D{ zXo)=o^}E`ln9FunlM|dsbXIxejmN=|XV=Tq+NrZPOW+q;p|n}|4{<`*JnL*{ zZPg&w&P`fE9CUS)%|Vp;sClU~F+ke4`-DD8=MrkyF3t$D#Z~HoYwdM}*6MU`i+OS- zZ9Ob=Hd5eE8f#NU{WvD_hhc9d3KZzpmwx93E|svq1^zzBZiiv(7*X%RgimKr`GpZj z6n|ww=JA$qCpuhUlUAiPYA`T_KKtQnh*Xy;K5-M ze59@IAQJ-f1b2YwAMm85tvlLcW|b(5^Cw}2`RlLdJC(@UqWOqJd}E8F{L6z zTv(3xiUxn(#N?&^c?e1G-{XXNs6%4z#Cu6dF`p5_l#zT^6L(}UI5P@g9|*aFOk*FR zWxhr`YIP8`rbX%`Pmv);S*FefzThx;?hl#L{;OlCS*x_;=HU2Fc5ZkAQ%z2=baX*| zqR`soP+(1XpxYNlu$6a+Vt?Yu;_%W*l0d~K6-6dw-SdWc;IDhI(VH3v<{e}P13SO$ z9PMR#K4Usp5p6Q`mX5mK3!X0f!==fUY+aU1EM$>`C=1VZFA#TO$MF$bNIqhf;Gd#L zs@8_2)%DLyt%cwtUH_!v0BFM#zM-a8#sv6$T6XZgda%@G1yUMx8wDGI0ddmr@EDSA zzRv!8hOk?2MG6Y;WMz?F50>E4Y4j1n0^tGe&Ni7e@J!%TQ0+ry#&GL+7f+kSd9R)= z#)4n+v3{L9y`F5lOn<_aJ<7j|O`P4>P=q^dFqjzI+3u1mdh8P@Xp&*!?vHs_>EbSD z(%TBY;=Eq!DADnTe0(2dp%=!U#z0%jqJ{zDBv^2X8n9VVreC9NQX91@JqaFEIp03D z`O&BCqQYiTUxJ9V1$s@N!y}u+S{Q3ZZ8FUV0;wvkMc25@z=L2*(w_DxSQ1{I#x0I$=<140JRqq2wCHSgrBh_} z)KL6YOrY`F)JrDpdb3^6n+_hhF^0-Xw5Blt!9)`S=pish)~Lym#-K6D*RLv^vkzs5 zl!e>4FHMJ=w=IP?X9Ztj?|=;nWanro_O7TIXmU)@gVQypJtoF~7aXv$2o|}gf|teY%{6A2Ni#@Z!2|@4S}d;al1Hb$Ubx6aA19yvi8*fCD{O8^(UKc)LEXbEhA;DxpC`xSv>Jog=)enshn)H|QqV4|7r==lv&M1Ys2$nk(~JsTJwjJ^TH&sOQr%L0)( z60XaAh$v0px|!eIps_d^DF(`GUwkWekl%xSf3;g%d`DV$(PoWCZ-$As_O*<*;GiX62iyT$XCFgc0e}vU#^Lnwq}D*XVTP)Yd}FoygQ4MGCK2{H z&%WnzjIyId=|ryA@hLN5Jmd>6V@b3o&j5H87McZC1Z?V;kLlaQ?;Sz}>}N)NEx@Di z-}A6=dREBTSzQ?9E`OI*75&iT#YB~92_JF+1=%x4kA=i9Rrf>x&FHhiJnMRERp+?Y$zgsvz=TeQt6KguN+uD;n>p=IKYGIofx3JifAFzO&)89zj zk?n6fj?^wO*X43m^y%ll8d{mI&dAWz|J5nQ%8Zo&vEZZ8x=R&TEoZ5b6#sVd+=sh- z5+98HVcy#6!ykk>`rG$zHrq<^n<}J&lv@t@{VRE537=e#EJf@*0$S!zP3bh61*)LeldL5&N(VmvuCR^pkXaL zZcWau?@lRkin#7?eW(mSLkOBPfXozQ@(4{dJHXQGJ<=gFkKW+#e;kGey^HxlK)VH= z8z*7Fr(?YS{Nv+e92>(5?I>Hx67gyz8R`QjG(!f^-{ z!hGFKDk~M)C1lZ5z}@7tFBc9`I79~IgTVEGgV<=E_y!6bxiG;AgK&X(Aa<&EkwWxw z=4XtlCMgf$=7p=0Wg4o(L^OYNd0-82Fh7S}*7TiwRgNnTQveVqU1CNUdN|Ejwt0G# zvb{c_^jV`;tnt84RS+7OqYQ`@gGR^U*;hEe?R%HvY}_SGA?q1F$O_gzaLk#(*I=Rl zs)Jmc>@!9jf{Ny^iAY$1!`SbD>wjB0LOwf&PZfUqtz#?vT-mY|Wza3@nHjayWHM+k zU5c)X(40pe?wO50vR**%hqCUXq6tg^y#EGF?l3ZLFZ#%)DBS2RqQo~-HZKL1arK?Pawr+(cGyDPrhL++SwX}F`ZY(o&xWoFTLU?t zZ9COrfz#@FKkV<=ZehaROW7Li@k?Cr$YPqE@JquV_OQs59%;|g0cH^#vuX(6YlHmM z0z|r8jnnm&mqL=@F~}0Nu?8%)7`mSinvZws*5A6-N>(4*uf(BLT&C4$4iD}W^n1Sg zD31H@`|E24Zs1&Ou|ptZ5kLl@P#a^Ti+;CfYzebf;dj_(++y|s)l~~MJ0x8pl~Ga^ z=!3j5LTi6>x}YnaB4?4p2rJUDc1gTkWjVi;0rKe}VNV@(TOuvN&y@@SGhJJ$Lr)~Z zIzEly4RW#Gz>HsHe1v8K8*?_+)UUJgc0Dh}*PJ-cZWR8aQUs=9C-m+PqD}k>+WC9! z{skmA1ENG^3)zJSuG=W%=9UQ#2O#NMC>^DT1jqK{8sU_plQg$8Fr^cx*X#Zzbr`86jkM zqD}2Yc5ChalbglR|E;VEOCJ2q=^lm54J^U^3Q)O12JY!x2kLDJ<|Q4Dnh+W2v_g88 znB$uuO9UW>>LZQ(XN@g@0?kt(<3EE1Il1AFk};s)1cr=xxJGLDwp;0WH{a&Ks2~>~F3Dnj&tfT&ryPd7+M3b6Tihk)JL8*{{>h1O3$?wuXUw z$F=1w9}n|S&hw+G@-&t&J>GK)0>xsZm{3^UufM_Xj4XAZlst@-tTh}`i@P6Fa44Z_ z^dL+N!qw=TCFMR1+3cNU14N2_eq3cx4*+2PAh`z37PLx9!I#QCsK8bbOsmm1i>Pc)@w zxw`ER#_v4*By`VWi{3x`>nM|i+)=IHDIxK8SD(PcUh%YaEg~$)<<`nLi+*Iu2!)>3hEgUgD?B*?cToyV1<0s2Z>HeHLHqDpY&L+JYCdnNJQ%~ zs|Y>CB~pO6?*`Z#bNQn_QHz*UaoknJ5h}}?YTIb6RDS`4acs;k1$q}Etc;e zFH>jZlgWI$VlnM?%RWm}PI91X*JhGKu_@a{teOqy=-ro(kBs$MX2LQ~@{^PS)mJkG zVH=zu)lxOQ?Yk8nmI$93dSz9&bX!J^#UZH(fk(#{ls2u6mNd_1KA4fP$IaqXJ7jIP z#cU-~NVzWrK<*&HSm>M677C|KaAp+JQJfv!{VMWS!)}WYp&3u=uMox5pZ52(F(YJP zceq(+_jh$MuAAoCu?YrwD$&e%_%iB;w@Zs1=SscMP__+OKc~XF0C@~6S6CP5eEhH* z`K#GLwZW!vv`8Ya>KF=yMxD>JlGHdnE3=_p#VC^wsFgFn#=&QQ=J)p&9j(`&4zB~H z;uW7I`qk@9rEkr(=Uz6BFOM^P4lgg<$QR~6PtatLhUi4X>IJP*Jw+sIEsgULG19T3 zWgEov{dCb&AuK6OULlf;`&_w%xm5HTEOs}LnJ#|P$2lk{7Q$(! z-9Hiue$3J*hc4AQZ16%7|Gi;q_BqZIZ@K=L7Q=KYsz>$OzXAJ6>L1%-H)2?2*u_3F z#V0Vwt)ckrecFhJ_VHsW=p=izwsPqofKDqmd59|6KQdsMFMX2!HBj52xo}hCS_&#r zIp=r~SUg=N zAU9z&`Fh+3umdf$7XAQzL!-l={H+V2Y6;IjOnxR#3p~NkIi>qozTev-&%GW&er@Hz zwcc`AKuwf&?w1bo>MvnE^z3a8s6u~Lh6&a25CKf*X7nVTz@>FPwXbA9sJ-t+I{uUP z_gx}ur{s}kl1^E8wsjRkeJfcMp29FT*6|O@HT`o#D~%ye(Jm#N$tI4eW}CI9L_nqr zdaL&xE-a{3Y^K^qLG6zY9uccG`!~FkH&XL?$cYZx1Ofs4H=17@nWwnkYKneA1NT#* zS>0g&`FuJrO~Z; zTr*H6r3J^DXioY0Zg264HDg>G4|sC|k1P_hQTr~oUs&=v;XYQS@#S`PfYU{UQ^Yc5 z@%x-vW@mdc8#PvPbTKz7BUqMwC9*O#xE%gg6!itN3pU+p`ReJ?{+C!Fx7`=Ai9VVe zXm02uMW7&wvaZQegErRHjq<_tTker6Jv~*(&77+bLLx&Wy+aX8qL79lc5g+(hWWI6 zzL~OJ_q!^wR~kQAn=)Uau3cOIzQ?J;EY?=KNtho8IplI*uePtjluG)}#Io77;Cn*s zahKa+-hYouP8PNKRcgi5z8|3%@0PW8 zx-TIlN)p&y?|K;A`&#uCL6U3p#)WKSPl4Z2m$rkPWxb18t4c5P9|f#JjPb(WXK9yG2cn%lLIii z!%fEiT{$oNdYr-eP7!W+_4{vBI(w>mxK-VaFXb_Di*}PGSP%x^$dS`!%Z{r%u6>^! zC9!E@wOP$Z5xUwdvU*a%8W;UyB$*|7n^%|A>Nl`SPAn4nAzP^ZtMi{RuST&j#I0W- zVCJWCj-%`H`P=EaR0iEz0&T`Ffx6$#_B{9MamY&wqiB3{WUaSIlFQHeX?dW?l{K8J zS(P8rH@4tgDL?AIgxHanp#0j5GpzMH7GyOi9r(NRYob*Rq2EUmeoeRUy2fLwCXuM+5L2fIKK77APCNQAM2Jci;#~aY9kK64{M5rL z0^64a#INRaxf-|-8;Y7HDT$^A-OpO9XUFh?QLXWEAiP}8W2&J0^&!yyY~@wf=T95=Ny>@O6c5&`#{C-BhIP~fRw!glFn z+3mNHV|;ulPn^S9foXv+nChS~%~!67H$dcOX2*o!_`$|GC+Kt;P9j4WCM}pH+F`#c zZN6s3X+|y5b7I1s$^45EO#!s3Uj(*w&61Q2PO<_<)Lgl~k1*aPl^9hTHWVA`wVOMh z_7r?9>e2Q5to2rE-1qGslyhiF&#ien4y3~jNR5Bmi5CBrtvsanSleQ{alOHLj^{-GH<5;iy?UNiZpXCE^ylpZ9NT=-k<{`Fpr9^#8OYESecw!h`aW#|>MRGqW;p49=LlJ=6sXNQ_sY@HhuteI7u`|C)+eQ+1KNDSg zT~bfK^iljidnPg-()y)Kj8i{`FbO>2T}$qAe;M@H5_NKvzyP&5;c4r9EQBkMDD<5- zeeZ3nNB-A8ihp$c))cB%_0Pmg!iF;x!9ub&Ws77*TKE9$1Q|oC=|ov&L0TudXL_0e zUAEIzzjUz!a(YH4w=4;okxO`KpC@8rflrv+xFt<3IjCK&~;I7#v zQvqvj%s1J4W|6h92ah6|HJX2MB22C>-IU$SIVB8HkM6s59vPNcZv0y9y59Et7`c&S zbc4LbDQ-qd;I?#`-YgMexO(-e*A^S?3=l7W0;+q&G6p_PBEabB_E1vkkep4eejCCP z3;a4-cK$vz6eyV2m)G!EF!X@LzRqd$_fr}uyu=_4=hlz5-r|0c2f7I2!@;N|(ccwI zrda0lnREA?;9q=dq4wY+@;j+XWhP`zpPfDLA6+&~C+OVm_kFKZgse%kl`f=_3+3b~ zw=dkdH)esXu|NGQ#)l*_Mw#(5;_79dBJFdo@gH3MT(L6PZIsN>euViEr*54cmu)ll z07?D)oRc?$yX&udiJv5$N5Pp>>A$zYepv`bTT&f^>_W{{ssj{jtfoSh&Vf(O74E6h z_G-C#`;^=`s|E4e2pd8y*ARb8f4Kl*3_Z5m-fYG61h0kd)1Cu5JkDc4Pp?D7xo?Ye zdduo{9!Qh8Xt2vD1a2!u(w9Y zrR3K@$3LM}nH3th#nrSo(ku~rsy1fImb4D21IROM0?ar+H%Q!I)m(hq9lMj24PQZ1 z_ozmET1zPi=ZTTI?bTTcj^&p446<&Pg=TVPF_P?}${i?c zurNr`#0T`Hj99zx&n!|4vqJcoeDUVokZ`vG4}VanBzrEix>Qo8=q z!W5>g$g%p0-Gbb^rOge0qvE;H(h}##)to0Iw%^Z{Ryw6T7RR{xV+HA3I1xUz??hn^y%WB$86jxnl`=KGe;sish*fQ9aC`fePD{a28n}lEU*Y)?&P?qlD)vGy{2F0ED-Hs_p)PII$^_(*+ zSMrn%L?axfh{~*wU)QJ>9#MX}?d!frM+F6SbjiAEjP?mj!>j&jyQXb!6gmfZ zXSDZY8Po<>D^tW9ZO_8s$!i%j8s)<2a}RgXxZbEqDE_H(b>Vc~1rEjHfZ1<(!>ZmQ z&GCv@GxY90=PmaAO4#9`_njZzqm#c!0LzDwDZxpm?SrIue*RhvP=9}r#3g@;msDTJ zNR73duB51V(x69yVQDj3W=U7{GWN~GU;O+6v3e-?xBLC&m+|#VmliH@Z?Zu@-NZ1! zHiPbxqy-}yqcypaIx%_SXb<9Q-pxcsTB-zDxAKx;@RAJEgkwO!6iD-<_LsgVo2}&r ztl{*K)CxS!Z^n?JHpdf*`Pv}Fod&s}%(bBT2E1(6WW3V*daBU`d6OlX0xi}uGqbo} z!)ori&|rg6AT${cYe6U6l>I(U>Aw*r^w{25p%xj@e%!4NOCf zYU9!pXsv206FGp)a`LpI8$|h<}2#OpuDkLcV&J-J~Xt_Rf)Z`{b-l)2G zC|?|s$V<0@w*L5eW@oh1T$a0W`nrMpHb}uRwD>#8moT2M3--sgDfQNWuQKuX{B?is zHIsTJ^aA!Q?KZcW6t3A7IPiD{N_Rha#5KDfP7XMF5^=SGUvRJHEt=gY+wptaz!%G! ze$<{Yeu*}C*pKm}_uu+hhI#Qv#D^IBJaCIjX2oDV{T{d5yl5p?P&3ZWlhfUVgyH*; zej{PYYS0AD<$qzb@Yg0w)l!k|EADdzy(B~L(^EEv*=g<=lx{^xw|Sd*eRD1I?)fxtNBdyW#;hkq&ZewK(Yvmb6#F|0qP4_QE5W``{>%DQs-3y1(4LiM^8=X8AE+{PRm?7>$WfP>6U^> z7A4r^r;t(rUp+L=pd!zcc zI{I?UWTW&(@NTuG3aZmLeKez7Ps}k#!|pV`$psOtQ#+x-Uw>R30llO!)@!eW^>Yk* z)q0a-8)nHB`(R5>O_$6m1AFym${4T*mo}S3xc)co6NNjS%&f=*-VGSx;)hSpNSU0H z>nAyi7c=29C9CAsMZ3HT?OdNM%`uWV@z`x(WncNMgN&erwRY$7#x2v@D*wPxR)DO@ z0HcU|;T%pxlsI$zuV2TBhR#@|C~BWZ!8$lmSce;*sJ0K6-#hH>uQTo|1&8c{%Hd}p zx;XV@uJBwhfiHY(4$XOg)K8oUVMQ7tLtWn8b5GoAb>GMIn0_}d`KEu3Yg?(g!__WS zstxiM6`){cm{};q+N~?#Y-H|Iqx&+E+6X?%$GahQA1ovGG<9m82j--+6e7;=pp+50 zQqY*u5J{Vg3?$a39|5v69d@g~v%YOTyo0{=`Ha>)#2)2N((bEKziY|g<<3tFPFwnG zs%Ug;fFuN>zEZTX+r6*CjNB~c7R*HOlEFg@>oqf%tI!wmby3v=7o|Z;uOe~A?_BZG zOZ=1*bl*?*`EId4cG-x>JvlpbF13aXMJMiF9y>A;M`ZUS1ErHCNUi!P zD8m*20DaO`h6SFV|M`$}d^_TLYq-`s=awvQC+Ym-3x|OMgpQ(!nBcM5r^}i^0pt0U z5q=D*dAojr<>LgNTX%%NB>arXl`am_#Qaj|+3w$Ig=xjgIO5tZK99Egl_sJuyZ1`t z11x6y4)v6S#)K6hj$3=>@zc~qlLmO`b{6?^wo(k51Ir) zsf2&mgNcXA<9?B#m1u6V)04xFXnW!ql8q4-#WZiPWd$icC7ZBHRwQi7W z_;-`8ST=%k79EKksH}KC*`EgzfK@J=--%XJj0&vZJtnkz-_dJjE7A2_t${a+Xy?f4 zu+?vIO`sxY;@f&Px@vBnZ_Ni`Je-{YpQ4(pK z_@ZIm0F?q|$iRN=Y7}1`tDRCvwIP$ZdAGTra2WeGgukPU+JAYRL9^&<(PLL) ztrDW*w6>o4SUdQgB^%Zp+CM;ruof_(UfkO%dcYYkfN@Coq$EN3bAK}bV2C5vHf3d!QN9Xr;O%B8FqhetQ*CC^5Ik*|K1A zW_LM@>r~a$+fnBu7Pry7(9K6`0D*%k7!pf3_v*&mGrdyo2~RU>*P*1zjodD`N(2;6 z>bexD;&Pj>(t78{w$^}>@Z&X-YB12^X{W^6ZM9tUR66XbjSbL9c$*$aBoH%`C~=j2 zE|gL|$dxVR^Gx{MI}U9|+z)Bx1|u zabGmg*l`(a(D`ik)~Q9@g z=86o%s1-+p6Q}B}=SDdbOVl`G(0xJ3GhR6bOyqL&@03Z4?pe3m#o^yN(Ws>tVKIkY zHq-wdLn-jBax@$bpQ{0$jkmwM#xOwt-3Z;98K*o?q3$thJkfm>d@J3%04d-7(69O! z+b=Yb-al#MtA6J?_hn*VyZFVQMtkIKaxzYtAx7eI(a`bIfVj!He2Ch81B}% zcEx^UVA&%34jFR}s+zh2cfU5d;3i2pWVuj^d_ja4X~?IZjbBdqVCgTdC`I&u&&zbA zj4gvzDso^)Sk`7fZFNjIp}V>=>Uh4TQ-7&G?4BRof8{e=90%=5^=k*2IV`yACp`;K z{bNxs3l0{h>z*!yFf(G518Naf>8{gVrd<8SkKX@?Yg;fM>p9;s(;mJ}|L{+Mv5`&l zXZ_8LwGw)^rSrT2?z?DP|98450yj^Is-HWVZn^ccyO#`E1NP)jXD5_yiSO+vgzP1{ zj(@fCz7`&|ZSyT^@07GbBIVlcAXi=61+x?;q z%XVszkggK)NIT+#kIWmvrAJVyYx5=0AkwZNzdfJNp{8XEn_xhlcWK?$?Arf4Fxbh) zV557aVJT?qBU*oJxb5a0^|Dpt%-u-htmxQ8<*jHh1a9*Ei!CUo`ky_Dw*6&eZ_GD? zte>T7f9`3sIyOR;y%=;yIwZzv(RN6p=MRa}bI3r&15Z2|mb<%jwxWoJ@{+5OqAqhR z=sJ9t=o;sS885G^J&3|BT#tt}Y#S3u8}nsX_tlrtr~ymMEMH4t^4DF%!H=cIF(0d5 zbB9*F|0=jRnWyjOD#63j^1Dc%!%1GSDD)qoEEex`Z6Qa)lP88mbRn>V@Xes}x$X&m z*r#gzZ@Fq-MYq@lr=m_w@x;>d?ZV)qibl+X@Tg8JJ~f++7%)&vR#)z9D5IVY#p;?>e65r;Wr`P`%s&^e@3b=qW)*p-3+H6EU*_Z zyno_bG*8Ht&7nD`{?;UmTPXxMV{?ImH=MQJi`gn_P;o=c-t5!s_&-f~v;04=8B-nf zEbNZE(5w0!mq;?2oAy)XWTbPJV4~h0%pRinEtE-Ub*n*G~5~u)*KU z(ZN9`6)ha_gV*nq=>9UX^igfM5BKe{EUlf~)QppHAeaByg!VGaVh{QyZtCl@@MhtU zhbu-qFewMZhNPn$Hr^eVicC)W9YPB>;*wbcg;7%c4A64&!bpNgn~g@mTTw z%U3rgcO^qYFoSaC#|eK&l9y|cdp{sb=i@AzrH4uefM1y>nYkG;%S>tkpx{db=8Xdy zii^zOoBK*?v*JF$3Oa$It3Y&oL~B>@q#ULT<+Cf0$&m2D+mc2t1+jd7ZQFjbBlEMR zN6wgkQTg?MO79@-%8g{Xu2HIJEm7VR!#_G=$CuC-t%ufCH-4r($CM zUvweF-+i1T^aaD1RG(Ba;HN9`NHdrVtwnNf44l0+X8r6UMPN3$Z2IEiV_mc(qM2Qy zk%|gJ@$5$nZgzHRO7UYGd}B;u1`2{XY=?pJPjE9;=XQ*Rb);8k-lZx1!+1?N_43oD-vwJO{k+RoK&;cTO3KD05UR#01v zx_%M(kSqwhk331;Ylc^Jc>Emx;u}{#f~ocomdl2mFRXI#o*ec${&hC}T2IUhqT9e- zdQ&%dQ}zYK5oFl#Q*VcK@ACE*99WvjaRhprIttc-jxypch z9XbQ;;KS+ETuTPlWdZ*>-v_KRZpb2Z)>aHKqn!_Ux&vA^imGFwgVPdy}kl-hX^ zA?RAav*QjFwK}B!77c@hbKLRk!4%Q?GTQYvAoEh&sECVqIf9Z*8fx<6CpSB#w44OR zefIo~Rx-TZ50Tn#*xCNIN$Dq+|(`FU5f0H?d?`xY4`kPDGbNi4exXw zB^OP+2%DDf1{s+k#)bmU4K!uV`Sv7}38ED7xfk+svRxTp;5}6p!PgH9^->^aE2xw8 z@xtY_LW%B1m~4zcqQ0&uY#(ql1+J}GvWkVfo^?#2|2$i`P@Mcw*IiHg!+zXv;jDex zU*CMxvPqs)#4vBZ`R`W`r^Nvt>Ugf-eKU{4VN4Pqb1Zkc3O16KJu-;ZeaNI1j@I(d z27dkNd9?on)~Rq45quc7ZxwTQEwa03(;fLD9{;K_DGk-edMxq zvFGZ{enS6_oEwmgsz1wKU!~hmx4@V~9oU{jo~gyW`{8(69^y9phJdvO%Rw`?WO9r~ zd7&Qc8eL8Fmypu!2ZwNeIcztT+Eqz!*xgsC$%!Nx01#ePZLZFFXS|M3>>U&{6H{cW zW!zo*9TTOKWf;xXlm?;qMVp>jdMgMDJ;MRa0^EC~NEY)DfwVjhb89;*s`&#zWxZ`PaA-A5E&^@h5B+G_NjJKuU)P_hLIIHAp@z%UlR8X9$Z^M<)2_WjU`2-w$Y z(m&}pt&9U$Mw76d$ST$bzv zhx=bFIS|6MxqXB52)vPRbBOk*Qnqo<9S3pYJ}j$`NFl~RW>kCVADy|c$xpymLnm>a z-b0RB=kEH?}Eg9|S)G1{L;p*$sg^&?nm!B)?dXlMge`*4S+ z!92KvY;KyT6mMt@%O*YOW2xCbBuwVh#J_Kvopyt6T$(cjDypKGid$n5CSN719U*Eh zy0PB5?gZVy8z3v*VQk{WI5l!@@F?F?Yufyoop3_4i`q%uSukUx3T`K}JhEijD*WHf zb!w!Y6=lxtLf^Ik4PVD5L6w0DkDS=$>e_Y5e7775cbqP8KZ85vSd-?t*368mks0{6 zKh~1)N@=u^4$ymYP5HMC@at5wIP%s=F89tm~r_d-(LQ%L{Z> zmpW{|bg2006D@JDlR+EckE^`t7(hR>FVMTz<4;XJLhB9u4gPoh=iC9BOwZxbuQZus zJ@DFTL9}U_3!loaXjL*cMj%~>BFwex9}?SQiAE7 z&(o(=d?`}p?5}c&9OhOExNev_Yw?ks3ju&sj;zmG2yfRso7*t&T^b~LjCw|cL8GE% z;dYyHZ~7Cu@csan-MT8q#pZKJ34?)T+#vH1=j54N{ZJfUqC8>W`4a(w?CL)?!cxd* zd1#)l{cu8;fY%+->v?qTcu1`d>-^E=ly_Qq%0L$|GXaK~8YwC*C7hEI!h z56)SHbHzASTj64PFTNhiFCu%qL=vp@y4SE={>5I~p$=wxlLjIjEsmk~$)TQqx&!0o%298Z~I zyi*KwhNtgw$*`C*qd!Dbhrtw2L2;5S*nL-4 zZ6I2b^a4aiT@t`FJPx{QU!^S-&f0TwCpkbDfUy6#rthQfrvp~i0>3*31exJuCfxa) zyboxytY9IsGxdEc)GaFsAwrOgj~UCM0w0!q6Sjv3{F|SlM$+4DTmJU85ar}&h8G*;=dV1*#lnkNO{0hv&1 zdi>o1@346THB>Mqn_o-`t%)BjcbN1LkN`UB@cfOAR7EO=>e`OIb2r5kN~K8?>_?X) zDU~NWzaTI+nQ(zGYpb?L(qlblArY0_5yRa3_dqaO!GJY7he0Da>UycvQ~!$Ucl{H4 zb=|J*Q!c_jiy+nqdjT1d(Odblu!1ODNr@>S6wG1)>3F9pWZJs;l~bL`v?KjP{T3$!f$;{Qh0KSn~6~S zPF9E1{~dw|=3ocDuh9<~Isi zbu{aF6kPO5KkJ<=i8MwM{hZ!~$uE@s_zpkYo`51#>Q7{9HjJG^gh)Bm3+RNRwuV}5 z`;Dettb;;oYDm4| zGAU|5nZgG|f9%b|y@&Are>?tZn8MWU42tSmdj_ z>}|Q(9g<8q0!3NG_%3&7;`r;;%I%=Y`wzb`-Hh{)AxvvS*2%ILv}e^-nLxGMog|3%s9ES(!l*4 zRyAZzkwzS@85!!o#zeqXK?W}6C1bE|i1vYZD*?T9Flet`laN#fWrvy_^d`4Sqp$aZF~6^?M;c{V+&Wb! z&5`k_6wxmKr#-7#$oYqN3}Ydm8E5a1OjtQ3c%XR3;i7Cu5+2Y(4w9eH$><@K^-e&o<)BqCt z9SRLL*R@q2Bpglj5ytkDi0c9#Ludy7fB1jA2OJ9kfWrZa--(j)Wx(#;r2*O+`p+BG H9pe87B5Ahh literal 0 HcmV?d00001 diff --git a/BYODSEC/src/main/resources/images/logo.png b/BYODSEC/src/main/resources/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5637f76b7d294863bb500a27b22ccc7211180e GIT binary patch literal 15551 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 + + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + + + log/client.log + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + log/client-%d{yyyy-MM-dd}.log + 30 + true + + + + + log/server.log + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + log/server-%d{yyyy-MM-dd}.log + 30 + true + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BYODSEC/src/main/resources/scripts/db.sql b/BYODSEC/src/main/resources/scripts/db.sql new file mode 100644 index 0000000..0a391b8 --- /dev/null +++ b/BYODSEC/src/main/resources/scripts/db.sql @@ -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 \ No newline at end of file diff --git a/DevSecOps/.gitkeep b/DevSecOps/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Integridos/.gitignore b/Integridos/.gitignore new file mode 100644 index 0000000..ddbe856 --- /dev/null +++ b/Integridos/.gitignore @@ -0,0 +1,2 @@ +.env +target/ \ No newline at end of file diff --git a/Integridos/log/client.log b/Integridos/log/client.log new file mode 100644 index 0000000..77ee674 --- /dev/null +++ b/Integridos/log/client.log @@ -0,0 +1,15 @@ +2025-10-06 19:44:03 INFO n.m.integridos.client.ClientSocket - El usuario 'alvgulveg' ha iniciado sesión correctamente. +2025-10-06 19:44:12 INFO n.m.integridos.client.ClientSocket - Transacción enviada: alvgulveg -> smt4497 | 50.0€ +2025-10-06 19:44:13 INFO n.m.integridos.client.ClientSocket - Usuario 'alvgulveg' ha cerrado sesión correctamente. +2025-10-06 19:48:16 WARN n.m.integridos.client.ClientSocket - Intento de login fallido para 'noexiste': El usuario no existe. +2025-10-06 19:48:53 INFO n.m.integridos.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-06 19:49:14 INFO n.m.integridos.client.ClientSocket - Transacción enviada: smt4497 -> alvgulveg | 50.0€ +2025-10-06 19:49:14 INFO n.m.integridos.client.ClientSocket - Usuario 'smt4497' ha cerrado sesión correctamente. +2025-10-06 19:49:35 WARN n.m.integridos.client.ClientSocket - Intento de login fallido para 'smt4497': Contraseña incorrecta. +2025-10-06 19:57:25 INFO n.m.integridos.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente. +2025-10-06 19:57:31 WARN n.m.integridos.client.ClientSocket - Error enviando transacción de smt4497 a alvgulveg: Cantidad no válida. +2025-10-06 19:57:38 WARN n.m.integridos.client.ClientSocket - Error enviando transacción de smt4497 a alvgulveg: Cantidad no válida. +2025-10-06 19:57:42 WARN n.m.integridos.client.ClientSocket - Error enviando transacción de smt4497 a noexiste: El destinatario no existe +2025-10-06 19:57:52 INFO n.m.integridos.client.ClientSocket - Usuario 'smt4497' ha cerrado sesión correctamente. +2025-10-06 19:57:58 WARN n.m.integridos.client.ClientSocket - Intento de login fallido para 'alvgulveg': Contraseña incorrecta. +2025-10-06 19:58:04 WARN n.m.integridos.client.ClientSocket - Intento de login fallido para 'noexiste': El usuario no existe. diff --git a/Integridos/log/server.log b/Integridos/log/server.log new file mode 100644 index 0000000..cba59da --- /dev/null +++ b/Integridos/log/server.log @@ -0,0 +1,46 @@ +2025-10-06 19:44:02 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:58622 +2025-10-06 19:44:02 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para alvgulveg +2025-10-06 19:44:11 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:36988 +2025-10-06 19:44:11 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de alvgulveg a smt4497 | 50.0 +2025-10-06 19:44:13 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:36992 +2025-10-06 19:44:13 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGOUT para 5b3532d0-d6ff-4845-8aa0-03a1d11a94b2 +2025-10-06 19:48:16 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34878 +2025-10-06 19:48:16 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para noexiste +2025-10-06 19:48:53 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34066 +2025-10-06 19:48:53 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-06 19:49:13 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:44270 +2025-10-06 19:49:13 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de smt4497 a alvgulveg | 50.0 +2025-10-06 19:49:13 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC recibida: 7f00bda9f22b98fdfbd0f2c1b61b4d40cd0eca66a38efb1304adf3fd33599fe0 +2025-10-06 19:49:13 [main] DEBUG n.m.integridos.server.RemoteSocket - Mensaje JSON para calcular HMAC: {"action":"SEND_TRANSACTION","payload":{"fromUserName":"smt4497","toUserName":"alvgulveg","amount":50.0,"nonce":"x8Dsu3aNocd014/bH7lymw\u003d\u003d"}} +2025-10-06 19:49:13 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC generada: 7f00bda9f22b98fdfbd0f2c1b61b4d40cd0eca66a38efb1304adf3fd33599fe0 +2025-10-06 19:49:13 [main] DEBUG n.m.integridos.server.RemoteSocket - Las HMAC coinciden +2025-10-06 19:49:14 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:44274 +2025-10-06 19:49:14 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGOUT para 06a85107-285d-4b10-a412-427e63afecfb +2025-10-06 19:49:35 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:59576 +2025-10-06 19:49:35 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-06 19:57:25 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:55456 +2025-10-06 19:57:25 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para smt4497 +2025-10-06 19:57:31 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:41488 +2025-10-06 19:57:31 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de smt4497 a alvgulveg | -10.0 +2025-10-06 19:57:31 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC recibida: 25417b475e6cf28707e519bb6d49a1241d502de90ef5db3c2ab384fcabdb6bdf +2025-10-06 19:57:31 [main] DEBUG n.m.integridos.server.RemoteSocket - Mensaje JSON para calcular HMAC: {"action":"SEND_TRANSACTION","payload":{"fromUserName":"smt4497","toUserName":"alvgulveg","amount":-10.0,"nonce":"Z5vRIKxPXsBBV6LKKZZITg\u003d\u003d"}} +2025-10-06 19:57:31 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC generada: 25417b475e6cf28707e519bb6d49a1241d502de90ef5db3c2ab384fcabdb6bdf +2025-10-06 19:57:31 [main] DEBUG n.m.integridos.server.RemoteSocket - Las HMAC coinciden +2025-10-06 19:57:37 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:50254 +2025-10-06 19:57:37 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de smt4497 a alvgulveg | 1000.0 +2025-10-06 19:57:37 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC recibida: 2e2ac0a1df12e8127bdb046a932cee65187c872fbc536161d0b982037142fc76 +2025-10-06 19:57:37 [main] DEBUG n.m.integridos.server.RemoteSocket - Mensaje JSON para calcular HMAC: {"action":"SEND_TRANSACTION","payload":{"fromUserName":"smt4497","toUserName":"alvgulveg","amount":1000.0,"nonce":"3dOKgYJ9fKOV4IdU/94Arg\u003d\u003d"}} +2025-10-06 19:57:37 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC generada: 2e2ac0a1df12e8127bdb046a932cee65187c872fbc536161d0b982037142fc76 +2025-10-06 19:57:37 [main] DEBUG n.m.integridos.server.RemoteSocket - Las HMAC coinciden +2025-10-06 19:57:42 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:50270 +2025-10-06 19:57:42 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de smt4497 a noexiste | 10.0 +2025-10-06 19:57:42 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC recibida: dea4a26a0fee02ae093acff80eca84d6409918b58dcc0e1706f8c5bf77bdaa31 +2025-10-06 19:57:42 [main] DEBUG n.m.integridos.server.RemoteSocket - Mensaje JSON para calcular HMAC: {"action":"SEND_TRANSACTION","payload":{"fromUserName":"smt4497","toUserName":"noexiste","amount":10.0,"nonce":"ok94L3xsMJG/626yvUlyRQ\u003d\u003d"}} +2025-10-06 19:57:42 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC generada: dea4a26a0fee02ae093acff80eca84d6409918b58dcc0e1706f8c5bf77bdaa31 +2025-10-06 19:57:42 [main] DEBUG n.m.integridos.server.RemoteSocket - Las HMAC coinciden +2025-10-06 19:57:51 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:53674 +2025-10-06 19:57:51 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGOUT para 06a85107-285d-4b10-a412-427e63afecfb +2025-10-06 19:57:58 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:42006 +2025-10-06 19:57:58 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para alvgulveg +2025-10-06 19:58:04 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:42008 +2025-10-06 19:58:04 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para noexiste diff --git a/Integridos/pom.xml b/Integridos/pom.xml new file mode 100644 index 0000000..73456ca --- /dev/null +++ b/Integridos/pom.xml @@ -0,0 +1,155 @@ + + 4.0.0 + net.miarma + integridos + 1.0.0 + net.miarma.integridos.Integridos + + + 25 + 25 + + + + + + com.google.code.gson + gson + 2.12.1 + + + + + + org.slf4j + slf4j-api + 2.0.12 + + + + ch.qos.logback + logback-classic + 1.5.13 + + + + + + org.mariadb.jdbc + mariadb-java-client + 3.5.6 + + + + jakarta.persistence + jakarta.persistence-api + 3.2.0 + + + + org.hibernate.orm + hibernate-core + 7.1.1.Final + + + + com.zaxxer + HikariCP + 5.1.0 + + + + + + com.auth0 + java-jwt + 4.5.0 + + + + + + org.mindrot + jbcrypt + 0.4 + + + + + + com.miglayout + miglayout-swing + 11.4.2 + + + + com.formdev + flatlaf-intellij-themes + 3.6.1 + + + + com.formdev + flatlaf + 3.6.1 + + + + com.formdev + flatlaf-extras + 3.6.1 + + + + com.github.jiconfont + jiconfont + 1.0.0 + + + + com.github.jiconfont + jiconfont-swing + 1.0.1 + + + + com.github.jiconfont + jiconfont-font_awesome + 4.7.0.1 + + + + com.github.jiconfont + jiconfont-google_material_design_icons + 2.2.0.2 + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.integridos.net.miarma.integridos.Integridos + + + + + + + + + \ No newline at end of file diff --git a/Integridos/src/main/java/module-info.java b/Integridos/src/main/java/module-info.java new file mode 100644 index 0000000..f075056 --- /dev/null +++ b/Integridos/src/main/java/module-info.java @@ -0,0 +1,8 @@ +/** + * + */ +/** + * + */ +module BYODSEC { +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/Integridos.java b/Integridos/src/main/java/net/miarma/integridos/Integridos.java new file mode 100644 index 0000000..11b9f35 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/Integridos.java @@ -0,0 +1,78 @@ +package net.miarma.integridos; + +import com.formdev.flatlaf.themes.FlatMacLightLaf; +import jiconfont.icons.font_awesome.FontAwesome; +import jiconfont.icons.google_material_design_icons.GoogleMaterialDesignIcons; +import jiconfont.swing.IconFontSwing; +import net.miarma.integridos.common.ConfigManager; +import net.miarma.integridos.common.Constants; +import net.miarma.integridos.common.db.DBPopulator; +import net.miarma.integridos.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; + +public class Integridos { + private static final Logger logger = LoggerFactory.getLogger(Integridos.class); + private static ConfigManager configManager; + + public static void main(String[] args) { + configManager = ConfigManager.getInstance(); + createFiles(); + configManager.loadConfig(); + if(DBPopulator.getInstance().isFirstRun()) { + DBPopulator.getInstance().populate(); + } + initUI(); + } + + private static void initUI() { + try { + UIManager.setLookAndFeel(new FlatMacLightLaf()); + } 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); + }); + } + + private static void createFiles() { + File configFile = new File(configManager.getConfigFile().getAbsolutePath()); + File parentDir = configFile.getParentFile(); + + if (!parentDir.exists()) { + if (parentDir.mkdirs()) { + logger.info("Created config directory: " + parentDir.getAbsolutePath()); + } else { + logger.error("Failed to create config directory: " + parentDir.getAbsolutePath()); + return; + } + } + + if (!configFile.exists()) { + try (InputStream in = Integridos.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()); + } + } + + +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/client/ClientSocket.java b/Integridos/src/main/java/net/miarma/integridos/client/ClientSocket.java new file mode 100644 index 0000000..977a17a --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/client/ClientSocket.java @@ -0,0 +1,63 @@ +package net.miarma.integridos.client; + +import com.google.gson.Gson; +import net.miarma.integridos.common.*; +import net.miarma.integridos.common.security.IntegrityProvider; +import net.miarma.integridos.common.socket.SocketRequest; +import net.miarma.integridos.common.socket.SocketResponse; +import net.miarma.integridos.common.db.dto.LoginDTO; +import net.miarma.integridos.common.db.dto.TransactionDTO; + +import java.io.*; +import java.net.Socket; + +public class ClientSocket implements AutoCloseable { + private Socket socket; + private PrintWriter output; + private BufferedReader input; + private final Gson gson = new Gson(); + + public ClientSocket() throws IOException { + this.socket = new Socket("localhost", 6969); + this.output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); + this.input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + } + + 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 sendTransaction(TransactionDTO transaction) throws Exception { + transaction.setHmac(null); + SocketRequest msg = new SocketRequest(SocketRequest.Action.SEND_TRANSACTION, transaction); + String jsonMsg = gson.toJson(msg); + String hmac = IntegrityProvider.generateHMAC(ConfigManager.getInstance().getStringProperty("secret"), jsonMsg); + transaction.setHmac(hmac); + msg.setPayload(transaction); + 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) {} + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/client/ui/AutoShrinkLabel.java b/Integridos/src/main/java/net/miarma/integridos/client/ui/AutoShrinkLabel.java new file mode 100644 index 0000000..7b3a3fc --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/client/ui/AutoShrinkLabel.java @@ -0,0 +1,62 @@ +package net.miarma.integridos.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); + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/client/ui/MainWindow.java b/Integridos/src/main/java/net/miarma/integridos/client/ui/MainWindow.java new file mode 100644 index 0000000..8baf548 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/client/ui/MainWindow.java @@ -0,0 +1,492 @@ +/* + * Created by JFormDesigner on Wed Oct 01 16:21:10 CEST 2025 + */ + +package net.miarma.integridos.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.integridos.common.Constants; + +import net.miarma.integridos.common.security.IntegrityProvider; +import net.miarma.integridos.common.socket.SocketResponse; +import net.miarma.integridos.common.socket.SocketStatus; +import net.miarma.integridos.common.db.dto.TransactionDTO; +import net.miarma.integridos.common.db.entities.UserEntity; +import net.miarma.integridos.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 AutoShrinkLabel balanceLabel = new AutoShrinkLabel("0.00€"); + 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(); + addBalanceLabel(); + } + + private void setIcons() { + userLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.USER, 18, Color.BLACK)); + passwordLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.KEY, 18, Color.BLACK)); + loginBtn.setIcon(IconFontSwing.buildIcon(FontAwesome.SIGN_IN, 18, Color.BLACK)); + registerBtn.setIcon(IconFontSwing.buildIcon(FontAwesome.USER_PLUS, 18, Color.BLACK)); + destLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.USER_SECRET, 18, Color.BLACK)); + amountLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.MONEY, 18, Color.BLACK)); + } + + private void clearLoginInputs() { + userTextField.setText(""); + passwordTextField.setText(""); + } + + private void clearMainInputs() { + destTextField.setText(""); + amountTextField.setText(""); + } + + private void addBalanceLabel() { + balanceLabel.setFont(new Font("Adwaita Sans", Font.BOLD, 64)); + balanceLabel.setHorizontalAlignment(SwingConstants.CENTER); + mainPanel.add(balanceLabel, "cell 0 0,alignx center,growx 0"); + } + + 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 + ); + setView("mainPanel"); + balanceLabel.setText(String.format("%.2f€", _loggedUser.getBalance())); + } 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 + ); + } + + clearLoginInputs(); + } 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 sendBtn(ActionEvent e) { + String destUser = destTextField.getText(); + double amount; + + try { + amount = Double.parseDouble(amountTextField.getText()); + } catch (NumberFormatException ex) { + logger.warn("Cantidad no válida introducida: {}", amountTextField.getText()); + JOptionPane.showMessageDialog(this, + "Cantidad no válida.", + "Error", + JOptionPane.ERROR_MESSAGE + ); + return; + } + + try (ClientSocket client = new ClientSocket()) { + String nonce = IntegrityProvider.generateNonce(); + TransactionDTO dto = new TransactionDTO(_loggedUser.getUserName(), destUser, amount, nonce); + SocketResponse res = client.sendTransaction(dto); + + 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 + ); + + if (res.getStatus() == SocketStatus.OK) { + logger.info("Transacción enviada: {} -> {} | {}€", _loggedUser.getUserName(), destUser, amount); + _loggedUser.setBalance(_loggedUser.getBalance() - amount); + balanceLabel.setText(String.format("%.2f€", _loggedUser.getBalance())); + } 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 transacción de {} a {}: {}", _loggedUser.getUserName(), destUser, + res.getMessage() != null ? res.getMessage().getMessage() : "Sin mensaje"); + } + + clearMainInputs(); + } catch (Exception ex) { + logger.error("Error de conexión con el servidor enviando transacción de {} a {}: {}", + _loggedUser.getUserName(), destUser, 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()); + } 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 logoutBtn(ActionEvent e) { + logout(); + setView("loginPanel"); + } + + private void thisWindowClosing(WindowEvent e) { + logout(); + } + + 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(); + welcomeLabel = 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(); + destPanel = new JPanel(); + destLabel = new JLabel(); + destTextField = new JTextField(); + amountPanel = new JPanel(); + amountLabel = new JLabel(); + amountTextField = new JTextField(); + sendBtn = new JButton(); + logoutBtn = new JButton(); + + //======== 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(12, 6)); + + //======== loginPanel ======== + { + loginPanel.setLayout(new MigLayout( + "hidemode 3,gapy 12", + // columns + "[grow,fill]", + // rows + "[]0" + + "[]para" + + "[fill]" + + "[fill]para" + + "[]" + + "[grow,fill]")); + + //---- logo ---- + logo.setIcon(new ImageIcon(getClass().getResource("/images/banco.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"); + + //---- welcomeLabel ---- + welcomeLabel.setText("Bienvenido a Banco Grupo 33. Inicie sesi\u00f3n."); + welcomeLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 16)); + loginPanel.add(welcomeLabel, "cell 0 1,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)); + userTextField.setCaretColor(new Color(0x076854)); + userTextField.setSelectionColor(new Color(0x076854)); + userPanel.add(userTextField, "cell 1 0"); + } + loginPanel.add(userPanel, "cell 0 2"); + + //======== 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)); + passwordTextField.setSelectionColor(new Color(0x076854)); + passwordTextField.setCaretColor(new Color(0x076854)); + passwordPanel.add(passwordTextField, "cell 1 0"); + } + loginPanel.add(passwordPanel, "cell 0 3"); + + //---- 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 4,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 5"); + } + contentPane.add(loginPanel, "loginPanel"); + + //======== mainPanel ======== + { + mainPanel.setLayout(new MigLayout( + "hidemode 3", + // columns + "[grow,fill]", + // rows + "[grow,fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[grow,fill]")); + + //======== destPanel ======== + { + destPanel.setLayout(new MigLayout( + "insets 0,hidemode 3", + // columns + "[fill]" + + "[grow,fill]", + // rows + "[]")); + + //---- destLabel ---- + destLabel.setText("Destinatario:"); + destLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 18)); + destLabel.setHorizontalAlignment(SwingConstants.LEFT); + destPanel.add(destLabel, "cell 0 0"); + destPanel.add(destTextField, "cell 1 0"); + } + mainPanel.add(destPanel, "cell 0 1"); + + //======== amountPanel ======== + { + amountPanel.setLayout(new MigLayout( + "insets 0,hidemode 3", + // columns + "[fill]" + + "[grow,fill]", + // rows + "[grow,fill]")); + + //---- amountLabel ---- + amountLabel.setText("Cantidad:"); + amountLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 18)); + amountLabel.setMaximumSize(new Dimension(108, 23)); + amountLabel.setMinimumSize(new Dimension(108, 23)); + amountLabel.setPreferredSize(new Dimension(108, 23)); + amountLabel.setHorizontalAlignment(SwingConstants.LEFT); + amountPanel.add(amountLabel, "cell 0 0"); + amountPanel.add(amountTextField, "cell 1 0"); + } + mainPanel.add(amountPanel, "cell 0 2"); + + //---- sendBtn ---- + sendBtn.setText("Enviar"); + sendBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 18)); + sendBtn.addActionListener(e -> sendBtn(e)); + mainPanel.add(sendBtn, "cell 0 3,alignx trailing,growx 0"); + + //---- logoutBtn ---- + logoutBtn.setText("Cerrar sesi\u00f3n"); + logoutBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 18)); + logoutBtn.addActionListener(e -> logoutBtn(e)); + mainPanel.add(logoutBtn, "cell 0 4,aligny bottom,growy 0"); + } + 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 JLabel welcomeLabel; + 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 JPanel destPanel; + private JLabel destLabel; + private JTextField destTextField; + private JPanel amountPanel; + private JLabel amountLabel; + private JTextField amountTextField; + private JButton sendBtn; + private JButton logoutBtn; + // JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/client/ui/MainWindow.jfd b/Integridos/src/main/java/net/miarma/integridos/client/ui/MainWindow.jfd new file mode 100644 index 0000000..a60b30c --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/client/ui/MainWindow.jfd @@ -0,0 +1,209 @@ +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": 12 + "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": "[]0[]para[fill][fill]para[][grow,fill]" + } ) { + name: "loginPanel" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "logo" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/images/banco.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 FormComponent( "javax.swing.JLabel" ) { + name: "welcomeLabel" + "text": "Bienvenido a Banco Grupo 33. Inicie sesión." + "font": new java.awt.Font( "Adwaita Sans", 0, 16 ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1,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 ) + "caretColor": &Color0 new java.awt.Color( 7, 104, 84, 255 ) + "selectionColor": new java.awt.Color( 7, 104, 84, 255 ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + 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 + "selectionColor": new java.awt.Color( 7, 104, 84, 255 ) + "caretColor": #Color0 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) + 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 4,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 5" + } ) + }, 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][fill][grow,fill]" + } ) { + name: "mainPanel" + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 0,hidemode 3" + "$columnConstraints": "[fill][grow,fill]" + "$rowConstraints": "[]" + } ) { + name: "destPanel" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "destLabel" + "text": "Destinatario:" + "font": &Font3 new java.awt.Font( "Adwaita Sans", 0, 18 ) + "horizontalAlignment": 2 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "destTextField" + }, 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: "amountPanel" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "amountLabel" + "text": "Cantidad:" + "font": #Font3 + "maximumSize": new java.awt.Dimension( 108, 23 ) + "minimumSize": new java.awt.Dimension( 108, 23 ) + "preferredSize": new java.awt.Dimension( 108, 23 ) + "horizontalAlignment": 2 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "amountTextField" + }, 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: "sendBtn" + "text": "Enviar" + "font": &Font4 new java.awt.Font( "Adwaita Sans", 0, 18 ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "sendBtn", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3,alignx trailing,growx 0" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "logoutBtn" + "text": "Cerrar sesión" + "font": #Font4 + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "logoutBtn", true ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4,aligny bottom,growy 0" + } ) + }, 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 ) + } ) + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/ConfigManager.java b/Integridos/src/main/java/net/miarma/integridos/common/ConfigManager.java new file mode 100644 index 0000000..67bf78d --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/ConfigManager.java @@ -0,0 +1,105 @@ +package net.miarma.integridos.common; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Properties; + +/** + * Gestión de toda la configuración de la aplicación. + * Se encarga de cargar, guardar y proporcionar acceso a las propiedades de configuración. + * Proporciona métodos para obtener la URL de la base de datos, directorios de archivos, + * y propiedades específicas como host, puerto, etc. + *

+ * Esta clase sigue el patron Singleton para asegurar una sola instancia. + * @author José Manuel Amador Gallardo + */ +public class ConfigManager { + private static ConfigManager INSTANCE; + private final File configFile; + private final Properties config; + private static final String CONFIG_FILE_NAME = "config.properties"; + private final Logger logger = LoggerFactory.getLogger(ConfigManager.class); + + private ConfigManager() { + String path = getBaseDir() + CONFIG_FILE_NAME; + this.configFile = new File(path); + 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 getConfigFile() { + return configFile; + } + + public String getHomeDir() { + return getOS() == OSType.WINDOWS ? + "C:/Users/" + System.getProperty("user.name") + "/" : + System.getProperty("user.home").contains("root") ? "/root/" : + "/home/" + System.getProperty("user.name") + "/"; + } + + public String getBaseDir() { + return getHomeDir() + + (getOS() == OSType.WINDOWS ? ".integridos/" : + getOS() == OSType.LINUX ? ".config/integridos/" : + ".integridos/"); + } + + 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 { + 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); + } + } +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/common/Constants.java b/Integridos/src/main/java/net/miarma/integridos/common/Constants.java new file mode 100644 index 0000000..6997f6d --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/Constants.java @@ -0,0 +1,10 @@ +package net.miarma.integridos.common; + +public class Constants { + public static final String APP_NAME = "Integridos"; + public static final String APP_VERSION = "1.0.0"; + + private Constants() { + throw new AssertionError("Utility class cannot be instantiated."); + } +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/common/OSType.java b/Integridos/src/main/java/net/miarma/integridos/common/OSType.java new file mode 100644 index 0000000..4e6b65f --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/OSType.java @@ -0,0 +1,9 @@ +package net.miarma.integridos.common; + +/** + * Enum que representa los diferentes tipos de sistemas operativos soportados + * @author José Manuel Amador Gallardo + */ +public enum OSType { + LINUX, WINDOWS, INVALID_OS +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/DBConnector.java b/Integridos/src/main/java/net/miarma/integridos/common/db/DBConnector.java new file mode 100644 index 0000000..bce1ff6 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/DBConnector.java @@ -0,0 +1,31 @@ +package net.miarma.integridos.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 DBConnector() { + this.emf = Persistence.createEntityManagerFactory("ssii-pai1"); + } + + public static DBConnector getInstance() { + if (instance == null) { + instance = new DBConnector(); + } + return instance; + } + + public EntityManager createEntityManager() { + return emf.createEntityManager(); + } + + public void close() { + if (emf != null && emf.isOpen()) { + emf.close(); + } + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/DBPopulator.java b/Integridos/src/main/java/net/miarma/integridos/common/db/DBPopulator.java new file mode 100644 index 0000000..271aeb3 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/DBPopulator.java @@ -0,0 +1,51 @@ +package net.miarma.integridos.common.db; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import net.miarma.integridos.common.security.PasswordHasher; +import net.miarma.integridos.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.createEntityManager(); + } + + public static DBPopulator getInstance() { + if (instance == null) { + instance = new DBPopulator(); + } + return instance; + } + + public boolean isFirstRun() { + TypedQuery 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(); + } + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/dao/SessionDAO.java b/Integridos/src/main/java/net/miarma/integridos/common/db/dao/SessionDAO.java new file mode 100644 index 0000000..0781d82 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/dao/SessionDAO.java @@ -0,0 +1,20 @@ +package net.miarma.integridos.common.db.dao; + +import jakarta.persistence.EntityManager; +import net.miarma.integridos.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; + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/dao/TransactionDAO.java b/Integridos/src/main/java/net/miarma/integridos/common/db/dao/TransactionDAO.java new file mode 100644 index 0000000..27d1761 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/dao/TransactionDAO.java @@ -0,0 +1,13 @@ +package net.miarma.integridos.common.db.dao; + +import jakarta.persistence.EntityManager; + +public class TransactionDAO { + public boolean isNonceUsed(EntityManager em, String nonce) { + Long count = em.createQuery( + "SELECT COUNT(t) FROM TransactionEntity t WHERE t.nonce = :nonce", Long.class) + .setParameter("nonce", nonce) + .getSingleResult(); + return count > 0; + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/dao/UserDAO.java b/Integridos/src/main/java/net/miarma/integridos/common/db/dao/UserDAO.java new file mode 100644 index 0000000..285c4ac --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/dao/UserDAO.java @@ -0,0 +1,16 @@ +package net.miarma.integridos.common.db.dao; + +import jakarta.persistence.EntityManager; +import net.miarma.integridos.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 + } + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/dto/LoginDTO.java b/Integridos/src/main/java/net/miarma/integridos/common/db/dto/LoginDTO.java new file mode 100644 index 0000000..0f88316 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/dto/LoginDTO.java @@ -0,0 +1,30 @@ +package net.miarma.integridos.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; + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/dto/TransactionDTO.java b/Integridos/src/main/java/net/miarma/integridos/common/db/dto/TransactionDTO.java new file mode 100644 index 0000000..8b50f4c --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/dto/TransactionDTO.java @@ -0,0 +1,58 @@ +package net.miarma.integridos.common.db.dto; + +public class TransactionDTO { + private String fromUserName; + private String toUserName; + private double amount; + private String hmac; + private String nonce; + + public TransactionDTO() {} + + public TransactionDTO(String fromUserName, String toUserName, double amount, String nonce) { + this.fromUserName = fromUserName; + this.toUserName = toUserName; + this.amount = amount; + this.nonce = nonce; + } + + public String getFromUserName() { + return fromUserName; + } + + public void setFromUserName(String fromUserName) { + this.fromUserName = fromUserName; + } + + public String getToUserName() { + return toUserName; + } + + public void setToUserName(String toUserName) { + this.toUserName = toUserName; + } + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public String getHmac() { + return hmac; + } + + public void setHmac(String hmac) { + this.hmac = hmac; + } + + public String getNonce() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/entities/SessionEntity.java b/Integridos/src/main/java/net/miarma/integridos/common/db/entities/SessionEntity.java new file mode 100644 index 0000000..c63e414 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/entities/SessionEntity.java @@ -0,0 +1,66 @@ +package net.miarma.integridos.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; + } +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/entities/TransactionEntity.java b/Integridos/src/main/java/net/miarma/integridos/common/db/entities/TransactionEntity.java new file mode 100644 index 0000000..e815993 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/entities/TransactionEntity.java @@ -0,0 +1,85 @@ +package net.miarma.integridos.common.db.entities; + +import jakarta.persistence.*; + +@Entity +@Table(name = "transactions") +public class TransactionEntity { + @Id + @Column(name = "transactionId", nullable = false, updatable = false, length = 36) + private String transactionId; + + @ManyToOne + @JoinColumn(name = "fromUser", referencedColumnName = "userId", nullable = false, updatable = false) + private UserEntity fromUser; + + @ManyToOne + @JoinColumn(name = "toUser", referencedColumnName = "userId", nullable = false, updatable = false) + private UserEntity toUser; + + @Column(name = "transactionAmount", nullable = false, updatable = false) + private Double transactionAmount; + + @Column(name = "nonce", nullable = false, updatable = false) + private String nonce; + + public TransactionEntity() {} + + public TransactionEntity(String transactionId, UserEntity fromUser, UserEntity toUser, Double transactionAmount) { + this.transactionId = transactionId; + this.fromUser = fromUser; + this.toUser = toUser; + this.transactionAmount = transactionAmount; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public UserEntity getFromUser() { + return fromUser; + } + + public void setFromUser(UserEntity fromUser) { + this.fromUser = fromUser; + } + + public UserEntity getToUser() { + return toUser; + } + + public void setToUser(UserEntity toUser) { + this.toUser = toUser; + } + + public Double getTransactionAmount() { + return transactionAmount; + } + + public void setTransactionAmount(Double transactionAmount) { + this.transactionAmount = transactionAmount; + } + + public String getNonce() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } + + @Override + public String toString() { + return "TransactionEntity{" + + "transactionId='" + transactionId + '\'' + + ", fromUser=" + fromUser + + ", toUser=" + toUser + + ", transactionAmount=" + transactionAmount + + ", nonce=" + nonce + + '}'; + } +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/common/db/entities/UserEntity.java b/Integridos/src/main/java/net/miarma/integridos/common/db/entities/UserEntity.java new file mode 100644 index 0000000..b0cfdbd --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/db/entities/UserEntity.java @@ -0,0 +1,63 @@ +package net.miarma.integridos.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; + + @Column(name = "balance", nullable = false) + private Double balance = 0.00; + + 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; + } + + public Double getBalance() { + return balance; + } + + public void setBalance(Double balance) { + this.balance = balance; + } +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/common/security/IntegrityProvider.java b/Integridos/src/main/java/net/miarma/integridos/common/security/IntegrityProvider.java new file mode 100644 index 0000000..7dbe943 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/security/IntegrityProvider.java @@ -0,0 +1,30 @@ +package net.miarma.integridos.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(); + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/security/PasswordHasher.java b/Integridos/src/main/java/net/miarma/integridos/common/security/PasswordHasher.java new file mode 100644 index 0000000..4b0d4e3 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/security/PasswordHasher.java @@ -0,0 +1,22 @@ +package net.miarma.integridos.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); + } +} \ No newline at end of file diff --git a/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketRequest.java b/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketRequest.java new file mode 100644 index 0000000..8656ac2 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketRequest.java @@ -0,0 +1,41 @@ +package net.miarma.integridos.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_TRANSACTION, LOGOUT + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketResponse.java b/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketResponse.java new file mode 100644 index 0000000..f34da53 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketResponse.java @@ -0,0 +1,80 @@ +package net.miarma.integridos.common.socket; + +public class SocketResponse { + + private SocketStatus status; + private Message message; + private Object data; + + public SocketResponse() {} + + public SocketResponse(SocketStatus status) { + this(status, (Message) null, null); + } + + public SocketResponse(SocketStatus status, Object data) { + this(status, null, data); + } + + public SocketResponse(SocketStatus status, Message message) { + this(status, message, message != null ? message.getMessage() : null); + } + + public SocketResponse(SocketStatus status, Exception e) { + this(status, Message.UNKNOWN_ERROR, e.getMessage()); + } + + 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"), + TRANSACTION_SENT("Su transacción se ha realizado"), + 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"); + + private final String message; + + Message(String userMessage) { + this.message = userMessage; + } + + public String getMessage() { + return message; + } + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketStatus.java b/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketStatus.java new file mode 100644 index 0000000..62d2765 --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/common/socket/SocketStatus.java @@ -0,0 +1,14 @@ +package net.miarma.integridos.common.socket; + +public enum SocketStatus { + OK, + ERROR, + SESSION_EXPIRED, + INTEGRITY_FAIL, + UNAUTHORIZED, + INVALID_REQUEST; + + public static boolean isOk(String status) { + return OK.name().equalsIgnoreCase(status); + } +} diff --git a/Integridos/src/main/java/net/miarma/integridos/server/RemoteSocket.java b/Integridos/src/main/java/net/miarma/integridos/server/RemoteSocket.java new file mode 100644 index 0000000..5cd61fa --- /dev/null +++ b/Integridos/src/main/java/net/miarma/integridos/server/RemoteSocket.java @@ -0,0 +1,207 @@ +package net.miarma.integridos.server; + +import com.google.gson.Gson; +import jakarta.persistence.EntityManager; +import net.miarma.integridos.common.*; +import net.miarma.integridos.common.db.entities.TransactionEntity; +import net.miarma.integridos.common.security.IntegrityProvider; +import net.miarma.integridos.common.security.PasswordHasher; +import net.miarma.integridos.common.socket.SocketRequest; +import net.miarma.integridos.common.socket.SocketResponse; +import net.miarma.integridos.common.socket.SocketStatus; +import net.miarma.integridos.common.db.DBConnector; +import net.miarma.integridos.common.db.dao.SessionDAO; +import net.miarma.integridos.common.db.dao.TransactionDAO; +import net.miarma.integridos.common.db.dao.UserDAO; +import net.miarma.integridos.common.db.dto.LoginDTO; +import net.miarma.integridos.common.db.dto.TransactionDTO; +import net.miarma.integridos.common.db.entities.SessionEntity; +import net.miarma.integridos.common.db.entities.UserEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.UUID; + +public class RemoteSocket { + static void main(String[] args) throws IOException { + ServerSocket serverSocket = new ServerSocket(6969); + DBConnector conn = DBConnector.getInstance(); + UserDAO userDAO = new UserDAO(); + SessionDAO sessionDAO = new SessionDAO(); + TransactionDAO transactionDAO = new TransactionDAO(); + Gson gson = new Gson(); + final Logger logger = LoggerFactory.getLogger(RemoteSocket.class); + ConfigManager.getInstance().loadConfig(); + + while (true) { + Socket socket = serverSocket.accept(); + logger.info("Nueva conexión desde {}:{}", socket.getInetAddress(), socket.getPort()); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + PrintWriter output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); + + try { + 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.createEntityManager(); + 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 { + em.close(); + } + } + + case LOGIN -> { + LoginDTO dto = gson.fromJson(gson.toJson(msg.getPayload()), LoginDTO.class); + logger.info("Procesando LOGIN para {}", dto.getUsername()); + EntityManager em = conn.createEntityManager(); + try { + 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())) { + 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()); + } finally { + em.close(); + } + } + + case LOGOUT -> { + String userId = gson.fromJson(gson.toJson(msg.getPayload()), String.class); + logger.info("Procesando LOGOUT para {}", userId); + EntityManager em = conn.createEntityManager(); + try { + 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); + } + } finally { + em.close(); + } + } + + case SEND_TRANSACTION -> { + TransactionDTO dto = gson.fromJson(gson.toJson(msg.getPayload()), TransactionDTO.class); + + logger.info("Procesando SEND_TRANSACTION de {} a {} | {}", dto.getFromUserName(), dto.getToUserName(), dto.getAmount()); + + String receivedHmac = dto.getHmac(); + + logger.debug("HMAC recibida: {}", receivedHmac); + + dto.setHmac(null); + msg.setPayload(dto); + String jsonMsg = gson.toJson(msg); + + logger.debug("Mensaje JSON para calcular HMAC: {}", jsonMsg); + + String generatedHmac = IntegrityProvider.generateHMAC( + ConfigManager.getInstance().getStringProperty("secret"), jsonMsg + ); + + logger.debug("HMAC generada: {}", generatedHmac); + + if (!generatedHmac.equals(receivedHmac)) { + res = new SocketResponse(SocketStatus.INTEGRITY_FAIL, SocketResponse.Message.INTEGRITY_FAIL); + break; + } + + logger.debug("Las HMAC coinciden"); + + EntityManager em = conn.createEntityManager(); + try { + if (transactionDAO.isNonceUsed(em, dto.getNonce())) { + res = new SocketResponse(SocketStatus.INTEGRITY_FAIL, SocketResponse.Message.REPLAY_ATTACK); + break; + } + + em.getTransaction().begin(); + UserEntity fromUser = userDAO.getByUserName(em, dto.getFromUserName()); + UserEntity toUser = userDAO.getByUserName(em, dto.getToUserName()); + + if (fromUser == null || !sessionDAO.isLoggedIn(em, fromUser.getUserId())) { + res = new SocketResponse(SocketStatus.SESSION_EXPIRED, SocketResponse.Message.SESSION_EXPIRED); + } else if (toUser == null) { + res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.RECIPIENT_NOT_FOUND); + } else if (dto.getAmount() <= 0 || dto.getAmount() > fromUser.getBalance()) { + res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.INVALID_AMOUNT); + } else { + TransactionEntity t = new TransactionEntity(UUID.randomUUID().toString(), fromUser, toUser, dto.getAmount()); + t.setNonce(dto.getNonce()); + + fromUser.setBalance(fromUser.getBalance() - dto.getAmount()); + toUser.setBalance(toUser.getBalance() + dto.getAmount()); + + em.persist(t); + em.merge(fromUser); + em.merge(toUser); + + res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.TRANSACTION_SENT, t); + } + + em.getTransaction().commit(); + } 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 { + em.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 (Exception e) { + logger.error("Fallo procesando solicitud", e); + } finally { + input.close(); + output.close(); + socket.close(); + } + } + } +} diff --git a/Integridos/src/main/resources/META-INF/persistence.xml b/Integridos/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..2eed1d7 --- /dev/null +++ b/Integridos/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,30 @@ + + + + + Persistence unit for PAI-1 + + + net.miarma.integridos.common.db.entities.UserEntity + net.miarma.integridos.common.db.entities.TransactionEntity + net.miarma.integridos.common.db.entities.SessionEntity + + + + + + + + + + + + + + + + + diff --git a/Integridos/src/main/resources/default.properties b/Integridos/src/main/resources/default.properties new file mode 100644 index 0000000..0e6879b --- /dev/null +++ b/Integridos/src/main/resources/default.properties @@ -0,0 +1 @@ +secret= \ No newline at end of file diff --git a/Integridos/src/main/resources/images/banco.png b/Integridos/src/main/resources/images/banco.png new file mode 100644 index 0000000000000000000000000000000000000000..ebf2e60714f09d5829e9c712a1e1d5eb67b07140 GIT binary patch literal 23172 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 H9pe87B5Ahh literal 0 HcmV?d00001 diff --git a/Integridos/src/main/resources/logback.xml b/Integridos/src/main/resources/logback.xml new file mode 100644 index 0000000..afab6b9 --- /dev/null +++ b/Integridos/src/main/resources/logback.xml @@ -0,0 +1,53 @@ + + + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + + + log/client.log + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + log/client-%d{yyyy-MM-dd}.log + 30 + true + + + + + log/server.log + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + log/server-%d{yyyy-MM-dd}.log + 30 + true + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Integridos/src/main/resources/scripts/db.sql b/Integridos/src/main/resources/scripts/db.sql new file mode 100644 index 0000000..243d967 --- /dev/null +++ b/Integridos/src/main/resources/scripts/db.sql @@ -0,0 +1,77 @@ +USE pai1; + +-- Cleanup +DROP TABLE IF EXISTS sessions; +DROP TABLE IF EXISTS transactions; +DROP TABLE IF EXISTS users; +-- END Cleanup + +-- DDL +CREATE TABLE users ( + userId UUID NOT NULL, + userName VARCHAR(64) NOT NULL UNIQUE, + password VARCHAR(256) NOT NULL, + balance DECIMAL(12,2) NOT NULL DEFAULT 0, + PRIMARY KEY (userId) +); + +CREATE TABLE transactions ( + transactionId UUID NOT NULL, + fromUser UUID NOT NULL, + toUser UUID NOT NULL, + transactionAmount DECIMAL(12,2) NOT NULL, + nonce TEXT NOT NULL, + PRIMARY KEY (transactionId), + FOREIGN KEY (fromUser) REFERENCES users (userId) ON DELETE CASCADE, + FOREIGN KEY (toUser) REFERENCES users (userId) ON DELETE CASCADE +); + +CREATE TABLE sessions ( + sessionId UUID NOT NULL, + userId UUID NOT NULL, + createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + expiresAt TIMESTAMP AS (createdAt + INTERVAL 15 MINUTE) STORED, + PRIMARY KEY (sessionId), + 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(); + +CREATE OR REPLACE TRIGGER tr_subtract_amount +AFTER INSERT ON transactions +FOR EACH ROW +BEGIN +UPDATE users +SET balance = balance - NEW.transactionAmount +WHERE userId = NEW.fromUser; +END; +-- END DDL + +-- DML +INSERT INTO users (userId, userName, password, balance) +VALUES + (UUID(), 'pepe', 'pass123', 100.50), + (UUID(), 'lola', 'secreto', 250.00); + +INSERT INTO transactions (transactionId, fromUser, toUser, transactionAmount) +VALUES ( + UUID(), + (SELECT userId FROM users WHERE userName = 'pepe'), + (SELECT userId FROM users WHERE userName = 'lola'), + 25.75 + ); + +INSERT INTO sessions (sessionId, userId) +VALUES ( + UUID(), + (SELECT userId FROM users WHERE userName = 'pepe') + ); +-- END DML + +-- DQL +SELECT * FROM sessions; + +-- END DQL \ No newline at end of file diff --git a/RedTeamPro/.gitkeep b/RedTeamPro/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Vulnaweb/.gitkeep b/Vulnaweb/.gitkeep new file mode 100644 index 0000000..e69de29