Userrollen Verwaltung, Admin-Ablauf möglich, Rolle GOD
This commit is contained in:
parent
fcd94bfba0
commit
fd3f82659f
|
|
@ -2,12 +2,14 @@ package it.boergmann.tkdApp;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class TkdApiApplication {
|
@EnableScheduling
|
||||||
|
public class TkdAppApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(TkdApiApplication.class, args);
|
SpringApplication.run(TkdAppApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,20 +2,31 @@ package it.boergmann.tkdApp.config;
|
||||||
|
|
||||||
import it.boergmann.tkdApp.security.CustomUserDetailsService;
|
import it.boergmann.tkdApp.security.CustomUserDetailsService;
|
||||||
import it.boergmann.tkdApp.security.JwtAuthenticationFilter;
|
import it.boergmann.tkdApp.security.JwtAuthenticationFilter;
|
||||||
|
import jakarta.servlet.Filter;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
|
@ -25,18 +36,34 @@ public class SecurityConfig {
|
||||||
private final CustomUserDetailsService userDetailsService;
|
private final CustomUserDetailsService userDetailsService;
|
||||||
private final JwtAuthenticationFilter jwtAuthFilter;
|
private final JwtAuthenticationFilter jwtAuthFilter;
|
||||||
|
|
||||||
|
|
||||||
|
// 🧱 1. API Security (JWT, kein Redirect)
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
@Order(1)
|
||||||
|
public SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(csrf -> csrf.disable())
|
.securityMatcher("/api/**")
|
||||||
.sessionManagement(session -> session
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
|
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
)
|
.authorizeHttpRequests(authz -> authz
|
||||||
.authorizeHttpRequests(auth -> auth
|
|
||||||
.requestMatchers("/api/login", "/api/register").permitAll()
|
.requestMatchers("/api/login", "/api/register").permitAll()
|
||||||
.requestMatchers("/admin/**", "/api/users/**").hasRole("ADMIN")
|
.anyRequest().authenticated()
|
||||||
.requestMatchers("/api/**").authenticated()
|
)
|
||||||
.requestMatchers("/register", "/login", "/css/**", "/js/**").permitAll()
|
.exceptionHandling(ex -> ex
|
||||||
|
.authenticationEntryPoint((req, res, ex1) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED))
|
||||||
|
)
|
||||||
|
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🌐 2. Web Security (Form Login)
|
||||||
|
@Bean
|
||||||
|
@Order(2)
|
||||||
|
public SecurityFilterChain webSecurity(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests(authz -> authz
|
||||||
|
.requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.formLogin(form -> form
|
.formLogin(form -> form
|
||||||
|
|
@ -44,12 +71,36 @@ public class SecurityConfig {
|
||||||
.defaultSuccessUrl("/me", true)
|
.defaultSuccessUrl("/me", true)
|
||||||
.permitAll()
|
.permitAll()
|
||||||
)
|
)
|
||||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
.logout(logout -> logout
|
||||||
|
.logoutSuccessUrl("/login?logout")
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
.exceptionHandling(exception -> exception
|
||||||
|
.accessDeniedPage("/access-denied")
|
||||||
|
);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AccessDeniedHandler accessDeniedHandler() {
|
||||||
|
return new CustomAccessDeniedHandler(); // Siehe unten
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||||
|
|
||||||
|
// Leite weiter auf eine benutzerdefinierte Seite
|
||||||
|
response.sendRedirect("/access-denied");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AuthenticationProvider authProvider() {
|
public AuthenticationProvider authProvider() {
|
||||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
|
|
|
||||||
|
|
@ -49,25 +49,4 @@ public class AuthController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/register")
|
|
||||||
public ResponseEntity<?> register(@RequestBody @Valid RegisterRequest request) {
|
|
||||||
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
|
||||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Benutzername bereits vergeben");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userRepository.findAll().stream().anyMatch(u -> u.getEmail().equalsIgnoreCase(request.getEmail()))) {
|
|
||||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Email bereits registriert");
|
|
||||||
}
|
|
||||||
|
|
||||||
AppUser user = AppUser.builder()
|
|
||||||
.username(request.getUsername())
|
|
||||||
.password(passwordEncoder.encode(request.getPassword()))
|
|
||||||
.email(request.getEmail())
|
|
||||||
.roles(List.of(Role.NONE)) // 👉 noch nicht freigeschaltet
|
|
||||||
.build();
|
|
||||||
|
|
||||||
userRepository.save(user);
|
|
||||||
|
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body("Registrierung erfolgreich. Admin muss freischalten.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,30 +3,44 @@ package it.boergmann.tkdApp.controller.api;
|
||||||
import it.boergmann.tkdApp.domain.Role;
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
import it.boergmann.tkdApp.service.AppUserService;
|
import it.boergmann.tkdApp.service.AppUserService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
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.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/users")
|
@RequestMapping("/api/users")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasAnyRole('ADMIN', 'GOD')")
|
||||||
public class UserAdminApiController {
|
public class UserAdminApiController {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(UserAdminApiController.class);
|
||||||
private final AppUserService userService;
|
private final AppUserService userService;
|
||||||
|
|
||||||
@PostMapping("/{id}/enable")
|
@PostMapping("/{id}/role/add")
|
||||||
public ResponseEntity<?> enableUser(@PathVariable UUID id, @RequestParam Role role) {
|
public ResponseEntity<?> addRoleApi(@PathVariable UUID id,
|
||||||
userService.activateUser(id, role);
|
@RequestParam Role role,
|
||||||
return ResponseEntity.ok(Map.of("message", "Benutzer freigeschaltet"));
|
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validUntil) {
|
||||||
|
|
||||||
|
log.info("Role Add called");
|
||||||
|
userService.updateUserRole(id, role, validUntil);
|
||||||
|
Map<String, Object> response = new HashMap<>(Map.of("message", "Rolle hinzugefügt"));
|
||||||
|
Optional.ofNullable(validUntil)
|
||||||
|
.ifPresent(v -> response.put("expires", v));
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}/role")
|
@GetMapping("/test")
|
||||||
public ResponseEntity<?> updateRole(@PathVariable UUID id, @RequestParam Role role) {
|
public ResponseEntity<?> testApiAccess() {
|
||||||
userService.changeUserRole(id, role);
|
log.info("TEST CALLED");
|
||||||
return ResponseEntity.ok(Map.of("message", "Benutzerrolle aktualisiert"));
|
return ResponseEntity.ok("Du bist eingeloggt als: " + SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,23 @@ package it.boergmann.tkdApp.controller.api;
|
||||||
|
|
||||||
import it.boergmann.tkdApp.dto.AppUserResponse;
|
import it.boergmann.tkdApp.dto.AppUserResponse;
|
||||||
import it.boergmann.tkdApp.domain.AppUser;
|
import it.boergmann.tkdApp.domain.AppUser;
|
||||||
|
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||||
import it.boergmann.tkdApp.service.AppUserService;
|
import it.boergmann.tkdApp.service.AppUserService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api")
|
@RequestMapping("/api")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class UserController {
|
public class UserApiController {
|
||||||
|
|
||||||
private final AppUserService appUserService;
|
private final AppUserService appUserService;
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(UserAdminApiController.class);
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public ResponseEntity<AppUserResponse> getCurrentUser() {
|
public ResponseEntity<AppUserResponse> getCurrentUser() {
|
||||||
|
|
@ -20,8 +26,14 @@ public class UserController {
|
||||||
AppUserResponse response = new AppUserResponse(
|
AppUserResponse response = new AppUserResponse(
|
||||||
user.getId(),
|
user.getId(),
|
||||||
user.getUsername(),
|
user.getUsername(),
|
||||||
user.getRoles()
|
user.getAppUserRoles()
|
||||||
);
|
);
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/register")
|
||||||
|
public ResponseEntity<?> registerUser(@Valid @RequestBody RegisterRequest request) {
|
||||||
|
appUserService.registerNewUser(request);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,42 +5,48 @@ import it.boergmann.tkdApp.domain.Role;
|
||||||
import it.boergmann.tkdApp.service.AppUserService;
|
import it.boergmann.tkdApp.service.AppUserService;
|
||||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/admin")
|
@RequestMapping("/admin")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasAnyRole('ADMIN', 'GOD')")
|
||||||
public class AdminController {
|
public class AdminController {
|
||||||
|
|
||||||
private final AppUserRepository userRepository;
|
private final AppUserRepository userRepository;
|
||||||
private final AppUserService userService;
|
private final AppUserService userService;
|
||||||
|
|
||||||
@GetMapping("/users")
|
@GetMapping("/users")
|
||||||
public String showPendingUsers(Model model) {
|
public String showAllUsers(Model model) {
|
||||||
List<AppUser> pendingUsers = userRepository.findAll().stream()
|
List<AppUser> allUsers = userRepository.findAll();
|
||||||
.filter(u -> u.getRoles().contains(Role.NONE))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
model.addAttribute("users", pendingUsers);
|
model.addAttribute("users", allUsers);
|
||||||
|
model.addAttribute("roles", Role.values()); // für Dropdown
|
||||||
return "admin_users";
|
return "admin_users";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/users/{id}/enable")
|
@PostMapping("/users/update")
|
||||||
public String enableUser(@PathVariable UUID id, @RequestParam Role role) {
|
public String updateUserRoles(@RequestParam UUID userId,
|
||||||
userService.activateUser(id, role);
|
@RequestParam Role role,
|
||||||
|
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validUntil) {
|
||||||
|
|
||||||
|
userService.updateUserRole(userId, role, validUntil);
|
||||||
return "redirect:/admin/users";
|
return "redirect:/admin/users";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/users/{id}/role")
|
@PostMapping("/users/{id}/role/add")
|
||||||
public String changeUserRole(@PathVariable UUID id, @RequestParam Role role) {
|
public String addRole(@PathVariable UUID id,
|
||||||
userService.changeUserRole(id, role);
|
@RequestParam Role role,
|
||||||
|
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime validUntil) {
|
||||||
|
userService.updateUserRole(id, role, validUntil);
|
||||||
return "redirect:/admin/users";
|
return "redirect:/admin/users";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||||
import it.boergmann.tkdApp.domain.AppUser;
|
import it.boergmann.tkdApp.domain.AppUser;
|
||||||
import it.boergmann.tkdApp.domain.Role;
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||||
|
import it.boergmann.tkdApp.service.AppUserService;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
@ -16,9 +17,9 @@ import java.util.List;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class RegisterController {
|
public class UserController {
|
||||||
|
|
||||||
private final AppUserRepository userRepository;
|
private final AppUserRepository userRepository;
|
||||||
|
private final AppUserService appUserService;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@GetMapping("/register")
|
@GetMapping("/register")
|
||||||
|
|
@ -29,26 +30,7 @@ public class RegisterController {
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public String handleRegister(@ModelAttribute("user") @Valid RegisterRequest request, BindingResult result, Model model) {
|
public String handleRegister(@ModelAttribute("user") @Valid RegisterRequest request, BindingResult result, Model model) {
|
||||||
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
appUserService.registerNewUser(request);
|
||||||
result.rejectValue("username", "error.user", "Benutzername existiert bereits");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userRepository.findAll().stream().anyMatch(u -> u.getEmail().equalsIgnoreCase(request.getEmail()))) {
|
|
||||||
result.rejectValue("email", "error.email", "Email-Adresse ist bereits registriert");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.hasErrors()) {
|
|
||||||
return "register";
|
|
||||||
}
|
|
||||||
|
|
||||||
AppUser newUser = AppUser.builder()
|
|
||||||
.username(request.getUsername())
|
|
||||||
.email(request.getEmail())
|
|
||||||
.password(passwordEncoder.encode(request.getPassword()))
|
|
||||||
.roles(List.of(Role.NONE))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
userRepository.save(newUser);
|
|
||||||
|
|
||||||
model.addAttribute("message", "Registrierung erfolgreich! Bitte auf Freischaltung durch Admin warten.");
|
model.addAttribute("message", "Registrierung erfolgreich! Bitte auf Freischaltung durch Admin warten.");
|
||||||
return "login";
|
return "login";
|
||||||
|
|
@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|
@ -24,4 +26,10 @@ public class WebController {
|
||||||
model.addAttribute("user", user);
|
model.addAttribute("user", user);
|
||||||
return "profile"; // src/main/resources/templates/profile.html
|
return "profile"; // src/main/resources/templates/profile.html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/access-denied")
|
||||||
|
public String accessDenied() {
|
||||||
|
return "error/403";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import lombok.*;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -27,19 +29,20 @@ public class AppUser implements UserDetails{
|
||||||
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||||
@Enumerated(EnumType.STRING)
|
@Builder.Default
|
||||||
private List<Role> roles;
|
private List<AppUserRole> appUserRoles = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return roles.stream()
|
LocalDateTime now = LocalDateTime.now();
|
||||||
.map(role ->(GrantedAuthority)() -> "ROLE_" +role)
|
return appUserRoles.stream()
|
||||||
|
.filter(r -> r.getValidUntil() == null || r.getValidUntil().isAfter(now)) // nur gültige Rollen
|
||||||
|
.map(r -> (GrantedAuthority) () -> "ROLE_" + r.getRole().name())
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean isAccountNonExpired() { return true; }
|
@Override public boolean isAccountNonExpired() { return true; }
|
||||||
@Override public boolean isAccountNonLocked() { return true; }
|
@Override public boolean isAccountNonLocked() { return true; }
|
||||||
@Override public boolean isCredentialsNonExpired() { return true; }
|
@Override public boolean isCredentialsNonExpired() { return true; }
|
||||||
@Override public boolean isEnabled() { return true; }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package it.boergmann.tkdApp.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class AppUserRole {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "user_id", nullable = false)
|
||||||
|
private AppUser user;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Role role;
|
||||||
|
|
||||||
|
private LocalDateTime validUntil; // 🕒 Ablaufzeitpunkt, optional
|
||||||
|
}
|
||||||
|
|
@ -3,5 +3,6 @@ package it.boergmann.tkdApp.domain;
|
||||||
public enum Role {
|
public enum Role {
|
||||||
NONE,
|
NONE,
|
||||||
USER,
|
USER,
|
||||||
ADMIN
|
ADMIN,
|
||||||
|
GOD
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package it.boergmann.tkdApp.dto;
|
package it.boergmann.tkdApp.dto;
|
||||||
|
|
||||||
|
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||||
import it.boergmann.tkdApp.domain.Role;
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
@ -12,5 +13,5 @@ import java.util.UUID;
|
||||||
public class AppUserResponse {
|
public class AppUserResponse {
|
||||||
private UUID id;
|
private UUID id;
|
||||||
private String username;
|
private String username;
|
||||||
private List<Role> roles;
|
private List<AppUserRole> roles;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,14 @@ import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class RegisterRequest {
|
public class RegisterRequest {
|
||||||
@NotBlank
|
@NotBlank(message = "Username darf nicht leer sein")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank(message = "Passwort darf nicht leer sein")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Email
|
@NotBlank(message = "Email darf nicht leer sein")
|
||||||
@NotBlank
|
@Email(message = "Ungültige E-Mail-Adresse")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package it.boergmann.tkdApp.init;
|
||||||
|
|
||||||
|
import it.boergmann.tkdApp.domain.AppUser;
|
||||||
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
|
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||||
|
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||||
|
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StartupAdminInitializer {
|
||||||
|
|
||||||
|
private final AppUserRepository userRepository;
|
||||||
|
private final AppUserRoleRepository roleRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplicationRunner initAdminUser() {
|
||||||
|
return args -> {
|
||||||
|
boolean adminExists = userRepository.findAll().stream()
|
||||||
|
.flatMap(user -> user.getAppUserRoles().stream())
|
||||||
|
.anyMatch(role -> role.getRole() == Role.ADMIN &&
|
||||||
|
(role.getValidUntil() == null || role.getValidUntil().isAfter(LocalDateTime.now())));
|
||||||
|
|
||||||
|
if (!adminExists) {
|
||||||
|
System.out.println("⚠️ Kein aktiver Admin gefunden. Erstelle Standard-Admin-User...");
|
||||||
|
|
||||||
|
AppUser admin = AppUser.builder()
|
||||||
|
.email("admin@boergmann.it")
|
||||||
|
.username("admin")
|
||||||
|
.password(passwordEncoder.encode("Dimrb#7361"))
|
||||||
|
.appUserRoles(List.of()) // Initial leer
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AppUser savedUser = userRepository.save(admin);
|
||||||
|
|
||||||
|
AppUserRole adminRole = AppUserRole.builder()
|
||||||
|
.user(savedUser)
|
||||||
|
.role(Role.GOD)
|
||||||
|
.validUntil(null) // dauerhaft gültig
|
||||||
|
.build();
|
||||||
|
|
||||||
|
roleRepository.save(adminRole);
|
||||||
|
|
||||||
|
System.out.println("✅ Admin-User admin/admin123 wurde erfolgreich angelegt.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package it.boergmann.tkdApp.jobs;
|
||||||
|
|
||||||
|
import it.boergmann.tkdApp.domain.AppUser;
|
||||||
|
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||||
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
|
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||||
|
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RoleCleanupScheduler {
|
||||||
|
|
||||||
|
private final AppUserRoleRepository appUserRoleRepository;
|
||||||
|
private final AppUserRepository appUserRepository;
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 * * * *") // stündlich z. B.
|
||||||
|
public void downgradeExpiredAdmins() {
|
||||||
|
List<AppUserRole> expiredAdmins = appUserRoleRepository.findExpiredAppUserRole(Role.ADMIN, LocalDateTime.now());
|
||||||
|
|
||||||
|
for (AppUserRole expired : expiredAdmins) {
|
||||||
|
appUserRoleRepository.delete(expired);
|
||||||
|
AppUser user = expired.getUser();
|
||||||
|
|
||||||
|
// Nur wenn keine andere Rolle vorhanden, neue Rolle setzen
|
||||||
|
if (user.getAppUserRoles().isEmpty()) {
|
||||||
|
var fallback = AppUserRole.builder()
|
||||||
|
.user(user)
|
||||||
|
.role(Role.USER)
|
||||||
|
.build();
|
||||||
|
user.getAppUserRoles().add(fallback);
|
||||||
|
appUserRepository.save(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,20 @@
|
||||||
package it.boergmann.tkdApp.repository;
|
package it.boergmann.tkdApp.repository;
|
||||||
|
|
||||||
import it.boergmann.tkdApp.domain.AppUser;
|
import it.boergmann.tkdApp.domain.AppUser;
|
||||||
|
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||||
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public interface AppUserRepository extends JpaRepository<AppUser, UUID> {
|
public interface AppUserRepository extends JpaRepository<AppUser, UUID> {
|
||||||
|
boolean existsByUsername(String username); // 🔥 hier hinzufügen!
|
||||||
|
boolean existsByEmail(String email);
|
||||||
Optional<AppUser> findByUsername(String username);
|
Optional<AppUser> findByUsername(String username);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package it.boergmann.tkdApp.repository;
|
||||||
|
|
||||||
|
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||||
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface AppUserRoleRepository extends JpaRepository<AppUserRole, UUID> {
|
||||||
|
@Query("SELECT r FROM AppUserRole r WHERE r.role = :role AND r.validUntil IS NOT NULL AND r.validUntil < :now")
|
||||||
|
List<AppUserRole> findExpiredAppUserRole(@Param("role") Role role, @Param("now") LocalDateTime now);
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||||
AppUser user = userRepository.findByUsername(username)
|
AppUser user = userRepository.findByUsername(username)
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
|
||||||
|
|
||||||
if (user.getRoles() == null || user.getRoles().contains(Role.NONE) || user.getRoles().isEmpty()) {
|
if (user.getAppUserRoles() == null || user.getAppUserRoles().contains(Role.NONE) || user.getAppUserRoles().isEmpty()) {
|
||||||
throw new UsernameNotFoundException("User has no active roles");
|
throw new UsernameNotFoundException("User has no active roles");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,17 @@ package it.boergmann.tkdApp.service;
|
||||||
|
|
||||||
import it.boergmann.tkdApp.domain.AppUser;
|
import it.boergmann.tkdApp.domain.AppUser;
|
||||||
import it.boergmann.tkdApp.domain.Role;
|
import it.boergmann.tkdApp.domain.Role;
|
||||||
|
import it.boergmann.tkdApp.domain.AppUserRole;
|
||||||
|
import it.boergmann.tkdApp.dto.RegisterRequest;
|
||||||
import it.boergmann.tkdApp.repository.AppUserRepository;
|
import it.boergmann.tkdApp.repository.AppUserRepository;
|
||||||
|
import it.boergmann.tkdApp.repository.AppUserRoleRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.time.LocalDateTime;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
|
@ -16,6 +20,8 @@ import java.util.UUID;
|
||||||
public class AppUserService {
|
public class AppUserService {
|
||||||
|
|
||||||
private final AppUserRepository userRepository;
|
private final AppUserRepository userRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final AppUserRoleRepository roleAssignmentRepository;
|
||||||
|
|
||||||
public AppUser getCurrentUser() {
|
public AppUser getCurrentUser() {
|
||||||
String username = SecurityContextHolder.getContext().getAuthentication().getName();
|
String username = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
|
@ -23,23 +29,52 @@ public class AppUserService {
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void activateUser(UUID userId, Role newRole) {
|
public void updateUserRole(UUID userId, Role newRole, LocalDateTime validUntil) {
|
||||||
|
System.out.println(userId.toString());
|
||||||
|
System.out.println(newRole.toString());
|
||||||
AppUser user = userRepository.findById(userId)
|
AppUser user = userRepository.findById(userId)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||||
|
|
||||||
if (!user.getRoles().contains(Role.NONE)) {
|
if (user.getAppUserRoles().stream().anyMatch(ra -> ra.getRole() == Role.GOD)) {
|
||||||
throw new IllegalStateException("User is already activated");
|
throw new IllegalStateException("GOD-Nutzer dürfen nicht geändert werden.");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setRoles(List.of(newRole)); // Nur eine Rolle zuweisen – oder erweitern
|
// Alte Rollen entfernen
|
||||||
|
user.getAppUserRoles().clear();
|
||||||
|
|
||||||
|
// Neue Rolle zuweisen
|
||||||
|
var roleAssignment = AppUserRole.builder()
|
||||||
|
.user(user)
|
||||||
|
.role(newRole)
|
||||||
|
.validUntil(validUntil)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
user.getAppUserRoles().add(roleAssignment);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeUserRole(UUID userId, Role newRole) {
|
public AppUser registerNewUser(RegisterRequest newUser) {
|
||||||
AppUser user = userRepository.findById(userId)
|
if (userRepository.existsByUsername(newUser.getUsername()) || userRepository.existsByEmail(newUser.getEmail())) {
|
||||||
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
throw new IllegalArgumentException("Benutzername oder E-Mail bereits vergeben");
|
||||||
|
}
|
||||||
|
|
||||||
user.setRoles(List.of(newRole)); // 👈 Rolle hart überschrieben (alternativ: erweitern)
|
var user = AppUser.builder()
|
||||||
userRepository.save(user);
|
.username(newUser.getUsername())
|
||||||
|
.email(newUser.getEmail())
|
||||||
|
.password(passwordEncoder.encode(newUser.getPassword()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
var savedUser = userRepository.save(user);
|
||||||
|
|
||||||
|
// Setze initiale Rolle: NONE
|
||||||
|
var roleAssignment = AppUserRole.builder()
|
||||||
|
.user(savedUser)
|
||||||
|
.role(Role.NONE)
|
||||||
|
.validUntil(null)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
roleAssignmentRepository.save(roleAssignment);
|
||||||
|
|
||||||
|
return savedUser;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ spring:
|
||||||
password: Sanctuary1-Crane-Erupt-Bogged
|
password: Sanctuary1-Crane-Erupt-Bogged
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: update
|
ddl-auto: create-drop
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,36 @@
|
||||||
<!DOCTYPE html>
|
<h1>Benutzerverwaltung</h1>
|
||||||
<html xmlns:th="http://www.thymeleaf.org">
|
<table>
|
||||||
<head>
|
<thead>
|
||||||
<title>Benutzer freischalten</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Offene Registrierungen</h1>
|
|
||||||
|
|
||||||
<table border="1">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Benutzername</th>
|
<th>Username</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
|
<th>Aktuelle Rolle</th>
|
||||||
|
<th>Neue Rolle</th>
|
||||||
|
<th>Gültig bis (nur für ADMIN)</th>
|
||||||
<th>Aktion</th>
|
<th>Aktion</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
<tr th:each="user : ${users}">
|
<tr th:each="user : ${users}">
|
||||||
<td th:text="${user.username}">Benutzer</td>
|
<form th:action="@{/admin/users/update}" method="post">
|
||||||
<td th:text="${user.email}">Email</td>
|
<input type="hidden" name="userId" th:value="${user.id}"/>
|
||||||
<td>
|
|
||||||
<form th:action="@{'/admin/users/' + ${user.id} + '/role'}" method="post">
|
<td th:text="${user.username}"></td>
|
||||||
|
<td th:text="${user.email}"></td>
|
||||||
|
<td>
|
||||||
|
<span th:each="ra : ${user.appUserRoles}"
|
||||||
|
th:text="${ra.role} + ' (bis ' + ${ra.validUntil} + ')'"></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
<select name="role">
|
<select name="role">
|
||||||
<option th:selected="${user.roles.contains(T(it.boergmann.tkdApp.domain.Role).USER)}" value="USER">USER</option>
|
<option th:each="r : ${roles}"
|
||||||
<option th:selected="${user.roles.contains(T(it.boergmann.tkdApp.domain.Role).ADMIN)}" value="ADMIN">ADMIN</option>
|
th:value="${r}"
|
||||||
<option th:selected="${user.roles.contains(T(it.boergmann.tkdApp.domain.Role).NONE)}" value="NONE">NONE</option>
|
th:text="${r}"></option>
|
||||||
</select>
|
</select>
|
||||||
<button type="submit">Rolle ändern</button>
|
</td>
|
||||||
</form>
|
<td><input type="datetime-local" name="validUntil"/></td>
|
||||||
</td>
|
<td><button type="submit">Ändern</button></td>
|
||||||
|
</form>
|
||||||
</tr>
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!-- src/main/resources/templates/error/403.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<title>Kein Zugriff</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🚫 Zugriff verweigert</h1>
|
||||||
|
<p>Du hast leider keine Berechtigung für diese Seite.</p>
|
||||||
|
<a href="/static" th:href="@{/static}">Zur Startseite</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Willkommen, <span th:text="${user.username}">Nutzer</span>!</h1>
|
<h1>Willkommen, <span th:text="${user.username}">Nutzer</span>!</h1>
|
||||||
<p>Deine Rollen: <span th:text="${user.roles}">[]</span></p>
|
<p>Deine Rollen: <span th:each="ra : ${user.appUserRoles}"
|
||||||
|
th:text="${ra.role} + ' (bis ' + ${ra.validUntil} + ')'"></span>
|
||||||
|
</td></p>
|
||||||
<a href="/logout">Logout</a>
|
<a href="/logout">Logout</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue