T
TanStack•3w ago
xenial-black

How to check if a server function has a certain middleware

Say i have a middleware
export const requireAuthMiddleware = createMiddleware({
type: "function",
}).server(async ({ next }) => {...});
export const requireAuthMiddleware = createMiddleware({
type: "function",
}).server(async ({ next }) => {...});
and i have some server function
export const serverGetUser = createServerFn({
method: "GET",
})
.middleware([ ??? ])
.handler(async ({ context }) => { ... });
export const serverGetUser = createServerFn({
method: "GET",
})
.middleware([ ??? ])
.handler(async ({ context }) => { ... });
how can i check if the sfn "serverGetUser" has the "requireAuthMiddleware" middleware? i need it so that i can implement a generic check for a folder where i'll put all my protected server fns if there's no way to check this, then is there a way to make wrappers/factories for the createServerFn? I've tried but as far as i understand its not possible at the moment because the compiler is designed to statically analyze createServerFn calls
5 Replies
rival-black
rival-black•3w ago
In middleware you should return
await next({context: user: {...}})
await next({context: user: {...}})
and then check in server fn for context.user
xenial-black
xenial-blackOP•3w ago
not at all what i was asking bro 😅 for a given set of server functions i need to programmatically inspect which one has which middleware, so that i can rune some custom checks
fair-rose
fair-rose•3w ago
i think we dont have that yet. can you explain your use case in more detail please?
xenial-black
xenial-blackOP•3w ago
sure, i guess this is more of a tanstack-router than tanstack-start context? anyways i've protected my routes from unauthorized access using the standard setup:
authenticated.tsx
_authenticated
└── admin
└── admin.manage.tsx
└── ...
└── _admin.tsx
└── profile.tsx
└── ...
authenticated.tsx
_authenticated
└── admin
└── admin.manage.tsx
└── ...
└── _admin.tsx
└── profile.tsx
└── ...
Now i wanna protect my server functions, because after all they're basically public API. To do that i know we can use middlewares, which i did, and it works great. The problem is that i dont have any neat mechanism of forcing devs to specify access rules for server functions. When working on pages that need authentication or authorization a dev needs to remember to add protection to those server fns which is easy to mess up because you're writing FE code in the same file, and the FE route is protected by default, so you can easily forget to protect the server function.. and this can also easily slip through code review. So usually we would solve this using a wrapper/factory function for api route creation, one for authorized endpoints, one for public endpoints, one for role-based endpoints etc. For example trpc supports this easily:
export const publicProcedure = t.procedure.use(errorHandlingMiddleware);
export const privateProcedure = t.procedure
.use(errorHandlingMiddleware)
.use(isAuthenticatedMiddleware)
export const publicProcedure = t.procedure.use(errorHandlingMiddleware);
export const privateProcedure = t.procedure
.use(errorHandlingMiddleware)
.use(isAuthenticatedMiddleware)
In tanstack start we cant use this approach, e.g.
export const privateServerFn = createServerFn().middleware([ requireAuthMiddleware ])
export const privateServerFn = createServerFn().middleware([ requireAuthMiddleware ])
because all calls to createServerFn have to end with .handler(). Compiler complains. I've also tried to create a generic wrapper/factory few days ago, but compiler complains
export function createProtectedServerFn<
TMethod extends Method = 'POST',
TServerFnResponseType extends ServerFnResponseType = 'data',
TResponse = unknown,
TMiddlewares = undefined,
TValidator = undefined
>(
options?: {
method?: TMethod;
response?: TServerFnResponseType;
type?: ServerFnType;
},
__opts?: ServerFnBaseOptions<
TMethod,
TServerFnResponseType,
TResponse,
TMiddlewares,
TValidator
>
) {
return createServerFn(options, __opts).middleware([authRequiredMiddleware]);
}
export function createProtectedServerFn<
TMethod extends Method = 'POST',
TServerFnResponseType extends ServerFnResponseType = 'data',
TResponse = unknown,
TMiddlewares = undefined,
TValidator = undefined
>(
options?: {
method?: TMethod;
response?: TServerFnResponseType;
type?: ServerFnType;
},
__opts?: ServerFnBaseOptions<
TMethod,
TServerFnResponseType,
TResponse,
TMiddlewares,
TValidator
>
) {
return createServerFn(options, __opts).middleware([authRequiredMiddleware]);
}
Cannot read properties of undefined (reading 'find')
TypeError: Cannot read properties of undefined (reading 'find')
at Object.handleCreateServerFnCallExpression [as handleCallExpression] (file:///app/node_modules/@tanstack/start-plugin-core/dist/esm/compilers.js:168:98)
Cannot read properties of undefined (reading 'find')
TypeError: Cannot read properties of undefined (reading 'find')
at Object.handleCreateServerFnCallExpression [as handleCallExpression] (file:///app/node_modules/@tanstack/start-plugin-core/dist/esm/compilers.js:168:98)
From gemini: In short, any abstraction that hides the direct, top-level call to createServerFn will break the build process. If you can make a way for us to build factory/wrapper functions around createServerFn that would be killer! So what i've figured is, if i cant make sure server functions are protected during development time, i could check it during runtime (+ development time). I planned on creating a similar folder structure for my server functions
_authenticated
└── admin
└── manage-something.sfn.tsx
└── ...
└── ...
_authenticated
└── admin
└── manage-something.sfn.tsx
└── ...
└── ...
And then just write some code that will import ALL sfn's from "_authenticated" folder and make sure each has "requireAuthMiddleware" (essentially using fs.readdirSync and dynamically import *), all from "admin" folder has "authorizeAdminMiddleware" etc. This would be doable if there was a way i could get a list of middlewares for a server function or a direct check "sfnContainsMiddleware(sfn, requireAuthMiddleware)". Then i can just import this code somewhere so that it runs on every build.
fair-rose
fair-rose•3w ago
yeah abstractions on top of createServerFn is in our backlog, but this will take some time

Did you find this page helpful?