From ef8f82b1a0ad934602c960e3cedb22ac2546acb7 Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Sun, 6 Aug 2023 14:09:03 -0600 Subject: [PATCH] API Keys --- .env.example | 3 +++ package.json | 1 + pnpm-lock.yaml | 7 +++++++ src/env.mjs | 2 ++ src/pages/api/cron/dbWarmer.ts | 13 ------------- src/server/api/root.ts | 2 ++ src/server/api/routers/hook.ts | 21 +++++++++++++++++++++ src/server/api/routers/vote.ts | 24 ++++++++++-------------- src/server/api/trpc.ts | 18 ++++++++++++++++-- src/server/unkey.ts | 15 +++++++++++++++ vercel.json | 8 -------- 11 files changed, 77 insertions(+), 37 deletions(-) delete mode 100644 src/pages/api/cron/dbWarmer.ts create mode 100644 src/server/api/routers/hook.ts create mode 100644 src/server/unkey.ts delete mode 100644 vercel.json diff --git a/.env.example b/.env.example index 9ea8a2a..caecbe6 100644 --- a/.env.example +++ b/.env.example @@ -27,6 +27,9 @@ NEXT_PUBLIC_ABLY_PUBLIC_KEY="" # Email RESEND_API_KEY="" +# Unkey +UNKEY_ROOT_KEY="" + # Misc APP_ENV="" NEXT_PUBLIC_APP_ENV="" diff --git a/package.json b/package.json index 4fef619..a123f11 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@trpc/next": "10.37.1", "@trpc/react-query": "10.37.1", "@trpc/server": "10.37.1", + "@unkey/api": "^0.5.0", "@upstash/ratelimit": "^0.4.3", "@upstash/redis": "^1.22.0", "ably": "^1.2.42", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e039d6f..0f2b82e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: '@trpc/server': specifier: 10.37.1 version: 10.37.1 + '@unkey/api': + specifier: ^0.5.0 + version: 0.5.0 '@upstash/ratelimit': specifier: ^0.4.3 version: 0.4.3 @@ -1359,6 +1362,10 @@ packages: eslint-visitor-keys: 3.4.2 dev: true + /@unkey/api@0.5.0: + resolution: {integrity: sha512-C3q2ITvBR5jpK68KNAhhqcR+BzRhXW9ppubdrbaXawEnwxE+F+I2qMU+KqkCM1MbP+ZUv4/E1gX8cobH4X0sWg==} + dev: false + /@upstash/core-analytics@0.0.6: resolution: {integrity: sha512-cpPSR0XJAJs4Ddz9nq3tINlPS5aLfWVCqhhtHnXt4p7qr5+/Znlt1Es736poB/9rnl1hAHrOsOvVj46NEXcVqA==} engines: {node: '>=16.0.0'} diff --git a/src/env.mjs b/src/env.mjs index 4c1f2de..6212cc3 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -30,6 +30,7 @@ const server = z.object({ ABLY_PRIVATE_KEY: z.string(), APP_ENV: z.string(), RESEND_API_KEY: z.string(), + UNKEY_ROOT_KEY: z.string(), }); /** @@ -66,6 +67,7 @@ const processEnv = { APP_ENV: process.env.APP_ENV, NEXT_PUBLIC_APP_ENV: process.env.NEXT_PUBLIC_APP_ENV, RESEND_API_KEY: process.env.RESEND_API_KEY, + UNKEY_ROOT_KEY: process.env.UNKEY_ROOT_KEY, }; // Don't touch the part below diff --git a/src/pages/api/cron/dbWarmer.ts b/src/pages/api/cron/dbWarmer.ts deleted file mode 100644 index 41ccfa9..0000000 --- a/src/pages/api/cron/dbWarmer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { prisma } from "~/server/db"; -import type { NextApiRequest, NextApiResponse } from "next"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - console.log("Cron Request: ", req); - // the most useless call... nothing exists here - await prisma.verificationToken.findMany(); - - res.status(200); -} diff --git a/src/server/api/root.ts b/src/server/api/root.ts index 93561db..9f41918 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -3,6 +3,7 @@ import { createTRPCRouter } from "~/server/api/trpc"; import { sessionRouter } from "./routers/session"; import { userRouter } from "./routers/user"; import { voteRouter } from "./routers/vote"; +import { hookRouter } from "./routers/hook"; /** * This is the primary router for your server. @@ -14,6 +15,7 @@ export const appRouter = createTRPCRouter({ vote: voteRouter, user: userRouter, session: sessionRouter, + hook: hookRouter, }); // export type definition of API diff --git a/src/server/api/routers/hook.ts b/src/server/api/routers/hook.ts new file mode 100644 index 0000000..78f0234 --- /dev/null +++ b/src/server/api/routers/hook.ts @@ -0,0 +1,21 @@ +import { validateApiKey } from "~/server/unkey"; +import { z } from "zod"; + +import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; +import { TRPCError } from "@trpc/server"; + +export const hookRouter = createTRPCRouter({ + dbWarmer: publicProcedure + .meta({ openapi: { method: "GET", path: "/rest/test" } }) + .input(z.object({ key: z.string() })) + .output(z.string()) + .query(async ({ ctx, input }) => { + const isValidKey = await validateApiKey(input.key); + if (isValidKey) { + await ctx.prisma.verificationToken.findMany(); + return "Toasted the DB"; + } else { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + }), +}); diff --git a/src/server/api/routers/vote.ts b/src/server/api/routers/vote.ts index 2c609a2..7ac337f 100644 --- a/src/server/api/routers/vote.ts +++ b/src/server/api/routers/vote.ts @@ -10,23 +10,19 @@ import { import { fetchCache, invalidateCache, setCache } from "~/server/redis"; export const voteRouter = createTRPCRouter({ - countAll: adminProcedure - .input(z.void()) - .output(z.number()) - .meta({ openapi: { method: "GET", path: "/votes/count" } }) - .query(async ({ ctx }) => { - const cachedResult = await fetchCache(`kv_votecount_admin`); + countAll: adminProcedure.query(async ({ ctx }) => { + const cachedResult = await fetchCache(`kv_votecount_admin`); - if (cachedResult) { - return cachedResult; - } else { - const votesCount = await ctx.prisma.vote.count(); + if (cachedResult) { + return cachedResult; + } else { + const votesCount = await ctx.prisma.vote.count(); - await setCache(`kv_votecount_admin`, votesCount); + await setCache(`kv_votecount_admin`, votesCount); - return votesCount; - } - }), + return votesCount; + } + }), getAllByRoomId: protectedProcedure .input(z.object({ roomId: z.string() })) .query(async ({ ctx, input }) => { diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index b4e956c..2bf95d0 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -107,7 +107,7 @@ export const createTRPCRouter = t.router; export const publicProcedure = t.procedure; /** Reusable middleware that enforces users are logged in before running the procedure. */ -const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => { +const enforceAuthSession = t.middleware(async ({ ctx, next }) => { // Auth if (!ctx.session || !ctx.session.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); @@ -146,6 +146,20 @@ const enforceAdminRole = t.middleware(async ({ ctx, next }) => { }); }); +// const enforceApiToken = t.middleware(async ({ ctx, next, path }) => { +// const res = await unkey.keys.verify({ +// key: "" +// }) +// if (!ctx.session || !ctx.session.user || !ctx.session?.user.isAdmin) +// throw new TRPCError({ code: "UNAUTHORIZED" }); + +// return next({ +// ctx: { +// session: { ...ctx.session, user: ctx.session.user }, +// }, +// }); +// }); + /** * Protected (authenticated) procedure * @@ -154,6 +168,6 @@ const enforceAdminRole = t.middleware(async ({ ctx, next }) => { * * @see https://trpc.io/docs/procedures */ -export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); +export const protectedProcedure = t.procedure.use(enforceAuthSession); export const adminProcedure = t.procedure.use(enforceAdminRole); diff --git a/src/server/unkey.ts b/src/server/unkey.ts new file mode 100644 index 0000000..84bcbb1 --- /dev/null +++ b/src/server/unkey.ts @@ -0,0 +1,15 @@ +import { Unkey } from "@unkey/api"; +import { env } from "~/env.mjs"; + +export const unkey = new Unkey({ token: env.UNKEY_ROOT_KEY }); + +export const validateApiKey = async (key: string) => { + try { + const res = await unkey.keys.verify({ + key, + }); + return res.valid; + } catch { + return false; + } +}; diff --git a/vercel.json b/vercel.json deleted file mode 100644 index 32dd5d2..0000000 --- a/vercel.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "crons": [ - { - "path": "/api/cron/dbWarmer", - "schedule": "0 0 */6 * *" - } - ] -}