Skip to content

feat: IPFS pinning + content-type validation for milestone evidence - #3

Open
mohadon0 wants to merge 1 commit into
mainfrom
feat/milestone-evidence-ipfs-validation
Open

feat: IPFS pinning + content-type validation for milestone evidence#3
mohadon0 wants to merge 1 commit into
mainfrom
feat/milestone-evidence-ipfs-validation

Conversation

@mohadon0

Copy link
Copy Markdown
Owner

Summary

Extends POST /api/validators/milestone to validate evidence_uri content type, enforce a maximum file size, and pin HTTPS URLs to IPFS before storage. The evidence_uri field in the stored record is always a CID — never a raw HTTPS URL.

Changes

app/api/validators/milestone/route.ts (new)

  • Accepts { playerId, validatorId, description, evidence_uri } in the request body
  • If evidence_uri is already an IPFS CID (v0 or v1) it is stored as-is — no Pinata call
  • If evidence_uri is an HTTPS URL:
    1. Fetch the content (axios maxContentLength enforces EVIDENCE_MAX_BYTES)
    2. Validate Content-Type: accept video/*, image/*, application/pdf, text/plain — reject everything else with 422
    3. Reject if file exceeds EVIDENCE_MAX_BYTES (env var, default 50 MB) — 422
    4. Pin to IPFS via Pinata pinFileToIPFS; store the returned CID
  • Non-CID, non-HTTPS values rejected with 400
  • Requires validator session cookie; returns 401 without one

lib/evidencePinner.ts (new)

  • isIpfsCid(value) — detects CIDv0 (Qm…) and CIDv1 (bafy… / z…)
  • isAcceptedContentType(ct) — allow-list check
  • getEvidenceMaxBytes() — reads EVIDENCE_MAX_BYTES env var (default 50 MB)
  • downloadAndPin(url) — fetches, validates, and pins; throws typed errors:
    • EvidenceContentTypeError → 422
    • EvidenceSizeError → 422
    • EvidencePinError → 502
  • ACCEPTED_CONTENT_TYPES exported constant

Tests

__tests__/api/validators/milestone/route.test.ts — 18 tests covering:

  • Auth (401 without session cookie)
  • Field validation (400 for each missing/empty field, invalid URI format)
  • CID passthrough (CIDv0 and CIDv1 stored without triggering Pinata)
  • HTTPS pinning for each accepted type (video/mp4, image/jpeg, application/pdf, text/plain)
  • 422 for unsupported types (application/x-executable, application/zip)
  • 422 when file exceeds EVIDENCE_MAX_BYTES
  • 502 when Pinata upload fails
  • Stored evidence_uri is always a CID, never the original URL

All 18 tests pass.

Acceptance Criteria

  • A valid video URL is pinned and stored as a CID
  • An application/x-executable URL is rejected with HTTP 422
  • Files exceeding EVIDENCE_MAX_BYTES are rejected
  • After submission, evidence_uri in the DB is always a CID
  • Tests cover each accepted and rejected content type

Extend the validator milestone submission endpoint to validate and pin
evidence before storing. evidence_uri is always persisted as a CID.

Endpoint: POST /api/validators/milestone
- If evidence_uri is already an IPFS CID (v0 or v1), stored as-is
- If evidence_uri is an HTTPS URL:
  - Fetch the content (using axios maxContentLength to enforce size)
  - Validate Content-Type: accept video/*, image/*, application/pdf,
    text/plain; reject others with 422
  - Enforce EVIDENCE_MAX_BYTES limit (env var, default 50 MB); 422 on
    exceeded
  - Pin to IPFS via Pinata pinFileToIPFS; store the returned CID
- Non-CID, non-HTTPS values rejected with 400

New lib: lib/evidencePinner.ts
- isIpfsCid() — CIDv0 and CIDv1 detection
- isAcceptedContentType() — content-type allow-list check
- downloadAndPin() — fetches URL, validates type+size, pins to Pinata
- Typed errors: EvidenceContentTypeError (→422), EvidenceSizeError (→422),
  EvidencePinError (→502)

Tests: __tests__/api/validators/milestone/route.test.ts (18 passing)
- Auth guard (401 without session)
- Field validation (400 for missing fields, bad URI format)
- CID passthrough (CIDv0 and CIDv1 stored without Pinata call)
- HTTPS pinning for each accepted type (video, image, PDF, text)
- 422 for unsupported types (application/x-executable, application/zip)
- 422 for oversized files (EVIDENCE_MAX_BYTES exceeded)
- 502 when Pinata upload fails
- Stored record always has evidence_uri as a CID
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant