refactor: remove Phase 1 auth endpoints, switch to Logto OIDC

Auth is now handled by Logto. Removed AuthController, AuthService,
and related DTOs. Integration tests use Spring Security JWT mocks.
Ed25519 JwtService retained for machine token signing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-04 15:12:50 +02:00
parent ab9ad1ab7f
commit db7647f7f4
12 changed files with 34 additions and 515 deletions

View File

@@ -1,54 +0,0 @@
package net.siegeln.cameleer.saas.auth;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import net.siegeln.cameleer.saas.auth.dto.AuthResponse;
import net.siegeln.cameleer.saas.auth.dto.LoginRequest;
import net.siegeln.cameleer.saas.auth.dto.RegisterRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping("/register")
public ResponseEntity<AuthResponse> register(@Valid @RequestBody RegisterRequest request,
HttpServletRequest httpRequest) {
try {
var response = authService.register(request, extractClientIp(httpRequest));
return ResponseEntity.status(HttpStatus.CREATED).body(response);
} catch (IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginRequest request,
HttpServletRequest httpRequest) {
try {
var response = authService.login(request, extractClientIp(httpRequest));
return ResponseEntity.ok(response);
} catch (IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
private String extractClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}

View File

@@ -1,82 +0,0 @@
package net.siegeln.cameleer.saas.auth;
import net.siegeln.cameleer.saas.audit.AuditAction;
import net.siegeln.cameleer.saas.audit.AuditService;
import net.siegeln.cameleer.saas.auth.dto.AuthResponse;
import net.siegeln.cameleer.saas.auth.dto.LoginRequest;
import net.siegeln.cameleer.saas.auth.dto.RegisterRequest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class AuthService {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final AuditService auditService;
public AuthService(UserRepository userRepository,
RoleRepository roleRepository,
PasswordEncoder passwordEncoder,
JwtService jwtService,
AuditService auditService) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.passwordEncoder = passwordEncoder;
this.jwtService = jwtService;
this.auditService = auditService;
}
public AuthResponse register(RegisterRequest request, String sourceIp) {
if (userRepository.existsByEmail(request.email())) {
throw new IllegalArgumentException("Email already registered");
}
var user = new UserEntity();
user.setEmail(request.email());
user.setName(request.name());
user.setPassword(passwordEncoder.encode(request.password()));
roleRepository.findByName("OWNER").ifPresent(role -> user.getRoles().add(role));
var saved = userRepository.save(user);
var token = jwtService.generateToken(saved);
auditService.log(
saved.getId(), saved.getEmail(), null,
AuditAction.AUTH_REGISTER, null,
null, sourceIp,
"SUCCESS", null
);
return new AuthResponse(token, saved.getEmail(), saved.getName());
}
public AuthResponse login(LoginRequest request, String sourceIp) {
var user = userRepository.findByEmail(request.email())
.orElseThrow(() -> new IllegalArgumentException("Invalid credentials"));
if (!passwordEncoder.matches(request.password(), user.getPassword())) {
auditService.log(
user.getId(), user.getEmail(), null,
AuditAction.AUTH_LOGIN_FAILED, null,
null, sourceIp,
"FAILURE", null
);
throw new IllegalArgumentException("Invalid credentials");
}
var token = jwtService.generateToken(user);
auditService.log(
user.getId(), user.getEmail(), null,
AuditAction.AUTH_LOGIN, null,
null, sourceIp,
"SUCCESS", null
);
return new AuthResponse(token, user.getEmail(), user.getName());
}
}

View File

@@ -1,8 +0,0 @@
package net.siegeln.cameleer.saas.auth.dto;
public record AuthResponse(
String token,
String email,
String name
) {
}

View File

@@ -1,10 +0,0 @@
package net.siegeln.cameleer.saas.auth.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public record LoginRequest(
@NotBlank @Email String email,
@NotBlank String password
) {
}

View File

@@ -1,12 +0,0 @@
package net.siegeln.cameleer.saas.auth.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record RegisterRequest(
@NotBlank @Email String email,
@NotBlank String name,
@NotBlank @Size(min = 8, max = 128) String password
) {
}

View File

@@ -47,7 +47,6 @@ public class SecurityConfig {
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/auth/verify").permitAll()
.anyRequest().authenticated()

View File

@@ -32,8 +32,14 @@ public class LicenseController {
var tenant = tenantService.getById(tenantId).orElse(null);
if (tenant == null) return ResponseEntity.notFound().build();
UUID actorId = authentication.getCredentials() instanceof UUID uid
? uid : UUID.fromString(authentication.getCredentials().toString());
// Extract actor ID from JWT subject (Logto OIDC: sub may be a non-UUID string)
String sub = authentication.getName();
UUID actorId;
try {
actorId = UUID.fromString(sub);
} catch (IllegalArgumentException e) {
actorId = UUID.nameUUIDFromBytes(sub.getBytes());
}
var license = licenseService.generateLicense(tenant, Duration.ofDays(365), actorId);
return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(license));

View File

@@ -29,9 +29,14 @@ public class TenantController {
public ResponseEntity<TenantResponse> create(@Valid @RequestBody CreateTenantRequest request,
Authentication authentication) {
try {
// Extract actor ID from authentication credentials (Phase 1: userId stored as credentials)
UUID actorId = authentication.getCredentials() instanceof UUID uid
? uid : UUID.fromString(authentication.getCredentials().toString());
// Extract actor ID from JWT subject (Logto OIDC: sub may be a non-UUID string)
String sub = authentication.getName();
UUID actorId;
try {
actorId = UUID.fromString(sub);
} catch (IllegalArgumentException e) {
actorId = UUID.nameUUIDFromBytes(sub.getBytes());
}
var entity = tenantService.create(request, actorId);
return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(entity));