Skip to content

Commit b28801c

Browse files
committed
fix(plugin-auth): let ImportProtocolLike type the import protocol members
The `const protocol: ImportProtocolLike` literal in `admin-import-users.ts` annotated all three required members `args: any`. An explicit parameter annotation wins over the contextual type, so the one hand-written in-repo implementor of the protocol was the one implementor NOT checked against the request dialect the runner sends — the same file whose frozen `$filter` read once degraded the duplicate probe into match-everything. Deleting the three annotations lets `ImportProtocolLike` type the parameters. `FindDataRequest` declares `query` optional, so `findData` now states its refusal explicitly (already-ledgered `INVALID_REQUEST`) instead of relying on an incidental TypeError from a property read on `undefined`. No `??` fallback, no optional chaining: both spell match-everything. Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW Co-authored-by: Claude <noreply@anthropic.com>
1 parent 65ad77d commit b28801c

1 file changed

Lines changed: 23 additions & 6 deletions

File tree

packages/plugins/plugin-auth/src/admin-import-users.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -362,17 +362,34 @@ export async function runAdminImportUsers(
362362
// to `{}` stops constraining anything, so the duplicate probe matches rows
363363
// it was given no key for and the upsert updates the WRONG user. One
364364
// dialect, read straight — a request that arrives without a `query` is a
365-
// caller defect and costs a loud TypeError, not a silent match-everything.
366-
async findData(args: any) {
367-
const where = args.query.where;
368-
const limit = args.query.limit;
365+
// caller defect and is refused loudly, not softened into match-everything.
366+
//
367+
// [#17422] The parameter is deliberately UNANNOTATED: `ImportProtocolLike`
368+
// types it, and an explicit annotation here would win over that contextual
369+
// type and opt this implementor back out of the contract (the runner's own
370+
// docblock says so). `FindDataRequest` declares `query` OPTIONAL, so the
371+
// contract makes this file write its refusal down instead of leaving it as
372+
// an incidental TypeError from a property read on `undefined`.
373+
// ⛔ Not `args.query ?? {}` and ⛔ not `args.query?.where`: both spell
374+
// match-everything, which is the exact regression this protocol's history
375+
// is about.
376+
async findData(args) {
377+
const query = args.query;
378+
if (!query) {
379+
throw Object.assign(
380+
new Error('import-users: findData was called without a query — refusing to match every user'),
381+
{ code: 'INVALID_REQUEST' },
382+
);
383+
}
384+
const where = query.where;
385+
const limit = query.limit;
369386
return engine.find(args.object, { where, limit, context: SYSTEM_CTX } as any);
370387
},
371388

372389
// One better-auth create per row — hashing + credential sys_account.
373390
// Deliberately NO createManyData: there is no safe bulk primitive for
374391
// identities, and scrypt dominates the cost anyway.
375-
async createData(args: any) {
392+
async createData(args) {
376393
const data: Record<string, any> = args?.data ?? {};
377394
const email: string = typeof data.email === 'string' && data.email.length > 0
378395
? data.email
@@ -431,7 +448,7 @@ export async function runAdminImportUsers(
431448

432449
// Upsert updates touch PROFILE fields only — never email, never anything
433450
// credential- or system-managed. An empty filtered patch is a no-op.
434-
async updateData(args: any) {
451+
async updateData(args) {
435452
const patch: Record<string, any> = {};
436453
for (const [k, v] of Object.entries(args?.data ?? {})) {
437454
if (UPDATE_ALLOWED_FIELDS.has(k) && v !== undefined && v !== null && v !== '') patch[k] = v;

0 commit comments

Comments
 (0)