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
|
||||
}
|
||||
var body struct {
|
||||
Date string `json:"date"`
|
||||
Action string `json:"action"`
|
||||
RuleID string `json:"rule_id"`
|
||||
ShiftID string `json:"shift_id"`
|
||||
ClientTime string `json:"client_time"`
|
||||
Location string `json:"location"`
|
||||
Wifi string `json:"wifi"`
|
||||
Device string `json:"device"`
|
||||
Date string `json:"date"`
|
||||
Action string `json:"action"`
|
||||
RuleID string `json:"rule_id"`
|
||||
ShiftID string `json:"shift_id"`
|
||||
ClientTime string `json:"client_time"`
|
||||
Location json.RawMessage `json:"location"`
|
||||
Wifi json.RawMessage `json:"wifi"`
|
||||
Device json.RawMessage `json:"device"`
|
||||
}
|
||||
if !decodeBody(w, r, &body) {
|
||||
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)
|
||||
if err != nil {
|
||||
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "date"})
|
||||
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)
|
||||
if err != nil {
|
||||
writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "client_time"})
|
||||
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{
|
||||
Date: date, Action: strings.TrimSpace(body.Action), RuleID: strings.TrimSpace(body.RuleID), ShiftID: strings.TrimSpace(body.ShiftID),
|
||||
ClientTime: clientTime, Location: strings.TrimSpace(body.Location), Wifi: strings.TrimSpace(body.Wifi), Device: strings.TrimSpace(body.Device), IdempotencyKey: idempotencyKey,
|
||||
Date: date, Action: action, RuleID: ruleID, ShiftID: shiftID,
|
||||
ClientTime: clientTime, Location: location, Wifi: wifi, Device: device, IdempotencyKey: idempotencyKey,
|
||||
})
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
buf := make([]byte, size)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user