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

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

2
BYODSEC/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/bin/
/target/

54
BYODSEC/log/client.log Normal file
View File

@@ -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.

575
BYODSEC/log/server.log Normal file
View File

@@ -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

155
BYODSEC/pom.xml Normal file
View File

@@ -0,0 +1,155 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.miarma</groupId>
<artifactId>integridos</artifactId>
<version>1.0.0</version>
<name>net.miarma.integridos.Integridos</name>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
<dependencies>
<!-- ##### Gson ##### -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.12.1</version>
</dependency>
<!-- ################ -->
<!-- ##### SLF4J + Logback #####-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.13</version>
</dependency>
<!-- ########################## -->
<!-- ##### BASE DE DATOS Y DATA SOURCE ##### -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>7.1.1.Final</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
<!-- ##################################### -->
<!-- ##### JWT Tokens ##### -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.5.0</version>
</dependency>
<!-- ####################### -->
<!-- ##### BCrypt ##### -->
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
<!-- ################### -->
<!-- ###################### SWING ############################# -->
<dependency>
<groupId>com.miglayout</groupId>
<artifactId>miglayout-swing</artifactId>
<version>11.4.2</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf-intellij-themes</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf-extras</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-swing</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-font_awesome</artifactId>
<version>4.7.0.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-google_material_design_icons</artifactId>
<version>2.2.0.2</version>
</dependency>
<!-- ################################################################# -->
</dependencies>
<build>
<plugins>
<!-- Maven Shade Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>net.miarma.integridos.net.miarma.integridos.Integridos</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

View File

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

0
DevSecOps/.gitkeep Normal file
View File

2
Integridos/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.env
target/

15
Integridos/log/client.log Normal file
View File

@@ -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.

46
Integridos/log/server.log Normal file
View File

@@ -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

155
Integridos/pom.xml Normal file
View File

@@ -0,0 +1,155 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.miarma</groupId>
<artifactId>integridos</artifactId>
<version>1.0.0</version>
<name>net.miarma.integridos.Integridos</name>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
<dependencies>
<!-- ##### Gson ##### -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.12.1</version>
</dependency>
<!-- ################ -->
<!-- ##### SLF4J + Logback #####-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.13</version>
</dependency>
<!-- ########################## -->
<!-- ##### BASE DE DATOS Y DATA SOURCE ##### -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>7.1.1.Final</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
<!-- ##################################### -->
<!-- ##### JWT Tokens ##### -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.5.0</version>
</dependency>
<!-- ####################### -->
<!-- ##### BCrypt ##### -->
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
<!-- ################### -->
<!-- ###################### SWING ############################# -->
<dependency>
<groupId>com.miglayout</groupId>
<artifactId>miglayout-swing</artifactId>
<version>11.4.2</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf-intellij-themes</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf-extras</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-swing</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-font_awesome</artifactId>
<version>4.7.0.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-google_material_design_icons</artifactId>
<version>2.2.0.2</version>
</dependency>
<!-- ################################################################# -->
</dependencies>
<build>
<plugins>
<!-- Maven Shade Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>net.miarma.integridos.net.miarma.integridos.Integridos</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,8 @@
/**
*
*/
/**
*
*/
module BYODSEC {
}

View File

@@ -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());
}
}
}

View File

@@ -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) {}
}
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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 )
} )
}
}

View File

@@ -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.
* <p>
* 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);
}
}
}

View File

@@ -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.");
}
}

View File

@@ -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
}

View File

@@ -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();
}
}
}

View File

@@ -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<Long> q = em.createQuery("SELECT COUNT(u) FROM UserEntity u", Long.class);
Long count = q.getSingleResult();
return count == 0;
}
public void populate() {
UserEntity u1 = new UserEntity(UUID.randomUUID().toString(),
"ricolinad", PasswordHasher.hash("ricardo2025"));
UserEntity u2 = new UserEntity(UUID.randomUUID().toString(),
"alvgulveg", PasswordHasher.hash("alcalde_dimision"));
UserEntity u3 = new UserEntity(UUID.randomUUID().toString(),
"smt4497", PasswordHasher.hash("quemenlaus"));
try {
em.getTransaction().begin();
em.persist(u1);
em.persist(u2);
em.persist(u3);
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
} finally {
em.close();
}
}
}

View File

@@ -0,0 +1,20 @@
package net.miarma.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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}
}

View File

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

View File

@@ -0,0 +1 @@
secret=

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

View File

@@ -0,0 +1,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

0
RedTeamPro/.gitkeep Normal file
View File

0
Vulnaweb/.gitkeep Normal file
View File