Skip to content

Add internal paginated location catalog for Sheets and extension - #190

Open
doobneek wants to merge 8 commits into
developfrom
codex/internal-location-catalog-sync
Open

Add internal paginated location catalog for Sheets and extension#190
doobneek wants to merge 8 commits into
developfrom
codex/internal-location-catalog-sync

Conversation

@doobneek

@doobneek doobneek commented Mar 18, 2026

Copy link
Copy Markdown

Summary

  • add a new authenticated /locations/catalog endpoint for internal catalog syncs without changing public /locations
  • require explicit Cognito auth configuration for the internal catalog route, including configured app client IDs and optional group gating
  • require an allowed browser or extension origin context instead of silently accepting missing/non-http origins
  • fix API Gateway mock OPTIONS responses so authenticated browser clients can preflight the route
  • add targeted unit coverage for auth-path and sortBy validation behavior

Validation

  • node --check src/services/internal-location-catalog-auth.js
  • node --check src/controllers/validation/locations.js
  • node --check test/services/internal-location-catalog-auth.test.js
  • node --check test/controllers/locations-validation.test.js
  • npx jest --runInBand test/services/internal-location-catalog-auth.test.js --config {setupFilesAfterEnv:[]}
  • npx jest --runInBand test/controllers/locations-validation.test.js --config {setupFilesAfterEnv:[]}
  • npm run build

Notes

  • the auth-path unit test avoids the repo's shared DB/app bootstrap and exercises the production verification logic directly
  • I scanned the current branch diff plus the PR body/comments for token-like secrets and did not find any exposed bearer/API tokens, so no history rewrite was needed

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR introduces a new authenticated internal catalog endpoint, but the current authz model appears too permissive and test coverage does not validate the risky production auth path.

Blocking issues

  • Potential broken access control in src/services/internal-location-catalog-auth.js: any valid Cognito token from the configured user pool is accepted (token_use in {id, access}) without audience/client-id/group/scope checks. If the pool contains non-internal users, they can access /locations/catalog.
  • Host allowlist enforcement is bypassable as a security boundary in assertAllowedRequestHost (src/services/internal-location-catalog-auth.js): when Origin/Referer are absent, requests are allowed, so non-browser clients with a valid token bypass host restrictions entirely.
  • Missing high-risk auth tests: test/integration/find-locations.test.js only exercises NODE_ENV=test bypass and does not test real JWT verification/claim enforcement (issuer, signature failure, token type, and authorization constraints). This leaves production auth behavior unclear.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d6b9357af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app.js Outdated
app.use(morgan('dev'));

