From ecb2a2080ce6c7822455782e7c4481708afd7685 Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Tue, 6 Aug 2024 23:17:21 +0545 Subject: [PATCH 01/13] fix: conflict in schema --- src/server/audit/schema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/audit/schema.ts b/src/server/audit/schema.ts index d952ead73..1ad07d362 100644 --- a/src/server/audit/schema.ts +++ b/src/server/audit/schema.ts @@ -59,6 +59,7 @@ export const AuditSchema = z.object({ "update.unshared", "accessToken.created", + "accessToken.rotated", "accessToken.deleted", "bucket.created", From 1416a9925cb51ed8ec679ed1848cbb3af22e5c2d Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Tue, 6 Aug 2024 23:18:22 +0545 Subject: [PATCH 02/13] fix: conflict --- src/trpc/routers/access-token/router.ts | 77 ++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/trpc/routers/access-token/router.ts b/src/trpc/routers/access-token/router.ts index adc33320e..4c37b48bb 100644 --- a/src/trpc/routers/access-token/router.ts +++ b/src/trpc/routers/access-token/router.ts @@ -1,7 +1,6 @@ import { createSecureHash, initializeAccessToken } from "@/lib/crypto"; import { AccessTokenType } from "@/prisma/enums"; import { Audit } from "@/server/audit"; - import { createTRPCRouter, withAccessControl } from "@/trpc/api/trpc"; import { TRPCError } from "@trpc/server"; import z from "zod"; @@ -92,6 +91,82 @@ export const accessTokenRouter = createTRPCRouter({ }; }), + rotate: withAccessControl + .input(z.object({ keyId: z.string() })) + .meta({ policies: { "api-keys": { allow: ["update"] } } }) + .mutation(async ({ ctx, input }) => { + try { + const { + db, + membership: { memberId, companyId }, + session, + requestIp, + userAgent, + } = ctx; + const { keyId } = input; + const { user } = session; + + const rotatedKey = await db.$transaction(async (tx) => { + const existingKey = await tx.apiKey.findUnique({ + where: { + keyId, + active: true, + }, + }); + + if (!existingKey) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Api key not found", + }); + } + + await tx.apiKey.delete({ + where: { keyId: existingKey.keyId }, + }); + + const key = await createApiKeyHandler({ tx, companyId, memberId }); + + await Audit.create( + { + action: "apikey.rotated", + companyId, + actor: { type: "user", id: user.id }, + context: { + userAgent, + requestIp, + }, + target: [{ type: "apiKey", id: key.id }], + summary: `${user.name} rotated the apiKey ${key.name}`, + }, + tx, + ); + return key; + }); + + return { + token: rotatedKey.hashedToken, + keyId: rotatedKey.keyId, + createdAt: rotatedKey.createdAt, + }; + } catch (error) { + console.error("Error rotating the api key :", error); + if (error instanceof TRPCError) { + return { + success: false, + message: error.message, + }; + } + return { + success: false, + message: + error instanceof Error + ? error.message + : "Oops, something went wrong. Please try again later.", + }; + } + }), + delete: withAccessControl .input(z.object({ tokenId: z.string() })) .mutation(async ({ ctx, input }) => { From fb2be209272c4008f577dd971a47f5d726eed1c5 Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Tue, 6 Aug 2024 23:19:40 +0545 Subject: [PATCH 03/13] fix: conflict --- src/trpc/routers/access-token/schema.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/trpc/routers/access-token/schema.ts diff --git a/src/trpc/routers/access-token/schema.ts b/src/trpc/routers/access-token/schema.ts new file mode 100644 index 000000000..327c17b97 --- /dev/null +++ b/src/trpc/routers/access-token/schema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +const apiKeyMutation = z.object({ + keyId: z.string(), +}); + +export type TApiKeyMutationSchema = z.infer; From f81dc2839544d59d8439d95fbd989ccc56ed30fb Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Tue, 6 Aug 2024 23:32:36 +0545 Subject: [PATCH 04/13] fix: conflict in table --- .../settings/developer/components/table.tsx | 233 +++++++++++++++++- 1 file changed, 226 insertions(+), 7 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx index efeddc9f4..5696eca5e 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx @@ -1,6 +1,8 @@ "use client"; import { dayjsExt } from "@/common/dayjs"; +import Loading from "@/components/common/loading"; +import Modal from "@/components/common/modal"; import Tldr from "@/components/common/tldr"; import { Allow } from "@/components/rbac/allow"; import { @@ -34,14 +36,29 @@ import { api } from "@/trpc/react"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { Fragment, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; +import { useCopyToClipboard } from "usehooks-ts"; interface DeleteDialogProps { tokenId: string; open: boolean; setOpen: (val: boolean) => void; } +interface RotateKeyProps extends DeleteDialogProps { + setShowModal: (val: boolean) => void; + setApiKey: (key: string) => void; + setLoading: (val: boolean) => void; +} +type KeyModalProps = Omit & { + apiKey: string; +}; + +interface ApiKey { + keyId: string; + createdAt: Date; + lastUsed: Date | null; +} function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { const router = useRouter(); @@ -81,6 +98,7 @@ function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { ); } +<<<<<<< HEAD:src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx type AccessTokens = RouterOutputs["accessToken"]["listAll"]["accessTokens"]; const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { @@ -92,10 +110,130 @@ const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { { + const key = `${keyId}:${token}` as string; + toast.success("Successfully rotated the api key."); + copy(key); + setApiKey(key); + setShowModal(true); + router.refresh(); + }, + + onError: (error) => { + console.error(error); + toast.error("An error occurred while creating the API key."); + }, + }); + return ( + + + + Are you sure? + + Are you sure you want to rotate this key? please make sure to + replace existing API keys if you have used it anywhere. + + + + Cancel + { + setLoading(true); + await rotateApiKey({ keyId }); + }} + > + Continue + + + + + ); +} + +function KeyModal({ apiKey, open, setOpen }: KeyModalProps) { + const [_copied, copy] = useCopyToClipboard(); + + return ( + - + } + dialogProps={{ + defaultOpen: open, + open, + onOpenChange: (val) => { + setOpen(val); + }, + }} + > + + Your API Key + { + copy(apiKey as string); + toast.success("API key copied to clipboard!"); + }} + > + {apiKey} + + + Click the API key above to copy + + + + ); +} + +const ApiKeysTable = ({ keys }: { keys: ApiKey[] }) => { + const [loading, setLoading] = useState(false); + const [openDeleteAlert, setOpenDeleteAlert] = useState(false); + const [openRotateAlert, setOpenRotateAlert] = useState(false); + const [copyApiKeyModal, setCopyApiKeyModal] = useState(false); + + const [apiKey, setApiKey] = useState(""); + const [selectedKey, setSelected] = useState(""); + + const handleDeleteKey = (key: string) => { + setSelected(key); + setOpenDeleteAlert(true); + }; + const handleRotateKey = (key: string) => { + setSelected(key); + setOpenRotateAlert(true); + }; + + return ( + <> + +
+ +
+<<<<<<< HEAD:src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx @@ -151,11 +289,92 @@ const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { /> +======= +
+ + + Key + Created + Last used + +>>>>>>> 4d4e4097 (feat: apikey table updated for key-rotation):src/app/(authenticated)/(dashboard)/[publicId]/settings/api/components/table.tsx - ))} - -
-
+ + + {keys.map((key) => ( + + + + {`${key.keyId.slice(0, 3)}...${key.keyId.slice(-3)}:****`} + + + + {dayjsExt().to(key.createdAt)} + + + {key.lastUsed ? dayjsExt().to(key.lastUsed) : "Never"} + + + +
+ + + + + + Options + + + + {(allow) => ( + handleRotateKey(key.keyId)} + > + Rotate key + + )} + + + + {(allow) => ( + handleDeleteKey(key.keyId)} + > + Delete key + + )} + + + +
+
+
+ ))} +
+ + setOpenDeleteAlert(val)} + keyId={selectedKey} + /> + setOpenRotateAlert(val)} + setShowModal={setCopyApiKeyModal} + setApiKey={setApiKey} + keyId={selectedKey} + setLoading={setLoading} + /> + + + {loading && } + ); }; From aa8d581c66651323344808f8b05b9531bfc80786 Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Wed, 7 Aug 2024 08:18:09 +0545 Subject: [PATCH 05/13] fix: conflict --- .../[publicId]/settings/api/page.tsx | 59 +++++++++++++++++++ src/trpc/routers/access-token/router.ts | 35 +++++++---- 2 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx new file mode 100644 index 000000000..6b1bd0bff --- /dev/null +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx @@ -0,0 +1,59 @@ +import EmptyState from "@/components/common/empty-state"; +import { UnAuthorizedState } from "@/components/ui/un-authorized-state"; +import { serverAccessControl } from "@/lib/rbac/access-control"; +import { api } from "@/trpc/server"; +import { RiTerminalBoxFill } from "@remixicon/react"; +import type { Metadata } from "next"; +import { Fragment } from "react"; +import CreateApiKey from "./components/create-key"; +import ApiKeysTable from "./components/table"; + +export const metadata: Metadata = { + title: "API Keys", +}; +const ApiSettingsPage = async () => { + const { allow } = await serverAccessControl(); + + const data = await allow(api.apiKey.listAll.query(), ["api-keys", "read"]); + + if (!data) { + return ; + } + + return ( + + {data.apiKeys.length === 0 ? ( + } + title="API Keys" + subtitle="Create and manage API keys" + > + + + ) : ( +
+
+
+

API Keys

+

+ Create and manage API keys +

+
+ +
+ +
+
+ + +
+ )} +
+ ); +}; + +export default ApiSettingsPage; + +// Great work with that push modal inside +// You can call it from anywhere ?? "":"" diff --git a/src/trpc/routers/access-token/router.ts b/src/trpc/routers/access-token/router.ts index 4c37b48bb..adf35e60a 100644 --- a/src/trpc/routers/access-token/router.ts +++ b/src/trpc/routers/access-token/router.ts @@ -103,13 +103,12 @@ export const accessTokenRouter = createTRPCRouter({ requestIp, userAgent, } = ctx; - const { keyId } = input; const { user } = session; - const rotatedKey = await db.$transaction(async (tx) => { + const key = await db.$transaction(async (tx) => { const existingKey = await tx.apiKey.findUnique({ where: { - keyId, + keyId: input.keyId, active: true, }, }); @@ -121,11 +120,21 @@ export const accessTokenRouter = createTRPCRouter({ }); } - await tx.apiKey.delete({ - where: { keyId: existingKey.keyId }, - }); + const token = createApiToken(); + const keyId = generatePublicId(); + const hashedToken = createSecureHash(token); - const key = await createApiKeyHandler({ tx, companyId, memberId }); + const newKey = await tx.apiKey.update({ + where: { + id: existingKey.id, + membershipId: memberId, + }, + data: { + keyId, + hashedToken, + active: true, + }, + }); await Audit.create( { @@ -136,18 +145,18 @@ export const accessTokenRouter = createTRPCRouter({ userAgent, requestIp, }, - target: [{ type: "apiKey", id: key.id }], - summary: `${user.name} rotated the apiKey ${key.name}`, + target: [{ type: "apiKey", id: newKey.id }], + summary: `${user.name} rotated the apiKey ${newKey.name}`, }, tx, ); - return key; + return newKey; }); return { - token: rotatedKey.hashedToken, - keyId: rotatedKey.keyId, - createdAt: rotatedKey.createdAt, + token: key.hashedToken, + keyId: key.keyId, + createdAt: key.createdAt, }; } catch (error) { console.error("Error rotating the api key :", error); From e637a2031115c810bb828b448a15fa678c666e93 Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Wed, 7 Aug 2024 08:19:02 +0545 Subject: [PATCH 06/13] fix: conflict --- .../[publicId]/settings/api/page.tsx | 3 - .../settings/developer/components/table.tsx | 645 ++++++++++++------ 2 files changed, 419 insertions(+), 229 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx index 6b1bd0bff..068bdde75 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx @@ -54,6 +54,3 @@ const ApiSettingsPage = async () => { }; export default ApiSettingsPage; - -// Great work with that push modal inside -// You can call it from anywhere ?? "":"" diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx index 5696eca5e..3ca556846 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx @@ -1,8 +1,6 @@ "use client"; import { dayjsExt } from "@/common/dayjs"; -import Loading from "@/components/common/loading"; -import Modal from "@/components/common/modal"; import Tldr from "@/components/common/tldr"; import { Allow } from "@/components/rbac/allow"; import { @@ -36,29 +34,14 @@ import { api } from "@/trpc/react"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; import { useRouter } from "next/navigation"; -import { Fragment, useEffect, useRef, useState } from "react"; +import { useState } from "react"; import { toast } from "sonner"; -import { useCopyToClipboard } from "usehooks-ts"; interface DeleteDialogProps { tokenId: string; open: boolean; setOpen: (val: boolean) => void; } -interface RotateKeyProps extends DeleteDialogProps { - setShowModal: (val: boolean) => void; - setApiKey: (key: string) => void; - setLoading: (val: boolean) => void; -} -type KeyModalProps = Omit & { - apiKey: string; -}; - -interface ApiKey { - keyId: string; - createdAt: Date; - lastUsed: Date | null; -} function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { const router = useRouter(); @@ -98,7 +81,6 @@ function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { ); } -<<<<<<< HEAD:src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx type AccessTokens = RouterOutputs["accessToken"]["listAll"]["accessTokens"]; const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { @@ -110,130 +92,10 @@ const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { { - console.error(error); - toast.error("An error occurred while creating the API key."); - }, - }); - return ( - - - - Are you sure? - - Are you sure you want to rotate this key? please make sure to - replace existing API keys if you have used it anywhere. - - - - Cancel - { - setLoading(true); - await rotateApiKey({ keyId }); - }} - > - Continue - - - - - ); -} - -function KeyModal({ apiKey, open, setOpen }: KeyModalProps) { - const [_copied, copy] = useCopyToClipboard(); - - return ( - - } - dialogProps={{ - defaultOpen: open, - open, - onOpenChange: (val) => { - setOpen(val); - }, - }} - > - - Your API Key - { - copy(apiKey as string); - toast.success("API key copied to clipboard!"); - }} - > - {apiKey} - - - Click the API key above to copy - - - - ); -} - -const ApiKeysTable = ({ keys }: { keys: ApiKey[] }) => { - const [loading, setLoading] = useState(false); - const [openDeleteAlert, setOpenDeleteAlert] = useState(false); - const [openRotateAlert, setOpenRotateAlert] = useState(false); - const [copyApiKeyModal, setCopyApiKeyModal] = useState(false); - - const [apiKey, setApiKey] = useState(""); - const [selectedKey, setSelected] = useState(""); - - const handleDeleteKey = (key: string) => { - setSelected(key); - setOpenDeleteAlert(true); - }; - const handleRotateKey = (key: string) => { - setSelected(key); - setOpenRotateAlert(true); - }; - - return ( - <> - -
- -
+ /> + -<<<<<<< HEAD:src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx @@ -289,93 +151,424 @@ const ApiKeysTable = ({ keys }: { keys: ApiKey[] }) => { /> -======= -
- - - Key - Created - Last used - ->>>>>>> 4d4e4097 (feat: apikey table updated for key-rotation):src/app/(authenticated)/(dashboard)/[publicId]/settings/api/components/table.tsx - - - {keys.map((key) => ( - - - - {`${key.keyId.slice(0, 3)}...${key.keyId.slice(-3)}:****`} - - - - {dayjsExt().to(key.createdAt)} - - - {key.lastUsed ? dayjsExt().to(key.lastUsed) : "Never"} - - - -
- - - - - - Options - - - - {(allow) => ( - handleRotateKey(key.keyId)} - > - Rotate key - - )} - - - - {(allow) => ( - handleDeleteKey(key.keyId)} - > - Delete key - - )} - - - -
-
-
- ))} -
-
- setOpenDeleteAlert(val)} - keyId={selectedKey} - /> - setOpenRotateAlert(val)} - setShowModal={setCopyApiKeyModal} - setApiKey={setApiKey} - keyId={selectedKey} - setLoading={setLoading} - /> - -
- {loading && } - + ))} + + + ); }; export default AccessTokenTable; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// "use client"; + +// import { dayjsExt } from "@/common/dayjs"; +// import Loading from "@/components/common/loading"; +// import Modal from "@/components/common/modal"; +// import Tldr from "@/components/common/tldr"; +// import { Allow } from "@/components/rbac/allow"; +// import { +// AlertDialog, +// AlertDialogAction, +// AlertDialogCancel, +// AlertDialogContent, +// AlertDialogDescription, +// AlertDialogFooter, +// AlertDialogHeader, +// AlertDialogTitle, +// } from "@/components/ui/alert-dialog"; +// import { Card } from "@/components/ui/card"; +// import { +// DropdownMenu, +// DropdownMenuContent, +// DropdownMenuItem, +// DropdownMenuLabel, +// DropdownMenuSeparator, +// DropdownMenuTrigger, +// } from "@/components/ui/dropdown-menu"; +// import { +// Table, +// TableBody, +// TableCell, +// TableHead, +// TableHeader, +// TableRow, +// } from "@/components/ui/table"; +// import { api } from "@/trpc/react"; +// import type { RouterOutputs } from "@/trpc/shared"; +// import { RiMore2Fill } from "@remixicon/react"; +// import { useRouter } from "next/navigation"; +// import { Fragment, useEffect, useRef, useState } from "react"; +// import { toast } from "sonner"; +// import { useCopyToClipboard } from "usehooks-ts"; + +// interface DeleteDialogProps { +// tokenId: string; +// open: boolean; +// setOpen: (val: boolean) => void; +// } +// interface RotateKeyProps extends DeleteDialogProps { +// setShowModal: (val: boolean) => void; +// setApiKey: (key: string) => void; +// setLoading: (val: boolean) => void; +// } +// type KeyModalProps = Omit & { +// apiKey: string; +// }; + +// interface ApiKey { +// keyId: string; +// createdAt: Date; +// lastUsed: Date | null; +// } + +// function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { +// const router = useRouter(); + +// const deleteMutation = api.accessToken.delete.useMutation({ +// onSuccess: ({ message }) => { +// toast.success(message); +// router.refresh(); +// }, + +// onError: (error) => { +// console.error(error); +// toast.error("An error occurred while creating the access token."); +// }, +// }); +// return ( +// +// +// +// Are you sure? +// +// Are you sure you want to delete this access token? This action +// cannot be undone and you will loose the access if this access token +// is currently being used. +// +// +// +// Cancel +// deleteMutation.mutateAsync({ tokenId })} +// > +// Continue +// +// +// +// +// ); +// } + +// <<<<<<< HEAD:src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx +// type AccessTokens = RouterOutputs["accessToken"]["listAll"]["accessTokens"]; + +// const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { +// const [open, setOpen] = useState(false); + +// return ( +// +//
+// { +// console.error(error); +// toast.error("An error occurred while creating the API key."); +// }, +// }); +// return ( +// +// +// +// Are you sure? +// +// Are you sure you want to rotate this key? please make sure to +// replace existing API keys if you have used it anywhere. +// +// +// +// Cancel +// { +// setLoading(true); +// await rotateApiKey({ keyId }); +// }} +// > +// Continue +// +// +// +// +// ); +// } + +// function KeyModal({ apiKey, open, setOpen }: KeyModalProps) { +// const [_copied, copy] = useCopyToClipboard(); + +// return ( +// +// } +// dialogProps={{ +// defaultOpen: open, +// open, +// onOpenChange: (val) => { +// setOpen(val); +// }, +// }} +// > +// +// Your API Key +// { +// copy(apiKey as string); +// toast.success("API key copied to clipboard!"); +// }} +// > +// {apiKey} +// +// +// Click the API key above to copy +// +// +// +// ); +// } + +// const ApiKeysTable = ({ keys }: { keys: ApiKey[] }) => { +// const [loading, setLoading] = useState(false); +// const [openDeleteAlert, setOpenDeleteAlert] = useState(false); +// const [openRotateAlert, setOpenRotateAlert] = useState(false); +// const [copyApiKeyModal, setCopyApiKeyModal] = useState(false); + +// const [apiKey, setApiKey] = useState(""); +// const [selectedKey, setSelected] = useState(""); + +// const handleDeleteKey = (key: string) => { +// setSelected(key); +// setOpenDeleteAlert(true); +// }; +// const handleRotateKey = (key: string) => { +// setSelected(key); +// setOpenRotateAlert(true); +// }; + +// return ( +// <> +// +//
+// +//
+ +// <<<<<<< HEAD:src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx +// +// +// +// Access token +// Created +// Last used +// +// +// +// +// {tokens.map((token: AccessTokens[number]) => ( +// +// +// {`${token.clientId}:***`} +// +// +// {dayjsExt().to(token.createdAt)} +// +// +// {token.lastUsed ? dayjsExt().to(token.lastUsed) : "Never"} +// + +// +//
+// +// +// +// +// +// Options +// + +// {}}> +// Rotate key +// + +// +// {(allow) => ( +// setOpen(true)} +// > +// Delete key +// +// )} +// +// +// +// setOpen(val)} +// tokenId={token.id} +// /> +//
+//
+// ======= +//
+// +// +// Key +// Created +// Last used +// +// >>>>>>> 4d4e4097 (feat: apikey table updated for key-rotation):src/app/(authenticated)/(dashboard)/[publicId]/settings/api/components/table.tsx +// +// +// +// {keys.map((key) => ( +// +// +// +// {`${key.keyId.slice(0, 3)}...${key.keyId.slice(-3)}:****`} +// +// +// +// {dayjsExt().to(key.createdAt)} +// +// +// {key.lastUsed ? dayjsExt().to(key.lastUsed) : "Never"} +// + +// +//
+// +// +// +// +// +// Options +// + +// +// {(allow) => ( +// handleRotateKey(key.keyId)} +// > +// Rotate key +// +// )} +// + +// +// {(allow) => ( +// handleDeleteKey(key.keyId)} +// > +// Delete key +// +// )} +// +// +// +//
+//
+//
+// ))} +//
+//
+// setOpenDeleteAlert(val)} +// keyId={selectedKey} +// /> +// setOpenRotateAlert(val)} +// setShowModal={setCopyApiKeyModal} +// setApiKey={setApiKey} +// keyId={selectedKey} +// setLoading={setLoading} +// /> +// +//
+// {loading && } +// +// ); +// }; + +// export default AccessTokenTable; From 4a2f2a5c37a8fd822a695245fe2b550964e2cf3d Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Sat, 10 Aug 2024 00:48:03 +0545 Subject: [PATCH 07/13] fix: use access token scheme for rotating-api-keys --- src/trpc/routers/access-token/router.ts | 50 +++++++++++++------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/trpc/routers/access-token/router.ts b/src/trpc/routers/access-token/router.ts index adf35e60a..1097fd6ee 100644 --- a/src/trpc/routers/access-token/router.ts +++ b/src/trpc/routers/access-token/router.ts @@ -4,6 +4,7 @@ import { Audit } from "@/server/audit"; import { createTRPCRouter, withAccessControl } from "@/trpc/api/trpc"; import { TRPCError } from "@trpc/server"; import z from "zod"; +import { TagType } from "./../../../lib/tags"; export const accessTokenRouter = createTRPCRouter({ listAll: withAccessControl @@ -42,6 +43,7 @@ export const accessTokenRouter = createTRPCRouter({ create: withAccessControl .input(z.object({ typeEnum: z.nativeEnum(AccessTokenType) })) + .meta({ policies: { developer: { allow: ["create"] } } }) .mutation(async ({ ctx, input }) => { const { db, @@ -92,74 +94,76 @@ export const accessTokenRouter = createTRPCRouter({ }), rotate: withAccessControl - .input(z.object({ keyId: z.string() })) - .meta({ policies: { "api-keys": { allow: ["update"] } } }) + .input(z.object({ tokenId: z.string() })) + .meta({ policies: { developer: { allow: ["update"] } } }) .mutation(async ({ ctx, input }) => { try { const { db, - membership: { memberId, companyId }, + membership: { userId, companyId }, session, requestIp, userAgent, } = ctx; const { user } = session; + const { tokenId } = input; const key = await db.$transaction(async (tx) => { - const existingKey = await tx.apiKey.findUnique({ + const existingToken = await tx.accessToken.findUnique({ where: { - keyId: input.keyId, + id: tokenId, + userId, active: true, }, }); - if (!existingKey) { + if (!existingToken) { throw new TRPCError({ code: "NOT_FOUND", - message: "Api key not found", + message: "Access token not found", }); } - const token = createApiToken(); - const keyId = generatePublicId(); - const hashedToken = createSecureHash(token); + const { clientId, clientSecret } = initializeAccessToken({ + prefix: existingToken.typeEnum, + }); + const hashedClientSecret = await createSecureHash(clientSecret); - const newKey = await tx.apiKey.update({ + const rotated = await tx.accessToken.update({ where: { - id: existingKey.id, - membershipId: memberId, + id: existingToken.id, }, data: { - keyId, - hashedToken, - active: true, + clientId, + clientSecret: hashedClientSecret, }, }); await Audit.create( { - action: "apikey.rotated", + action: "accessToken.rotated", companyId, actor: { type: "user", id: user.id }, context: { userAgent, requestIp, }, - target: [{ type: "apiKey", id: newKey.id }], - summary: `${user.name} rotated the apiKey ${newKey.name}`, + target: [{ type: "accessToken", id: rotated.id }], + summary: `${user.name} rotated the developer accessToken.`, }, tx, ); - return newKey; + return rotated; }); return { - token: key.hashedToken, - keyId: key.keyId, + success: true, + token: `${key.clientId}:${key.clientSecret}`, + clientId: key.clientId, createdAt: key.createdAt, }; } catch (error) { - console.error("Error rotating the api key :", error); + console.error("Error rotating the api access token :", error); if (error instanceof TRPCError) { return { success: false, From b6b4d87a17af6c556de812e3f4d64a705e665dbb Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Sat, 10 Aug 2024 00:49:25 +0545 Subject: [PATCH 08/13] fix: changed variable names and cleanups --- .../settings/developer/components/table.tsx | 726 ++++++------------ 1 file changed, 240 insertions(+), 486 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx index 3ca556846..14b656386 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx @@ -1,6 +1,8 @@ "use client"; import { dayjsExt } from "@/common/dayjs"; +import Loading from "@/components/common/loading"; +import Modal from "@/components/common/modal"; import Tldr from "@/components/common/tldr"; import { Allow } from "@/components/rbac/allow"; import { @@ -34,22 +36,29 @@ import { api } from "@/trpc/react"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { Fragment, useState } from "react"; import { toast } from "sonner"; +import { useCopyToClipboard } from "usehooks-ts"; interface DeleteDialogProps { - tokenId: string; - open: boolean; - setOpen: (val: boolean) => void; + accessToken: string; + openAlert: boolean; + setOpenAlert: (val: boolean) => void; } -function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { +function DeleteKeyAlert({ + accessToken, + openAlert, + setOpenAlert, +}: DeleteDialogProps) { const router = useRouter(); const deleteMutation = api.accessToken.delete.useMutation({ - onSuccess: ({ message }) => { - toast.success(message); - router.refresh(); + onSuccess: ({ success, message }) => { + if (success) { + toast.success(message); + router.refresh(); + } }, onError: (error) => { @@ -58,7 +67,7 @@ function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { }, }); return ( - + Are you sure? @@ -71,7 +80,7 @@ function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { Cancel deleteMutation.mutateAsync({ tokenId })} + onClick={() => deleteMutation.mutateAsync({ tokenId: accessToken })} > Continue @@ -81,494 +90,239 @@ function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { ); } -type AccessTokens = RouterOutputs["accessToken"]["listAll"]["accessTokens"]; +type TokenViewerModalProps = Omit< + DeleteDialogProps, + "keyId" | "openAlert" | "setOpenAlert" +> & { + accessToken: string; + openViewer: boolean; + setOpenViewer: (val: boolean) => void; +}; -const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { - const [open, setOpen] = useState(false); +function TokenViewerModal({ + accessToken, + openViewer, + setOpenViewer, +}: TokenViewerModalProps) { + const [_copied, copy] = useCopyToClipboard(); return ( - -
+ -
- - - - - Access token - Created - Last used - - - - - {tokens.map((token: AccessTokens[number]) => ( - - - {`${token.clientId}:***`} - - - {dayjsExt().to(token.createdAt)} - - - {token.lastUsed ? dayjsExt().to(token.lastUsed) : "Never"} - - - -
- - - - - - Options - - - {}}> - Rotate key - - - - {(allow) => ( - setOpen(true)} - > - Delete key - - )} - - - - setOpen(val)} - tokenId={token.id} - /> -
-
-
- ))} -
-
-
+ } + dialogProps={{ + defaultOpen: openViewer, + open: openViewer, + onOpenChange: (val) => { + setOpenViewer(val); + }, + }} + > + + Your API Key + { + copy(accessToken as string); + toast.success("Access token copied to clipboard!"); + }} + > + {accessToken} + + + Click the access token above to copy + + + ); -}; - -export default AccessTokenTable; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// "use client"; - -// import { dayjsExt } from "@/common/dayjs"; -// import Loading from "@/components/common/loading"; -// import Modal from "@/components/common/modal"; -// import Tldr from "@/components/common/tldr"; -// import { Allow } from "@/components/rbac/allow"; -// import { -// AlertDialog, -// AlertDialogAction, -// AlertDialogCancel, -// AlertDialogContent, -// AlertDialogDescription, -// AlertDialogFooter, -// AlertDialogHeader, -// AlertDialogTitle, -// } from "@/components/ui/alert-dialog"; -// import { Card } from "@/components/ui/card"; -// import { -// DropdownMenu, -// DropdownMenuContent, -// DropdownMenuItem, -// DropdownMenuLabel, -// DropdownMenuSeparator, -// DropdownMenuTrigger, -// } from "@/components/ui/dropdown-menu"; -// import { -// Table, -// TableBody, -// TableCell, -// TableHead, -// TableHeader, -// TableRow, -// } from "@/components/ui/table"; -// import { api } from "@/trpc/react"; -// import type { RouterOutputs } from "@/trpc/shared"; -// import { RiMore2Fill } from "@remixicon/react"; -// import { useRouter } from "next/navigation"; -// import { Fragment, useEffect, useRef, useState } from "react"; -// import { toast } from "sonner"; -// import { useCopyToClipboard } from "usehooks-ts"; - -// interface DeleteDialogProps { -// tokenId: string; -// open: boolean; -// setOpen: (val: boolean) => void; -// } -// interface RotateKeyProps extends DeleteDialogProps { -// setShowModal: (val: boolean) => void; -// setApiKey: (key: string) => void; -// setLoading: (val: boolean) => void; -// } -// type KeyModalProps = Omit & { -// apiKey: string; -// }; - -// interface ApiKey { -// keyId: string; -// createdAt: Date; -// lastUsed: Date | null; -// } - -// function DeleteKey({ tokenId, open, setOpen }: DeleteDialogProps) { -// const router = useRouter(); - -// const deleteMutation = api.accessToken.delete.useMutation({ -// onSuccess: ({ message }) => { -// toast.success(message); -// router.refresh(); -// }, - -// onError: (error) => { -// console.error(error); -// toast.error("An error occurred while creating the access token."); -// }, -// }); -// return ( -// -// -// -// Are you sure? -// -// Are you sure you want to delete this access token? This action -// cannot be undone and you will loose the access if this access token -// is currently being used. -// -// -// -// Cancel -// deleteMutation.mutateAsync({ tokenId })} -// > -// Continue -// -// -// -// -// ); -// } - -// <<<<<<< HEAD:src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx -// type AccessTokens = RouterOutputs["accessToken"]["listAll"]["accessTokens"]; - -// const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { -// const [open, setOpen] = useState(false); - -// return ( -// -//
-// { -// console.error(error); -// toast.error("An error occurred while creating the API key."); -// }, -// }); -// return ( -// -// -// -// Are you sure? -// -// Are you sure you want to rotate this key? please make sure to -// replace existing API keys if you have used it anywhere. -// -// -// -// Cancel -// { -// setLoading(true); -// await rotateApiKey({ keyId }); -// }} -// > -// Continue -// -// -// -// -// ); -// } - -// function KeyModal({ apiKey, open, setOpen }: KeyModalProps) { -// const [_copied, copy] = useCopyToClipboard(); - -// return ( -// -// } -// dialogProps={{ -// defaultOpen: open, -// open, -// onOpenChange: (val) => { -// setOpen(val); -// }, -// }} -// > -// -// Your API Key -// { -// copy(apiKey as string); -// toast.success("API key copied to clipboard!"); -// }} -// > -// {apiKey} -// -// -// Click the API key above to copy -// -// -// -// ); -// } - -// const ApiKeysTable = ({ keys }: { keys: ApiKey[] }) => { -// const [loading, setLoading] = useState(false); -// const [openDeleteAlert, setOpenDeleteAlert] = useState(false); -// const [openRotateAlert, setOpenRotateAlert] = useState(false); -// const [copyApiKeyModal, setCopyApiKeyModal] = useState(false); - -// const [apiKey, setApiKey] = useState(""); -// const [selectedKey, setSelected] = useState(""); - -// const handleDeleteKey = (key: string) => { -// setSelected(key); -// setOpenDeleteAlert(true); -// }; -// const handleRotateKey = (key: string) => { -// setSelected(key); -// setOpenRotateAlert(true); -// }; - -// return ( -// <> -// -//
-// -//
+} -// <<<<<<< HEAD:src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx -// -// -// -// Access token -// Created -// Last used -// -// -// -// -// {tokens.map((token: AccessTokens[number]) => ( -// -// -// {`${token.clientId}:***`} -// -// -// {dayjsExt().to(token.createdAt)} -// -// -// {token.lastUsed ? dayjsExt().to(token.lastUsed) : "Never"} -// +interface RotateKeyProps extends DeleteDialogProps { + setOpenViewer: (val: boolean) => void; + setAccessToken: (key: string) => void; + setLoading: (val: boolean) => void; +} -// -//
-// -// -// -// -// -// Options -// +function RotateKeyAlert({ + accessToken, + openAlert, + setOpenAlert, + setOpenViewer, + setAccessToken, + setLoading, +}: RotateKeyProps) { + const router = useRouter(); + const [_copied, copy] = useCopyToClipboard(); + + const { mutateAsync: rotateApiKey } = api.accessToken.rotate.useMutation({ + onSuccess: ({ success, token }) => { + if (success && token) { + toast.promise(copy(token), { + loading: "Rotating access token", + success: "Successfully rotated the api key.", + error: "Error rotating the access token", + }); + setAccessToken(token); + setOpenViewer(true); + router.refresh(); + } + }, -// {}}> -// Rotate key -// + onError: (error) => { + console.error(error); + toast.error("An error occurred while creating the API key."); + }, -// -// {(allow) => ( -// setOpen(true)} -// > -// Delete key -// -// )} -// -// -// -// setOpen(val)} -// tokenId={token.id} -// /> -//
-//
-// ======= -//
-// -// -// Key -// Created -// Last used -// -// >>>>>>> 4d4e4097 (feat: apikey table updated for key-rotation):src/app/(authenticated)/(dashboard)/[publicId]/settings/api/components/table.tsx -// -// -// -// {keys.map((key) => ( -// -// -// -// {`${key.keyId.slice(0, 3)}...${key.keyId.slice(-3)}:****`} -// -// -// -// {dayjsExt().to(key.createdAt)} -// -// -// {key.lastUsed ? dayjsExt().to(key.lastUsed) : "Never"} -// + onSettled: () => { + setLoading(false); + }, + }); + return ( + + + + Are you sure? + + Are you sure you want to rotate this key? please make sure to + replace existing API keys if you have used it anywhere. + + + + Cancel + { + setLoading(true); + await rotateApiKey({ tokenId: accessToken }); + }} + > + Continue + + + + + ); +} -// -//
-// -// -// -// -// -// Options -// +type AccessTokens = RouterOutputs["accessToken"]["listAll"]["accessTokens"]; -// -// {(allow) => ( -// handleRotateKey(key.keyId)} -// > -// Rotate key -// -// )} -// +const AccessTokenTable = ({ tokens }: { tokens: AccessTokens }) => { + const [loading, setLoading] = useState(false); + const [selectedToken, setSelectedToken] = useState(""); + const [accessToken, setAccessToken] = useState(""); + const [openRotateAlert, setOpenRotateAlert] = useState(false); + const [openDeleteAlert, setOpenDeleteAlert] = useState(false); + const [showTokenViewerModal, setShowTokenViewerModal] = + useState(false); + + const handleDeleteKey = (key: string) => { + setSelectedToken(key); + setOpenDeleteAlert(true); + }; + const handleRotateKey = (key: string) => { + setSelectedToken(key); + setOpenRotateAlert(true); + }; -// -// {(allow) => ( -// handleDeleteKey(key.keyId)} -// > -// Delete key -// -// )} -// -// -// -//
-//
-//
-// ))} -//
-//
-// setOpenDeleteAlert(val)} -// keyId={selectedKey} -// /> -// setOpenRotateAlert(val)} -// setShowModal={setCopyApiKeyModal} -// setApiKey={setApiKey} -// keyId={selectedKey} -// setLoading={setLoading} -// /> -// -//
-// {loading && } -// -// ); -// }; + return ( + <> + +
+ +
+ + + + + Access token + Created + Last used + + + + + {tokens.map((token: AccessTokens[number]) => ( + + + {`${token.clientId}:***`} + + + {dayjsExt().to(token.createdAt)} + + + {token.lastUsed ? dayjsExt().to(token.lastUsed) : "Never"} + + + +
+ + + + + + Options + + + + {(allow) => ( + handleRotateKey(token.id)} + > + Rotate key + + )} + + + + {(allow) => ( + handleDeleteKey(token.id)} + > + Delete key + + )} + + + +
+
+
+ ))} +
+
+ setOpenDeleteAlert(val)} + accessToken={selectedToken} + /> + setOpenRotateAlert(val)} + setOpenViewer={setShowTokenViewerModal} + accessToken={selectedToken} + setAccessToken={setAccessToken} + setLoading={setLoading} + /> + +
+ {loading && } + + ); +}; -// export default AccessTokenTable; +export default AccessTokenTable; From dd1a7dddeedd2e46d915bf0f7f60d1ba1a1bd41e Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Sat, 10 Aug 2024 00:59:19 +0545 Subject: [PATCH 09/13] fix: type in token-viewer-modal-props --- .../[publicId]/settings/developer/components/table.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx index 14b656386..71bbafe72 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx @@ -92,9 +92,8 @@ function DeleteKeyAlert({ type TokenViewerModalProps = Omit< DeleteDialogProps, - "keyId" | "openAlert" | "setOpenAlert" + "openAlert" | "setOpenAlert" > & { - accessToken: string; openViewer: boolean; setOpenViewer: (val: boolean) => void; }; From 40af78e08f62c3fcfb90bd60a9dccead620b8bda Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Sat, 10 Aug 2024 01:15:00 +0545 Subject: [PATCH 10/13] fix: types --- .../settings/developer/components/table.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx index 71bbafe72..296c08683 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/components/table.tsx @@ -44,16 +44,18 @@ interface DeleteDialogProps { accessToken: string; openAlert: boolean; setOpenAlert: (val: boolean) => void; + setLoading: (val: boolean) => void; } function DeleteKeyAlert({ accessToken, openAlert, setOpenAlert, + setLoading, }: DeleteDialogProps) { const router = useRouter(); - const deleteMutation = api.accessToken.delete.useMutation({ + const { mutateAsync: deleteApiKey } = api.accessToken.delete.useMutation({ onSuccess: ({ success, message }) => { if (success) { toast.success(message); @@ -65,6 +67,10 @@ function DeleteKeyAlert({ console.error(error); toast.error("An error occurred while creating the access token."); }, + + onSettled: () => { + setLoading(false); + }, }); return ( @@ -80,7 +86,10 @@ function DeleteKeyAlert({ Cancel deleteMutation.mutateAsync({ tokenId: accessToken })} + onClick={async () => { + setLoading(true); + await deleteApiKey({ tokenId: accessToken }); + }} > Continue @@ -92,7 +101,7 @@ function DeleteKeyAlert({ type TokenViewerModalProps = Omit< DeleteDialogProps, - "openAlert" | "setOpenAlert" + "openAlert" | "setOpenAlert" | "setLoading" > & { openViewer: boolean; setOpenViewer: (val: boolean) => void; @@ -107,7 +116,7 @@ function TokenViewerModal({ return ( Date: Sat, 10 Aug 2024 01:32:54 +0545 Subject: [PATCH 12/13] chore: deleted --- .../[publicId]/settings/api/page.tsx | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx deleted file mode 100644 index 068bdde75..000000000 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/api/page.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import EmptyState from "@/components/common/empty-state"; -import { UnAuthorizedState } from "@/components/ui/un-authorized-state"; -import { serverAccessControl } from "@/lib/rbac/access-control"; -import { api } from "@/trpc/server"; -import { RiTerminalBoxFill } from "@remixicon/react"; -import type { Metadata } from "next"; -import { Fragment } from "react"; -import CreateApiKey from "./components/create-key"; -import ApiKeysTable from "./components/table"; - -export const metadata: Metadata = { - title: "API Keys", -}; -const ApiSettingsPage = async () => { - const { allow } = await serverAccessControl(); - - const data = await allow(api.apiKey.listAll.query(), ["api-keys", "read"]); - - if (!data) { - return ; - } - - return ( - - {data.apiKeys.length === 0 ? ( - } - title="API Keys" - subtitle="Create and manage API keys" - > - - - ) : ( -
-
-
-

