Christian
Christian13mo ago

How is mutation authentication supposed to work?

In this template https://www.convex.dev/templates/nextjs-app-router in the posts.ts inside the convex folder there is this code
export const create = mutation({
args: { authorId: v.id("users"), text: v.string() },
handler: async (ctx, { text, authorId }) => {

if (text.length <= 0 || text.length > CHARACTER_LIMIT) {
throw new Error("Message is too damn long! (or empty)");
}

const numSentRecently = (
await ctx.db
.query("posts")
.withIndex("byAuthorId", (q) =>
q
.eq("authorId", authorId)
.gte("_creationTime", Date.now() - 1000 * 60)
)
.take(3)
).length;

if (numSentRecently >= 3) {
throw new Error("Too fast, slow down!");
}

await ctx.db.insert("posts", { authorId, text });
// Instead of computing the number of tweets when a profile
// is loaded, we "denormalize" the data and increment
// a counter - this is safe thanks to Convex's ACID properties!
const author = (await ctx.db.get(authorId))!;
await ctx.db.patch(authorId, { numPosts: author.numPosts + 1 });
},
});
export const create = mutation({
args: { authorId: v.id("users"), text: v.string() },
handler: async (ctx, { text, authorId }) => {

if (text.length <= 0 || text.length > CHARACTER_LIMIT) {
throw new Error("Message is too damn long! (or empty)");
}

const numSentRecently = (
await ctx.db
.query("posts")
.withIndex("byAuthorId", (q) =>
q
.eq("authorId", authorId)
.gte("_creationTime", Date.now() - 1000 * 60)
)
.take(3)
).length;

if (numSentRecently >= 3) {
throw new Error("Too fast, slow down!");
}

await ctx.db.insert("posts", { authorId, text });
// Instead of computing the number of tweets when a profile
// is loaded, we "denormalize" the data and increment
// a counter - this is safe thanks to Convex's ACID properties!
const author = (await ctx.db.get(authorId))!;
await ctx.db.patch(authorId, { numPosts: author.numPosts + 1 });
},
});
There is no check for what user is running this code and seemingly no safe guards against a bad actor trying to post as another user. Am I missing something or is there more to add for a production ready app?
Templates
The backend application platform with everything you need to build your product.
14 Replies
jamwt
jamwt13mo ago
hi! this is just a basic example. if you want an authenticated mutation, you use ctx.auth.getUserIdentity() to check the logged-in user
jamwt
jamwt13mo ago
you can see an example here ( https://github.com/AntonioErdeljac/next14-miro-clone/blob/master/convex/board.ts#L18 ) from @CodeWithAntonio 's most recent video
GitHub
next14-miro-clone/convex/board.ts at master · AntonioErdeljac/next1...
Contribute to AntonioErdeljac/next14-miro-clone development by creating an account on GitHub.
jamwt
jamwt13mo ago
his is using clerk for auth
jamwt
jamwt13mo ago
Convex Clerk | Convex Developer Hub
Clerk is an authentication platform providing login via
Christian
ChristianOP13mo ago
Thanks for the guidance. Yeah that makes sense, and i realized that a while ago. That "getUserIdentitity" function is used in the store function under the user.ts file on the template. Is there a way to enfore a unique on a schema value?
jamwt
jamwt13mo ago
not "automatically", but mutations are ACID transactions (no data races). so if you check if it's there before you insert it, it's guaranteed unique
Christian
ChristianOP13mo ago
That's unreal, having no race conditions is a game changer. So if it's ACID is it single threaded aswell?
jamwt
jamwt13mo ago
nope, not single threaded this was a recent deep dive on @Web Dev Cody's server with a lot of detail about how it works: https://discord.com/channels/663478877355507769/1201995998503911424
Christian
ChristianOP13mo ago
Dang that link is #unknown He's actually the only reason i'm checking out Convex lol
jamwt
jamwt13mo ago
cool. well, in his yt-feedback channel, there's a whole thread that spawned out of this question on Jan 30
No description
Michal Srb
Michal Srb13mo ago
GitHub
convex-nextjs-app-router-demo/convex/posts.ts at main · get-convex/...
Demo showing a Next.js App Router app powered by Convex backend - get-convex/convex-nextjs-app-router-demo
Christian
ChristianOP13mo ago
@Michal Srb Seems legit, thanks for the update. I am curious why you chose to use the tokenIdentifier instead of the subject. From what i understand the difference between them is that tokenIdentifier = issuer + subject(The issuer is a url, and the subject is the userId provided by clerk) This is what the identity looks like when logged for clarification
tokenIdentifier: 'https://wordly-derdly-0.clerk.accounts.dev|user_eairnshtulikviflrlkrfv',
issuer: 'https://wordly-derdly-0.clerk.accounts.dev',
subject: 'user_eairnshtulikviflrlkrfv',
name: 'Michal Srb',
givenName: 'Michal',
familyName: 'Srb',
pictureUrl: 'https://img.clerk.com/eyJ0eXBlIjoicHJveHkiLCJzcmMiOiJodHRwczovL2ltYWdlcy5jbGVyay5kZXYvb2F1dGhfZ29vZ2xlL2ltZ18yY0JkRGhzdUxLcDBzZkhQMVZ4SEgwQ1JSQ2YifQ',
email: 'fakeemail@gmail.com',
emailVerified: true,
phoneNumberVerified: false,
updatedAt: '2024-02-11T21:39:03+00:00'
tokenIdentifier: 'https://wordly-derdly-0.clerk.accounts.dev|user_eairnshtulikviflrlkrfv',
issuer: 'https://wordly-derdly-0.clerk.accounts.dev',
subject: 'user_eairnshtulikviflrlkrfv',
name: 'Michal Srb',
givenName: 'Michal',
familyName: 'Srb',
pictureUrl: 'https://img.clerk.com/eyJ0eXBlIjoicHJveHkiLCJzcmMiOiJodHRwczovL2ltYWdlcy5jbGVyay5kZXYvb2F1dGhfZ29vZ2xlL2ltZ18yY0JkRGhzdUxLcDBzZkhQMVZ4SEgwQ1JSQ2YifQ',
email: 'fakeemail@gmail.com',
emailVerified: true,
phoneNumberVerified: false,
updatedAt: '2024-02-11T21:39:03+00:00'
thedevstockgirl
thedevstockgirl13mo ago
This confused me too, as I saw a lot of guides using tokenIdentifier, and took a while to realize that subject is probably what I wanted.
ian
ian13mo ago
tokenIdentifier allows you, in the future, use two different issuers (such as Clerk & Auth0) and not worry about subjects colliding. It's more defensive, but for most applications just using subject is fine

Did you find this page helpful?