shadow_aya
shadow_aya
CCConvex Community
Created by shadow_aya on 4/30/2024 in #support-community
Convex & Lucia might be depricated on v3
Unfortunate news, just I was about to celebrate that Convex & Lucia work well together and I ran into a roadblock. Lucia's API got changed in version 3, the issue is getting a session - convex-lucia-auth-demo convex/withAuth.ts:117 - new lucia API github discussion the new Lucia class has no getSession function, which allowed you to only read the session notice the comment above in the v2 demo repo:
// The cast is OK because we will only expose the existing session
// The cast is OK because we will only expose the existing session
with seemingly only Lucia#validateSession left, I think it's no longer possible to use in queries at all. can someone else have a look and confirm? if you wish, I can quickly share my v3 repo where I found this out (but there isn't really much more to it) Thanks :FallenQiqi:
8 replies
CCConvex Community
Created by shadow_aya on 12/6/2023 in #support-community
Create user on Clerk registration
I would like to create an entry in my users table when Clerk auth finishes (for new users). I created a bumpUser function:
export const bumpUser = mutation({
async handler(ctx) {
const user = await ctx.auth.getUserIdentity();

if (!user) throw new Error("User not found");

const existingUser = await ctx.db
.query("users")
.withIndex("by_identity", q =>
q.eq("identity", user?.tokenIdentifier.split("|")[1])
)
.first();

if (existingUser) return;

await ctx.db
.insert("users", {
identity: user?.tokenIdentifier.split("|")[1],
...
});

},
})
export const bumpUser = mutation({
async handler(ctx) {
const user = await ctx.auth.getUserIdentity();

if (!user) throw new Error("User not found");

const existingUser = await ctx.db
.query("users")
.withIndex("by_identity", q =>
q.eq("identity", user?.tokenIdentifier.split("|")[1])
)
.first();

if (existingUser) return;

await ctx.db
.insert("users", {
identity: user?.tokenIdentifier.split("|")[1],
...
});

},
})
My intent was to trigger it via Clerk middleware, but I immediately realized that might not be a good idea: middleware.ts
const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export default authMiddleware({
publicRoutes: [
"/"
],
afterAuth: async (auth) => {
const token = await auth.getToken({
template: "convex"
});
if (!token) return;
convex.setAuth(token);
convex.mutation(api.accounts.bumpUser);
}
});

export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
};
const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export default authMiddleware({
publicRoutes: [
"/"
],
afterAuth: async (auth) => {
const token = await auth.getToken({
template: "convex"
});
if (!token) return;
convex.setAuth(token);
convex.mutation(api.accounts.bumpUser);
}
});