API Keys

-

- Create and manage API keys -

-
- -
- -
-
- - -
- )} -
- ); -}; - -export default ApiSettingsPage; From 64b93f4897973e1aec7a997387c5d8e798419018 Mon Sep 17 00:00:00 2001 From: Raju kadel Date: Sat, 10 Aug 2024 01:49:03 +0545 Subject: [PATCH 13/13] chore: minor refactoring --- src/trpc/routers/access-token/router.ts | 1 - src/trpc/routers/access-token/schema.ts | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 src/trpc/routers/access-token/schema.ts diff --git a/src/trpc/routers/access-token/router.ts b/src/trpc/routers/access-token/router.ts index 0e0449de9..f828eb080 100644 --- a/src/trpc/routers/access-token/router.ts +++ b/src/trpc/routers/access-token/router.ts @@ -4,7 +4,6 @@ import { Audit } from "@/server/audit"; import { createTRPCRouter, withAccessControl } from "@/trpc/api/trpc"; import { TRPCError } from "@trpc/server"; import z from "zod"; -import { TagType } from "./../../../lib/tags"; export const accessTokenRouter = createTRPCRouter({ listAll: withAccessControl diff --git a/src/trpc/routers/access-token/schema.ts b/src/trpc/routers/access-token/schema.ts deleted file mode 100644 index 327c17b97..000000000 --- a/src/trpc/routers/access-token/schema.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from "zod"; - -const apiKeyMutation = z.object({ - keyId: z.string(), -}); - -export type TApiKeyMutationSchema = z.infer;