Merge pull request #1987 from akhilmhdh/feat/ui-permission-check-broken

New API endpoints for Tag update, get by id and get by slug
This commit is contained in:
Akhil Mohan
2024-06-17 19:13:45 +05:30
committed by GitHub
11 changed files with 228 additions and 10 deletions

View File

@@ -6459,12 +6459,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -8115,9 +8115,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"

View File

@@ -508,12 +508,27 @@ export const SECRET_TAGS = {
LIST: {
projectId: "The ID of the project to list tags from."
},
GET_TAG_BY_ID: {
projectId: "The ID of the project to get tags from.",
tagId: "The ID of the tag to get details"
},
GET_TAG_BY_SLUG: {
projectId: "The ID of the project to get tags from.",
tagSlug: "The slug of the tag to get details"
},
CREATE: {
projectId: "The ID of the project to create the tag in.",
name: "The name of the tag to create.",
slug: "The slug of the tag to create.",
color: "The color of the tag to create."
},
UPDATE: {
projectId: "The ID of the project to update the tag in.",
tagId: "The ID of the tag to get details",
name: "The name of the tag to update.",
slug: "The slug of the tag to update.",
color: "The color of the tag to update."
},
DELETE: {
tagId: "The ID of the tag to delete.",
projectId: "The ID of the project to delete the tag from."

View File

@@ -59,6 +59,18 @@ export class BadRequestError extends Error {
}
}
export class NotFoundError extends Error {
name: string;
error: unknown;
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
super(message ?? "The requested entity is not found");
this.name = name || "NotFound";
this.error = error;
}
}
export class DisableRotationErrors extends Error {
name: string;

View File

@@ -6,6 +6,7 @@ import {
BadRequestError,
DatabaseError,
InternalServerError,
NotFoundError,
ScimRequestError,
UnauthorizedError
} from "@app/lib/errors";
@@ -15,6 +16,8 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
req.log.error(error);
if (error instanceof BadRequestError) {
void res.status(400).send({ statusCode: 400, message: error.message, error: error.name });
} else if (error instanceof NotFoundError) {
void res.status(404).send({ statusCode: 404, message: error.message, error: error.name });
} else if (error instanceof UnauthorizedError) {
void res.status(403).send({ statusCode: 403, message: error.message, error: error.name });
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {

View File

@@ -36,6 +36,67 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "GET",
url: "/:projectId/tags/:tagId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_ID.projectId),
tagId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_ID.tagId)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.getTagById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.tagId
});
return { workspaceTag };
}
});
server.route({
method: "GET",
url: "/:projectId/tags/slug/:tagSlug",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_SLUG.projectId),
tagSlug: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_SLUG.tagSlug)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.getTagBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.params.tagSlug,
projectId: req.params.projectId
});
return { workspaceTag };
}
});
server.route({
method: "POST",
url: "/:projectId/tags",
@@ -71,6 +132,42 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "PATCH",
url: "/:projectId/tags/:tagId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.UPDATE.projectId),
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
}),
body: z.object({
name: z.string().trim().describe(SECRET_TAGS.UPDATE.name),
slug: z.string().trim().describe(SECRET_TAGS.UPDATE.slug),
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.updateTag({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
id: req.params.tagId
});
return { workspaceTag };
}
});
server.route({
method: "DELETE",
url: "/:projectId/tags/:tagId",

View File

@@ -2,10 +2,17 @@ import { ForbiddenError } from "@casl/ability";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TSecretTagDALFactory } from "./secret-tag-dal";
import { TCreateTagDTO, TDeleteTagDTO, TListProjectTagsDTO } from "./secret-tag-types";
import {
TCreateTagDTO,
TDeleteTagDTO,
TGetTagByIdDTO,
TGetTagBySlugDTO,
TListProjectTagsDTO,
TUpdateTagDTO
} from "./secret-tag-types";
type TSecretTagServiceFactoryDep = {
secretTagDAL: TSecretTagDALFactory;
@@ -48,6 +55,28 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
return newTag;
};
const updateTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id, name, color, slug }: TUpdateTagDTO) => {
const tag = await secretTagDAL.findById(id);
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
if (slug) {
const existingTag = await secretTagDAL.findOne({ slug, projectId: tag.projectId });
if (existingTag && existingTag.id !== tag.id) throw new BadRequestError({ message: "Tag already exist" });
}
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
tag.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
const updatedTag = await secretTagDAL.updateById(tag.id, { name, color, slug });
return updatedTag;
};
const deleteTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteTagDTO) => {
const tag = await secretTagDAL.findById(id);
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
@@ -65,6 +94,38 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
return deletedTag;
};
const getTagById = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetTagByIdDTO) => {
const tag = await secretTagDAL.findById(id);
if (!tag) throw new NotFoundError({ message: "Tag doesn't exist" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
tag.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
return tag;
};
const getTagBySlug = async ({ actorId, actor, actorOrgId, actorAuthMethod, slug, projectId }: TGetTagBySlugDTO) => {
const tag = await secretTagDAL.findOne({ projectId, slug });
if (!tag) throw new NotFoundError({ message: "Tag doesn't exist" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
tag.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
return tag;
};
const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -79,5 +140,5 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
return tags;
};
return { createTag, deleteTag, getProjectTags };
return { createTag, deleteTag, getProjectTags, getTagById, getTagBySlug, updateTag };
};

View File

@@ -6,6 +6,21 @@ export type TCreateTagDTO = {
slug: string;
} & TProjectPermission;
export type TUpdateTagDTO = {
id: string;
name?: string;
slug?: string;
color?: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetTagByIdDTO = {
id: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetTagBySlugDTO = {
slug: string;
} & TProjectPermission;
export type TDeleteTagDTO = {
id: string;
} & Omit<TProjectPermission, "projectId">;

View File

@@ -0,0 +1,4 @@
---
title: "Get By ID"
openapi: "GET /api/v1/workspace/{projectId}/tags/{tagId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get By Slug"
openapi: "GET /api/v1/workspace/{projectId}/tags/slug/{tagSlug}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/workspace/{projectId}/tags/{tagId}"
---

View File

@@ -505,7 +505,10 @@
"group": "Secret Tags",
"pages": [
"api-reference/endpoints/secret-tags/list",
"api-reference/endpoints/secret-tags/get-by-id",
"api-reference/endpoints/secret-tags/get-by-slug",
"api-reference/endpoints/secret-tags/create",
"api-reference/endpoints/secret-tags/update",
"api-reference/endpoints/secret-tags/delete"
]
},