chatwoot admin panel integration
All checks were successful
Deploy to VPS / deploy (push) Successful in 1m23s
All checks were successful
Deploy to VPS / deploy (push) Successful in 1m23s
This commit is contained in:
@@ -103,8 +103,8 @@ public class AdminSecurityConfig {
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/api/admin/login").permitAll()
|
||||
.requestMatchers("/api/admin/users/**").hasAnyRole("ADMIN", "GAME_ADMIN")
|
||||
.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")
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.honey.honey.controller;
|
||||
|
||||
import com.honey.honey.dto.AdminLoginRequest;
|
||||
import com.honey.honey.dto.AdminLoginResponse;
|
||||
import com.honey.honey.dto.ChatwootSessionRequest;
|
||||
import com.honey.honey.security.admin.JwtUtil;
|
||||
import com.honey.honey.service.AdminService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -19,6 +22,11 @@ import java.util.Optional;
|
||||
public class AdminLoginController {
|
||||
|
||||
private final AdminService adminService;
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
/** Shared secret with Chatwoot. Set via env CHATWOOT_INTEGRATION_SECRET (e.g. from VPS secret file). */
|
||||
@Value("${CHATWOOT_INTEGRATION_SECRET:}")
|
||||
private String chatwootIntegrationSecret;
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<?> login(@RequestBody AdminLoginRequest request) {
|
||||
@@ -47,5 +55,30 @@ public class AdminLoginController {
|
||||
role
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchanges a Chatwoot integration API key for an admin JWT with ROLE_TICKETS_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.
|
||||
*/
|
||||
@PostMapping("/chatwoot-session")
|
||||
public ResponseEntity<?> chatwootSession(@RequestBody ChatwootSessionRequest request) {
|
||||
if (request.getApiKey() == null || request.getApiKey().isBlank()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("apiKey is required");
|
||||
}
|
||||
if (chatwootIntegrationSecret == null || chatwootIntegrationSecret.isBlank()) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Chatwoot integration is not configured (CHATWOOT_INTEGRATION_SECRET)");
|
||||
}
|
||||
if (!chatwootIntegrationSecret.equals(request.getApiKey().trim())) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid apiKey");
|
||||
}
|
||||
String token = jwtUtil.generateTokenWithRole("__chatwoot__", "ROLE_TICKETS_SUPPORT");
|
||||
return ResponseEntity.ok(new AdminLoginResponse(
|
||||
token,
|
||||
"__chatwoot__",
|
||||
"ROLE_TICKETS_SUPPORT"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.honey.honey.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChatwootSessionRequest {
|
||||
/** API key shared with Chatwoot (Dashboard App). Must match CHATWOOT_INTEGRATION_SECRET on server. */
|
||||
private String apiKey;
|
||||
}
|
||||
@@ -43,11 +43,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
if (jwtUtil.validateToken(jwt, username)) {
|
||||
// Get admin from database to retrieve actual role
|
||||
String role = adminRepository.findByUsername(username)
|
||||
.map(Admin::getRole)
|
||||
.orElse("ROLE_ADMIN"); // Fallback to ROLE_ADMIN if not found
|
||||
|
||||
// If token has explicit role (e.g. Chatwoot integration), use it; otherwise load from DB
|
||||
String role = jwtUtil.getRoleFromToken(jwt);
|
||||
if (role == null || role.isBlank()) {
|
||||
role = adminRepository.findByUsername(username)
|
||||
.map(Admin::getRole)
|
||||
.orElse("ROLE_ADMIN");
|
||||
}
|
||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||
username,
|
||||
null,
|
||||
|
||||
@@ -33,6 +33,27 @@ public class JwtUtil {
|
||||
return createToken(claims, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a token with an explicit role (e.g. for Chatwoot integration).
|
||||
* Used when the subject is not a DB admin; the filter will use this role from the token.
|
||||
*/
|
||||
public String generateTokenWithRole(String subject, String role) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("role", role);
|
||||
return createToken(claims, subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the role from token claims, or null if not present.
|
||||
*/
|
||||
public String getRoleFromToken(String token) {
|
||||
try {
|
||||
return getClaimFromToken(token, claims -> claims.get("role", String.class));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String createToken(Map<String, Object> claims, String subject) {
|
||||
return Jwts.builder()
|
||||
.claims(claims)
|
||||
|
||||
@@ -121,6 +121,7 @@ app:
|
||||
secret: ${APP_ADMIN_JWT_SECRET:change-this-to-a-secure-random-string-in-production-min-32-characters}
|
||||
# JWT expiration time in milliseconds (default: 24 hours)
|
||||
expiration: ${APP_ADMIN_JWT_EXPIRATION:86400000}
|
||||
chatwoot-integration-secret: ${CHATWOOT_INTEGRATION_SECRET:}
|
||||
|
||||
# GeoIP configuration
|
||||
# Set GEOIP_DB_PATH environment variable to use external file (recommended for production)
|
||||
|
||||
Reference in New Issue
Block a user