Skip to content

update.before name veto aborts every public /update-user call (key-presence check vs endpoint-injected undefined name) #2299

Description

@jakebromberg

Problem

PR #2297 (merged, auto-deployed) added deriveOrRejectUserNameOnUpdate as the databaseHooks.user.update.before choke point. Its rejection predicate is 'name' in datakey presence. But better-auth's public POST /update-user handler (api/routes/update-user.mjs, v1.6.26) always builds its adapter payload as { name, image, ...additionalFields }, so every update through that endpoint reaches the hook with the name key present, value undefined whenever the client didn't send one. The hook therefore returns false and aborts every public profile update — currently live in production.

First observable casualty: dj-site's experience switch (authClient.updateUser({ appSkin }), src/hooks/experienceSwitchHooks.ts), caught by dj-site PR WXYC/dj-site#1279's E2E rerun — [setup] › e2e/auth.setup.ts times out waiting for #classic-container because the appSkin write is silently dropped. The veto is silent at the HTTP layer: updateWithHooks returns null and the route falls back to echoing session data with { status: true } (200), so no client sees an error.

Writers that construct their payloads without a name key (complete-onboarding.ts's markOnboardingComplete, admin-plugin routes) are unaffected — which is why PR #2297's integration tests passed.

Fix

  • shared/authentication/src/derive-user-display-name.ts: veto on value, not key presence — data.name !== undefined ? false : undefined. undefined reads as "not supplied"; any supplied value (explicit null included) is still an attempted direct write and aborts the whole payload. Docblock updated with the endpoint-shape constraint.
  • tests/unit/authentication/derive-user-display-name.test.ts: regression tests for the endpoint-injected { name: undefined, image: undefined, ...rest } shape.
  • tests/integration/update-user-name-veto.spec.js: new wire spec through the real auth service — asserts against the auth_user row (not the response, which lies): an { appSkin }-only update persists; a payload supplying name aborts entirely (sibling fields don't land).

Acceptance criteria

  • Unit + integration suites green in CI.
  • dj-site [Tracker] Project #32 close-out sequencing #1279's E2E setup (provision classic-preference identity) passes after this deploys.
  • The veto still rejects supplied-name payloads (sentinel spec and hook rejection tests untouched).

Activity

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 working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions