mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 18:27:36 +00:00
Merge branch 'heads/main' into daniel/scim-deprovisioning-ui
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas/models";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "maxTimePeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
||||||
|
t.string("maxTimePeriod").nullable(); // Ex: 1h - Null is permanent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "maxTimePeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
||||||
|
t.dropColumn("maxTimePeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,8 @@ export const AccessApprovalPoliciesSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
enforcementLevel: z.string().default("hard"),
|
enforcementLevel: z.string().default("hard"),
|
||||||
deletedAt: z.date().nullable().optional(),
|
deletedAt: z.date().nullable().optional(),
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
allowedSelfApprovals: z.boolean().default(true),
|
||||||
|
maxTimePeriod: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
||||||
|
|||||||
@@ -3,12 +3,32 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const maxTimePeriodSchema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.nullish()
|
||||||
|
.transform((val, ctx) => {
|
||||||
|
if (val === undefined) return undefined;
|
||||||
|
if (!val || val === "permanent") return null;
|
||||||
|
const parsedMs = ms(val);
|
||||||
|
|
||||||
|
if (typeof parsedMs !== "number" || parsedMs <= 0) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "Invalid time period format or value. Must be a positive duration (e.g., '1h', '30m', '2d')."
|
||||||
|
});
|
||||||
|
return z.NEVER;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
});
|
||||||
|
|
||||||
export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvider) => {
|
export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
@@ -71,7 +91,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.optional(),
|
.optional(),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
allowedSelfApprovals: z.boolean().default(true),
|
||||||
|
maxTimePeriod: maxTimePeriodSchema
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(val) => Boolean(val.environment) || Boolean(val.environments),
|
(val) => Boolean(val.environment) || Boolean(val.environments),
|
||||||
@@ -124,7 +145,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.array()
|
.array()
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
bypassers: z.object({ type: z.nativeEnum(BypasserType), id: z.string().nullable().optional() }).array()
|
bypassers: z.object({ type: z.nativeEnum(BypasserType), id: z.string().nullable().optional() }).array(),
|
||||||
|
maxTimePeriod: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.nullable()
|
.nullable()
|
||||||
@@ -233,7 +255,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
stepNumber: z.number().int()
|
stepNumber: z.number().int()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional(),
|
||||||
|
maxTimePeriod: maxTimePeriodSchema
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -314,7 +337,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional()
|
.optional(),
|
||||||
|
maxTimePeriod: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
envId: z.string(),
|
envId: z.string(),
|
||||||
enforcementLevel: z.string(),
|
enforcementLevel: z.string(),
|
||||||
deletedAt: z.date().nullish(),
|
deletedAt: z.date().nullish(),
|
||||||
allowedSelfApprovals: z.boolean()
|
allowedSelfApprovals: z.boolean(),
|
||||||
|
maxTimePeriod: z.string().nullable().optional()
|
||||||
}),
|
}),
|
||||||
reviewers: z
|
reviewers: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
bypassers: (
|
bypassers: (
|
||||||
| {
|
| {
|
||||||
@@ -96,6 +97,7 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
environments: {
|
environments: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -141,6 +143,7 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
environments,
|
environments,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired,
|
||||||
|
maxTimePeriod
|
||||||
}) => {
|
}) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
@@ -219,7 +220,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals
|
allowedSelfApprovals,
|
||||||
|
maxTimePeriod
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -318,7 +320,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired,
|
approvalsRequired,
|
||||||
environments
|
environments,
|
||||||
|
maxTimePeriod
|
||||||
}: TUpdateAccessApprovalPolicy) => {
|
}: TUpdateAccessApprovalPolicy) => {
|
||||||
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
|
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
|
||||||
|
|
||||||
@@ -461,7 +464,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals
|
allowedSelfApprovals,
|
||||||
|
maxTimePeriod
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export type TCreateAccessApprovalPolicy = {
|
|||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateAccessApprovalPolicy = {
|
export type TUpdateAccessApprovalPolicy = {
|
||||||
@@ -60,6 +61,7 @@ export type TUpdateAccessApprovalPolicy = {
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||||
environments?: string[];
|
environments?: string[];
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteAccessApprovalPolicy = {
|
export type TDeleteAccessApprovalPolicy = {
|
||||||
@@ -104,7 +106,8 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
environment,
|
environment,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired,
|
||||||
|
maxTimePeriod
|
||||||
}: TCreateAccessApprovalPolicy) => Promise<{
|
}: TCreateAccessApprovalPolicy) => Promise<{
|
||||||
environment: {
|
environment: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -135,6 +138,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
}>;
|
}>;
|
||||||
deleteAccessApprovalPolicy: ({
|
deleteAccessApprovalPolicy: ({
|
||||||
policyId,
|
policyId,
|
||||||
@@ -159,6 +163,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -185,7 +190,8 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired,
|
approvalsRequired,
|
||||||
environments
|
environments,
|
||||||
|
maxTimePeriod
|
||||||
}: TUpdateAccessApprovalPolicy) => Promise<{
|
}: TUpdateAccessApprovalPolicy) => Promise<{
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -208,6 +214,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath?: string | null | undefined;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
}>;
|
}>;
|
||||||
getAccessApprovalPolicyByProjectSlug: ({
|
getAccessApprovalPolicyByProjectSlug: ({
|
||||||
actorId,
|
actorId,
|
||||||
@@ -242,6 +249,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -298,6 +306,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
deletedAt: Date | null | undefined;
|
deletedAt: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
};
|
};
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environments: string[];
|
environments: string[];
|
||||||
@@ -164,6 +165,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
envId: string;
|
envId: string;
|
||||||
deletedAt: Date | null | undefined;
|
deletedAt: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
};
|
};
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
@@ -292,7 +294,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
`requestedByUser.id`
|
`requestedByUser.id`
|
||||||
)
|
)
|
||||||
|
|
||||||
// I added these 3:
|
|
||||||
.leftJoin<TOrgMemberships>(
|
.leftJoin<TOrgMemberships>(
|
||||||
db(TableName.OrgMembership).as("approverOrgMembership"),
|
db(TableName.OrgMembership).as("approverOrgMembership"),
|
||||||
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
||||||
@@ -324,10 +325,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
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("approverOrgMembership").as("approverIsOrgMembershipActive"),
|
||||||
db.ref("isActive").withSchema("approverGroupOrgMembership").as("approverGroupIsOrgMembershipActive"),
|
db.ref("isActive").withSchema("approverGroupOrgMembership").as("approverGroupIsOrgMembershipActive"),
|
||||||
db.ref("isActive").withSchema("reviewerOrgMembership").as("reviewerIsOrgMembershipActive")
|
db.ref("isActive").withSchema("reviewerOrgMembership").as("reviewerIsOrgMembershipActive"),
|
||||||
|
db.ref("maxTimePeriod").withSchema(TableName.AccessApprovalPolicy).as("policyMaxTimePeriod")
|
||||||
)
|
)
|
||||||
.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"))
|
||||||
@@ -394,7 +395,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
enforcementLevel: doc.policyEnforcementLevel,
|
enforcementLevel: doc.policyEnforcementLevel,
|
||||||
allowedSelfApprovals: doc.policyAllowedSelfApprovals,
|
allowedSelfApprovals: doc.policyAllowedSelfApprovals,
|
||||||
envId: doc.policyEnvId,
|
envId: doc.policyEnvId,
|
||||||
deletedAt: doc.policyDeletedAt
|
deletedAt: doc.policyDeletedAt,
|
||||||
|
maxTimePeriod: doc.policyMaxTimePeriod
|
||||||
},
|
},
|
||||||
requestedByUser: {
|
requestedByUser: {
|
||||||
userId: doc.requestedByUserId,
|
userId: doc.requestedByUserId,
|
||||||
@@ -615,7 +617,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
tx.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
tx.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||||
tx.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
tx.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt"),
|
||||||
|
tx.ref("maxTimePeriod").withSchema(TableName.AccessApprovalPolicy).as("policyMaxTimePeriod")
|
||||||
);
|
);
|
||||||
|
|
||||||
const findById: TAccessApprovalRequestDALFactory["findById"] = async (id, tx) => {
|
const findById: TAccessApprovalRequestDALFactory["findById"] = async (id, tx) => {
|
||||||
@@ -636,7 +639,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
secretPath: el.policySecretPath,
|
secretPath: el.policySecretPath,
|
||||||
enforcementLevel: el.policyEnforcementLevel,
|
enforcementLevel: el.policyEnforcementLevel,
|
||||||
allowedSelfApprovals: el.policyAllowedSelfApprovals,
|
allowedSelfApprovals: el.policyAllowedSelfApprovals,
|
||||||
deletedAt: el.policyDeletedAt
|
deletedAt: el.policyDeletedAt,
|
||||||
|
maxTimePeriod: el.policyMaxTimePeriod
|
||||||
},
|
},
|
||||||
requestedByUser: {
|
requestedByUser: {
|
||||||
userId: el.requestedByUserId,
|
userId: el.requestedByUserId,
|
||||||
|
|||||||
@@ -156,6 +156,15 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "The policy linked to this request has been deleted" });
|
throw new BadRequestError({ message: "The policy linked to this request has been deleted" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the requested time falls under policy.maxTimePeriod
|
||||||
|
if (policy.maxTimePeriod) {
|
||||||
|
if (!temporaryRange || ms(temporaryRange) > ms(policy.maxTimePeriod)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Requested access time range is limited to ${policy.maxTimePeriod} by policy`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const approverIds: string[] = [];
|
const approverIds: string[] = [];
|
||||||
const approverGroupIds: string[] = [];
|
const approverGroupIds: string[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
envId: string;
|
envId: string;
|
||||||
deletedAt: Date | null | undefined;
|
deletedAt: Date | null | undefined;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
};
|
};
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
.transform(removeTrailingSlash)
|
.transform(removeTrailingSlash)
|
||||||
.describe(FOLDERS.CREATE.path)
|
.describe(FOLDERS.CREATE.path)
|
||||||
.optional(),
|
.optional(),
|
||||||
// backward compatiability with cli
|
// backward compatibility with cli
|
||||||
directory: z
|
directory: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -58,7 +58,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
folder: SecretFoldersSchema
|
folder: SecretFoldersSchema.extend({
|
||||||
|
path: z.string()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -130,7 +132,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
.transform(removeTrailingSlash)
|
.transform(removeTrailingSlash)
|
||||||
.describe(FOLDERS.UPDATE.path)
|
.describe(FOLDERS.UPDATE.path)
|
||||||
.optional(),
|
.optional(),
|
||||||
// backward compatiability with cli
|
// backward compatibility with cli
|
||||||
directory: z
|
directory: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -143,7 +145,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
folder: SecretFoldersSchema
|
folder: SecretFoldersSchema.extend({
|
||||||
|
path: z.string()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -359,7 +363,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
.transform(removeTrailingSlash)
|
.transform(removeTrailingSlash)
|
||||||
.describe(FOLDERS.LIST.path)
|
.describe(FOLDERS.LIST.path)
|
||||||
.optional(),
|
.optional(),
|
||||||
// backward compatiability with cli
|
// backward compatibility with cli
|
||||||
directory: z
|
directory: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
|||||||
@@ -49,6 +49,19 @@ export const dailyResourceCleanUpQueueServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
|
await queueService.stopRepeatableJob(
|
||||||
|
QueueName.AuditLogPrune,
|
||||||
|
QueueJobs.AuditLogPrune,
|
||||||
|
{ pattern: "0 0 * * *", utc: true },
|
||||||
|
QueueName.AuditLogPrune // just a job id
|
||||||
|
);
|
||||||
|
await queueService.stopRepeatableJob(
|
||||||
|
QueueName.DailyResourceCleanUp,
|
||||||
|
QueueJobs.DailyResourceCleanUp,
|
||||||
|
{ pattern: "0 0 * * *", utc: true },
|
||||||
|
QueueName.DailyResourceCleanUp // just a job id
|
||||||
|
);
|
||||||
|
|
||||||
await queueService.startPg<QueueName.DailyResourceCleanUp>(
|
await queueService.startPg<QueueName.DailyResourceCleanUp>(
|
||||||
QueueJobs.DailyResourceCleanUp,
|
QueueJobs.DailyResourceCleanUp,
|
||||||
async () => {
|
async () => {
|
||||||
|
|||||||
@@ -238,8 +238,16 @@ export const secretFolderServiceFactory = ({
|
|||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [folderWithFullPath] = await folderDAL.findSecretPathByFolderIds(projectId, [folder.id]);
|
||||||
|
|
||||||
|
if (!folderWithFullPath) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to retrieve path for folder with ID '${folder.id}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folder.parentId as string);
|
await snapshotService.performSnapshot(folder.parentId as string);
|
||||||
return folder;
|
return { ...folder, path: folderWithFullPath.path };
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateManyFolders = async ({
|
const updateManyFolders = async ({
|
||||||
@@ -496,8 +504,27 @@ export const secretFolderServiceFactory = ({
|
|||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const foldersWithFullPaths = await folderDAL.findSecretPathByFolderIds(projectId, [newFolder.id, folder.id]);
|
||||||
|
|
||||||
|
const newFolderWithFullPath = foldersWithFullPaths.find((f) => f?.id === newFolder.id);
|
||||||
|
if (!newFolderWithFullPath) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to retrieve path for folder with ID '${newFolder.id}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderWithFullPath = foldersWithFullPaths.find((f) => f?.id === folder.id);
|
||||||
|
if (!folderWithFullPath) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to retrieve path for folder with ID '${folder.id}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await snapshotService.performSnapshot(newFolder.parentId as string);
|
await snapshotService.performSnapshot(newFolder.parentId as string);
|
||||||
return { folder: newFolder, old: folder };
|
return {
|
||||||
|
folder: { ...newFolder, path: newFolderWithFullPath.path },
|
||||||
|
old: { ...folder, path: folderWithFullPath.path }
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const $checkFolderPolicy = async ({
|
const $checkFolderPolicy = async ({
|
||||||
|
|||||||
@@ -53,8 +53,8 @@
|
|||||||
"project-id": "Project ID",
|
"project-id": "Project ID",
|
||||||
"save-changes": "Save Changes",
|
"save-changes": "Save Changes",
|
||||||
"saved": "Saved",
|
"saved": "Saved",
|
||||||
"drop-zone": "Drag and drop a .env, .json, or .yml file here.",
|
"drop-zone": "Drag and drop a .env, .json, .csv, or .yml file here.",
|
||||||
"drop-zone-keys": "Drag and drop a .env, .json, or .yml file here to add more secrets.",
|
"drop-zone-keys": "Drag and drop a .env, .json, .csv, or .yml file here to add more secrets.",
|
||||||
"role": "Role",
|
"role": "Role",
|
||||||
"role_admin": "admin",
|
"role_admin": "admin",
|
||||||
"display-name": "Display Name",
|
"display-name": "Display Name",
|
||||||
|
|||||||
@@ -165,3 +165,61 @@ export function parseYaml(src: ArrayBuffer | string) {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function detectSeparator(csvContent: string): string {
|
||||||
|
const firstLine = csvContent.split("\n")[0];
|
||||||
|
const separators = [",", ";", "\t", "|"];
|
||||||
|
|
||||||
|
const counts = separators.map((sep) => ({
|
||||||
|
separator: sep,
|
||||||
|
count: (firstLine.match(new RegExp(`\\${sep}`, "g")) || []).length
|
||||||
|
}));
|
||||||
|
|
||||||
|
const detected = counts.reduce((max, curr) => (curr.count > max.count ? curr : max));
|
||||||
|
|
||||||
|
return detected.count > 0 ? detected.separator : ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseCsvToMatrix(src: ArrayBuffer | string): string[][] {
|
||||||
|
let csvContent: string;
|
||||||
|
if (typeof src === "string") {
|
||||||
|
csvContent = src;
|
||||||
|
} else {
|
||||||
|
csvContent = new TextDecoder("utf-8").decode(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
const separator = detectSeparator(csvContent);
|
||||||
|
const lines = csvContent.replace(/\r\n?/g, "\n").split("\n");
|
||||||
|
const matrix: string[][] = [];
|
||||||
|
|
||||||
|
lines.forEach((line) => {
|
||||||
|
if (line.trim() !== "") {
|
||||||
|
const cells: string[] = [];
|
||||||
|
let currentCell = "";
|
||||||
|
let inQuote = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < line.length; i += 1) {
|
||||||
|
const char = line[i];
|
||||||
|
const nextChar = line[i + 1];
|
||||||
|
|
||||||
|
if (char === '"') {
|
||||||
|
if (inQuote && nextChar === '"') {
|
||||||
|
currentCell += '"';
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
inQuote = !inQuote;
|
||||||
|
}
|
||||||
|
} else if (char === separator && !inQuote) {
|
||||||
|
cells.push(currentCell.trim());
|
||||||
|
currentCell = "";
|
||||||
|
} else {
|
||||||
|
currentCell += char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cells.push(currentCell.trim());
|
||||||
|
matrix.push(cells);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export const useCreateAccessApprovalPolicy = () => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired,
|
||||||
|
maxTimePeriod
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
|
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
|
||||||
environments,
|
environments,
|
||||||
@@ -38,7 +39,8 @@ export const useCreateAccessApprovalPolicy = () => {
|
|||||||
name,
|
name,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired,
|
||||||
|
maxTimePeriod
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
@@ -64,7 +66,8 @@ export const useUpdateAccessApprovalPolicy = () => {
|
|||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired,
|
approvalsRequired,
|
||||||
environments
|
environments,
|
||||||
|
maxTimePeriod
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
|
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
|
||||||
approvals,
|
approvals,
|
||||||
@@ -75,7 +78,8 @@ export const useUpdateAccessApprovalPolicy = () => {
|
|||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired,
|
approvalsRequired,
|
||||||
environments
|
environments,
|
||||||
|
maxTimePeriod
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type TAccessApprovalPolicy = {
|
|||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ApproverType {
|
export enum ApproverType {
|
||||||
@@ -95,6 +96,7 @@ export type TAccessApprovalRequest = {
|
|||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
deletedAt: Date | null;
|
deletedAt: Date | null;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
reviewers: {
|
reviewers: {
|
||||||
@@ -176,6 +178,7 @@ export type TCreateAccessPolicyDTO = {
|
|||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateAccessPolicyDTO = {
|
export type TUpdateAccessPolicyDTO = {
|
||||||
@@ -191,6 +194,7 @@ export type TUpdateAccessPolicyDTO = {
|
|||||||
// for invalidating list
|
// for invalidating list
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||||
|
maxTimePeriod?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteSecretPolicyDTO = {
|
export type TDeleteSecretPolicyDTO = {
|
||||||
|
|||||||
+18
@@ -220,6 +220,24 @@ export const SpecificPrivilegeSecretForm = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const policy = policies.find(
|
||||||
|
(p) =>
|
||||||
|
p.environments.find((e) => e.slug === selectedEnvironment) && p.secretPath === secretPath
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
policy?.maxTimePeriod &&
|
||||||
|
(!data.temporaryAccess.isTemporary ||
|
||||||
|
ms(data.temporaryAccess.temporaryRange) > ms(policy.maxTimePeriod))
|
||||||
|
) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: `Requested access time range is limited to ${policy.maxTimePeriod} by policy`,
|
||||||
|
title: "Error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{ action: ProjectPermissionActions.Read, allowed: data.read },
|
{ action: ProjectPermissionActions.Read, allowed: data.read },
|
||||||
{ action: ProjectPermissionActions.Create, allowed: data.create },
|
{ action: ProjectPermissionActions.Create, allowed: data.create },
|
||||||
|
|||||||
+21
-1
@@ -91,7 +91,8 @@ const formSchema = z
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.default([])
|
.default([])
|
||||||
.optional()
|
.optional(),
|
||||||
|
maxTimePeriod: z.string().trim().optional()
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (data.policyType === PolicyType.ChangePolicy) {
|
if (data.policyType === PolicyType.ChangePolicy) {
|
||||||
@@ -444,6 +445,25 @@ const Form = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{isAccessPolicyType && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="maxTimePeriod"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Max. Time Period"
|
||||||
|
tooltipText="The maximum amount of time someone can request access for. Ex: 1h, 3w, 30d"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="flex-shrink"
|
||||||
|
>
|
||||||
|
<Input {...field} value={field.value} placeholder="permanent" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{!isAccessPolicyType && (
|
{!isAccessPolicyType && (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
+222
-13
@@ -1,7 +1,14 @@
|
|||||||
import { ChangeEvent, DragEvent } from "react";
|
import { ChangeEvent, Dispatch, DragEvent, SetStateAction, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { subject } from "@casl/ability";
|
import { subject } from "@casl/ability";
|
||||||
import { faPlus, faUpload } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faArrowRight,
|
||||||
|
faAsterisk,
|
||||||
|
faComment,
|
||||||
|
faKey,
|
||||||
|
faPlus,
|
||||||
|
faUpload
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
@@ -9,8 +16,22 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
|
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
|
||||||
import { parseDotEnv, parseJson, parseYaml } from "@app/components/utilities/parseSecrets";
|
import {
|
||||||
import { Button, Lottie, Modal, ModalContent } from "@app/components/v2";
|
parseCsvToMatrix,
|
||||||
|
parseDotEnv,
|
||||||
|
parseJson,
|
||||||
|
parseYaml
|
||||||
|
} from "@app/components/utilities/parseSecrets";
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
FormLabel,
|
||||||
|
Lottie,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api";
|
import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api";
|
||||||
@@ -38,6 +59,84 @@ type Props = {
|
|||||||
isProtectedBranch?: boolean;
|
isProtectedBranch?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SecretMatrixMap = {
|
||||||
|
key: number;
|
||||||
|
value: number | null;
|
||||||
|
comment: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const popupKeys = ["importSecEnv", "confirmUpload", "pasteSecEnv", "importMatrixMap"] as const;
|
||||||
|
|
||||||
|
const MatrixImportModalTableRow = ({
|
||||||
|
importSecretMatrixMap,
|
||||||
|
setImportSecretMatrixMap,
|
||||||
|
headers,
|
||||||
|
mapKey
|
||||||
|
}: {
|
||||||
|
importSecretMatrixMap: SecretMatrixMap;
|
||||||
|
setImportSecretMatrixMap: Dispatch<SetStateAction<SecretMatrixMap>>;
|
||||||
|
headers: string[];
|
||||||
|
mapKey: keyof SecretMatrixMap;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td className="w-full">
|
||||||
|
<Select
|
||||||
|
value={importSecretMatrixMap[mapKey]?.toString() || (null as unknown as string)}
|
||||||
|
onValueChange={(v) =>
|
||||||
|
setImportSecretMatrixMap((ism) => ({
|
||||||
|
...ism,
|
||||||
|
[mapKey]: v ? parseInt(v, 10) : null
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
position="popper"
|
||||||
|
placeholder="Select an option..."
|
||||||
|
dropdownContainerClassName="max-w-none"
|
||||||
|
>
|
||||||
|
{mapKey !== "key" && <SelectItem value={null as unknown as string}>None</SelectItem>}
|
||||||
|
{headers.map((header, col) => {
|
||||||
|
return (
|
||||||
|
<SelectItem value={col.toString()} key={`${mapKey}-${header}`}>
|
||||||
|
{header}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</td>
|
||||||
|
<td className="whitespace-nowrap pl-5 pr-5">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<FontAwesomeIcon className="text-mineshaft-400" icon={faArrowRight} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="whitespace-nowrap">
|
||||||
|
<div className="flex h-full items-start justify-center">
|
||||||
|
<Badge className="pointer-events-none flex h-[36px] w-full items-center justify-center gap-1.5 whitespace-nowrap border border-mineshaft-600 bg-mineshaft-600 text-bunker-200">
|
||||||
|
{mapKey === "key" && (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faKey} />
|
||||||
|
<span>Secret Key</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{mapKey === "value" && (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faAsterisk} />
|
||||||
|
<span>Secret Value</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{mapKey === "comment" && (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faComment} />
|
||||||
|
<span>Comment</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const SecretDropzone = ({
|
export const SecretDropzone = ({
|
||||||
isSmaller,
|
isSmaller,
|
||||||
environments = [],
|
environments = [],
|
||||||
@@ -50,11 +149,14 @@ export const SecretDropzone = ({
|
|||||||
const [isDragActive, setDragActive] = useToggle();
|
const [isDragActive, setDragActive] = useToggle();
|
||||||
const [isLoading, setIsLoading] = useToggle();
|
const [isLoading, setIsLoading] = useToggle();
|
||||||
|
|
||||||
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
// Maps matrix columns to parts of a secret
|
||||||
"importSecEnv",
|
const [importSecretMatrixMap, setImportSecretMatrixMap] = useState<SecretMatrixMap>({
|
||||||
"confirmUpload",
|
key: 0,
|
||||||
"pasteSecEnv"
|
value: null,
|
||||||
] as const);
|
comment: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp(popupKeys);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { openPopUp } = usePopUpAction();
|
const { openPopUp } = usePopUpAction();
|
||||||
|
|
||||||
@@ -136,10 +238,17 @@ export const SecretDropzone = ({
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// const fileType = file.name.split('.')[1];
|
|
||||||
setIsLoading.on();
|
setIsLoading.on();
|
||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
if (!event?.target?.result) return;
|
if (!event?.target?.result) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Invalid file contents."
|
||||||
|
});
|
||||||
|
setIsLoading.off();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let env: TParsedEnv;
|
let env: TParsedEnv;
|
||||||
|
|
||||||
@@ -154,7 +263,22 @@ export const SecretDropzone = ({
|
|||||||
case "application/yaml":
|
case "application/yaml":
|
||||||
env = parseYaml(src);
|
env = parseYaml(src);
|
||||||
break;
|
break;
|
||||||
|
case "text/csv": {
|
||||||
|
const fullMatrix = parseCsvToMatrix(src);
|
||||||
|
if (!fullMatrix.length) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to find secrets in CSV file. File might be empty."
|
||||||
|
});
|
||||||
|
setIsLoading.off();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const headers = fullMatrix[0];
|
||||||
|
const matrix = fullMatrix.slice(1);
|
||||||
|
handlePopUpOpen("importMatrixMap", { headers, matrix });
|
||||||
|
setIsLoading.off();
|
||||||
|
return;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
env = parseDotEnv(src);
|
env = parseDotEnv(src);
|
||||||
break;
|
break;
|
||||||
@@ -171,6 +295,22 @@ export const SecretDropzone = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const finishMappedMatrixImport = (matrix: string[][]) => {
|
||||||
|
const env: TParsedEnv = {};
|
||||||
|
matrix.forEach((row) => {
|
||||||
|
const key = row[importSecretMatrixMap.key];
|
||||||
|
if (key) {
|
||||||
|
env[key] = {
|
||||||
|
value: importSecretMatrixMap.value ? row[importSecretMatrixMap.value] : "",
|
||||||
|
comments: importSecretMatrixMap.comment ? [row[importSecretMatrixMap.comment]] : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handlePopUpClose("importMatrixMap");
|
||||||
|
setImportSecretMatrixMap({ key: 0, value: null, comment: null });
|
||||||
|
handleParsedEnv(env);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDrop = (e: DragEvent) => {
|
const handleDrop = (e: DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -293,7 +433,7 @@ export const SecretDropzone = ({
|
|||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
type="file"
|
type="file"
|
||||||
className="absolute h-full w-full cursor-pointer opacity-0"
|
className="absolute h-full w-full cursor-pointer opacity-0"
|
||||||
accept=".txt,.env,.yml,.yaml,.json"
|
accept=".txt,.env,.yml,.yaml,.json,.csv"
|
||||||
onChange={handleFileUpload}
|
onChange={handleFileUpload}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -407,6 +547,75 @@ export const SecretDropzone = ({
|
|||||||
)}
|
)}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* Matrix Import Modal */}
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.importMatrixMap?.isOpen}
|
||||||
|
onOpenChange={(open) => handlePopUpToggle("importMatrixMap", open)}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title="Import Column Mapping"
|
||||||
|
subTitle="Map your data columns to different parts of the secret"
|
||||||
|
>
|
||||||
|
<div className="w-full overflow-hidden">
|
||||||
|
<table className="w-full table-auto">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-left">
|
||||||
|
<th>
|
||||||
|
<FormLabel tooltipClassName="max-w-sm" label="Import Column" />
|
||||||
|
</th>
|
||||||
|
<th />
|
||||||
|
<th className="whitespace-nowrap">
|
||||||
|
<FormLabel label="Resulting Import" />
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{/* Key */}
|
||||||
|
<MatrixImportModalTableRow
|
||||||
|
importSecretMatrixMap={importSecretMatrixMap}
|
||||||
|
setImportSecretMatrixMap={setImportSecretMatrixMap}
|
||||||
|
headers={popUp?.importMatrixMap.data?.headers || []}
|
||||||
|
mapKey="key"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Value */}
|
||||||
|
<MatrixImportModalTableRow
|
||||||
|
importSecretMatrixMap={importSecretMatrixMap}
|
||||||
|
setImportSecretMatrixMap={setImportSecretMatrixMap}
|
||||||
|
headers={popUp?.importMatrixMap.data?.headers || []}
|
||||||
|
mapKey="value"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Comment */}
|
||||||
|
<MatrixImportModalTableRow
|
||||||
|
importSecretMatrixMap={importSecretMatrixMap}
|
||||||
|
setImportSecretMatrixMap={setImportSecretMatrixMap}
|
||||||
|
headers={popUp?.importMatrixMap.data?.headers || []}
|
||||||
|
mapKey="comment"
|
||||||
|
/>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
popUp.importMatrixMap.data?.matrix
|
||||||
|
? finishMappedMatrixImport(popUp.importMatrixMap.data?.matrix)
|
||||||
|
: createNotification({
|
||||||
|
text: "Invalid secret matrix.",
|
||||||
|
type: "error"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isFullWidth
|
||||||
|
variant="outline_bg"
|
||||||
|
>
|
||||||
|
Import Secrets
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+10
-3
@@ -27,7 +27,10 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { hasSecretReference, SecretReferenceTree } from "@app/components/secrets/SecretReferenceDetails";
|
import {
|
||||||
|
hasSecretReference,
|
||||||
|
SecretReferenceTree
|
||||||
|
} from "@app/components/secrets/SecretReferenceDetails";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Drawer,
|
Drawer,
|
||||||
@@ -49,8 +52,12 @@ import {
|
|||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useProjectPermission,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
import { getProjectBaseURL } from "@app/helpers/project";
|
import { getProjectBaseURL } from "@app/helpers/project";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
|
|||||||
Reference in New Issue
Block a user