extended user registration process, initial event based processing

This commit is contained in:
Bernhard Müller 2026-06-08 15:59:42 +02:00
parent 9bb354a805
commit a46566f8c6
11 changed files with 179 additions and 16 deletions

View File

@ -42,6 +42,16 @@
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId> <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>3.0.3</version> <version>3.0.3</version>
</dependency> </dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
@ -99,6 +109,31 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.3</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amapstruct.defaultComponentModel=spring</arg>
<arg>-Amapstruct.defaultInjectionStrategy=constructor</arg>
<arg>-Amapstruct.unmappedTargetPolicy=ERROR</arg>
</compilerArgs>
</configuration>
<executions> <executions>
<execution> <execution>
<id>default-compile</id> <id>default-compile</id>

View File

@ -0,0 +1,8 @@
package at.mueller.eeg.backend.common.config.event;
import java.util.UUID;
public record EdaTopologyCheckEvent(
UUID userId,
String atNumber
) {}

View File

@ -0,0 +1,8 @@
package at.mueller.eeg.backend.common.config.event;
import java.util.UUID;
public record UserRegisteredEvent(
UUID userId,
String atNumber
) {}

View File

@ -0,0 +1,33 @@
package at.mueller.eeg.backend.community.event.listener;
import at.mueller.eeg.backend.common.config.event.EdaTopologyCheckEvent;
import at.mueller.eeg.backend.common.config.event.UserRegisteredEvent;
import at.mueller.eeg.backend.community.domain.MeteringPoint;
import at.mueller.eeg.backend.community.domain.PointType;
import at.mueller.eeg.backend.community.repository.MeteringPointRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@Component
@RequiredArgsConstructor
public class MeteringPointEventListener {
private final MeteringPointRepository meteringPointRepository;
private final ApplicationEventPublisher eventPublisher;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional
public void handleUserRegistered(UserRegisteredEvent event) {
MeteringPoint point = new MeteringPoint();
point.setUserId(event.userId());
point.setAtNumber(event.atNumber());
point.setType(PointType.CONSUMER);
meteringPointRepository.save(point);
eventPublisher.publishEvent(new EdaTopologyCheckEvent(point.getId(), event.atNumber()));
}
}

View File

@ -0,0 +1,7 @@
package at.mueller.eeg.backend.community.repository;
import at.mueller.eeg.backend.community.domain.MeteringPoint;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MeteringPointRepository extends JpaRepository<MeteringPoint, String> {
}

View File

@ -1,8 +1,10 @@
package at.mueller.eeg.backend.iam.api; package at.mueller.eeg.backend.iam.api;
import at.mueller.eeg.backend.iam.api.dto.IamDtos.UserProfileResponse; import at.mueller.eeg.backend.iam.api.dto.IamDtos.UserProfileResponse;
import at.mueller.eeg.backend.iam.repository.UserRepository;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -14,16 +16,19 @@ import java.util.UUID;
@RequestMapping("/api/admin/users") @RequestMapping("/api/admin/users")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@Tag(name = "Admin IAM", description = "User-Verwaltung und Freigabe-Workflow") @Tag(name = "Admin IAM", description = "User-Verwaltung und Freigabe-Workflow")
@RequiredArgsConstructor
public class AdminIamController { public class AdminIamController {
// private final UserManagementService userManagementService; private final UserRepository userRepository;
@GetMapping("/pending") @GetMapping("/pending")
@Operation(summary = "Lädt alle wartenden User", @Operation(summary = "Lädt alle wartenden User",
description = "Zeigt User im Status PENDING inkl. der Ergebnisse des EDA-Netz-Checks.") description = "Zeigt User im Status PENDING inkl. der Ergebnisse des EDA-Netz-Checks.")
public ResponseEntity<List<UserProfileResponse>> getPendingUsers() { public ResponseEntity<List<UserProfileResponse>> getPendingUsers() {
// Logik: Finde alle User mit status = PENDING
return ResponseEntity.ok(List.of(/* ... */));
//return ResponseEntity.ok(userRepository.findAllByStatus(RegistrationStatus.PENDING));
return null;
} }
@PostMapping("/{userId}/approve") @PostMapping("/{userId}/approve")

View File

@ -0,0 +1,21 @@
package at.mueller.eeg.backend.iam.mapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import static at.mueller.eeg.backend.iam.mapper.UserMapperAnnotations.PasswordHash;
@Component
@RequiredArgsConstructor
public class PasswordHashMapper {
private final PasswordEncoder passwordEncoder;
@PasswordHash
public String hashPassword(String password) {
return passwordEncoder.encode(password);
}
}

View File

@ -0,0 +1,28 @@
package at.mueller.eeg.backend.iam.mapper;
import at.mueller.eeg.backend.iam.api.dto.IamDtos;
import at.mueller.eeg.backend.iam.domain.User;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
@Mapper(
uses = PasswordHashMapper.class
)
public interface UserMapper {
@Mapping(target = "primaryAtNumber", ignore = true)
IamDtos.UserProfileResponse mapToDto(User user);
@Mapping(target = "status", constant = "PENDING")
@Mapping(target = "role", constant = "MEMBER")
@Mapping(target = "passwordHash", source = "password", qualifiedBy = UserMapperAnnotations.PasswordHash.class)
@Mapping(target = "id", ignore = true)
@Mapping(target = "iban", ignore = true)
@Mapping(target = "enabled", constant = "false")
@Mapping(target = "authorities", ignore = true)
User mapToEntity(IamDtos.RegistrationRequest request, @MappingTarget User user);
}

View File

@ -0,0 +1,19 @@
package at.mueller.eeg.backend.iam.mapper;
import lombok.experimental.UtilityClass;
import org.mapstruct.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@UtilityClass
public class UserMapperAnnotations {
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PasswordHash {
}
}

View File

@ -1,11 +1,15 @@
package at.mueller.eeg.backend.iam.repository; package at.mueller.eeg.backend.iam.repository;
import at.mueller.eeg.backend.iam.domain.RegistrationStatus;
import at.mueller.eeg.backend.iam.domain.User; import at.mueller.eeg.backend.iam.domain.User;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface UserRepository extends JpaRepository<User, String> { public interface UserRepository extends JpaRepository<User, String> {
Optional<User> findByEmail(String email); Optional<User> findByEmail(String email);
List<User> findAllByStatus(RegistrationStatus status);
} }

View File

@ -1,14 +1,15 @@
package at.mueller.eeg.backend.iam.service; package at.mueller.eeg.backend.iam.service;
import at.mueller.eeg.backend.iam.domain.RegistrationStatus; import at.mueller.eeg.backend.common.config.event.UserRegisteredEvent;
import at.mueller.eeg.backend.iam.domain.User; import at.mueller.eeg.backend.iam.domain.User;
import at.mueller.eeg.backend.iam.mapper.UserMapper;
import at.mueller.eeg.backend.iam.repository.UserRepository; import at.mueller.eeg.backend.iam.repository.UserRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtClaimsSet;
@ -27,8 +28,9 @@ public class AuthService {
private final AuthenticationManager authenticationManager; private final AuthenticationManager authenticationManager;
private final JwtEncoder jwtEncoder; private final JwtEncoder jwtEncoder;
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserMapper userMapper;
private final ApplicationEventPublisher eventPublisher;
/** /**
* Verarbeitet den Login und gibt den JWT-Token als String zurück. * Verarbeitet den Login und gibt den JWT-Token als String zurück.
@ -57,17 +59,10 @@ public class AuthService {
throw new RuntimeException("E-Mail already exists!"); throw new RuntimeException("E-Mail already exists!");
}); });
String hashedPassword = passwordEncoder.encode(request.password()); User newUser = userMapper.mapToEntity(request, new User());
User newUser = new User();
newUser.setEmail(request.email());
newUser.setPasswordHash(hashedPassword);
newUser.setFirstName(request.firstName());
newUser.setLastName(request.lastName());
newUser.setStatus(RegistrationStatus.PENDING);
newUser.setEnabled(false);
userRepository.save(newUser); userRepository.save(newUser);
eventPublisher.publishEvent(new UserRegisteredEvent(newUser.getId(), request.atNumber()));
} }
/** /**