Skip to content

feat(webui): prompt anonymous visitors to log in on the reviews tab - #2148

Draft
netomi wants to merge 1 commit into
mainfrom
feat/reviews-login-prompt
Draft

feat(webui): prompt anonymous visitors to log in on the reviews tab#2148
netomi wants to merge 1 commit into
mainfrom
feat/reviews-login-prompt

Conversation

@netomi

@netomi netomi commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Draft — the nudge half of #1975. Opening early for feedback; expect refinements.

The problem

On the reviews tab, renderButton() bailed out whenever there was no user, so the slot where Write a Review sits was simply blank for a signed-out visitor:

if (!context.user || !reviewList) {
    return '';
}

The path in the issue is arriving from an IDE's star-rating link, which lands you on this tab without ever passing the header — so there was nothing on the page to act on, and nothing saying an account is needed. On an extension with no reviews yet it reads worse, since "Be the first to review this extension" invites an action that has no affordance anywhere on screen.

The change

Handle the signed-out case first and render the existing LoginComponent in that slot, labelled Log in to Review. Reusing that component rather than hand-rolling a button means no new login plumbing:

  • one configured provider → a direct link to /oauth2/authorization/{provider};
  • several → the existing provider-picker dialog;
  • none → nothing rendered, the same loginProviders guard menu-content.tsx and publish-page.tsx already use.

The !reviewList check moved below it, since the prompt doesn't depend on the list having loaded — only the signed-in branches do, to know whether this user already reviewed.

Be the first to review this extension is left as-is: with a login button directly above it, it now reads as an invitation with a real affordance.

Scope: this is only the nudge

@netomi's suggestion in the issue was to also redirect back to the current URL after login. That is deliberately not here — login still lands on the landing page as before. It needs server-side work, and two approaches that look like they'd work don't:

  • SavedRequestCustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler so it would honour one, but the entry point is Http403ForbiddenEntryPoint (SecurityConfig.java:95), which returns a bare 403 and saves nothing. The webui also links straight to the authorization endpoint, so nothing is ever saved.
  • targetUrlParametersetTargetUrlParameter(...) reads the parameter off the request that reaches the success handler, i.e. the OAuth2 callback /login/oauth2/code/{provider}, whose URL is pinned by the registered redirect_uri. A parameter appended to the authorization URL doesn't survive the round trip.

So it needs state carried across the dance — a session attribute read back in determineTargetUrl, or a custom AuthorizationRequestRepository putting the target in the OAuth2 state. Either way the stored target must be validated as a same-origin relative path (reject a scheme, protocol-relative //, backslashes) or it's an open redirect.

One thing worth deciding before that part is scoped: CustomAuthenticationSuccessHandler.java:35 unconditionally sends the eclipse provider to /user-settings/profile. That override runs before any return-to-URL logic would, so wherever eclipse is an enabled login provider the redirect-back would silently not work. In-repo deploy configs register only github, and github is the sole built-in default in OAuth2AttributesConfig, so eclipse comes from out-of-repo config — someone who knows the production config should confirm whether open-vsx.org enables it for login.

Testing

New webui/test/unit/pages/extension-detail/extension-detail-reviews.spec.tsx, 4 cases: single-provider direct link, multi-provider picker, no-providers suppression, and signed-in still getting Write a Review. Confirmed not vacuous — with the source change stashed, the two new-behaviour cases fail and the other two pass.

Full webui suite green (51 files, 264 tests); tsc, eslint and prettier clean.

Known loose end

The container's responsive rule at ≤360px is '& button': { maxWidth: '12rem' }, and MUI renders the single-provider case as an <a>, not a <button> — so that cap doesn't match it. The prompt is wrapped in a <Box> mirroring the existing Write a Review branch, which makes the Box the stretching flex child and leaves the inline-flex anchor content-sized, so it should behave. Reasoned, not eyeballed — worth a look in a narrow viewport.

Refs #1975

🤖 Generated with Claude Code

The reviews tab gave a signed-out visitor no hint that reviewing needs an
account: renderButton() returned an empty string whenever there was no
user, so the slot where "Write a Review" sits was simply blank. Landing
there from an IDE's star-rating link - which is how you get to this tab
without ever passing the header - left nothing to act on. On an extension
with no reviews yet it was worse, since "Be the first to review this
extension" invited an action with no affordance anywhere on the page.

Render the existing LoginComponent in that slot instead, labelled "Log in
to Review". It already handles both shapes: a single configured provider
becomes a direct link, several open the provider picker. Suppressed on a
registry with no login providers configured, matching the guard the header
and the publish page already use.

This is the nudge asked for in #1975. Redirecting back to the extension
afterwards is deliberately not part of it - the login lands on the
landing page as before, since carrying a target across the OAuth2 dance
needs server-side work.

Refs #1975

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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