Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/auth-sso-boot-report-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@objectstack/plugin-auth': patch
---

Gate the `no_sign_in_account_at_boot` boot report on whether the deployment has a delegated sign-in path.

The report fires on one store shape — human `sys_user` rows, zero `sys_account` rows — and calls it unrecoverable. On a deployment whose sign-in is delegated to an identity provider that shape is the healthy resting state: `ssoOnlyMode` states it in the auth config contract ("managed (IdP-provisioned) users simply hold no local credential") and names cloud-as-IdP. Such a kernel logged the report at `error` on every boot, including boots that had just served a successful SSO sign-in.

The report now also reads the runtime's sign-in wiring — SSO-only mode declared, a configured social/OIDC provider, or enterprise SSO with at least one registered `sys_sso_provider` — and stays silent at `error` when one of them holds, recording the shape at `debug` under the same grep token with the reason named.

Unchanged: `probeSignInAccountsPresence` keeps its existence-only predicate, and a deployment with no delegated sign-in path — including one that merely switched the SSO plugin on with no identity provider registered — still reports at `error`.
15 changes: 13 additions & 2 deletions packages/plugins/plugin-auth/src/auth-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ import {
type WalledOwnerAccountState,
} from './walled-owner-verification-path.js';
import {
probeSignInPathWiring,
probeSignInReachability,
reportIfNoSignInAccountExists,
type SignInPathConfigView,
} from './boot-sign-in-reachability.js';
import { judgePlatformAdmin, isPlatformAdminUser, type PlatformAdminActor } from './platform-admin-gate.js';
import {
Expand Down Expand Up @@ -1002,7 +1004,7 @@ export class AuthPlugin implements Plugin {
// `AuthManager` without ever registering the kernel `email` service, and
// the sibling hook below injects the service into it. Reading BOTH makes
// this hook's answer independent of hook registration order.
let pub: { socialProviders?: unknown[]; features?: { sso?: boolean } } | undefined;
let pub: SignInPathConfigView | undefined;
try { pub = this.authManager?.getPublicConfig(); } catch { pub = undefined; }
const hasEmailTransport = !!emailSvc || !!this.authManager?.hasEmailTransport();
const hasFederatedSignIn =
Expand All @@ -1025,7 +1027,16 @@ export class AuthPlugin implements Plugin {
// and the answer handed to the walled-owner probe, so no boot pages
// `sys_user` twice. Cost on a fresh store is a single bounded page.
const reachability = await probeSignInReachability(ql);
const deadEnd = reportIfNoSignInAccountExists(reachability, ctx.logger);
// [#15074] …and the fact that decides whether "humans, zero accounts" is
// a dead end AT ALL on this deployment: does it sign people in through an
// identity provider, which needs no `sys_account` row of its own? On a
// platform-SSO tenant kernel that population is the HEALTHY one, and the
// report's "NOBODY CAN SIGN IN" was false on every boot. The resolver
// pays for its bounded provider read only when the answer can change what
// is reported; a deployment with no delegated path is untouched and still
// reports at `error`.
const signInPath = await probeSignInPathWiring(reachability, pub, ql);
const deadEnd = reportIfNoSignInAccountExists(reachability, ctx.logger, signInPath);

let ownerAccountState: WalledOwnerAccountState = 'unknown';
if (
Expand Down
Loading
Loading