diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-fns.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-fns.ts index 96d95be85..0e242f04c 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-fns.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-fns.ts @@ -1,23 +1,21 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors"; import { ActorType } from "@app/services/auth/auth-type"; import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission"; -import { TVerifyApprovers, VerifyApproversError } from "./access-approval-policy-types"; +import { TIsApproversValid } from "./access-approval-policy-types"; -export const verifyApprovers = async ({ +export const isApproversValid = async ({ userIds, projectId, orgId, envSlug, actorAuthMethod, secretPath, - permissionService, - error -}: TVerifyApprovers) => { - for await (const userId of userIds) { - try { + permissionService +}: TIsApproversValid) => { + try { + for await (const userId of userIds) { const { permission: approverPermission } = await permissionService.getProjectPermission( ActorType.USER, userId, @@ -30,17 +28,9 @@ export const verifyApprovers = async ({ ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment: envSlug, secretPath }) ); - } catch (err) { - if (error === VerifyApproversError.BadRequestError) { - throw new BadRequestError({ - message: "One or more approvers doesn't have access to be specified secret path" - }); - } - if (error === VerifyApproversError.ForbiddenError) { - throw new ForbiddenRequestError({ - message: "You don't have access to approve this request" - }); - } } + } catch { + return false; } + return true; }; diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts index 013444166..2633a026f 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts @@ -2,7 +2,7 @@ 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, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; @@ -11,7 +11,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal"; import { TGroupDALFactory } from "../group/group-dal"; import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal"; import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal"; -import { verifyApprovers } from "./access-approval-policy-fns"; +import { isApproversValid } from "./access-approval-policy-fns"; import { ApproverType, TCreateAccessApprovalPolicy, @@ -19,8 +19,7 @@ import { TGetAccessApprovalPolicyByIdDTO, TGetAccessPolicyCountByEnvironmentDTO, TListAccessApprovalPoliciesDTO, - TUpdateAccessApprovalPolicy, - VerifyApproversError + TUpdateAccessApprovalPolicy } from "./access-approval-policy-types"; type TSecretApprovalPolicyServiceFactoryDep = { @@ -133,17 +132,22 @@ export const accessApprovalPolicyServiceFactory = ({ .map((user) => user.id); verifyAllApprovers.push(...verifyGroupApprovers); - await verifyApprovers({ + const approversValid = await isApproversValid({ projectId: project.id, orgId: actorOrgId, envSlug: environment, secretPath, actorAuthMethod, permissionService, - userIds: verifyAllApprovers, - error: VerifyApproversError.BadRequestError + userIds: verifyAllApprovers }); + if (!approversValid) { + throw new BadRequestError({ + message: "One or more approvers doesn't have access to be specified secret path" + }); + } + const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => { const doc = await accessApprovalPolicyDAL.create( { @@ -285,17 +289,22 @@ export const accessApprovalPolicyServiceFactory = ({ userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id)); } - await verifyApprovers({ + const approversValid = await isApproversValid({ projectId: accessApprovalPolicy.projectId, orgId: actorOrgId, envSlug: accessApprovalPolicy.environment.slug, secretPath: doc.secretPath!, actorAuthMethod, permissionService, - userIds: userApproverIds, - error: VerifyApproversError.BadRequestError + userIds: userApproverIds }); + if (!approversValid) { + throw new BadRequestError({ + message: "One or more approvers doesn't have access to be specified secret path" + }); + } + await accessApprovalPolicyApproverDAL.insertMany( userApproverIds.map((userId) => ({ approverUserId: userId, @@ -325,16 +334,22 @@ export const accessApprovalPolicyServiceFactory = ({ .filter((user) => user.isPartOfGroup) .map((user) => user.id); - await verifyApprovers({ + const approversValid = await isApproversValid({ projectId: accessApprovalPolicy.projectId, orgId: actorOrgId, envSlug: accessApprovalPolicy.environment.slug, secretPath: doc.secretPath!, actorAuthMethod, permissionService, - userIds: verifyGroupApprovers, - error: VerifyApproversError.BadRequestError + userIds: verifyGroupApprovers }); + + if (!approversValid) { + throw new BadRequestError({ + message: "One or more approvers doesn't have access to be specified secret path" + }); + } + await accessApprovalPolicyApproverDAL.insertMany( groupApprovers.map((groupId) => ({ approverGroupId: groupId, @@ -398,7 +413,9 @@ export const accessApprovalPolicyServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!membership) throw new NotFoundError({ message: "User not found in project" }); + if (!membership) { + throw new ForbiddenRequestError({ message: "You are not a member of this project" }); + } const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug }); if (!environment) throw new NotFoundError({ message: "Environment not found" }); diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-types.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-types.ts index 8a5290581..a42c89e7a 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-types.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-types.ts @@ -3,12 +3,7 @@ import { ActorAuthMethod } from "@app/services/auth/auth-type"; import { TPermissionServiceFactory } from "../permission/permission-service"; -export enum VerifyApproversError { - ForbiddenError = "ForbiddenError", - BadRequestError = "BadRequestError" -} - -export type TVerifyApprovers = { +export type TIsApproversValid = { userIds: string[]; permissionService: Pick; envSlug: string; @@ -16,7 +11,6 @@ export type TVerifyApprovers = { secretPath: string; projectId: string; orgId: string; - error: VerifyApproversError; }; export enum ApproverType { diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index 4fab994ed..7c1f00a37 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -17,8 +17,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal"; import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-policy/access-approval-policy-approver-dal"; import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal"; -import { verifyApprovers } from "../access-approval-policy/access-approval-policy-fns"; -import { VerifyApproversError } from "../access-approval-policy/access-approval-policy-types"; +import { isApproversValid } from "../access-approval-policy/access-approval-policy-fns"; import { TGroupDALFactory } from "../group/group-dal"; import { TPermissionServiceFactory } from "../permission/permission-service"; import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal"; @@ -100,7 +99,7 @@ export const accessApprovalRequestServiceFactory = ({ }: TCreateAccessApprovalRequestDTO) => { const cfg = getConfig(); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); - if (!project) throw new ForbiddenRequestError({ message: "Project not found" }); + if (!project) throw new NotFoundError({ message: "Project not found" }); // Anyone can create an access approval request. const { membership } = await permissionService.getProjectPermission( @@ -110,7 +109,9 @@ export const accessApprovalRequestServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!membership) throw new ForbiddenRequestError({ message: "You are not a member of this project" }); + if (!membership) { + throw new ForbiddenRequestError({ message: "You are not a member of this project" }); + } const requestedByUser = await userDAL.findById(actorId); if (!requestedByUser) throw new ForbiddenRequestError({ message: "User not found" }); @@ -272,7 +273,9 @@ export const accessApprovalRequestServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!membership) throw new NotFoundError({ message: "You don't have a membership for the specified project" }); + if (!membership) { + throw new ForbiddenRequestError({ message: "You are not a member of this project" }); + } const policies = await accessApprovalPolicyDAL.find({ projectId: project.id }); let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id)); @@ -308,7 +311,9 @@ export const accessApprovalRequestServiceFactory = ({ actorOrgId ); - if (!membership) throw new ForbiddenRequestError({ message: "You are not a member of this project" }); + if (!membership) { + throw new ForbiddenRequestError({ message: "You are not a member of this project" }); + } if ( !hasRole(ProjectMembershipRole.Admin) && @@ -320,17 +325,20 @@ export const accessApprovalRequestServiceFactory = ({ const reviewerProjectMembership = await projectMembershipDAL.findById(membership.id); - await verifyApprovers({ + const approversValid = await isApproversValid({ projectId: accessApprovalRequest.projectId, orgId: actorOrgId, envSlug: accessApprovalRequest.environment, secretPath: accessApprovalRequest.policy.secretPath!, actorAuthMethod, permissionService, - userIds: [reviewerProjectMembership.userId], - error: VerifyApproversError.ForbiddenError + userIds: [reviewerProjectMembership.userId] }); + if (!approversValid) { + throw new ForbiddenRequestError({ message: "You don't have access to approve this request" }); + } + const existingReviews = await accessApprovalRequestReviewerDAL.find({ requestId: accessApprovalRequest.id }); if (existingReviews.some((review) => review.status === ApprovalStatus.REJECTED)) { throw new BadRequestError({ message: "The request has already been rejected by another reviewer" }); @@ -422,7 +430,9 @@ export const accessApprovalRequestServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!membership) throw new NotFoundError({ message: "You don't have a membership for the specified project" }); + if (!membership) { + throw new ForbiddenRequestError({ message: "You are not a member of this project" }); + } const count = await accessApprovalRequestDAL.getCount({ projectId: project.id }); diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 3871f6b45..119ac6fca 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -116,7 +116,7 @@ export const permissionServiceFactory = ({ if (userOrgId && userOrgId !== orgId) throw new ForbiddenRequestError({ message: "Invalid user token. Scoped to different organization." }); const membership = await permissionDAL.getOrgPermission(userId, orgId); - if (!membership) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" }); + if (!membership) throw new ForbiddenRequestError({ name: "You are not apart of this organization" }); if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom organization permission not found" }); } @@ -143,7 +143,7 @@ export const permissionServiceFactory = ({ const getIdentityOrgPermission = async (identityId: string, orgId: string) => { const membership = await permissionDAL.getOrgIdentityPermission(identityId, orgId); - if (!membership) throw new ForbiddenRequestError({ name: "Identity is not a part of the specified organization" }); + if (!membership) throw new ForbiddenRequestError({ name: "Identity is not apart of this organization" }); if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { throw new NotFoundError({ name: "Custom organization permission not found" }); } diff --git a/backend/src/server/plugins/error-handler.ts b/backend/src/server/plugins/error-handler.ts index ee57a080d..8ea8b8223 100644 --- a/backend/src/server/plugins/error-handler.ts +++ b/backend/src/server/plugins/error-handler.ts @@ -19,28 +19,47 @@ enum JWTErrors { InvalidAlgorithm = "invalid algorithm" } +enum HttpStatusCodes { + BadRequest = 400, + NotFound = 404, + Unauthorized = 401, + Forbidden = 403, + // eslint-disable-next-line @typescript-eslint/no-shadow + InternalServerError = 500 +} + export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => { server.setErrorHandler((error, req, res) => { req.log.error(error); if (error instanceof BadRequestError) { - void res.status(400).send({ statusCode: 400, message: error.message, error: error.name }); + void res + .status(HttpStatusCodes.BadRequest) + .send({ statusCode: HttpStatusCodes.BadRequest, message: error.message, error: error.name }); } else if (error instanceof NotFoundError) { - void res.status(404).send({ statusCode: 404, message: error.message, error: error.name }); + void res + .status(HttpStatusCodes.NotFound) + .send({ statusCode: HttpStatusCodes.NotFound, message: error.message, error: error.name }); } else if (error instanceof UnauthorizedError) { - void res.status(401).send({ statusCode: 401, message: error.message, error: error.name }); + void res + .status(HttpStatusCodes.Unauthorized) + .send({ statusCode: HttpStatusCodes.Unauthorized, message: error.message, error: error.name }); } else if (error instanceof DatabaseError || error instanceof InternalServerError) { - void res.status(500).send({ statusCode: 500, message: "Something went wrong", error: error.name }); + void res + .status(HttpStatusCodes.InternalServerError) + .send({ statusCode: HttpStatusCodes.InternalServerError, message: "Something went wrong", error: error.name }); } else if (error instanceof ZodError) { - void res.status(401).send({ statusCode: 401, error: "ValidationFailure", message: error.issues }); + void res + .status(HttpStatusCodes.Unauthorized) + .send({ statusCode: HttpStatusCodes.Unauthorized, error: "ValidationFailure", message: error.issues }); } else if (error instanceof ForbiddenError) { - void res.status(403).send({ - statusCode: 403, + void res.status(HttpStatusCodes.Forbidden).send({ + statusCode: HttpStatusCodes.Forbidden, error: "PermissionDenied", message: `You are not allowed to ${error.action} on ${error.subjectType}` }); } else if (error instanceof ForbiddenRequestError) { - void res.status(403).send({ - statusCode: 403, + void res.status(HttpStatusCodes.Forbidden).send({ + statusCode: HttpStatusCodes.Forbidden, message: error.message, error: error.name }); @@ -66,8 +85,8 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider return error.message; })(); - void res.status(403).send({ - statusCode: 403, + void res.status(HttpStatusCodes.Forbidden).send({ + statusCode: HttpStatusCodes.Forbidden, error: "TokenError", message }); diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 54dc9515c..aa53ed50e 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -40,12 +40,12 @@ export const DefaultResponseErrorsSchema = { }), 401: z.object({ statusCode: z.literal(401), - message: z.string(), + message: z.any(), error: z.string() }), 403: z.object({ statusCode: z.literal(403), - message: z.any(), + message: z.string(), error: z.string() }), 500: z.object({ diff --git a/backend/src/services/identity-access-token/identity-access-token-service.ts b/backend/src/services/identity-access-token/identity-access-token-service.ts index 4825773fb..c625c1407 100644 --- a/backend/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend/src/services/identity-access-token/identity-access-token-service.ts @@ -2,7 +2,7 @@ import jwt, { JwtPayload } from "jsonwebtoken"; import { TableName, TIdentityAccessTokens } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors"; +import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip"; import { TAccessTokenQueueServiceFactory } from "../access-token-queue/access-token-queue"; @@ -39,7 +39,7 @@ export const identityAccessTokenServiceFactory = ({ if (accessTokenNumUsesLimit > 0 && accessTokenNumUses > 0 && accessTokenNumUses >= accessTokenNumUsesLimit) { await identityAccessTokenDAL.deleteById(tokenId); - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Unable to renew because access token number of uses limit reached" }); } @@ -55,7 +55,7 @@ export const identityAccessTokenServiceFactory = ({ if (currentDate > expirationDate) { await identityAccessTokenDAL.deleteById(tokenId); - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Failed to renew MI access token due to TTL expiration" }); } @@ -67,7 +67,7 @@ export const identityAccessTokenServiceFactory = ({ if (currentDate > expirationDate) { await identityAccessTokenDAL.deleteById(tokenId); - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Failed to renew MI access token due to TTL expiration" }); } @@ -82,7 +82,7 @@ export const identityAccessTokenServiceFactory = ({ identityAccessTokenId: string; }; if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) { - throw new ForbiddenRequestError({ message: "Only identity access tokens can be renewed" }); + throw new BadRequestError({ message: "Only identity access tokens can be renewed" }); } const identityAccessToken = await identityAccessTokenDAL.findOne({ @@ -109,7 +109,7 @@ export const identityAccessTokenServiceFactory = ({ if (currentDate > expirationDate) { await identityAccessTokenDAL.deleteById(identityAccessToken.id); - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Failed to renew MI access token due to Max TTL expiration" }); } @@ -117,7 +117,7 @@ export const identityAccessTokenServiceFactory = ({ const extendToDate = new Date(currentDate.getTime() + Number(accessTokenTTL * 1000)); if (extendToDate > expirationDate) { await identityAccessTokenDAL.deleteById(identityAccessToken.id); - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Failed to renew MI access token past its Max TTL expiration" }); } @@ -137,7 +137,7 @@ export const identityAccessTokenServiceFactory = ({ identityAccessTokenId: string; }; if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) { - throw new ForbiddenRequestError({ message: "Only identity access tokens can be revoked" }); + throw new UnauthorizedError({ message: "Only identity access tokens can be revoked" }); } const identityAccessToken = await identityAccessTokenDAL.findOne({ @@ -160,7 +160,7 @@ export const identityAccessTokenServiceFactory = ({ }); if (!identityAccessToken) throw new UnauthorizedError({ message: "No identity access token found" }); if (identityAccessToken.isAccessTokenRevoked) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Failed to authorize revoked access token, access token is revoked" }); diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts index 620c1338b..6ef37eba3 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts @@ -9,7 +9,7 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/pe import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; -import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; @@ -81,7 +81,7 @@ export const identityAwsAuthServiceFactory = ({ .some((accountId) => accountId === Account); if (!isAccountAllowed) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: AWS account ID not allowed." }); } @@ -100,7 +100,7 @@ export const identityAwsAuthServiceFactory = ({ }); if (!isArnAllowed) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: AWS principal ARN not allowed." }); } diff --git a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts index f93eec2c2..cbda504db 100644 --- a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts +++ b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts @@ -73,7 +73,7 @@ export const identityAzureAuthServiceFactory = ({ .map((servicePrincipalId) => servicePrincipalId.trim()) .some((servicePrincipalId) => servicePrincipalId === azureIdentity.oid); - if (!isServicePrincipalAllowed) throw new ForbiddenRequestError({ message: "Service principal not allowed" }); + if (!isServicePrincipalAllowed) throw new UnauthorizedError({ message: "Service principal not allowed" }); } const identityAccessToken = await identityAzureAuthDAL.transaction(async (tx) => { @@ -314,8 +314,7 @@ export const identityAzureAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) + if (!isAtLeastAsPrivileged(permission, rolePermission)) throw new ForbiddenRequestError({ message: "Failed to revoke azure auth of identity with more privileged role" }); diff --git a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts index 2f666b821..14e4dcd3f 100644 --- a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts +++ b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts @@ -86,7 +86,7 @@ export const identityGcpAuthServiceFactory = ({ .some((serviceAccount) => serviceAccount === gcpIdentityDetails.email); if (!isServiceAccountAllowed) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: GCP service account not allowed." }); } @@ -100,7 +100,7 @@ export const identityGcpAuthServiceFactory = ({ .some((project) => project === gcpIdentityDetails.computeEngineDetails?.project_id); if (!isProjectAllowed) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: GCP project not allowed." }); } @@ -112,7 +112,7 @@ export const identityGcpAuthServiceFactory = ({ .some((zone) => zone === gcpIdentityDetails.computeEngineDetails?.zone); if (!isZoneAllowed) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: GCP zone not allowed." }); } @@ -359,8 +359,7 @@ export const identityGcpAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) + if (!isAtLeastAsPrivileged(permission, rolePermission)) throw new ForbiddenRequestError({ message: "Failed to revoke gcp auth of identity with more privileged role" }); diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 72f55b0e5..de7b7fd04 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -132,7 +132,7 @@ export const identityKubernetesAuthServiceFactory = ({ // check the response to determine if the token is valid if (!(data.status && data.status.authenticated)) - throw new ForbiddenRequestError({ message: "Kubernetes token not authenticated" }); + throw new UnauthorizedError({ message: "Kubernetes token not authenticated" }); const { namespace: targetNamespace, name: targetName } = extractK8sUsername(data.status.user.username); @@ -145,7 +145,7 @@ export const identityKubernetesAuthServiceFactory = ({ .some((namespace) => namespace === targetNamespace); if (!isNamespaceAllowed) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: K8s namespace not allowed." }); } @@ -159,7 +159,7 @@ export const identityKubernetesAuthServiceFactory = ({ .some((name) => name === targetName); if (!isNameAllowed) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: K8s name not allowed." }); } @@ -171,7 +171,7 @@ export const identityKubernetesAuthServiceFactory = ({ ); if (!isAudienceAllowed) - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: K8s audience not allowed." }); } diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index 1741074b5..7c481f788 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -148,7 +148,7 @@ export const identityOidcAuthServiceFactory = ({ .split(", ") .some((policyValue) => doesFieldValueMatchOidcPolicy(tokenData.aud, policyValue)) ) { - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: OIDC audience not allowed." }); } @@ -161,7 +161,7 @@ export const identityOidcAuthServiceFactory = ({ if ( !claimValue.split(", ").some((claimEntry) => doesFieldValueMatchOidcPolicy(tokenData[claimKey], claimEntry)) ) { - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied: OIDC claim not allowed." }); } @@ -532,8 +532,7 @@ export const identityOidcAuthServiceFactory = ({ actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) { + if (!isAtLeastAsPrivileged(permission, rolePermission)) { throw new ForbiddenRequestError({ message: "Failed to revoke OIDC auth of identity with more privileged role" }); diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index b153d9e22..757afa4d2 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -88,7 +88,7 @@ export const identityUaServiceFactory = ({ isClientSecretRevoked: true }); - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied due to expired client secret" }); } @@ -100,7 +100,7 @@ export const identityUaServiceFactory = ({ await identityUaClientSecretDAL.updateById(validClientSecretInfo.id, { isClientSecretRevoked: true }); - throw new ForbiddenRequestError({ + throw new UnauthorizedError({ message: "Access denied due to client secret usage limit reached" }); } @@ -368,8 +368,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) + if (!isAtLeastAsPrivileged(permission, rolePermission)) throw new ForbiddenRequestError({ message: "Failed to revoke universal auth of identity with more privileged role" }); @@ -474,8 +473,8 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) + + if (!isAtLeastAsPrivileged(permission, rolePermission)) throw new ForbiddenRequestError({ message: "Failed to add identity to project with more privileged role" }); @@ -521,8 +520,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) + if (!isAtLeastAsPrivileged(permission, rolePermission)) throw new ForbiddenRequestError({ message: "Failed to read identity client secret of project with more privileged role" }); @@ -561,8 +559,8 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) + + if (!isAtLeastAsPrivileged(permission, rolePermission)) throw new ForbiddenRequestError({ message: "Failed to revoke identity client secret with more privileged role" }); diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index c80cbba67..faa3adba6 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -892,7 +892,7 @@ export const orgServiceFactory = ({ const membership = await orgMembershipDAL.findOrgMembershipById(membershipId); if (!membership) { - throw new NotFoundError({ message: "Failed to find organization membership" }); + throw new NotFoundError({ message: "Organization membership not found" }); } if (membership.orgId !== orgId) { throw new ForbiddenRequestError({ message: "Membership does not belong to organization" }); @@ -937,7 +937,9 @@ export const orgServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); const membership = await orgMembershipDAL.findOrgMembershipById(orgMembershipId); - if (!membership) throw new NotFoundError({ message: "Failed to find organization membership" }); + if (!membership) { + throw new NotFoundError({ message: "Organization membership not found" }); + } if (membership.orgId !== orgId) throw new NotFoundError({ message: "Failed to find organization membership" }); const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, membership.user.id); diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index a39d42ebe..863cf24c2 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -909,9 +909,7 @@ export const projectServiceFactory = ({ ); if (!membership) { - throw new ForbiddenRequestError({ - message: "User is not a member of the project" - }); + throw new ForbiddenRequestError({ message: "You are not a member of this project" }); } const kmsKeyId = await kmsService.getProjectSecretManagerKmsKeyId(projectId); diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 1487d9380..5ecbcf54d 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -6,7 +6,7 @@ import bcrypt from "bcrypt"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; -import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { TAccessTokenQueueServiceFactory } from "../access-token-queue/access-token-queue"; import { ActorType } from "../auth/auth-type"; @@ -168,7 +168,7 @@ export const serviceTokenServiceFactory = ({ } const isMatch = await bcrypt.compare(tokenSecret, serviceToken.secretHash); - if (!isMatch) throw new ForbiddenRequestError(); + if (!isMatch) throw new UnauthorizedError({ message: "Invalid service token" }); await accessTokenQueue.updateServiceTokenStatus(serviceToken.id); return { ...serviceToken, lastUsed: new Date(), orgId: project.orgId };