mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-07 14:20:31 -06:00
Merge branch 'api-keys'
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
PaintBrushIcon,
|
||||
UserCircleIcon,
|
||||
UsersIcon,
|
||||
KeyIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
@@ -54,6 +55,13 @@ export default function SettingsNavbar({ environmentId }: { environmentId: strin
|
||||
current: pathname?.includes("/lookandfeel"),
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
name: "API Keys",
|
||||
href: `/environments/${environmentId}/settings/api-keys`,
|
||||
icon: KeyIcon,
|
||||
current: pathname?.includes("/api-keys"),
|
||||
hidden: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import Modal from "@/components/shared/Modal";
|
||||
import { Button, Input, Label } from "@formbricks/ui";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
interface MemberModalProps {
|
||||
open: boolean;
|
||||
setOpen: (v: boolean) => void;
|
||||
onSubmit: (data: { label: string; environment: string }) => void;
|
||||
}
|
||||
|
||||
export default function AddMemberModal({ open, setOpen, onSubmit }: MemberModalProps) {
|
||||
const { register, getValues, handleSubmit, reset } = useForm<{ label: string; environment: string }>();
|
||||
|
||||
const submitAPIKey = async () => {
|
||||
const data = getValues();
|
||||
onSubmit(data);
|
||||
setOpen(false);
|
||||
reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen} noPadding closeOnOutsideClick={false}>
|
||||
<div className="flex h-full flex-col rounded-lg">
|
||||
<div className="rounded-t-lg bg-slate-100">
|
||||
<div className="flex items-center justify-between p-6">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="text-xl font-medium text-slate-700">Add API Key</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(submitAPIKey)}>
|
||||
<div className="flex justify-between rounded-lg p-6">
|
||||
<div className="w-full space-y-4">
|
||||
<div>
|
||||
<Label>API Key Label</Label>
|
||||
<Input placeholder="e.g. GitHub, PostHog, Slack" {...register("label", { required: true })} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center rounded-lg border border-slate-200 bg-slate-100 p-2 text-sm text-slate-700">
|
||||
<ExclamationTriangleIcon className="mx-3 h-12 w-12 text-amber-500" />
|
||||
<p>
|
||||
For security reasons, the API key will only be <strong>shown once</strong> after creation.
|
||||
Please copy it to your destination right away.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end border-t border-slate-200 p-6">
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="minimal"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" type="submit">
|
||||
Add API Key
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useProduct } from "@/lib/products/products";
|
||||
import EditApiKeys from "./EditApiKeys";
|
||||
|
||||
export default function ApiKeyList({
|
||||
environmentId,
|
||||
environmentType,
|
||||
}: {
|
||||
environmentId: string;
|
||||
environmentType: string;
|
||||
}) {
|
||||
const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId);
|
||||
|
||||
const findEnvironmentByType = (environments, targetType) => {
|
||||
for (const environment of environments) {
|
||||
if (environment.type === targetType) {
|
||||
return environment.id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (isLoadingProduct) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (isErrorProduct) {
|
||||
<ErrorComponent />;
|
||||
}
|
||||
|
||||
const environmentTypeId = findEnvironmentByType(product?.environments, environmentType);
|
||||
|
||||
console.log(environmentTypeId);
|
||||
|
||||
return <EditApiKeys environmentTypeId={environmentTypeId} environmentType={environmentType} />;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
"use client";
|
||||
|
||||
import { timeSince } from "@/../../packages/lib/time";
|
||||
import DeleteDialog from "@/components/shared/DeleteDialog";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { createApiKey, deleteApiKey, useApiKeys } from "@/lib/apiKeys";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils";
|
||||
import { Button, ErrorComponent } from "@formbricks/ui";
|
||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import AddAPIKeyModal from "./AddApiKeyModal";
|
||||
|
||||
export default function EditAPIKeys({
|
||||
environmentTypeId,
|
||||
environmentType,
|
||||
}: {
|
||||
environmentTypeId: string;
|
||||
environmentType: string;
|
||||
}) {
|
||||
const { apiKeys, mutateApiKeys, isLoadingApiKeys, isErrorApiKeys } = useApiKeys(environmentTypeId);
|
||||
|
||||
const [isAddAPIKeyModalOpen, setOpenAddAPIKeyModal] = useState(false);
|
||||
const [isDeleteKeyModalOpen, setOpenDeleteKeyModal] = useState(false);
|
||||
|
||||
const [activeKey, setActiveKey] = useState({} as any);
|
||||
|
||||
const handleOpenDeleteKeyModal = (e, apiKey) => {
|
||||
e.preventDefault();
|
||||
setActiveKey(apiKey);
|
||||
setOpenDeleteKeyModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteKey = async () => {
|
||||
await deleteApiKey(environmentTypeId, activeKey);
|
||||
mutateApiKeys();
|
||||
setOpenDeleteKeyModal(false);
|
||||
toast.success("API Key deleted");
|
||||
};
|
||||
|
||||
const handleAddAPIKey = async (data) => {
|
||||
console.log(data);
|
||||
const apiKey = await createApiKey(environmentTypeId, { label: data.label });
|
||||
mutateApiKeys([...JSON.parse(JSON.stringify(apiKeys)), apiKey], false);
|
||||
setOpenAddAPIKeyModal(false);
|
||||
};
|
||||
|
||||
if (isLoadingApiKeys) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (isErrorApiKeys) {
|
||||
<ErrorComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 text-right">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
setOpenAddAPIKeyModal(true);
|
||||
}}>
|
||||
{`Add ${capitalizeFirstLetter(environmentType)} API Key`}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="grid h-12 grid-cols-9 content-center rounded-t-lg bg-slate-100 px-6 text-left text-sm font-semibold text-slate-900">
|
||||
<div className="col-span-2">Label</div>
|
||||
<div className="col-span-2">API Key</div>
|
||||
<div className="col-span-2">Last used</div>
|
||||
<div className="col-span-2">Created at</div>
|
||||
<div className=""></div>
|
||||
</div>
|
||||
<div className="grid-cols-9">
|
||||
{apiKeys.length === 0 ? (
|
||||
<div className="flex h-12 items-center justify-center whitespace-nowrap px-6 text-sm font-medium text-slate-400 ">
|
||||
You don't have any API keys yet
|
||||
</div>
|
||||
) : (
|
||||
apiKeys.map((apiKey) => (
|
||||
<div
|
||||
className="grid h-12 w-full grid-cols-9 content-center rounded-lg px-6 text-left text-sm text-slate-900"
|
||||
key={apiKey.hashedKey}>
|
||||
<div className="col-span-2 font-semibold">{apiKey.label}</div>
|
||||
<div className="col-span-2">{apiKey.apiKey || <span className="italic">secret</span>}</div>
|
||||
<div className="col-span-2">{apiKey.lastUsed && timeSince(apiKey.lastUsed)}</div>
|
||||
<div className="col-span-2">{timeSince(apiKey.createdAt)}</div>
|
||||
<div className="col-span-1 text-center">
|
||||
<button onClick={(e) => handleOpenDeleteKeyModal(e, apiKey)}>
|
||||
<TrashIcon className="h-5 w-5 text-slate-700 hover:text-slate-500" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AddAPIKeyModal
|
||||
open={isAddAPIKeyModalOpen}
|
||||
setOpen={setOpenAddAPIKeyModal}
|
||||
onSubmit={handleAddAPIKey}
|
||||
/>
|
||||
<DeleteDialog
|
||||
open={isDeleteKeyModalOpen}
|
||||
setOpen={setOpenDeleteKeyModal}
|
||||
deleteWhat="API Key"
|
||||
onDelete={handleDeleteKey}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import SettingsCard from "../SettingsCard";
|
||||
import SettingsTitle from "../SettingsTitle";
|
||||
import ApiKeyList from "./ApiKeyList";
|
||||
|
||||
export default async function ProfileSettingsPage({ params }) {
|
||||
return (
|
||||
<div>
|
||||
<SettingsTitle title="API Keys" />
|
||||
<SettingsCard
|
||||
title="Development Env Keys"
|
||||
description="Add and remove API keys for your Development environment.">
|
||||
<ApiKeyList environmentId={params.environmentId} environmentType="development" />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
title="Production Env Keys"
|
||||
description="Add and remove API keys for your Production environment.">
|
||||
<ApiKeyList environmentId={params.environmentId} environmentType="production" />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export function EditMemberships({ environmentId }) {
|
||||
className="grid h-12 w-full grid-cols-7 content-center rounded-lg p-0.5 py-2 text-left text-sm text-slate-900"
|
||||
key={member.email}>
|
||||
<div className="h-58 px-6 ">
|
||||
<ProfileAvatar userId={member.userId} />
|
||||
<ProfileAvatar userId={member.userId || member.email} />
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col justify-center">
|
||||
<p>{member.name}</p>
|
||||
|
||||
@@ -6,28 +6,26 @@ import { getServerSession } from "next-auth";
|
||||
|
||||
export const hashApiKey = (key: string): string => createHash("sha256").update(key).digest("hex");
|
||||
|
||||
export const hasOwnership = async (model, session, id) => {
|
||||
try {
|
||||
const entity = await prisma[model].findUnique({
|
||||
where: { id: id },
|
||||
include: {
|
||||
user: {
|
||||
select: { email: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
if (entity.user.email === session.email) {
|
||||
return true;
|
||||
} else {
|
||||
export const hasEnvironmentAccess = async (req, res, environmentId) => {
|
||||
if (req.headers["x-api-key"]) {
|
||||
const ownership = await hasApiEnvironmentAccess(req.headers["x-api-key"].toString(), environmentId);
|
||||
if (!ownership) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const user = await getSessionUser(req, res);
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
const ownership = await hasUserEnvironmentAccess(user, environmentId);
|
||||
if (!ownership) {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`can't verify ownership: ${e}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const hasEnvironmentAccess = async (user, environmentId) => {
|
||||
export const hasUserEnvironmentAccess = async (user, environmentId) => {
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: {
|
||||
id: environmentId,
|
||||
@@ -55,6 +53,23 @@ export const hasEnvironmentAccess = async (user, environmentId) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const hasApiEnvironmentAccess = async (apiKey, environmentId) => {
|
||||
// write function to check if the API Key has access to the environment
|
||||
const apiKeyData = await prisma.apiKey.findUnique({
|
||||
where: {
|
||||
hashedKey: hashApiKey(apiKey),
|
||||
},
|
||||
select: {
|
||||
environmentId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (apiKeyData?.environmentId === environmentId) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const hasTeamAccess = async (user, teamId) => {
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
@@ -70,22 +85,8 @@ export const hasTeamAccess = async (user, teamId) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getSessionOrUser = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
export const getSessionUser = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
// check for session (browser usage)
|
||||
let session: any = await getServerSession(req, res, authOptions);
|
||||
if (session && "user" in session) return session.user;
|
||||
// check for api key
|
||||
if (req.headers["x-api-key"]) {
|
||||
const apiKey = await prisma.apiKey.findUnique({
|
||||
where: {
|
||||
hashedKey: hashApiKey(req.headers["x-api-key"].toString()),
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
if (apiKey && apiKey.user) {
|
||||
return apiKey.user;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
62
apps/web/lib/apiKeys.ts
Normal file
62
apps/web/lib/apiKeys.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import useSWR from "swr";
|
||||
import { fetcher } from "@formbricks/lib/fetcher";
|
||||
|
||||
export const useApiKeys = (environmentId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/v1/environments/${environmentId}/api-keys`, fetcher);
|
||||
|
||||
return {
|
||||
apiKeys: data,
|
||||
isLoadingApiKeys: !error && !data,
|
||||
isErrorApiKeys: error,
|
||||
mutateApiKeys: mutate,
|
||||
};
|
||||
};
|
||||
|
||||
export const useApiKey = (environmentId: string, id: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/v1/environments/${environmentId}/api-keys/${id}`, fetcher);
|
||||
|
||||
return {
|
||||
apiKey: data,
|
||||
isLoadingApiKey: !error && !data,
|
||||
isErrorApiKey: error,
|
||||
mutateApiKey: mutate,
|
||||
};
|
||||
};
|
||||
|
||||
export const persistApiKey = async (environmentId: string, apiKey) => {
|
||||
try {
|
||||
await fetch(`/api/v1/environments/${environmentId}/api-keys/${apiKey.id}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(apiKey),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const createApiKey = async (environmentId: string, apiKey = {}) => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/environments/${environmentId}/api-keys`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(apiKey),
|
||||
});
|
||||
return await res.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw Error(`createApiKey: unable to create api-key: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteApiKey = async (environmentId: string, apiKey) => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/environments/${environmentId}/api-keys/${apiKey.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
return await res.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw Error(`deleteApiKey: unable to delete api-key: ${error.message}`);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
const apiKeyId = req.query.apiKeyId?.toString();
|
||||
|
||||
if (!apiKeyId || !environmentId) {
|
||||
return res.status(400).json({ message: "Missing apiKeyId or environmentId" });
|
||||
}
|
||||
|
||||
if (!(await hasEnvironmentAccess(req, res, environmentId))) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
// DELETE /api/environments/:environmentId/api-keys/:apiKeyId
|
||||
// Deletes an existing API Key
|
||||
// Required fields in body: environmentId, apiKeyId
|
||||
// Optional fields in body: -
|
||||
if (req.method === "DELETE") {
|
||||
const prismaRes = await prisma.apiKey.delete({
|
||||
where: { id: apiKeyId },
|
||||
});
|
||||
return res.json(prismaRes);
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,42 @@
|
||||
import { getSessionOrUser, hashApiKey } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess, hashApiKey } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { randomBytes } from "crypto";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const session = await getSessionOrUser(req, res);
|
||||
if (!session) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
if (!(await hasEnvironmentAccess(req, res, environmentId))) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
// GET /api/users/[userId]/api-keys/
|
||||
// Gets all ApiKeys of a user
|
||||
// GET /api/environments/[environmentId]/api-keys/
|
||||
// Gets all ApiKeys of an environment
|
||||
if (req.method === "GET") {
|
||||
const apiKeys = await prisma.apiKey.findMany({
|
||||
where: {
|
||||
user: { email: session.email },
|
||||
environmentId,
|
||||
},
|
||||
});
|
||||
return res.json(apiKeys);
|
||||
}
|
||||
// POST /api/users/[userId]/api-keys/
|
||||
// Creates a ApiKey
|
||||
// Required fields in body: -
|
||||
// Optional fields in body: note
|
||||
else if (req.method === "POST") {
|
||||
const apiKey = req.body;
|
||||
|
||||
// POST /api/environments/:environmentId/api-keys
|
||||
// Creates an API Key
|
||||
// Optional fields in body: label
|
||||
if (req.method === "POST") {
|
||||
const apiKey = req.body;
|
||||
const key = randomBytes(16).toString("hex");
|
||||
// create form in database
|
||||
// Create API Key in the database
|
||||
const result = await prisma.apiKey.create({
|
||||
data: {
|
||||
...apiKey,
|
||||
hashedKey: hashApiKey(key),
|
||||
user: { connect: { email: session?.email } },
|
||||
environment: { connect: { id: environmentId } },
|
||||
},
|
||||
});
|
||||
res.json({ ...result, apiKey: key });
|
||||
@@ -1,14 +1,8 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
if (environmentId === undefined) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
@@ -19,8 +13,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(400).json({ message: "Missing attributeClassId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
if (!(await hasEnvironmentAccess(req, res, environmentId))) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
// GET
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
const eventClassId = req.query.eventClassId?.toString();
|
||||
@@ -20,8 +14,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(400).json({ message: "Missing eventClassId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
if (!(await hasEnvironmentAccess(req, res, environmentId))) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
// GET
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query?.environmentId?.toString();
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
@@ -36,21 +30,6 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
if (environment === null) {
|
||||
return res.status(404).json({ message: "This environment doesn't exist" });
|
||||
}
|
||||
|
||||
// check if membership exists
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_teamId: {
|
||||
userId: user.id,
|
||||
teamId: environment.product.teamId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
return res.json(environment);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getSessionOrUser } from "@/lib/api/apiHelper";
|
||||
import { getSessionUser } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
const user: any = await getSessionUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
const personId = req.query.personId?.toString();
|
||||
@@ -20,8 +14,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(400).json({ message: "Missing personId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query?.environmentId?.toString();
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
// GET
|
||||
if (req.method === "GET") {
|
||||
const environment = await prisma.environment.findUnique({
|
||||
@@ -33,6 +26,14 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
where: {
|
||||
id: environment.productId,
|
||||
},
|
||||
include: {
|
||||
environments: {
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (product === null) {
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
const surveyId = req.query.surveyId?.toString();
|
||||
@@ -20,8 +14,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(400).json({ message: "Missing surveyId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
const surveyId = req.query.surveyId?.toString();
|
||||
const responseId = req.query.responseId?.toString();
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
const surveyId = req.query.surveyId?.toString();
|
||||
|
||||
@@ -19,8 +13,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(400).json({ message: "Missing surveyId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import { getSessionOrUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(user, environmentId);
|
||||
if (hasAccess === false) {
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSessionOrUser } from "@/lib/api/apiHelper";
|
||||
import { getSessionUser } from "@/lib/api/apiHelper";
|
||||
import { populateEnvironment } from "@/lib/populate";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
@@ -6,7 +6,7 @@ import { EnvironmentType } from "@prisma/client";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
const user: any = await getSessionUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getSessionOrUser } from "@/lib/api/apiHelper";
|
||||
import { getSessionUser } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const user: any = await getSessionOrUser(req, res);
|
||||
const user: any = await getSessionUser(req, res);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getSessionOrUser } from "@/lib/api/apiHelper";
|
||||
import { getSessionUser } from "@/lib/api/apiHelper";
|
||||
import { sendInviteMemberEmail } from "@/lib/email";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const currentUser: any = await getSessionOrUser(req, res);
|
||||
const currentUser: any = await getSessionUser(req, res);
|
||||
if (!currentUser) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getSessionOrUser, hasTeamAccess } from "@/lib/api/apiHelper";
|
||||
import { getSessionUser, hasTeamAccess } from "@/lib/api/apiHelper";
|
||||
import { sendInviteMemberEmail } from "@/lib/email";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const currentUser: any = await getSessionOrUser(req, res);
|
||||
const currentUser: any = await getSessionUser(req, res);
|
||||
if (!currentUser) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
|
||||
const hasAccess = await hasTeamAccess(currentUser, teamId);
|
||||
if (hasAccess === false) {
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
// TODO check if User is ADMIN or OWNER
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getSessionOrUser, hasTeamAccess } from "@/lib/api/apiHelper";
|
||||
import { getSessionUser, hasTeamAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const currentUser: any = await getSessionOrUser(req, res);
|
||||
const currentUser: any = await getSessionUser(req, res);
|
||||
if (!currentUser) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
|
||||
const hasAccess = await hasTeamAccess(currentUser, teamId);
|
||||
if (hasAccess === false) {
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { getSessionOrUser, hasOwnership } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const session = await getSessionOrUser(req, res);
|
||||
if (!session) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const apiKeyId = req.query.apiKeyId?.toString();
|
||||
|
||||
if (!apiKeyId) {
|
||||
return res.status(400).json({ message: "Missing apiKeyId" });
|
||||
}
|
||||
|
||||
const ownership = await hasOwnership("apiKey", session, apiKeyId);
|
||||
if (!ownership) {
|
||||
return res.status(401).json({ message: "You are not authorized to access this apiKey" });
|
||||
}
|
||||
|
||||
// GET /api/users/me/api-keys/:apiKeyId
|
||||
// Get apiKey with specific id
|
||||
if (req.method === "GET") {
|
||||
const apiKey = await prisma.apiKey.findUnique({
|
||||
where: {
|
||||
id: apiKeyId,
|
||||
},
|
||||
});
|
||||
if (apiKey === null) return res.status(404).json({ error: "not found" });
|
||||
return res.json(apiKey);
|
||||
}
|
||||
// DELETE /api/users/me/api-keys/:apiKeyId
|
||||
// Deletes an existing apiKey
|
||||
// Required fields in body: -
|
||||
// Optional fields in body: -
|
||||
else if (req.method === "DELETE") {
|
||||
const prismaRes = await prisma.apiKey.delete({
|
||||
where: { id: apiKeyId },
|
||||
});
|
||||
return res.json(prismaRes);
|
||||
}
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getSessionOrUser } from "@/lib/api/apiHelper";
|
||||
import { getSessionUser } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Check Authentication
|
||||
const session = await getSessionOrUser(req, res);
|
||||
const session = await getSessionUser(req, res);
|
||||
if (!session) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
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 `environmentId` to the `ApiKey` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "ApiKey" DROP CONSTRAINT "ApiKey_userId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "ApiKey" DROP COLUMN "userId",
|
||||
ADD COLUMN "environmentId" TEXT NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "Environment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -196,6 +196,7 @@ model Environment {
|
||||
people Person[]
|
||||
eventClasses EventClass[]
|
||||
attributeClasses AttributeClass[]
|
||||
apiKeys ApiKey[]
|
||||
}
|
||||
|
||||
model Product {
|
||||
@@ -265,13 +266,13 @@ model Invite {
|
||||
}
|
||||
|
||||
model ApiKey {
|
||||
id String @id @unique @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
lastUsedAt DateTime?
|
||||
label String?
|
||||
hashedKey String @unique()
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
id String @id @unique @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
lastUsedAt DateTime?
|
||||
label String?
|
||||
hashedKey String @unique()
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
environmentId String
|
||||
}
|
||||
|
||||
enum IdentityProvider {
|
||||
@@ -311,7 +312,6 @@ model User {
|
||||
identityProviderAccountId String?
|
||||
memberships Membership[]
|
||||
accounts Account[]
|
||||
apiKeys ApiKey[]
|
||||
groupId String?
|
||||
invitesCreated Invite[] @relation("inviteCreatedBy")
|
||||
invitesAccepted Invite[] @relation("inviteAcceptedBy")
|
||||
|
||||
Reference in New Issue
Block a user