package com.l2.shots.auth; import io.quarkus.security.Authenticated; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.NewCookie; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.jwt.JsonWebToken; import java.util.Optional; import java.util.UUID; @Path("/api/auth") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class AuthResource { @Inject AuthService authService; @Inject JwtCookieAuth jwtCookieAuth; @Inject JsonWebToken currentJwt; @ConfigProperty(name = "app.auth.cookie-name") String cookieName; @ConfigProperty(name = "app.auth.cookie-max-age-seconds") int cookieMaxAge; @POST @Path("/register") public Response register(Credentials creds) { Optional result = authService.register(creds.username(), creds.password()); if (result.isEmpty()) { return Response.status(409) .entity(new ErrorBody("username no disponible o datos inválidos")) .build(); } User user = result.get(); String token = authService.buildToken(user.id); return Response.ok(AuthService.toAuthMe(user)) .cookie(buildAuthCookie(token)) .build(); } @POST @Path("/login") public Response login(Credentials creds) { Optional result = authService.authenticate(creds.username(), creds.password()); if (result.isEmpty()) { return Response.status(401) .entity(new ErrorBody("credenciales inválidas")) .build(); } User user = result.get(); String token = authService.buildToken(user.id); return Response.ok(AuthService.toAuthMe(user)) .cookie(buildAuthCookie(token)) .build(); } @POST @Path("/logout") public Response logout() { return Response.noContent() .cookie(clearAuthCookie()) .build(); } @POST @Path("/change-password") @Authenticated public Response changePassword(@Context HttpHeaders headers, ChangePasswordRequest body) { if (body == null || body.currentPassword() == null || body.newPassword() == null) { return Response.status(400).entity(new ErrorBody("Faltan campos requeridos")).build(); } Optional jwt = jwtCookieAuth.extractToken(headers); if (jwt.isEmpty()) return Response.status(401).build(); Optional user = authService.getEntityFromToken(jwt.get()); if (user.isEmpty()) return Response.status(401).build(); AuthService.ChangePasswordResult result = authService.changePassword( user.get().id, body.currentPassword(), body.newPassword()); switch (result) { case OK: User refreshed = User.findById(user.get().id); if (refreshed == null) return Response.status(401).build(); String newToken = authService.buildToken(refreshed.id); return Response.ok(AuthService.toAuthMe(refreshed)) .cookie(buildAuthCookie(newToken)) .build(); case WRONG_CURRENT_PASSWORD: return Response.status(401).entity(new ErrorBody("La contraseña actual es incorrecta")).build(); case WEAK_NEW_PASSWORD: return Response.status(400).entity(new ErrorBody( "La nueva contraseña debe tener al menos " + AuthService.MIN_PASSWORD_LENGTH + " caracteres")).build(); case NOT_FOUND: return Response.status(401).build(); default: return Response.status(500).build(); } } @POST @Path("/admin/reset-password") @RolesAllowed("admin") public Response adminResetPassword(AdminResetPasswordRequest body) { if (body == null || body.username() == null || body.newPassword() == null) { return Response.status(400).entity(new ErrorBody("Faltan campos requeridos")).build(); } UUID requesterId; try { requesterId = UUID.fromString(currentJwt.getSubject()); } catch (Exception e) { return Response.status(401).build(); } boolean ok = authService.resetPasswordAsAdmin(requesterId, body.username(), body.newPassword()); if (!ok) { return Response.status(400).entity(new ErrorBody("Usuario no encontrado o contraseña inválida")).build(); } return Response.ok().build(); } @GET @Path("/me") public Response me(@Context HttpHeaders headers) { Optional jwt = jwtCookieAuth.extractToken(headers); if (jwt.isEmpty()) { return Response.status(401).build(); } return authService.getUserFromToken(jwt.get()) .map(u -> Response.ok(u).build()) .orElse(Response.status(401).build()); } @GET @Path("/admin/users") @RolesAllowed("admin") public Response adminListUsers() { return Response.ok(authService.listUsersForAdmin()).build(); } @GET @Path("/check") @Authenticated public Response check() { return Response.ok().build(); } private NewCookie buildAuthCookie(String token) { return new NewCookie.Builder(cookieName) .value(token) .path("/") .httpOnly(true) .secure(false) .sameSite(NewCookie.SameSite.LAX) .maxAge(cookieMaxAge) .build(); } private NewCookie clearAuthCookie() { return new NewCookie.Builder(cookieName) .value("") .path("/") .httpOnly(true) .secure(false) .sameSite(NewCookie.SameSite.LAX) .maxAge(0) .build(); } public record ErrorBody(String error) { } }