aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/trpc/auth.ts99
-rw-r--r--packages/trpc/index.ts (renamed from packages/web/server/api/trpc.ts)6
-rw-r--r--packages/trpc/package.json28
-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/trpc/testUtils.ts (renamed from packages/web/lib/testUtils.ts)4
-rw-r--r--packages/trpc/tsconfig.json13
-rw-r--r--packages/trpc/types/bookmarks.ts (renamed from packages/web/lib/types/api/bookmarks.ts)2
-rw-r--r--packages/trpc/types/lists.ts (renamed from packages/web/lib/types/api/lists.ts)0
-rw-r--r--packages/trpc/types/tags.ts (renamed from packages/web/lib/types/api/tags.ts)0
-rw-r--r--packages/trpc/types/users.ts (renamed from packages/web/lib/types/api/users.ts)0
-rw-r--r--packages/trpc/vitest.config.ts14
-rw-r--r--packages/web/app/api/trpc/[trpc]/route.ts4
-rw-r--r--packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx2
-rw-r--r--packages/web/app/dashboard/bookmarks/components/BookmarkedTextEditor.tsx2
-rw-r--r--packages/web/app/dashboard/bookmarks/components/Bookmarks.tsx2
-rw-r--r--packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx2
-rw-r--r--packages/web/app/dashboard/bookmarks/components/LinkCard.tsx2
-rw-r--r--packages/web/app/dashboard/bookmarks/components/TagList.tsx2
-rw-r--r--packages/web/app/dashboard/bookmarks/components/TagModal.tsx4
-rw-r--r--packages/web/app/dashboard/bookmarks/components/TextCard.tsx2
-rw-r--r--packages/web/app/dashboard/components/AllLists.tsx2
-rw-r--r--packages/web/app/dashboard/lists/[listId]/components/DeleteListButton.tsx2
-rw-r--r--packages/web/app/dashboard/lists/[listId]/components/ListView.tsx4
-rw-r--r--packages/web/app/dashboard/lists/components/AllListsView.tsx2
-rw-r--r--packages/web/app/signin/components/CredentialsForm.tsx2
-rw-r--r--packages/web/lib/trpc.tsx2
-rw-r--r--packages/web/package.json6
-rw-r--r--packages/web/server/api/client.ts4
-rw-r--r--packages/web/server/auth.ts100
-rw-r--r--pnpm-lock.yaml213
37 files changed, 405 insertions, 148 deletions
diff --git a/packages/trpc/auth.ts b/packages/trpc/auth.ts
new file mode 100644
index 00000000..6854303b
--- /dev/null
+++ b/packages/trpc/auth.ts
@@ -0,0 +1,99 @@
+import { randomBytes } from "crypto";
+import { apiKeys } from "@hoarder/db/schema";
+import * as bcrypt from "bcrypt";
+import { db } from "@hoarder/db";
+
+// 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;
+}
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/trpc/package.json b/packages/trpc/package.json
new file mode 100644
index 00000000..1e33eff0
--- /dev/null
+++ b/packages/trpc/package.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "@hoarder/trpc",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "typecheck": "tsc --noEmit",
+ "test": "vitest"
+ },
+ "dependencies": {
+ "@hoarder/db": "workspace:*",
+ "@hoarder/shared": "workspace:*",
+ "@trpc/server": "11.0.0-next-beta.304",
+ "bcrypt": "^5.1.1",
+ "drizzle-orm": "^0.29.4",
+ "superjson": "^2.2.1",
+ "zod": "^3.22.4"
+ },
+ "devDependencies": {
+ "@tsconfig/node21": "^21.0.1",
+ "@types/bcrypt": "^5.0.2",
+ "aws-sdk": "^2.1570.0",
+ "mock-aws-s3": "^4.0.2",
+ "nock": "^13.5.4",
+ "vite-tsconfig-paths": "^4.3.1",
+ "vitest": "^1.3.1"
+ }
+}
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/lib/testUtils.ts b/packages/trpc/testUtils.ts
index bad78463..d5f24def 100644
--- a/packages/web/lib/testUtils.ts
+++ b/packages/trpc/testUtils.ts
@@ -1,7 +1,7 @@
import { users } from "@hoarder/db/schema";
import { getInMemoryDB } from "@hoarder/db/drizzle";
-import { appRouter } from "@/server/api/routers/_app";
-import { createCallerFactory } from "@/server/api/trpc";
+import { appRouter } from "./routers/_app";
+import { createCallerFactory } from "./index";
export function getTestDB() {
return getInMemoryDB(true);
diff --git a/packages/trpc/tsconfig.json b/packages/trpc/tsconfig.json
new file mode 100644
index 00000000..bf020b01
--- /dev/null
+++ b/packages/trpc/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "@tsconfig/node21/tsconfig.json",
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules"],
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "baseUrl": "./",
+ "esModuleInterop": true
+ }
+}
+
diff --git a/packages/web/lib/types/api/bookmarks.ts b/packages/trpc/types/bookmarks.ts
index 5fabc7ca..b61ab0e0 100644
--- a/packages/web/lib/types/api/bookmarks.ts
+++ b/packages/trpc/types/bookmarks.ts
@@ -1,5 +1,5 @@
import { z } from "zod";
-import { zBookmarkTagSchema } from "@/lib/types/api/tags";
+import { zBookmarkTagSchema } from "./tags";
export const zBookmarkedLinkSchema = z.object({
type: z.literal("link"),
diff --git a/packages/web/lib/types/api/lists.ts b/packages/trpc/types/lists.ts
index 4b0ccaca..4b0ccaca 100644
--- a/packages/web/lib/types/api/lists.ts
+++ b/packages/trpc/types/lists.ts
diff --git a/packages/web/lib/types/api/tags.ts b/packages/trpc/types/tags.ts
index 7a99dad4..7a99dad4 100644
--- a/packages/web/lib/types/api/tags.ts
+++ b/packages/trpc/types/tags.ts
diff --git a/packages/web/lib/types/api/users.ts b/packages/trpc/types/users.ts
index c2fe182a..c2fe182a 100644
--- a/packages/web/lib/types/api/users.ts
+++ b/packages/trpc/types/users.ts
diff --git a/packages/trpc/vitest.config.ts b/packages/trpc/vitest.config.ts
new file mode 100644
index 00000000..c3d02f71
--- /dev/null
+++ b/packages/trpc/vitest.config.ts
@@ -0,0 +1,14 @@
+/// <reference types="vitest" />
+
+import { defineConfig } from "vitest/config";
+import tsconfigPaths from "vite-tsconfig-paths";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [tsconfigPaths()],
+ test: {
+ alias: {
+ "@/*": "./*",
+ },
+ },
+});
diff --git a/packages/web/app/api/trpc/[trpc]/route.ts b/packages/web/app/api/trpc/[trpc]/route.ts
index 7d56cadc..b6753101 100644
--- a/packages/web/app/api/trpc/[trpc]/route.ts
+++ b/packages/web/app/api/trpc/[trpc]/route.ts
@@ -1,7 +1,7 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
-import { appRouter } from "@/server/api/routers/_app";
+import { appRouter } from "@hoarder/trpc/routers/_app";
import { createContext } from "@/server/api/client";
-import { authenticateApiKey } from "@/server/auth";
+import { authenticateApiKey } from "@hoarder/trpc/auth";
import { db } from "@hoarder/db";
const handler = (req: Request) =>
diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx
index 584e8708..4f08ebee 100644
--- a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx
@@ -2,7 +2,7 @@
import { useToast } from "@/components/ui/use-toast";
import { api } from "@/lib/trpc";
-import { ZBookmark, ZBookmarkedLink } from "@/lib/types/api/bookmarks";
+import { ZBookmark, ZBookmarkedLink } from "@hoarder/trpc/types/bookmarks";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkedTextEditor.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkedTextEditor.tsx
index c449fae3..a5b58f1a 100644
--- a/packages/web/app/dashboard/bookmarks/components/BookmarkedTextEditor.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/BookmarkedTextEditor.tsx
@@ -1,4 +1,4 @@
-import { ZBookmark } from "@/lib/types/api/bookmarks";
+import { ZBookmark } from "@hoarder/trpc/types/bookmarks";
import {
Dialog,
DialogClose,
diff --git a/packages/web/app/dashboard/bookmarks/components/Bookmarks.tsx b/packages/web/app/dashboard/bookmarks/components/Bookmarks.tsx
index 62b93dc8..1ad3670c 100644
--- a/packages/web/app/dashboard/bookmarks/components/Bookmarks.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/Bookmarks.tsx
@@ -1,6 +1,6 @@
import { redirect } from "next/navigation";
import BookmarksGrid from "./BookmarksGrid";
-import { ZGetBookmarksRequest } from "@/lib/types/api/bookmarks";
+import { ZGetBookmarksRequest } from "@hoarder/trpc/types/bookmarks";
import { api } from "@/server/api/client";
import { getServerAuthSession } from "@/server/auth";
diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx
index 554d20a0..4d5b6b0a 100644
--- a/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx
@@ -1,7 +1,7 @@
"use client";
import LinkCard from "./LinkCard";
-import { ZBookmark, ZGetBookmarksRequest } from "@/lib/types/api/bookmarks";
+import { ZBookmark, ZGetBookmarksRequest } from "@hoarder/trpc/types/bookmarks";
import { api } from "@/lib/trpc";
import TextCard from "./TextCard";
import { Slot } from "@radix-ui/react-slot";
diff --git a/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx b/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx
index 5af11aa3..76d3f1b8 100644
--- a/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx
@@ -8,7 +8,7 @@ import {
ImageCardFooter,
ImageCardTitle,
} from "@/components/ui/imageCard";
-import { ZBookmark } from "@/lib/types/api/bookmarks";
+import { ZBookmark } from "@hoarder/trpc/types/bookmarks";
import Link from "next/link";
import BookmarkOptions from "./BookmarkOptions";
import { api } from "@/lib/trpc";
diff --git a/packages/web/app/dashboard/bookmarks/components/TagList.tsx b/packages/web/app/dashboard/bookmarks/components/TagList.tsx
index 82d9f376..6c9d2d22 100644
--- a/packages/web/app/dashboard/bookmarks/components/TagList.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/TagList.tsx
@@ -1,7 +1,7 @@
import { badgeVariants } from "@/components/ui/badge";
import Link from "next/link";
import { Skeleton } from "@/components/ui/skeleton";
-import { ZBookmark } from "@/lib/types/api/bookmarks";
+import { ZBookmark } from "@hoarder/trpc/types/bookmarks";
import { cn } from "@/lib/utils";
export default function TagList({
diff --git a/packages/web/app/dashboard/bookmarks/components/TagModal.tsx b/packages/web/app/dashboard/bookmarks/components/TagModal.tsx
index 703c4221..8c09d00e 100644
--- a/packages/web/app/dashboard/bookmarks/components/TagModal.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/TagModal.tsx
@@ -11,8 +11,8 @@ import {
import { Input } from "@/components/ui/input";
import { toast } from "@/components/ui/use-toast";
import { api } from "@/lib/trpc";
-import { ZBookmark } from "@/lib/types/api/bookmarks";
-import { ZAttachedByEnum } from "@/lib/types/api/tags";
+import { ZBookmark } from "@hoarder/trpc/types/bookmarks";
+import { ZAttachedByEnum } from "@hoarder/trpc/types/tags";
import { cn } from "@/lib/utils";
import { Sparkles, X } from "lucide-react";
import { useState, KeyboardEvent, useEffect } from "react";
diff --git a/packages/web/app/dashboard/bookmarks/components/TextCard.tsx b/packages/web/app/dashboard/bookmarks/components/TextCard.tsx
index 029800ac..5e0ba3f9 100644
--- a/packages/web/app/dashboard/bookmarks/components/TextCard.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/TextCard.tsx
@@ -1,6 +1,6 @@
"use client";
-import { ZBookmark } from "@/lib/types/api/bookmarks";
+import { ZBookmark } from "@hoarder/trpc/types/bookmarks";
import BookmarkOptions from "./BookmarkOptions";
import { api } from "@/lib/trpc";
import { Maximize2, Star } from "lucide-react";
diff --git a/packages/web/app/dashboard/components/AllLists.tsx b/packages/web/app/dashboard/components/AllLists.tsx
index 8903c82a..a77252d0 100644
--- a/packages/web/app/dashboard/components/AllLists.tsx
+++ b/packages/web/app/dashboard/components/AllLists.tsx
@@ -5,7 +5,7 @@ import SidebarItem from "./SidebarItem";
import NewListModal, { useNewListModal } from "./NewListModal";
import { Plus } from "lucide-react";
import Link from "next/link";
-import { ZBookmarkList } from "@/lib/types/api/lists";
+import { ZBookmarkList } from "@hoarder/trpc/types/lists";
export default function AllLists({
initialData,
diff --git a/packages/web/app/dashboard/lists/[listId]/components/DeleteListButton.tsx b/packages/web/app/dashboard/lists/[listId]/components/DeleteListButton.tsx
index 32a7facf..5303b217 100644
--- a/packages/web/app/dashboard/lists/[listId]/components/DeleteListButton.tsx
+++ b/packages/web/app/dashboard/lists/[listId]/components/DeleteListButton.tsx
@@ -16,7 +16,7 @@ import { toast } from "@/components/ui/use-toast";
import { api } from "@/lib/trpc";
import { ActionButton } from "@/components/ui/action-button";
import { useState } from "react";
-import { ZBookmarkList } from "@/lib/types/api/lists";
+import { ZBookmarkList } from "@hoarder/trpc/types/lists";
export default function DeleteListButton({ list }: { list: ZBookmarkList }) {
const [isDialogOpen, setDialogOpen] = useState(false);
diff --git a/packages/web/app/dashboard/lists/[listId]/components/ListView.tsx b/packages/web/app/dashboard/lists/[listId]/components/ListView.tsx
index 6489e9f0..979b522f 100644
--- a/packages/web/app/dashboard/lists/[listId]/components/ListView.tsx
+++ b/packages/web/app/dashboard/lists/[listId]/components/ListView.tsx
@@ -1,8 +1,8 @@
"use client";
import BookmarksGrid from "@/app/dashboard/bookmarks/components/BookmarksGrid";
-import { ZBookmark } from "@/lib/types/api/bookmarks";
-import { ZBookmarkListWithBookmarks } from "@/lib/types/api/lists";
+import { ZBookmark } from "@hoarder/trpc/types/bookmarks";
+import { ZBookmarkListWithBookmarks } from "@hoarder/trpc/types/lists";
import { api } from "@/lib/trpc";
export default function ListView({
diff --git a/packages/web/app/dashboard/lists/components/AllListsView.tsx b/packages/web/app/dashboard/lists/components/AllListsView.tsx
index d81f5fca..0e2f898b 100644
--- a/packages/web/app/dashboard/lists/components/AllListsView.tsx
+++ b/packages/web/app/dashboard/lists/components/AllListsView.tsx
@@ -2,7 +2,7 @@
import { Button } from "@/components/ui/button";
import { api } from "@/lib/trpc";
-import { ZBookmarkList } from "@/lib/types/api/lists";
+import { ZBookmarkList } from "@hoarder/trpc/types/lists";
import { keepPreviousData } from "@tanstack/react-query";
import { Plus } from "lucide-react";
import Link from "next/link";
diff --git a/packages/web/app/signin/components/CredentialsForm.tsx b/packages/web/app/signin/components/CredentialsForm.tsx
index f47708f6..5296e163 100644
--- a/packages/web/app/signin/components/CredentialsForm.tsx
+++ b/packages/web/app/signin/components/CredentialsForm.tsx
@@ -13,7 +13,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { ActionButton } from "@/components/ui/action-button";
-import { zSignUpSchema } from "@/lib/types/api/users";
+import { zSignUpSchema } from "@hoarder/trpc/types/users";
import { signIn } from "next-auth/react";
import { useState } from "react";
import { api } from "@/lib/trpc";
diff --git a/packages/web/lib/trpc.tsx b/packages/web/lib/trpc.tsx
index aa246047..79a2a9fe 100644
--- a/packages/web/lib/trpc.tsx
+++ b/packages/web/lib/trpc.tsx
@@ -1,5 +1,5 @@
"use client";
-import type { AppRouter } from "@/server/api/routers/_app";
+import type { AppRouter } from "@hoarder/trpc/routers/_app";
import { createTRPCReact } from "@trpc/react-query";
export const api = createTRPCReact<AppRouter>();
diff --git a/packages/web/package.json b/packages/web/package.json
index 5367d189..e0c9d407 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -8,7 +8,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
- "test": "vitest"
+ "test": "vitest",
+ "typecheck": "tsc --noEmit"
},
"dependencies": {
"@auth/drizzle-adapter": "^0.8.0",
@@ -16,6 +17,7 @@
"@emoji-mart/react": "^1.1.1",
"@hoarder/db": "0.1.0",
"@hoarder/shared": "0.1.0",
+ "@hoarder/trpc": "0.1.0",
"@hookform/resolvers": "^3.3.4",
"@next/eslint-plugin-next": "^14.1.1",
"@radix-ui/react-dialog": "^1.0.5",
@@ -34,7 +36,6 @@
"@trpc/next": "11.0.0-next-beta.304",
"@trpc/react-query": "^11.0.0-next-beta.304",
"@trpc/server": "11.0.0-next-beta.304",
- "bcrypt": "^5.1.1",
"better-sqlite3": "^9.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
@@ -60,7 +61,6 @@
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.10",
- "@types/bcrypt": "^5.0.2",
"@types/emoji-mart": "^3.0.14",
"@types/react": "^18",
"@types/react-dom": "^18",
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;
-}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4f7a22a6..dc333de5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -177,6 +177,52 @@ importers:
specifier: ^3.22.4
version: 3.22.4
+ packages/trpc:
+ dependencies:
+ '@hoarder/db':
+ specifier: workspace:*
+ version: link:../db
+ '@hoarder/shared':
+ specifier: workspace:*
+ version: link:../shared
+ '@trpc/server':
+ specifier: 11.0.0-next-beta.304
+ version: 11.0.0-next-beta.304
+ bcrypt:
+ specifier: ^5.1.1
+ version: 5.1.1
+ drizzle-orm:
+ specifier: ^0.29.4
+ version: 0.29.4(@types/react@18.2.58)(better-sqlite3@9.4.3)(react@18.2.0)
+ superjson:
+ specifier: ^2.2.1
+ version: 2.2.1
+ zod:
+ specifier: ^3.22.4
+ version: 3.22.4
+ devDependencies:
+ '@tsconfig/node21':
+ specifier: ^21.0.1
+ version: 21.0.1
+ '@types/bcrypt':
+ specifier: ^5.0.2
+ version: 5.0.2
+ aws-sdk:
+ specifier: ^2.1570.0
+ version: 2.1570.0
+ mock-aws-s3:
+ specifier: ^4.0.2
+ version: 4.0.2
+ nock:
+ specifier: ^13.5.4
+ version: 13.5.4
+ vite-tsconfig-paths:
+ specifier: ^4.3.1
+ version: 4.3.1(typescript@5.3.3)
+ vitest:
+ specifier: ^1.3.1
+ version: 1.3.1(@types/node@20.11.20)
+
packages/web:
dependencies:
'@auth/drizzle-adapter':
@@ -194,6 +240,9 @@ importers:
'@hoarder/shared':
specifier: 0.1.0
version: link:../shared
+ '@hoarder/trpc':
+ specifier: 0.1.0
+ version: link:../trpc
'@hookform/resolvers':
specifier: ^3.3.4
version: 3.3.4(react-hook-form@7.50.1)
@@ -248,9 +297,6 @@ importers:
'@trpc/server':
specifier: 11.0.0-next-beta.304
version: 11.0.0-next-beta.304
- bcrypt:
- specifier: ^5.1.1
- version: 5.1.1
better-sqlite3:
specifier: ^9.4.3
version: 9.4.3
@@ -321,9 +367,6 @@ importers:
'@tailwindcss/typography':
specifier: ^0.5.10
version: 0.5.10(tailwindcss@3.4.1)
- '@types/bcrypt':
- specifier: ^5.0.2
- version: 5.0.2
'@types/emoji-mart':
specifier: ^3.0.14
version: 3.0.14
@@ -4804,6 +4847,22 @@ packages:
dependencies:
possible-typed-array-names: 1.0.0
+ /aws-sdk@2.1570.0:
+ resolution: {integrity: sha512-WySdibC3YOPCFcXNSevX7cGp6Nc0Ksv7m6aaz6YoqSrmSn7mZhkWaVXqfd14nsjJuyEbEgX+gAiZaahyvkUYJw==}
+ engines: {node: '>= 10.0.0'}
+ dependencies:
+ buffer: 4.9.2
+ events: 1.1.1
+ ieee754: 1.1.13
+ jmespath: 0.16.0
+ querystring: 0.2.0
+ sax: 1.2.1
+ url: 0.10.3
+ util: 0.12.5
+ uuid: 8.0.0
+ xml2js: 0.6.2
+ dev: true
+
/axe-core@4.7.0:
resolution: {integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==}
engines: {node: '>=4'}
@@ -4914,7 +4973,6 @@ packages:
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
- dev: false
/basic-ftp@5.0.4:
resolution: {integrity: sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==}
@@ -4963,6 +5021,10 @@ packages:
readable-stream: 3.6.2
dev: false
+ /bluebird@3.7.2:
+ resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
+ dev: true
+
/boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -5000,6 +5062,14 @@ packages:
/buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+ /buffer@4.9.2:
+ resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==}
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+ isarray: 1.0.0
+ dev: true
+
/buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
dependencies:
@@ -6647,6 +6717,11 @@ packages:
engines: {node: '>=6'}
dev: false
+ /events@1.1.1:
+ resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==}
+ engines: {node: '>=0.4.x'}
+ dev: true
+
/events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
@@ -6879,6 +6954,15 @@ packages:
universalify: 2.0.1
dev: false
+ /fs-extra@7.0.1:
+ resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
+ engines: {node: '>=6 <7 || >=8'}
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 4.0.0
+ universalify: 0.1.2
+ dev: true
+
/fs-extra@9.1.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
engines: {node: '>=10'}
@@ -7311,9 +7395,12 @@ packages:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
dev: false
+ /ieee754@1.1.13:
+ resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==}
+ dev: true
+
/ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
- dev: false
/ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
@@ -7426,6 +7513,14 @@ packages:
is-decimal: 2.0.1
dev: false
+ /is-arguments@1.1.1:
+ resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.7
+ has-tostringtag: 1.0.2
+ dev: true
+
/is-array-buffer@3.0.4:
resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
engines: {node: '>= 0.4'}
@@ -7677,6 +7772,10 @@ packages:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
engines: {node: '>=12.13'}
+ /isarray@1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+ dev: true
+
/isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
@@ -7751,6 +7850,11 @@ packages:
resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
hasBin: true
+ /jmespath@0.16.0:
+ resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==}
+ engines: {node: '>= 0.6.0'}
+ dev: true
+
/jose@4.15.4:
resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==}
dev: false
@@ -7859,6 +7963,10 @@ packages:
/json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+ /json-stringify-safe@5.0.1:
+ resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
+ dev: true
+
/json5@1.0.2:
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
hasBin: true
@@ -7876,6 +7984,12 @@ packages:
resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==}
dev: true
+ /jsonfile@4.0.0:
+ resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+ optionalDependencies:
+ graceful-fs: 4.2.11
+ dev: true
+
/jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
dependencies:
@@ -8791,6 +8905,15 @@ packages:
ufo: 1.4.0
dev: true
+ /mock-aws-s3@4.0.2:
+ resolution: {integrity: sha512-J6g3MMCuKHeuqVEOgvQfRGIfVmg6KKrED48Bux/L9rTY3NPK9TFRh/9bCf5AuzjJm9PIlwhDEO99tD8+smnTyQ==}
+ engines: {node: '>=10.0.0'}
+ dependencies:
+ bluebird: 3.7.2
+ fs-extra: 7.0.1
+ underscore: 1.12.1
+ dev: true
+
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: true
@@ -8950,6 +9073,17 @@ packages:
- babel-plugin-macros
dev: false
+ /nock@13.5.4:
+ resolution: {integrity: sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==}
+ engines: {node: '>= 10.13'}
+ dependencies:
+ debug: 4.3.4
+ json-stringify-safe: 5.0.1
+ propagate: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/node-abi@3.56.0:
resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==}
engines: {node: '>=10'}
@@ -9660,6 +9794,11 @@ packages:
object-assign: 4.1.1
react-is: 16.13.1
+ /propagate@2.0.1:
+ resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==}
+ engines: {node: '>= 8'}
+ dev: true
+
/property-information@6.4.1:
resolution: {integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==}
dev: false
@@ -9700,6 +9839,10 @@ packages:
engines: {node: '>=0.10'}
dev: false
+ /punycode@1.3.2:
+ resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==}
+ dev: true
+
/punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -9875,6 +10018,12 @@ packages:
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
dev: true
+ /querystring@0.2.0:
+ resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==}
+ engines: {node: '>=0.4.x'}
+ deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
+ dev: true
+
/querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
dev: false
@@ -10342,6 +10491,10 @@ packages:
requiresBuild: true
dev: false
+ /sax@1.2.1:
+ resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==}
+ dev: true
+
/saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
@@ -11238,6 +11391,10 @@ packages:
through: 2.3.8
dev: false
+ /underscore@1.12.1:
+ resolution: {integrity: sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==}
+ dev: true
+
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
@@ -11337,6 +11494,11 @@ packages:
unist-util-visit-parents: 6.0.1
dev: false
+ /universalify@0.1.2:
+ resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
+ engines: {node: '>= 4.0.0'}
+ dev: true
+
/universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
@@ -11387,6 +11549,13 @@ packages:
tlds: 1.250.0
dev: false
+ /url@0.10.3:
+ resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==}
+ dependencies:
+ punycode: 1.3.2
+ querystring: 0.2.0
+ dev: true
+
/urlpattern-polyfill@10.0.0:
resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==}
dev: false
@@ -11441,6 +11610,21 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ /util@0.12.5:
+ resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
+ dependencies:
+ inherits: 2.0.4
+ is-arguments: 1.1.1
+ is-generator-function: 1.0.10
+ is-typed-array: 1.1.13
+ which-typed-array: 1.1.14
+ dev: true
+
+ /uuid@8.0.0:
+ resolution: {integrity: sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==}
+ hasBin: true
+ dev: true
+
/uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -12042,6 +12226,19 @@ packages:
engines: {node: '>=18'}
dev: false
+ /xml2js@0.6.2:
+ resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
+ engines: {node: '>=4.0.0'}
+ dependencies:
+ sax: 1.2.1
+ xmlbuilder: 11.0.1
+ dev: true
+
+ /xmlbuilder@11.0.1:
+ resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
+ engines: {node: '>=4.0'}
+ dev: true
+
/xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
dev: false