feat(backend): unified response helpers, refresh token, security hardening
- new resp package (OK/Page/Fail/BadRequest/.../InternalErr): unified error
envelope {detail, code?}, sanitized 500s (full error logged server-side,
generic message to client - stops DB error leakage), pagination envelope
{items,total,page,page_size}; migrate 200+ inline c.JSON error sites
- refresh token: 7d rotation tokens (SHA-256 hashed at rest), public
POST /auth/refresh with single-use rotation, POST /auth/logout revokes
all user refresh tokens (endpoint previously missing - frontend 404)
- security: gh-ost command line password masked in task status payload;
Instance.MarshalJSON guarantees encrypted passwords never leave the API
(removes reliance on per-handler manual masking); production (DEBUG=false)
with default JWT secret now auto-generates and persists a random secret
- safe.Go recover wrapper for background goroutines (instance probe,
gh-ost async)
- delete dead service layer (zero callers)
- fix pre-existing go vet unkeyed bson.E literals
This commit is contained in:
1 parent
d5b8b8116c
commit
16f1f7d65f
26 files changed
+916
-963
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"sql-platform/internal/handler/resp"
|
||||
"sql-platform/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -15,7 +16,7 @@ func GetWorkflow(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var wf model.WorkflowDefinition
|
||||
if err := model.DB.First(&wf, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Workflow not found"})
|
||||
resp.NotFound(c, "Workflow not found")
|
||||
return
|
||||
}
|
||||
var steps []model.WorkflowStep
|
||||
@@ -55,7 +56,7 @@ func CreateWorkflow(c *gin.Context) {
|
||||
} `json:"steps"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
resp.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ func CreateWorkflow(c *gin.Context) {
|
||||
IsDefault: input.IsDefault,
|
||||
}
|
||||
if err := model.DB.Create(&wf).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||||
resp.InternalErr(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -99,13 +100,13 @@ func UpdateWorkflow(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var wf model.WorkflowDefinition
|
||||
if err := model.DB.First(&wf, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"detail": "Workflow not found"})
|
||||
resp.NotFound(c, "Workflow not found")
|
||||
return
|
||||
}
|
||||
|
||||
var input map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
resp.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,7 +172,7 @@ func ResetPassword(c *gin.Context) {
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
resp.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
_, _ = id, input.NewPassword
|
||||
@@ -182,18 +183,18 @@ func ResetPassword(c *gin.Context) {
|
||||
// POST /api/v1/users/batch-import
|
||||
func BatchImportUsers(c *gin.Context) {
|
||||
var input struct {
|
||||
Users []struct {
|
||||
Users []struct {
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
Password string `json:"password"`
|
||||
} `json:"users"`
|
||||
ImportType string `json:"import_type"`
|
||||
SkipExisting bool `json:"skip_existing"`
|
||||
ImportType string `json:"import_type"`
|
||||
SkipExisting bool `json:"skip_existing"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
resp.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -267,16 +268,16 @@ func BatchImportUsers(c *gin.Context) {
|
||||
func BatchImportLdapUsers(c *gin.Context) {
|
||||
var input struct {
|
||||
Users []struct {
|
||||
DN string `json:"dn"`
|
||||
CN string `json:"cn"`
|
||||
Nickname string `json:"nickname"`
|
||||
UID string `json:"uid"`
|
||||
Username string `json:"username"`
|
||||
Mail string `json:"mail"`
|
||||
DN string `json:"dn"`
|
||||
CN string `json:"cn"`
|
||||
Nickname string `json:"nickname"`
|
||||
UID string `json:"uid"`
|
||||
Username string `json:"username"`
|
||||
Mail string `json:"mail"`
|
||||
} `json:"users"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
resp.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -359,21 +360,21 @@ func ResetUserPassword(c *gin.Context) {
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||
resp.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
id, err := strconv.Atoi(userId)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "Invalid user ID"})
|
||||
resp.BadRequest(c, "Invalid user ID")
|
||||
return
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(input.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "Hash failed"})
|
||||
resp.InternalMsg(c, "Hash failed")
|
||||
return
|
||||
}
|
||||
if err := model.DB.Model(&model.User{}).Where("id = ?", id).Update("hashed_password", string(hash)).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"detail": "Update failed"})
|
||||
resp.InternalMsg(c, "Update failed")
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Password reset successfully"})
|
||||
|
||||
Reference in new issue
Block a user