Skip to content

Commit 18caf0f

Browse files
committed
Fix _id where-clauses matching documents from other models
findOne/findMany resolved _id where-clauses with a bare ctx.db.get, which returns whatever document the id points to regardless of the requested model. Resolve through ctx.db.normalizeId first so foreign ids and invalid id strings are treated as no match.
1 parent c628916 commit 18caf0f

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

src/client/adapter-utils.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { asyncMap } from "convex-helpers";
22
import { v } from "convex/values";
3-
import type { GenericId, Infer } from "convex/values";
3+
import type { Infer } from "convex/values";
44
import type {
55
DocumentByName,
66
GenericDataModel,
@@ -522,11 +522,17 @@ export const paginate = async <
522522
`No index found for ${args.model}.${uniqueWhere.field}`
523523
);
524524
}
525+
// Resolve _id values through the requested model. normalizeId returns
526+
// null when the id belongs to a different table or isn't a valid id at
527+
// all (e.g. better-auth's adapter tests pass in UUIDs as values), both
528+
// of which mean "no match" here.
529+
const uniqueWhereId =
530+
uniqueWhere.field === "_id" && typeof uniqueWhere.value === "string"
531+
? ctx.db.normalizeId(args.model as T, uniqueWhere.value)
532+
: null;
525533
const doc =
526534
uniqueWhere.field === "_id"
527-
? // Unfortunately this is one place where tests pass in UUIDs as values and convex-test doesn't support them
528-
// eslint-disable-next-line @convex-dev/explicit-table-ids
529-
await ctx.db.get(uniqueWhere.value as GenericId<T>)
535+
? uniqueWhereId && (await ctx.db.get(args.model, uniqueWhereId))
530536
: await ctx.db
531537
.query(args.model as any)
532538
.withIndex(index?.indexDescriptor as any, (q) =>
@@ -568,7 +574,13 @@ export const paginate = async <
568574
// For ids, just use asyncMap + .get()
569575
if (inWhere.field === "_id") {
570576
const docs = await asyncMap(inWhere.value as any[], async (value) => {
571-
return ctx.db.get(args.model, value as GenericId<T>);
577+
// Same model scoping as the unique _id branch above: foreign or
578+
// invalid ids are filtered out instead of matching or throwing.
579+
const id =
580+
typeof value === "string"
581+
? ctx.db.normalizeId(args.model as T, value)
582+
: null;
583+
return id && (await ctx.db.get(args.model, id));
572584
});
573585
const filteredDocs = docs
574586
.flatMap((doc) => (doc ? [doc] : []))

src/test/adapter-factory/convex-custom.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,41 @@ export const convexCustomTestSuite = createTestSuite(
6363
).toEqual([user]);
6464
},
6565

66+
"should not match an id from a different model": async () => {
67+
const user = await adapter.create({
68+
model: "user",
69+
data: {
70+
name: "cross-model",
71+
email: "cross@model.com",
72+
},
73+
});
74+
// user.id is a valid id of the user table; a lookup scoped to another
75+
// model must treat it as no match instead of returning the user doc.
76+
expect(
77+
await adapter.findOne({
78+
model: "session",
79+
where: [
80+
{
81+
field: "id",
82+
value: user.id,
83+
},
84+
],
85+
}),
86+
).toEqual(null);
87+
expect(
88+
await adapter.findMany({
89+
model: "session",
90+
where: [
91+
{
92+
field: "id",
93+
operator: "in",
94+
value: [user.id],
95+
},
96+
],
97+
}),
98+
).toEqual([]);
99+
},
100+
66101
"should handle compound indexes that include id field": async () => {
67102
const user = await adapter.create({
68103
model: "user",

0 commit comments

Comments
 (0)