From 2501c57030d50ea4bd099cd05250057e76ff1fe4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 13 Aug 2025 02:06:01 +0400 Subject: [PATCH 1/9] feat(approvals): visualization of deprovisioned scim users --- .../v1/access-approval-request-router.ts | 2 + .../v1/secret-approval-request-router.ts | 7 +- .../access-approval-request-dal.ts | 53 ++++++- .../access-approval-request-types.ts | 6 + .../src/ee/services/license/license-fns.ts | 8 +- .../secret-approval-request-dal.ts | 51 ++++++- .../secret-approval-request-service.ts | 1 + .../src/server/routes/v1/project-router.ts | 6 +- .../group-project/group-project-dal.ts | 9 +- .../project-membership-dal.ts | 18 ++- .../project-membership-service.ts | 1 - .../src/hooks/api/accessApproval/types.ts | 7 +- .../src/hooks/api/secretApproval/types.ts | 5 +- .../hooks/api/secretApprovalRequest/types.ts | 2 + frontend/src/hooks/api/users/types.ts | 1 + .../components/ReviewAccessModal.tsx | 68 +++++++-- .../components/ApprovalPolicyRow.tsx | 31 ++++- .../SecretApprovalRequestChanges.tsx | 131 ++++++++++++++---- 18 files changed, 332 insertions(+), 75 deletions(-) diff --git a/backend/src/ee/routes/v1/access-approval-request-router.ts b/backend/src/ee/routes/v1/access-approval-request-router.ts index 7a70d6374..a7e8e79cc 100644 --- a/backend/src/ee/routes/v1/access-approval-request-router.ts +++ b/backend/src/ee/routes/v1/access-approval-request-router.ts @@ -116,6 +116,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv approvals: z.number(), approvers: z .object({ + isOrgMembershipActive: z.boolean(), userId: z.string().nullable().optional(), sequence: z.number().nullable().optional(), approvalsRequired: z.number().nullable().optional(), @@ -132,6 +133,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv }), reviewers: z .object({ + isOrgMembershipActive: z.boolean(), userId: z.string(), status: z.string() }) diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index 3d07af562..956baef59 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -294,12 +294,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv 200: z.object({ approval: SecretApprovalRequestsSchema.merge( z.object({ - // secretPath: z.string(), policy: z.object({ id: z.string(), name: z.string(), approvals: z.number(), - approvers: approvalRequestUser.array(), + approvers: approvalRequestUser.extend({ isOrgMembershipActive: z.boolean() }).array(), bypassers: approvalRequestUser.array(), secretPath: z.string().optional().nullable(), enforcementLevel: z.string(), @@ -309,7 +308,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv environment: z.string(), statusChangedByUser: approvalRequestUser.optional(), committerUser: approvalRequestUser.nullish(), - reviewers: approvalRequestUser.extend({ status: z.string(), comment: z.string().optional() }).array(), + reviewers: approvalRequestUser + .extend({ status: z.string(), comment: z.string().optional(), isOrgMembershipActive: z.boolean() }) + .array(), secretPath: z.string(), commits: secretRawSchema .omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true }) diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts b/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts index 9872df067..f930ceaf7 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts @@ -5,6 +5,7 @@ import { AccessApprovalRequestsSchema, TableName, TAccessApprovalRequests, + TOrgMemberships, TUserGroupMembership, TUsers } from "@app/db/schemas"; @@ -143,6 +144,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit( + db(TableName.OrgMembership).as("approverOrgMembership"), + `${TableName.AccessApprovalPolicyApprover}.approverUserId`, + `approverOrgMembership.userId` + ) + + .leftJoin( + db(TableName.OrgMembership).as("approverGroupOrgMembership"), + `${TableName.Users}.id`, + `approverGroupOrgMembership.userId` + ) + + .leftJoin( + db(TableName.OrgMembership).as("reviewerOrgMembership"), + `${TableName.AccessApprovalRequestReviewer}.reviewerUserId`, + `reviewerOrgMembership.userId` + ) + .leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`) .select(selectAllTableCols(TableName.AccessApprovalRequest)) @@ -297,7 +322,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"), db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"), db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"), - db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt") + db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt"), + + // Added: + db.ref("isActive").withSchema("approverOrgMembership").as("approverIsOrgMembershipActive"), + db.ref("isActive").withSchema("approverGroupOrgMembership").as("approverGroupIsOrgMembershipActive"), + db.ref("isActive").withSchema("reviewerOrgMembership").as("reviewerIsOrgMembershipActive") ) .select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover)) .select(db.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence")) @@ -392,17 +422,26 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR { key: "reviewerUserId", label: "reviewers" as const, - mapper: ({ reviewerUserId: userId, reviewerStatus: status }) => (userId ? { userId, status } : undefined) + mapper: ({ reviewerUserId: userId, reviewerStatus: status, reviewerIsOrgMembershipActive }) => + userId ? { userId, status, isOrgMembershipActive: reviewerIsOrgMembershipActive } : undefined }, { key: "approverUserId", label: "approvers" as const, - mapper: ({ approverUserId, approverSequence, approvalsRequired, approverUsername, approverEmail }) => ({ + mapper: ({ + approverUserId, + approverSequence, + approvalsRequired, + approverUsername, + approverEmail, + approverIsOrgMembershipActive + }) => ({ userId: approverUserId, sequence: approverSequence, approvalsRequired, email: approverEmail, - username: approverUsername + username: approverUsername, + isOrgMembershipActive: approverIsOrgMembershipActive }) }, { @@ -413,13 +452,15 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR approverSequence, approvalsRequired, approverGroupEmail, - approverGroupUsername + approverGroupUsername, + approverGroupIsOrgMembershipActive }) => ({ userId: approverGroupUserId, sequence: approverSequence, approvalsRequired, email: approverGroupEmail, - username: approverGroupUsername + username: approverGroupUsername, + isOrgMembershipActive: approverGroupIsOrgMembershipActive }) }, { key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId }, diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-types.ts b/backend/src/ee/services/access-approval-request/access-approval-request-types.ts index 9066aec8f..0d318c6c4 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-types.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-types.ts @@ -64,6 +64,7 @@ export interface TAccessApprovalRequestServiceFactory { approvalsRequired: number | null | undefined; email: string | null | undefined; username: string; + isOrgMembershipActive: boolean; } | { userId: string; @@ -71,6 +72,7 @@ export interface TAccessApprovalRequestServiceFactory { approvalsRequired: number | null | undefined; email: string | null | undefined; username: string; + isOrgMembershipActive: boolean; } )[]; bypassers: string[]; @@ -82,6 +84,7 @@ export interface TAccessApprovalRequestServiceFactory { allowedSelfApprovals: boolean; envId: string; deletedAt: Date | null | undefined; + isOrgMembershipActive: boolean; }; projectId: string; environment: string; @@ -121,6 +124,7 @@ export interface TAccessApprovalRequestServiceFactory { reviewers: { userId: string; status: string; + isOrgMembershipActive: boolean; }[]; approvers: ( | { @@ -129,6 +133,7 @@ export interface TAccessApprovalRequestServiceFactory { approvalsRequired: number | null | undefined; email: string | null | undefined; username: string; + isOrgMembershipActive: boolean; } | { userId: string; @@ -136,6 +141,7 @@ export interface TAccessApprovalRequestServiceFactory { approvalsRequired: number | null | undefined; email: string | null | undefined; username: string; + isOrgMembershipActive: boolean; } )[]; bypassers: string[]; diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index bd3949a7e..be300c0e4 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -31,16 +31,16 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ auditLogsRetentionDays: 0, auditLogStreams: false, auditLogStreamLimit: 3, - samlSSO: false, + samlSSO: true, hsm: false, oidcSSO: false, - scim: false, + scim: true, ldap: false, - groups: false, + groups: true, status: null, trial_end: null, has_used_trial: true, - secretApproval: false, + secretApproval: true, secretRotation: false, caCrl: false, instanceUserManagement: false, diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts index 49f31bdf6..49512768c 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts @@ -4,6 +4,7 @@ import { TDbClient } from "@app/db"; import { SecretApprovalRequestsSchema, TableName, + TOrgMemberships, TSecretApprovalRequests, TSecretApprovalRequestsSecrets, TUserGroupMembership, @@ -107,11 +108,32 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { `${TableName.SecretApprovalRequestReviewer}.reviewerUserId`, `secretApprovalReviewerUser.id` ) + + .leftJoin( + db(TableName.OrgMembership).as("approverOrgMembership"), + `${TableName.SecretApprovalPolicyApprover}.approverUserId`, + `approverOrgMembership.userId` + ) + + .leftJoin( + db(TableName.OrgMembership).as("approverGroupOrgMembership"), + `secretApprovalPolicyGroupApproverUser.id`, + `approverGroupOrgMembership.userId` + ) + + .leftJoin( + db(TableName.OrgMembership).as("reviewerOrgMembership"), + `${TableName.SecretApprovalRequestReviewer}.reviewerUserId`, + `reviewerOrgMembership.userId` + ) + .select(selectAllTableCols(TableName.SecretApprovalRequest)) .select( tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover), tx.ref("userId").withSchema("approverUserGroupMembership").as("approverGroupUserId"), tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"), + tx.ref("isActive").withSchema("approverOrgMembership").as("approverIsOrgMembershipActive"), + tx.ref("isActive").withSchema("approverGroupOrgMembership").as("approverGroupIsOrgMembershipActive"), tx.ref("email").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupEmail"), tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"), tx.ref("username").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupUsername"), @@ -148,6 +170,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { tx.ref("username").withSchema("secretApprovalReviewerUser").as("reviewerUsername"), tx.ref("firstName").withSchema("secretApprovalReviewerUser").as("reviewerFirstName"), tx.ref("lastName").withSchema("secretApprovalReviewerUser").as("reviewerLastName"), + tx.ref("isActive").withSchema("reviewerOrgMembership").as("reviewerIsOrgMembershipActive"), tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"), tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"), tx.ref("projectId").withSchema(TableName.Environment), @@ -211,9 +234,21 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { reviewerLastName: lastName, reviewerUsername: username, reviewerFirstName: firstName, - reviewerComment: comment + reviewerComment: comment, + reviewerIsOrgMembershipActive: isOrgMembershipActive }) => - userId ? { userId, status, email, firstName, lastName, username, comment: comment ?? "" } : undefined + userId + ? { + userId, + status, + email, + firstName, + lastName, + username, + comment: comment ?? "", + isOrgMembershipActive + } + : undefined }, { key: "approverUserId", @@ -223,13 +258,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { approverEmail: email, approverUsername: username, approverLastName: lastName, - approverFirstName: firstName + approverFirstName: firstName, + approverIsOrgMembershipActive: isOrgMembershipActive }) => ({ userId, email, firstName, lastName, - username + username, + isOrgMembershipActive }) }, { @@ -240,13 +277,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { approverGroupEmail: email, approverGroupUsername: username, approverGroupLastName: lastName, - approverGroupFirstName: firstName + approverGroupFirstName: firstName, + approverGroupIsOrgMembershipActive: isOrgMembershipActive }) => ({ userId, email, firstName, lastName, - username + username, + isOrgMembershipActive }) }, { diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index 1d1ca0a00..2cdeeb094 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -258,6 +258,7 @@ export const secretApprovalRequestServiceFactory = ({ if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); const secretApprovalRequest = await secretApprovalRequestDAL.findById(id); + if (!secretApprovalRequest) throw new NotFoundError({ message: `Secret approval request with ID '${id}' not found` }); diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 7a5a9341f..5e7dce76e 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -108,7 +108,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { firstName: true, lastName: true, id: true - }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + }) + .merge(UserEncryptionKeysSchema.pick({ publicKey: true })) + .extend({ + isOrgMembershipActive: z.boolean() + }), project: SanitizedProjectSchema.pick({ name: true, id: true }), roles: z.array( z.object({ diff --git a/backend/src/services/group-project/group-project-dal.ts b/backend/src/services/group-project/group-project-dal.ts index a776f7245..263838a9d 100644 --- a/backend/src/services/group-project/group-project-dal.ts +++ b/backend/src/services/group-project/group-project-dal.ts @@ -156,6 +156,7 @@ export const groupProjectDALFactory = (db: TDbClient) => { `${TableName.GroupProjectMembershipRole}.customRoleId`, `${TableName.ProjectRoles}.id` ) + .join(TableName.OrgMembership, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`) .select( db.ref("id").withSchema(TableName.UserGroupMembership), db.ref("createdAt").withSchema(TableName.UserGroupMembership), @@ -176,7 +177,8 @@ export const groupProjectDALFactory = (db: TDbClient) => { db.ref("temporaryRange").withSchema(TableName.GroupProjectMembershipRole), db.ref("temporaryAccessStartTime").withSchema(TableName.GroupProjectMembershipRole), db.ref("temporaryAccessEndTime").withSchema(TableName.GroupProjectMembershipRole), - db.ref("name").as("projectName").withSchema(TableName.Project) + db.ref("name").as("projectName").withSchema(TableName.Project), + db.ref("isActive").withSchema(TableName.OrgMembership) ) .where({ isGhost: false }); @@ -192,7 +194,8 @@ export const groupProjectDALFactory = (db: TDbClient) => { id, userId, projectName, - createdAt + createdAt, + isActive }) => ({ isGroupMember: true, id, @@ -202,7 +205,7 @@ export const groupProjectDALFactory = (db: TDbClient) => { id: projectId, name: projectName }, - user: { email, username, firstName, lastName, id: userId, publicKey, isGhost }, + user: { email, username, firstName, lastName, id: userId, publicKey, isGhost, isOrgMembershipActive: isActive }, createdAt }), key: "id", diff --git a/backend/src/services/project-membership/project-membership-dal.ts b/backend/src/services/project-membership/project-membership-dal.ts index 7dfd1f65c..9bc7e9aeb 100644 --- a/backend/src/services/project-membership/project-membership-dal.ts +++ b/backend/src/services/project-membership/project-membership-dal.ts @@ -21,6 +21,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => { .where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId }) .join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`) .join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`) + .join(TableName.OrgMembership, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`) .where((qb) => { if (filter.usernames) { void qb.whereIn("username", filter.usernames); @@ -90,7 +91,8 @@ export const projectMembershipDALFactory = (db: TDbClient) => { db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole), db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole), db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole), - db.ref("name").as("projectName").withSchema(TableName.Project) + db.ref("name").as("projectName").withSchema(TableName.Project), + db.ref("isActive").withSchema(TableName.OrgMembership) ) .where({ isGhost: false }) .orderBy(`${TableName.Users}.username` as "username"); @@ -107,12 +109,22 @@ export const projectMembershipDALFactory = (db: TDbClient) => { id, userId, projectName, - createdAt + createdAt, + isActive }) => ({ id, userId, projectId, - user: { email, username, firstName, lastName, id: userId, publicKey, isGhost }, + user: { + email, + username, + firstName, + lastName, + id: userId, + publicKey, + isGhost, + isOrgMembershipActive: isActive + }, project: { id: projectId, name: projectName diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index b9e502922..7cf665141 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -97,7 +97,6 @@ export const projectMembershipServiceFactory = ({ const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId, { roles }); - // projectMembers[0].project if (includeGroupMembers) { const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId); const allMembers = [ diff --git a/frontend/src/hooks/api/accessApproval/types.ts b/frontend/src/hooks/api/accessApproval/types.ts index bc569165b..dde4ed70e 100644 --- a/frontend/src/hooks/api/accessApproval/types.ts +++ b/frontend/src/hooks/api/accessApproval/types.ts @@ -35,6 +35,7 @@ export type Approver = { type: ApproverType; sequence?: number; approvalsRequired?: number; + isOrgMembershipActive: boolean; }; export type Bypasser = { @@ -81,6 +82,7 @@ export type TAccessApprovalRequest = { name: string; approvals: number; approvers: { + isOrgMembershipActive: boolean; userId: string; sequence?: number; approvalsRequired?: number; @@ -96,6 +98,7 @@ export type TAccessApprovalRequest = { }; reviewers: { + isOrgMembershipActive: boolean; userId: string; status: string; }[]; @@ -166,7 +169,7 @@ export type TCreateAccessPolicyDTO = { projectSlug: string; name?: string; environments: string[]; - approvers?: Approver[]; + approvers?: Omit[]; bypassers?: Bypasser[]; approvals?: number; secretPath: string; @@ -178,7 +181,7 @@ export type TCreateAccessPolicyDTO = { export type TUpdateAccessPolicyDTO = { id: string; name?: string; - approvers?: Approver[]; + approvers?: Omit[]; bypassers?: Bypasser[]; secretPath?: string; environments?: string[]; diff --git a/frontend/src/hooks/api/secretApproval/types.ts b/frontend/src/hooks/api/secretApproval/types.ts index 8fd86624d..1785ffa8a 100644 --- a/frontend/src/hooks/api/secretApproval/types.ts +++ b/frontend/src/hooks/api/secretApproval/types.ts @@ -20,6 +20,7 @@ export enum ApproverType { } export type Approver = { + isOrgMembershipActive: boolean; id: string; type: ApproverType; }; @@ -49,7 +50,7 @@ export type TCreateSecretPolicyDTO = { name?: string; environments: string[]; secretPath: string; - approvers?: Approver[]; + approvers?: Omit[]; bypassers?: Bypasser[]; approvals?: number; enforcementLevel: EnforcementLevel; @@ -59,7 +60,7 @@ export type TCreateSecretPolicyDTO = { export type TUpdateSecretPolicyDTO = { id: string; name?: string; - approvers?: Approver[]; + approvers?: Omit[]; bypassers?: Bypasser[]; secretPath?: string; approvals?: number; diff --git a/frontend/src/hooks/api/secretApprovalRequest/types.ts b/frontend/src/hooks/api/secretApprovalRequest/types.ts index caf1df320..0eef3fa3d 100644 --- a/frontend/src/hooks/api/secretApprovalRequest/types.ts +++ b/frontend/src/hooks/api/secretApprovalRequest/types.ts @@ -53,6 +53,7 @@ export type TSecretApprovalRequest = { firstName: string; lastName: string; username: string; + isOrgMembershipActive: boolean; }[]; workspace: string; environment: string; @@ -62,6 +63,7 @@ export type TSecretApprovalRequest = { status: "open" | "close"; policy: Omit & { approvers: { + isOrgMembershipActive: boolean; userId: string; email: string; firstName: string; diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 3af283b7e..0a43e850e 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -83,6 +83,7 @@ export type TProjectMembership = { export type TWorkspaceUser = { id: string; user: { + isOrgMembershipActive: boolean; email: string; username: string; firstName: string; diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx index e9e9c21c4..3e46f59de 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx @@ -3,7 +3,10 @@ import { faBan, faCheck, faHourglass, - faTriangleExclamation + faQuestionCircle, + faTriangleExclamation, + faUser, + faUserSlash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import ms from "ms"; @@ -33,7 +36,7 @@ import { EnforcementLevel } from "@app/hooks/api/policies/enums"; import { ApprovalStatus, TWorkspaceUser } from "@app/hooks/api/types"; import { groupBy } from "@app/lib/fn/array"; -const getReviewedStatusSymbol = (status?: ApprovalStatus) => { +const getReviewedStatusSymbol = (status?: ApprovalStatus, isOrgMembershipActive?: boolean) => { if (status === ApprovalStatus.APPROVED) return ( @@ -46,6 +49,16 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus) => { ); + + if (!isOrgMembershipActive) { + return ( + // Can't do a tooltip here because nested tooltips doesn't work properly as of yet. + // TODO(daniel): Fix nested tooltips in the future. + + + + ); + } return ( @@ -81,6 +94,7 @@ export const ReviewAccessRequestModal = ({ }) => { const [isLoading, setIsLoading] = useState<"approved" | "rejected" | null>(null); const [bypassApproval, setBypassApproval] = useState(false); + const [bypassReason, setBypassReason] = useState(""); const { currentWorkspace } = useWorkspace(); const { data: groupMemberships = [] } = useListWorkspaceGroups(currentWorkspace?.id || ""); @@ -215,7 +229,10 @@ export const ReviewAccessRequestModal = ({ const approvers = approversBySequence?.map((approverChain) => { const reviewers = request.policy.approvers .filter((el) => (el.sequence || 1) === approverChain.sequence) - .map((el) => ({ ...el, status: reviewesGroupById?.[el.userId]?.[0]?.status })); + .map((el) => ({ + ...el, + status: reviewesGroupById?.[el.userId]?.[0]?.status + })); const hasApproved = reviewers.filter((el) => el.status === "approved").length >= (approverChain?.approvals || 1); @@ -383,12 +400,31 @@ export const ReviewAccessRequestModal = ({ )}
- - {approver?.user - ?.map( - (el) => approverSequence?.membersGroupById?.[el.id]?.[0]?.user?.username - ) - .join(", ")} + + {Boolean(approver.user.length) && ( +
+ {approver?.user?.map((el) => { + const member = approverSequence?.membersGroupById?.[el.id]?.[0]; + if (!member) return null; + + return member.user.isOrgMembershipActive ? ( + {member.user.username} + ) : ( + + {member.user.username}{" "} + + +
+ (Inactive){" "} + +
+
+
+
+ ); + })} +
+ )}
{approver?.group @@ -413,8 +449,18 @@ export const ReviewAccessRequestModal = ({ key={`reviewer-${idx + 1}`} className="flex items-center gap-2 px-2 py-2 text-sm" > -
{el.username}
- {getReviewedStatusSymbol(el?.status as ApprovalStatus)} +
+ {el.username} +
+ {getReviewedStatusSymbol( + el?.status as ApprovalStatus, + el.isOrgMembershipActive + )}
))} diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx index b19ace7ea..8e25397dc 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx @@ -3,6 +3,7 @@ import { faClipboardCheck, faEdit, faEllipsisV, + faQuestionCircle, faTrash, faUser, faUserGroup @@ -19,6 +20,7 @@ import { GenericFieldLabel, IconButton, Td, + Tooltip, Tr } from "@app/components/v2"; import { Badge } from "@app/components/v2/Badge"; @@ -86,10 +88,9 @@ export const ApprovalPolicyRow = ({ return entityInSameSequence?.map((el) => { return { sequence: el.sequence || policy.approvals, - userLabels: members - ?.filter((member) => el.user.find((i) => i.id === member.user.id)) - .map((member) => getMemberLabel(member)) - .join(", "), + + users: members.filter((member) => el.user.find((i) => i.id === member.user.id)), + groupLabels: groups ?.filter(({ group }) => el.group.find((i) => i.id === group.id)) .map(({ group }) => group.name) @@ -212,7 +213,27 @@ export const ApprovalPolicyRow = ({ )}
- {el.userLabels} + {Boolean(el.users.length) && ( +
+ {el.users.map((u, i) => { + return u.user.isOrgMembershipActive ? ( + {getMemberLabel(u)} + ) : ( + + {getMemberLabel(u)}{" "} + + +
+ (Inactive){" "} + +
+
+
+
+ ); + })} +
+ )}
{el.groupLabels} diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index 0f2b00299..f7090f43b 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -8,7 +8,8 @@ import { faCodeBranch, faComment, faFolder, - faHourglass + faHourglass, + faUserSlash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -85,6 +86,7 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus) => { return ; if (status === ApprovalStatus.REJECTED) return ; + return ; }; @@ -162,15 +164,21 @@ export const SecretApprovalRequestChanges = ({ secretApprovalRequestDetails.policy.bypassers.some(({ userId }) => userId === userSession.id); const reviewedUsers = secretApprovalRequestDetails?.reviewers?.reduce< - Record + Record >( (prev, curr) => ({ ...prev, - [curr.userId]: { status: curr.status, comment: curr.comment } + [curr.userId]: { + status: curr.status, + comment: curr.comment, + isOrgMembershipActive: curr.isOrgMembershipActive + } }), {} ); + console.log("reviewedUsers", reviewedUsers); + const handleSecretApprovalStatusUpdate = async (status: ApprovalStatus, comment: string) => { try { await updateSecretApprovalRequestStatus({ @@ -524,7 +532,7 @@ export const SecretApprovalRequestChanges = ({
-
Reviewers
+
Reviewerssss
{secretApprovalRequestDetails?.policy?.approvers .filter( @@ -533,26 +541,50 @@ export const SecretApprovalRequestChanges = ({ ) .map((requiredApprover) => { const reviewer = reviewedUsers?.[requiredApprover.userId]; + const isOrgMembershipActive = requiredApprover.isOrgMembershipActive; + + console.log("isOrgMembershipActive", isOrgMembershipActive); + return (
- -
-
{requiredApprover?.email}
- * -
-
-
+ +
+
{requiredApprover?.email}
+ * +
+
+ + {!isOrgMembershipActive && ( + + + + )} +
+
{reviewer?.comment && ( )} - - {getReviewedStatusSymbol(reviewer?.status)} - +
+ + Status:{" "} + + {reviewer?.status || ApprovalStatus.PENDING} + + + } + > + {getReviewedStatusSymbol(reviewer?.status)} + +
); @@ -578,20 +622,43 @@ export const SecretApprovalRequestChanges = ({ ) .map((reviewer) => { const status = reviewedUsers?.[reviewer.userId].status; + const isOrgMembershipActive = reviewer.isOrgMembershipActive; return (
-
- - {reviewer?.email} +
+ +
+ {reviewer?.email} +
- * + {!isOrgMembershipActive && ( + + + + )}
+
{reviewer.comment && ( - + )} - + + Status:{" "} + {status || ApprovalStatus.PENDING} + + } + > {getReviewedStatusSymbol(status)}
From c8f124e4c5cd52e98b644659d2b2fdbfc8e044d6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 13 Aug 2025 02:19:22 +0400 Subject: [PATCH 2/9] fix: failing tests --- .../access-approval-request-types.ts | 1 - backend/src/ee/services/license/license-fns.ts | 8 ++++---- .../ApprovalPolicyList/components/AccessPolicyModal.tsx | 8 ++++++-- .../ApprovalPolicyList/components/ApprovalPolicyRow.tsx | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-types.ts b/backend/src/ee/services/access-approval-request/access-approval-request-types.ts index 0d318c6c4..d56c64bfd 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-types.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-types.ts @@ -84,7 +84,6 @@ export interface TAccessApprovalRequestServiceFactory { allowedSelfApprovals: boolean; envId: string; deletedAt: Date | null | undefined; - isOrgMembershipActive: boolean; }; projectId: string; environment: string; diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index be300c0e4..bd3949a7e 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -31,16 +31,16 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ auditLogsRetentionDays: 0, auditLogStreams: false, auditLogStreamLimit: 3, - samlSSO: true, + samlSSO: false, hsm: false, oidcSSO: false, - scim: true, + scim: false, ldap: false, - groups: true, + groups: false, status: null, trial_end: null, has_used_trial: true, - secretApproval: true, + secretApproval: false, secretRotation: false, caCrl: false, instanceUserManagement: false, diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index b254b8ac7..7ece7d29a 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -234,7 +234,9 @@ const Form = ({ ...data, approvers: sequenceApprovers?.flatMap((approvers, index) => approvers.user - .map((el) => ({ ...el, sequence: index + 1 }) as Approver) + .map( + (el) => ({ ...el, sequence: index + 1 }) as Omit + ) .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) ), approvalsRequired: sequenceApprovers?.map((el, index) => ({ @@ -290,7 +292,9 @@ const Form = ({ ...data, approvers: sequenceApprovers?.flatMap((approvers, index) => approvers.user - .map((el) => ({ ...el, sequence: index + 1 }) as Approver) + .map( + (el) => ({ ...el, sequence: index + 1 }) as Omit + ) .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) ), approvalsRequired: sequenceApprovers?.map((el, index) => ({ diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx index 8e25397dc..410593f12 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx @@ -215,7 +215,7 @@ export const ApprovalPolicyRow = ({ {Boolean(el.users.length) && (
- {el.users.map((u, i) => { + {el.users.map((u) => { return u.user.isOrgMembershipActive ? ( {getMemberLabel(u)} ) : ( From 22fca374f2a8874db541af98413ca5598ad05384 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 13 Aug 2025 02:46:14 +0400 Subject: [PATCH 3/9] requested changes --- .../src/ee/routes/v1/access-approval-request-router.ts | 4 ++-- .../src/ee/routes/v1/secret-approval-request-router.ts | 10 ++++++++-- .../project-membership/project-membership-dal.ts | 9 ++++++++- .../components/SecretApprovalRequestChanges.tsx | 6 +----- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/backend/src/ee/routes/v1/access-approval-request-router.ts b/backend/src/ee/routes/v1/access-approval-request-router.ts index 511c93cae..b546b2f8c 100644 --- a/backend/src/ee/routes/v1/access-approval-request-router.ts +++ b/backend/src/ee/routes/v1/access-approval-request-router.ts @@ -116,7 +116,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv approvals: z.number(), approvers: z .object({ - isOrgMembershipActive: z.boolean(), + isOrgMembershipActive: z.boolean().nullable().optional(), userId: z.string().nullable().optional(), sequence: z.number().nullable().optional(), approvalsRequired: z.number().nullable().optional(), @@ -134,7 +134,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv }), reviewers: z .object({ - isOrgMembershipActive: z.boolean(), + isOrgMembershipActive: z.boolean().nullable().optional(), userId: z.string(), status: z.string() }) diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index 956baef59..104738140 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -298,7 +298,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv id: z.string(), name: z.string(), approvals: z.number(), - approvers: approvalRequestUser.extend({ isOrgMembershipActive: z.boolean() }).array(), + approvers: approvalRequestUser + .extend({ isOrgMembershipActive: z.boolean().nullable().optional() }) + .array(), bypassers: approvalRequestUser.array(), secretPath: z.string().optional().nullable(), enforcementLevel: z.string(), @@ -309,7 +311,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv statusChangedByUser: approvalRequestUser.optional(), committerUser: approvalRequestUser.nullish(), reviewers: approvalRequestUser - .extend({ status: z.string(), comment: z.string().optional(), isOrgMembershipActive: z.boolean() }) + .extend({ + status: z.string(), + comment: z.string().optional(), + isOrgMembershipActive: z.boolean().nullable().optional() + }) .array(), secretPath: z.string(), commits: secretRawSchema diff --git a/backend/src/services/project-membership/project-membership-dal.ts b/backend/src/services/project-membership/project-membership-dal.ts index 9bc7e9aeb..dd503c2a3 100644 --- a/backend/src/services/project-membership/project-membership-dal.ts +++ b/backend/src/services/project-membership/project-membership-dal.ts @@ -21,7 +21,14 @@ export const projectMembershipDALFactory = (db: TDbClient) => { .where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId }) .join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`) .join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`) - .join(TableName.OrgMembership, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`) + .join(TableName.OrgMembership, (qb) => { + qb.on(`${TableName.Users}.id`, "=", `${TableName.OrgMembership}.userId`).andOn( + `${TableName.OrgMembership}.orgId`, + "=", + `${TableName.Project}.orgId` + ); + }) + .where((qb) => { if (filter.usernames) { void qb.whereIn("username", filter.usernames); diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index f7090f43b..1869106a5 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -177,8 +177,6 @@ export const SecretApprovalRequestChanges = ({ {} ); - console.log("reviewedUsers", reviewedUsers); - const handleSecretApprovalStatusUpdate = async (status: ApprovalStatus, comment: string) => { try { await updateSecretApprovalRequestStatus({ @@ -532,7 +530,7 @@ export const SecretApprovalRequestChanges = ({
-
Reviewerssss
+
Reviewers
{secretApprovalRequestDetails?.policy?.approvers .filter( @@ -543,8 +541,6 @@ export const SecretApprovalRequestChanges = ({ const reviewer = reviewedUsers?.[requiredApprover.userId]; const isOrgMembershipActive = requiredApprover.isOrgMembershipActive; - console.log("isOrgMembershipActive", isOrgMembershipActive); - return (
Date: Tue, 12 Aug 2025 15:46:21 -0700 Subject: [PATCH 4/9] improvement: remove console log and add user approver option component --- .../components/AccessPolicyModal.tsx | 25 ++++++++++-- .../components/PolicyMemberOption.tsx | 39 +++++++++++++++++++ .../SecretApprovalRequestChanges.tsx | 6 +-- 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index 648a6ab80..046e0838e 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -43,6 +43,8 @@ import { import { EnforcementLevel, PolicyType } from "@app/hooks/api/policies/enums"; import { TWorkspaceUser } from "@app/hooks/api/users/types"; +import { PolicyMemberOption } from "./PolicyMemberOption"; + type Props = { isOpen?: boolean; onToggle: (isOpen: boolean) => void; @@ -59,7 +61,11 @@ const formSchema = z secretPath: z.string().trim().min(1), approvals: z.number().min(1).default(1), userApprovers: z - .object({ type: z.literal(ApproverType.User), id: z.string() }) + .object({ + type: z.literal(ApproverType.User), + id: z.string(), + isOrgMembershipActive: z.boolean() + }) .array() .default([]), groupApprovers: z @@ -80,7 +86,11 @@ const formSchema = z sequenceApprovers: z .object({ user: z - .object({ type: z.literal(ApproverType.User), id: z.string() }) + .object({ + type: z.literal(ApproverType.User), + id: z.string(), + isOrgMembershipActive: z.boolean() + }) .array() .default([]), group: z @@ -139,7 +149,11 @@ const Form = ({ userApprovers: editValues?.approvers ?.filter((approver) => approver.type === ApproverType.User) - .map(({ id, type }) => ({ id, type: type as ApproverType.User })) || [], + .map(({ id, type, isOrgMembershipActive }) => ({ + id, + type: type as ApproverType.User, + isOrgMembershipActive + })) || [], groupApprovers: editValues?.approvers ?.filter((approver) => approver.type === ApproverType.Group) @@ -333,7 +347,8 @@ const Form = ({ () => members.map((member) => ({ id: member.user.id, - type: ApproverType.User + type: ApproverType.User, + isOrgMembershipActive: member.user.isOrgMembershipActive })), [members] ); @@ -612,6 +627,7 @@ const Form = ({ isMulti placeholder="Select members..." options={memberOptions} + components={{ Option: PolicyMemberOption }} getOptionValue={(option) => option.id} getOptionLabel={(option) => { const member = members?.find((m) => m.user.id === option.id); @@ -689,6 +705,7 @@ const Form = ({ menuPlacement="top" isMulti placeholder="Select members..." + components={{ Option: PolicyMemberOption }} options={memberOptions} getOptionValue={(option) => option.id} getOptionLabel={(option) => { diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx new file mode 100644 index 000000000..d6cc0eae0 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx @@ -0,0 +1,39 @@ +import { components, OptionProps } from "react-select"; +import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; +import { faBan } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; + +import { Badge } from "@app/components/v2"; +import { ApproverType } from "@app/hooks/api/accessApproval/types"; + +export const PolicyMemberOption = ({ + isSelected, + children, + ...props +}: OptionProps<{ + id: string; + isOrgMembershipActive: boolean; + type: ApproverType; +}>) => { + return ( + +
+

+ {children} +

+ {!props.data.isOrgMembershipActive && ( + + + Inactive + + )} + {isSelected && ( + + )} +
+
+ ); +}; diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index f7090f43b..5985e35e8 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -541,9 +541,7 @@ export const SecretApprovalRequestChanges = ({ ) .map((requiredApprover) => { const reviewer = reviewedUsers?.[requiredApprover.userId]; - const isOrgMembershipActive = requiredApprover.isOrgMembershipActive; - - console.log("isOrgMembershipActive", isOrgMembershipActive); + const { isOrgMembershipActive } = requiredApprover; return (
{ const status = reviewedUsers?.[reviewer.userId].status; - const isOrgMembershipActive = reviewer.isOrgMembershipActive; + const { isOrgMembershipActive } = reviewer; return (
Date: Wed, 13 Aug 2025 03:19:22 +0400 Subject: [PATCH 5/9] add bypassers inactive state --- .../components/AccessPolicyModal.tsx | 19 ++++++--- .../components/PolicyBypasserMemberOption.tsx | 39 +++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyBypasserMemberOption.tsx diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index 046e0838e..83819b09a 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -44,6 +44,7 @@ import { EnforcementLevel, PolicyType } from "@app/hooks/api/policies/enums"; import { TWorkspaceUser } from "@app/hooks/api/users/types"; import { PolicyMemberOption } from "./PolicyMemberOption"; +import { PolicyBypasserMemberOption } from "./PolicyBypasserMemberOption"; type Props = { isOpen?: boolean; @@ -64,7 +65,7 @@ const formSchema = z .object({ type: z.literal(ApproverType.User), id: z.string(), - isOrgMembershipActive: z.boolean() + isOrgMembershipActive: z.boolean().optional().nullable() }) .array() .default([]), @@ -73,7 +74,11 @@ const formSchema = z .array() .default([]), userBypassers: z - .object({ type: z.literal(BypasserType.User), id: z.string() }) + .object({ + type: z.literal(BypasserType.User), + id: z.string(), + isOrgMembershipActive: z.boolean().optional().nullable() + }) .array() .default([]), groupBypassers: z @@ -89,7 +94,7 @@ const formSchema = z .object({ type: z.literal(ApproverType.User), id: z.string(), - isOrgMembershipActive: z.boolean() + isOrgMembershipActive: z.boolean().optional().nullable() }) .array() .default([]), @@ -139,7 +144,7 @@ const Form = ({ handleSubmit, watch, resetField, - formState: { isSubmitting } + formState: { isSubmitting, errors } } = useForm({ resolver: zodResolver(formSchema), values: editValues @@ -366,7 +371,8 @@ const Form = ({ () => members.map((member) => ({ id: member.user.id, - type: BypasserType.User + type: BypasserType.User, + isOrgMembershipActive: member.user.isOrgMembershipActive })), [members] ); @@ -409,6 +415,8 @@ const Form = ({ setDragOverItem(null); }; + console.log("error", errors); + return (
@@ -804,6 +812,7 @@ const Form = ({ menuPlacement="top" isMulti placeholder="Select members..." + components={{ Option: PolicyBypasserMemberOption }} options={bypasserMemberOptions} getOptionValue={(option) => option.id} getOptionLabel={(option) => { diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyBypasserMemberOption.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyBypasserMemberOption.tsx new file mode 100644 index 000000000..ed572bdba --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyBypasserMemberOption.tsx @@ -0,0 +1,39 @@ +import { components, OptionProps } from "react-select"; +import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; +import { faBan } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; + +import { Badge } from "@app/components/v2"; +import { BypasserType } from "@app/hooks/api/accessApproval/types"; + +export const PolicyBypasserMemberOption = ({ + isSelected, + children, + ...props +}: OptionProps<{ + id: string; + type: BypasserType; + isOrgMembershipActive?: boolean; +}>) => { + return ( + +
+

+ {children} +

+ {!props.data.isOrgMembershipActive && ( + + + Inactive + + )} + {isSelected && ( + + )} +
+
+ ); +}; From 41a3ac6bd4ddf902b2990ae89ff017f382a6a599 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 13 Aug 2025 04:15:11 +0400 Subject: [PATCH 6/9] fix type errors --- .../ApprovalPolicyList/components/AccessPolicyModal.tsx | 6 +++--- .../ApprovalPolicyList/components/PolicyMemberOption.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index 2773ca354..268dba4a9 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -65,7 +65,7 @@ const formSchema = z .object({ type: z.literal(ApproverType.User), id: z.string(), - isOrgMembershipActive: z.boolean().optional().nullable() + isOrgMembershipActive: z.boolean().optional() }) .array() .default([]), @@ -77,7 +77,7 @@ const formSchema = z .object({ type: z.literal(BypasserType.User), id: z.string(), - isOrgMembershipActive: z.boolean().optional().nullable() + isOrgMembershipActive: z.boolean().optional() }) .array() .default([]), @@ -94,7 +94,7 @@ const formSchema = z .object({ type: z.literal(ApproverType.User), id: z.string(), - isOrgMembershipActive: z.boolean().optional().nullable() + isOrgMembershipActive: z.boolean().optional() }) .array() .default([]), diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx index d6cc0eae0..24284d690 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx @@ -13,7 +13,7 @@ export const PolicyMemberOption = ({ ...props }: OptionProps<{ id: string; - isOrgMembershipActive: boolean; + isOrgMembershipActive?: boolean; type: ApproverType; }>) => { return ( From 9a98192b9b26c10e23274c8e1e7f6506317cfe48 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 25 Aug 2025 17:26:41 +0200 Subject: [PATCH 7/9] fix: requested changes --- .../components/ReviewAccessModal.tsx | 40 ++++++++----- .../components/AccessPolicyModal.tsx | 7 +-- .../components/ApprovalPolicyRow.tsx | 34 ++++++----- .../components/PolicyBypasserMemberOption.tsx | 39 ------------- .../components/PolicyMemberOption.tsx | 5 +- .../SecretApprovalRequestChanges.tsx | 57 +++++++++---------- 6 files changed, 77 insertions(+), 105 deletions(-) delete mode 100644 frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyBypasserMemberOption.tsx diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx index c54237503..c5e1e5cf0 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx @@ -4,7 +4,6 @@ import { faCheck, faEdit, faHourglass, - faQuestionCircle, faTriangleExclamation, faUser, faUserSlash @@ -58,8 +57,9 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus, isOrgMembershipActive? return ( // Can't do a tooltip here because nested tooltips doesn't work properly as of yet. // TODO(daniel): Fix nested tooltips in the future. - - + + + ); } @@ -206,6 +206,7 @@ export const ReviewAccessRequestModal = ({ (acc, curr) => { if (acc.length && acc[acc.length - 1].sequence === curr.sequence) { acc[acc.length - 1][curr.type]?.push(curr); + return acc; } @@ -217,6 +218,7 @@ export const ReviewAccessRequestModal = ({ ? { user: [curr], group: [], sequence, approvals } : { group: [curr], user: [], sequence, approvals } ); + return acc; }, [] as { @@ -430,24 +432,32 @@ export const ReviewAccessRequestModal = ({ {Boolean(approver.user.length) && (
- {approver?.user?.map((el) => { + {approver?.user?.map((el, index) => { const member = approverSequence?.membersGroupById?.[el.id]?.[0]; if (!member) return null; return member.user.isOrgMembershipActive ? ( - {member.user.username} +
+ {member.user.username} + {index < approver.user.length - 1 && ","} +
) : ( - - {member.user.username}{" "} - - -
- (Inactive){" "} - -
-
+
+ + {member.user.username} + + +
+ + + Inactive + +
+
+
- + {index < approver.user.length - 1 && ","} +
); })}
diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index 268dba4a9..856c16ee1 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -44,7 +44,6 @@ import { EnforcementLevel, PolicyType } from "@app/hooks/api/policies/enums"; import { TWorkspaceUser } from "@app/hooks/api/users/types"; import { PolicyMemberOption } from "./PolicyMemberOption"; -import { PolicyBypasserMemberOption } from "./PolicyBypasserMemberOption"; type Props = { isOpen?: boolean; @@ -144,7 +143,7 @@ const Form = ({ handleSubmit, watch, resetField, - formState: { isSubmitting, errors } + formState: { isSubmitting } } = useForm({ resolver: zodResolver(formSchema), values: editValues @@ -415,8 +414,6 @@ const Form = ({ setDragOverItem(null); }; - console.log("error", errors); - return (
@@ -812,7 +809,7 @@ const Form = ({ menuPlacement="top" isMulti placeholder="Select members..." - components={{ Option: PolicyBypasserMemberOption }} + components={{ Option: PolicyMemberOption }} options={bypasserMemberOptions} getOptionValue={(option) => option.id} getOptionLabel={(option) => { diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx index 410593f12..d1c54d85f 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx @@ -1,9 +1,9 @@ import { useMemo } from "react"; import { + faBan, faClipboardCheck, faEdit, faEllipsisV, - faQuestionCircle, faTrash, faUser, faUserGroup @@ -215,21 +215,29 @@ export const ApprovalPolicyRow = ({ {Boolean(el.users.length) && (
- {el.users.map((u) => { + {el.users.map((u, index) => { return u.user.isOrgMembershipActive ? ( - {getMemberLabel(u)} +
+ {getMemberLabel(u)} + {index < el.users.length - 1 && ","} +
) : ( - - {getMemberLabel(u)}{" "} - - -
- (Inactive){" "} - -
-
+
+ + {getMemberLabel(u)} + + +
+ + + Inactive + +
+
+
- + {index < el.users.length - 1 && ","} +
); })}
diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyBypasserMemberOption.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyBypasserMemberOption.tsx deleted file mode 100644 index ed572bdba..000000000 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyBypasserMemberOption.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { components, OptionProps } from "react-select"; -import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; -import { faBan } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; - -import { Badge } from "@app/components/v2"; -import { BypasserType } from "@app/hooks/api/accessApproval/types"; - -export const PolicyBypasserMemberOption = ({ - isSelected, - children, - ...props -}: OptionProps<{ - id: string; - type: BypasserType; - isOrgMembershipActive?: boolean; -}>) => { - return ( - -
-

- {children} -

- {!props.data.isOrgMembershipActive && ( - - - Inactive - - )} - {isSelected && ( - - )} -
-
- ); -}; diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx index 24284d690..50376521c 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/PolicyMemberOption.tsx @@ -5,7 +5,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; import { Badge } from "@app/components/v2"; -import { ApproverType } from "@app/hooks/api/accessApproval/types"; +import { BypasserType } from "@app/hooks/api/accessApproval/types"; +import { ApproverType } from "@app/hooks/api/secretApproval/types"; export const PolicyMemberOption = ({ isSelected, @@ -13,8 +14,8 @@ export const PolicyMemberOption = ({ ...props }: OptionProps<{ id: string; + type: BypasserType | ApproverType; isOrgMembershipActive?: boolean; - type: ApproverType; }>) => { return ( diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index 3736dd649..1f4d708f9 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -554,31 +554,27 @@ export const SecretApprovalRequestChanges = ({ > -
+
{requiredApprover?.email}
* + {!isOrgMembershipActive && ( + + )}
- - {!isOrgMembershipActive && ( - - - - )}
{reviewer?.comment && ( @@ -632,24 +628,23 @@ export const SecretApprovalRequestChanges = ({ > -
+
{reviewer?.email} + {!isOrgMembershipActive && ( + + )}
- {!isOrgMembershipActive && ( - - - - )}
From 8123be4c14c5ccf4d50193e2b02832cbecd9bee9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 25 Aug 2025 17:46:38 +0200 Subject: [PATCH 8/9] failing tests --- .../AccessApprovalRequest/components/ReviewAccessModal.tsx | 6 +++--- .../ApprovalPolicyList/components/ApprovalPolicyRow.tsx | 6 +++--- .../components/SecretApprovalRequestChanges.tsx | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx index c5e1e5cf0..b1cd906bb 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx @@ -432,14 +432,14 @@ export const ReviewAccessRequestModal = ({ {Boolean(approver.user.length) && (
- {approver?.user?.map((el, index) => { + {approver?.user?.map((el, idx) => { const member = approverSequence?.membersGroupById?.[el.id]?.[0]; if (!member) return null; return member.user.isOrgMembershipActive ? (
{member.user.username} - {index < approver.user.length - 1 && ","} + {idx < approver.user.length - 1 && ","}
) : (
@@ -456,7 +456,7 @@ export const ReviewAccessRequestModal = ({ - {index < approver.user.length - 1 && ","} + {idx < approver.user.length - 1 && ","}
); })} diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx index d1c54d85f..89a97003e 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx @@ -215,11 +215,11 @@ export const ApprovalPolicyRow = ({ {Boolean(el.users.length) && (
- {el.users.map((u, index) => { + {el.users.map((u, idx) => { return u.user.isOrgMembershipActive ? (
{getMemberLabel(u)} - {index < el.users.length - 1 && ","} + {idx < el.users.length - 1 && ","}
) : (
@@ -236,7 +236,7 @@ export const ApprovalPolicyRow = ({ - {index < el.users.length - 1 && ","} + {idx < el.users.length - 1 && ","}
); })} diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index 1f4d708f9..4bfebc5da 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -629,6 +629,7 @@ export const SecretApprovalRequestChanges = ({ Date: Mon, 25 Aug 2025 17:55:44 +0200 Subject: [PATCH 9/9] lint fix --- .../SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx | 2 +- .../components/SecretApprovalRequestChanges.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx index de568931a..53df91974 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx @@ -1,5 +1,6 @@ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; @@ -13,7 +14,6 @@ import { } from "@app/context"; import { useLogoutUser, useUpdateOrg } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; -import { twMerge } from "tailwind-merge"; enum EnforceAuthType { SAML = "saml", diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index 4bfebc5da..6a0c3cd6d 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-nested-ternary */ import { ReactNode } from "react"; import { Controller, useForm } from "react-hook-form"; import { @@ -629,7 +630,6 @@ export const SecretApprovalRequestChanges = ({