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 0000000..ebf2e60 Binary files /dev/null and b/BYODSEC/src/main/resources/images/banco.png differ diff --git a/BYODSEC/src/main/resources/images/logo.png b/BYODSEC/src/main/resources/images/logo.png new file mode 100644 index 0000000..7e5637f Binary files /dev/null and b/BYODSEC/src/main/resources/images/logo.png differ diff --git a/BYODSEC/src/main/resources/logback.xml b/BYODSEC/src/main/resources/logback.xml new file mode 100644 index 0000000..a96ee4e --- /dev/null +++ b/BYODSEC/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/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 0000000..ebf2e60 Binary files /dev/null and b/Integridos/src/main/resources/images/banco.png differ 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