Skip to content

fix!: require auth for restricted products#284

Open
alukach wants to merge 14 commits into
mainfrom
chore/rm-data-mode
Open

fix!: require auth for restricted products#284
alukach wants to merge 14 commits into
mainfrom
chore/rm-data-mode

Conversation

@alukach
Copy link
Copy Markdown
Contributor

@alukach alukach commented Apr 7, 2026

What I'm changing

Currently, our app does not enforce Product visibility. As such, anonymous users can view restricted products (e.g. https://staging.source.coop/alukach/alukach-experimentation). This PR cleans up our data model by removing the legacy data_mode field from the Product and integrates the visibility into our authorization tooling. As such, we will require valid permissions for Products marked as restricted.

How I did it

The Product model currently contains both a legacy data_mode and a modern visibility field. The data_mode should have been removed when we migrated to our new data model. During this migration, we populated the visibility field from the data_mode:

// Convert visibility based on old fields
const visibility =
oldProduct.data_mode === "open"
? oldProduct.state === "listed"
? "public"
: "unlisted"
: "restricted";

However, the code base currently erroneously makes use of both data_mode and visibility when determining the product authorization:

// If the repository is open, everyone is authorized
if (product.data_mode === ProductDataMode.Open) {
return true;
}

// If the repository is open, everyone is authorized
// TODO: Right now we are treating unset data_mode as open
if (!product.data_mode || product.data_mode === ProductDataMode.Open) {
return true;
}

// If the repository is listed , everyone is authorized
if (
// product.state === RepositoryState.Listed &&
// product.data_mode === RepositoryDataMode.Open
product.visibility === "public"
) {
return true;
}

Note that we don't surface data_mode in the Product form, only visibility, as the data_mode field is deprecated.

This PR removes the data_mode field and only uses visibility for auth permissions. This seems inline with the documentation written during S2 development:

2. **Visibility Control**:
- Old: Simple `data_mode` and `state` fields
- New: Single `visibility` field with three states:
- `public`: Visible to everyone, listed in search/sitemap
- `unlisted`: Visible via direct URL to authenticated contributors
- `restricted`: Only visible to explicitly granted users

How you can test it

Compare

Note

Before merging this, we need to determine what to do with the DataConnection.allowed_data_modes field. It's currently unenforced.

allowed_data_modes: z.array(z.nativeEnum(RepositoryDataMode)),


closes #261

Warning

BREAKING CHANGE: existing data-connections records must be backfilled.
Run: npx tsx scripts/backfill-allowed-data-modes.ts

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
source-cooperative Ready Ready Preview, Comment May 5, 2026 8:40pm

Request Review

@alukach alukach changed the title chore: rm legacy data_mode fix: require auth for restricted products Apr 7, 2026
@source-release-bot source-release-bot Bot added fix and removed chore labels Apr 7, 2026
@claude
Copy link
Copy Markdown

claude Bot commented May 4, 2026

Claude finished @alukach's task in 2m 41s —— View job


PR Review Complete

  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files
  • Post inline comments (3 comments)
  • Post summary comment

Review posted. Key findings:

  • One concern before merging: DataConnection.allowed_data_modes changed element type from RepositoryDataModeProductVisibility; existing DB records with old string values will fail Zod parsing.
  • Positive: new isAuthorized gate in the product layout is a good security improvement that was previously missing.
  • Minor: subscription-org-repo-id fixture is now redundant; field naming allowed_data_modes with ProductVisibility type is misleading.

Comment thread src/types/data-connection.ts Outdated
Comment on lines +159 to +160
// NOTE: allowed_data_modes is currently unenforced
allowed_data_modes: z.array(z.nativeEnum(ProductVisibility)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Potential runtime parse failure for existing data

The type of allowed_data_modes changed from RepositoryDataMode (values: open, private, subscription) to ProductVisibility (values: public, unlisted, restricted). Any DataConnection records in the database that still carry old RepositoryDataMode strings will now fail Zod validation when parsed.

Since this field is explicitly unenforced, the practical blast radius is limited — but if any code path calls DataConnectionSchema.parse(...) on a record with stale values, it will throw at runtime.

Options:

  1. Add a DB migration to update the values (e.g. openpublic, private/subscriptionrestricted)
  2. Use z.array(z.string()) as a soft type until the data is cleaned up, then tighten it back
  3. Use .catch([]) so stale values degrade gracefully rather than crashing

Also worth a rename follow-up: allowed_data_modes describing ProductVisibility values is confusing. allowed_visibility would be clearer.

Comment thread fixtures/products.json
Comment on lines +44 to +46
if (!isAuthorized(session, product, Actions.GetRepository)) {
notFound();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good catch — this authorization gate was previously missing from the layout. Without it, a user who typed a restricted product URL directly could see the product shell (header, breadcrumbs, upload zone) even though the data API would reject their requests. Now they correctly get a 404 instead.

@claude
Copy link
Copy Markdown

claude Bot commented May 4, 2026

@/home/runner/work/source.coop/source.coop/.claude-review-summary.txt

@claude
Copy link
Copy Markdown

claude Bot commented May 4, 2026

Claude finished @alukach's task in 3m 17s —— View job


Claude finished @alukach's taskView job


  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files
  • Post inline comments (2 comments)
  • Post summary comment

Review posted. Key findings:

  • One concern before merging: DataConnection.allowed_data_modes type changed from RepositoryDataModeProductVisibility; existing production DB records with old string values will fail Zod parsing.
  • Positive: the new isAuthorized gate in the product layout is a good security improvement that was previously missing; profile/listing pages correctly filter via isAuthorized too.
  • Minor: subscription-org-repo-id fixture is now a duplicate restricted fixture unreferenced by any test; field naming allowed_data_modes with ProductVisibility type is misleading.

Comment thread src/types/data-connection.ts Outdated
read_only: z.boolean(),
allowed_data_modes: z.array(z.nativeEnum(RepositoryDataMode)),
// NOTE: allowed_data_modes is currently unenforced
allowed_data_modes: z.array(z.nativeEnum(ProductVisibility)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Production DB records may fail Zod parsing after this change

allowed_data_modes previously held RepositoryDataMode values (open, private, subscription). It now holds ProductVisibility values (public, unlisted, restricted). Any DataConnection documents in the production database that still carry the old string values will throw when parsed through DataConnectionSchema.

Since the enforcement code for this field is commented out (route.ts:147-157), no runtime path today acts on the parsed value — but if any code path calls DataConnectionSchema.parse() on a stale record, it will throw.

Options before merging:

  1. DB migration: map openpublic, private/subscriptionrestricted in existing records.
  2. Soft type: use z.array(z.string()) as a transitional type until the data is clean, then tighten it.
  3. Graceful fallback: .catch([]) so stale values degrade to an empty array instead of crashing.

The fix: conform to product visibility type for allowed_data_modes commit updated the fixtures correctly, but production data is a separate concern.

Comment thread fixtures/products.json Outdated

"featured": 0
},
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

subscription-org-repo-id fixture is now authz-identical to private-org-repo-id and is unused in tests

With data_mode removed, both subscription-org-repo-id and private-org-repo-id have visibility: "restricted" and no other distinguishing field. A grep across the test suite shows subscription-org-repo-id is not referenced in any test assertion — it exists in the fixture file but exercises no distinct code path.

Consider removing it to reduce confusion, or add a comment explaining why it's kept (e.g., reserved for a future distinction).

@claude
Copy link
Copy Markdown

claude Bot commented May 4, 2026

Claude finished @alukach's task in 3m 36s —— View job


Claude finished @alukach's task


  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files
  • Post inline comments (2 comments)
  • Post summary comment

Review posted. Key findings:

  • Addressed: subscription-org-repo-id duplicate fixture has been removed — good.
  • One concern before merging: DataConnection.allowed_data_modes element type changed from RepositoryDataModeProductVisibility; existing production DB records with old string values will fail Zod parsing.
  • New minor issue: All 7 product fixtures still carry a config sub-object in their mirror definitions, but ProductMirrorSchema no longer includes that field. Zod strips it silently (no runtime error — credentials.ts/page.tsx already read from DataConnection.details), but the fixtures now diverge from the schema.
  • Positive: the isAuthorized gate in the product layout correctly closes the gap where restricted product shells were visible without auth; unlisted semantics are handled correctly across all three auth functions.

Comment thread src/types/data-connection.ts Outdated
read_only: z.boolean(),
allowed_data_modes: z.array(z.nativeEnum(RepositoryDataMode)),
// NOTE: allowed_data_modes is currently unenforced
allowed_data_modes: z.array(z.nativeEnum(ProductVisibility)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Production DB records will fail Zod parsing after this change

allowed_data_modes previously held RepositoryDataMode values (open, private, subscription). It now holds ProductVisibility values (public, unlisted, restricted). Any DataConnection documents in production that still carry old string values will throw when parsed through DataConnectionSchema.

Since no enforcement code runs today against this field, the blast radius is limited to a hard parse failure on any code path that calls DataConnectionSchema.parse() on a stale record.

Options before merging:

  1. DB migration: map openpublic, private/subscriptionrestricted
  2. Soft type: z.array(z.string()) until data is clean, then tighten it back
  3. Graceful fallback: .catch([]) so stale values degrade to an empty array instead of throwing

The fix: conform to product visibility type for allowed_data_modes commit updated fixtures correctly, but production data is a separate concern.

Comment thread src/types/product.ts
endpoint: z.string().optional(), // For MinIO/Ceph
}),
// Mirror-specific settings
is_primary: z.boolean(), // Is this the primary mirror?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

config removed from schema but all fixtures and production records still carry it

ProductMirrorSchema no longer includes the config sub-object. Zod uses strip mode by default, so parsing won't throw — the field is silently dropped.

Two things worth tracking:

  1. All 7 fixtures in fixtures/products.json still have config: { region, bucket } in their mirror definitions. These fixtures now diverge from the schema — a developer reading them will be confused about what a valid mirror looks like.
  2. Production records have the same field. If any code path reads a product, parses it through ProductSchema, and writes it back (e.g. an update endpoint), the config field will be silently removed from the DB record.

credentials.ts and page.tsx already read bucket/region from DataConnection.details, not from mirror.config, so there's no runtime regression from the code side. But the fixture cleanup would prevent future confusion. Fix this →

@alukach alukach marked this pull request as ready for review May 4, 2026 23:51
@claude
Copy link
Copy Markdown

claude Bot commented May 4, 2026

Claude finished @alukach's task in 3m 16s —— View job


  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files
  • Post inline comments (1 comment)
  • Post summary comment

Review posted. Key findings:

  • Must-do before merging: Run scripts/backfill-allowed-data-modes.ts on production before deploying — DataConnection.allowed_data_modes element type changed from RepositoryDataModeProductVisibility, so stale DB records will fail Zod parsing (already called out in the PR description).
  • Minor: All 7 product fixtures still carry config: { region, bucket } in their mirror definitions, but ProductMirrorSchema no longer includes that field. Zod strips it silently — no runtime error — but fixtures now diverge from the schema.
  • Minor: ProductVisibilitySchema is missing .openapi("ProductVisibility") — the enum won't appear as a named $ref component in the generated spec (inline comment posted).
  • Positive: The isAuthorized gate in the product layout correctly closes the gap where restricted product shells were visible without auth; listRepository correctly treats unlisted as non-listable-but-accessible.

@claude
Copy link
Copy Markdown

claude Bot commented May 4, 2026

Claude finished @alukach's task in 3m 46s —— View job


  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files
  • Post inline comments (1 comment on src/types/data-connection.ts)
  • Post summary comment

Review posted. Key findings:

  • Must-do before merging: DataConnectionSchema.parse() is called in three production routes (/api/v1/data-connections, /api/v1/data-connections/available, /api/v1/data-connections/[id]). A single un-migrated record breaks the entire list endpoint with a 500. Run the backfill script against production before deploying, not after.
  • Minor: All 6 product fixtures still carry a config sub-object in their mirror definitions, but ProductMirrorSchema no longer has that field — Zod strips it silently but the fixtures are now misleading.
  • Minor follow-up: allowed_data_modes field name is a misnomer now that it holds ProductVisibility values; allowed_visibility would be clearer.
  • Positive: the isAuthorized gate in the product layout is the core security fix; listRepository correctly handles unlisted semantics; backfill script is well-structured.

return a.every((v, i) => v === b[i]);
}

async function backfill(tableName: string, dryRun: boolean) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deployment ordering: run this before deploying the code change

The new z.nativeEnum(ProductVisibility) in DataConnectionSchema will cause DataConnectionSchema.parse() to throw for any record whose allowed_data_modes still contains old string values (open, private, subscription). This script must be run against the production table successfully before the code change is deployed.

The script itself looks well-written: the mapping is correct, the ?? [] guard handles missing fields, and the Set deduplication correctly collapses ["private", "subscription"]["restricted"]. Idempotency is preserved because LEGACY_TO_NEW only maps legacy keys — new values pass through unchanged.

Comment thread src/types/product.ts Outdated
})
.openapi("ProductDataMode");

