Skip to content

Per-environment runtime: POST /api/v1/auth/set-initial-password → 404 (recovery flow dead-ends) #1544

@xuyushun441-sys

Description

@xuyushun441-sys

Symptom

On a per-environment ObjectOS runtime (cloud distribution, *.objectos.app/*.objectos.ai), after an owner enters via SSO-as-owner, the auth-proxy redirects them (no local credential yet) to /_console/system/profile?recovery_needed=true to "Set a recovery password". Submitting that form POSTs /api/v1/auth/set-initial-password and gets 404 — the recovery flow dead-ends and the user is stuck.

Empirical confirmation (staging crm.objectos.app)

POST /api/v1/auth/set-initial-password  → 404   (route not found)
GET  /api/v1/auth/get-session           → 200   (better-auth IS mounted & responding)

So better-auth itself is fine; the specific route is simply absent on the runtime.

Root cause

  • set-initial-password is a custom route registered by the full AuthPluginpackages/plugins/plugin-auth/src/auth-plugin.ts (rawApp.post(\${basePath}/set-initial-password`, …)`, ~L483).
  • On a per-environment kernel the AuthPlugin is skipped ("⚠ AuthPlugin skipped on host kernel — runtime mode (ObjectOSEnvironmentPlugin detected)"). Auth there is served by packages/runtime/src/cloud/auth-proxy-plugin.ts, which special-cases sso-handoff-issue and sso-exchange and otherwise forwards /api/v1/auth/* to better-auth. better-auth has no set-initial-password route → 404.

Net: the recovery flow (auth-proxy redirect → "Set local password") promises an endpoint that only exists on the host-kernel AuthPlugin, not on the runtime. It can never have worked on a per-environment runtime.

Suggested fix

Add a set-initial-password handler to runtime/src/cloud/auth-proxy-plugin.ts (alongside the existing sso-exchange special-case), mirroring the AuthPlugin logic against the environment's auth service (which is already resolvable there — get-session works, so getAuthContext()internalAdapter + password.hash + createAccount are available):

  1. require a valid session (else 401),
  2. reject if a credential account with a password already exists (409 → "use change-password"),
  3. enforce min/max length,
  4. internalAdapter.createAccount({ userId, providerId: 'credential', accountId: userId, password: hash }).

This is the same body as auth-plugin.ts L483–536; factoring it into a shared helper both call sites can reuse would avoid drift.

Impact / context

Exposed now that per-environment runtimes boot cleanly (the prior LocalCryptoProvider crash-loop masked it — the runtime never stayed up long enough to reach the recovery page). Blocks the "Open environment as admin" → first-time owner UX end-to-end. framework @ 654a2fd9 (also present at the cloud-pinned cf03ef2f7).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions