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 trongCheatController, thêm:Xác nhận@Autowired private SlackNotificationService slackNotificationService;SlackNotificationServicetồn tại trong moduleadminhoặccommon. Nếu chỉ có trong module khác, kiểm tra xem moduleadmincó import dependency đó không (xempom.xmlcủ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ụ:Kiểm traprivate String getCurrentAdminEmail() { return ((AdminUserDetails) SecurityContextHolder.getContext() .getAuthentication().getPrincipal()).getEmail(); }AdminUserDetailscó fieldemailkhông. Nếu không, dùnggetUsername()thay thế.Injection note —
IPUtils: Xác nhậnIPUtils(hoặcIpUtils) 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.javabuild thành công;SlackNotificationService,IPUtils,HttpServletRequest, vàInstantđều resolve đúng - [ ] Slack alert gửi: Gọi
POST /api/admin/cheats/add-coinvớ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:
ipfield trong alert phản ánh IP thực của caller (không phải127.0.0.1hoặ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— mockSlackNotificationService, verifysendAlert()được gọi với đúng message format sau mỗi successfuladdCoin
Files to Modify
admin/controller/cheat/CheatController.java