EmbedScoped: allow /embed/builder re-entry with a pinned scope

EmbedBuilderController is the JWT re-entry point but runs behind
EmbedScoped's enforce_embed_scope! before_action. Once a prior open
pins an embed_scope into the session cookie, re-opening the builder
(the same template again, or a different one) hit `head :forbidden`
because /embed/builder was not in UNSCOPED_ALLOW — the concern locked
out its own re-entry endpoint. First open worked; every reopen 403'd.

The token, not the session, authenticates /embed/builder, so always
let it through; EmbedBuilderController then overwrites the scope from
the fresh token. Add a request spec covering the re-entry/re-scope case.
pull/697/head
shipeasy-ai 2 months ago
parent aa1eb1413c
commit b2d7199756

@ -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)},

@ -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

Loading…
Cancel
Save