diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-05 13:11:06 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-03-05 13:11:06 +0000 |
| commit | 8a46ecb7373d6c5e7300861169ea51a7917cd2b4 (patch) | |
| tree | 4ad318c3b5fc8b7a74cba6d0e37b6ade24db829a /packages/web/server | |
| parent | 224aa38d5976523f213e2860b6addc7630d472ba (diff) | |
| download | karakeep-8a46ecb7373d6c5e7300861169ea51a7917cd2b4.tar.zst | |
refactor: Extract trpc logic into its package
Diffstat (limited to '')
| -rw-r--r-- | packages/trpc/index.ts (renamed from packages/web/server/api/trpc.ts) | 6 | ||||
| -rw-r--r-- | packages/trpc/routers/_app.ts (renamed from packages/web/server/api/routers/_app.ts) | 2 | ||||
| -rw-r--r-- | packages/trpc/routers/admin.ts (renamed from packages/web/server/api/routers/admin.ts) | 2 | ||||
| -rw-r--r-- | packages/trpc/routers/apiKeys.ts (renamed from packages/web/server/api/routers/apiKeys.ts) | 4 | ||||
| -rw-r--r-- | packages/trpc/routers/bookmarks.test.ts (renamed from packages/web/server/api/routers/bookmarks.test.ts) | 2 | ||||
| -rw-r--r-- | packages/trpc/routers/bookmarks.ts (renamed from packages/web/server/api/routers/bookmarks.ts) | 6 | ||||
| -rw-r--r-- | packages/trpc/routers/lists.ts (renamed from packages/web/server/api/routers/lists.ts) | 4 | ||||
| -rw-r--r-- | packages/trpc/routers/users.test.ts (renamed from packages/web/server/api/routers/users.test.ts) | 2 | ||||
| -rw-r--r-- | packages/trpc/routers/users.ts (renamed from packages/web/server/api/routers/users.ts) | 6 | ||||
| -rw-r--r-- | packages/web/server/api/client.ts | 4 | ||||
| -rw-r--r-- | packages/web/server/auth.ts | 100 |
11 files changed, 22 insertions, 116 deletions
diff --git a/packages/web/server/api/trpc.ts b/packages/trpc/index.ts index 0ba09e94..a32eb871 100644 --- a/packages/web/server/api/trpc.ts +++ b/packages/trpc/index.ts @@ -1,9 +1,13 @@ import { db } from "@hoarder/db"; import serverConfig from "@hoarder/shared/config"; import { TRPCError, initTRPC } from "@trpc/server"; -import { User } from "next-auth"; import superjson from "superjson"; +type User = { + id: string; + role: "admin" | "user" | null; +}; + export type Context = { user: User | null; db: typeof db; diff --git a/packages/web/server/api/routers/_app.ts b/packages/trpc/routers/_app.ts index 43ab6f5d..6e5dd91d 100644 --- a/packages/web/server/api/routers/_app.ts +++ b/packages/trpc/routers/_app.ts @@ -1,4 +1,4 @@ -import { router } from "../trpc"; +import { router } from "../index"; import { adminAppRouter } from "./admin"; import { apiKeysAppRouter } from "./apiKeys"; import { bookmarksAppRouter } from "./bookmarks"; diff --git a/packages/web/server/api/routers/admin.ts b/packages/trpc/routers/admin.ts index c3f6235a..8a7b592d 100644 --- a/packages/web/server/api/routers/admin.ts +++ b/packages/trpc/routers/admin.ts @@ -1,4 +1,4 @@ -import { adminProcedure, router } from "../trpc"; +import { adminProcedure, router } from "../index"; import { z } from "zod"; import { count } from "drizzle-orm"; import { bookmarks, users } from "@hoarder/db/schema"; diff --git a/packages/web/server/api/routers/apiKeys.ts b/packages/trpc/routers/apiKeys.ts index 9eb36974..d13f87fb 100644 --- a/packages/web/server/api/routers/apiKeys.ts +++ b/packages/trpc/routers/apiKeys.ts @@ -1,5 +1,5 @@ -import { generateApiKey } from "@/server/auth"; -import { authedProcedure, router } from "../trpc"; +import { generateApiKey } from "../auth"; +import { authedProcedure, router } from "../index"; import { z } from "zod"; import { apiKeys } from "@hoarder/db/schema"; import { eq, and } from "drizzle-orm"; diff --git a/packages/web/server/api/routers/bookmarks.test.ts b/packages/trpc/routers/bookmarks.test.ts index 626a7250..724a9998 100644 --- a/packages/web/server/api/routers/bookmarks.test.ts +++ b/packages/trpc/routers/bookmarks.test.ts @@ -1,4 +1,4 @@ -import { CustomTestContext, defaultBeforeEach } from "@/lib/testUtils"; +import { CustomTestContext, defaultBeforeEach } from "../testUtils"; import { expect, describe, test, beforeEach, assert } from "vitest"; beforeEach<CustomTestContext>(defaultBeforeEach(true)); diff --git a/packages/web/server/api/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts index 73818508..ea7ffef8 100644 --- a/packages/web/server/api/routers/bookmarks.ts +++ b/packages/trpc/routers/bookmarks.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { Context, authedProcedure, router } from "../trpc"; +import { Context, authedProcedure, router } from "../index"; import { getSearchIdxClient } from "@hoarder/shared/search"; import { ZBookmark, @@ -10,7 +10,7 @@ import { zGetBookmarksResponseSchema, zNewBookmarkRequestSchema, zUpdateBookmarksRequestSchema, -} from "@/lib/types/api/bookmarks"; +} from "../types/bookmarks"; import { bookmarkLinks, bookmarkTags, @@ -25,7 +25,7 @@ import { } from "@hoarder/shared/queues"; import { TRPCError, experimental_trpcMiddleware } from "@trpc/server"; import { and, desc, eq, inArray } from "drizzle-orm"; -import { ZBookmarkTags } from "@/lib/types/api/tags"; +import { ZBookmarkTags } from "../types/tags"; import { db as DONT_USE_db } from "@hoarder/db"; diff --git a/packages/web/server/api/routers/lists.ts b/packages/trpc/routers/lists.ts index 7bf5eed5..fa97929d 100644 --- a/packages/web/server/api/routers/lists.ts +++ b/packages/trpc/routers/lists.ts @@ -1,10 +1,10 @@ -import { Context, authedProcedure, router } from "../trpc"; +import { Context, authedProcedure, router } from "../index"; import { SqliteError } from "@hoarder/db"; import { z } from "zod"; import { TRPCError, experimental_trpcMiddleware } from "@trpc/server"; import { bookmarkLists, bookmarksInLists } from "@hoarder/db/schema"; import { and, eq } from "drizzle-orm"; -import { zBookmarkListSchema } from "@/lib/types/api/lists"; +import { zBookmarkListSchema } from "../types/lists"; const ensureListOwnership = experimental_trpcMiddleware<{ ctx: Context; diff --git a/packages/web/server/api/routers/users.test.ts b/packages/trpc/routers/users.test.ts index 1ee04f99..87814407 100644 --- a/packages/web/server/api/routers/users.test.ts +++ b/packages/trpc/routers/users.test.ts @@ -2,7 +2,7 @@ import { CustomTestContext, defaultBeforeEach, getApiCaller, -} from "@/lib/testUtils"; +} from "../testUtils"; import { expect, describe, test, beforeEach, assert } from "vitest"; beforeEach<CustomTestContext>(defaultBeforeEach(false)); diff --git a/packages/web/server/api/routers/users.ts b/packages/trpc/routers/users.ts index 32d10860..b5334f99 100644 --- a/packages/web/server/api/routers/users.ts +++ b/packages/trpc/routers/users.ts @@ -1,8 +1,8 @@ -import { zSignUpSchema } from "@/lib/types/api/users"; -import { adminProcedure, publicProcedure, router } from "../trpc"; +import { zSignUpSchema } from "../types/users"; +import { adminProcedure, publicProcedure, router } from "../index"; import { SqliteError } from "@hoarder/db"; import { z } from "zod"; -import { hashPassword } from "@/server/auth"; +import { hashPassword } from "../auth"; import { TRPCError } from "@trpc/server"; import { users } from "@hoarder/db/schema"; import { count, eq } from "drizzle-orm"; diff --git a/packages/web/server/api/client.ts b/packages/web/server/api/client.ts index 130f4f87..88ea7a0e 100644 --- a/packages/web/server/api/client.ts +++ b/packages/web/server/api/client.ts @@ -1,6 +1,6 @@ -import { appRouter } from "./routers/_app"; +import { appRouter } from "@hoarder/trpc/routers/_app"; import { getServerAuthSession } from "@/server/auth"; -import { Context, createCallerFactory } from "./trpc"; +import { Context, createCallerFactory } from "@hoarder/trpc"; import { db } from "@hoarder/db"; export const createContext = async (database?: typeof db): Promise<Context> => { diff --git a/packages/web/server/auth.ts b/packages/web/server/auth.ts index 1810c87d..950443b9 100644 --- a/packages/web/server/auth.ts +++ b/packages/web/server/auth.ts @@ -2,15 +2,13 @@ import NextAuth, { NextAuthOptions, getServerSession } from "next-auth"; import type { Adapter } from "next-auth/adapters"; import AuthentikProvider from "next-auth/providers/authentik"; import serverConfig from "@hoarder/shared/config"; +import { validatePassword } from "@hoarder/trpc/auth"; import { db } from "@hoarder/db"; import { DefaultSession } from "next-auth"; -import * as bcrypt from "bcrypt"; import CredentialsProvider from "next-auth/providers/credentials"; import { DrizzleAdapter } from "@auth/drizzle-adapter"; -import { randomBytes } from "crypto"; import { Provider } from "next-auth/providers/index"; -import { apiKeys } from "@hoarder/db/schema"; declare module "next-auth/jwt" { export interface JWT { @@ -96,99 +94,3 @@ export const authOptions: NextAuthOptions = { export const authHandler = NextAuth(authOptions); export const getServerAuthSession = () => getServerSession(authOptions); - -// API Keys - -const BCRYPT_SALT_ROUNDS = 10; -const API_KEY_PREFIX = "ak1"; - -export async function generateApiKey(name: string, userId: string) { - const id = randomBytes(10).toString("hex"); - const secret = randomBytes(10).toString("hex"); - const secretHash = await bcrypt.hash(secret, BCRYPT_SALT_ROUNDS); - - const plain = `${API_KEY_PREFIX}_${id}_${secret}`; - - const key = ( - await db - .insert(apiKeys) - .values({ - name: name, - userId: userId, - keyId: id, - keyHash: secretHash, - }) - .returning() - )[0]; - - return { - id: key.id, - name: key.name, - createdAt: key.createdAt, - key: plain, - }; -} - -function parseApiKey(plain: string) { - const parts = plain.split("_"); - if (parts.length != 3) { - throw new Error( - `Malformd API key. API keys should have 3 segments, found ${parts.length} instead.`, - ); - } - if (parts[0] !== API_KEY_PREFIX) { - throw new Error(`Malformd API key. Got unexpected key prefix.`); - } - return { - keyId: parts[1], - keySecret: parts[2], - }; -} - -export async function authenticateApiKey(key: string) { - const { keyId, keySecret } = parseApiKey(key); - const apiKey = await db.query.apiKeys.findFirst({ - where: (k, { eq }) => eq(k.keyId, keyId), - with: { - user: true, - }, - }); - - if (!apiKey) { - throw new Error("API key not found"); - } - - const hash = apiKey.keyHash; - - const validation = await bcrypt.compare(keySecret, hash); - if (!validation) { - throw new Error("Invalid API Key"); - } - - return apiKey.user; -} - -export async function hashPassword(password: string) { - return bcrypt.hash(password, BCRYPT_SALT_ROUNDS); -} - -export async function validatePassword(email: string, password: string) { - const user = await db.query.users.findFirst({ - where: (u, { eq }) => eq(u.email, email), - }); - - if (!user) { - throw new Error("User not found"); - } - - if (!user.password) { - throw new Error("This user doesn't have a password defined"); - } - - const validation = await bcrypt.compare(password, user.password); - if (!validation) { - throw new Error("Wrong password"); - } - - return user; -} |
