Skip to content

Auth adapter: a fields remap colliding with another field's default key silently misresolves #1545

Description

@borisno2

Context

Found during review of #1543 (test coverage for #1241's codecFor/mapped-increment gap).

The gap

packages/auth/src/adapter/index.ts's resolveField/isBigInt/toColumn/toFieldKey all resolve a name via better-auth's getDefaultFieldName (from @better-auth/core/db/adapter), which tries a direct hit first against the model's own field keys before falling back to a reverse lookup by fieldName:

let f = schema[model]?.fields[field];
if (!f) {
  const result = Object.entries(schema[model]!.fields!).find(([_, f]) => f.fieldName === field);
  ...
}

This makes the resolution ambiguous when an app's authPlugin({ <model>: { fields: {...} } }) remap sends one field's column to a string that happens to equal a different field's own (unmapped) default key in the same model — e.g.:

authPlugin({
  rateLimit: {
    enabled: true,
    storage: 'database',
    // lastRequest's physical column is now "count" — which collides with
    // the rateLimit model's own unmapped `count` field key.
    fields: { count: 'total_count', lastRequest: 'count' },
  },
})

Here, the physical column "count" hits schema[model].fields['count'] directly and resolves to the original count field instead of lastRequest. resolveField/isBigInt then check count's (non-bigint) attributes instead of lastRequest's, so incrementOne/outward silently skip the BigInt widening/narrowing a real int8 column needs — wrong values or precision loss on a remapped bigint column, with no error raised.

What to do

Pick one:

  • (a) Add a generate-time check in packages/auth/src/config/derive-auth-lists.ts that rejects a fields remap whose target string equals another field's own default key in the same model, naming the model and the colliding fields.
  • (b) In packages/auth/src/adapter/index.ts, avoid re-deriving the field key via getDefaultFieldName's ambiguous direct-hit-first logic — carry the field key resolved once (e.g. from sqlWhere's own resolution path) forward explicitly instead of re-resolving from a bare column string.

Acceptance criteria

  • A fields remap that creates this kind of collision either fails fast at generate/config time with a clear error naming the two fields, or resolves correctly end-to-end
  • A regression test drives incrementOne (or another read/write path) against a rateLimit-like model configured with a colliding remap, and asserts the correct field's attributes (in particular bigint) are used

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpriority:mediumShould land before or shortly after release; has a workaround or narrow blast radiusready-for-agentFully specified, ready for an AFK agent

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions