chatwoot admin panel fixes
All checks were successful
Deploy to VPS / deploy (push) Successful in 1m16s

This commit is contained in:
Tihon
2026-03-16 18:48:46 +02:00
parent 2779e7a1c1
commit 0c0bb5a5bc
8 changed files with 43 additions and 43 deletions

View File

@@ -104,13 +104,13 @@ public class AdminSecurityConfig {
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/login", "/api/admin/chatwoot-session").permitAll()
.requestMatchers("/api/admin/users/**").hasAnyRole("ADMIN", "GAME_ADMIN", "TICKETS_SUPPORT")
.requestMatchers("/api/admin/payments/**").hasAnyRole("ADMIN", "GAME_ADMIN")
.requestMatchers("/api/admin/payouts/**").hasAnyRole("ADMIN", "PAYOUT_SUPPORT", "GAME_ADMIN")
.requestMatchers("/api/admin/rooms/**").hasAnyRole("ADMIN", "GAME_ADMIN")
.requestMatchers("/api/admin/configurations/**").hasAnyRole("ADMIN", "GAME_ADMIN")
.requestMatchers("/api/admin/tickets/**").hasAnyRole("ADMIN", "TICKETS_SUPPORT", "GAME_ADMIN")
.requestMatchers("/api/admin/quick-answers/**").hasAnyRole("ADMIN", "TICKETS_SUPPORT", "GAME_ADMIN")
.requestMatchers("/api/admin/users/**").hasAnyRole("ADMIN", "SUPPORT")
.requestMatchers("/api/admin/payments/**").hasAnyRole("ADMIN", "SUPPORT")
.requestMatchers("/api/admin/payouts/**").hasAnyRole("ADMIN", "SUPPORT")
.requestMatchers("/api/admin/rooms/**").hasAnyRole("ADMIN")
.requestMatchers("/api/admin/configurations/**").hasAnyRole("ADMIN")
.requestMatchers("/api/admin/tickets/**").hasAnyRole("ADMIN")
.requestMatchers("/api/admin/quick-answers/**").hasAnyRole("ADMIN")
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().denyAll()
)

View File

@@ -57,7 +57,7 @@ public class AdminLoginController {
}
/**
* Exchanges a Chatwoot integration API key for an admin JWT with ROLE_TICKETS_SUPPORT.
* Exchanges a Chatwoot integration API key for an admin JWT with ROLE_SUPPORT.
* Used when the admin panel is embedded in Chatwoot as a Dashboard App iframe.
* API key must match CHATWOOT_INTEGRATION_SECRET on the server.
*/
@@ -73,11 +73,11 @@ public class AdminLoginController {
if (!chatwootIntegrationSecret.equals(request.getApiKey().trim())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid apiKey");
}
String token = jwtUtil.generateTokenWithRole("__chatwoot__", "ROLE_TICKETS_SUPPORT");
String token = jwtUtil.generateTokenWithRole("__chatwoot__", "ROLE_SUPPORT");
return ResponseEntity.ok(new AdminLoginResponse(
token,
"__chatwoot__",
"ROLE_TICKETS_SUPPORT"
"ROLE_SUPPORT"
));
}
}

View File

