Proto Account Self-Heal Findings

Purpose

This note captures the current findings from the proto-account discussion so we can refer back to them before making any self-healing changes.

Scope:

  • consumer SSO login behavior
  • missing profile
  • missing user_email
  • missing or broken user_identity
  • pending-account and recovery side effects

No code changes are proposed in this document.

Summary

  • Missing profile looks safe to heal.
  • Missing user_email is mostly non-blocking for login, but it matters for recovery and email-based account flows.
  • Missing or broken user_identity is the dangerous case.
  • The current pending-account flow can activate a local user based on email confirmation without also proving that the delegated identity link is healthy.

Current Login Path

SSO login uses the submitted email as the username

In public_html/process-login.php, the login flow sets:

  • $username = $email

It then looks up the principal before asking api.user for a root session:

  • consumer mode: platform\package\SSO\m7\db\User::get
  • tenant mode: platform\package\SSO\m7\db\Client::get

Relevant path:

Full record lookup does not include email rows

The shared registry lookup loads:

  • user
  • identity
  • profile

It does not load user_email as part of the login lookup shape.

Relevant path:

Missing identity is not fully rejected at the first SSO lookup for active users

platform\package\SSO\m7\db\User::get() requests a verified identity row, but it does not explicitly reject a missing identity object if the user row exists and the account is active.

Relevant path:

For pending users, SSO throws PendingAccountException.

Relevant path:

api.user root-session does require a linked identity

The consumer root-session endpoint loads the user by name and then calls validateUserActive().

That validator explicitly rejects a missing identity:

  • Linked identity not found

Relevant paths:

So for active consumer users, a missing local identity row usually fails downstream in api.user, even if the first SSO lookup succeeds.

Missing Profile

Current behavior

Missing profile does not appear to block login.

api.user auth paths read profile data only opportunistically, such as profile.avatar.

Relevant path:

Existing self-heal behavior

/me/set-profile already creates the profile row if it is missing.

Relevant path:

Recommendation

This is the safest self-heal candidate.

Creating a minimal profile on login or first authenticated account load should be low risk.

Missing Email

Current behavior

Missing user_email does not appear to block the basic login path, because login does not authenticate through the email row itself.

However, email rows matter for:

  • account confirmation
  • forgot-login
  • forgot-password
  • primary-email selection and account UX

Existing tolerant behavior

/me/get-email already returns an empty-ready response when no email row exists:

  • no email found. ready for input

Relevant path:

Existing recovery / pending behavior

The pending-account flow can create or restore an email row for the user:

  • restore archived row if present
  • otherwise insert a new email row
  • mark it unverified
  • archive competing email rows

Relevant path:

Recovery depends on verified email

Forgot-login only resolves by a verified active email row.

Relevant path:

Forgot-password also filters to verified email rows when building local recovery methods.

Relevant path:

Recommendation

Missing email is partly handled already.

Good rule of thumb:

  • do not block login on missing email if identity is healthy
  • do allow post-login setup or repair
  • keep recovery flows dependent on verified email

Missing Or Broken Identity

Current behavior

This is the highest-risk case.

If the local user_identity row is missing, api.user consumer auth rejects the login because validateUserActive() requires a linked identity.

Relevant path:

If the local identity row exists but the remote delegated child is broken upstream, there does not appear to be any self-heal path today. That likely fails later in the remote signer / password validation path.

Important nuance: unverified identity is not the same as missing identity

Consumer signup currently creates:

  • local user
  • remote child
  • remote password
  • local identity row

But the local identity row is inserted with verified = 0.

Relevant paths:

For consumer auth, the practical hard requirement in api.user is that an identity object exists, not that it is marked verified.

So the truly dangerous state is:

  • no identity row
  • wrong rid
  • remote child deleted or unusable

not merely verified = 0.

Pending-Account Risk

Email can be attached during pending repair

The pending-account send operation accepts a submitted email, prepares a local email row for the pending user, and requests confirmation.

Relevant path:

Email confirmation can activate the local user

After successful confirmation, the pending-account verify operation:

  • marks the local email verified
  • sets it primary
  • updates local user status to active

Relevant path:

This activation step currently does not also prove that the delegated identity link exists and is healthy.

Why this matters

This means the system can move a local user from pending to active through email proof alone, even though login still ultimately depends on the delegated identity state.

That is the main place where the current flow feels too permissive relative to the danger of missing identity.

“Accept Any Username” Concern

What the code suggests today:

  • it is not “any username on earth”
  • it is closer to “an existing local pending account can have a new email attached and confirmed”

That is still risky enough to deserve tightening, especially if the account’s delegated identity is missing or broken.

The handoff data for pending accounts even preserves identity_rid when available:

That makes identity-aware repair possible later, but it is not enforced today in the activation step.

Recommended Guardrails

Safe to auto-heal

  • missing profile

Probably safe to keep non-blocking

  • missing user_email during login, as long as identity is healthy

Should not be silently auto-healed during normal login

  • missing or broken user_identity

For identity repair, the safer shape would be:

  • require a pre-existing trusted anchor such as a verified email already attached to that uid
  • require an explicit repair flow
  • verify the remote child can be created or re-linked correctly
  • only then allow activation or normal login

Best Future Hardening Target

If we do one surgical hardening pass later, the highest-value place is probably the pending-account activation flow.

Specifically:

  • do not promote the local user to active based only on email proof if the delegated identity is absent or broken
  • route that case into an explicit identity-repair step instead

That would keep the friendly recovery behavior for missing email and missing profile, while avoiding magic recovery for the most security-sensitive state.

1