Skip to content

console: switching organization can land a member on a chrome-less "no apps" page — resolveLandingPath ignores per-user app access #8091

Closed as not planned

Description

@baozhoutao

Switching the active organization (and accepting an invitation, which switches as its last step) can land the user on a page with no header, no navigation and no workspace switcher — there is no way back except editing the URL by hand.

Repro

  1. User A (owner of workspace 甲) invites user B as member.
  2. B accepts the invitation, then creates a workspace of their own (B's workspace, where B is owner).
  3. As B, from /_console/home of B's workspace, open the workspace switcher → select 甲.

Actual

B lands on /_console/apps/setup and sees a bare page:

尚未配置应用 — 当前没有任何已注册的应用。请创建您的第一个应用,或前往系统设置进行配置。

document.querySelectorAll('header').length === 0 — no top bar, no workspace switcher, no way to switch back. The same page is reached right after accepting an invitation (AcceptInvitationPage calls navigate('/home'), but the org switch triggers a full-page load to the console root, which re-resolves the landing).

/_console/home in the very same state renders correctly: top bar, workspace switcher, and a friendly empty state ("还没有应用 — 工作区正在准备中——管理员共享给你的应用会显示在这里。"). So the landing target is the problem, not the workspace.

Expected

Switching into an organization never strands the user. If the resolved landing app is not accessible to the current user in the target org, fall back to /home.

Cause

WorkspaceSwitcher does a full-page navigation to the console root so RootLandingRedirect can resolve the landing (packages/app-shell/src/layout/WorkspaceSwitcher.tsx:97). resolveLandingPath then picks the app purely from app metadata, with no notion of whether the current user may enter it:

// apps/console/src/components/RootLandingRedirect.tsx
const visible = list.filter((a) => a.active !== false && a.hidden !== true);
if (visible.length === 1) return `/apps/${visible[0].name}`;   // ← rule 2

In a fresh workspace com.objectstack.setup is the only visible app, so rule 2 sends every user to /apps/setup. A member has no access to it (sys_permission_set reads 403 for positions [org_member, everyone]), and the app surface renders the chrome-less empty state above.

Note the resolver is documented as a product decision ("a build/dev-time PRODUCT decision, declared in metadata — not a runtime, per-tenant preference"). That is fine for choosing which app a product lands in; the gap is that the result is not checked against the current user's access before redirecting.

Suggested fix

Either filter the candidate list by the user's app access before applying rules 1–2, or make the app surface itself bounce to /home (with the shell chrome) when the app is not accessible, instead of rendering a chrome-less empty state. The second also covers a hand-typed /_console/apps/<inaccessible> URL.

Environment

Local dev server http://localhost:8080, multi-org enabled, zh locale, observed 2026-08-12. The server was started by the maintainer and its exact commit is not verified; the source quoted above is the current objectui main checkout.

Activity

  1. baozhoutao commented on Aug 12, 2026

    @baozhoutao
    ContributorAuthor

    Found in the same multi-org walkthrough (sign-up → create workspace → invite → second user accepts → second workspace → switch → isolation/authz probes): #8090, #8091, #8092, #8093, #8094.

  2. baozhoutao commented on Aug 12, 2026

    @baozhoutao
    ContributorAuthor

    Two further findings from the same pass, filed for the platform to rule on: #8095, #8096.

  3. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Moved to objectstack-ai/objectui#4473 (rebuilt verbatim + provenance header + a triage note linking the distinguishability half to #8013 / objectui#4252; native transfer is not available to this seat). Closing here as moved — authority: the repo-routing rule "Issue 住在修复落地的仓" (maintainer ruling 2026-08-10, recorded in the pm-dispatch skill); the fix lands in RootLandingRedirect / the app-surface fallback, both in objectui. The filer-applied domain:identity lane label does not travel — objectstack lane labels route objectstack packages only. 本评论来自分诊座位 Routine。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions