From 3929bbb95e6ea340d5d9cb6908897fcc59d971e8 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:32:18 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20delete=20dead=20auth=20code=20?= =?UTF-8?q?=E2=80=94=20users/roles/JWTs/ForwardAuth=20live=20in=20Logto=20?= =?UTF-8?q?now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../saas/auth/JwtAuthenticationFilter.java | 60 --------- .../cameleer/saas/auth/JwtService.java | 120 ----------------- .../cameleer/saas/auth/PermissionEntity.java | 45 ------- .../cameleer/saas/auth/RoleEntity.java | 94 -------------- .../cameleer/saas/auth/RoleRepository.java | 13 -- .../cameleer/saas/auth/UserEntity.java | 122 ------------------ .../cameleer/saas/auth/UserRepository.java | 15 --- .../saas/config/ForwardAuthController.java | 43 ------ .../cameleer/saas/config/JwtConfig.java | 82 ------------ .../db/migration/V001__create_users_table.sql | 12 -- .../V002__create_roles_and_permissions.sql | 25 ---- .../db/migration/V003__seed_default_roles.sql | 37 ------ .../cameleer/saas/auth/JwtServiceTest.java | 104 --------------- 13 files changed, 772 deletions(-) delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/JwtAuthenticationFilter.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/JwtService.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/PermissionEntity.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/RoleEntity.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/RoleRepository.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/UserEntity.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/auth/UserRepository.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/config/ForwardAuthController.java delete mode 100644 src/main/java/net/siegeln/cameleer/saas/config/JwtConfig.java delete mode 100644 src/main/resources/db/migration/V001__create_users_table.sql delete mode 100644 src/main/resources/db/migration/V002__create_roles_and_permissions.sql delete mode 100644 src/main/resources/db/migration/V003__seed_default_roles.sql delete mode 100644 src/test/java/net/siegeln/cameleer/saas/auth/JwtServiceTest.java diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/JwtAuthenticationFilter.java b/src/main/java/net/siegeln/cameleer/saas/auth/JwtAuthenticationFilter.java deleted file mode 100644 index d4c9e7e..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/JwtAuthenticationFilter.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; - -@Component -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtService jwtService; - - public JwtAuthenticationFilter(JwtService jwtService) { - this.jwtService = jwtService; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - String authHeader = request.getHeader("Authorization"); - - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - filterChain.doFilter(request, response); - return; - } - - String token = authHeader.substring(7); - - if (!jwtService.isTokenValid(token)) { - filterChain.doFilter(request, response); - return; - } - - String email = jwtService.extractEmail(token); - var userId = jwtService.extractUserId(token); - var roles = jwtService.extractRoles(token); - - var authorities = roles.stream() - .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) - .toList(); - - var authentication = new UsernamePasswordAuthenticationToken( - email, userId, authorities - ); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authentication); - - filterChain.doFilter(request, response); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/JwtService.java b/src/main/java/net/siegeln/cameleer/saas/auth/JwtService.java deleted file mode 100644 index a74bdf6..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/JwtService.java +++ /dev/null @@ -1,120 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import net.siegeln.cameleer.saas.config.JwtConfig; -import org.springframework.stereotype.Service; - -import java.security.Signature; -import java.time.Instant; -import java.util.Base64; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -@Service -public class JwtService { - - private final JwtConfig jwtConfig; - private final ObjectMapper objectMapper; - - public JwtService(JwtConfig jwtConfig) { - this.jwtConfig = jwtConfig; - this.objectMapper = new ObjectMapper(); - } - - public String generateToken(UserEntity user) { - try { - String header = base64UrlEncode(objectMapper.writeValueAsBytes( - Map.of("alg", "EdDSA", "typ", "JWT") - )); - - Instant now = Instant.now(); - Map payload = new LinkedHashMap<>(); - payload.put("sub", user.getEmail()); - payload.put("uid", user.getId().toString()); - payload.put("name", user.getName()); - payload.put("roles", user.getRoles().stream() - .map(RoleEntity::getName) - .toList()); - payload.put("iat", now.getEpochSecond()); - payload.put("exp", now.getEpochSecond() + jwtConfig.getExpirationSeconds()); - - String payloadEncoded = base64UrlEncode(objectMapper.writeValueAsBytes(payload)); - - String signingInput = header + "." + payloadEncoded; - Signature sig = Signature.getInstance("Ed25519"); - sig.initSign(jwtConfig.getPrivateKey()); - sig.update(signingInput.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - String signature = base64UrlEncode(sig.sign()); - - return signingInput + "." + signature; - } catch (Exception e) { - throw new RuntimeException("Failed to generate JWT", e); - } - } - - public String extractEmail(String token) { - Map payload = parsePayload(token); - return (String) payload.get("sub"); - } - - public UUID extractUserId(String token) { - Map payload = parsePayload(token); - return UUID.fromString((String) payload.get("uid")); - } - - @SuppressWarnings("unchecked") - public Set extractRoles(String token) { - Map payload = parsePayload(token); - List roles = (List) payload.get("roles"); - return roles.stream().collect(Collectors.toSet()); - } - - public boolean isTokenValid(String token) { - try { - String[] parts = token.split("\\."); - if (parts.length != 3) { - return false; - } - - String signingInput = parts[0] + "." + parts[1]; - byte[] signatureBytes = base64UrlDecode(parts[2]); - - Signature sig = Signature.getInstance("Ed25519"); - sig.initVerify(jwtConfig.getPublicKey()); - sig.update(signingInput.getBytes(java.nio.charset.StandardCharsets.UTF_8)); - - if (!sig.verify(signatureBytes)) { - return false; - } - - Map payload = parsePayload(token); - long exp = ((Number) payload.get("exp")).longValue(); - return Instant.now().getEpochSecond() < exp; - } catch (Exception e) { - return false; - } - } - - private Map parsePayload(String token) { - try { - String[] parts = token.split("\\."); - byte[] payloadBytes = base64UrlDecode(parts[1]); - return objectMapper.readValue(payloadBytes, new TypeReference<>() {}); - } catch (Exception e) { - throw new RuntimeException("Failed to parse JWT payload", e); - } - } - - private String base64UrlEncode(byte[] data) { - return Base64.getUrlEncoder().withoutPadding().encodeToString(data); - } - - private byte[] base64UrlDecode(String data) { - return Base64.getUrlDecoder().decode(data); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/PermissionEntity.java b/src/main/java/net/siegeln/cameleer/saas/auth/PermissionEntity.java deleted file mode 100644 index 52d4fba..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/PermissionEntity.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -import java.util.UUID; - -@Entity -@Table(name = "permissions") -public class PermissionEntity { - - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - - @Column(name = "name", nullable = false, unique = true, length = 100) - private String name; - - @Column(name = "description") - private String description; - - public UUID getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/RoleEntity.java b/src/main/java/net/siegeln/cameleer/saas/auth/RoleEntity.java deleted file mode 100644 index cedda48..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/RoleEntity.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.PrePersist; -import jakarta.persistence.Table; - -import java.time.Instant; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -@Entity -@Table(name = "roles") -public class RoleEntity { - - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - - @Column(name = "name", nullable = false, unique = true, length = 50) - private String name; - - @Column(name = "description") - private String description; - - @Column(name = "built_in", nullable = false) - private boolean builtIn; - - @Column(name = "created_at", nullable = false, updatable = false) - private Instant createdAt; - - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable( - name = "role_permissions", - joinColumns = @JoinColumn(name = "role_id"), - inverseJoinColumns = @JoinColumn(name = "permission_id") - ) - private Set permissions = new HashSet<>(); - - @PrePersist - protected void onCreate() { - if (createdAt == null) { - createdAt = Instant.now(); - } - } - - public UUID getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public boolean isBuiltIn() { - return builtIn; - } - - public void setBuiltIn(boolean builtIn) { - this.builtIn = builtIn; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public Set getPermissions() { - return permissions; - } - - public void setPermissions(Set permissions) { - this.permissions = permissions; - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/RoleRepository.java b/src/main/java/net/siegeln/cameleer/saas/auth/RoleRepository.java deleted file mode 100644 index ff456bd..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/RoleRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; -import java.util.UUID; - -@Repository -public interface RoleRepository extends JpaRepository { - - Optional findByName(String name); -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/UserEntity.java b/src/main/java/net/siegeln/cameleer/saas/auth/UserEntity.java deleted file mode 100644 index c4965f2..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/UserEntity.java +++ /dev/null @@ -1,122 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.PrePersist; -import jakarta.persistence.PreUpdate; -import jakarta.persistence.Table; - -import java.time.Instant; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -@Entity -@Table(name = "users") -public class UserEntity { - - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - - @Column(name = "email", nullable = false, unique = true) - private String email; - - @Column(name = "password", nullable = false) - private String password; - - @Column(name = "name", nullable = false) - private String name; - - @Column(name = "status", nullable = false, length = 20) - private String status = "ACTIVE"; - - @Column(name = "created_at", nullable = false, updatable = false) - private Instant createdAt; - - @Column(name = "updated_at", nullable = false) - private Instant updatedAt; - - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable( - name = "user_roles", - joinColumns = @JoinColumn(name = "user_id"), - inverseJoinColumns = @JoinColumn(name = "role_id") - ) - private Set roles = new HashSet<>(); - - @PrePersist - protected void onCreate() { - Instant now = Instant.now(); - if (createdAt == null) { - createdAt = now; - } - if (updatedAt == null) { - updatedAt = now; - } - } - - @PreUpdate - protected void onUpdate() { - updatedAt = Instant.now(); - } - - public UUID getId() { - return id; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public Set getRoles() { - return roles; - } - - public void setRoles(Set roles) { - this.roles = roles; - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/UserRepository.java b/src/main/java/net/siegeln/cameleer/saas/auth/UserRepository.java deleted file mode 100644 index 3d13743..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/UserRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; -import java.util.UUID; - -@Repository -public interface UserRepository extends JpaRepository { - - Optional findByEmail(String email); - - boolean existsByEmail(String email); -} diff --git a/src/main/java/net/siegeln/cameleer/saas/config/ForwardAuthController.java b/src/main/java/net/siegeln/cameleer/saas/config/ForwardAuthController.java deleted file mode 100644 index 0609155..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/config/ForwardAuthController.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.siegeln.cameleer.saas.config; - -import net.siegeln.cameleer.saas.auth.JwtService; -import net.siegeln.cameleer.saas.tenant.TenantService; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import jakarta.servlet.http.HttpServletRequest; - -@RestController -public class ForwardAuthController { - - private final JwtService jwtService; - private final TenantService tenantService; - - public ForwardAuthController(JwtService jwtService, TenantService tenantService) { - this.jwtService = jwtService; - this.tenantService = tenantService; - } - - @GetMapping("/auth/verify") - public ResponseEntity verify(HttpServletRequest request) { - String authHeader = request.getHeader("Authorization"); - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - return ResponseEntity.status(401).build(); - } - - String token = authHeader.substring(7); - - if (jwtService.isTokenValid(token)) { - String email = jwtService.extractEmail(token); - var userId = jwtService.extractUserId(token); - - return ResponseEntity.ok() - .header("X-User-Id", userId.toString()) - .header("X-User-Email", email) - .build(); - } - - return ResponseEntity.status(401).build(); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/config/JwtConfig.java b/src/main/java/net/siegeln/cameleer/saas/config/JwtConfig.java deleted file mode 100644 index ae79022..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/config/JwtConfig.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.siegeln.cameleer.saas.config; - -import jakarta.annotation.PostConstruct; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; - -@Component -public class JwtConfig { - - private static final Logger log = LoggerFactory.getLogger(JwtConfig.class); - - @Value("${cameleer.jwt.expiration:86400}") - private long expirationSeconds = 86400; - - @Value("${cameleer.jwt.private-key-path:}") - private String privateKeyPath = ""; - - @Value("${cameleer.jwt.public-key-path:}") - private String publicKeyPath = ""; - - private KeyPair keyPair; - - @PostConstruct - public void init() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { - if (privateKeyPath.isEmpty() || publicKeyPath.isEmpty()) { - log.warn("No Ed25519 key files configured — generating ephemeral keys (dev mode)"); - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("Ed25519"); - this.keyPair = keyGen.generateKeyPair(); - } else { - log.info("Loading Ed25519 keys from {} and {}", privateKeyPath, publicKeyPath); - PrivateKey privateKey = loadPrivateKey(Path.of(privateKeyPath)); - PublicKey publicKey = loadPublicKey(Path.of(publicKeyPath)); - this.keyPair = new KeyPair(publicKey, privateKey); - } - } - - private PrivateKey loadPrivateKey(Path path) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { - String pem = Files.readString(path) - .replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replaceAll("\\s+", ""); - byte[] decoded = Base64.getDecoder().decode(pem); - return KeyFactory.getInstance("Ed25519").generatePrivate(new PKCS8EncodedKeySpec(decoded)); - } - - private PublicKey loadPublicKey(Path path) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { - String pem = Files.readString(path) - .replace("-----BEGIN PUBLIC KEY-----", "") - .replace("-----END PUBLIC KEY-----", "") - .replaceAll("\\s+", ""); - byte[] decoded = Base64.getDecoder().decode(pem); - return KeyFactory.getInstance("Ed25519").generatePublic(new X509EncodedKeySpec(decoded)); - } - - public PrivateKey getPrivateKey() { - return keyPair.getPrivate(); - } - - public PublicKey getPublicKey() { - return keyPair.getPublic(); - } - - public long getExpirationSeconds() { - return expirationSeconds; - } -} diff --git a/src/main/resources/db/migration/V001__create_users_table.sql b/src/main/resources/db/migration/V001__create_users_table.sql deleted file mode 100644 index e828ff1..0000000 --- a/src/main/resources/db/migration/V001__create_users_table.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - email VARCHAR(255) NOT NULL UNIQUE, - password VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE INDEX idx_users_email ON users (email); -CREATE INDEX idx_users_status ON users (status); diff --git a/src/main/resources/db/migration/V002__create_roles_and_permissions.sql b/src/main/resources/db/migration/V002__create_roles_and_permissions.sql deleted file mode 100644 index f2a65d5..0000000 --- a/src/main/resources/db/migration/V002__create_roles_and_permissions.sql +++ /dev/null @@ -1,25 +0,0 @@ -CREATE TABLE roles ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(50) NOT NULL UNIQUE, - description VARCHAR(255), - built_in BOOLEAN NOT NULL DEFAULT false, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE TABLE permissions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(100) NOT NULL UNIQUE, - description VARCHAR(255) -); - -CREATE TABLE role_permissions ( - role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, - permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE, - PRIMARY KEY (role_id, permission_id) -); - -CREATE TABLE user_roles ( - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, - PRIMARY KEY (user_id, role_id) -); diff --git a/src/main/resources/db/migration/V003__seed_default_roles.sql b/src/main/resources/db/migration/V003__seed_default_roles.sql deleted file mode 100644 index bf67750..0000000 --- a/src/main/resources/db/migration/V003__seed_default_roles.sql +++ /dev/null @@ -1,37 +0,0 @@ --- Permissions -INSERT INTO permissions (id, name, description) VALUES - ('00000000-0000-0000-0000-000000000001', 'tenant:manage', 'Full tenant administration'), - ('00000000-0000-0000-0000-000000000002', 'billing:manage', 'Manage billing and subscriptions'), - ('00000000-0000-0000-0000-000000000003', 'team:manage', 'Manage team members and roles'), - ('00000000-0000-0000-0000-000000000004', 'apps:manage', 'Deploy, configure, and manage applications'), - ('00000000-0000-0000-0000-000000000005', 'apps:deploy', 'Deploy and promote applications'), - ('00000000-0000-0000-0000-000000000006', 'secrets:manage', 'Create and rotate secrets'), - ('00000000-0000-0000-0000-000000000007', 'observe:read', 'View traces, topology, dashboards'), - ('00000000-0000-0000-0000-000000000008', 'observe:debug', 'Use debugger and replay'), - ('00000000-0000-0000-0000-000000000009', 'settings:manage', 'Manage tenant settings'); - --- Roles -INSERT INTO roles (id, name, description, built_in) VALUES - ('10000000-0000-0000-0000-000000000001', 'OWNER', 'Full tenant admin including billing and deletion', true), - ('10000000-0000-0000-0000-000000000002', 'ADMIN', 'Manage apps, secrets, team. No billing.', true), - ('10000000-0000-0000-0000-000000000003', 'DEVELOPER', 'Deploy apps, view traces, use debugger.', true), - ('10000000-0000-0000-0000-000000000004', 'VIEWER', 'Read-only access to dashboards and traces.', true); - --- Owner: all permissions -INSERT INTO role_permissions (role_id, permission_id) -SELECT '10000000-0000-0000-0000-000000000001', id FROM permissions; - --- Admin: everything except billing and tenant management -INSERT INTO role_permissions (role_id, permission_id) -SELECT '10000000-0000-0000-0000-000000000002', id FROM permissions -WHERE name NOT IN ('tenant:manage', 'billing:manage'); - --- Developer: apps, secrets, observe (including debug) -INSERT INTO role_permissions (role_id, permission_id) -SELECT '10000000-0000-0000-0000-000000000003', id FROM permissions -WHERE name IN ('apps:deploy', 'secrets:manage', 'observe:read', 'observe:debug'); - --- Viewer: observe read-only -INSERT INTO role_permissions (role_id, permission_id) -SELECT '10000000-0000-0000-0000-000000000004', id FROM permissions -WHERE name = 'observe:read'; diff --git a/src/test/java/net/siegeln/cameleer/saas/auth/JwtServiceTest.java b/src/test/java/net/siegeln/cameleer/saas/auth/JwtServiceTest.java deleted file mode 100644 index 151c9a8..0000000 --- a/src/test/java/net/siegeln/cameleer/saas/auth/JwtServiceTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import net.siegeln.cameleer.saas.config.JwtConfig; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class JwtServiceTest { - - private JwtService jwtService; - - @BeforeEach - void setUp() throws Exception { - JwtConfig config = new JwtConfig(); - config.init(); - jwtService = new JwtService(config); - } - - @Test - void generateToken_producesValidJwt() { - UserEntity user = createUser("test@example.com", "OWNER"); - - String token = jwtService.generateToken(user); - - assertNotNull(token); - String[] parts = token.split("\\."); - assertEquals(3, parts.length, "JWT should have 3 parts separated by dots"); - } - - @Test - void extractEmail_returnsCorrectEmail() { - UserEntity user = createUser("test@example.com", "OWNER"); - - String token = jwtService.generateToken(user); - String email = jwtService.extractEmail(token); - - assertEquals("test@example.com", email); - } - - @Test - void isTokenValid_returnsTrueForValidToken() { - UserEntity user = createUser("test@example.com", "OWNER"); - - String token = jwtService.generateToken(user); - - assertTrue(jwtService.isTokenValid(token)); - } - - @Test - void isTokenValid_returnsFalseForTamperedToken() { - UserEntity user = createUser("test@example.com", "OWNER"); - - String token = jwtService.generateToken(user); - // Tamper with the last 5 characters of the signature - String tampered = token.substring(0, token.length() - 5) + "XXXXX"; - - assertFalse(jwtService.isTokenValid(tampered)); - } - - @Test - void extractRoles_returnsUserRoles() { - UserEntity user = createUser("test@example.com", "OWNER"); - - String token = jwtService.generateToken(user); - var roles = jwtService.extractRoles(token); - - assertNotNull(roles); - assertTrue(roles.contains("OWNER")); - assertEquals(1, roles.size()); - } - - @Test - void extractUserId_returnsCorrectId() { - UserEntity user = createUser("test@example.com", "OWNER"); - - String token = jwtService.generateToken(user); - UUID extractedId = jwtService.extractUserId(token); - - assertEquals(user.getId(), extractedId); - } - - private UserEntity createUser(String email, String roleName) { - var role = new RoleEntity(); - role.setName(roleName); - var user = new UserEntity(); - user.setEmail(email); - user.setName("Test User"); - user.getRoles().add(role); - try { - var idField = UserEntity.class.getDeclaredField("id"); - idField.setAccessible(true); - idField.set(user, UUID.randomUUID()); - } catch (Exception e) { - throw new RuntimeException(e); - } - return user; - } -}