import { TRPCError, initTRPC } from '@trpc/server'; import SuperJSON from 'superjson'; import type { OpenApiMeta } from 'trpc-openapi'; import { AppError, genericErrorCodeToTrpcErrorCodeMap } from '@documenso/lib/errors/app-error'; import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin'; import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token'; import type { TrpcContext } from './context'; const t = initTRPC .meta() .context() .create({ transformer: SuperJSON, errorFormatter(opts) { const { shape, error } = opts; const originalError = error.cause; let data: Record = shape.data; // Default unknown errors to 400, since if you're throwing an AppError it is expected // that you already know what you're doing. if (originalError instanceof AppError) { data = { ...data, appError: AppError.toJSON(originalError), code: originalError.code, httpStatus: originalError.statusCode ?? genericErrorCodeToTrpcErrorCodeMap[originalError.code]?.status ?? 400, }; } return { ...shape, data, }; }, }); /** * Middlewares */ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => { const authorizationHeader = ctx.req.headers.authorization; // Taken from `authenticatedMiddleware` in `@documenso/api/v1/middleware/authenticated.ts`. if (authorizationHeader) { // Support for both "Authorization: Bearer api_xxx" and "Authorization: api_xxx" const [token] = (authorizationHeader || '').split('Bearer ').filter((s) => s.length > 0); if (!token) { throw new Error('Token was not provided for authenticated middleware'); } const apiToken = await getApiTokenByToken({ token }); return await next({ ctx: { ...ctx, user: apiToken.user, session: null, source: 'api', }, }); } if (!ctx.session) { throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You must be logged in to perform this action.', }); } return await next({ ctx: { ...ctx, user: ctx.user, session: ctx.session, source: 'app', }, }); }); export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next }) => { return await next({ ctx: { ...ctx, user: ctx.user, session: ctx.session, }, }); }); export const adminMiddleware = t.middleware(async ({ ctx, next }) => { if (!ctx.session || !ctx.user) { throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You must be logged in to perform this action.', }); } const isUserAdmin = isAdmin(ctx.user); if (!isUserAdmin) { throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authorized to perform this action.', }); } return await next({ ctx: { ...ctx, user: ctx.user, session: ctx.session, }, }); }); /** * Routers and Procedures */ export const router = t.router; export const procedure = t.procedure; export const authenticatedProcedure = t.procedure.use(authenticatedMiddleware); // While this is functionally the same as `procedure`, it's useful for indicating purpose export const maybeAuthenticatedProcedure = t.procedure.use(maybeAuthenticatedMiddleware); export const adminProcedure = t.procedure.use(adminMiddleware);