feat(cloudserver): add input validation for register and search endpoints#51
feat(cloudserver): add input validation for register and search endpoints#51Tech-Code1 wants to merge 3 commits intoGentleman-Programming:mainfrom
Conversation
…ectly Introduce ErrNotFound in the cloudstore package so callers can distinguish "resource does not exist" from database or internal errors. Previously, GetObservation, GetChunk and GetPrompt surfaced raw sql.ErrNoRows, causing writeStoreError to fall through to a 500 Internal Server Error even when the resource simply did not exist. Changes: - Add cloudstore.ErrNotFound sentinel alongside ErrProjectSyncPaused - Wrap sql.ErrNoRows with ErrNotFound in GetObservation, GetChunk, GetPrompt - Update writeStoreError to resolve ErrNotFound to HTTP 404 - Simplify handlePullChunk to use writeStoreError (removes duplicate branches) - Add errors_test.go with unit tests for writeStoreError and isDBConnectionError
Alan-TheGentleman
left a comment
There was a problem hiding this comment.
The validation layer is a solid idea — catching bad input before it hits Postgres is the right call, and the bcrypt 72-char guard is a nice detail.
A few things to fix before this can go in:
-
This PR depends on #50 — the diff includes the
ErrNotFoundcheck inwriteStoreErrorwhich doesn't exist on main yet. Please rebase onto #50 or wait until it's merged, then rebase onto main. -
Username charset is too restrictive — right now it rejects dots and unicode characters, so usernames like
alan.borseñor_devwould fail. For an open source project with global users, we should at minimum allow dots (.). Consider whether unicode letters should be allowed too (Go'sunicode.IsLetteralready handles this, you just need to keep it in the allowed set). -
Password min length not validated — the comment says it "mirrors the 8-char floor in auth.ErrWeakPassword" but the function only checks the max (72). Either validate both min and max here (defense in depth), or drop the comment so it doesn't mislead. I'd prefer validating both since we're already doing input validation at this layer.
-
Email rejects
user@localhost— this is valid in local/dev environments. Not a hard blocker, but if it's easy to relax (e.g. only require a dot if the domain has more than one segment), that'd be nice. Up to you on this one.
Fix (1), (2), and (3) and this is ready.
…ints Add lightweight validation for the two HTTP endpoints most exposed to external input: POST /auth/register and GET /sync/search. Registration now rejects usernames that are too short/long or contain invalid characters, malformed email addresses, and passwords exceeding bcrypt's 72-byte processing limit (which would silently truncate longer passwords, making them weaker than users expect). Search now enforces a 500-character cap on the query string and clamps the limit parameter to 1-100, preventing unbounded Postgres FTS queries. Validation happens before any database call, returning HTTP 400 with a descriptive message. The auth.ErrWeakPassword check in the auth layer is preserved and complementary.
…ation - Allow dots in usernames so handles like `alan.b` are accepted - Keep unicode.IsLetter so non-ASCII usernames like `señor_dev` work - Add password minimum length check (8 chars) for defense in depth - Relax email domain validation to accept single-segment domains like `user@localhost`, which are valid in local and dev environments - Update tests to cover all new cases
9f2e874 to
177d88c
Compare
Summary
POST /auth/registernow validates username format/length, email structure, and password length before hitting the database.GET /sync/searchnow enforces a 500-character cap on the query string and clampslimitto 1–100.validators.gofile — pure functions, no dependencies, easy to extend.Motivation
Both endpoints accepted arbitrary input with no bounds checking:
""or 200 characters was accepted.notanemailpassed through to Postgres, which would reject it with a cryptic constraint error.GET /sync/search?limit=999999would issue an unbounded Postgres FTS query.qparameter would be passed directly to the FTS engine.Changes
POST /auth/register[a-zA-Z0-9_-]onlyPOST /auth/register@, domain must contain.POST /auth/registerGET /sync/searchGET /sync/searchThe existing
auth.ErrWeakPasswordcheck (min 8 chars) in the auth layer is preserved — both validations coexist.Test plan
TestValidateRegisterRequest— 13 table-driven cases covering valid input and every rejection pathTestValidateSearchParams— 8 cases covering bounds and edge valuescloudservertests pass (SKIP_DOCKER_TESTS=1)go build ./...)🤖 Generated with Claude Code