mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 05:27:48 +00:00
feat(approvals): visualization of deprovisioned scim users
This commit is contained in:
@@ -116,6 +116,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
approvals: z.number(),
|
approvals: z.number(),
|
||||||
approvers: z
|
approvers: z
|
||||||
.object({
|
.object({
|
||||||
|
isOrgMembershipActive: z.boolean(),
|
||||||
userId: z.string().nullable().optional(),
|
userId: z.string().nullable().optional(),
|
||||||
sequence: z.number().nullable().optional(),
|
sequence: z.number().nullable().optional(),
|
||||||
approvalsRequired: z.number().nullable().optional(),
|
approvalsRequired: z.number().nullable().optional(),
|
||||||
@@ -132,6 +133,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
}),
|
}),
|
||||||
reviewers: z
|
reviewers: z
|
||||||
.object({
|
.object({
|
||||||
|
isOrgMembershipActive: z.boolean(),
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
status: z.string()
|
status: z.string()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -294,12 +294,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
approval: SecretApprovalRequestsSchema.merge(
|
approval: SecretApprovalRequestsSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
// secretPath: z.string(),
|
|
||||||
policy: z.object({
|
policy: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
approvals: z.number(),
|
approvals: z.number(),
|
||||||
approvers: approvalRequestUser.array(),
|
approvers: approvalRequestUser.extend({ isOrgMembershipActive: z.boolean() }).array(),
|
||||||
bypassers: approvalRequestUser.array(),
|
bypassers: approvalRequestUser.array(),
|
||||||
secretPath: z.string().optional().nullable(),
|
secretPath: z.string().optional().nullable(),
|
||||||
enforcementLevel: z.string(),
|
enforcementLevel: z.string(),
|
||||||
@@ -309,7 +308,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
statusChangedByUser: approvalRequestUser.optional(),
|
statusChangedByUser: approvalRequestUser.optional(),
|
||||||
committerUser: approvalRequestUser.nullish(),
|
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(),
|
secretPath: z.string(),
|
||||||
commits: secretRawSchema
|
commits: secretRawSchema
|
||||||
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true })
|
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true })
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
AccessApprovalRequestsSchema,
|
AccessApprovalRequestsSchema,
|
||||||
TableName,
|
TableName,
|
||||||
TAccessApprovalRequests,
|
TAccessApprovalRequests,
|
||||||
|
TOrgMemberships,
|
||||||
TUserGroupMembership,
|
TUserGroupMembership,
|
||||||
TUsers
|
TUsers
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
@@ -143,6 +144,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
approvalsRequired: number | null | undefined;
|
approvalsRequired: number | null | undefined;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -150,6 +152,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
approvalsRequired: number | null | undefined;
|
approvalsRequired: number | null | undefined;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
bypassers: string[];
|
bypassers: string[];
|
||||||
@@ -200,6 +203,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
reviewers: {
|
reviewers: {
|
||||||
userId: string;
|
userId: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}[];
|
}[];
|
||||||
approvers: (
|
approvers: (
|
||||||
| {
|
| {
|
||||||
@@ -208,6 +212,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
approvalsRequired: number | null | undefined;
|
approvalsRequired: number | null | undefined;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -215,6 +220,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
approvalsRequired: number | null | undefined;
|
approvalsRequired: number | null | undefined;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
bypassers: string[];
|
bypassers: string[];
|
||||||
@@ -286,6 +292,25 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
`requestedByUser.id`
|
`requestedByUser.id`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// I added these 3:
|
||||||
|
.leftJoin<TOrgMemberships>(
|
||||||
|
db(TableName.OrgMembership).as("approverOrgMembership"),
|
||||||
|
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
||||||
|
`approverOrgMembership.userId`
|
||||||
|
)
|
||||||
|
|
||||||
|
.leftJoin<TOrgMemberships>(
|
||||||
|
db(TableName.OrgMembership).as("approverGroupOrgMembership"),
|
||||||
|
`${TableName.Users}.id`,
|
||||||
|
`approverGroupOrgMembership.userId`
|
||||||
|
)
|
||||||
|
|
||||||
|
.leftJoin<TOrgMemberships>(
|
||||||
|
db(TableName.OrgMembership).as("reviewerOrgMembership"),
|
||||||
|
`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`,
|
||||||
|
`reviewerOrgMembership.userId`
|
||||||
|
)
|
||||||
|
|
||||||
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
|
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||||
@@ -297,7 +322,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
|
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("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||||
.select(db.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence"))
|
.select(db.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence"))
|
||||||
@@ -392,17 +422,26 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
{
|
{
|
||||||
key: "reviewerUserId",
|
key: "reviewerUserId",
|
||||||
label: "reviewers" as const,
|
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",
|
key: "approverUserId",
|
||||||
label: "approvers" as const,
|
label: "approvers" as const,
|
||||||
mapper: ({ approverUserId, approverSequence, approvalsRequired, approverUsername, approverEmail }) => ({
|
mapper: ({
|
||||||
|
approverUserId,
|
||||||
|
approverSequence,
|
||||||
|
approvalsRequired,
|
||||||
|
approverUsername,
|
||||||
|
approverEmail,
|
||||||
|
approverIsOrgMembershipActive
|
||||||
|
}) => ({
|
||||||
userId: approverUserId,
|
userId: approverUserId,
|
||||||
sequence: approverSequence,
|
sequence: approverSequence,
|
||||||
approvalsRequired,
|
approvalsRequired,
|
||||||
email: approverEmail,
|
email: approverEmail,
|
||||||
username: approverUsername
|
username: approverUsername,
|
||||||
|
isOrgMembershipActive: approverIsOrgMembershipActive
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -413,13 +452,15 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
approverSequence,
|
approverSequence,
|
||||||
approvalsRequired,
|
approvalsRequired,
|
||||||
approverGroupEmail,
|
approverGroupEmail,
|
||||||
approverGroupUsername
|
approverGroupUsername,
|
||||||
|
approverGroupIsOrgMembershipActive
|
||||||
}) => ({
|
}) => ({
|
||||||
userId: approverGroupUserId,
|
userId: approverGroupUserId,
|
||||||
sequence: approverSequence,
|
sequence: approverSequence,
|
||||||
approvalsRequired,
|
approvalsRequired,
|
||||||
email: approverGroupEmail,
|
email: approverGroupEmail,
|
||||||
username: approverGroupUsername
|
username: approverGroupUsername,
|
||||||
|
isOrgMembershipActive: approverGroupIsOrgMembershipActive
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
|
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
|||||||
approvalsRequired: number | null | undefined;
|
approvalsRequired: number | null | undefined;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -71,6 +72,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
|||||||
approvalsRequired: number | null | undefined;
|
approvalsRequired: number | null | undefined;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
bypassers: string[];
|
bypassers: string[];
|
||||||
@@ -82,6 +84,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
envId: string;
|
envId: string;
|
||||||
deletedAt: Date | null | undefined;
|
deletedAt: Date | null | undefined;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
};
|
};
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
@@ -121,6 +124,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
|||||||
reviewers: {
|
reviewers: {
|
||||||
userId: string;
|
userId: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}[];
|
}[];
|
||||||
approvers: (
|
approvers: (
|
||||||
| {
|
| {
|
||||||
@@ -129,6 +133,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
|||||||
approvalsRequired: number | null | undefined;
|
approvalsRequired: number | null | undefined;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -136,6 +141,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
|||||||
approvalsRequired: number | null | undefined;
|
approvalsRequired: number | null | undefined;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
bypassers: string[];
|
bypassers: string[];
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
auditLogStreams: false,
|
auditLogStreams: false,
|
||||||
auditLogStreamLimit: 3,
|
auditLogStreamLimit: 3,
|
||||||
samlSSO: false,
|
samlSSO: true,
|
||||||
hsm: false,
|
hsm: false,
|
||||||
oidcSSO: false,
|
oidcSSO: false,
|
||||||
scim: false,
|
scim: true,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
groups: false,
|
groups: true,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
secretApproval: false,
|
secretApproval: true,
|
||||||
secretRotation: false,
|
secretRotation: false,
|
||||||
caCrl: false,
|
caCrl: false,
|
||||||
instanceUserManagement: false,
|
instanceUserManagement: false,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
|
|||||||
import {
|
import {
|
||||||
SecretApprovalRequestsSchema,
|
SecretApprovalRequestsSchema,
|
||||||
TableName,
|
TableName,
|
||||||
|
TOrgMemberships,
|
||||||
TSecretApprovalRequests,
|
TSecretApprovalRequests,
|
||||||
TSecretApprovalRequestsSecrets,
|
TSecretApprovalRequestsSecrets,
|
||||||
TUserGroupMembership,
|
TUserGroupMembership,
|
||||||
@@ -107,11 +108,32 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`,
|
`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`,
|
||||||
`secretApprovalReviewerUser.id`
|
`secretApprovalReviewerUser.id`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.leftJoin<TOrgMemberships>(
|
||||||
|
db(TableName.OrgMembership).as("approverOrgMembership"),
|
||||||
|
`${TableName.SecretApprovalPolicyApprover}.approverUserId`,
|
||||||
|
`approverOrgMembership.userId`
|
||||||
|
)
|
||||||
|
|
||||||
|
.leftJoin<TOrgMemberships>(
|
||||||
|
db(TableName.OrgMembership).as("approverGroupOrgMembership"),
|
||||||
|
`secretApprovalPolicyGroupApproverUser.id`,
|
||||||
|
`approverGroupOrgMembership.userId`
|
||||||
|
)
|
||||||
|
|
||||||
|
.leftJoin<TOrgMemberships>(
|
||||||
|
db(TableName.OrgMembership).as("reviewerOrgMembership"),
|
||||||
|
`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`,
|
||||||
|
`reviewerOrgMembership.userId`
|
||||||
|
)
|
||||||
|
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
||||||
.select(
|
.select(
|
||||||
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||||
tx.ref("userId").withSchema("approverUserGroupMembership").as("approverGroupUserId"),
|
tx.ref("userId").withSchema("approverUserGroupMembership").as("approverGroupUserId"),
|
||||||
tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"),
|
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("email").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupEmail"),
|
||||||
tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"),
|
tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"),
|
||||||
tx.ref("username").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupUsername"),
|
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("username").withSchema("secretApprovalReviewerUser").as("reviewerUsername"),
|
||||||
tx.ref("firstName").withSchema("secretApprovalReviewerUser").as("reviewerFirstName"),
|
tx.ref("firstName").withSchema("secretApprovalReviewerUser").as("reviewerFirstName"),
|
||||||
tx.ref("lastName").withSchema("secretApprovalReviewerUser").as("reviewerLastName"),
|
tx.ref("lastName").withSchema("secretApprovalReviewerUser").as("reviewerLastName"),
|
||||||
|
tx.ref("isActive").withSchema("reviewerOrgMembership").as("reviewerIsOrgMembershipActive"),
|
||||||
tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
|
tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
|
||||||
tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
|
tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
|
||||||
tx.ref("projectId").withSchema(TableName.Environment),
|
tx.ref("projectId").withSchema(TableName.Environment),
|
||||||
@@ -211,9 +234,21 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
reviewerLastName: lastName,
|
reviewerLastName: lastName,
|
||||||
reviewerUsername: username,
|
reviewerUsername: username,
|
||||||
reviewerFirstName: firstName,
|
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",
|
key: "approverUserId",
|
||||||
@@ -223,13 +258,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
approverEmail: email,
|
approverEmail: email,
|
||||||
approverUsername: username,
|
approverUsername: username,
|
||||||
approverLastName: lastName,
|
approverLastName: lastName,
|
||||||
approverFirstName: firstName
|
approverFirstName: firstName,
|
||||||
|
approverIsOrgMembershipActive: isOrgMembershipActive
|
||||||
}) => ({
|
}) => ({
|
||||||
userId,
|
userId,
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
username
|
username,
|
||||||
|
isOrgMembershipActive
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -240,13 +277,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
approverGroupEmail: email,
|
approverGroupEmail: email,
|
||||||
approverGroupUsername: username,
|
approverGroupUsername: username,
|
||||||
approverGroupLastName: lastName,
|
approverGroupLastName: lastName,
|
||||||
approverGroupFirstName: firstName
|
approverGroupFirstName: firstName,
|
||||||
|
approverGroupIsOrgMembershipActive: isOrgMembershipActive
|
||||||
}) => ({
|
}) => ({
|
||||||
userId,
|
userId,
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
username
|
username,
|
||||||
|
isOrgMembershipActive
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
|
||||||
|
|
||||||
if (!secretApprovalRequest)
|
if (!secretApprovalRequest)
|
||||||
throw new NotFoundError({ message: `Secret approval request with ID '${id}' not found` });
|
throw new NotFoundError({ message: `Secret approval request with ID '${id}' not found` });
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
firstName: true,
|
firstName: true,
|
||||||
lastName: true,
|
lastName: true,
|
||||||
id: true
|
id: true
|
||||||
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
|
})
|
||||||
|
.merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
|
||||||
|
.extend({
|
||||||
|
isOrgMembershipActive: z.boolean()
|
||||||
|
}),
|
||||||
project: SanitizedProjectSchema.pick({ name: true, id: true }),
|
project: SanitizedProjectSchema.pick({ name: true, id: true }),
|
||||||
roles: z.array(
|
roles: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
|
.join(TableName.OrgMembership, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||||
db.ref("createdAt").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("temporaryRange").withSchema(TableName.GroupProjectMembershipRole),
|
||||||
db.ref("temporaryAccessStartTime").withSchema(TableName.GroupProjectMembershipRole),
|
db.ref("temporaryAccessStartTime").withSchema(TableName.GroupProjectMembershipRole),
|
||||||
db.ref("temporaryAccessEndTime").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 });
|
.where({ isGhost: false });
|
||||||
|
|
||||||
@@ -192,7 +194,8 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
id,
|
id,
|
||||||
userId,
|
userId,
|
||||||
projectName,
|
projectName,
|
||||||
createdAt
|
createdAt,
|
||||||
|
isActive
|
||||||
}) => ({
|
}) => ({
|
||||||
isGroupMember: true,
|
isGroupMember: true,
|
||||||
id,
|
id,
|
||||||
@@ -202,7 +205,7 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
id: projectId,
|
id: projectId,
|
||||||
name: projectName
|
name: projectName
|
||||||
},
|
},
|
||||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
|
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost, isOrgMembershipActive: isActive },
|
||||||
createdAt
|
createdAt
|
||||||
}),
|
}),
|
||||||
key: "id",
|
key: "id",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
.where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId })
|
.where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId })
|
||||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||||
|
.join(TableName.OrgMembership, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`)
|
||||||
.where((qb) => {
|
.where((qb) => {
|
||||||
if (filter.usernames) {
|
if (filter.usernames) {
|
||||||
void qb.whereIn("username", 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("temporaryRange").withSchema(TableName.ProjectUserMembershipRole),
|
||||||
db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole),
|
db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole),
|
||||||
db.ref("temporaryAccessEndTime").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 })
|
.where({ isGhost: false })
|
||||||
.orderBy(`${TableName.Users}.username` as "username");
|
.orderBy(`${TableName.Users}.username` as "username");
|
||||||
@@ -107,12 +109,22 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
id,
|
id,
|
||||||
userId,
|
userId,
|
||||||
projectName,
|
projectName,
|
||||||
createdAt
|
createdAt,
|
||||||
|
isActive
|
||||||
}) => ({
|
}) => ({
|
||||||
id,
|
id,
|
||||||
userId,
|
userId,
|
||||||
projectId,
|
projectId,
|
||||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
|
user: {
|
||||||
|
email,
|
||||||
|
username,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
id: userId,
|
||||||
|
publicKey,
|
||||||
|
isGhost,
|
||||||
|
isOrgMembershipActive: isActive
|
||||||
|
},
|
||||||
project: {
|
project: {
|
||||||
id: projectId,
|
id: projectId,
|
||||||
name: projectName
|
name: projectName
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ export const projectMembershipServiceFactory = ({
|
|||||||
|
|
||||||
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId, { roles });
|
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId, { roles });
|
||||||
|
|
||||||
// projectMembers[0].project
|
|
||||||
if (includeGroupMembers) {
|
if (includeGroupMembers) {
|
||||||
const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId);
|
const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId);
|
||||||
const allMembers = [
|
const allMembers = [
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export type Approver = {
|
|||||||
type: ApproverType;
|
type: ApproverType;
|
||||||
sequence?: number;
|
sequence?: number;
|
||||||
approvalsRequired?: number;
|
approvalsRequired?: number;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Bypasser = {
|
export type Bypasser = {
|
||||||
@@ -81,6 +82,7 @@ export type TAccessApprovalRequest = {
|
|||||||
name: string;
|
name: string;
|
||||||
approvals: number;
|
approvals: number;
|
||||||
approvers: {
|
approvers: {
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
userId: string;
|
userId: string;
|
||||||
sequence?: number;
|
sequence?: number;
|
||||||
approvalsRequired?: number;
|
approvalsRequired?: number;
|
||||||
@@ -96,6 +98,7 @@ export type TAccessApprovalRequest = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
reviewers: {
|
reviewers: {
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
userId: string;
|
userId: string;
|
||||||
status: string;
|
status: string;
|
||||||
}[];
|
}[];
|
||||||
@@ -166,7 +169,7 @@ export type TCreateAccessPolicyDTO = {
|
|||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
environments: string[];
|
environments: string[];
|
||||||
approvers?: Approver[];
|
approvers?: Omit<Approver, "isOrgMembershipActive">[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
@@ -178,7 +181,7 @@ export type TCreateAccessPolicyDTO = {
|
|||||||
export type TUpdateAccessPolicyDTO = {
|
export type TUpdateAccessPolicyDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
approvers?: Approver[];
|
approvers?: Omit<Approver, "isOrgMembershipActive">[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
environments?: string[];
|
environments?: string[];
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export enum ApproverType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Approver = {
|
export type Approver = {
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
type: ApproverType;
|
type: ApproverType;
|
||||||
};
|
};
|
||||||
@@ -49,7 +50,7 @@ export type TCreateSecretPolicyDTO = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
environments: string[];
|
environments: string[];
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
approvers?: Approver[];
|
approvers?: Omit<Approver, "isOrgMembershipActive">[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
@@ -59,7 +60,7 @@ export type TCreateSecretPolicyDTO = {
|
|||||||
export type TUpdateSecretPolicyDTO = {
|
export type TUpdateSecretPolicyDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
approvers?: Approver[];
|
approvers?: Omit<Approver, "isOrgMembershipActive">[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export type TSecretApprovalRequest = {
|
|||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
}[];
|
}[];
|
||||||
workspace: string;
|
workspace: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
@@ -62,6 +63,7 @@ export type TSecretApprovalRequest = {
|
|||||||
status: "open" | "close";
|
status: "open" | "close";
|
||||||
policy: Omit<TSecretApprovalPolicy, "approvers" | "bypassers"> & {
|
policy: Omit<TSecretApprovalPolicy, "approvers" | "bypassers"> & {
|
||||||
approvers: {
|
approvers: {
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export type TProjectMembership = {
|
|||||||
export type TWorkspaceUser = {
|
export type TWorkspaceUser = {
|
||||||
id: string;
|
id: string;
|
||||||
user: {
|
user: {
|
||||||
|
isOrgMembershipActive: boolean;
|
||||||
email: string;
|
email: string;
|
||||||
username: string;
|
username: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
|
|||||||
+57
-11
@@ -3,7 +3,10 @@ import {
|
|||||||
faBan,
|
faBan,
|
||||||
faCheck,
|
faCheck,
|
||||||
faHourglass,
|
faHourglass,
|
||||||
faTriangleExclamation
|
faQuestionCircle,
|
||||||
|
faTriangleExclamation,
|
||||||
|
faUser,
|
||||||
|
faUserSlash
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import ms from "ms";
|
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 { ApprovalStatus, TWorkspaceUser } from "@app/hooks/api/types";
|
||||||
import { groupBy } from "@app/lib/fn/array";
|
import { groupBy } from "@app/lib/fn/array";
|
||||||
|
|
||||||
const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
|
const getReviewedStatusSymbol = (status?: ApprovalStatus, isOrgMembershipActive?: boolean) => {
|
||||||
if (status === ApprovalStatus.APPROVED)
|
if (status === ApprovalStatus.APPROVED)
|
||||||
return (
|
return (
|
||||||
<Badge variant="success" className="flex h-4 items-center justify-center">
|
<Badge variant="success" className="flex h-4 items-center justify-center">
|
||||||
@@ -46,6 +49,16 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
|
|||||||
<FontAwesomeIcon icon={faBan} size="xs" />
|
<FontAwesomeIcon icon={faBan} size="xs" />
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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.
|
||||||
|
<Badge variant="danger" className="flex h-4 items-center justify-center">
|
||||||
|
<FontAwesomeIcon icon={faUserSlash} size="xs" />
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Badge variant="primary" className="flex h-4 items-center justify-center">
|
<Badge variant="primary" className="flex h-4 items-center justify-center">
|
||||||
<FontAwesomeIcon icon={faHourglass} size="xs" />
|
<FontAwesomeIcon icon={faHourglass} size="xs" />
|
||||||
@@ -81,6 +94,7 @@ export const ReviewAccessRequestModal = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [isLoading, setIsLoading] = useState<"approved" | "rejected" | null>(null);
|
const [isLoading, setIsLoading] = useState<"approved" | "rejected" | null>(null);
|
||||||
const [bypassApproval, setBypassApproval] = useState(false);
|
const [bypassApproval, setBypassApproval] = useState(false);
|
||||||
|
|
||||||
const [bypassReason, setBypassReason] = useState("");
|
const [bypassReason, setBypassReason] = useState("");
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data: groupMemberships = [] } = useListWorkspaceGroups(currentWorkspace?.id || "");
|
const { data: groupMemberships = [] } = useListWorkspaceGroups(currentWorkspace?.id || "");
|
||||||
@@ -215,7 +229,10 @@ export const ReviewAccessRequestModal = ({
|
|||||||
const approvers = approversBySequence?.map((approverChain) => {
|
const approvers = approversBySequence?.map((approverChain) => {
|
||||||
const reviewers = request.policy.approvers
|
const reviewers = request.policy.approvers
|
||||||
.filter((el) => (el.sequence || 1) === approverChain.sequence)
|
.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 =
|
const hasApproved =
|
||||||
reviewers.filter((el) => el.status === "approved").length >=
|
reviewers.filter((el) => el.status === "approved").length >=
|
||||||
(approverChain?.approvals || 1);
|
(approverChain?.approvals || 1);
|
||||||
@@ -383,12 +400,31 @@ export const ReviewAccessRequestModal = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid flex-1 grid-cols-5 border-b border-mineshaft-600 p-4">
|
<div className="grid flex-1 grid-cols-5 border-b border-mineshaft-600 p-4">
|
||||||
<GenericFieldLabel className="col-span-2" label="Users">
|
<GenericFieldLabel className="col-span-2" icon={faUser} label="Users">
|
||||||
{approver?.user
|
{Boolean(approver.user.length) && (
|
||||||
?.map(
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
(el) => approverSequence?.membersGroupById?.[el.id]?.[0]?.user?.username
|
{approver?.user?.map((el) => {
|
||||||
)
|
const member = approverSequence?.membersGroupById?.[el.id]?.[0];
|
||||||
.join(", ")}
|
if (!member) return null;
|
||||||
|
|
||||||
|
return member.user.isOrgMembershipActive ? (
|
||||||
|
<span key={el.id}>{member.user.username}</span>
|
||||||
|
) : (
|
||||||
|
<span className="opacity-40" key={el.id}>
|
||||||
|
{member.user.username}{" "}
|
||||||
|
<span className="text-xs">
|
||||||
|
<Tooltip content="This user has been deactivated and no longer has an active organization membership.">
|
||||||
|
<div>
|
||||||
|
(Inactive){" "}
|
||||||
|
<FontAwesomeIcon size="xs" icon={faQuestionCircle} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</GenericFieldLabel>
|
</GenericFieldLabel>
|
||||||
<GenericFieldLabel className="col-span-2" label="Groups">
|
<GenericFieldLabel className="col-span-2" label="Groups">
|
||||||
{approver?.group
|
{approver?.group
|
||||||
@@ -413,8 +449,18 @@ export const ReviewAccessRequestModal = ({
|
|||||||
key={`reviewer-${idx + 1}`}
|
key={`reviewer-${idx + 1}`}
|
||||||
className="flex items-center gap-2 px-2 py-2 text-sm"
|
className="flex items-center gap-2 px-2 py-2 text-sm"
|
||||||
>
|
>
|
||||||
<div className="flex-1">{el.username}</div>
|
<div
|
||||||
{getReviewedStatusSymbol(el?.status as ApprovalStatus)}
|
className={twMerge(
|
||||||
|
"flex-1",
|
||||||
|
!el.isOrgMembershipActive && "opacity-40"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{el.username}
|
||||||
|
</div>
|
||||||
|
{getReviewedStatusSymbol(
|
||||||
|
el?.status as ApprovalStatus,
|
||||||
|
el.isOrgMembershipActive
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+26
-5
@@ -3,6 +3,7 @@ import {
|
|||||||
faClipboardCheck,
|
faClipboardCheck,
|
||||||
faEdit,
|
faEdit,
|
||||||
faEllipsisV,
|
faEllipsisV,
|
||||||
|
faQuestionCircle,
|
||||||
faTrash,
|
faTrash,
|
||||||
faUser,
|
faUser,
|
||||||
faUserGroup
|
faUserGroup
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
GenericFieldLabel,
|
GenericFieldLabel,
|
||||||
IconButton,
|
IconButton,
|
||||||
Td,
|
Td,
|
||||||
|
Tooltip,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Badge } from "@app/components/v2/Badge";
|
import { Badge } from "@app/components/v2/Badge";
|
||||||
@@ -86,10 +88,9 @@ export const ApprovalPolicyRow = ({
|
|||||||
return entityInSameSequence?.map((el) => {
|
return entityInSameSequence?.map((el) => {
|
||||||
return {
|
return {
|
||||||
sequence: el.sequence || policy.approvals,
|
sequence: el.sequence || policy.approvals,
|
||||||
userLabels: members
|
|
||||||
?.filter((member) => el.user.find((i) => i.id === member.user.id))
|
users: members.filter((member) => el.user.find((i) => i.id === member.user.id)),
|
||||||
.map((member) => getMemberLabel(member))
|
|
||||||
.join(", "),
|
|
||||||
groupLabels: groups
|
groupLabels: groups
|
||||||
?.filter(({ group }) => el.group.find((i) => i.id === group.id))
|
?.filter(({ group }) => el.group.find((i) => i.id === group.id))
|
||||||
.map(({ group }) => group.name)
|
.map(({ group }) => group.name)
|
||||||
@@ -212,7 +213,27 @@ export const ApprovalPolicyRow = ({
|
|||||||
)}
|
)}
|
||||||
<div className="grid flex-1 grid-cols-5 border-b border-mineshaft-600 p-4">
|
<div className="grid flex-1 grid-cols-5 border-b border-mineshaft-600 p-4">
|
||||||
<GenericFieldLabel className="col-span-2" icon={faUser} label="Users">
|
<GenericFieldLabel className="col-span-2" icon={faUser} label="Users">
|
||||||
{el.userLabels}
|
{Boolean(el.users.length) && (
|
||||||
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
|
{el.users.map((u, i) => {
|
||||||
|
return u.user.isOrgMembershipActive ? (
|
||||||
|
<span key={u.id}>{getMemberLabel(u)}</span>
|
||||||
|
) : (
|
||||||
|
<span className="opacity-40" key={u.id}>
|
||||||
|
{getMemberLabel(u)}{" "}
|
||||||
|
<span className="text-xs">
|
||||||
|
<Tooltip content="This user has been deactivated and no longer has an active organization membership.">
|
||||||
|
<div>
|
||||||
|
(Inactive){" "}
|
||||||
|
<FontAwesomeIcon size="xs" icon={faQuestionCircle} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</GenericFieldLabel>
|
</GenericFieldLabel>
|
||||||
<GenericFieldLabel className="col-span-2" icon={faUserGroup} label="Groups">
|
<GenericFieldLabel className="col-span-2" icon={faUserGroup} label="Groups">
|
||||||
{el.groupLabels}
|
{el.groupLabels}
|
||||||
|
|||||||
+103
-28
@@ -8,7 +8,8 @@ import {
|
|||||||
faCodeBranch,
|
faCodeBranch,
|
||||||
faComment,
|
faComment,
|
||||||
faFolder,
|
faFolder,
|
||||||
faHourglass
|
faHourglass,
|
||||||
|
faUserSlash
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -85,6 +86,7 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
|
|||||||
return <FontAwesomeIcon icon={faCheck} size="xs" className="text-green" />;
|
return <FontAwesomeIcon icon={faCheck} size="xs" className="text-green" />;
|
||||||
if (status === ApprovalStatus.REJECTED)
|
if (status === ApprovalStatus.REJECTED)
|
||||||
return <FontAwesomeIcon icon={faBan} size="xs" className="text-red" />;
|
return <FontAwesomeIcon icon={faBan} size="xs" className="text-red" />;
|
||||||
|
|
||||||
return <FontAwesomeIcon icon={faHourglass} size="xs" className="text-yellow" />;
|
return <FontAwesomeIcon icon={faHourglass} size="xs" className="text-yellow" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -162,15 +164,21 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
secretApprovalRequestDetails.policy.bypassers.some(({ userId }) => userId === userSession.id);
|
secretApprovalRequestDetails.policy.bypassers.some(({ userId }) => userId === userSession.id);
|
||||||
|
|
||||||
const reviewedUsers = secretApprovalRequestDetails?.reviewers?.reduce<
|
const reviewedUsers = secretApprovalRequestDetails?.reviewers?.reduce<
|
||||||
Record<string, { status: ApprovalStatus; comment: string }>
|
Record<string, { status: ApprovalStatus; comment: string; isOrgMembershipActive: boolean }>
|
||||||
>(
|
>(
|
||||||
(prev, curr) => ({
|
(prev, curr) => ({
|
||||||
...prev,
|
...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) => {
|
const handleSecretApprovalStatusUpdate = async (status: ApprovalStatus, comment: string) => {
|
||||||
try {
|
try {
|
||||||
await updateSecretApprovalRequestStatus({
|
await updateSecretApprovalRequestStatus({
|
||||||
@@ -524,7 +532,7 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sticky top-0 z-[51] w-1/5 cursor-default pt-2" style={{ minWidth: "240px" }}>
|
<div className="sticky top-0 z-[51] w-1/5 cursor-default pt-2" style={{ minWidth: "240px" }}>
|
||||||
<div className="text-sm text-bunker-300">Reviewers</div>
|
<div className="text-sm text-bunker-300">Reviewerssss</div>
|
||||||
<div className="mt-2 flex flex-col space-y-2 text-sm">
|
<div className="mt-2 flex flex-col space-y-2 text-sm">
|
||||||
{secretApprovalRequestDetails?.policy?.approvers
|
{secretApprovalRequestDetails?.policy?.approvers
|
||||||
.filter(
|
.filter(
|
||||||
@@ -533,26 +541,50 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
)
|
)
|
||||||
.map((requiredApprover) => {
|
.map((requiredApprover) => {
|
||||||
const reviewer = reviewedUsers?.[requiredApprover.userId];
|
const reviewer = reviewedUsers?.[requiredApprover.userId];
|
||||||
|
const isOrgMembershipActive = requiredApprover.isOrgMembershipActive;
|
||||||
|
|
||||||
|
console.log("isOrgMembershipActive", isOrgMembershipActive);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-nowrap items-center justify-between space-x-2 rounded border border-mineshaft-600 bg-mineshaft-800 px-2 py-1"
|
className="flex flex-nowrap items-center justify-between space-x-2 rounded border border-mineshaft-600 bg-mineshaft-800 px-2 py-1"
|
||||||
key={`required-approver-${requiredApprover.userId}`}
|
key={`required-approver-${requiredApprover.userId}`}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<div
|
||||||
content={
|
className={twMerge(
|
||||||
requiredApprover.firstName
|
"flex items-center gap-1 text-sm",
|
||||||
? `${requiredApprover.firstName || ""} ${requiredApprover.lastName || ""}`
|
!isOrgMembershipActive && "opacity-40"
|
||||||
: undefined
|
)}
|
||||||
}
|
|
||||||
position="left"
|
|
||||||
sideOffset={10}
|
|
||||||
>
|
>
|
||||||
<div className="flex text-sm">
|
<Tooltip
|
||||||
<div>{requiredApprover?.email}</div>
|
content={
|
||||||
<span className="text-red">*</span>
|
requiredApprover.firstName
|
||||||
</div>
|
? `${requiredApprover.firstName || ""} ${requiredApprover.lastName || ""}`
|
||||||
</Tooltip>
|
: undefined
|
||||||
<div>
|
}
|
||||||
|
position="left"
|
||||||
|
sideOffset={10}
|
||||||
|
>
|
||||||
|
<div className="flex">
|
||||||
|
<div>{requiredApprover?.email}</div>
|
||||||
|
<span className="text-red">*</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{!isOrgMembershipActive && (
|
||||||
|
<Tooltip
|
||||||
|
className="relative !z-[500]"
|
||||||
|
content="This user has been deactivated and no longer has an active organization membership."
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faUserSlash}
|
||||||
|
size="xs"
|
||||||
|
className="text-mineshaft-300"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
{reviewer?.comment && (
|
{reviewer?.comment && (
|
||||||
<Tooltip className="max-w-lg break-words" content={reviewer.comment}>
|
<Tooltip className="max-w-lg break-words" content={reviewer.comment}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
@@ -562,9 +594,21 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip content={`Status: ${reviewer?.status || ApprovalStatus.PENDING}`}>
|
<div className="flex gap-2">
|
||||||
{getReviewedStatusSymbol(reviewer?.status)}
|
<Tooltip
|
||||||
</Tooltip>
|
className="relative !z-[500]"
|
||||||
|
content={
|
||||||
|
<span className="text-sm">
|
||||||
|
Status:{" "}
|
||||||
|
<span className="capitalize">
|
||||||
|
{reviewer?.status || ApprovalStatus.PENDING}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{getReviewedStatusSymbol(reviewer?.status)}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -578,20 +622,43 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
)
|
)
|
||||||
.map((reviewer) => {
|
.map((reviewer) => {
|
||||||
const status = reviewedUsers?.[reviewer.userId].status;
|
const status = reviewedUsers?.[reviewer.userId].status;
|
||||||
|
const isOrgMembershipActive = reviewer.isOrgMembershipActive;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
|
className="flex flex-nowrap items-center justify-between space-x-2 rounded bg-mineshaft-800 px-2 py-1"
|
||||||
key={`required-approver-${reviewer.userId}`}
|
key={`required-approver-${reviewer.userId}`}
|
||||||
>
|
>
|
||||||
<div className="flex-grow text-sm">
|
<div
|
||||||
<Tooltip content={`${reviewer.firstName || ""} ${reviewer.lastName || ""}`}>
|
className={twMerge(
|
||||||
<span>{reviewer?.email} </span>
|
"flex items-center gap-1 text-sm",
|
||||||
|
!isOrgMembershipActive && "opacity-40"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
className="relative !z-[500]"
|
||||||
|
content={`${reviewer.firstName || ""} ${reviewer.lastName || ""}`}
|
||||||
|
>
|
||||||
|
<div className="flex">
|
||||||
|
<span>{reviewer?.email} </span>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span className="text-red">*</span>
|
{!isOrgMembershipActive && (
|
||||||
|
<Tooltip
|
||||||
|
className="relative !z-[500]"
|
||||||
|
content="This user has been deactivated and no longer has an active organization membership."
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faUserSlash}
|
||||||
|
size="xs"
|
||||||
|
className="text-mineshaft-300"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{reviewer.comment && (
|
{reviewer.comment && (
|
||||||
<Tooltip content={reviewer.comment}>
|
<Tooltip className="relative !z-[500]" content={reviewer.comment}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faComment}
|
icon={faComment}
|
||||||
size="xs"
|
size="xs"
|
||||||
@@ -599,7 +666,15 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip content={status || ApprovalStatus.PENDING}>
|
<Tooltip
|
||||||
|
className="relative !z-[500]"
|
||||||
|
content={
|
||||||
|
<span className="text-sm">
|
||||||
|
Status:{" "}
|
||||||
|
<span className="capitalize">{status || ApprovalStatus.PENDING}</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
{getReviewedStatusSymbol(status)}
|
{getReviewedStatusSymbol(status)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user