export const ProductVisibilitySchema = z.nativeEnum(ProductVisibility);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ProductVisibilitySchema lost its OpenAPI name

The old ProductDataModeSchema used .openapi("ProductDataMode") which registered it as a named component in the generated spec. The new inline z.nativeEnum(ProductVisibility) has no .openapi() call, so it will be inlined everywhere it's referenced rather than appearing as a named $ref. Non-blocking, but if the OpenAPI spec is consumed by external clients a follow-up .openapi("ProductVisibility") would keep the spec cleaner:

Suggested change
export const ProductVisibilitySchema = z.nativeEnum(ProductVisibility);
export const ProductVisibilitySchema = z.nativeEnum(ProductVisibility).openapi("ProductVisibility");

@claude
Copy link
Copy Markdown

claude Bot commented May 4, 2026

@/home/runner/work/source.coop/source.coop/.claude-review-summary.txt

Comment thread src/types/data-connection.ts Outdated
read_only: z.boolean(),
allowed_data_modes: z.array(z.nativeEnum(RepositoryDataMode)),
// NOTE: allowed_data_modes is currently unenforced
allowed_data_modes: z.array(z.nativeEnum(ProductVisibility)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Backfill must run before deployment — parse failures will break these API routes

DataConnectionSchema.parse() is called in three production routes that will 500 on any record carrying legacy values:

  • GET /api/v1/data-connections (route.ts:43)
  • GET /api/v1/data-connections/available (available/route.ts:52)
  • GET /api/v1/data-connections/[id] ([data_connection_id]/route.ts)

A record with allowed_data_modes: ["open"] will fail z.nativeEnum(ProductVisibility) and throw in the map, turning the entire endpoint into a 500. Since filter runs before map in those routes, even a single un-migrated record breaks the whole list response.

The backfill script in this PR is the right fix. Run the backfill against production before deploying this code, not after.

Also worth a rename follow-up: now that this field holds ProductVisibility values, allowed_data_modes is a misnomer. allowed_visibility would be clearer — but that's a separate PR.

Comment thread src/types/product.ts Outdated
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
@claude
Copy link
Copy Markdown

claude Bot commented May 5, 2026

Claude finished @alukach's task in 3m 57s —— View job


  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files
  • Post inline comments (1 comment on src/types/data-connection.ts:160)
  • Post summary comment

Review complete. Key findings:

  • Resolved: ProductVisibilitySchema now has .openapi("ProductVisibility") ✅ and subscription-org-repo-id duplicate fixture is removed ✅
  • Must do before merging: Run scripts/backfill-allowed-data-modes.ts on production before deploying — DataConnectionSchema.parse() in the route handlers will 500 on any record with legacy allowed_data_modes values (open, private, subscription). The DB client uses a type cast, not a Zod parse, so failure surfaces in the API routes.
  • Minor: All 7 product fixtures still carry config: { region, bucket } in their mirror definitions, which ProductMirrorSchema no longer accepts (Zod strips silently — no runtime error).
  • Follow-up: allowed_data_modes field name is a misnomer now that it holds ProductVisibility values; allowed_visibility would be clearer.

Comment thread src/types/data-connection.ts Outdated
read_only: z.boolean(),
allowed_data_modes: z.array(z.nativeEnum(RepositoryDataMode)),
// NOTE: allowed_data_modes is currently unenforced
allowed_data_modes: z.array(z.nativeEnum(ProductVisibility)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deployment order: backfill must run before this code ships

dataConnectionsTable.listAll() returns raw DynamoDB items via a TypeScript cast (item as DataConnection) — no Zod parse at the DB layer. The parse happens in the route handlers:

  • route.ts:39DataConnectionSchema.omit({authentication: true}).parse(connection) — runs on every connection in the list response
  • available/route.ts:47,52 — same pattern
  • [data_connection_id]/route.ts:128 — on PUT

Any record still carrying legacy allowed_data_modes values (open, private, subscription) will throw at .parse(), turning the entire list endpoint into a 500 — because the throw happens inside .map() after the filter has already passed.

The backfill script in this PR is the right fix. Run it successfully against production before deploying — not after.

Also: allowed_data_modes is now a misnomer since it holds ProductVisibility values. A follow-up rename to allowed_visibility would help future readers, but that's a separate PR.

@claude
Copy link
Copy Markdown

claude Bot commented May 5, 2026

Claude finished @alukach's task in 2m 28s —— View job


PR Review

Good incremental progress — the last push (fb0e9d3) was a cosmetic cleanup (trailing blank lines in fixtures). No new issues introduced.

Resolved since prior review cycles ✅

  • ProductVisibilitySchema now has .openapi("ProductVisibility") — named $ref will appear correctly in the generated spec
  • subscription-org-repo-id duplicate fixture removed

Must address before merging

Run the backfill script before deploying — parse failures will break API routes

DataConnectionSchema.parse() is called in three live route handlers. A single un-migrated record (allowed_data_modes: ["open"]) will fail z.nativeEnum(ProductVisibility) inside .map(), turning the entire list endpoint into a 500:

  • GET /api/v1/data-connectionsroute.ts:39,43
  • GET /api/v1/data-connections/availableavailable/route.ts:47,52
  • PUT /api/v1/data-connections/[id][data_connection_id]/route.ts:128

The backfill script is well-written (correct mapping, Set deduplication, paginated scan, dry-run support, idempotent). Run it successfully against production before deploying this code.

Minor (non-blocking)

config still present in all fixture mirrors (fixtures/products.json)

All 7 product fixtures carry config: { region, bucket } inside each mirror entry, but ProductMirrorSchema no longer defines that field. Zod strips it silently — no runtime error — but the fixtures now describe a shape the schema no longer accepts, which will confuse future contributors. Fix this →

allowed_data_modes field name is a misnomer

The field now holds ProductVisibility values. A follow-up rename to allowed_visibility would help future readers — not required for this PR.

Positive notes

  • The isAuthorized(session, product, Actions.GetRepository) gate in layout.tsx:44–46 correctly closes the gap where restricted product shells were accessible without auth — this is the core security fix.
  • listRepository correctly restricts unlisted from appearing in listings while allowing direct access — visibility semantics are correct across all three auth functions.
  • Backfill script is production-ready.

Comment thread src/types/data-connection.ts Outdated
read_only: z.boolean(),
allowed_data_modes: z.array(z.nativeEnum(RepositoryDataMode)),
// NOTE: allowed_data_modes is currently unenforced
allowed_data_modes: z.array(z.nativeEnum(ProductVisibility)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deployment order: backfill must run before this code ships

DataConnectionSchema.parse() is called in three production routes that will 500 on any record carrying legacy values:

  • GET /api/v1/data-connectionsroute.ts:39,43
  • GET /api/v1/data-connections/availableavailable/route.ts:47,52
  • PUT /api/v1/data-connections/[id][data_connection_id]/route.ts:128

A single record still holding allowed_data_modes: ["open"] will fail z.nativeEnum(ProductVisibility) inside .map(), turning the entire list endpoint into a 500. Run scripts/backfill-allowed-data-modes.ts successfully against production before deploying this code, not after.

Also: allowed_data_modes is now a misnomer since it holds ProductVisibility values. A follow-up rename to allowed_visibility would help future readers.

@claude
Copy link
Copy Markdown

claude Bot commented May 5, 2026

Claude finished @alukach's task in 2m 53s —— View job


  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files
  • Post inline comments (1 comment on src/types/data-connection.ts:160)
  • Post summary comment

Review complete. The latest two commits (fb0e9d3, 973d6ef) are purely cosmetic cleanup — no new issues introduced.

Key findings:

  • Must do before merging: Run scripts/backfill-allowed-data-modes.ts on production before deploying — DataConnectionSchema.parse() in the route handlers will 500 on any record with legacy allowed_data_modes values (open, private, subscription).
  • Minor: All 7 product fixtures still carry config: { region, bucket } in their mirror definitions, which ProductMirrorSchema no longer accepts (Zod strips silently — no runtime error).
  • Follow-up: allowed_data_modes field name is a misnomer now that it holds ProductVisibility values; allowed_visibility would be clearer.
  • Positive: isAuthorized gate in layout.tsx:44–46 correctly closes the gap where restricted product shells were accessible without auth; listRepository handles unlisted semantics correctly.

Comment thread src/types/data-connection.ts Outdated
read_only: z.boolean(),
allowed_data_modes: z.array(z.nativeEnum(RepositoryDataMode)),
// NOTE: allowed_data_modes is currently unenforced
allowed_data_modes: z.array(z.nativeEnum(ProductVisibility)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deployment order: backfill must run before this code ships

DataConnectionSchema.parse() is called in three live route handlers that will 500 on any record carrying legacy values:

  • GET /api/v1/data-connectionsroute.ts:37,43
  • GET /api/v1/data-connections/availableavailable/route.ts:47,52
  • PUT /api/v1/data-connections/[id][data_connection_id]/route.ts:128

A single un-migrated record (allowed_data_modes: ["open"]) fails z.nativeEnum(ProductVisibility) inside .map(), turning the entire list endpoint into a 500. Run scripts/backfill-allowed-data-modes.ts against production before deploying this code, not after.

Also worth a follow-up rename: allowed_data_modes is a misnomer now that it holds ProductVisibility values — allowed_visibility would be clearer.

@claude
Copy link
Copy Markdown

claude Bot commented May 5, 2026

PR Review

The latest commit (6907663) completes the rename of allowed_data_modesallowed_visibilities across all code, tests, fixtures, and the backfill script. All previously flagged items are now resolved.

Resolved across review cycles ✅

  • ProductVisibilitySchema has .openapi("ProductVisibility") — named $ref in generated spec ✅
  • subscription-org-repo-id duplicate fixture removed
  • config sub-object removed from all product fixture mirrors
  • allowed_data_modes renamed to allowed_visibilities — clearer field name ✅

Must address before merging

Run the backfill script before deploying — parse failures will 500 your API routes

DataConnectionSchema.parse() is called in three live routes. Any production record that still has allowed_data_modes (old field name) but no allowed_visibilities will fail because allowed_visibilities is a required field in the schema — turning the entire list endpoint into a 500:

  • GET /api/v1/data-connectionsroute.ts:37,43
  • GET /api/v1/data-connections/availableavailable/route.ts:45,52
  • PUT /api/v1/data-connections/[id][data_connection_id]/route.ts:128

The backfill script (scripts/backfill-allowed-visibilities.ts) handles both concerns atomically: it maps legacy values (openpublic, private/subscriptionrestricted) and performs the field rename (SET allowed_visibilities REMOVE allowed_data_modes) in a single DynamoDB update. Run it against production before deploying.

Note: The PR description still references the old script name scripts/backfill-allowed-data-modes.ts — the script has since been renamed to scripts/backfill-allowed-visibilities.ts. Update the description before merging to avoid confusion.

Minor (non-blocking)

Commit message typo (6907663): message reads "rename allowed_data_modes to allowed_visbilities" (missing i). The code itself is correct — just a cosmetic issue in the git log.

Positive notes

  • The isAuthorized(session, product, Actions.GetRepository) gate in layout.tsx:44–46 correctly closes the gap where restricted product shells were accessible without auth — this is the core security fix.
  • listRepository correctly restricts unlisted from appearing in listings while still allowing direct access — visibility semantics are correct across all three auth functions.
  • Backfill script is production-ready: correct value mapping, Set deduplication, paginated scan, dry-run support, idempotent.

@claude
Copy link
Copy Markdown

claude Bot commented May 5, 2026

Claude finished @alukach's task in 2m 16s —— View job


  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files
  • Post inline comments (none needed — all prior inline concerns resolved)
  • Post summary comment

Review complete. The latest commits (5b4ccaf, 973d6ef) resolved the last remaining minor issue — config sub-objects have been removed from all product fixture mirrors. All previously flagged minor issues are now resolved.

One thing still required before merging: run scripts/backfill-allowed-data-modes.ts on production before deploying — DataConnectionSchema.parse() in the route handlers will 500 on any record with legacy allowed_data_modes values (open, private, subscription). This is already called out in the PR description.

@claude
Copy link
Copy Markdown

claude Bot commented May 5, 2026

Claude finished @alukach's task in 2m 31s —— View job


  • Clean up prior review artifacts
  • Read CLAUDE.md and gather context
  • Review changed files (latest commit: 6907663 refactor: rename allowed_data_modes to allowed_visibilities)
  • Post inline comments (none needed — all prior inline concerns resolved)
  • Post summary comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Data Product Visible Even Though Set to Restricted

1 participant