T
tRPC

Best way to implement input based validation on a router procedure

Best way to implement input based validation on a router procedure

JJavascriptMick2/23/2023
Hi guys, bit of a noob. I have already created a 'protectedProcedure', ensuring the user is logged in, but for some of my procedures, I also want to ensure the user is an ADMIN for the account specified on the input. This is my first try with validation just added at the top of the procedure implementation...
changeUserAccessWithinAccount: protectedProcedure
.input(z.object({ user_id: z.number(), account_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
.query(async ({ ctx, input }) => {
// validate that the context user is an admin within the account specified as an input param.... where should this go.. an additional input parser?
const test_membership = ctx.dbUser.memberships.find(membership => membership.account_id == input.account_id);
if(!test_membership || (test_membership?.access !== ACCOUNT_ACCESS.ADMIN && test_membership?.access !== ACCOUNT_ACCESS.OWNER)) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
//....do the thing...
changeUserAccessWithinAccount: protectedProcedure
.input(z.object({ user_id: z.number(), account_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
.query(async ({ ctx, input }) => {
// validate that the context user is an admin within the account specified as an input param.... where should this go.. an additional input parser?
const test_membership = ctx.dbUser.memberships.find(membership => membership.account_id == input.account_id);
if(!test_membership || (test_membership?.access !== ACCOUNT_ACCESS.ADMIN && test_membership?.access !== ACCOUNT_ACCESS.OWNER)) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
//....do the thing...
Is there a way to implement this with multiple input parsers (https://trpc.io/docs/procedures#multiple-input-parsers) ?? what would that look like?
Nnlucas2/23/2023
Inputs aren’t really meant for this kind of thing, authentication and authorisation are both best handled by middlewares which use the request headers to find a user’s identity and then check their access You can use Meta on the route to specify what role you should be in to access it and the middleware can read this I realise this may be a lot of new words, but it’s all native tRPC and can be found in the docs
JJavascriptMick2/23/2023
Thanks Nick, that makes sense but I am trying to implement a multi-tenant model where a single user can be a member of multiple accounts so when a request comes in to mess around with accounts, the request includes the id of the account and the current users access within that account is therefore dependent on the input parameters.... maybe I am completely off the track here, what do you think? Maybe, the 'active' account should be a session property and then the middleware approach might work better @Nick Lucas you may find this disconcerting but I was asking chatgpt the same question and shared our conversation.... and the combination of your context yielded a perfect answer...
Nnlucas2/23/2023
Middlewares can access the parsed input if the middleware comes after it in the chain But I think it’s awesome that chatgpt can fill the gaps that I can’t provide from a phone! 😇
JJavascriptMick2/23/2023
JJavascriptMick2/23/2023
win win
Nnlucas2/23/2023
It's really remarkable how much of a good starting point that is gpt has also bailed me out a couple times 😄
JJavascriptMick2/25/2023
for reference, this is what I ended up with...
/**
* auth middlewares
**/
const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
user: ctx.user,
},
});
});

const isAdminForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
if (!ctx.dbUser) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
const result = z.object({ account_id: z.number() }).safeParse(rawInput);
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
const { account_id } = result.data;
const test_membership = ctx.dbUser.memberships.find(membership => membership.account_id == account_id);
if(!test_membership || (test_membership?.access !== ACCOUNT_ACCESS.ADMIN && test_membership?.access !== ACCOUNT_ACCESS.OWNER)) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}

return next({ ctx });
});

export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = protectedProcedure.use(isAdminForInputAccountId);
/**
* auth middlewares
**/
const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
user: ctx.user,
},
});
});

const isAdminForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
if (!ctx.dbUser) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
const result = z.object({ account_id: z.number() }).safeParse(rawInput);
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
const { account_id } = result.data;
const test_membership = ctx.dbUser.memberships.find(membership => membership.account_id == account_id);
if(!test_membership || (test_membership?.access !== ACCOUNT_ACCESS.ADMIN && test_membership?.access !== ACCOUNT_ACCESS.OWNER)) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}

return next({ ctx });
});

export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = protectedProcedure.use(isAdminForInputAccountId);
UUUnknown User2/25/2023
Message Not Public
Sign In & Join Server To View

Looking for more? Join the community!

T
tRPC

Best way to implement input based validation on a router procedure

Join Server
Recommended Posts
[Help] Turbo shared typesI have a turborepo with two apps (web and embed). web is a t3 stack and embed is a create-react-app.Cache SSG helper responseI'm using `createProxySSGHelpers` in GSSP with `ssr: false` in the global config. I trying to cacheInput is too big for a single dispatchI decided to try tRPC for my Crypto analytics dashboard. However, I'm having a hard time passing thetypesafe permissionsHi, So I wanted to infer all the procedures from my router recursively & assign a permission (stringawaiting for procedure & logging the response.Hi, I was wondering if there is a way to handle the return object via the post-middleware's? I know createCaller Dependency Injection in Middleware ctx ?`createCaller` makes it really easy to inject dependencies via anything that's created during the `cbest practices for organizing routes/procedures?i'm trying to find some practices/styles in which people generally define routes with trpc. currentlValidating input inside middleware declaration```js const enforceUserIsCreatorOfEvent = t.middleware(({ ctx, next, input }) => { if (!input.evenFetch errors on stale pagesRecently I have been getting a lot of fetch errors on stale pages, in particular ones that have querDistribute typesafe tRPC Client in an NPM libraryHi ! super fan of trpc over here. We are building a javascript sdk for our API that is essentiallyMutation or query for something that updates db, but runs on every app load?I have 2 operations that need to run before showing my app UI. Those operations perform updates in DWebsocket is not defined errorI'm getting a "WebSocket is not defined error" on my next app connected to an express backend. Any i@trpc/server in a non-server environment Error in Azure CIIm trying to add vitest unit tests for my trpc procedures. I followed some examples and on the localHow are people handling authorization?I noticed that with V10, any mentions of `trpc-shield` are gone from the documentation. Also, it onlVitest context router callerHi, Im trying to setup vitest to test trpc. I would like to have a trpc approuter caller to be accesCannot read properties of undefined (reading 'data') of res.error.data, when trpc errors outHello everyone, I am using `@trpc/react-query` alongside `trpc` for express, and I am experiencing aExtending middlewareshttps://trpc.io/docs/middlewares#extending-middlewares Is this available?Calling a trpc endpoint inside of a trpc endpointHey all. I'm wondering how I am able to call these endpoints from within themselves? For example,Frozen input paramIs it possible to define a parameter on input schema (zod) that will have a hardcoded/frozen value wtype mismatch between tRPC return (in sveltekit) and defined typei've got this piece of code: ```ts read: async () => { const res = await trpc($page).getCards.quer