@@ -7,8 +7,6 @@ import com.honey.honey.repository.PaymentRepository;
import com.honey.honey.repository.UserARepository;
import com.honey.honey.repository.UserDRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -16,6 +14,8 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -31,18 +31,18 @@ import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/admin/payments")
@RequiredArgsConstructor
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public class AdminPaymentController {
private final PaymentRepository paymentRepository;
private final UserARepository userARepository;
private final UserDRepository userDRepository;
private boolean isGameAdmin() {
private static boolean isSupport() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || auth.getAuthorities() == null) return false;
return auth.getAuthorities().stream()
.anyMatch(a -> "ROLE_GAME_ADMIN".equals(a.getAuthority()));
.anyMatch(a -> "ROLE_SUPPORT".equals(a.getAuthority()));
}
@GetMapping
@@ -70,7 +70,7 @@ public class AdminPaymentController {
Pageable pageable = PageRequest.of(page, size, sort);
List<Integer> masterIds = isGameAdmin() ? userDRepository.findMasterUserIds() : List.of();
List<Integer> masterIds = isSupport() ? userDRepository.findMasterUserIds() : List.of();
// Build specification
Specification<Payment> spec = (root, query, cb) -> {

View File

@@ -35,7 +35,7 @@ import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/admin/payouts")
@RequiredArgsConstructor
@PreAuthorize("hasAnyRole('ADMIN', 'PAYOUT_SUPPORT', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public class AdminPayoutController {
private final PayoutRepository payoutRepository;
@@ -45,11 +45,11 @@ public class AdminPayoutController {
private final PayoutService payoutService;
private final LocalizationService localizationService;
private boolean isGameAdmin() {
private static boolean isSupport() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || auth.getAuthorities() == null) return false;
return auth.getAuthorities().stream()
.anyMatch(a -> "ROLE_GAME_ADMIN".equals(a.getAuthority()));
.anyMatch(a -> "ROLE_SUPPORT".equals(a.getAuthority()));
}
@GetMapping
@@ -74,7 +74,7 @@ public class AdminPayoutController {
Pageable pageable = PageRequest.of(page, size, sort);
List<Integer> masterIds = isGameAdmin() ? userDRepository.findMasterUserIds() : List.of();
List<Integer> masterIds = isSupport() ? userDRepository.findMasterUserIds() : List.of();
// Build specification
Specification<Payout> spec = (root, query, cb) -> {
@@ -156,7 +156,7 @@ public class AdminPayoutController {
}
@PostMapping("/{id}/complete")
@PreAuthorize("hasAnyRole('ADMIN', 'PAYOUT_SUPPORT')")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> completePayout(@PathVariable Long id) {
try {
Optional<Payout> payoutOpt = payoutRepository.findById(id);
@@ -179,7 +179,7 @@ public class AdminPayoutController {
}
@PostMapping("/{id}/cancel")
@PreAuthorize("hasAnyRole('ADMIN', 'PAYOUT_SUPPORT')")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> cancelPayout(@PathVariable Long id) {
try {
Optional<Payout> payoutOpt = payoutRepository.findById(id);

View File

@@ -35,7 +35,7 @@ import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/admin/tickets")
@RequiredArgsConstructor
@PreAuthorize("hasAnyRole('ADMIN', 'TICKETS_SUPPORT', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN')")
public class AdminSupportTicketController {
private final SupportTicketRepository supportTicketRepository;

View File

@@ -25,6 +25,13 @@ import java.util.Set;
@RequiredArgsConstructor
public class AdminUserController {
private static boolean isSupport() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || auth.getAuthorities() == null) return false;
return auth.getAuthorities().stream()
.anyMatch(a -> "ROLE_SUPPORT".equals(a.getAuthority()));
}
/** Sortable fields: UserA properties plus UserB/UserD (handled via custom query in service). */
private static final Set<String> SORTABLE_FIELDS = Set.of(
"id", "screenName", "telegramId", "telegramName", "isPremium",
@@ -37,15 +44,8 @@ public class AdminUserController {
private final AdminUserService adminUserService;
private final UserService userService;
private boolean isGameAdmin() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || auth.getAuthorities() == null) return false;
return auth.getAuthorities().stream()
.anyMatch(a -> "ROLE_GAME_ADMIN".equals(a.getAuthority()));
}
@GetMapping
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<Map<String, Object>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "50") int size,
@@ -85,7 +85,7 @@ public class AdminUserController {
Long balanceMinBigint = balanceMin != null ? balanceMin * 1000000L : null;
Long balanceMaxBigint = balanceMax != null ? balanceMax * 1000000L : null;
boolean excludeMasters = isGameAdmin();
boolean excludeMasters = isSupport();
Page<AdminUserDto> dtoPage = adminUserService.getUsers(
pageable,
search,
@@ -124,7 +124,7 @@ public class AdminUserController {
* Chatwoot (Telegram channel) often uses Telegram user ID as contact identifier.
*/
@GetMapping("/by-telegram-id")
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN', 'TICKETS_SUPPORT')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<Map<String, Integer>> getUserByTelegramId(@RequestParam("telegram_id") Long telegramId) {
if (telegramId == null) {
return ResponseEntity.badRequest().build();
@@ -135,9 +135,9 @@ public class AdminUserController {
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN', 'TICKETS_SUPPORT')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<AdminUserDetailDto> getUserDetail(@PathVariable Integer id) {
AdminUserDetailDto userDetail = adminUserService.getUserDetail(id, isGameAdmin());
AdminUserDetailDto userDetail = adminUserService.getUserDetail(id, isSupport());
if (userDetail == null) {
return ResponseEntity.notFound().build();
}
@@ -145,7 +145,7 @@ public class AdminUserController {
}
@GetMapping("/{id}/transactions")
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<Map<String, Object>> getUserTransactions(
@PathVariable Integer id,
@RequestParam(defaultValue = "0") int page,
@@ -167,7 +167,7 @@ public class AdminUserController {
}
@GetMapping("/{id}/payments")
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<Map<String, Object>> getUserPayments(
@PathVariable Integer id,
@RequestParam(defaultValue = "0") int page,
@@ -190,7 +190,7 @@ public class AdminUserController {
}
@GetMapping("/{id}/payouts")
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<Map<String, Object>> getUserPayouts(
@PathVariable Integer id,
@RequestParam(defaultValue = "0") int page,
@@ -213,14 +213,14 @@ public class AdminUserController {
}
@GetMapping("/{id}/tasks")
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<Map<String, Object>> getUserTasks(@PathVariable Integer id) {
Map<String, Object> tasks = adminUserService.getUserTasks(id);
return ResponseEntity.ok(tasks);
}
@PatchMapping("/{id}/ban")
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<?> setUserBanned(
@PathVariable Integer id,
@RequestBody Map<String, Boolean> body) {
@@ -237,7 +237,7 @@ public class AdminUserController {
}
@PatchMapping("/{id}/withdrawals-enabled")
@PreAuthorize("hasAnyRole('ADMIN', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPPORT')")
public ResponseEntity<?> setWithdrawalsEnabled(
@PathVariable Integer id,
@RequestBody Map<String, Boolean> body) {

View File

@@ -21,7 +21,7 @@ import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/admin/quick-answers")
@RequiredArgsConstructor
@PreAuthorize("hasAnyRole('ADMIN', 'TICKETS_SUPPORT', 'GAME_ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN')")
public class QuickAnswerController {
private final QuickAnswerRepository quickAnswerRepository;

View File

@@ -88,7 +88,7 @@ public interface UserDRepository extends JpaRepository<UserD, Integer> {
List<UserD> findAllMasters();
/**
* IDs of users who are Masters (id = master_id and master_id > 0). Used to exclude them from GAME_ADMIN views.
* IDs of users who are Masters (id = master_id and master_id > 0).
*/
@Query("SELECT d.id FROM UserD d WHERE d.id = d.masterId AND d.masterId > 0")
List<Integer> findMasterUserIds();