feat(audit): add localized security audit logs (#5462)
This commit is contained in:
+69
-13
@@ -90,6 +90,43 @@ func Login(c *gin.Context) {
|
||||
setupLogin(&user, c)
|
||||
}
|
||||
|
||||
// loginMethodFromContext 根据请求路径推导登录方式,用于登录审计日志。
|
||||
func loginMethodFromContext(c *gin.Context) string {
|
||||
switch c.FullPath() {
|
||||
case "/api/user/login":
|
||||
return "password"
|
||||
case "/api/user/login/2fa":
|
||||
return "2fa"
|
||||
case "/api/user/passkey/login/finish":
|
||||
return "passkey"
|
||||
case "/api/oauth/wechat":
|
||||
return "wechat"
|
||||
case "/api/oauth/telegram/login":
|
||||
return "telegram"
|
||||
case "/api/oauth/:provider":
|
||||
if provider := c.Param("provider"); provider != "" {
|
||||
return "oauth:" + provider
|
||||
}
|
||||
return "oauth"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// recordLoginAudit 记录登录成功审计日志(对所有用户启用,仅记录成功,不记录失败)。
|
||||
func recordLoginAudit(user *model.User, c *gin.Context) {
|
||||
method := loginMethodFromContext(c)
|
||||
ip := c.ClientIP()
|
||||
extra := map[string]interface{}{
|
||||
"login_method": method,
|
||||
"user_agent": c.Request.UserAgent(),
|
||||
}
|
||||
content := fmt.Sprintf("Logged in successfully via %s", method)
|
||||
model.RecordLoginLog(user.Id, user.Username, content, ip, "login", map[string]interface{}{
|
||||
"method": method,
|
||||
}, extra)
|
||||
}
|
||||
|
||||
// setup session & cookies and then return user info
|
||||
func setupLogin(user *model.User, c *gin.Context) {
|
||||
model.UpdateUserLastLoginAt(user.Id)
|
||||
@@ -104,6 +141,7 @@ func setupLogin(user *model.User, c *gin.Context) {
|
||||
common.ApiErrorI18n(c, i18n.MsgUserSessionSaveFailed)
|
||||
return
|
||||
}
|
||||
recordLoginAudit(user, c)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "",
|
||||
"success": true,
|
||||
@@ -599,6 +637,10 @@ func UpdateUser(c *gin.Context) {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
recordManageAuditFor(c, updatedUser.Id, "user.update", map[string]interface{}{
|
||||
"username": originUser.Username,
|
||||
"id": updatedUser.Id,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -636,7 +678,10 @@ func AdminClearUserBinding(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
model.RecordLog(user.Id, model.LogTypeManage, fmt.Sprintf("admin cleared %s binding for user %s", bindingType, user.Username))
|
||||
recordManageAuditFor(c, user.Id, "user.binding_clear", map[string]interface{}{
|
||||
"bindingType": bindingType,
|
||||
"username": user.Username,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
@@ -797,6 +842,10 @@ func DeleteUser(c *gin.Context) {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
recordManageAuditFor(c, originUser.Id, "user.delete", map[string]interface{}{
|
||||
"username": originUser.Username,
|
||||
"id": originUser.Id,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -857,6 +906,10 @@ func CreateUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
recordManageAuditFor(c, cleanUser.Id, "user.create", map[string]interface{}{
|
||||
"username": cleanUser.Username,
|
||||
"role": cleanUser.Role,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -941,12 +994,6 @@ func ManageUser(c *gin.Context) {
|
||||
}
|
||||
user.Role = common.RoleCommonUser
|
||||
case "add_quota":
|
||||
adminName := c.GetString("username")
|
||||
adminId := c.GetInt("id")
|
||||
adminInfo := map[string]interface{}{
|
||||
"admin_id": adminId,
|
||||
"admin_username": adminName,
|
||||
}
|
||||
switch req.Mode {
|
||||
case "add":
|
||||
if req.Value <= 0 {
|
||||
@@ -957,8 +1004,9 @@ func ManageUser(c *gin.Context) {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
model.RecordLogWithAdminInfo(user.Id, model.LogTypeManage,
|
||||
fmt.Sprintf("管理员增加用户额度 %s", logger.LogQuota(req.Value)), adminInfo)
|
||||
recordManageAuditFor(c, user.Id, "user.quota_add", map[string]interface{}{
|
||||
"quota": logger.LogQuota(req.Value),
|
||||
})
|
||||
case "subtract":
|
||||
if req.Value <= 0 {
|
||||
common.ApiErrorI18n(c, i18n.MsgUserQuotaChangeZero)
|
||||
@@ -968,16 +1016,19 @@ func ManageUser(c *gin.Context) {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
model.RecordLogWithAdminInfo(user.Id, model.LogTypeManage,
|
||||
fmt.Sprintf("管理员减少用户额度 %s", logger.LogQuota(req.Value)), adminInfo)
|
||||
recordManageAuditFor(c, user.Id, "user.quota_subtract", map[string]interface{}{
|
||||
"quota": logger.LogQuota(req.Value),
|
||||
})
|
||||
case "override":
|
||||
oldQuota := user.Quota
|
||||
if err := model.DB.Model(&model.User{}).Where("id = ?", user.Id).Update("quota", req.Value).Error; err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
model.RecordLogWithAdminInfo(user.Id, model.LogTypeManage,
|
||||
fmt.Sprintf("管理员覆盖用户额度从 %s 为 %s", logger.LogQuota(oldQuota), logger.LogQuota(req.Value)), adminInfo)
|
||||
recordManageAuditFor(c, user.Id, "user.quota_override", map[string]interface{}{
|
||||
"from": logger.LogQuota(oldQuota),
|
||||
"to": logger.LogQuota(req.Value),
|
||||
})
|
||||
default:
|
||||
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
|
||||
return
|
||||
@@ -1005,6 +1056,11 @@ func ManageUser(c *gin.Context) {
|
||||
common.SysLog(fmt.Sprintf("failed to invalidate tokens cache for user %d: %s", user.Id, err.Error()))
|
||||
}
|
||||
}
|
||||
recordManageAuditFor(c, user.Id, "user.manage", map[string]interface{}{
|
||||
"action": req.Action,
|
||||
"username": user.Username,
|
||||
"id": user.Id,
|
||||
})
|
||||
clearUser := model.User{
|
||||
Role: user.Role,
|
||||
Status: user.Status,
|
||||
|
||||
Reference in New Issue
Block a user