fix: validate attendance checkin payload
This commit is contained in:
+72
-10
@@ -248,31 +248,65 @@ func (a *API) attendanceCheckins(w http.ResponseWriter, r *http.Request, p *auth
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var body struct {
|
var body struct {
|
||||||
Date string `json:"date"`
|
Date string `json:"date"`
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
RuleID string `json:"rule_id"`
|
RuleID string `json:"rule_id"`
|
||||||
ShiftID string `json:"shift_id"`
|
ShiftID string `json:"shift_id"`
|
||||||
ClientTime string `json:"client_time"`
|
ClientTime string `json:"client_time"`
|
||||||
Location string `json:"location"`
|
Location json.RawMessage `json:"location"`
|
||||||
Wifi string `json:"wifi"`
|
Wifi json.RawMessage `json:"wifi"`
|
||||||
Device string `json:"device"`
|
Device json.RawMessage `json:"device"`
|
||||||
}
|
}
|
||||||
if !decodeBody(w, r, &body) {
|
if !decodeBody(w, r, &body) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
action := strings.TrimSpace(body.Action)
|
||||||
|
if action == "" || len(action) > 32 {
|
||||||
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "action"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ruleID := strings.TrimSpace(body.RuleID)
|
||||||
|
if len(ruleID) > 64 {
|
||||||
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "rule_id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shiftID := strings.TrimSpace(body.ShiftID)
|
||||||
|
if shiftID == "" {
|
||||||
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "shift_id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
date, err := parseDate(body.Date)
|
date, err := parseDate(body.Date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "date"})
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "date"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if maxShiftIDLength := maxAttendanceShiftIDLength(p.UserID, date); len(shiftID) > maxShiftIDLength {
|
||||||
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "shift_id", "max_length": maxShiftIDLength})
|
||||||
|
return
|
||||||
|
}
|
||||||
clientTime, err := parseTime(body.ClientTime)
|
clientTime, err := parseTime(body.ClientTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "client_time"})
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "client_time"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
location, err := normalizeJSONObject(body.Location)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "location"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wifi, err := normalizeJSONObject(body.Wifi)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "wifi"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
device, err := normalizeJSONObject(body.Device)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "device"})
|
||||||
|
return
|
||||||
|
}
|
||||||
record, checkin, err := a.store.CreateAttendanceCheckin(r.Context(), p.UserID, "attc_"+randomToken(12), store.AttendanceCheckinRequest{
|
record, checkin, err := a.store.CreateAttendanceCheckin(r.Context(), p.UserID, "attc_"+randomToken(12), store.AttendanceCheckinRequest{
|
||||||
Date: date, Action: strings.TrimSpace(body.Action), RuleID: strings.TrimSpace(body.RuleID), ShiftID: strings.TrimSpace(body.ShiftID),
|
Date: date, Action: action, RuleID: ruleID, ShiftID: shiftID,
|
||||||
ClientTime: clientTime, Location: strings.TrimSpace(body.Location), Wifi: strings.TrimSpace(body.Wifi), Device: strings.TrimSpace(body.Device), IdempotencyKey: idempotencyKey,
|
ClientTime: clientTime, Location: location, Wifi: wifi, Device: device, IdempotencyKey: idempotencyKey,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeStoreError(w, r, err)
|
writeStoreError(w, r, err)
|
||||||
@@ -957,6 +991,34 @@ func parseTime(value string) (time.Time, error) {
|
|||||||
return time.ParseInLocation("2006-01-02 15:04:05", value, time.Local)
|
return time.ParseInLocation("2006-01-02 15:04:05", value, time.Local)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maxAttendanceShiftIDLength(userID int64, date time.Time) int {
|
||||||
|
prefix := fmt.Sprintf("att_%d_%s_", userID, date.Format("20060102"))
|
||||||
|
return 64 - len(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeJSONObject(raw json.RawMessage) (string, error) {
|
||||||
|
if len(raw) == 0 || strings.TrimSpace(string(raw)) == "" || string(raw) == "null" {
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
var text string
|
||||||
|
if err := json.Unmarshal(raw, &text); err == nil {
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
if text == "" || text == "null" {
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
raw = json.RawMessage(text)
|
||||||
|
}
|
||||||
|
var value map[string]any
|
||||||
|
if err := json.Unmarshal(raw, &value); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
normalized, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(normalized), nil
|
||||||
|
}
|
||||||
|
|
||||||
func randomToken(size int) string {
|
func randomToken(size int) string {
|
||||||
buf := make([]byte, size)
|
buf := make([]byte, size)
|
||||||
if _, err := rand.Read(buf); err != nil {
|
if _, err := rand.Read(buf); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user