mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-08 06:41:45 -05:00
chore: add authorization to server actions for Actions (#868)
* poc: use server session and api key validation on deletion * feat: use server session and api key validation on deletion and creation * feat: packages/lib/apiKey for apiKey services and auth * shubham/auth-for-api-key * fix: caching * feat: handle authorization for action creation, update, delete * feat: use cached method across and wrapper for authzn check * fix: club caching methods and use authzn errors * feat: add caching in canUserAccessApiKey * feat: add caching in canUserAccessAction and use Authzn error * fix: rename action to actionClass wherever needed * fix: use cache getActionClass * fix: make changes * fix: import --------- Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
e01d5a44f4
commit
c4b4d2a312
@@ -0,0 +1,24 @@
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { getActionClass } from "./service";
|
||||
import { unstable_cache } from "next/cache";
|
||||
|
||||
export const canUserAccessActionClass = async (userId: string, actionClassId: string): Promise<boolean> =>
|
||||
await unstable_cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZId], [actionClassId, ZId]);
|
||||
if (!userId) return false;
|
||||
|
||||
const actionClass = await getActionClass(actionClassId);
|
||||
if (!actionClass) return false;
|
||||
|
||||
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, actionClass.environmentId);
|
||||
if (!hasAccessToEnvironment) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
[`users-${userId}-actionClasses-${actionClassId}`],
|
||||
{ revalidate: 30 * 60, tags: [`actionClasses-${actionClassId}`] }
|
||||
)(); // 30 minutes
|
||||
@@ -0,0 +1,173 @@
|
||||
"use server";
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TActionClass, TActionClassInput, ZActionClassInput } from "@formbricks/types/v1/actionClasses";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
|
||||
import { revalidateTag, unstable_cache } from "next/cache";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
const halfHourInSeconds = 60 * 30;
|
||||
|
||||
export const getActionClassCacheTag = (name: string, environmentId: string): string =>
|
||||
`environments-${environmentId}-actionClass-${name}`;
|
||||
|
||||
const getActionClassesCacheTag = (environmentId: string): string =>
|
||||
`environments-${environmentId}-actionClasses`;
|
||||
|
||||
const select = {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
description: true,
|
||||
type: true,
|
||||
noCodeConfig: true,
|
||||
environmentId: true,
|
||||
};
|
||||
|
||||
export const getActionClasses = (environmentId: string): Promise<TActionClass[]> =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId]);
|
||||
try {
|
||||
let actionClasses = await prisma.eventClass.findMany({
|
||||
where: {
|
||||
environmentId: environmentId,
|
||||
},
|
||||
select,
|
||||
orderBy: {
|
||||
createdAt: "asc",
|
||||
},
|
||||
});
|
||||
|
||||
return actionClasses;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when fetching actions for environment ${environmentId}`);
|
||||
}
|
||||
},
|
||||
[`environments-${environmentId}-actionClasses`],
|
||||
{
|
||||
tags: [getActionClassesCacheTag(environmentId)],
|
||||
revalidate: halfHourInSeconds,
|
||||
}
|
||||
)();
|
||||
|
||||
export const getActionClass = async (actionClassId: string): Promise<TActionClass | null> => {
|
||||
validateInputs([actionClassId, ZId]);
|
||||
try {
|
||||
let actionClass = await prisma.eventClass.findUnique({
|
||||
where: {
|
||||
id: actionClassId,
|
||||
},
|
||||
select,
|
||||
});
|
||||
|
||||
return actionClass;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when fetching action`);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteActionClass = async (
|
||||
environmentId: string,
|
||||
actionClassId: string
|
||||
): Promise<TActionClass> => {
|
||||
validateInputs([environmentId, ZId], [actionClassId, ZId]);
|
||||
try {
|
||||
const result = await prisma.eventClass.delete({
|
||||
where: {
|
||||
id: actionClassId,
|
||||
},
|
||||
select,
|
||||
});
|
||||
if (result === null) throw new ResourceNotFoundError("Action", actionClassId);
|
||||
|
||||
// revalidate cache
|
||||
revalidateTag(getActionClassesCacheTag(result.environmentId));
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(
|
||||
`Database error when deleting an action with id ${actionClassId} for environment ${environmentId}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const createActionClass = async (
|
||||
environmentId: string,
|
||||
actionClass: TActionClassInput
|
||||
): Promise<TActionClass> => {
|
||||
validateInputs([environmentId, ZId], [actionClass, ZActionClassInput]);
|
||||
try {
|
||||
const result = await prisma.eventClass.create({
|
||||
data: {
|
||||
name: actionClass.name,
|
||||
description: actionClass.description,
|
||||
type: actionClass.type,
|
||||
noCodeConfig: actionClass.noCodeConfig
|
||||
? JSON.parse(JSON.stringify(actionClass.noCodeConfig))
|
||||
: undefined,
|
||||
environment: { connect: { id: environmentId } },
|
||||
},
|
||||
select,
|
||||
});
|
||||
|
||||
// revalidate cache
|
||||
revalidateTag(getActionClassesCacheTag(environmentId));
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when creating an action for environment ${environmentId}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateActionClass = async (
|
||||
environmentId: string,
|
||||
actionClassId: string,
|
||||
inputActionClass: Partial<TActionClassInput>
|
||||
): Promise<TActionClass> => {
|
||||
validateInputs([environmentId, ZId], [actionClassId, ZId], [inputActionClass, ZActionClassInput.partial()]);
|
||||
try {
|
||||
const result = await prisma.eventClass.update({
|
||||
where: {
|
||||
id: actionClassId,
|
||||
},
|
||||
data: {
|
||||
name: inputActionClass.name,
|
||||
description: inputActionClass.description,
|
||||
type: inputActionClass.type,
|
||||
noCodeConfig: inputActionClass.noCodeConfig
|
||||
? JSON.parse(JSON.stringify(inputActionClass.noCodeConfig))
|
||||
: undefined,
|
||||
},
|
||||
select,
|
||||
});
|
||||
|
||||
// revalidate cache
|
||||
revalidateTag(getActionClassCacheTag(result.name, result.environmentId));
|
||||
revalidateTag(getActionClassesCacheTag(result.environmentId));
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when updating an action for environment ${environmentId}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getActionClassCached = async (name: string, environmentId: string) =>
|
||||
unstable_cache(
|
||||
async () => {
|
||||
return await prisma.eventClass.findFirst({
|
||||
where: {
|
||||
name,
|
||||
environmentId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[`environments-${environmentId}-actionClasses-${name}`],
|
||||
{
|
||||
tags: [getActionClassesCacheTag(environmentId)],
|
||||
revalidate: halfHourInSeconds,
|
||||
}
|
||||
)();
|
||||
Reference in New Issue
Block a user