Skip to content

Commit 421eeea

Browse files
committed
fix(auth): align better-auth family on 1.7.0-rc.1 + new adapter methods
The @better-auth/oauth-provider fix (GHSA-p2fr-6hmx-4528) only ships in the 1.7 line, whose plugins import CLIENT_ASSERTION_TYPE and other symbols that exist only in @better-auth/core 1.7.x. Pinning oauth-provider to 1.7.0-rc.1 while core/better-auth stayed on 1.6.23 made sign-in 500 at runtime ("@better-auth/core/oauth2 does not provide an export named CLIENT_ASSERTION_TYPE"), failing the dogfood regression gate. Pin the ENTIRE better-auth family (better-auth, @better-auth/core, and all @better-auth/* plugins/adapters) to 1.7.0-rc.1 via pnpm-workspace overrides so the stack is internally consistent. better-auth 1.7 also adds two methods to its CustomAdapter contract, now implemented in the ObjectQL adapter as find-then-write mirrors of the existing delete/update methods (ObjectQL has no native atomic primitive): - consumeOne: atomic single-row consume (used for verification tokens on the sign-in path) - incrementOne: guarded field = field + delta counter mutation Verified locally: plugin-auth builds (dts included) and all 440 unit tests pass; frozen-lockfile is in sync; only plugin-auth consumes better-auth. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JoYhorCUhGc8bgFYsErfE
1 parent f99722c commit 421eeea

4 files changed

Lines changed: 164 additions & 83 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
fix(auth): align the better-auth family on 1.7.0-rc.1 and implement the new adapter methods (#2974)
6+
7+
Remediating GHSA-p2fr-6hmx-4528 (`@better-auth/oauth-provider`) requires the
8+
1.7 plugin line, which imports `CLIENT_ASSERTION_TYPE` and other symbols that
9+
only exist in `@better-auth/core` 1.7.x — so the whole better-auth family is
10+
pinned to `1.7.0-rc.1` together (mixing a 1.7 plugin with 1.6.23 core 500s on
11+
sign-in). better-auth 1.7 also extends its `CustomAdapter` contract with two
12+
new methods, which the ObjectQL adapter now implements:
13+
14+
- `consumeOne` — atomic single-row consume (find the guarded row, delete it,
15+
return it), used by better-auth for single-use credential consumption
16+
(e.g. verification tokens on the sign-in path).
17+
- `incrementOne` — guarded counter mutation (`field = field + delta` per
18+
`increment` entry plus any absolute `set` values), returning the updated row
19+
or `null` when the guard matches nothing.
20+
21+
Both are find-then-write mirrors of the existing `delete` / `update` methods
22+
(ObjectQL exposes no native atomic primitive) and honour the same core/plugin
23+
field-name bridging.

packages/plugins/plugin-auth/src/objectql-adapter.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,55 @@ export function createObjectQLAdapterFactory(rawDataEngine: IDataEngine) {
334334
}
335335
return records.length;
336336
},
337+
338+
// Atomic single-row consume (better-auth 1.7+). ObjectQL has no native
339+
// `DELETE ... RETURNING`, so we find the single guarded row, delete it,
340+
// and return the consumed record — a find-then-write mirror of `delete`.
341+
consumeOne: async <T>(
342+
{ model, where }: { model: string; where: CleanedWhere[] },
343+
): Promise<T | null> => {
344+
const objectName = resolveProtocolName(model);
345+
const bridged = objectName !== model;
346+
const filter = convertWhere(bridged ? remapWhere(where) : where);
347+
348+
const record = await dataEngine.findOne(objectName, { where: filter });
349+
if (!record) return null;
350+
await dataEngine.delete(objectName, { where: { id: record.id } });
351+
const norm = normaliseLegacyDates(model, record);
352+
return (bridged ? remapKeys(norm, snakeToCamel) : norm) as T;
353+
},
354+
355+
// Guarded counter mutation (better-auth 1.7+). ObjectQL has no native
356+
// `SET n = n + $delta ... RETURNING`, so we read the guarded row, apply
357+
// `field = field + delta` for each `increment` entry (negative deltas
358+
// decrement) plus any absolute `set` values, and write it back. `where`
359+
// is both selector and guard, so a non-matching guard returns null.
360+
incrementOne: async <T>(
361+
{ model, where, increment, set }: {
362+
model: string; where: CleanedWhere[];
363+
increment: Record<string, number>; set?: Record<string, unknown>;
364+
},
365+
): Promise<T | null> => {
366+
const objectName = resolveProtocolName(model);
367+
const bridged = objectName !== model;
368+
const filter = convertWhere(bridged ? remapWhere(where) : where);
369+
370+
const record = await dataEngine.findOne(objectName, { where: filter });
371+
if (!record) return null;
372+
373+
const patch: Record<string, any> = {};
374+
for (const [field, delta] of Object.entries(increment)) {
375+
const col = bridged ? camelToSnake(field) : field;
376+
const current = Number((record as Record<string, any>)[col] ?? 0);
377+
patch[col] = current + delta;
378+
}
379+
if (set) Object.assign(patch, bridged ? remapKeys(set, camelToSnake) : set);
380+
381+
const result = await dataEngine.update(objectName, { ...patch, id: record.id });
382+
if (!result) return null;
383+
const norm = normaliseLegacyDates(model, result);
384+
return (bridged ? remapKeys(norm, snakeToCamel) : norm) as T;
385+
},
337386
}),
338387
});
339388
}

0 commit comments

Comments
 (0)