Merge branch 'main' into rbac-documents

This commit is contained in:
nafees nazik
2024-07-17 13:58:50 +05:30
11 changed files with 250 additions and 151 deletions
+4 -4
View File
@@ -36,14 +36,14 @@
"@blocknote/react": "^0.12.2",
"@hono/swagger-ui": "^0.3.0",
"@hono/zod-openapi": "^0.14.8",
"@hookform/resolvers": "^3.6.0",
"@hookform/resolvers": "^3.9.0",
"@next-auth/prisma-adapter": "^1.0.7",
"@prisma/client": "^5.13.0",
"@prisma/generator-helper": "^5.9.1",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
@@ -68,7 +68,7 @@
"@t3-oss/env-nextjs": "^0.10.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.19.2",
"@tremor/react": "^3.17.2",
"@tremor/react": "^3.17.4",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.45.2",
"@trpc/react-query": "^10.43.6",
@@ -104,7 +104,7 @@
"react": "18.3.1",
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.51.5",
"react-hook-form": "^7.52.1",
"react-number-format": "^5.3.4",
"react-pdf": "^8.0.2",
"sharp": "^0.33.3",
+51 -52
View File
@@ -27,8 +27,8 @@ importers:
specifier: ^0.14.8
version: 0.14.8([email protected])([email protected])
'@hookform/resolvers':
specifier: ^3.6.0
version: 3.6.0([email protected]1.5([email protected]))
specifier: ^3.9.0
version: 3.9.0([email protected]2.1([email protected]))
'@next-auth/prisma-adapter':
specifier: ^1.0.7
version: 1.0.7(@prisma/[email protected]([email protected]))([email protected]([email protected](@babel/[email protected])(@opentelemetry/[email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected]))([email protected]))
@@ -48,8 +48,8 @@ importers:
specifier: ^1.0.4
version: 1.0.4(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-checkbox':
specifier: ^1.0.4
version: 1.0.4(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
specifier: ^1.1.1
version: 1.1.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
@@ -123,8 +123,8 @@ importers:
specifier: ^8.19.2
version: 8.19.2([email protected]([email protected]))([email protected])
'@tremor/react':
specifier: ^3.17.2
version: 3.17.2([email protected]([email protected]))([email protected])([email protected])
specifier: ^3.17.4
version: 3.17.4([email protected]([email protected]))([email protected])([email protected])
'@trpc/client':
specifier: ^10.43.6
version: 10.45.2(@trpc/[email protected])
@@ -231,8 +231,8 @@ importers:
specifier: ^14.2.3
version: 14.2.3([email protected])
react-hook-form:
specifier: ^7.51.5
version: 7.51.5([email protected])
specifier: ^7.52.1
version: 7.52.1([email protected])
react-number-format:
specifier: ^5.3.4
version: 5.3.4([email protected]([email protected]))([email protected])
@@ -1432,8 +1432,8 @@ packages:
hono: '>=3.9.0'
zod: ^3.19.1
'@hookform/resolvers@3.6.0':
resolution: {integrity: sha512-UBcpyOX3+RR+dNnqBd0lchXpoL8p4xC21XP8H6Meb8uve5Br1GCnmg0PcBoKKqPKgGu9GHQ/oygcmPrQhetwqw==}
'@hookform/resolvers@3.9.0':
resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==}
peerDependencies:
react-hook-form: ^7.0.0
@@ -2068,13 +2068,13 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-checkbox@1.0.4':
resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==}
'@radix-ui/react-checkbox@1.1.1':
resolution: {integrity: sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
@@ -3607,8 +3607,8 @@ packages:
react: ^17.0.0 || ^18.0.0
react-dom: ^17.0.0 || ^18.0.0
'@tremor/[email protected].2':
resolution: {integrity: sha512-zQovHBTonoeJbVk23VAqDeu9yL8N9N2884WYtLr0QvEo4kfC/YUYHkwyg5R7i8ahiJcYbR5zbCG8r9Y4Oqg9qQ==}
'@tremor/[email protected].4':
resolution: {integrity: sha512-teItTLbZglXJwakW8XYtRfIXZvp1Y6HKsaD9MC8cJqXG3vf1n6D9aPz3OKyWmDkBYE1Yjo/PTmEHKQIwN+2suQ==}
peerDependencies:
react: ^18.0.0
react-dom: '>=16.6.0'
@@ -7225,11 +7225,11 @@ packages:
peerDependencies:
react: '>= 16.8 || 18.0.0'
[email protected]1.5:
resolution: {integrity: sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==}
[email protected]2.1:
resolution: {integrity: sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg==}
engines: {node: '>=12.22.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18
react: ^16.8.0 || ^17 || ^18 || ^19
[email protected]:
resolution: {integrity: sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==}
@@ -8730,10 +8730,10 @@ snapshots:
'@aws-crypto/sha1-browser': 3.0.0
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
'@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/client-sts': 3.577.0
'@aws-sdk/client-sso-oidc': 3.577.0
'@aws-sdk/client-sts': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/core': 3.576.0
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected](@aws-sdk/[email protected]))
'@aws-sdk/middleware-bucket-endpoint': 3.577.0
'@aws-sdk/middleware-expect-continue': 3.577.0
'@aws-sdk/middleware-flexible-checksums': 3.577.0
@@ -8788,13 +8788,13 @@ snapshots:
transitivePeerDependencies:
- aws-crt
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
'@aws-sdk/[email protected]':
dependencies:
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
'@aws-sdk/client-sts': 3.577.0
'@aws-sdk/client-sts': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/core': 3.576.0
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected](@aws-sdk/[email protected]))
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@@ -8831,7 +8831,6 @@ snapshots:
'@smithy/util-utf8': 3.0.0
tslib: 2.6.2
transitivePeerDependencies:
- '@aws-sdk/client-sts'
- aws-crt
'@aws-sdk/[email protected]':
@@ -8877,13 +8876,13 @@ snapshots:
transitivePeerDependencies:
- aws-crt
'@aws-sdk/[email protected]':
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
dependencies:
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
'@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/client-sso-oidc': 3.577.0
'@aws-sdk/core': 3.576.0
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected](@aws-sdk/[email protected]))
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@@ -8920,6 +8919,7 @@ snapshots:
'@smithy/util-utf8': 3.0.0
tslib: 2.6.2
transitivePeerDependencies:
- '@aws-sdk/client-sso-oidc'
- aws-crt
'@aws-sdk/[email protected]':
@@ -8951,13 +8951,13 @@ snapshots:
'@smithy/util-stream': 3.0.1
tslib: 2.6.2
'@aws-sdk/[email protected](@aws-sdk/[email protected])(@aws-sdk/[email protected])':
'@aws-sdk/[email protected](@aws-sdk/[email protected])(@aws-sdk/[email protected](@aws-sdk/[email protected]))':
dependencies:
'@aws-sdk/client-sts': 3.577.0
'@aws-sdk/client-sts': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-env': 3.577.0
'@aws-sdk/credential-provider-process': 3.577.0
'@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/[email protected](@aws-sdk/[email protected]))
'@aws-sdk/types': 3.577.0
'@smithy/credential-provider-imds': 3.0.0
'@smithy/property-provider': 3.0.0
@@ -8968,14 +8968,14 @@ snapshots:
- '@aws-sdk/client-sso-oidc'
- aws-crt
'@aws-sdk/[email protected](@aws-sdk/[email protected])(@aws-sdk/[email protected])':
'@aws-sdk/[email protected](@aws-sdk/[email protected])(@aws-sdk/[email protected](@aws-sdk/[email protected]))':
dependencies:
'@aws-sdk/credential-provider-env': 3.577.0
'@aws-sdk/credential-provider-http': 3.577.0
'@aws-sdk/credential-provider-ini': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-ini': 3.577.0(@aws-sdk/[email protected])(@aws-sdk/[email protected](@aws-sdk/[email protected]))
'@aws-sdk/credential-provider-process': 3.577.0
'@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/[email protected](@aws-sdk/[email protected]))
'@aws-sdk/types': 3.577.0
'@smithy/credential-provider-imds': 3.0.0
'@smithy/property-provider': 3.0.0
@@ -9008,9 +9008,9 @@ snapshots:
- '@aws-sdk/client-sso-oidc'
- aws-crt
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
'@aws-sdk/[email protected](@aws-sdk/[email protected](@aws-sdk/[email protected]))':
dependencies:
'@aws-sdk/client-sts': 3.577.0
'@aws-sdk/client-sts': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.0.0
'@smithy/types': 3.0.0
@@ -9137,7 +9137,7 @@ snapshots:
'@aws-sdk/[email protected](@aws-sdk/[email protected])':
dependencies:
'@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/[email protected])
'@aws-sdk/client-sso-oidc': 3.577.0
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.0.0
'@smithy/shared-ini-file-loader': 3.0.0
@@ -9988,9 +9988,9 @@ snapshots:
hono: 4.4.8
zod: 3.23.8
'@hookform/resolvers@3.6.0([email protected]1.5([email protected]))':
'@hookform/resolvers@3.9.0([email protected]2.1([email protected]))':
dependencies:
react-hook-form: 7.51.5([email protected])
react-hook-form: 7.52.1([email protected])
'@img/[email protected]':
optionalDependencies:
@@ -10731,17 +10731,16 @@ snapshots:
'@types/react': 18.3.2
'@types/react-dom': 18.3.0
'@radix-ui/react-checkbox@1.0.4(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
'@radix-ui/react-checkbox@1.1.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])':
dependencies:
'@babel/runtime': 7.24.5
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/[email protected])([email protected])
'@radix-ui/react-context': 1.0.1(@types/react@18.3.2)([email protected])
'@radix-ui/react-presence': 1.0.1(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-primitive': 1.0.3(@types/react[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-use-controllable-state': 1.0.1(@types/[email protected])([email protected])
'@radix-ui/react-use-previous': 1.0.1(@types/[email protected])([email protected])
'@radix-ui/react-use-size': 1.0.1(@types/[email protected])([email protected])
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-compose-refs': 1.1.0(@types/[email protected])([email protected])
'@radix-ui/react-context': 1.1.0(@types/[email protected])([email protected])
'@radix-ui/react-presence': 1.1.0(@types/react[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-primitive': 2.0.0(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.2)([email protected])
'@radix-ui/react-use-previous': 1.1.0(@types/[email protected])([email protected])
'@radix-ui/react-use-size': 1.1.0(@types/[email protected])([email protected])
react: 18.3.1
react-dom: 18.2.0([email protected])
optionalDependencies:
@@ -12793,7 +12792,7 @@ snapshots:
react: 18.3.1
react-dom: 18.2.0([email protected])
'@tremor/[email protected].2([email protected]([email protected]))([email protected])([email protected])':
'@tremor/[email protected].4([email protected]([email protected]))([email protected])([email protected])':
dependencies:
'@floating-ui/react': 0.19.2([email protected]([email protected]))([email protected])
'@headlessui/react': 1.7.19([email protected]([email protected]))([email protected])
@@ -17531,7 +17530,7 @@ snapshots:
prop-types: 15.8.1
react: 18.3.1
[email protected]1.5([email protected]):
[email protected]2.1([email protected]):
dependencies:
react: 18.3.1
@@ -0,0 +1,27 @@
/*
Warnings:
- You are about to drop the column `userId` on the `ApiKey` table. All the data in the column will be lost.
- Added the required column `companyId` to the `ApiKey` table without a default value. This is not possible if the table is not empty.
- Added the required column `membershipId` to the `ApiKey` table without a default value. This is not possible if the table is not empty.
*/
-- DropIndex
DROP INDEX "ApiKey_userId_active_idx";
-- DropIndex
DROP INDEX "ApiKey_userId_idx";
-- AlterTable
ALTER TABLE "ApiKey" DROP COLUMN "userId",
ADD COLUMN "companyId" TEXT NOT NULL,
ADD COLUMN "membershipId" TEXT NOT NULL;
-- CreateIndex
CREATE INDEX "ApiKey_membershipId_idx" ON "ApiKey"("membershipId");
-- CreateIndex
CREATE INDEX "ApiKey_membershipId_active_idx" ON "ApiKey"("membershipId", "active");
-- CreateIndex
CREATE INDEX "ApiKey_companyId_idx" ON "ApiKey"("companyId");
+11 -6
View File
@@ -60,7 +60,6 @@ model User {
companies Member[]
passkeys Passkey[]
verificationToken VerificationToken[]
apiKeys ApiKey[]
lastSignedIn DateTime @default(now())
identityProvider String?
@@ -156,7 +155,8 @@ model Company {
dataRooms DataRoom[]
eSignAudits EsignAudit[]
billingCustomers BillingCustomer[]
Role CustomRole[]
customRoles CustomRole[]
apiKeys ApiKey[]
@@unique([publicId])
}
@@ -198,6 +198,7 @@ model Member {
updates Update[]
dataRooms DataRoomRecipient[]
UpdateRecipient UpdateRecipient[]
ApiKey ApiKey[]
@@unique([companyId, userId])
@@index([companyId])
@@ -1099,10 +1100,14 @@ model ApiKey {
updatedAt DateTime @updatedAt
lastUsed DateTime?
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
membershipId String
member Member @relation(fields: [membershipId], references: [id], onDelete: Cascade)
@@index([userId])
company Company @relation(fields: [companyId], references: [id])
companyId String
@@index([membershipId])
@@index([keyId, active])
@@index([userId, active])
@@index([membershipId, active])
@@index([companyId])
}
@@ -2,13 +2,18 @@
import { dayjsExt } from "@/common/dayjs";
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 { api } from "@/trpc/react";
import { RiMore2Fill } from "@remixicon/react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { ConfirmDialog } from "@/components/common/confirmDialog";
import {
DropdownMenu,
DropdownMenuContent,
@@ -25,6 +30,55 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { api } from "@/trpc/react";
import { RiMore2Fill } from "@remixicon/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { toast } from "sonner";
interface DeleteDialogProps {
keyId: string;
open: boolean;
setOpen: (val: boolean) => void;
}
function DeleteKey({ keyId, open, setOpen }: DeleteDialogProps) {
const router = useRouter();
const deleteMutation = api.apiKey.delete.useMutation({
onSuccess: ({ message }) => {
toast.success(message);
router.refresh();
},
onError: (error) => {
console.error(error);
toast.error("An error occurred while creating the API key.");
},
});
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this key? This action cannot be
undone and you will loose the access if this API key is currently
being used.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => deleteMutation.mutateAsync({ keyId })}
>
Continue
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
interface ApiKey {
keyId: string;
@@ -33,27 +87,7 @@ interface ApiKey {
}
const ApiKeysTable = ({ keys }: { keys: ApiKey[] }) => {
const router = useRouter();
const deleteMutation = api.apiKey.delete.useMutation({
onSuccess: ({ message }) => {
toast.success(message);
},
onError: (error) => {
console.error(error);
toast.error("An error occurred while creating the API key.");
},
onSettled: () => {
router.refresh();
},
});
const dialogTitle = "Are you sure?";
const dialogBody =
"Are you sure you want to delete this key? This action cannot be undone and you will loose the access if this API key is currently being used.";
const dialogTrigger = <div className="text-rose-600">Delete key</div>;
const [open, setOpen] = useState(false);
return (
<Card className="mx-auto mt-3 w-[28rem] sm:w-[38rem] md:w-full">
@@ -103,17 +137,23 @@ const ApiKeysTable = ({ keys }: { keys: ApiKey[] }) => {
Rotate key
</DropdownMenuItem>
<ConfirmDialog
key={key.keyId}
title={dialogTitle}
body={dialogBody}
trigger={dialogTrigger}
onConfirm={() => {
deleteMutation.mutate({ keyId: key.keyId });
}}
/>
<Allow action="delete" subject="api-keys">
{(allow) => (
<DropdownMenuItem
disabled={!allow}
onSelect={() => setOpen(true)}
>
Delete key
</DropdownMenuItem>
)}
</Allow>
</DropdownMenuContent>
</DropdownMenu>
<DeleteKey
open={open}
setOpen={(val) => setOpen(val)}
keyId={key.keyId}
/>
</div>
</TableCell>
</TableRow>
@@ -1,7 +1,7 @@
import EmptyState from "@/components/common/empty-state";
import { Card } from "@/components/ui/card";
import { withServerComponentSession } from "@/server/auth";
import { db } from "@/server/db";
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";
@@ -12,31 +12,17 @@ export const metadata: Metadata = {
title: "API Keys",
};
const ApiSettingsPage = async () => {
const session = await withServerComponentSession();
const { user } = session;
const apiKeys = await db.apiKey.findMany({
where: {
userId: user.id,
active: true,
},
const { allow } = await serverAccessControl();
orderBy: {
createdAt: "desc",
},
const data = await allow(api.apiKey.listAll.query(), ["api-keys", "read"]);
select: {
id: true,
keyId: true,
createdAt: true,
lastUsed: true,
},
});
console.log({ apiKeys });
if (!data) {
return <UnAuthorizedState />;
}
return (
<Fragment>
{apiKeys.length === 0 ? (
{data.apiKeys.length === 0 ? (
<EmptyState
bordered={false}
icon={<RiTerminalBoxFill />}
@@ -60,7 +46,7 @@ const ApiSettingsPage = async () => {
</div>
</div>
<ApiKeysTable keys={apiKeys} />
<ApiKeysTable keys={data.apiKeys} />
</div>
)}
</Fragment>
+2 -2
View File
@@ -27,7 +27,7 @@ const EmptyState = ({
children,
}: EmptyStateProps) => {
return (
<section className="overflow-hidden">
<div role="alert" className="overflow-hidden">
<div className="container mx-auto px-4">
<div
className={cn(
@@ -46,7 +46,7 @@ const EmptyState = ({
</div>
</div>
</div>
</section>
</div>
);
};
+2 -2
View File
@@ -10,8 +10,8 @@ export const createHash = async (key: string) => {
.toString();
};
export const createApiToken = async () => {
return await randomBytes(32).toString("base64url");
export const createApiToken = () => {
return randomBytes(32).toString("base64url");
};
export const createSecureHash = (key: string) => {
+1
View File
@@ -4,5 +4,6 @@ export const SUBJECTS = [
"stakeholder",
"roles",
"documents",
"api-keys",
] as const;
export type TSubjects = (typeof SUBJECTS)[number];
+7 -2
View File
@@ -94,7 +94,12 @@ const verifyBearerToken = async (bearerToken: string | null | undefined) => {
const apiKey = await db.apiKey.findUnique({
where: { keyId, active: true },
select: { id: true, keyId: true, hashedToken: true, user: true },
select: {
id: true,
keyId: true,
hashedToken: true,
member: { select: { user: true } },
},
});
if (!apiKey) {
@@ -118,5 +123,5 @@ const verifyBearerToken = async (bearerToken: string | null | undefined) => {
data: { lastUsed: new Date() },
});
return apiKey.user;
return apiKey.member.user;
};
+59 -23
View File
@@ -1,22 +1,58 @@
import { generatePublicId } from "@/common/id";
import { createApiToken, createSecureHash } from "@/lib/crypto";
import { createTRPCRouter, withAuth } from "@/trpc/api/trpc";
import { createTRPCRouter, withAccessControl } from "@/trpc/api/trpc";
import { TRPCError } from "@trpc/server";
import z from "zod";
export const apiKeyRouter = createTRPCRouter({
create: withAuth.mutation(async ({ ctx }) => {
const { db, session } = ctx;
const user = session.user;
listAll: withAccessControl
.meta({ policies: { "api-keys": { allow: ["read"] } } })
.query(async ({ ctx }) => {
const {
db,
membership: { companyId, memberId },
} = ctx;
const data = await db.$transaction(async (tx) => {
const token = await createApiToken();
const apiKeys = await db.apiKey.findMany({
where: {
active: true,
companyId,
membershipId: memberId,
},
orderBy: {
createdAt: "desc",
},
select: {
id: true,
keyId: true,
createdAt: true,
lastUsed: true,
},
});
return {
apiKeys,
};
}),
create: withAccessControl
.meta({ policies: { "api-keys": { allow: ["create"] } } })
.mutation(async ({ ctx }) => {
const {
db,
membership: { companyId, memberId },
} = ctx;
const token = createApiToken();
const keyId = generatePublicId();
const hashedToken = await createSecureHash(token);
const hashedToken = createSecureHash(token);
const key = await tx.apiKey.create({
const key = await db.apiKey.create({
data: {
keyId,
userId: user.id,
companyId,
membershipId: memberId,
hashedToken,
},
});
@@ -26,24 +62,24 @@ export const apiKeyRouter = createTRPCRouter({
keyId: key.keyId,
createdAt: key.createdAt,
};
});
}),
return data;
}),
delete: withAuth
delete: withAccessControl
.input(z.object({ keyId: z.string() }))
.meta({ policies: { "api-keys": { allow: ["delete"] } } })
.mutation(async ({ ctx, input }) => {
const {
db,
membership: { memberId, companyId },
} = ctx;
const { keyId } = input;
try {
const { db } = ctx;
const { keyId } = input;
await db.$transaction(async (tx) => {
await tx.apiKey.delete({
where: {
keyId,
},
});
await db.apiKey.delete({
where: {
keyId,
membershipId: memberId,
companyId,
},
});
return {