Joaquín Reñé
2025-10-07 146a0fb8b0e90f9196e569152f649baf60d6cc8f
securis/src/main/java/net/curisit/securis/services/UserResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.util.Date;
....@@ -47,291 +50,437 @@
4750 import net.curisit.securis.utils.TokenHelper;
4851
4952 /**
50
- * User resource
51
- *
53
+ * UserResource
54
+ * <p>
55
+ * REST resource that manages users (CRUD + authentication helpers).
56
+ * All endpoints are guarded and ADMIN-only unless otherwise stated.
57
+ * <p>
58
+ * Notes:
59
+ * - Uses {@link BasicSecurityContext} authorization via @Securable and @RolesAllowed.
60
+ * - Uses JPA {@link EntityManager} injected through @Context.
61
+ * - Mutating endpoints are wrapped in @EnsureTransaction to guarantee commit/rollback.
62
+ * - Passwords are stored as SHA-256 hashes (see {@link Utils#sha256(String)}).
63
+ *
64
+ * Endpoints:
65
+ * GET /user/ -> list users
66
+ * GET /user/{uid} -> get user by username
67
+ * POST /user/ -> create user (idempotent: upsert semantics)
68
+ * PUT /user/{uid} -> update user (creates if not exists)
69
+ * POST /user/login -> password authentication; returns token and basic identity
70
+ * POST /user/check -> validates a token and returns token metadata
71
+ * GET /user/logout -> invalidates HTTP session (non-token based)
72
+ *
73
+ * Thread-safety: RequestScoped. No shared mutable state.
74
+ *
5275 * @author roberto <roberto.sanchez@curisit.net>
76
+ * Last reviewed by JRA on Oct 5, 2025.
5377 */
5478 @Path("/user")
5579 @RequestScoped
5680 public class UserResource {
5781
58
- @Inject
59
- TokenHelper tokenHelper;
82
+ /** Token encoder/decoder & validator. */
83
+ @Inject TokenHelper tokenHelper;
6084
61
- @Inject
62
- private CacheTTL cache;
85
+ /** Small cache to invalidate role/org derived data after user mutations. */
86
+ @Inject private CacheTTL cache;
6387
64
- @Context
65
- EntityManager em;
88
+ /** JPA entity manager bound to the current request context. */
89
+ @Context EntityManager em;
6690
67
- private static final Logger LOG = LogManager.getLogger(UserResource.class);
91
+ private static final Logger LOG = LogManager.getLogger(UserResource.class);
6892
69
- public UserResource() {
70
- }
93
+ /**
94
+ * UserResource
95
+ * Default constructor for CDI.
96
+ */
97
+ public UserResource() {
98
+ }
7199
72
- /**
73
- *
74
- * @return the server version in format majorVersion.minorVersion
75
- */
76
- @GET
77
- @Path("/")
78
- @Produces({ MediaType.APPLICATION_JSON })
79
- @Securable(roles = Rol.ADMIN)
80
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
81
- public Response index() {
82
- LOG.info("Getting users list ");
100
+ // ---------------------------------------------------------------------
101
+ // Read operations
102
+ // ---------------------------------------------------------------------
83103
84
- // EntityManager em = emProvider.get();
85
- em.clear();
86
- TypedQuery<User> q = em.createNamedQuery("list-users", User.class);
104
+ /**
105
+ * index
106
+ * <p>
107
+ * List all users.
108
+ *
109
+ * Security: ADMIN only.
110
+ *
111
+ * @return 200 OK with JSON array of {@link User}, or 200 OK with empty list.
112
+ */
113
+ @GET
114
+ @Path("/")
115
+ @Produces({ MediaType.APPLICATION_JSON })
116
+ @Securable(roles = Rol.ADMIN)
117
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
118
+ public Response index() {
119
+ LOG.info("Getting users list ");
87120
88
- List<User> list = q.getResultList();
121
+ em.clear();
122
+ TypedQuery<User> q = em.createNamedQuery("list-users", User.class);
123
+ List<User> list = q.getResultList();
89124
90
- return Response.ok(list).build();
91
- }
125
+ return Response.ok(list).build();
126
+ }
92127
93
- /**
94
- *
95
- * @return The user
96
- */
97
- @GET
98
- @Path("/{uid}")
99
- @Produces({ MediaType.APPLICATION_JSON })
100
- @Securable(roles = Rol.ADMIN)
101
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
102
- public Response get(@PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
103
- LOG.info("Getting user data for id: {}: ", uid);
104
- if (uid == null || "".equals(uid)) {
105
- LOG.error("User ID is mandatory");
106
- return Response.status(Status.NOT_FOUND).build();
107
- }
128
+ /**
129
+ * get
130
+ * <p>
131
+ * Retrieve a single user by username.
132
+ *
133
+ * Security: ADMIN only.
134
+ *
135
+ * @param uid Username (primary key).
136
+ * @param token Optional token header (unused here, enforced by filters).
137
+ * @return 200 OK with user payload or 404 if not found/invalid uid.
138
+ */
139
+ @GET
140
+ @Path("/{uid}")
141
+ @Produces({ MediaType.APPLICATION_JSON })
142
+ @Securable(roles = Rol.ADMIN)
143
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
144
+ public Response get(@PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
145
+ LOG.info("Getting user data for id: {}: ", uid);
146
+ if (uid == null || "".equals(uid)) {
147
+ LOG.error("User ID is mandatory");
148
+ return Response.status(Status.NOT_FOUND).build();
149
+ }
108150
109
- // EntityManager em = emProvider.get();
110
- em.clear();
111
- User lt = em.find(User.class, uid);
112
- if (lt == null) {
113
- LOG.error("User with id {} not found in DB", uid);
114
- return Response.status(Status.NOT_FOUND).build();
115
- }
116
- return Response.ok(lt).build();
117
- }
151
+ em.clear();
152
+ User lt = em.find(User.class, uid);
153
+ if (lt == null) {
154
+ LOG.error("User with id {} not found in DB", uid);
155
+ return Response.status(Status.NOT_FOUND).build();
156
+ }
157
+ return Response.ok(lt).build();
158
+ }
118159
119
- @POST
120
- @Path("/")
121
- @Consumes(MediaType.APPLICATION_JSON)
122
- @Produces({ MediaType.APPLICATION_JSON })
123
- @EnsureTransaction
124
- @Securable(roles = Rol.ADMIN)
125
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
126
- public Response create(User user, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
127
- LOG.info("Creating new user");
128
- // EntityManager em = emProvider.get();
129
- User currentUser = em.find(User.class, user.getUsername());
130
- if (currentUser != null) {
131
- LOG.info("User with id {} was found in DB, we'll try to modify it", user.getUsername());
132
- return modify(user, user.getUsername(), token);
133
- }
160
+ // ---------------------------------------------------------------------
161
+ // Create / Update / Delete
162
+ // ---------------------------------------------------------------------
134163
135
- try {
136
- this.setUserOrgs(user, user.getOrgsIds(), em);
137
- } catch (SeCurisException e) {
138
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
139
- }
140
- try {
141
- this.setUserApps(user, user.getAppsIds(), em);
142
- } catch (SeCurisException e) {
143
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
144
- }
145
- if (user.getPassword() != null && !"".equals(user.getPassword())) {
146
- user.setPassword(Utils.sha256(user.getPassword()));
147
- } else {
148
- return Response.status(DefaultExceptionHandler.DEFAULT_APP_ERROR_STATUS_CODE).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "User password is mandatory")
149
- .build();
150
- }
151
- user.setModificationTimestamp(new Date());
152
- user.setLastLogin(null);
153
- user.setCreationTimestamp(new Date());
154
- em.persist(user);
164
+ /**
165
+ * create
166
+ * <p>
167
+ * Create a new user. If the username already exists, delegates to {@link #modify(User, String, String)}
168
+ * to behave like an upsert.
169
+ *
170
+ * Security: ADMIN only.
171
+ * Transaction: yes (via @EnsureTransaction).
172
+ *
173
+ * @param user Incoming user payload. Password must be non-empty (plain text).
174
+ * Password is SHA-256 hashed before persist.
175
+ * @param token Security token header (unused here; enforced by filters).
176
+ * @return 200 OK with created/updated user; 4xx on validation errors.
177
+ */
178
+ @POST
179
+ @Path("/")
180
+ @Consumes(MediaType.APPLICATION_JSON)
181
+ @Produces({ MediaType.APPLICATION_JSON })
182
+ @EnsureTransaction
183
+ @Securable(roles = Rol.ADMIN)
184
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
185
+ public Response create(User user, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
186
+ LOG.info("Creating new user");
155187
156
- return Response.ok(user).build();
157
- }
188
+ User currentUser = em.find(User.class, user.getUsername());
189
+ if (currentUser != null) {
190
+ LOG.info("User with id {} was found in DB, we'll try to modify it", user.getUsername());
191
+ return modify(user, user.getUsername(), token);
192
+ }
158193
159
- private void setUserOrgs(User user, Set<Integer> orgsIds, EntityManager em) throws SeCurisException {
160
- Set<Organization> orgs = null;
161
- if (orgsIds != null && !orgsIds.isEmpty()) {
162
- orgs = new HashSet<>();
163
- for (Integer orgId : orgsIds) {
164
- Organization o = em.find(Organization.class, orgId);
165
- if (o == null) {
166
- LOG.error("User organization with id {} not found in DB", orgId);
167
- throw new SeCurisException("User's organization not found with ID: " + orgId);
168
- }
169
- orgs.add(o);
170
- }
171
- }
194
+ try {
195
+ this.setUserOrgs(user, user.getOrgsIds(), em);
196
+ } catch (SeCurisException e) {
197
+ return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
198
+ }
199
+ try {
200
+ this.setUserApps(user, user.getAppsIds(), em);
201
+ } catch (SeCurisException e) {
202
+ return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
203
+ }
172204
173
- user.setOrganizations(orgs);
205
+ // Password must be provided on create
206
+ if (user.getPassword() != null && !"".equals(user.getPassword())) {
207
+ user.setPassword(Utils.sha256(user.getPassword()));
208
+ } else {
209
+ return Response.status(DefaultExceptionHandler.DEFAULT_APP_ERROR_STATUS_CODE)
210
+ .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "User password is mandatory")
211
+ .build();
212
+ }
174213
175
- }
214
+ user.setModificationTimestamp(new Date());
215
+ user.setLastLogin(null);
216
+ user.setCreationTimestamp(new Date());
217
+ em.persist(user);
176218
177
- private void setUserApps(User user, Set<Integer> appsIds, EntityManager em) throws SeCurisException {
178
- Set<Application> apps = null;
179
- if (appsIds != null && !appsIds.isEmpty()) {
180
- apps = new HashSet<>();
181
- for (Integer appId : appsIds) {
182
- Application o = em.find(Application.class, appId);
183
- if (o == null) {
184
- LOG.error("User application with id {} not found in DB", appId);
185
- throw new SeCurisException("User's application not found with ID: " + appId);
186
- }
187
- apps.add(o);
188
- }
189
- }
219
+ return Response.ok(user).build();
220
+ }
190221
191
- user.setApplications(apps);
192
- }
222
+ /**
223
+ * setUserOrgs
224
+ * <p>
225
+ * Resolve and set the organizations for a user from a set of IDs.
226
+ * Validates each id exists in DB.
227
+ *
228
+ * @param user Target user entity.
229
+ * @param orgsIds Organization ids to assign (nullable/empty allowed).
230
+ * @param em EntityManager.
231
+ * @throws SeCurisException if any of the referenced organizations does not exist.
232
+ */
233
+ private void setUserOrgs(User user, Set<Integer> orgsIds, EntityManager em) throws SeCurisException {
234
+ Set<Organization> orgs = null;
235
+ if (orgsIds != null && !orgsIds.isEmpty()) {
236
+ orgs = new HashSet<>();
237
+ for (Integer orgId : orgsIds) {
238
+ Organization o = em.find(Organization.class, orgId);
239
+ if (o == null) {
240
+ LOG.error("User organization with id {} not found in DB", orgId);
241
+ throw new SeCurisException("User's organization not found with ID: " + orgId);
242
+ }
243
+ orgs.add(o);
244
+ }
245
+ }
246
+ user.setOrganizations(orgs);
247
+ }
193248
194
- @PUT
195
- @POST
196
- @Path("/{uid}")
197
- @EnsureTransaction
198
- @Consumes(MediaType.APPLICATION_JSON)
199
- @Produces({ MediaType.APPLICATION_JSON })
200
- @Securable(roles = Rol.ADMIN)
201
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
202
- public Response modify(User user, @PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
203
- LOG.info("Modifying user with id: {}", uid);
204
- // EntityManager em = emProvider.get();
205
- User currentUser = em.find(User.class, uid);
206
- if (currentUser == null) {
207
- LOG.info("User with id {} not found in DB, we'll try to create it", uid);
208
- return create(user, token);
209
- }
249
+ /**
250
+ * setUserApps
251
+ * <p>
252
+ * Resolve and set the applications for a user from a set of IDs.
253
+ * Validates each id exists in DB.
254
+ *
255
+ * @param user Target user entity.
256
+ * @param appsIds Application ids to assign (nullable/empty allowed).
257
+ * @param em EntityManager.
258
+ * @throws SeCurisException if any of the referenced applications does not exist.
259
+ */
260
+ private void setUserApps(User user, Set<Integer> appsIds, EntityManager em) throws SeCurisException {
261
+ Set<Application> apps = null;
262
+ if (appsIds != null && !appsIds.isEmpty()) {
263
+ apps = new HashSet<>();
264
+ for (Integer appId : appsIds) {
265
+ Application o = em.find(Application.class, appId);
266
+ if (o == null) {
267
+ LOG.error("User application with id {} not found in DB", appId);
268
+ throw new SeCurisException("User's application not found with ID: " + appId);
269
+ }
270
+ apps.add(o);
271
+ }
272
+ }
273
+ user.setApplications(apps);
274
+ }
210275
211
- try {
212
- this.setUserOrgs(currentUser, user.getOrgsIds(), em);
213
- } catch (SeCurisException e) {
214
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
215
- }
216
- try {
217
- this.setUserApps(currentUser, user.getAppsIds(), em);
218
- } catch (SeCurisException e) {
219
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
220
- }
221
- currentUser.setFirstName(user.getFirstName());
222
- currentUser.setLastName(user.getLastName());
223
- currentUser.setRoles(user.getRoles());
224
- currentUser.setLang(user.getLang());
225
- currentUser.setModificationTimestamp(new Date());
226
- if (user.getPassword() != null && !"".equals(user.getPassword())) {
227
- currentUser.setPassword(Utils.sha256(user.getPassword()));
228
- } else {
229
- // Password has not been modified
230
- // return
231
- }
276
+ /**
277
+ * modify
278
+ * <p>
279
+ * Update an existing user. If the user does not exist, delegates to {@link #create(User, String)}.
280
+ * Password is updated only if a non-empty password is provided.
281
+ * Organizations & applications are fully replaced with the given ids.
282
+ *
283
+ * Security: ADMIN only.
284
+ * Transaction: yes (via @EnsureTransaction).
285
+ *
286
+ * @param user Incoming user payload.
287
+ * @param uid Username (path param) to update.
288
+ * @param token Security token header (unused here).
289
+ * @return 200 OK with updated user; 404 if reference entities are missing.
290
+ */
291
+ @PUT
292
+ @POST
293
+ @Path("/{uid}")
294
+ @EnsureTransaction
295
+ @Consumes(MediaType.APPLICATION_JSON)
296
+ @Produces({ MediaType.APPLICATION_JSON })
297
+ @Securable(roles = Rol.ADMIN)
298
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
299
+ public Response modify(User user, @PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
300
+ LOG.info("Modifying user with id: {}", uid);
232301
233
- currentUser.setLastLogin(user.getLastLogin());
302
+ User currentUser = em.find(User.class, uid);
303
+ if (currentUser == null) {
304
+ LOG.info("User with id {} not found in DB, we'll try to create it", uid);
305
+ return create(user, token);
306
+ }
234307
235
- em.persist(currentUser);
236
- clearUserCache(currentUser.getUsername());
308
+ try {
309
+ this.setUserOrgs(currentUser, user.getOrgsIds(), em);
310
+ } catch (SeCurisException e) {
311
+ return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
312
+ }
313
+ try {
314
+ this.setUserApps(currentUser, user.getAppsIds(), em);
315
+ } catch (SeCurisException e) {
316
+ return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
317
+ }
237318
238
- return Response.ok(currentUser).build();
239
- }
319
+ currentUser.setFirstName(user.getFirstName());
320
+ currentUser.setLastName(user.getLastName());
321
+ currentUser.setRoles(user.getRoles());
322
+ currentUser.setLang(user.getLang());
323
+ currentUser.setModificationTimestamp(new Date());
240324
241
- @DELETE
242
- @Path("/{uid}")
243
- @EnsureTransaction
244
- @Produces({ MediaType.APPLICATION_JSON })
245
- @Securable(roles = Rol.ADMIN)
246
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
247
- public Response delete(@PathParam("uid") String uid, @Context HttpServletRequest request) {
248
- LOG.info("Deleting app with id: {}", uid);
249
- // EntityManager em = emProvider.get();
250
- User user = em.find(User.class, uid);
251
- if (user == null) {
252
- LOG.error("User with id {} can not be deleted, It was not found in DB", uid);
253
- return Response.status(Status.NOT_FOUND).build();
254
- }
325
+ // Optional password update
326
+ if (user.getPassword() != null && !"".equals(user.getPassword())) {
327
+ currentUser.setPassword(Utils.sha256(user.getPassword()));
328
+ }
255329
256
- em.remove(user);
257
- clearUserCache(user.getUsername());
258
- return Response.ok(Utils.createMap("success", true, "id", uid)).build();
259
- }
330
+ // lastLogin can be set through API (rare), otherwise managed at login
331
+ currentUser.setLastLogin(user.getLastLogin());
260332
261
- private void clearUserCache(String username) {
262
- cache.remove("roles_" + username);
263
- cache.remove("orgs_" + username);
264
- }
333
+ em.persist(currentUser);
334
+ clearUserCache(currentUser.getUsername());
265335
266
- @POST
267
- @Path("/login")
268
- @Produces({ MediaType.APPLICATION_JSON })
269
- public Response login(@FormParam("username") String username, @FormParam("password") String password, @Context HttpServletRequest request) throws SeCurisServiceException {
270
- LOG.info("index session: " + request.getSession());
336
+ return Response.ok(currentUser).build();
337
+ }
271338
272
- // EntityManager em = emProvider.get();
273
- User user = em.find(User.class, username);
274
- if (user == null) {
275
- LOG.error("Unknown username {} used in login service", username);
276
- throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
277
- }
278
- String securedPassword = Utils.sha256(password);
339
+ /**
340
+ * delete
341
+ * <p>
342
+ * Delete a user by username.
343
+ *
344
+ * Security: ADMIN only.
345
+ * Transaction: yes (via @EnsureTransaction).
346
+ *
347
+ * @param uid Username to delete.
348
+ * @param request Http servlet request (unused).
349
+ * @return 200 OK on success; 404 if user does not exist.
350
+ */
351
+ @DELETE
352
+ @Path("/{uid}")
353
+ @EnsureTransaction
354
+ @Produces({ MediaType.APPLICATION_JSON })
355
+ @Securable(roles = Rol.ADMIN)
356
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
357
+ public Response delete(@PathParam("uid") String uid, @Context HttpServletRequest request) {
358
+ LOG.info("Deleting app with id: {}", uid);
279359
280
- if (securedPassword == null || !securedPassword.equals(user.getPassword())) {
281
- throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
282
- }
283
- user.setLastLogin(new Date());
284
- em.getTransaction().begin();
285
- try {
286
- em.persist(user);
287
- em.getTransaction().commit();
288
- } catch (PersistenceException ex) {
289
- LOG.error("Error updating last login date for user: {}", username);
290
- LOG.error(ex);
291
- em.getTransaction().rollback();
292
- }
293
- clearUserCache(username);
294
- String userFullName = String.format("%s %s", user.getFirstName(), user.getLastName() == null ? "" : user.getLastName()).trim();
295
- String tokenAuth = tokenHelper.generateToken(username);
296
- return Response.ok(Utils.createMap("success", true, "token", tokenAuth, "username", username, "full_name", userFullName)).build();
297
- }
360
+ User user = em.find(User.class, uid);
361
+ if (user == null) {
362
+ LOG.error("User with id {} can not be deleted, It was not found in DB", uid);
363
+ return Response.status(Status.NOT_FOUND).build();
364
+ }
298365
299
- /**
300
- * Check if current token is valid
301
- *
302
- * @param user
303
- * @param password
304
- * @param request
305
- * @return
306
- */
307
- @POST
308
- @Path("/check")
309
- @Produces({ MediaType.APPLICATION_JSON })
310
- public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) {
311
- if (token == null) {
312
- token = token2;
313
- }
314
- if (token == null) {
315
- return Response.status(Status.FORBIDDEN).build();
316
- }
366
+ em.remove(user);
367
+ clearUserCache(user.getUsername());
368
+ return Response.ok(Utils.createMap("success", true, "id", uid)).build();
369
+ }
317370
318
- LOG.info("Token : " + token);
319
- String user = tokenHelper.extractUserFromToken(token);
320
- LOG.info("Token user: " + user);
321
- Date date = tokenHelper.extractDateCreationFromToken(token);
322
- LOG.info("Token date: " + date);
323
- boolean valid = tokenHelper.isTokenValid(token);
371
+ /**
372
+ * clearUserCache
373
+ * <p>
374
+ * Helper to invalidate cached role/org projections after changes.
375
+ *
376
+ * @param username The user whose cache entries must be cleared.
377
+ */
378
+ private void clearUserCache(String username) {
379
+ cache.remove("roles_" + username);
380
+ cache.remove("orgs_" + username);
381
+ }
324382
325
- LOG.info("Is Token valid: " + valid);
383
+ // ---------------------------------------------------------------------
384
+ // Auth helpers
385
+ // ---------------------------------------------------------------------
326386
327
- return Response.ok(Utils.createMap("valid", true, "user", user, "date", date, "token", token)).build();
328
- }
387
+ /**
388
+ * login
389
+ * <p>
390
+ * Validates username & password against stored SHA-256 hash. On success,
391
+ * updates lastLogin timestamp, clears cache and returns an auth token.
392
+ *
393
+ * Token format: Base64("<secret> <user> <ISO8601-date>")
394
+ * where secret = SHA-256(seed + user + date).
395
+ *
396
+ * @param username Plain username.
397
+ * @param password Plain password (SHA-256 will be computed server-side).
398
+ * @param request Http request, used to log underlying session (not required for token flow).
399
+ * @return 200 OK with {token, username, full_name}; 401 on invalid credentials.
400
+ * @throws SeCurisServiceException if user is missing or password mismatch.
401
+ */
402
+ @POST
403
+ @Path("/login")
404
+ @Produces({ MediaType.APPLICATION_JSON })
405
+ public Response login(@FormParam("username") String username, @FormParam("password") String password, @Context HttpServletRequest request) throws SeCurisServiceException {
406
+ LOG.info("index session: " + request.getSession());
329407
330
- @GET
331
- @Path("/logout")
332
- @Produces({ MediaType.APPLICATION_JSON })
333
- public Response logout(@Context HttpServletRequest request) {
334
- request.getSession().invalidate();
335
- return Response.ok().build();
336
- }
408
+ User user = em.find(User.class, username);
409
+ if (user == null) {
410
+ LOG.error("Unknown username {} used in login service", username);
411
+ throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
412
+ }
413
+ String securedPassword = Utils.sha256(password);
414
+
415
+ if (securedPassword == null || !securedPassword.equals(user.getPassword())) {
416
+ throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
417
+ }
418
+
419
+ user.setLastLogin(new Date());
420
+ em.getTransaction().begin();
421
+ try {
422
+ em.persist(user);
423
+ em.getTransaction().commit();
424
+ } catch (PersistenceException ex) {
425
+ LOG.error("Error updating last login date for user: {}", username);
426
+ LOG.error(ex);
427
+ em.getTransaction().rollback();
428
+ }
429
+
430
+ clearUserCache(username);
431
+ String userFullName = String.format("%s %s", user.getFirstName(), user.getLastName() == null ? "" : user.getLastName()).trim();
432
+ String tokenAuth = tokenHelper.generateToken(username);
433
+ return Response.ok(Utils.createMap("success", true, "token", tokenAuth, "username", username, "full_name", userFullName)).build();
434
+ }
435
+
436
+ /**
437
+ * check
438
+ * <p>
439
+ * Validates a token and echoes token claims (user, creation date, token string).
440
+ * Accepts header or query param for convenience.
441
+ *
442
+ * @param token Token in header {@link TokenHelper#TOKEN_HEADER_PÀRAM}, may be null.
443
+ * @param token2 Token in query param 'token', used if header is null.
444
+ * @return 200 OK with {valid, user, date, token} or 403 if token missing.
445
+ */
446
+ @POST
447
+ @Path("/check")
448
+ @Produces({ MediaType.APPLICATION_JSON })
449
+ public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) {
450
+ if (token == null) {
451
+ token = token2;
452
+ }
453
+ if (token == null) {
454
+ return Response.status(Status.FORBIDDEN).build();
455
+ }
456
+
457
+ LOG.info("Token : " + token);
458
+ String user = tokenHelper.extractUserFromToken(token);
459
+ LOG.info("Token user: " + user);
460
+ Date date = tokenHelper.extractDateCreationFromToken(token);
461
+ LOG.info("Token date: " + date);
462
+ boolean valid = tokenHelper.isTokenValid(token);
463
+
464
+ LOG.info("Is Token valid: " + valid);
465
+
466
+ return Response.ok(Utils.createMap("valid", true, "user", user, "date", date, "token", token)).build();
467
+ }
468
+
469
+ /**
470
+ * logout
471
+ * <p>
472
+ * Invalidates the HTTP session (useful if the UI also tracks session).
473
+ * Note: token-based auth is stateless; tokens are not revoked here.
474
+ *
475
+ * @param request HttpServletRequest to invalidate session.
476
+ * @return 200 OK always.
477
+ */
478
+ @GET
479
+ @Path("/logout")
480
+ @Produces({ MediaType.APPLICATION_JSON })
481
+ public Response logout(@Context HttpServletRequest request) {
482
+ request.getSession().invalidate();
483
+ return Response.ok().build();
484
+ }
337485 }
486
+