package handlers import ( "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/hex" "fmt" "net/http" "strings" "github.com/gin-gonic/gin" "sproutgate-backend/internal/auth" "sproutgate-backend/internal/models" ) func bearerToken(header string) string { if header == "" { return "" } if strings.HasPrefix(strings.ToLower(header), "bearer ") { return strings.TrimSpace(header[7:]) } return "" } func adminTokenFromRequest(c *gin.Context) string { if token := strings.TrimSpace(c.Query("token")); token != "" { return token } if token := strings.TrimSpace(c.GetHeader("X-Admin-Token")); token != "" { return token } authHeader := strings.TrimSpace(c.GetHeader("Authorization")) return bearerToken(authHeader) } func generateVerificationCode() (string, error) { randomBytes := make([]byte, 3) if _, err := rand.Read(randomBytes); err != nil { return "", err } number := int(randomBytes[0])<<16 | int(randomBytes[1])<<8 | int(randomBytes[2]) return fmt.Sprintf("%06d", number%1000000), nil } func hashCode(code string) string { sum := sha256.Sum256([]byte(code)) return hex.EncodeToString(sum[:]) } func verifyCode(code string, hash string) bool { return subtle.ConstantTimeCompare([]byte(hashCode(code)), []byte(hash)) == 1 } func writeBanJSON(c *gin.Context, reason string) { h := gin.H{"error": "account is banned", "valid": false} if r := strings.TrimSpace(reason); r != "" { h["banReason"] = r } c.JSON(http.StatusForbidden, h) } // abortIfTokenEpochStale 若 JWT 内 epoch 与用户当前 token_epoch 不一致,则拒绝(例如管理员已封禁并递增 epoch)。 func abortIfTokenEpochStale(c *gin.Context, claims *auth.Claims, u models.UserRecord) bool { if claims.TokenEpoch == u.TokenEpoch { return false } c.JSON(http.StatusUnauthorized, gin.H{"valid": false, "error": "token revoked"}) return true } func abortIfUserBanned(c *gin.Context, u models.UserRecord) bool { if !u.Banned { return false } writeBanJSON(c, u.BanReason) return true }