From 7563622d8beb746d4944cf67dece2718b9d450fd Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Sun, 13 Aug 2023 22:18:43 -0600 Subject: [PATCH] API updates --- src/server/api/root.ts | 2 ++ src/server/api/routers/rest.ts | 16 -------------- src/server/api/routers/webhook.ts | 24 +++++++++++++++++++++ src/server/api/trpc.ts | 35 +++++++++++++++++++++++++++---- 4 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 src/server/api/routers/webhook.ts diff --git a/src/server/api/root.ts b/src/server/api/root.ts index 4acf4e6..b331d52 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -2,6 +2,7 @@ import { roomRouter } from "~/server/api/routers/room"; import { createTRPCRouter } from "~/server/api/trpc"; import { voteRouter } from "./routers/vote"; import { restRouter } from "./routers/rest"; +import { webhookRouter } from "./routers/webhook"; /** * This is the primary router for your server. @@ -12,6 +13,7 @@ export const appRouter = createTRPCRouter({ room: roomRouter, vote: voteRouter, rest: restRouter, + webhook: webhookRouter, }); // export type definition of API diff --git a/src/server/api/routers/rest.ts b/src/server/api/routers/rest.ts index ad90207..12e1bb3 100644 --- a/src/server/api/routers/rest.ts +++ b/src/server/api/routers/rest.ts @@ -1,27 +1,11 @@ -import { validateApiKey } from "~/server/unkey"; import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { TRPCError } from "@trpc/server"; import { fetchCache, setCache } from "~/server/redis"; import { sql } from "drizzle-orm"; import { rooms, votes } from "~/server/schema"; export const restRouter = createTRPCRouter({ - dbWarmer: publicProcedure - .meta({ openapi: { method: "POST", path: "/rest/dbwarmer" } }) - .input(z.object({ key: z.string() })) - .output(z.string()) - .query(async ({ ctx, input }) => { - const isValidKey = await validateApiKey(input.key); - if (isValidKey) { - await ctx.db.query.votes.findMany(); - return "Toasted the DB"; - } else { - throw new TRPCError({ code: "UNAUTHORIZED" }); - } - }), - voteCount: publicProcedure .meta({ openapi: { method: "GET", path: "/rest/votes/count" } }) .input(z.void()) diff --git a/src/server/api/routers/webhook.ts b/src/server/api/routers/webhook.ts new file mode 100644 index 0000000..c2e60dc --- /dev/null +++ b/src/server/api/routers/webhook.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; + +import { createTRPCRouter, keyProtectedProcedure } from "~/server/api/trpc"; + +export const webhookRouter = createTRPCRouter({ + onUserDelete: keyProtectedProcedure + .meta({ openapi: { method: "POST", path: "/webhook/user/delete" } }) + .input( + z.object({ + data: z.object({ + deleted: z.boolean(), + id: z.string(), + object: z.string(), + }), + object: z.string(), + type: z.string(), + }) + ) + .output(z.string()) + .query(({ input }) => { + console.log(input.data.deleted); + return `Deleted: ${input.data.deleted}`; + }), +}); diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index ebce472..6baa90f 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -25,8 +25,9 @@ import type { import { db } from "../db"; -interface AuthContext { +interface ContextType { auth: SignedInAuthObject | SignedOutAuthObject; + key: string | null; } /** * This helper generates the "internals" for a tRPC context. If you need to use @@ -37,8 +38,9 @@ interface AuthContext { * - trpc's `createSSGHelpers` where we don't have req/res * @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts */ -const createInnerTRPCContext = ({ auth }: AuthContext) => { +const createInnerTRPCContext = ({ auth, key }: ContextType) => { return { + key, auth, db, }; @@ -49,8 +51,19 @@ const createInnerTRPCContext = ({ auth }: AuthContext) => { * process every request that goes through your tRPC endpoint * @link https://trpc.io/docs/context */ -export const createTRPCContext = (opts: CreateNextContextOptions) => { - return createInnerTRPCContext({ auth: getAuth(opts.req) }); +export const createTRPCContext = async (opts: CreateNextContextOptions) => { + let keyValue: string | null = null; + + if (opts.req.headers.authorization) { + const key = opts.req.headers.authorization.split("Bearer ").at(1); + if (key) { + const isValidKey = await validateApiKey(key); + if (isValidKey) { + keyValue = key; + } + } + } + return createInnerTRPCContext({ auth: getAuth(opts.req), key: keyValue }); }; /** @@ -62,6 +75,7 @@ export const createTRPCContext = (opts: CreateNextContextOptions) => { import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; import type { OpenApiMeta } from "trpc-openapi"; +import { validateApiKey } from "../unkey"; const t = initTRPC .context() @@ -85,6 +99,18 @@ const isAuthed = t.middleware(({ next, ctx }) => { }, }); }); + +const isKeyAuthed = t.middleware(({ next, ctx }) => { + if (!ctx.key) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + return next({ + ctx: { + key: ctx.key, + }, + }); +}); /** * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) * @@ -107,3 +133,4 @@ export const createTRPCRouter = t.router; */ export const publicProcedure = t.procedure; export const protectedProcedure = t.procedure.use(isAuthed); +export const keyProtectedProcedure = t.procedure.use(isKeyAuthed);