fix: corrigir expiração de token e padronizar constantes

- Substitui LocalDateTime.now() por Instant.now() no issuedAt — a string
  produzida por LocalDateTime não contém timezone e era interpretada como
  hora local pelo browser, fazendo o frontend calcular expiresAt errado e
  nunca deslogar o usuário quando o token vencia.
- Cria TokenConstants com ACCESS_TOKEN_EXPIRY_SECONDS e
  REFRESH_TOKEN_EXPIRY_SECONDS, eliminando os valores hardcoded em
  LoginUseCase e RefreshTokenUseCase.
- Remove imports não utilizados em AuthController.

closes #24

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gabriel Santana de Menezes 2026-05-14 22:43:26 -04:00
parent db463551e6
commit 9ba47e0ae5
4 changed files with 21 additions and 14 deletions

View file

@ -19,9 +19,6 @@ import net.beetechgroup.beetask.usecase.user.create.CreateUserInput;
import net.beetechgroup.beetask.usecase.user.create.CreateUserOutput; import net.beetechgroup.beetask.usecase.user.create.CreateUserOutput;
import net.beetechgroup.beetask.usecase.user.create.CreateUserUseCase; import net.beetechgroup.beetask.usecase.user.create.CreateUserUseCase;
import java.time.LocalDateTime;
import java.util.List;
import io.quarkus.security.Authenticated; import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;

View file

@ -0,0 +1,9 @@
package net.beetechgroup.beetask.usecase.auth;
public final class TokenConstants {
public static final long ACCESS_TOKEN_EXPIRY_SECONDS = 3600L; // 1 hour
public static final long REFRESH_TOKEN_EXPIRY_SECONDS = 86400L; // 24 hours
private TokenConstants() {}
}

View file

@ -3,9 +3,10 @@ package net.beetechgroup.beetask.usecase.auth.login;
import io.smallrye.jwt.build.Jwt; import io.smallrye.jwt.build.Jwt;
import net.beetechgroup.beetask.entities.User; import net.beetechgroup.beetask.entities.User;
import net.beetechgroup.beetask.entities.organization.UserOrganization; import net.beetechgroup.beetask.entities.organization.UserOrganization;
import net.beetechgroup.beetask.usecase.auth.TokenConstants;
import net.beetechgroup.beetask.usecase.repository.UserRepository; import net.beetechgroup.beetask.usecase.repository.UserRepository;
import java.time.LocalDateTime; import java.time.Instant;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -51,7 +52,7 @@ public class LoginUseCase {
.upn(user.getEmail()) .upn(user.getEmail())
.groups(groups) .groups(groups)
.claim("name", user.getName()) .claim("name", user.getName())
.expiresIn(3600) .expiresIn(TokenConstants.ACCESS_TOKEN_EXPIRY_SECONDS)
.sign(); .sign();
List<LoginOutput.OrganizationOutput> orgs = userOrganizations.stream() List<LoginOutput.OrganizationOutput> orgs = userOrganizations.stream()
@ -69,7 +70,7 @@ public class LoginUseCase {
String refreshToken = Jwt.issuer(this.issuer) String refreshToken = Jwt.issuer(this.issuer)
.upn(user.getEmail()) .upn(user.getEmail())
.expiresIn(86400) // 24 hours .expiresIn(TokenConstants.REFRESH_TOKEN_EXPIRY_SECONDS)
.sign(); .sign();
LOGGER.infof("Login completed for user %s with %d organizations", user.getEmail(), orgs.size()); LOGGER.infof("Login completed for user %s with %d organizations", user.getEmail(), orgs.size());
@ -79,8 +80,8 @@ public class LoginUseCase {
Objects.nonNull(user.getPhoto()) ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random", Objects.nonNull(user.getPhoto()) ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random",
token, token,
refreshToken, refreshToken,
3600L, TokenConstants.ACCESS_TOKEN_EXPIRY_SECONDS,
LocalDateTime.now().toString(), Instant.now().toString(),
orgs orgs
); );
} }

View file

@ -5,11 +5,12 @@ import io.smallrye.jwt.auth.principal.ParseException;
import io.smallrye.jwt.build.Jwt; import io.smallrye.jwt.build.Jwt;
import net.beetechgroup.beetask.entities.User; import net.beetechgroup.beetask.entities.User;
import net.beetechgroup.beetask.entities.organization.UserOrganization; import net.beetechgroup.beetask.entities.organization.UserOrganization;
import net.beetechgroup.beetask.usecase.auth.TokenConstants;
import net.beetechgroup.beetask.usecase.auth.login.LoginOutput; import net.beetechgroup.beetask.usecase.auth.login.LoginOutput;
import net.beetechgroup.beetask.usecase.repository.UserRepository; import net.beetechgroup.beetask.usecase.repository.UserRepository;
import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.jwt.JsonWebToken;
import java.time.LocalDateTime; import java.time.Instant;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -58,13 +59,12 @@ public class RefreshTokenUseCase {
.upn(user.getEmail()) .upn(user.getEmail())
.groups(groups) .groups(groups)
.claim("name", user.getName()) .claim("name", user.getName())
.expiresIn(3600) .expiresIn(TokenConstants.ACCESS_TOKEN_EXPIRY_SECONDS)
.sign(); .sign();
// Generate a new refresh token (also a JWT but with longer expiration)
String newRefreshToken = Jwt.issuer(this.issuer) String newRefreshToken = Jwt.issuer(this.issuer)
.upn(user.getEmail()) .upn(user.getEmail())
.expiresIn(86400) // 24 hours .expiresIn(TokenConstants.REFRESH_TOKEN_EXPIRY_SECONDS)
.sign(); .sign();
List<LoginOutput.OrganizationOutput> orgs = userOrganizations.stream() List<LoginOutput.OrganizationOutput> orgs = userOrganizations.stream()
@ -87,8 +87,8 @@ public class RefreshTokenUseCase {
Objects.nonNull(user.getPhoto()) ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random", Objects.nonNull(user.getPhoto()) ? user.getPhoto() : "https://ui-avatars.com/api/?name=" + user.getName().replace(" ", "+") + "&background=random",
newToken, newToken,
newRefreshToken, newRefreshToken,
3600L, TokenConstants.ACCESS_TOKEN_EXPIRY_SECONDS,
LocalDateTime.now().toString(), Instant.now().toString(),
orgs orgs
); );
} catch (ParseException e) { } catch (ParseException e) {