fix(appsync): require authentication for getSharedCampaign - #170
Merged
Conversation
getSharedCampaign had no authorization directive and its request mapping template performed no access check. The shared campaign code is the bearer capability for redemption (same model as profile invite codes): the createCampaign Lambda accepts the code from any authenticated user, and there is no per-user grant table for shared campaigns. The simplest correct authorization model is therefore authentication — matching getCatalog. - Add @aws_cognito_user_pools to Query.getSharedCampaign so the field is explicitly restricted to authenticated users even if additional auth providers are ever added to the API. - Add an explicit $util.unauthorized() guard in get_shared_campaign_request.vtl when no identity is present. - getCatalog is unchanged: authenticated-only, no owner/isPublic check. Tests: integration coverage rejecting unauthenticated getSharedCampaign and confirming an authenticated non-creator can still retrieve by code (the existing redemption flow).
dmeiser
force-pushed
the
fm/KW-FIX-SHARED-CAMPAIGN-AUTH
branch
from
August 23, 2026 23:59
a1bc1de to
8de8fe9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #169
Intent
Fix the authorization gap on getSharedCampaign (issue #169) so it is no longer accessible to just anyone holding a shared campaign code. getCatalog(catalogId: ID!) is intentionally accessible to any authenticated user and must NOT be changed - it stays authenticated-only with no owner/isPublic check. Required behavior: only users granted access to the campaign via the share/invite model should be able to redeem/view it; the brief delegated choosing the simplest correct authorization model (either authentication plus a share-record check, or binding redemption to the invite/share model). Decision made: the data model has no per-user grant table for shared campaigns, and the createCampaign redemption Lambda accepts the code from any authenticated user - the shared campaign code is a bearer capability, the same model as profile invite codes (redeemProfileInvite requires only code plus authentication). A share-record check would require a new grant/invite system and would break first-time redemption, so authentication is the simplest correct model, which the issue explicitly lists as an acceptable option. Implemented: @aws_cognito_user_pools on Query.getSharedCampaign, an explicit unauthorized guard on missing identity in get_shared_campaign_request.vtl, and integration tests proving unauthenticated access is rejected while an authenticated non-creator can still retrieve by code (existing redemption flow). Acceptance criteria: unauthorized/unauthenticated access to getSharedCampaign is rejected; existing authorized flows continue to work; getCatalog unchanged.
What Changed
@aws_cognito_user_poolstoQuery.getSharedCampaignintofu/application/schema/schema.graphqland an explicit$ctx.identityunauthorized guard inget_shared_campaign_request.vtlso only authenticated users can view/redeem a shared campaign by its code.AGENT.md, including the shared-campaign code-as-capability model, and fixed the brokenAGENT.mdlink indocs/SCHEMA.md.Risk Assessment
Testing
I inspected the diff and verified getSharedCampaign now has
@aws_cognito_user_poolsand an explicit$ctx.identityunauthorized guard in its VTL, while getCatalog remains authenticated-only with no owner/isPublic check. The local JS resolver suite passes. I attempted the new sharedCampaignCrud integration tests, but the harness skipped all 15 tests because AWS credentials have expired and no explicitTEST_APPSYNC_ENDPOINT/ Cognito env vars are configured, so live AppSync end-to-end evidence could not be collected in this environment. Transient test dependencies were removed and the worktree is clean.Evidence: Schema and VTL verification
Evidence: Integration test changes
Evidence: JS resolver unit test results
Evidence: Integration test run failure (expired AWS session)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
tofu/application/schema/schema.graphql:435- Sibling query findSharedCampaigns remains unauthenticated and returns the same SharedCampaign type (including catalogId, unit info, createdBy, etc.) without requiring a bearer code or any identity check. With AppSync configured as AMAZON_COGNITO_USER_POOLS/default_action=ALLOW, fields lacking @aws_cognito_user_pools are publicly reachable, so the same unauthenticated data-exposure failure addressed for getSharedCampaign is still reachable via this discovery path.tests/integration/resolvers/sharedCampaignCrud.integration.test.ts:441- Live AppSync integration test evidence for getSharedCampaign authorization could not be collected: the AWS session is expired and no explicit .env / AppSync endpoint / Cognito credentials are configured in this environment. The sharedCampaignCrud integration tests exist and correctly assert both unauthenticated rejection and authenticated non-creator retrieval by code, but all 15 tests in the file were skipped. Decide whether to re-authenticate AWS locally or rely on the CI ephemeral-environment run for end-to-end proof.git diff 0746924f7c1c7a0fac3226a23391225373966713..99f593a37f82f5f29cb90ceec4e3e98b283d77c5to review the changed filesgrep -n "getCatalog\|getSharedCampaign" tofu/application/schema/schema.graphqlto verify schema directivesReadtofu/application/appsync/mapping-templates/get_shared_campaign_request.vtlto verify the$ctx.identityguardReadtofu/application/appsync/mapping-templates/get_catalog_request.vtlto confirm no identity check was addednpm run test:js-resolvers— local JS resolver unit tests (36 passed)npx vitest --run resolvers/sharedCampaignCrud.integration.test.ts— attempted the focused integration test file; blocked by expired AWS credentials (CredentialsProviderError: Your session has expired)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.