app.use(cors());
app.use(cors({ exposedHeaders }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update API Gateway preflight for the new authenticated catalog

/locations/catalog is meant for browser clients (sheets.doobneek.org and the extension), but this change only updates Express CORS. I checked simple-proxy-api.yaml, and both mock OPTIONS handlers still return Access-Control-Allow-Origin: 'https://example.com' (lines 43-50 and 86-93). Because the new route requires an Authorization header, browsers will preflight it and reject requests from the intended origins before they ever reach Express, so the feature is unusable outside server-side callers.

Useful? React with 👍 / 👎.

@doobneek

doobneek commented Mar 18, 2026

Copy link
Copy Markdown
Author

Addressed the review feedback in aaab98d.

  • internal catalog access now requires configured Cognito app client IDs, with optional Cognito group gating
  • browser preflight is unblocked in simple-proxy-api.yaml
  • added auth-path unit coverage for signature, token type, client-id, origin, and group checks

I also scanned the current branch diff plus the PR body/comments for token-like secrets and did not find any exposed bearer/API tokens, so no history rewrite was needed.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not safe to approve: there are security control gaps in the new internal catalog auth path and at least one likely validation regression that is untested.

Blocking issues

  • assertAllowedRequestHost only enforces the host allowlist for http/https origins and also skips checks when Origin/Referer are absent. This creates an origin-policy bypass for non-http origins (for example null/extension/file contexts) and weakens the intended browser restriction on a sensitive endpoint.
  • src/config.js introduces hardcoded default Cognito pool/client authorization values (DEFAULT_COGNITO_USER_POOL_ID, DEFAULT_INTERNAL_CATALOG_ALLOWED_CLIENT_IDS). This is fail-open auth configuration: if env config is missing/mis-set, the endpoint still authorizes tokens against baked-in identities.
  • sortBy validation in src/controllers/validation/locations.js uses Joi.string().valid([ ... ]), which likely validates against a single array literal instead of allowed string values. This is a likely API regression and there is no integration test covering sortBy behavior.

@doobneek

Copy link
Copy Markdown
Author

Addressed the latest review feedback in 13e0e5e.

  • internal catalog auth now fails closed if the Cognito issuer/client config is missing
  • requests now need an explicit allowed origin context: allowed web hosts or configured extension origin patterns
  • catalog sortBy validation is explicit and covered by a direct schema test
  • auth-path unit coverage now includes missing-origin and fail-closed config behavior

Validation run on this commit:

  • npx jest --runInBand test/services/internal-location-catalog-auth.test.js --config {setupFilesAfterEnv:[]}
  • npx jest --runInBand test/controllers/locations-validation.test.js --config {setupFilesAfterEnv:[]}
  • npm run build

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not safe to approve: the PR introduces an authentication bypass path in production code and a broad CORS policy relaxation with security/regression risk.

Blocking issues

  • High severity auth bypass: src/services/internal-location-catalog-auth.js accepts Bearer test-internal-token whenever NODE_ENV==='test'. Keeping this shortcut in application auth logic creates a hardcoded backdoor if environment config is ever wrong (or reused outside test harness). This should be removed from runtime code and handled via test-only mocking/fixtures.
  • Security regression risk: simple-proxy-api.yaml changes Access-Control-Allow-Origin from a fixed origin to '*' on API Gateway mock OPTIONS responses. This broadens cross-origin access for the whole API surface and is not covered by targeted security tests/policy justification in this PR.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not consider this safe to approve yet due to authorization and regression risk concerns.

Blocking issues

  • src/services/internal-location-catalog-auth.js: Origin enforcement is treated as an auth gate, but Origin/Referer are client-controlled headers and can be spoofed outside browsers; this creates unclear and potentially over-trusting access behavior for an internal endpoint.
  • src/services/internal-location-catalog-auth.js: Access can be granted based on allowed client ID alone (group check optional), which may over-authorize if that client is broadly used; for an internal catalog endpoint this is a risky default and needs stricter documented constraints.
  • simple-proxy-api.yaml: CORS Access-Control-Allow-Origin was changed globally to a single hardcoded origin (https://sheets.doobneek.org) for API Gateway mock OPTIONS responses, which is a potential breaking regression for other browser consumers and is not covered by regression tests.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR adds a sensitive internal endpoint with custom auth, but the auth cache implementation introduces a denial-of-service risk that is not safe to approve as-is.

Blocking issues

  • src/services/internal-location-catalog-auth.js uses an unbounded in-memory tokenCache keyed by raw bearer token, and entries are never actively evicted (expired entries remain unless the same token is seen again). Because caching happens before client/group authorization, any valid Cognito token from the issuer can be used to grow process memory indefinitely. This is a high-severity availability risk (memory exhaustion/DoS) for a public-facing endpoint.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not safe to approve: there is a CORS-policy bypass on the new internal catalog endpoint due to path matching logic, and risky path-variant behavior is not covered by tests.

Blocking issues

  • Security/CORS bypass: in src/app.js, CORS middleware selection uses if (req.path === '/locations/catalog'). Express will still route /locations/catalog/ (and potentially other equivalent path variants) to the same handler, but those requests will fall back to publicApiCors (allow-all) instead of internalLocationCatalogCors, defeating the origin allowlist for browser requests.
  • Missing regression test for path-variant CORS behavior: tests cover allowed/disallowed origins for /locations/catalog but do not verify that /locations/catalog/ (or equivalent variants) enforces the same restricted CORS policy. Given this is security-sensitive routing logic, this gap is blocking.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not safe to approve yet. The new catalog query has a pagination correctness risk that can cause skipped/duplicated locations across pages, and tests do not cover this high-risk case.

Blocking issues

  • src/models/location.js (Location.findCatalog): applying limit/offset together with a hasMany include (PhysicalAddress) can paginate SQL join rows instead of distinct locations. In Sequelize this often causes unstable paging (duplicate parent rows on one page, missing parents on later pages). This is a behavioral regression risk for /locations/catalog pagination.
  • test/integration/find-locations.test.js: new catalog tests do not cover locations with multiple PhysicalAddresses, so the pagination/join risk above is untested. Per conservative review rules, risky pagination logic without regression tests is blocking.

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