Skip to content

plugin-auth: with local sign-in off the sign-in floor still counts email and password, and the direct sign-in door's landings lose ?redirect= and the IP allow-list page #33358

Open

Description

@objectstack-fleet

domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx

Filing gate: follow-up of #33183 / PR #33221, from contract review records 6879172654 (N7, N8), 6880597586 (the rate-limited landing) and 6881390951 (N13) on that PR. Each is a reproducible defect next to direct sign-in that the seat kept out of that PR's scope. An administrator can lock everyone out of a self-hosted system, and people who follow a deep link land on the home page instead of where they were going. ⛔ Not a claim; triage sets the lane and grade.

What happens today

  1. N7 · the sign-in floor counts a way in the page no longer offers. sign-in-method-floor.ts and the boot report count "email and password on" as a way in, mirroring what the sign-in page offers. With localSignIn: 'off' (from feat(plugin-auth): with single sign-on configured, a signed-out visit can go straight to the identity provider and back, with a configurable local sign-in path #33221) the page no longer offers it, so deleting the last identity provider passes the floor and leaves the page with "No sign-in method is available". On hosted the platform provider cannot be deleted; a self-hosted system with OS_AUTH_LOCAL_SIGN_IN=off can reach it.
  2. N8 · a client the IP allow-list blocks gets a raw body. The allow-list exempts /config so a blocked client still gets a clean page, but GET /api/v1/auth/sign-in/direct is a top-level navigation and is not exempt, so with direct sign-in on a blocked client lands on the raw 403 IP_NOT_ALLOWED JSON instead of the sign-in page.
  3. N8 · an identity-provider error loses ?redirect=. The error return address the door hands the vendor is the bare /login, so after a provider error the retry starts from /login without the deep link.
  4. The rate-limited landing loses ?redirect= the same way: ?error=direct_sign_in_rate_limited lands on /login without the original ?redirect=.
  5. N13 · the sign-out note can outlive a provider that does not send the browser back. createAuthClient.signOut writes a note in the tab's sessionStorage before the provider-logout navigation; the first AuthProvider mount reads and drops it within ten minutes. If the provider never returns the browser, the next load of the app in that tab within the window reads sessionEndedBySignOut: true, so LoginRedirect drops the deep link's ?redirect= and, with direct sign-in on, the page holds for one click. True with direct sign-in off as well.

What this card would build

  • Make the floor and the boot report count email and password only when the page offers it (localSignIn not off), and pin deleting the last provider under off as refused.
  • Give the direct sign-in door a clean blocked-client landing (exempt it like /config, or answer a redirect to the sign-in page with an error the page explains), pinned with the allow-list on.
  • Carry ?redirect= through the door's provider-error return address and its rate-limited landing, with a pin for each.
  • Bound N13: scope the note to the provider-logout round trip (for example, clear it on any load that is not the return), or keep ?redirect= when the flag is raised, and pin the deep-link case.

Seam

Seam: plugin-auth:sign-in-method-floor.ts + direct-sign-in.ts (error / rate-limited return addresses) + IP allow-list exemptions → ui/packages/auth signOutRoundTrip.ts / console ProtectedRoute.tsx (LoginRedirect).

Dedupe words: sign-in method floor localSignIn, direct sign-in IP_NOT_ALLOWED, direct_sign_in_rate_limited redirect, sessionEndedBySignOut deep link, signOutRoundTrip.

Activity

  1. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: first touch → domain:server · p2 · pm:queue · 2026-11-14T17:46Z

    ⛔ Not a claim.

    Area: A1 (plugin-auth)

  2. added
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Nov 14, 2026
  3. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: +lease:A1 · 2026-11-14T18:50Z

    ⛔ Not a claim; claim, grade and state unchanged.

    Area A1 under the area-lease pilot (#33059, table on #28677). The label replaces the text Area: line now that lease: labels exist. Decisions go area → domain:server#5.

  4. objectstack-fleet commented on Nov 15, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4d1NJYFGVi2FRztyBNo

    Claim: server area-lease pilot (#33059), area A1 (auth and identity), dispatched by seat domain:server#5 · p2 · executed by an os-dev subagent of the area A1 thread · posted at 2026-11-15T02:25Z
    Session: session_016WV4d1NJYFGVi2FRztyBNo (area A1 thread)
    Account: os-elon-musk
    Branch: claude/area-A1-33358
    Domain: domain:server
    Scope: this round is item 1 (N7: the sign-in method floor and the boot report count email and password only when the page offers it, localSignIn not off; deleting the last provider under off refused), and items 3 and 4 (the direct sign-in door's provider-error return address and its rate-limited landing carry ?redirect=) if they fit the same PR. Items 2 (IP allow-list landing) and 5 (N13 sign-out note) stay on the card for a later round; the PR says Part of #33358.
    File surface: packages/plugins/plugin-auth/src/sign-in-method-floor.ts, the boot report that mirrors it, direct-sign-in.ts (return addresses) and their tests, a changeset.

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 15, 2026
  6. objectstack-fleet commented on Nov 15, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4d1NJYFGVi2FRztyBNo
    os-dev-report

    {
     "issue": 33358,
     "status": "done",
     "branch": "claude/area-A1-33358",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/33623",
     "session": "session_016WV4d1NJYFGVi2FRztyBNo",
     "premise_still_valid": true,
     "summary": "Items 1, 3 and 4 of #33358 (PR says Part of #33358; items 2 and 5 remain). Item 1: sign-in-method-floor.ts now counts email and password toward the provider-delete door and the boot report only when the sign-in page offers it, via pageOffersLocalSignIn(config) = features.localSignIn !== 'off', the same served value and test LoginForm uses (getPublicConfig serves localSignIn only while direct sign-in is on); under 'off' the delete of the last provider answers 409 DELETE_RESTRICTED with a new 11-locale copy enterpriseSsoLocalOff (the old copy's remedy, turn email and password back on, does nothing there) and the boot report names OS_AUTH_DIRECT_SIGN_IN_LOCAL_PATH=hidden as recovery. Phone sign-in is likewise not counted under 'off' (bounded in-place fix: same class, same file, same claim, same gates; the page hides phone under 'off' too). Items 3/4: direct-sign-in.ts carriedRedirect(kept) reads redirect from the already-kept callbackURL, keeps it only if sent once and itself a keptReturnPath, and both the vendor errorCallbackURL and the door's own /login landing (rate-limited and unavailable alike) carry it; no or foreign redirect gives bare page as before. Card premise note: the env var is OS_AUTH_DIRECT_SIGN_IN_LOCAL_PATH (directSignIn.localSignIn), not OS_AUTH_LOCAL_SIGN_IN as the card and dispatch say; premise otherwise holds. Declared Clause-2 yes (narrowing), title carries !, changeset patch with BREAKING + adr-0087 not-required (no-migration-prescription), Metadata-protocol none, Skills-docs none. Claim file surface widened by content/docs/permissions/sso.mdx (two short paragraphs stating both rules); the claim cannot be edited by me, so it is named here.",
     "tests": "Head 2a03b651b6. pnpm --filter @objectstack/plugin-auth test (lock): Test Files 274 passed (274), Tests 5312 passed | 10 skipped (5322), VERDICT command-exit 0. Single files: src/sign-in-method-floor.test.ts 40/40 (new: composition delete under off refused 409 enterpriseSsoLocalOff row kept; CONTROL hidden delete 200 row gone; boot under off with no provider reports naming OS_AUTH_DIRECT_SIGN_IN_LOCAL_PATH=hidden; CONTROL hidden boot silent; judge case off/hidden/phone), src/direct-sign-in.test.ts 87/87 (new: errorCallbackURL keeps redirect; rate-limited landing keeps redirect; CONTROL no redirect = bare page for both; foreign redirect dropped). Ablations via scripts/ablation-replace.mjs (mutation landed, anchor 1 to 0, blob changed; restored blob == HEAD, git diff HEAD empty; tests import src so no dist leg): delete door reverted 2 failed/38; boot report reverted 2 failed/38; errorCallbackURL bare 1 failed/87; landing without carried redirect 1 failed/87.",
     "gates": "Local: plugin-auth build + typecheck VERDICT command-exit 0 (check:test-typecheck OK, ledger unchanged); eslint --no-inline-config on 4 changed .ts exit 0; check-nul-bytes OK. dispatch-gates --commands (97 families) all run: 94 exit 0; 3 exit 3 PREREQUISITE NOT MET = NOT MEASURED, left to CI: check:skill-examples (no client-react dist), check:dual-build-cjs-loads (packages unbuilt), check:type-check-debt (re-measure prerequisite). dispatch-gates --ran: 97 derived, 97 run, 0 UNRUN (my record carried no exit codes; codes above come from my per-gate log). CI on 2a03b651b6 at report time: Check Changeset success, Governed Surface Queue Guard success, Dogfood Regression Gate success, PR-body guards success, Lint PM dispatch-gates success, Spec property liveness success, Check PR Size success; Build Core, Lint ESLint/repo/docs/CI lanes, Test Core 1-8, Live E2E UI in_progress.",
     "dogfood_battery": "2a03b651b6 · os-verify-lock: VERDICT command-exit 0 (held 31m46s) · Test Files 467 passed | 3 skipped (470) · Tests 3950 passed | 42 skipped (3992) · no failures",
     "mcp_calls": "0",
     "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST issues/33623/assignees os-elon-musk; read-back 9709 bytes identical); post-stamped os-dev-report comment POST /repos/objectstack-ai/objectstack/issues/33358/comments. Plus git push of the branch.",
     "open_questions": [
      {
       "question": "os-dev.md says copy Clause-2/Metadata-protocol lines from the claim and write none when absent; the claim carried none, while the dispatch and AGENTS.md Post-Task Checklist step 3 require both declarations in PR body and changeset. Which governs?",
       "options": [
        "A AGENTS.md: declare per diff (what this PR did)",
        "B os-dev.md: zero-write when the claim omits them"
       ],
       "recommendation": "A, because AGENTS.md outranks other instruction files and Check Changeset reads the declaration; the claim template should carry them next time."
      }
     ],
     "security_findings": "none",
     "out_of_scope_findings": [
      "class: a · reach: auth settings save door (PUT settings auth) under directSignIn.localSignIn off with email and password on: saving google_enabled=false when Google is the last social provider passes signInMethodFloorPredicate (it returns early on emailPasswordAfter) and the page then shows No sign-in method is available · evidence: sign-in-method-floor.ts signInMethodFloorPredicate reads config.emailPassword.enabled only, not features.localSignIn; same N7 family, left out because its refusal copy (google) tells the admin to keep email and password on, which does nothing under off, so it needs its own copy · dedupe words: sign-in floor settings save localSignIn off, google_enabled last provider local sign-in off · carrier: #33358 (fold into the card as the settings-door half of item 1)",
      "carrier: #33358 · noted, not filed: enterpriseSsoUnverified 503 copy still says with email and password off; under off with it on the wording is slightly inaccurate (refusal itself is right)"
     ]
    }
  7. objectstack-fleet commented on Nov 15, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4d1NJYFGVi2FRztyBNo

    Scope note · area A1 · 2026-11-15T03:16Z

    PR #33623 (Part of #33358) covers items 1 (the provider-delete door and the boot report), 3 and 4. Still open on this card after it lands:

    • Item 2 (direct sign-in door under the IP allow-list) and item 5 (N13 sign-out note).
    • Item 1, settings-save half (found by the os-dev, not reproduced in a test): PUT auth settings with directSignIn.localSignIn: 'off' and email and password on — saving google_enabled=false when Google is the last social provider passes signInMethodFloorPredicate (it returns early on emailPasswordAfter and never reads features.localSignIn), and the page then shows "No sign-in method is available". Its refusal copy tells the admin to keep email and password on, which does nothing under off, so the fix needs its own copy.
    • Copy nit: enterpriseSsoUnverified (503) still says "with email and password off"; under off with email and password on the wording is slightly off, the refusal itself is right.
    • The card's env var OS_AUTH_LOCAL_SIGN_IN is OS_AUTH_DIRECT_SIGN_IN_LOCAL_PATH in the code.
  8. objectstack-fleet commented on Nov 15, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4d1NJYFGVi2FRztyBNo

    Release · area A1 · 2026-11-15T05:08Z

    PR #33623 merged (items 1, 3 and 4: the provider-delete door and the boot report under localSignIn: 'off', and ?redirect= carried through the direct sign-in door's provider-error and rate-limited landings). The pilot is draining, so the rest of this card goes back to the queue unclaimed.

    Resume-from: main after #33623. Remaining, as listed in the scope note (comment 6888442664):

    • Item 2: the direct sign-in door under the IP allow-list lands a blocked client on the raw 403 IP_NOT_ALLOWED body.
    • Item 5: the N13 sign-out note can outlive a provider that never sends the browser back.
    • Item 1, settings-save half: PUT auth settings under directSignIn.localSignIn: 'off' still counts email and password (signInMethodFloorPredicate never reads features.localSignIn); needs its own refusal copy, like enterpriseSsoLocalOff.
      Reuse pageOffersLocalSignIn / pageOffersEmailPassword from sign-in-method-floor.ts for the settings-save half.
  9. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 15, 2026
  10. objectstack-fleet commented on Nov 15, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cytdN5jRpgKX58AY4d

    Release: area A1's claim 6887922269, 2026-11-15T05:08Z — the pilot drains (no new p2 work from 04:00Z); the built part landed, the rest was not started.
    Resume-from: main@a27d88df617a1aee1fa12dee92ab79b4435bd9fa (#33623 merged; no WIP branch for the remaining items)

    Landed in #33623 (Part of this card): item 1 for the delete and the boot report (the floor counts email and password only when the sign-in page offers it, phone excluded under off, with the local-path remedy text), item 3 and item 4 (the direct sign-in door's error and rate-limited landings keep ?redirect=).

    Still open:

    • Item 1, settings-save half: signInMethodFloorPredicate (sign-in-method-floor.ts) still counts email and password under localSignIn: 'off'. content/docs/permissions/sso.mdx says so today.
    • Item 2 (N8): a client the IP allow-list blocks gets a raw body at GET /api/v1/auth/sign-in/direct.
    • Item 5 (N13): the sign-out note can outlive a provider that does not send the browser back.
    • Review#3 nit N6 (record 6888701895): the exits under off (another provider row, a social or OIDC provider, a host handoff) have no pin under localSignIn: 'off'.

    State change: pm:dispatched → pm:queue, os-elon-musk unassigned; lease:A1 kept.

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

    bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatlease:A1pm:queueReady for the PM dispatch looppriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions