Skip to content

feat: add centralized snippet request validation - #178

Open
Okorie2000-code wants to merge 2 commits into
SudiptaPaul-31:mainfrom
Okorie2000-code:feat/157-snippet-request-validation
Open

feat: add centralized snippet request validation#178
Okorie2000-code wants to merge 2 commits into
SudiptaPaul-31:mainfrom
Okorie2000-code:feat/157-snippet-request-validation

Conversation

@Okorie2000-code

Copy link
Copy Markdown

Summary

Adds a centralized snippet request validation layer shared by every snippet operation — create, update, import, and share. All rules live in one module (app/api/snippets/snippet.validator.ts) so validation can never diverge between endpoints. No per-route validation duplication remains.

Fields / rules validated

Field Rule
title Required, non-empty/non-whitespace, 3–100 chars
description Required on create, max 500 chars
language Must be one of the project's existing supported languages (lib/languages.ts), no conflicting registry
code Required, rejects empty/whitespace-only, 1–10 000 chars (consistent with the existing form limit)
tags Array, each tag trimmed, max 20 tags, ≥1 tag on create
ownerWalletAddress Format and checksum validated via the Stellar SDK (StrKey.isValidEd25519PublicKey) — no hand-rolled crypto
visibility Only private, public, shared; anything else → 400. Defaults to private. Persisted via a new visibility column (migration: scripts/add-visibility-column.sql)

Import validation

New endpoint POST /api/snippets/import:

  • JSON body: single snippet object or array of snippet objects.
  • ZIP body: archive containing a JSON manifest (stored or deflate) via a small dependency-free, bounded ZIP reader (import/zip.util.ts).
  • Every snippet is validated with the same centralized rules as creation before anything is persisted — one invalid item rejects the whole payload, so invalid data can never partially enter the database.
  • Safety limits: 5 MB payload cap, 50 entries, 2 MB per entry, 5 MB uncompressed total, zip-bomb protection, size-vs-header integrity checks.

Authorization protection

  • Import requires an authenticated wallet and the authenticated wallet always becomes the owner (an ownerWalletAddress inside an imported file is rejected).
  • Update cannot change the owner (ownerWalletAddress in an update body is rejected) and ownership/edit checks are unchanged.
  • Share still requires the snippet owner; visibility changes go through the existing ownership model.
  • GET stays permission-gated; only public snippets become readable without a wallet.
  • No authentication or authorization rules were weakened.

Standardized error handling

Every snippet endpoint now returns one consistent JSON error payload on validation failure with HTTP 400:

{ "error": "Validation failed", "message": "<first issue>", "details": [{ "field": "title", "message": "..." }] }

No stack traces, secrets, database errors, or internal details are exposed.

Tests added

  • app/api/snippets/snippet.validator.test.ts — 54 tests: valid inputs, every supported language, every visibility value, invalid inputs (missing/empty/short/long title, whitespace code, oversized code, unsupported language, malformed wallet/checksum, invalid visibility), boundaries (3/100-char title, 1/10 000-char code), and error-format checks (including no internal detail leakage).
  • app/api/snippets/import/zip.util.test.ts — 13 tests: stored/deflate archives, an externally-generated (Python zipfile) fixture, corrupt/truncated/unsupported archives, zip bombs, entry-count and size limits.
  • app/api/snippets/import/route.test.ts — 19 tests: 401 without wallet, malformed JSON, invalid ZIP, missing/invalid fields inside imports, oversized content, no partial persistence, valid JSON + ZIP imports.
  • app/api/snippets/route.test.ts (create) — 12 tests, including HTTP 400 error shape and no persistence on invalid input.
  • app/api/snippets/[id]/route.test.ts (update) — 9 tests: 401/403/404, invalid title/visibility/code, owner cannot be changed via update.
  • app/api/snippets/[id]/share/route.test.ts — 5 tests: 401/403, valid share, malformed expiresAt, invalid isReadOnly.
  • lib/snippet.service.test.ts — updated to use a checksum-valid Stellar address and extended with invalid-input cases (malformed wallet, unsupported language, invalid visibility).

Validation commands / results

Check Result
npx eslint (changed files) ✅ 0 errors
npx tsc --noEmit (changed files) ✅ 0 errors (repo baseline has 872 pre-existing env errors: missing @types/jest, stale .next types)
npx jest (all snippet suites) ✅ 8 suites, 145 tests passed
npx next build ⚠️ fails identically before/after this change (pre-existing submitCollectionToStellar/ui/switch issues in collections code)

Notes:

  • jest.config.mjs + jest.setup.ts add a TextDecoder/TextEncoder polyfill so the jsdom test environment can load server libraries (e.g. @neondatabase/serverless) — required for any test to run.
  • Run scripts/add-visibility-column.sql (or psql -f scripts/add-visibility-column.sql) to add the visibility column before deploying.

Closes #157

Introduce a single validation module reused by every snippet operation
(create, update, import, share): title 3-100 chars, supported-language
enum, code size/whitespace limits, Stellar wallet format+checksum via
the SDK, and visibility restricted to private/public/shared. All
failures return one consistent JSON error payload with HTTP 400 and
nothing is persisted for invalid input. Adds a bounded ZIP reader plus
an import endpoint that validates JSON/ZIP manifests before persistence
and always assigns the authenticated wallet as owner. Includes a
visibility column migration and comprehensive unit/route tests.

Closes SudiptaPaul-31#157

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

@Okorie2000-code is attempting to deploy a commit to the Sudipta 's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Aug 26, 2026

Copy link
Copy Markdown

@Okorie2000-code Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@SudiptaPaul-31

Copy link
Copy Markdown
Owner

@Okorie2000-code resolve conflicts

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.

API Request Validation Layer

2 participants