Commit Graph

41 Commits

Author SHA1 Message Date
CartSnitch Engineer Bot 2460a00d4e feat(api): implement lifespan with DB and Redis connection pooling
- Refactor database.py to use init_db()/close_db() lifecycle
- Add create_db_engine() with pool_size=10, max_overflow=20, pool_pre_ping=True
- Replace cache.py stub with real Redis client using redis.asyncio
- Implement init_redis()/close_redis() with graceful error handling
- Replace no-op lifespan in main.py with proper startup/shutdown
- Enhance health endpoint to check DB and Redis connectivity
- Add tests for database, cache, and health endpoint lifecycle

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-04 15:07:28 +00:00
cartsnitch-ceo[bot] 15ab4ed38c feat(api): implement Redis cache get/set/delete with TTL support (#195)
feat(api): implement Redis cache get/set/delete with TTL support
2026-04-15 03:32:11 +00:00
cartsnitch-ceo[bot] fef5e86645 feat: Redis-backed rate limiting with stricter auth limits (#194)
feat: Redis-backed rate limiting with stricter auth limits
2026-04-15 03:31:42 +00:00
cartsnitch-ceo[bot] 5308923136 feat(api): add input validation on public endpoints (#171)
feat(api): add input validation on public endpoints
2026-04-15 03:26:38 +00:00
cartsnitch-ceo[bot] bdaca519f6 feat: implement audit logging middleware for sensitive API operations (#183)
feat: implement audit logging middleware for sensitive API operations
2026-04-15 03:23:37 +00:00
Barcode Betty c03e599ae3 feat: Redis-backed rate limiting with stricter auth limits
- Add rate_limit_auth_requests (5/min) and rate_limit_auth_window_seconds (60) settings
- Add rate_limit_redis_enabled flag for opt-in Redis usage
- Refactor _SlidingWindowCounter into InMemorySlidingWindow class
- Add RedisSlidingWindow using sorted sets with fallback to in-memory
- Add third _auth_strict_limiter for POST /auth/* paths (5 req/min)
- Add protocol-based backend selection at module load time
- Update tests for auth strict limiter and Redis fallback behavior

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 02:10:02 +00:00
Paperclip 1ce5d738d1 feat(api): implement Redis cache get/set/delete with TTL support
- Add async Redis client using redis-py with connection pooling
- Implement get/set/delete with graceful degradation when unavailable
- Add TTL support (default 300s) via SETEX
- Add cache invalidation hooks for price and product changes
- Use pattern-based SCAN for bulk invalidation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 16:00:35 +00:00
Paperclip 4c217757c3 feat: Redis-backed rate limiting with stricter auth limits
- Add rate_limit_auth_requests (5/min) and rate_limit_auth_window_seconds (60)
  settings to config.py
- Refactor rate_limit.py to use protocol/ABC pattern with InMemorySlidingWindow
  and RedisSlidingWindow implementations
- Add RedisSlidingWindow using sorted sets for distributed rate limiting
- Add auth_strict_limiter for /auth/* POST endpoints (5 req/min per IP)
- Fall back to in-memory when Redis is unavailable
- Update tests to cover new functionality

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 15:46:52 +00:00
Barcode Betty 6b75d4906f feat: implement audit logging middleware for sensitive API operations
- Add AuditMiddleware that logs POST/PUT/PATCH/DELETE and GET /auth/me
- Logs structured JSON: event, timestamp, user_id, method, path, client_ip, status_code, duration_ms
- Excludes health endpoints and OPTIONS requests
- Never logs request/response bodies or auth headers/cookies
- Wire user_id from auth dependency via request.state
- Add add_audit_middleware() to app factory

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 13:41:55 +00:00
Barcode Betty 68e6be1985 feat(api): implement FastAPI lifespan with connection pooling
- Add connection pool config to SQLAlchemy async engine (pool_size=10, max_overflow=20, pool_pre_ping, pool_recycle)
- Implement Redis connection pool in CacheClient with initialize/close lifecycle
- Wire lifespan startup/shutdown to initialize and dispose pools
- Add dispose_engine() for graceful DB pool cleanup on shutdown

Closes CAR-550

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 13:12:46 +00:00
cartsnitch-cto[bot] c2a0263ddd fix(security): use SHA-256 hash for rate limit key instead of token suffix (#169)
fix(security): use SHA-256 hash for rate limit key instead of token suffix
2026-04-14 12:45:15 +00:00
CartSnitch Engineer Bot 37798251be fix: restrict CORS to explicit methods and add security headers
- Replace allow_methods=["*"] with explicit list: GET, POST, PUT, DELETE, PATCH, OPTIONS
- Replace allow_headers=["*"] with explicit list: Content-Type, Authorization, Accept, Origin, X-Requested-With
- Add X-Frame-Options, X-Content-Type-Options, Referrer-Policy, CSP nginx headers

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 11:49:02 +00:00
CartSnitch Engineer Bot cfea2586cb feat(api): add input validation on public endpoints
- Add days query param to GET /public/trends/{product_id} (ge=1, le=365)
- Add category query param to GET /public/store-comparison
- Add category and period query params to GET /public/inflation
- Add boundary and malicious input test cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 11:45:53 +00:00
CartSnitch Engineer Bot bc5e03e7a0 fix(security): use SHA-256 hash for rate limit key instead of token suffix
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 11:36:17 +00:00
CartSnitch Engineer Bot 538a5f4f4d fix: remove hardcoded default secrets from API config
Remove dangerous default values for jwt_secret_key, service_key, and
fernet_key. Add startup validation that raises RuntimeError if these
secrets are not set via environment variables or contain placeholder
values.

Add test fixture to provide explicit test values for these secrets,
ensuring existing tests continue to pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 11:11:23 +00:00
Barcode Betty 3745f5be69 fix(auth): parse compound Better-Auth cookie/bearer token to extract token part
Better-Auth sets the session cookie as "token.sessionId". The DB stores
only the token part, so passing the full compound value caused 401s.

Splits on "." for both cookie and Bearer paths.

Tests added for compound cookie, raw token cookie (regression), and
compound Bearer token.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 20:32:43 +00:00
Barcode Betty ec9deb515b fix(api): accept DATABASE_URL as fallback for shared DB with auth service
API config.py now reads CARTSNITCH_DATABASE_URL first, falls back to
DATABASE_URL (which the infra K8s overlay sets for all pods), and finally
falls back to the hardcoded default. Also normalizes plain postgresql://
to postgresql+asyncpg:// for the asyncpg driver.

Fixes CAR-510.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 19:52:24 +00:00
Barcode Betty 25edd8d5e3 fix(api): revert SHA-256 session token hashing — better-auth stores raw tokens
Better-auth v1.5.6 stores raw 32-char tokens in sessions.token, not SHA-256
hashes. The SHA-256 fix from PR #136 causes all authenticated API calls to
return 401 because the UAT sessions table contains raw tokens.

- Remove hashlib from dependencies.py; compare tokens directly
- Remove hashlib from conftest.py; store raw tokens in test DB
- Remove hashlib from test_expired_session_rejected; use raw tokens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 19:21:26 +00:00
Barcode Betty 138033be9b fix(api): hash session token with SHA-256 before DB lookup
Better-Auth v1.2+ stores SHA-256(raw_token) in the sessions.token
column. The cookie/Bearer header carries the raw token, so the API was
doing a plain-text lookup that would never match a hashed value —
causing all authenticated endpoints to return 401.

- Add hashlib import and hash token in _validate_session_token()
- Update conftest._create_test_user_and_session() to store hashed tokens
- Update test_expired_session_rejected() to store hashed tokens

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 19:00:09 +00:00
Barcode Betty def921f115 fix(api): read __Secure- prefixed session cookie in auth
Better-auth sets the session cookie with the __Secure- prefix on HTTPS
deployments. The API was only reading the plain cookie name, causing all
authenticated calls to return 401 in dev/UAT/prod environments.

Check __Secure-better-auth.session_token first, fall back to
better-auth.session_token for HTTP local dev compatibility.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 18:40:22 +00:00
Pawla Abdul e90637c227 fix(api): make alembic migrations idempotent for fresh databases
- 001: guard has_table check; skip if session_data already TEXT
- 002: guard each ADD COLUMN / CREATE TABLE; guard password migration
- 003: guard has_table; guard nullable check
- 004: guard has_table; skip if users.id already TEXT
- env.py: add Base.metadata.create_all after run_migrations to bootstrap fresh DBs
- api/user.py: make hashed_password nullable; add email_verified, image, email_inbound_token fields

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 16:28:29 +00:00
cartsnitch-engineer[bot] 6c297b5e81 fix: correct email-in-address format, remove dead code, update tests (#110)
- Fix email format in AuthService.get_email_in_address to use
  receipts+{token}@receipts.cartsnitch.com (was broken: @email.cartsnitch.com)
- Remove dead EmailInAddressResponse class and GET /auth/me/email-in-address
  endpoint from auth/routes.py (endpoint moved to routes/user.py)
- Add instructions field to EmailInAddressResponse schema
- Update routes/user.py to include instructions in the response
- Update test URLs from /auth/me/email-in-address to /api/v1/me/email-in-address

Co-authored-by: CartSnitch Engineer Bot <cartnoreply@cartsnitch.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-03 13:34:21 +00:00
CartSnitch Engineer Bot 1af98c40ab fix: move email-in-address endpoint from /auth to /api/v1 prefix
The GET /me/email-in-address endpoint was unreachable because the Gateway
HTTPRoute routes all /auth/* traffic to Better-Auth (port 3001), not the
API service. This change:
- Moves the endpoint from the /auth router to a new /api/v1/me/ router
- Adds EmailInAddressResponse schema and get_email_in_address service method
- Updates Settings.tsx to call /api/v1/me/email-in-address

Fixes CAR-445.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-03 11:44:31 +00:00
CartSnitch Engineer Bot c855575e77 fix(api): restore /api/v1 prefix on data routers
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-03 10:15:21 +00:00
CartSnitch Engineer Bot f721918f95 fix(api): revert auth/type regressions from standalone sync, keep email-in feature only
- Revert auth/dependencies.py to cookie+Bearer dual auth with str user IDs
- Add GET /auth/me/email-in-address endpoint for receipt email routing
- Update User model: add email_inbound_token, change id/store_id/user_id to str
- Update AuthService and UserResponse to use str user IDs
- Update route count test: 33 -> 34 routes
- Restore e2e test for email-in-address endpoint

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-03 09:40:39 +00:00
CartSnitch Engineer Bot 692f42fbbb fix(auth): revert to Better-Auth session-cookie auth, preserve email-in feature
- Revert auth/dependencies.py, auth/routes.py, services/auth.py, schemas.py
  to Better-Auth session-cookie auth (removed JWT register/login/refresh)
- Preserve GET /auth/me/email-in-address endpoint
- Fix UUIDString TypeDecorator: process_result_value returns uuid.UUID
  (not str) so SQLAlchemy 2.0 sentinel tracking matches UUID-to-UUID
- Fix seed_data fixture: look up real user_id from session token via
  sessions table; purchases now reference actual user FK
- Update purchase_data fixture to use session-cookie auth
- Update test_auth_endpoints, test_auth_validation to cookie-based tests
- Remove TestRegistrationErrors and TestLoginErrors (no longer applicable)
- Update test_openapi.py expected routes and count
- Update test_error_handler.py to use PATCH /auth/me validation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-03 09:15:00 +00:00
Barcode Betty 70b9d1d6d6 sync(api): copy latest standalone code and merge alembic migrations
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-03 07:54:31 +00:00
cartsnitch-ceo[bot] 0b31badbcd Merge branch 'main' into fix/api-date-schema-types 2026-04-01 20:56:13 +00:00
cartsnitch-ceo[bot] 086868d450 Merge branch 'main' into fix/dashboard-hardcoded-product-ids 2026-04-01 20:19:22 +00:00
CartSnitch Engineer Bot 8e8d4a4774 fix(api): change purchased_at and expires_at schema types from datetime to date
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-01 19:41:35 +00:00
CartSnitch Engineer Bot 43cb62a4d6 fix(api): remove TimestampMixin from models whose DB tables lack timestamp columns
Remove TimestampMixin (created_at/updated_at) from Purchase, PurchaseItem,
PriceHistory, Coupon, and ShrinkflationEvent models since their PostgreSQL
tables do not have those columns. This was causing 500 errors on
/api/v1/purchases and /api/v1/purchases/stats.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-01 19:36:21 +00:00
cartsnitch-engineer[bot] f7e1574176 fix(api): replace UUID type with str for Better-Auth nanoid user IDs (#98)
Better-Auth uses nanoid strings for user IDs, not UUIDs. Changed all
user_id parameter/return types in the API layer from UUID to str,
removed the obsolete UUID import where unused, and updated the
_validate_session_token return type accordingly.

Co-authored-by: CartSnitch Engineer Bot <cartnoreply@cartsnitch.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-01 19:15:58 +00:00
CartSnitch Engineer Bot 2f37f0501f fix(api): parse signed session cookie instead of SHA-256 hashing
Better-Auth v1.5.6 stores raw tokens in sessions.token, not SHA-256
hashes. The session cookie is signed (rawToken.hmacSignature), so
strip the HMAC signature suffix before querying the DB.

Fixes 401 errors on all data endpoints caused by the incorrect hash.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-01 11:09:29 +00:00
cartsnitch-cto[bot] 4c36fd4156 fix(api): restore SHA-256 session token hashing (regression from PR #95)
Restores sha256 import and token hashing in _validate_session_token.

Regression introduced when PR #95 (cookie name fix) was merged without
the hash fix from PR #93.

QA approved: CAR-324 (Checkout Charlie)
CTO approved: Paperclip (Savannah Savings)
Resolves CAR-323

cc @cpfarhood
2026-04-01 10:29:05 +00:00
cartsnitch-engineer[bot] ac4cba2b0d fix(api): read __Secure- prefixed session cookie for HTTPS environments
Better-Auth automatically prefixes cookie names with __Secure- when serving
over HTTPS. The API gateway now tries __Secure-better-auth.session_token
first (HTTPS/deployed), falling back to better-auth.session_token (HTTP/local dev).

Fixes CAR-321.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-01 04:02:49 +00:00
CartSnitch Engineer Bot d475b3876a fix(api): hash session token before DB lookup to match Better-Auth storage
Better-Auth v1.5.6+ stores session tokens as SHA-256 hashes in the
sessions table. The raw token from the cookie was being queried directly,
causing all authenticated /api/v1/* requests to return 401.

Fixes CAR-313.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-01 02:09:55 +00:00
cartsnitch-qa[bot] 470b615528 Merge branch 'main' into fix/api-v1-prefix 2026-04-01 01:45:37 +00:00
CartSnitch Engineer Bot f26f8f7e56 fix(api): mount data routers under /api/v1 prefix
Fixes CAR-161 UAT failure: k8s HTTPRoute forwards /api/* to the API
gateway without path rewriting, so requests arrive at FastAPI as
/api/v1/purchases, /api/v1/products, etc. FastAPI previously mounted
data routers at root, causing 404s on all /api/v1/* calls.

Keep health and auth routers at root (probes hit /health directly;
auth traffic is routed to the auth service via HTTPRoute).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 23:56:20 +00:00
Stockboy Steve 00b2b2469b fix: change users.id and FK columns from uuid to text for Better-Auth compatibility
Better-Auth generates nanoid-style text IDs (e.g. pGud2ln2WAFHC0KYjBVKR4Rc7mM8OcTI),
but the users table used PostgreSQL uuid type, causing registration failures:
  ERROR: invalid input syntax for type uuid: "pGud2ln2WAFHC0KYjBVKR4Rc7mM8OcTI"

Changes:
- User.id: removed UUIDPrimaryKeyMixin, use explicit text PK
- UserStoreAccount.user_id: Mapped[uuid.UUID] -> Mapped[str]
- Purchase.user_id: Mapped[uuid.UUID] -> Mapped[str]
- UserResponse schema: id field from UUID -> str
- New Alembic migration 004_fix_user_id_text: drops FKs, alters column
  types, re-adds FKs (using id::text cast)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 17:56:13 +00:00
Coupon Carl 782448a54a feat: migrate authentication to Better-Auth (Phase 1)
Replace hand-rolled JWT auth with Better-Auth session-based authentication.

- Scaffold auth/ Node.js service with Better-Auth, bcrypt password compat,
  Postgres adapter mapped to existing users table
- Add Alembic migration (002) creating sessions, accounts, verifications
  tables and migrating password hashes to accounts table
- Update FastAPI auth dependency to validate sessions via shared DB
  (supports both cookie and Bearer token)
- Remove registration/login/refresh endpoints from API gateway (now
  handled by Better-Auth service)
- Update frontend to use better-auth/react client with httpOnly cookies
  (no tokens in localStorage or memory)
- Rewrite auth store, Login, Register, Dashboard, Settings, ProtectedRoute
  to use session-based auth
- Update all tests to create sessions directly in DB instead of JWT tokens

Resolves CAR-27
See plan: CAR-26#document-plan

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-28 04:46:10 +00:00
Coupon Carl 27fe957074 feat: merge cartsnitch/api into api/ subdirectory
Consolidate API gateway service into monorepo.
Squashed from https://github.com/cartsnitch/api main (89bacb1).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-28 02:24:02 +00:00