Skip to content

fix(plans): add server-side past-date guard in createPlannedPurchases #1275

Closed as not planned

Description

@cristim

Summary

PR #1258 (QA 5.6) added a browser-level `min` attribute to the Add Purchases start-date picker to block past dates on the client. The backend handler `parseCreatePurchasesRequest` in `internal/api/handler_plans.go` only validates the date format (`YYYY-MM-DD`), not that the date is >= today.

A caller that bypasses the browser (e.g., `curl`) can submit any past date, causing purchase executions to be scheduled in the past -- which the scheduler will execute immediately in bulk or skip depending on its catch-up behaviour, potentially making unintended purchases.

Root cause

`internal/api/handler_plans.go`, function `parseCreatePurchasesRequest` (~line 337):

startDate, err := time.Parse("2006-01-02", req.StartDate)
if err != nil {
    return nil, time.Time{}, NewClientError(400, "invalid start_date format, expected YYYY-MM-DD")
}
// Missing: check startDate.Before(time.Now().Truncate(24*time.Hour))

Fix

After parsing, add:

today := time.Now().UTC().Truncate(24 * time.Hour)
if startDate.UTC().Before(today) {
    return nil, time.Time{}, NewClientError(400, "start_date must be today or a future date")
}

Use UTC truncation to align with ISO-8601 dates which are timezone-neutral; this matches how the frontend sends `YYYY-MM-DD` strings derived from `new Date().toISOString()`.

Test

Add a unit test in the handler plans test file that calls `parseCreatePurchasesRequest` with yesterday's date and expects a 400 `ClientError`.

References

Activity

  1. cristim commented on Sep 2, 2026

    @cristim
    MemberAuthor

    Duplicate of LeanerCloud/cloud-commitments-platform#50: both ask for the same past-date rejection in parseCreatePurchasesRequest (internal/api/handler_plans.go:483), and LeanerCloud/cloud-commitments-platform#50 additionally specifies the client-side check and the timezone decision, so the work is tracked there.

  2. added
    duplicateThis issue or pull request already exists
    on Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions