shadow_aya
shadow_aya15mo ago

"'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:
2 Replies
Michal Srb
Michal Srb15mo ago
Yup, validating a fixed token is the simplest kind of authentication, and it works as long as you never reveal the token. Nicely done!
ian
ian15mo ago
Thank you!

Did you find this page helpful?