export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
};
- afterAuth fires multiple times. this is probably intentional on Clerk's side, but it's kind of stupid to be honest, they even combined the flow for all users who pass public pages and protected pages for those who are logged in. so far Convex queued the mutations well and didn't create duplicate users, so I assume I don't have to worry about this? - I can imagine the function blowing up even when two users request at the same time and token is mistakenly set. yes yes, this is very unlikely, but it looks like it could happen. I'm surprised the http client methods don't have an options argument where I could explicitly declare this token. you can take this part as a feature request, unless someone clarifies why that's not necessary should I instead somehow call the mutation on the client? do I make some adjustment? I am new to Clerk, and I've never done auth with Convex either. these abstractions are very simple, but can also be broken in a simple way. last thing I thought of while typing this - how's the type safety between Clerk and Convex user objects? (actually this might be a dumb question since Convex must be formatting the user object in its own way when verifying and typing it, unless there's also some missing keys for the various auth methods Convex can be used with) Thanks :PatTao: update looking into Clerk's webhooks instead, but I'm still interested about the token setting
2 replies
CCConvex Community
Created by shadow_aya on 11/11/2023 in #show-and-tell
"'internal' authentication" for a HTTP client
Personally, almost all of my Convex functions are meant to be used backend-to-db (might be questionable design but let's forget that for a minute). In finding https://discord.com/channels/1019350475847499849/1103697990331416587 and realizing that, in theory, anyone can call my session & rate limiting functions, I first panicked, and then came with an acceptable solution. ConvexHttpClientAsServer
type OmitDbToken<T extends FunctionReference<any>> = Omit<T, '_args'> & { _args: Omit<T['_args'], 'dbToken'> };
type HasDbToken<Fn extends FunctionReference<any>, T> = 'dbToken' extends keyof Fn['_args'] ? T : never;

class ConvexHttpClientAsServer extends ConvexHttpClient {
constructor(url: string) {
super(url);
}

public serverMutation<Mutation extends FunctionReference<"mutation">>(
ctx: Mutation,
...args: HasDbToken<Mutation, OptionalRestArgs<OmitDbToken<Mutation>>>
) {
return super.mutation(
ctx,
Object.assign({}, { dbToken: process.env.CONVEX_DB_TOKEN! }, ...args)
);
}

public serverQuery<Query extends FunctionReference<"query">>(
ctx: Query,
...args: HasDbToken<Query, OptionalRestArgs<OmitDbToken<Query>>>
) {
return super.query(
ctx,
Object.assign({}, { dbToken: process.env.CONVEX_DB_TOKEN! }, ...args)
);
}

// the rest (like actions) isn't implemented cause I don't use em yet
// but it's all pretty similar, just gotta copypaste

}

export const convex = new ConvexHttpClientAsServer(process.env.NEXT_PUBLIC_CONVEX_URL!);
type OmitDbToken<T extends FunctionReference<any>> = Omit<T, '_args'> & { _args: Omit<T['_args'], 'dbToken'> };
type HasDbToken<Fn extends FunctionReference<any>, T> = 'dbToken' extends keyof Fn['_args'] ? T : never;

class ConvexHttpClientAsServer extends ConvexHttpClient {
constructor(url: string) {
super(url);
}

public serverMutation<Mutation extends FunctionReference<"mutation">>(
ctx: Mutation,
...args: HasDbToken<Mutation, OptionalRestArgs<OmitDbToken<Mutation>>>
) {
return super.mutation(
ctx,
Object.assign({}, { dbToken: process.env.CONVEX_DB_TOKEN! }, ...args)
);
}

public serverQuery<Query extends FunctionReference<"query">>(
ctx: Query,
...args: HasDbToken<Query, OptionalRestArgs<OmitDbToken<Query>>>
) {
return super.query(
ctx,
Object.assign({}, { dbToken: process.env.CONVEX_DB_TOKEN! }, ...args)
);
}

// the rest (like actions) isn't implemented cause I don't use em yet
// but it's all pretty similar, just gotta copypaste

}

export const convex = new ConvexHttpClientAsServer(process.env.NEXT_PUBLIC_CONVEX_URL!);
for context DB_TOKEN on Convex matches CONVEX_DB_TOKEN on server usage
convex.serverQuery(api.siteSessions.findBySid, {sid: sid})
convex.serverQuery(api.siteSessions.findBySid, {sid: sid})
findBySid method
export const findBySid = query({
args: {
dbToken: v.string(),
sid: v.string(),
},
async handler(ctx, { dbToken, ...args }) {
if (process.env.DB_TOKEN !== dbToken) throw new Error(); // reject request

// ...

},
});
export const findBySid = query({
args: {
dbToken: v.string(),
sid: v.string(),
},
async handler(ctx, { dbToken, ...args }) {
if (process.env.DB_TOKEN !== dbToken) throw new Error(); // reject request

// ...

},
});
this is kind of a madness but it works - no need to dotenv anywhere else, and it's still typesafe. note: to validate the function's arguments - in case 'dbToken' doesn't exist, args will be never So yeah, if anyone wants to use this, you may :HuTaoThumbsUp:
3 replies
CCConvex Community
Created by shadow_aya on 10/21/2023 in #support-community
Query typing malfunction
I've got an issue where the destroy method from my session store doesn't work. Upon further inspection, I found out that one of my Convex functions returns random values (table entries with seemingly valid data that don't exist in reality). I cleared my siteSessions table, and still got results for non-existent documents. Even when there are results, _id is in mismatch. My other tables are small enough as of now that I even checked elsewhere, and the logged _id was nowhere to be found. I'm not sure why this is happening, all my code is attached. I hope I made a stupid mistake or overlooked something rather than there being an underlying issue. Note that I deleted some documents by hand, but I don't think that should have any effect. session.Store#destroy method
class ConvexSiteSessionStore extends session.Store {

// ...

destroy(sid: string, callback?: ((err?: any) => void) | undefined) {

convex.query(api.siteSessions.findBySid, {sid: sid}).then((session) => {

console.log(session); // logging here

if (!session) callback();
else convex.mutation(api.siteSessions.remove, {id: session._id}).then(() => {
callback();
}).catch((err) => {
callback(err);
});
}).catch((err) => {
callback(err);
});

}
}
class ConvexSiteSessionStore extends session.Store {

// ...

destroy(sid: string, callback?: ((err?: any) => void) | undefined) {

convex.query(api.siteSessions.findBySid, {sid: sid}).then((session) => {

console.log(session); // logging here

if (!session) callback();
else convex.mutation(api.siteSessions.remove, {id: session._id}).then(() => {
callback();
}).catch((err) => {
callback(err);
});
}).catch((err) => {
callback(err);
});

}
}
siteSessions#findBySid Convex function
export const findBySid = query({
args: {
sid: v.string(),
},
async handler(ctx, args) {
return await ctx.db
.query("siteSessions")
.withIndex("by_sid") // also tried without the index
.filter((q) =>
q.eq(q.field("sid"), args.sid)
)
.first();
},
});
export const findBySid = query({
args: {
sid: v.string(),
},
async handler(ctx, args) {
return await ctx.db
.query("siteSessions")
.withIndex("by_sid") // also tried without the index
.filter((q) =>
q.eq(q.field("sid"), args.sid)
)
.first();
},
});
siteSessions table schema
siteSessions: defineTable({
sid: v.string(),
data: v.string(),
expires: v.number(),
})
.index("by_sid", ["sid"])
siteSessions: defineTable({
sid: v.string(),
data: v.string(),
expires: v.number(),
})
.index("by_sid", ["sid"])
15 replies