Skip to content

Task 2-2: Slack Alert khi Cheat Endpoint Được Gọi

Phase: 2 - Observability Priority: Medium Depends on: task-1-1 Reference: SPEC.md — xem mục 4 (ADM-F-06 Cheat detection) và mục 2.12 (CheatController)

Background

CheatController.addCoin() là high-risk operation. Ngay cả khi đã restrict bằng RBAC (task-1-1), cần có realtime alert để ops team biết khi endpoint này được gọi.

Tasks

File: admin/controller/cheat/CheatController.java

  • [ ] Verify SlackNotificationService đã được inject (module admin có dùng không?)
  • [ ] Thêm Slack notification với đầy đủ context:

    @PostMapping("/add-coin")
    public Result<?> addCoin(@RequestBody AddCoinUserRequest request, HttpServletRequest httpRequest) {
        String adminId = getCurrentAdminId();
        String adminEmail = getCurrentAdminEmail();
        String ip = IPUtils.getClientIp(httpRequest);
    
        slackNotificationService.sendAlert(String.format(
            "[CHEAT_ALERT] addCoin called!\n" +
            "Admin: %s (%s)\n" +
            "Target userId: %s\n" +
            "Amount: %s\n" +
            "IP: %s\n" +
            "Time: %s",
            adminEmail, adminId,
            request.getUserId(),
            request.getAmount(),
            ip,
            Instant.now()
        ));
    
        // ... existing logic ...
    }
    

    Injection note — SlackNotificationService: Nếu chưa được inject trong CheatController, thêm:

    @Autowired
    private SlackNotificationService slackNotificationService;
    
    Xác nhận SlackNotificationService tồn tại trong module admin hoặc common. Nếu chỉ có trong module khác, kiểm tra xem module admin có import dependency đó không (xem pom.xml của module admin). Nếu không, cần tạo wrapper hoặc thêm dependency.

    Injection note — getCurrentAdminEmail(): Method này cần được cung cấp bởi base controller hoặc từ AdminUserDetails. Ví dụ:

    private String getCurrentAdminEmail() {
        return ((AdminUserDetails) SecurityContextHolder.getContext()
            .getAuthentication().getPrincipal()).getEmail();
    }
    
    Kiểm tra AdminUserDetails có field email không. Nếu không, dùng getUsername() thay thế.

    Injection note — IPUtils: Xác nhận IPUtils (hoặc IpUtils) tồn tại trong project. Nếu chưa có:

    public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip.split(",")[0].trim(); // lấy IP đầu tiên nếu có nhiều proxy
    }
    

    Import HttpServletRequest:

    import javax.servlet.http.HttpServletRequest; // hoặc jakarta.servlet nếu Spring Boot 3+
    

    Import Instant:

    import java.time.Instant;
    

  • [ ] Test: gọi endpoint → Slack channel nhận alert trong vài giây

Verification / Acceptance Criteria

  • [ ] Code compiles: CheatController.java build thành công; SlackNotificationService, IPUtils, HttpServletRequest, và Instant đều resolve đúng
  • [ ] Slack alert gửi: Gọi POST /api/admin/cheats/add-coin với SUPER_ADMIN JWT → Slack channel nhận message trong vòng vài giây
  • [ ] Alert message đầy đủ: Message Slack chứa adminEmail, adminId, userId, amount, IP address, và timestamp
  • [ ] IP address đúng: ip field trong alert phản ánh IP thực của caller (không phải 127.0.0.1 hoặc internal proxy IP)
  • [ ] Alert KHÔNG gửi nếu call fail: Nếu non-SUPER_ADMIN gọi → 403 và Slack không nhận alert (do Spring Security block trước khi vào method body)
  • [ ] Test class tham khảo: CheatControllerTest — mock SlackNotificationService, verify sendAlert() được gọi với đúng message format sau mỗi successful addCoin

Files to Modify

  • admin/controller/cheat/CheatController.java