mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-28 09:20:49 -06:00
chore: API key types (#4610)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { getApiKeyFromKey } from "@formbricks/lib/apiKey/service";
|
||||
import { TAuthenticationApiKey } from "@formbricks/types/auth";
|
||||
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { getEnvironmentIdFromApiKey } from "./lib/api-key";
|
||||
|
||||
export const authenticateRequest = async (request: Request): Promise<TAuthenticationApiKey | null> => {
|
||||
const apiKey = request.headers.get("x-api-key");
|
||||
if (apiKey) {
|
||||
const apiKeyData = await getApiKeyFromKey(apiKey);
|
||||
if (apiKeyData) {
|
||||
const environmentId = await getEnvironmentIdFromApiKey(apiKey);
|
||||
if (environmentId) {
|
||||
const authentication: TAuthenticationApiKey = {
|
||||
type: "apiKey",
|
||||
environmentId: apiKeyData.environmentId,
|
||||
environmentId,
|
||||
};
|
||||
return authentication;
|
||||
}
|
||||
|
||||
50
apps/web/app/api/v1/lib/api-key.ts
Normal file
50
apps/web/app/api/v1/lib/api-key.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { apiKeyCache } from "@/lib/cache/api-key";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { getHash } from "@formbricks/lib/crypto";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { ZString } from "@formbricks/types/common";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
|
||||
export const getEnvironmentIdFromApiKey = reactCache(async (apiKey: string): Promise<string | null> => {
|
||||
const hashedKey = getHash(apiKey);
|
||||
return cache(
|
||||
async () => {
|
||||
validateInputs([apiKey, ZString]);
|
||||
|
||||
if (!apiKey) {
|
||||
throw new InvalidInputError("API key cannot be null or undefined.");
|
||||
}
|
||||
|
||||
try {
|
||||
const apiKeyData = await prisma.apiKey.findUnique({
|
||||
where: {
|
||||
hashedKey,
|
||||
},
|
||||
select: {
|
||||
environmentId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!apiKeyData) {
|
||||
throw new ResourceNotFoundError("apiKey", apiKey);
|
||||
}
|
||||
|
||||
return apiKeyData.environmentId;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getEnvironmentIdFromApiKey-${apiKey}`],
|
||||
{
|
||||
tags: [apiKeyCache.tag.byHashedKey(hashedKey)],
|
||||
}
|
||||
)();
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getEnvironmentIdFromApiKey } from "@/app/api/v1/lib/api-key";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { headers } from "next/headers";
|
||||
import { getApiKeyFromKey } from "@formbricks/lib/apiKey/service";
|
||||
import { deleteWebhook, getWebhook } from "@formbricks/lib/webhook/service";
|
||||
|
||||
export const GET = async (_: Request, props: { params: Promise<{ webhookId: string }> }) => {
|
||||
@@ -10,8 +10,8 @@ export const GET = async (_: Request, props: { params: Promise<{ webhookId: stri
|
||||
if (!apiKey) {
|
||||
return responses.notAuthenticatedResponse();
|
||||
}
|
||||
const apiKeyData = await getApiKeyFromKey(apiKey);
|
||||
if (!apiKeyData) {
|
||||
const environmentId = await getEnvironmentIdFromApiKey(apiKey);
|
||||
if (!environmentId) {
|
||||
return responses.notAuthenticatedResponse();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const GET = async (_: Request, props: { params: Promise<{ webhookId: stri
|
||||
if (!webhook) {
|
||||
return responses.notFoundResponse("Webhook", params.webhookId);
|
||||
}
|
||||
if (webhook.environmentId !== apiKeyData.environmentId) {
|
||||
if (webhook.environmentId !== environmentId) {
|
||||
return responses.unauthorizedResponse();
|
||||
}
|
||||
return responses.successResponse(webhook);
|
||||
@@ -33,8 +33,8 @@ export const DELETE = async (_: Request, props: { params: Promise<{ webhookId: s
|
||||
if (!apiKey) {
|
||||
return responses.notAuthenticatedResponse();
|
||||
}
|
||||
const apiKeyData = await getApiKeyFromKey(apiKey);
|
||||
if (!apiKeyData) {
|
||||
const environmentId = await getEnvironmentIdFromApiKey(apiKey);
|
||||
if (!environmentId) {
|
||||
return responses.notAuthenticatedResponse();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export const DELETE = async (_: Request, props: { params: Promise<{ webhookId: s
|
||||
if (!webhook) {
|
||||
return responses.notFoundResponse("Webhook", params.webhookId);
|
||||
}
|
||||
if (webhook.environmentId !== apiKeyData.environmentId) {
|
||||
if (webhook.environmentId !== environmentId) {
|
||||
return responses.unauthorizedResponse();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getEnvironmentIdFromApiKey } from "@/app/api/v1/lib/api-key";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { headers } from "next/headers";
|
||||
import { getApiKeyFromKey } from "@formbricks/lib/apiKey/service";
|
||||
import { createWebhook, getWebhooks } from "@formbricks/lib/webhook/service";
|
||||
import { DatabaseError, InvalidInputError } from "@formbricks/types/errors";
|
||||
import { ZWebhookInput } from "@formbricks/types/webhooks";
|
||||
@@ -12,14 +12,14 @@ export const GET = async () => {
|
||||
if (!apiKey) {
|
||||
return responses.notAuthenticatedResponse();
|
||||
}
|
||||
const apiKeyData = await getApiKeyFromKey(apiKey);
|
||||
if (!apiKeyData) {
|
||||
const environmentId = await getEnvironmentIdFromApiKey(apiKey);
|
||||
if (!environmentId) {
|
||||
return responses.notAuthenticatedResponse();
|
||||
}
|
||||
|
||||
// get webhooks from database
|
||||
try {
|
||||
const webhooks = await getWebhooks(apiKeyData.environmentId);
|
||||
const webhooks = await getWebhooks(environmentId);
|
||||
return Response.json({ data: webhooks });
|
||||
} catch (error) {
|
||||
if (error instanceof DatabaseError) {
|
||||
@@ -35,8 +35,8 @@ export const POST = async (request: Request) => {
|
||||
if (!apiKey) {
|
||||
return responses.notAuthenticatedResponse();
|
||||
}
|
||||
const apiKeyData = await getApiKeyFromKey(apiKey);
|
||||
if (!apiKeyData) {
|
||||
const environmentId = await getEnvironmentIdFromApiKey(apiKey);
|
||||
if (!environmentId) {
|
||||
return responses.notAuthenticatedResponse();
|
||||
}
|
||||
const webhookInput = await request.json();
|
||||
@@ -52,7 +52,7 @@ export const POST = async (request: Request) => {
|
||||
|
||||
// add webhook to database
|
||||
try {
|
||||
const webhook = await createWebhook(apiKeyData.environmentId, inputValidation.data);
|
||||
const webhook = await createWebhook(environmentId, inputValidation.data);
|
||||
return responses.successResponse(webhook);
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidInputError) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use server";
|
||||
|
||||
import { apiKeyCache } from "@/lib/cache/api-key";
|
||||
import { contactCache } from "@/lib/cache/contact";
|
||||
import { teamCache } from "@/lib/cache/team";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { actionClassCache } from "@formbricks/lib/actionClass/cache";
|
||||
import { apiKeyCache } from "@formbricks/lib/apiKey/cache";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { segmentCache } from "@formbricks/lib/cache/segment";
|
||||
import { environmentCache } from "@formbricks/lib/environment/cache";
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
getProjectIdFromApiKeyId,
|
||||
getProjectIdFromEnvironmentId,
|
||||
} from "@/lib/utils/helper";
|
||||
import { createApiKey, deleteApiKey } from "@/modules/projects/settings/lib/api-key";
|
||||
import { createApiKey, deleteApiKey } from "@/modules/projects/settings/api-keys/lib/api-key";
|
||||
import { z } from "zod";
|
||||
import { ZApiKeyCreateInput } from "@formbricks/types/api-keys";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { ZApiKeyCreateInput } from "./types/api-keys";
|
||||
|
||||
const ZDeleteApiKeyAction = z.object({
|
||||
id: ZId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getApiKeys } from "@/modules/projects/settings/api-keys/lib/api-key";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getApiKeys } from "@formbricks/lib/apiKey/service";
|
||||
import { getEnvironments } from "@formbricks/lib/environment/service";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { TApiKey } from "@/modules/projects/settings/api-keys/types/api-keys";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
import { FilesIcon, TrashIcon } from "lucide-react";
|
||||
@@ -8,7 +9,6 @@ import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { timeSince } from "@formbricks/lib/time";
|
||||
import { TApiKey } from "@formbricks/types/api-keys";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { createApiKeyAction, deleteApiKeyAction } from "../actions";
|
||||
import { AddApiKeyModal } from "./add-api-key-modal";
|
||||
@@ -60,7 +60,6 @@ export const EditAPIKeys = ({
|
||||
environmentId: environmentTypeId,
|
||||
apiKeyData: { label: data.label },
|
||||
});
|
||||
console.log("createApiKeyResponse", createApiKeyResponse);
|
||||
if (createApiKeyResponse?.data) {
|
||||
const updatedApiKeys = [...apiKeysLocal!, createApiKeyResponse.data];
|
||||
setApiKeysLocal(updatedApiKeys);
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
import "server-only";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { apiKeyCache } from "@/lib/cache/api-key";
|
||||
import { TApiKeyCreateInput, ZApiKeyCreateInput } from "@/modules/projects/settings/api-keys/types/api-keys";
|
||||
import { TApiKey } from "@/modules/projects/settings/api-keys/types/api-keys";
|
||||
import { ApiKey, Prisma } from "@prisma/client";
|
||||
import { createHash, randomBytes } from "crypto";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { apiKeyCache } from "@formbricks/lib/apiKey/cache";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { ITEMS_PER_PAGE } from "@formbricks/lib/constants";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { TApiKey, TApiKeyCreateInput, ZApiKeyCreateInput } from "@formbricks/types/api-keys";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { ZId, ZOptionalNumber } from "@formbricks/types/common";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
|
||||
export const deleteApiKey = async (id: string): Promise<TApiKey | null> => {
|
||||
export const getApiKeys = reactCache(
|
||||
async (environmentId: string, page?: number): Promise<ApiKey[]> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
||||
|
||||
try {
|
||||
const apiKeys = await prisma.apiKey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
},
|
||||
take: page ? ITEMS_PER_PAGE : undefined,
|
||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||
});
|
||||
|
||||
return apiKeys;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getApiKeys-${environmentId}-${page}`],
|
||||
{
|
||||
tags: [apiKeyCache.tag.byEnvironmentId(environmentId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
|
||||
export const deleteApiKey = async (id: string): Promise<ApiKey | null> => {
|
||||
validateInputs([id, ZId]);
|
||||
|
||||
try {
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ApiKey } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
import { ZApiKey } from "@formbricks/database/zod/api-keys";
|
||||
|
||||
export const ZApiKeyCreateInput = ZApiKey.required({
|
||||
label: true,
|
||||
}).pick({
|
||||
label: true,
|
||||
});
|
||||
|
||||
export type TApiKeyCreateInput = z.infer<typeof ZApiKeyCreateInput>;
|
||||
|
||||
export interface TApiKey extends ApiKey {
|
||||
apiKey?: string;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { type ApiKey } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZApiKey = z.object({
|
||||
@@ -7,12 +8,4 @@ export const ZApiKey = z.object({
|
||||
label: z.string().nullable(),
|
||||
hashedKey: z.string(),
|
||||
environmentId: z.string().cuid2(),
|
||||
apiKey: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TApiKey = z.infer<typeof ZApiKey>;
|
||||
|
||||
export const ZApiKeyCreateInput = z.object({
|
||||
label: z.string(),
|
||||
});
|
||||
export type TApiKeyCreateInput = z.infer<typeof ZApiKeyCreateInput>;
|
||||
}) satisfies z.ZodType<ApiKey>;
|
||||
@@ -1,29 +0,0 @@
|
||||
import "server-only";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { cache } from "../cache";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { apiKeyCache } from "./cache";
|
||||
import { getApiKey } from "./service";
|
||||
|
||||
export const canUserAccessApiKey = (userId: string, apiKeyId: string): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZId], [apiKeyId, ZId]);
|
||||
|
||||
try {
|
||||
const apiKeyFromServer = await getApiKey(apiKeyId);
|
||||
if (!apiKeyFromServer) return false;
|
||||
|
||||
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, apiKeyFromServer.environmentId);
|
||||
if (!hasAccessToEnvironment) return false;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
[`canUserAccessApiKey-${userId}-${apiKeyId}`],
|
||||
{ tags: [apiKeyCache.tag.byId(apiKeyId)] }
|
||||
)();
|
||||
@@ -1,109 +0,0 @@
|
||||
import "server-only";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TApiKey } from "@formbricks/types/api-keys";
|
||||
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError, InvalidInputError } from "@formbricks/types/errors";
|
||||
import { cache } from "../cache";
|
||||
import { ITEMS_PER_PAGE } from "../constants";
|
||||
import { getHash } from "../crypto";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { apiKeyCache } from "./cache";
|
||||
|
||||
export const getApiKey = reactCache(
|
||||
async (apiKeyId: string): Promise<TApiKey | null> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([apiKeyId, ZString]);
|
||||
|
||||
if (!apiKeyId) {
|
||||
throw new InvalidInputError("API key cannot be null or undefined.");
|
||||
}
|
||||
|
||||
try {
|
||||
const apiKeyData = await prisma.apiKey.findUnique({
|
||||
where: {
|
||||
id: apiKeyId,
|
||||
},
|
||||
});
|
||||
|
||||
return apiKeyData;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getApiKey-${apiKeyId}`],
|
||||
{
|
||||
tags: [apiKeyCache.tag.byId(apiKeyId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
|
||||
export const getApiKeys = reactCache(
|
||||
async (environmentId: string, page?: number): Promise<TApiKey[]> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId], [page, ZOptionalNumber]);
|
||||
|
||||
try {
|
||||
const apiKeys = await prisma.apiKey.findMany({
|
||||
where: {
|
||||
environmentId,
|
||||
},
|
||||
take: page ? ITEMS_PER_PAGE : undefined,
|
||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||
});
|
||||
|
||||
return apiKeys;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getApiKeys-${environmentId}-${page}`],
|
||||
{
|
||||
tags: [apiKeyCache.tag.byEnvironmentId(environmentId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
|
||||
export const getApiKeyFromKey = reactCache(async (apiKey: string): Promise<TApiKey | null> => {
|
||||
const hashedKey = getHash(apiKey);
|
||||
return cache(
|
||||
async () => {
|
||||
validateInputs([apiKey, ZString]);
|
||||
|
||||
if (!apiKey) {
|
||||
throw new InvalidInputError("API key cannot be null or undefined.");
|
||||
}
|
||||
|
||||
try {
|
||||
const apiKeyData = await prisma.apiKey.findUnique({
|
||||
where: {
|
||||
hashedKey,
|
||||
},
|
||||
});
|
||||
|
||||
return apiKeyData;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getApiKeyFromKey-${apiKey}`],
|
||||
{
|
||||
tags: [apiKeyCache.tag.byHashedKey(hashedKey)],
|
||||
}
|
||||
)();
|
||||
});
|
||||
@@ -18,6 +18,7 @@
|
||||
".",
|
||||
"../types/*.d.ts",
|
||||
"../../apps/web/lib/cache/contact-attribute-key.ts",
|
||||
"../../apps/web/modules/utils/hooks"
|
||||
"../../apps/web/modules/utils/hooks",
|
||||
"../../apps/web/lib/cache/api-key.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { z } from "zod";
|
||||
import { ZId } from "./common";
|
||||
|
||||
export const ZContact = z.object({
|
||||
id: ZId,
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
environmentId: ZId,
|
||||
});
|
||||
|
||||
export type TContact = z.infer<typeof ZContact>;
|
||||
@@ -20,12 +20,6 @@ export const ZDisplayCreateInput = z.object({
|
||||
|
||||
export type TDisplayCreateInput = z.infer<typeof ZDisplayCreateInput>;
|
||||
|
||||
export const ZDisplaysWithSurveyName = ZDisplay.extend({
|
||||
surveyName: z.string(),
|
||||
});
|
||||
|
||||
export type TDisplaysWithSurveyName = z.infer<typeof ZDisplaysWithSurveyName>;
|
||||
|
||||
export const ZDisplayFilters = z.object({
|
||||
createdAt: z
|
||||
.object({
|
||||
|
||||
Reference in New Issue
Block a user