diff --git a/app/controllers/concerns/embed_scoped.rb b/app/controllers/concerns/embed_scoped.rb index cc60829e..af71d78e 100644 --- a/app/controllers/concerns/embed_scoped.rb +++ b/app/controllers/concerns/embed_scoped.rb @@ -26,6 +26,13 @@ module EmbedScoped # create/upload flow, the signing/asset surfaces, and the few endpoints the # builder's own browser code calls. None expose another account's templates. UNSCOPED_ALLOW = [ + # The JWT re-entry point itself. It runs BEHIND this before_action, so once + # a prior scope is pinned in the session cookie, re-opening the builder + # (the same template again, or a different one) would 403 here before + # EmbedBuilderController gets to re-establish the scope from the new token. + # The token — not the session — authenticates this endpoint, so always let + # it through; the controller then overwrites the scope. + %r{\A/embed/builder(?:[/?]|\z)}, %r{\A/new(?:[/?]|\z)}, %r{\A/templates/new(?:[/?]|\z)}, %r{\A/templates_uploads?(?:[/?]|\z)}, diff --git a/spec/requests/embed_builder_spec.rb b/spec/requests/embed_builder_spec.rb index 5f08e073..5705553b 100644 --- a/spec/requests/embed_builder_spec.rb +++ b/spec/requests/embed_builder_spec.rb @@ -113,5 +113,30 @@ describe 'Embed builder' do expect(response).to have_http_status(:forbidden) end + + it 're-enters /embed/builder with a pinned scope and re-scopes to the new template' do + first = create(:template, account:, author: user, external_id: 'first') + second = create(:template, account:, author: user, external_id: 'second') + + # First open pins the embed scope to `first` in the session cookie. + get embed_builder_path, params: { token: token(template_id: first.id) } + expect(response).to redirect_to(edit_template_path(first)) + + # Re-opening the builder for a DIFFERENT template (the cookie still carries + # the `first` scope) must NOT 403 — /embed/builder is the re-entry point + # that re-establishes scope from the new token. Regression: it used to + # 403 here because the stale scope blocked its own re-entry endpoint. + get embed_builder_path, params: { token: token(template_id: second.id) } + expect(response).not_to have_http_status(:forbidden) + expect(response).to redirect_to(edit_template_path(second)) + + # The new scope now allows the second template's editor. + begin + get edit_template_path(second) + expect(response).not_to have_http_status(:forbidden) + rescue ActionView::Template::Error, Shakapacker::Manifest::MissingEntryError + # Reached view rendering => the re-scoped template was allowed through. + end + end end end