go__sqs_demo/back/web/confirm.go

203 lines
4.5 KiB
Go

package web
import (
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
csrf "github.com/utrack/gin-csrf"
"golang.org/x/exp/maps"
"code.osinet.fr/fgm/sqs_demo/back/services/redriver"
)
type (
// Level is an information level: danger | warning.
Level string
// QueueOp is an allowed operation or reserved error: delete|redrive|invalid.
QueueOp string
)
const (
LevelDanger Level = "danger"
LevelInfo Level = "info"
LevelWarning Level = "warning"
OpDelete QueueOp = "delete"
OpInvalid QueueOp = "invalid"
OpPurge QueueOp = "purge"
OpRedrive QueueOp = "redrive"
)
var confirms = map[QueueOp]struct {
question, description string
confirm string
Level // danger|warning
}{
OpDelete: {
confirm: "Clear",
description: "These messages cannot be recovered after that step",
question: "Do you confirm this deletion request?",
Level: LevelDanger,
},
OpInvalid: {
description: "The operation requested is invalid",
Level: LevelInfo,
},
OpPurge: {
confirm: "Purge",
description: "All the messages in the letter queue will be lost after that step",
question: "Do you confirm this purge request?",
Level: LevelDanger,
},
OpRedrive: {
confirm: "Redrive",
description: "These messages will be in their source queue after that step",
question: "Do you confirm this redriving request?",
Level: LevelWarning,
},
}
// getOp extracts the operation from the unsafe form data, returning a safe value.
func getOp(u *url.URL) QueueOp {
q := u.Query()
found := 0
var res QueueOp
for key := range q {
switch op := QueueOp(strings.ToLower(key)); op {
case OpDelete, OpPurge, OpRedrive:
res = op
found++
default:
continue
}
}
if found != 1 {
return OpInvalid
}
return res
}
type IDValidator func(in string) bool
// validateUint is an IDValidator for unsigned integer strings.
func validateUint(in string) bool {
_, err := strconv.Atoi(in)
return err == nil
}
// validateUUID is an IDValidator for UUID strings
func validateUUID(in string) bool {
_, err := uuid.Parse(in)
return err == nil
}
// validateARN is an IDValidator for ARN strings
func validateARN(in string) bool {
return arn.IsARN(in)
}
func parseIDs(v url.Values, isValid IDValidator) []string {
const prefix = "id-"
var ids []string
for k, vs := range v {
// Weed out bad keys, bad value lengths, and non-matching checkbox statuses.
if pos := strings.Index(k, prefix); pos == -1 || len(vs) != 1 || vs[0] != "on" {
continue
}
b, a, found := strings.Cut(k, prefix)
if b == "" && found && isValid(a) {
ids = append(ids, a)
}
}
return ids
}
func parseMessages(v url.Values, ids []string) []redriver.Message {
m := make(map[int]redriver.Message, len(ids))
keyRx := regexp.MustCompile("^(id|mid|rh)-(.+)")
mOK := make(map[int]bool, len(ids))
for k, vs := range v {
ms := keyRx.FindStringSubmatch(k)
if len(ms) != 3 || len(vs) != 1 {
continue
}
v := vs[0]
id, err := strconv.Atoi(ms[2])
if err != nil {
continue
}
message := m[id]
switch ms[1] {
case "id":
if v == "on" {
mOK[id] = true
}
case "rh":
message.ReceiptHandle = v
case "mid":
message.MessageId = v
}
m[id] = message
}
// Only return selected messages.
res := make(map[int]redriver.Message, len(mOK))
for id := range mOK {
res[id] = m[id]
}
return maps.Values(res)
}
func makeConfirmHandler() gin.HandlerFunc {
return func(c *gin.Context) {
qName := c.Param("name")
u := c.Request.URL
sess := sessions.Default(c)
flashes := sess.Flashes()
defer func() {
_ = sess.Save()
}()
op := getOp(u)
ids := parseIDs(u.Query(), validateUint)
var messages []redriver.Message
if len(ids) == 0 && op != OpPurge {
op = OpInvalid
messages = nil
} else {
messages = parseMessages(u.Query(), ids)
}
token := csrf.GetToken(c)
data := confirms[op] // op is guaranteed to be valid by getOp().
h := gin.H{
"action": "/queue/" + qName + "/" + string(op),
"cancel": "Cancel",
"cancelURL": "/queue/" + qName,
"confirm": data.confirm,
"csrf": token,
"description": data.description,
"flashes": flashes,
"level": data.Level,
"messages": messages,
"question": data.question,
}
if qName == "" {
h["cancelURL"] = "/"
}
if op == OpInvalid {
h["messages"] = nil
}
c.HTML(http.StatusOK, "confirm", h)
}
}