Commit Graph

49 Commits

Author SHA1 Message Date
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 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
cartsnitch-engineer[bot] b2725fd512 fix(api): create domain tables migration + fix create_all commit
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 21:22:24 +00:00
Barcode Betty 0be7ccd4b4 fix(api): import Base from models package to register all ORM tables
The models/__init__.py imports all ORM model classes (Store, Product,
Coupon, etc.) which registers their table definitions with Base.metadata.
Importing Base directly from models.base skips this registration, so
alembic's create_all() on fresh databases fails to create app tables.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 21:12:13 +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
cartsnitch-cto[bot] bd3cb3b9ab fix(api): hash session token with SHA-256 before DB lookup (#136)
fix(api): hash session token with SHA-256 before DB lookup
2026-04-04 19:06:30 +00:00
cartsnitch-cto[bot] 3bedc651c6 Merge pull request #133 from cartsnitch/fix/alembic-version-table-width
fix(api): widen alembic version_table column to 128 chars
2026-04-04 19:01:09 +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 43ee1c3531 fix(api): widen alembic version_table column to 128 chars
Default varchar(32) alembic_version column truncates long revision IDs
like 003_make_users_hashed_password_nullable (39 chars) on fresh databases.
Set version_table_column_width=128 in both context.configure() calls.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 18:32:36 +00:00
Barcode Betty 7bf0165fe4 fix(api): bootstrap users table in migration 007 + harden create_all
Create migration 007 to raw-SQL CREATE TABLE IF NOT EXISTS the users table
as a safety net for fresh databases where Base.metadata.create_all() may
fail due to import errors before the table is created.

Wrap the create_all call in env.py with try/except so alembic never crashes
due to create_all failures — migrations already handle table creation.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 17:10:29 +00:00
cartsnitch-cto[bot] ef63c47b7c fix(api): make alembic migrations idempotent for fresh databases (#129)
fix(api): make alembic migrations idempotent for fresh databases
2026-04-04 16:41:02 +00:00
Pawla Abdul be75c7f254 fix(api): add fresh-DB guards to migrations 002, 005, and 006
- 002: wrap add_column calls in has_table("users") guard
- 005: add has_table + column-existence guard before add_column
- 006: add has_table + column + default-existence guard before alter_column

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 16:39:27 +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
Barcode Betty a25b673dd6 fix: install libpq5 runtime in API prod Docker stage
psycopg2 compiled against libpq-dev in the build stage now has
its runtime dependency (libpq5) available in the prod stage.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 10:51:57 +00:00
Barcode Betty 4996ff7432 fix(api): escape percent signs in alembic database URL for configparser
CNPG-generated passwords containing URL-encoded chars (e.g. %2B, %2F) cause
configparser.BasicInterpolation to fail with "invalid interpolation syntax".
Escaping % as %% prevents this.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 06:31:48 +00:00
Pawla Abdul cf16415720 fix(api): add server_default to users.email_inbound_token
Better-Auth creates users via raw SQL INSERT (not through SQLAlchemy),
so it bypasses ORM defaults and causes HTTP 500 on sign-up/sign-in.
Adds PostgreSQL server_default so INSERT without email_inbound_token
auto-generates a URL-safe token matching Python secrets.token_urlsafe(16).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-04 06:17:37 +00:00
cartsnitch-engineer[bot] 6e0cb93ee2 fix(api): include alembic config and migrations in Docker image 2026-04-04 04:40:50 +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
cartsnitch-engineer[bot] 8af7b37b38 fix(api): run Alembic migrations on startup (#90)
Merged by Coupon Carl (CEO). QA approved, CTO approved. CI green (lighthouse failure is known/tracked). cc @cpfarhood
2026-03-31 21:55:00 +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
cartsnitch-engineer[bot] e9bc46121f fix(api): add libpq5 to prod stage for psycopg2 runtime
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 02:48:25 +00:00
cartsnitch-ci[bot] b7b9e987df fix(api): correct COPY paths in Dockerfile for monorepo build context
The api/Dockerfile used bare paths (COPY pyproject.toml ./, COPY src/
./src/) which resolved to the repo root with context: ., causing Docker
builds to fail since api/pyproject.toml and api/src/ don't exist at the
repo root.

Add 'api/' prefix to all COPY source paths, matching the pattern already
used in receiptwitness/Dockerfile.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 01:09:16 +00:00
cartsnitch-ceo[bot] b18cb24ec4 chore: remove polyrepo CI workflow leftovers (#72)
Remove leftover polyrepo CI workflow files that are no longer applicable to the monorepo.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 21:54:17 +00:00
Barcode Betty ea8dcad398 chore: remove polyrepo CI workflow leftovers
Delete nested .github/workflows/ci.yml files from api/ and receiptwitness/
directories. These workflows were from the polyrepo era and reference the
deleted cartsnitch/common repo. They do not execute as GitHub Actions (not
at repo root) and are confusing.

No functional change — the monorepo CI is defined at .github/workflows/.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 21:14:43 +00:00
Barcode Betty d5ee743d84 fix(deploy): include alembic in API Docker image
Adds alembic.ini and alembic/ directory to the production API image so
alembic upgrade head can run in-cluster as an init container.

Also carries migration 003 (make hashed_password nullable) from PR #66.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 20:13:56 +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