Skip to content

feat(auth): verify membership on onboard rescan, configurable joined-screen links - #662

Open
adenarc wants to merge 1 commit into
NEARBuilders:mainfrom
adenarc:feat/onboard-rescan-and-success-link
Open

adenarc wants to merge 1 commit into
NEARBuilders:mainfrom
adenarc:feat/onboard-rescan-and-success-link

Conversation

@adenarc

@adenarc adenarc commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Closes #583. For NEARBuilders/citynode.app#399 and NEARBuilders/citynode.app#394.

What it does

Rescan by someone who left (citynode.app#399)

  • redeemOnboardingCode with alreadyRedeemed: true: the onboard page now checks a fresh listOrganizations against the exact organizationId from the redeem response (redeemOnboardingCode: include the organizationId in the output #550). Still a member → the joined screen as today. Not a member → "You’re no longer a member of this community. Ask an organizer for a new code or an invitation." No toast, no joined state.
  • Since fix(auth): close email-boundary bypasses on raw organization routes and onboarding re-redeem #615 the server already refuses a prior redeemer who left. That refusal was a plain FORBIDDEN, same as revoked/expired/unapproved, so the page showed the raw message under "Could not join". It's now its own MembershipEnded error with data.action: "rejoin-organization", and the page shows the copy above. Message unchanged.
  • Both paths clear a stale active organization (one that isn't in the fresh org list), refresh the session and invalidate the router. If the lookup or the clear fails, the page shows a retryable error instead of success or "no longer a member".
  • First-time join is untouched: one redeem call, no extra request, no extra screen.
  • The lookup is verifyOrganizationMembership in ui/src/lib/onboard-membership.ts.

Configurable links (#583) and community channels (citynode.app#394)

New optional onboard auth variable:

onboard: {
  links: {
    next: { href: "/communities/{organizationId}", label: "Open your community" } | false, // default /build "Get build prompts"
    home: { href, label? },     // default /dashboard
    explore: { href, label? },  // default /explore, used on the error/status panels
  },
  community: { url: "/api/…/{organizationId}" }, // returns { name?, href?, channels?: [{ label, url }] }
}
  • {organizationId} is filled with the joined org's ID in next, home and community.url (not explore, since the error panels have no org). Hrefs must be app paths or https://. Bad config fails the auth variables check at boot, and the page falls back to the defaults.
  • When links.next is configured, the joined title becomes "You're in" instead of "Ready to start building?".
  • When community.url is set, the joined screen fetches it for the joined org and shows "Visit {community}" plus the channels (new tab, rel="noopener noreferrer"). A failed or invalid response shows nothing extra and never blocks the joined screen. It's never fetched for someone who left.
  • New strings are in en/es/fr/zh. The rest of the page is still English (auth plugin UI: localize settings and onboard pages (es/fr/zh) #582).

Why a variable and not a UI slot: there's no extension-slot mechanism for plugin UIs today, and #583 only needs per-app destinations. A variable plus one JSON endpoint covers #583 and #394 without a new federation surface. If you'd rather have a slot, the page's link and community bits are small to move.

Tested

  • Component tests (-onboard.test.tsx): still-member rescan, left-member rescan (matched by ID, with a same-name org in the list), the server's membership-ended refusal, valid active org left alone, lookup failure + retry, clear failure, default links, configured links, next: false, configured explore on error panels, unsafe href ignored, community with channels, without channels, failed lookup, no lookup after leaving, happy path makes no membership request.
  • Integration test: the re-redeem refusal carries FORBIDDEN + data.action.
  • Browser regression spec onboard-rescan.spec.ts (dev:ssr): join → rescan as member → member removed with stale active org → rescan shows "no longer a member" and the active org is cleared.
  • pnpm run typecheck, pnpm run lint, pnpm --filter @everything-dev/auth-plugin test (452), ui/api/e2e/framework tests. Locally, the framework types-gen tests need TMPDIR on the same disk as the checkout (EXDEV rename otherwise), and the process-kill test flaked once under load; both pass on their own and aren't touched here.
  • The new browser spec isn't in the dev:ssr smoke list, so CI runs it on start:ssr/backcompat. I ran it locally by adding it to dev:ssr temporarily.

One call for you: when the redeem refuses a member who left, the page doesn't know the org ID (FORBIDDEN data only carries action), so it clears the session's active org only if that org isn't in the caller's fresh org list. If platform admins can have a non-member active org, that would clear it on this one screen. Happy to narrow it if that matters.

Screenshots (375px, from the regression spec)

Rescan while still a member Rescan after being removed
Rescan while still a member Rescan after leaving

Not tested

  • A real passkey sign-up on the onboard page (sessions come from the regression fixtures).
  • The Visit button and channels in a browser: no app in this repo serves a community endpoint, so they're covered only by component tests.
  • City Node's side: the onboard config in its bos.app.ts, its community endpoint, and the platform pin bump come after a release.

…n links configurable

- a rescan by someone who left now says they're no longer a member instead of "You're in"
- redeem refusal for a prior redeemer who left is its own MembershipEnded error (FORBIDDEN, data.action rejoin-organization)
- onboard auth variable: links.next/home/explore and community.url for a Visit button and channel links

This branch has not been deployed

No deployments
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.

auth plugin UI: configurable app-specific link on the onboard success screen

1 participant