mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-15 09:37:43 +00:00
Compare commits
16 Commits
server-adm
...
move-sso-s
Author | SHA1 | Date | |
---|---|---|---|
1aa9be203e | |||
e35ac599f8 | |||
6d91297ca9 | |||
db369b8f51 | |||
a50a95ad6e | |||
4ec0031c42 | |||
a6edb67f58 | |||
aae5831f35 | |||
6f78a6b4c1 | |||
c2e326b95a | |||
97c96acea5 | |||
5e24015f2a | |||
f17e1f6699 | |||
e71b136859 | |||
7d2d69fc7d | |||
0569c7e692 |
@ -0,0 +1,55 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy)
|
||||
.whereNull("secretPath")
|
||||
.orWhere("secretPath", "");
|
||||
|
||||
const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy)
|
||||
.whereNull("secretPath")
|
||||
.orWhere("secretPath", "");
|
||||
|
||||
// update all the secret approval policies secretPath to be "/**"
|
||||
if (existingSecretApprovalPolicies.length) {
|
||||
await knex(TableName.SecretApprovalPolicy)
|
||||
.whereIn(
|
||||
"id",
|
||||
existingSecretApprovalPolicies.map((el) => el.id)
|
||||
)
|
||||
.update({
|
||||
secretPath: "/**"
|
||||
});
|
||||
}
|
||||
|
||||
// update all the access approval policies secretPath to be "/**"
|
||||
if (existingAccessApprovalPolicies.length) {
|
||||
await knex(TableName.AccessApprovalPolicy)
|
||||
.whereIn(
|
||||
"id",
|
||||
existingAccessApprovalPolicies.map((el) => el.id)
|
||||
)
|
||||
.update({
|
||||
secretPath: "/**"
|
||||
});
|
||||
}
|
||||
|
||||
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (table) => {
|
||||
table.string("secretPath").notNullable().alter();
|
||||
});
|
||||
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (table) => {
|
||||
table.string("secretPath").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (table) => {
|
||||
table.string("secretPath").nullable().alter();
|
||||
});
|
||||
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (table) => {
|
||||
table.string("secretPath").nullable().alter();
|
||||
});
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "@app/db/schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasCommitterCol = await knex.schema.hasColumn(TableName.SecretApprovalRequest, "committerUserId");
|
||||
|
||||
if (hasCommitterCol) {
|
||||
await knex.schema.alterTable(TableName.SecretApprovalRequest, (tb) => {
|
||||
tb.uuid("committerUserId").nullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
const hasRequesterCol = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "requestedByUserId");
|
||||
|
||||
if (hasRequesterCol) {
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequest, (tb) => {
|
||||
tb.dropForeign("requestedByUserId");
|
||||
tb.foreign("requestedByUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
// can't undo committer nullable
|
||||
|
||||
const hasRequesterCol = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "requestedByUserId");
|
||||
|
||||
if (hasRequesterCol) {
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequest, (tb) => {
|
||||
tb.dropForeign("requestedByUserId");
|
||||
tb.foreign("requestedByUserId").references("id").inTable(TableName.Users).onDelete("SET NULL");
|
||||
});
|
||||
}
|
||||
}
|
@ -14,8 +14,8 @@ export const AccessApprovalPoliciesApproversSchema = z.object({
|
||||
updatedAt: z.date(),
|
||||
approverUserId: z.string().uuid().nullable().optional(),
|
||||
approverGroupId: z.string().uuid().nullable().optional(),
|
||||
sequence: z.number().default(0).nullable().optional(),
|
||||
approvalsRequired: z.number().default(1).nullable().optional()
|
||||
sequence: z.number().default(1).nullable().optional(),
|
||||
approvalsRequired: z.number().nullable().optional()
|
||||
});
|
||||
|
||||
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;
|
||||
|
@ -11,7 +11,7 @@ export const AccessApprovalPoliciesSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string(),
|
||||
approvals: z.number().default(1),
|
||||
secretPath: z.string().nullable().optional(),
|
||||
secretPath: z.string(),
|
||||
envId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
|
@ -12,8 +12,8 @@ export const CertificateAuthoritiesSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
projectId: z.string(),
|
||||
enableDirectIssuance: z.boolean().default(true),
|
||||
status: z.string(),
|
||||
enableDirectIssuance: z.boolean().default(true),
|
||||
name: z.string()
|
||||
});
|
||||
|
||||
|
@ -25,8 +25,8 @@ export const CertificatesSchema = z.object({
|
||||
certificateTemplateId: z.string().uuid().nullable().optional(),
|
||||
keyUsages: z.string().array().nullable().optional(),
|
||||
extendedKeyUsages: z.string().array().nullable().optional(),
|
||||
pkiSubscriberId: z.string().uuid().nullable().optional(),
|
||||
projectId: z.string()
|
||||
projectId: z.string(),
|
||||
pkiSubscriberId: z.string().uuid().nullable().optional()
|
||||
});
|
||||
|
||||
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||
|
@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
|
||||
export const SecretApprovalPoliciesSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string(),
|
||||
secretPath: z.string().nullable().optional(),
|
||||
secretPath: z.string(),
|
||||
approvals: z.number().default(1),
|
||||
envId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
|
@ -18,7 +18,7 @@ export const SecretApprovalRequestsSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
isReplicated: z.boolean().nullable().optional(),
|
||||
committerUserId: z.string().uuid(),
|
||||
committerUserId: z.string().uuid().nullable().optional(),
|
||||
statusChangedByUserId: z.string().uuid().nullable().optional(),
|
||||
bypassReason: z.string().nullable().optional()
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
@ -19,7 +20,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
body: z.object({
|
||||
projectSlug: z.string().trim(),
|
||||
name: z.string().optional(),
|
||||
secretPath: z.string().trim().default("/"),
|
||||
secretPath: z.string().trim().min(1, { message: "Secret path cannot be empty" }).transform(removeTrailingSlash),
|
||||
environment: z.string(),
|
||||
approvers: z
|
||||
.discriminatedUnion("type", [
|
||||
@ -174,8 +175,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Secret path cannot be empty" })
|
||||
.optional()
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
approvers: z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({
|
||||
|
@ -23,10 +23,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
environment: z.string(),
|
||||
secretPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.nullable()
|
||||
.default("/")
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
.min(1, { message: "Secret path cannot be empty" })
|
||||
.transform((val) => removeTrailingSlash(val)),
|
||||
approvers: z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||
@ -100,10 +98,10 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
approvals: z.number().min(1).default(1),
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Secret path cannot be empty" })
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : undefined)),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
||||
allowedSelfApprovals: z.boolean().default(true)
|
||||
}),
|
||||
|
@ -58,7 +58,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
deletedAt: z.date().nullish(),
|
||||
allowedSelfApprovals: z.boolean()
|
||||
}),
|
||||
committerUser: approvalRequestUser,
|
||||
committerUser: approvalRequestUser.nullish(),
|
||||
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
||||
environment: z.string(),
|
||||
reviewers: z.object({ userId: z.string(), status: z.string() }).array(),
|
||||
@ -308,7 +308,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
}),
|
||||
environment: z.string(),
|
||||
statusChangedByUser: approvalRequestUser.optional(),
|
||||
committerUser: approvalRequestUser,
|
||||
committerUser: approvalRequestUser.nullish(),
|
||||
reviewers: approvalRequestUser.extend({ status: z.string(), comment: z.string().optional() }).array(),
|
||||
secretPath: z.string(),
|
||||
commits: secretRawSchema
|
||||
|
@ -53,7 +53,7 @@ export interface TAccessApprovalPolicyDALFactory
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -93,7 +93,7 @@ export interface TAccessApprovalPolicyDALFactory
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -116,7 +116,7 @@ export interface TAccessApprovalPolicyDALFactory
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
findLastValidPolicy: (
|
||||
@ -138,7 +138,7 @@ export interface TAccessApprovalPolicyDALFactory
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}
|
||||
| undefined
|
||||
@ -190,7 +190,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
deleteAccessApprovalPolicy: ({
|
||||
@ -214,7 +214,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -252,7 +252,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
getAccessApprovalPolicyByProjectSlug: ({
|
||||
@ -286,7 +286,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -337,7 +337,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
|
@ -60,6 +60,26 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
accessApprovalRequestReviewerDAL,
|
||||
orgMembershipDAL
|
||||
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
||||
const $policyExists = async ({
|
||||
envId,
|
||||
secretPath,
|
||||
policyId
|
||||
}: {
|
||||
envId: string;
|
||||
secretPath: string;
|
||||
policyId?: string;
|
||||
}) => {
|
||||
const policy = await accessApprovalPolicyDAL
|
||||
.findOne({
|
||||
envId,
|
||||
secretPath,
|
||||
deletedAt: null
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||
};
|
||||
|
||||
const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({
|
||||
name,
|
||||
actor,
|
||||
@ -106,6 +126,12 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
|
||||
|
||||
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||
throw new BadRequestError({
|
||||
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
||||
});
|
||||
}
|
||||
|
||||
let approverUserIds = userApprovers;
|
||||
if (userApproverNames.length) {
|
||||
const approverUsersInDB = await userDAL.find({
|
||||
@ -279,7 +305,11 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
) as { username: string; sequence?: number }[];
|
||||
|
||||
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
||||
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Approval policy not found" });
|
||||
if (!accessApprovalPolicy) {
|
||||
throw new NotFoundError({
|
||||
message: `Access approval policy with ID '${policyId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
const currentApprovals = approvals || accessApprovalPolicy.approvals;
|
||||
if (
|
||||
@ -290,9 +320,18 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||
}
|
||||
|
||||
if (!accessApprovalPolicy) {
|
||||
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
||||
if (
|
||||
await $policyExists({
|
||||
envId: accessApprovalPolicy.envId,
|
||||
secretPath: secretPath || accessApprovalPolicy.secretPath,
|
||||
policyId: accessApprovalPolicy.id
|
||||
})
|
||||
) {
|
||||
throw new BadRequestError({
|
||||
message: `A policy for secret path '${secretPath}' already exists in environment '${accessApprovalPolicy.environment.slug}'`
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
|
@ -122,7 +122,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
deleteAccessApprovalPolicy: ({
|
||||
@ -146,7 +146,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -218,7 +218,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -269,7 +269,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
|
@ -1711,7 +1711,7 @@ interface SecretApprovalReopened {
|
||||
interface SecretApprovalRequest {
|
||||
type: EventType.SECRET_APPROVAL_REQUEST;
|
||||
metadata: {
|
||||
committedBy: string;
|
||||
committedBy?: string | null;
|
||||
secretApprovalRequestSlug: string;
|
||||
secretApprovalRequestId: string;
|
||||
eventType: SecretApprovalEvent;
|
||||
|
@ -361,13 +361,6 @@ export const ldapConfigServiceFactory = ({
|
||||
});
|
||||
} else {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
||||
});
|
||||
}
|
||||
|
||||
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||
throw new BadRequestError({
|
||||
|
@ -1,5 +1,4 @@
|
||||
export const BillingPlanRows = {
|
||||
MemberLimit: { name: "Organization member limit", field: "memberLimit" },
|
||||
IdentityLimit: { name: "Organization identity limit", field: "identityLimit" },
|
||||
WorkspaceLimit: { name: "Project limit", field: "workspaceLimit" },
|
||||
EnvironmentLimit: { name: "Environment limit", field: "environmentLimit" },
|
||||
|
@ -442,9 +442,7 @@ export const licenseServiceFactory = ({
|
||||
rows: data.rows.map((el) => {
|
||||
let used = "-";
|
||||
|
||||
if (el.name === BillingPlanRows.MemberLimit.name) {
|
||||
used = orgMembersUsed.toString();
|
||||
} else if (el.name === BillingPlanRows.WorkspaceLimit.name) {
|
||||
if (el.name === BillingPlanRows.WorkspaceLimit.name) {
|
||||
used = projectCount.toString();
|
||||
} else if (el.name === BillingPlanRows.IdentityLimit.name) {
|
||||
used = (identityUsed + orgMembersUsed).toString();
|
||||
@ -464,12 +462,10 @@ export const licenseServiceFactory = ({
|
||||
const allowed = onPremFeatures[field as keyof TFeatureSet];
|
||||
let used = "-";
|
||||
|
||||
if (field === BillingPlanRows.MemberLimit.field) {
|
||||
used = orgMembersUsed.toString();
|
||||
} else if (field === BillingPlanRows.WorkspaceLimit.field) {
|
||||
if (field === BillingPlanRows.WorkspaceLimit.field) {
|
||||
used = projectCount.toString();
|
||||
} else if (field === BillingPlanRows.IdentityLimit.field) {
|
||||
used = identityUsed.toString();
|
||||
used = (identityUsed + orgMembersUsed).toString();
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -311,13 +311,6 @@ export const samlConfigServiceFactory = ({
|
||||
});
|
||||
} else {
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
||||
});
|
||||
}
|
||||
|
||||
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||
throw new BadRequestError({
|
||||
|
@ -55,6 +55,26 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
licenseService,
|
||||
secretApprovalRequestDAL
|
||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||
const $policyExists = async ({
|
||||
envId,
|
||||
secretPath,
|
||||
policyId
|
||||
}: {
|
||||
envId: string;
|
||||
secretPath: string;
|
||||
policyId?: string;
|
||||
}) => {
|
||||
const policy = await secretApprovalPolicyDAL
|
||||
.findOne({
|
||||
envId,
|
||||
secretPath,
|
||||
deletedAt: null
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||
};
|
||||
|
||||
const createSecretApprovalPolicy = async ({
|
||||
name,
|
||||
actor,
|
||||
@ -106,10 +126,17 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
}
|
||||
|
||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||
if (!env)
|
||||
if (!env) {
|
||||
throw new NotFoundError({
|
||||
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
||||
});
|
||||
}
|
||||
|
||||
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||
throw new BadRequestError({
|
||||
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
||||
});
|
||||
}
|
||||
|
||||
let groupBypassers: string[] = [];
|
||||
let bypasserUserIds: string[] = [];
|
||||
@ -260,6 +287,18 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
await $policyExists({
|
||||
envId: secretApprovalPolicy.envId,
|
||||
secretPath: secretPath || secretApprovalPolicy.secretPath,
|
||||
policyId: secretApprovalPolicy.id
|
||||
})
|
||||
) {
|
||||
throw new BadRequestError({
|
||||
message: `A policy for secret path '${secretPath}' already exists in environment '${secretApprovalPolicy.environment.slug}'`
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
|
@ -4,7 +4,7 @@ import { ApproverType, BypasserType } from "../access-approval-policy/access-app
|
||||
|
||||
export type TCreateSapDTO = {
|
||||
approvals: number;
|
||||
secretPath?: string | null;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||
bypassers?: (
|
||||
@ -20,7 +20,7 @@ export type TCreateSapDTO = {
|
||||
export type TUpdateSapDTO = {
|
||||
secretPolicyId: string;
|
||||
approvals?: number;
|
||||
secretPath?: string | null;
|
||||
secretPath?: string;
|
||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||
bypassers?: (
|
||||
| { type: BypasserType.Group; id: string }
|
||||
|
@ -45,7 +45,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`${TableName.SecretApprovalRequest}.statusChangedByUserId`,
|
||||
`statusChangedByUser.id`
|
||||
)
|
||||
.join<TUsers>(
|
||||
.leftJoin<TUsers>(
|
||||
db(TableName.Users).as("committerUser"),
|
||||
`${TableName.SecretApprovalRequest}.committerUserId`,
|
||||
`committerUser.id`
|
||||
@ -173,13 +173,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
username: el.statusChangedByUserUsername
|
||||
}
|
||||
: undefined,
|
||||
committerUser: {
|
||||
userId: el.committerUserId,
|
||||
email: el.committerUserEmail,
|
||||
firstName: el.committerUserFirstName,
|
||||
lastName: el.committerUserLastName,
|
||||
username: el.committerUserUsername
|
||||
},
|
||||
committerUser: el.committerUserId
|
||||
? {
|
||||
userId: el.committerUserId,
|
||||
email: el.committerUserEmail,
|
||||
firstName: el.committerUserFirstName,
|
||||
lastName: el.committerUserLastName,
|
||||
username: el.committerUserUsername
|
||||
}
|
||||
: null,
|
||||
policy: {
|
||||
id: el.policyId,
|
||||
name: el.policyName,
|
||||
@ -377,7 +379,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
|
||||
`bypasserUserGroupMembership.groupId`
|
||||
)
|
||||
.join<TUsers>(
|
||||
.leftJoin<TUsers>(
|
||||
db(TableName.Users).as("committerUser"),
|
||||
`${TableName.SecretApprovalRequest}.committerUserId`,
|
||||
`committerUser.id`
|
||||
@ -488,13 +490,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
enforcementLevel: el.policyEnforcementLevel,
|
||||
allowedSelfApprovals: el.policyAllowedSelfApprovals
|
||||
},
|
||||
committerUser: {
|
||||
userId: el.committerUserId,
|
||||
email: el.committerUserEmail,
|
||||
firstName: el.committerUserFirstName,
|
||||
lastName: el.committerUserLastName,
|
||||
username: el.committerUserUsername
|
||||
}
|
||||
committerUser: el.committerUserId
|
||||
? {
|
||||
userId: el.committerUserId,
|
||||
email: el.committerUserEmail,
|
||||
firstName: el.committerUserFirstName,
|
||||
lastName: el.committerUserLastName,
|
||||
username: el.committerUserUsername
|
||||
}
|
||||
: null
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
@ -581,7 +585,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
|
||||
`bypasserUserGroupMembership.groupId`
|
||||
)
|
||||
.join<TUsers>(
|
||||
.leftJoin<TUsers>(
|
||||
db(TableName.Users).as("committerUser"),
|
||||
`${TableName.SecretApprovalRequest}.committerUserId`,
|
||||
`committerUser.id`
|
||||
@ -693,13 +697,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
enforcementLevel: el.policyEnforcementLevel,
|
||||
allowedSelfApprovals: el.policyAllowedSelfApprovals
|
||||
},
|
||||
committerUser: {
|
||||
userId: el.committerUserId,
|
||||
email: el.committerUserEmail,
|
||||
firstName: el.committerUserFirstName,
|
||||
lastName: el.committerUserLastName,
|
||||
username: el.committerUserUsername
|
||||
}
|
||||
committerUser: el.committerUserId
|
||||
? {
|
||||
userId: el.committerUserId,
|
||||
email: el.committerUserEmail,
|
||||
firstName: el.committerUserFirstName,
|
||||
lastName: el.committerUserLastName,
|
||||
username: el.committerUserUsername
|
||||
}
|
||||
: null
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
|
@ -1320,7 +1320,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
});
|
||||
|
||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||
const user = await userDAL.findById(actorId);
|
||||
|
||||
await triggerWorkflowIntegrationNotification({
|
||||
input: {
|
||||
@ -1657,7 +1657,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
return { ...doc, commits: approvalCommits };
|
||||
});
|
||||
|
||||
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||
const user = await userDAL.findById(actorId);
|
||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||
|
||||
await triggerWorkflowIntegrationNotification({
|
||||
|
@ -37,7 +37,8 @@ import {
|
||||
TQueueSecretScanningDataSourceFullScan,
|
||||
TQueueSecretScanningResourceDiffScan,
|
||||
TQueueSecretScanningSendNotification,
|
||||
TSecretScanningDataSourceWithConnection
|
||||
TSecretScanningDataSourceWithConnection,
|
||||
TSecretScanningFinding
|
||||
} from "./secret-scanning-v2-types";
|
||||
|
||||
type TSecretRotationV2QueueServiceFactoryDep = {
|
||||
@ -459,13 +460,16 @@ export const secretScanningV2QueueServiceFactory = async ({
|
||||
const newFindings = allFindings.filter((finding) => finding.scanId === scanId);
|
||||
|
||||
if (newFindings.length) {
|
||||
const finding = newFindings[0] as TSecretScanningFinding;
|
||||
await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, {
|
||||
status: SecretScanningScanStatus.Completed,
|
||||
resourceName: resource.name,
|
||||
isDiffScan: true,
|
||||
dataSource,
|
||||
numberOfSecrets: newFindings.length,
|
||||
scanId
|
||||
scanId,
|
||||
authorName: finding?.details?.author,
|
||||
authorEmail: finding?.details?.email
|
||||
});
|
||||
}
|
||||
|
||||
@ -582,8 +586,8 @@ export const secretScanningV2QueueServiceFactory = async ({
|
||||
substitutions:
|
||||
payload.status === SecretScanningScanStatus.Completed
|
||||
? {
|
||||
authorName: "Jim",
|
||||
authorEmail: "jim@infisical.com",
|
||||
authorName: payload.authorName,
|
||||
authorEmail: payload.authorEmail,
|
||||
resourceName,
|
||||
numberOfSecrets: payload.numberOfSecrets,
|
||||
isDiffScan: payload.isDiffScan,
|
||||
|
@ -119,7 +119,14 @@ export type TQueueSecretScanningSendNotification = {
|
||||
resourceName: string;
|
||||
} & (
|
||||
| { status: SecretScanningScanStatus.Failed; errorMessage: string }
|
||||
| { status: SecretScanningScanStatus.Completed; numberOfSecrets: number; scanId: string; isDiffScan: boolean }
|
||||
| {
|
||||
status: SecretScanningScanStatus.Completed;
|
||||
numberOfSecrets: number;
|
||||
scanId: string;
|
||||
isDiffScan: boolean;
|
||||
authorName?: string;
|
||||
authorEmail?: string;
|
||||
}
|
||||
);
|
||||
|
||||
export type TCloneRepository = {
|
||||
|
@ -912,14 +912,6 @@ export const orgServiceFactory = ({
|
||||
|
||||
// if there exist no org membership we set is as given by the request
|
||||
if (!inviteeOrgMembership) {
|
||||
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||
throw new BadRequestError({
|
||||
name: "InviteUser",
|
||||
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
||||
});
|
||||
}
|
||||
|
||||
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||
throw new BadRequestError({
|
||||
|
@ -1,6 +1,32 @@
|
||||
FROM node:20-alpine
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
RUN npm install -g mint
|
||||
|
||||
RUN npm install -g mint@4.2.13
|
||||
|
||||
COPY . .
|
||||
|
||||
# Install a local version of our OpenAPI spec
|
||||
RUN apk add --no-cache wget jq && \
|
||||
wget -O spec.json https://app.infisical.com/api/docs/json && \
|
||||
jq '.api.openapi = "./spec.json"' docs.json > temp.json && \
|
||||
mv temp.json docs.json
|
||||
|
||||
# Run mint dev briefly to download the web client
|
||||
RUN timeout 30 mint dev || true
|
||||
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm install -g mint@4.2.13
|
||||
|
||||
COPY . .
|
||||
|
||||
COPY --from=builder /root/.mintlify /root/.mintlify
|
||||
COPY --from=builder /app/docs.json /app/docs.json
|
||||
COPY --from=builder /app/spec.json /app/spec.json
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["mint", "dev"]
|
||||
|
@ -78,7 +78,10 @@
|
||||
},
|
||||
{
|
||||
"group": "Infisical SSH",
|
||||
"pages": ["documentation/platform/ssh/overview", "documentation/platform/ssh/host-groups"]
|
||||
"pages": [
|
||||
"documentation/platform/ssh/overview",
|
||||
"documentation/platform/ssh/host-groups"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Key Management (KMS)",
|
||||
@ -375,7 +378,10 @@
|
||||
},
|
||||
{
|
||||
"group": "Architecture",
|
||||
"pages": ["internals/architecture/components", "internals/architecture/cloud"]
|
||||
"pages": [
|
||||
"internals/architecture/components",
|
||||
"internals/architecture/cloud"
|
||||
]
|
||||
},
|
||||
"internals/security",
|
||||
"internals/service-tokens"
|
||||
@ -546,7 +552,10 @@
|
||||
"integrations/cloud/gcp-secret-manager",
|
||||
{
|
||||
"group": "Cloudflare",
|
||||
"pages": ["integrations/cloud/cloudflare-pages", "integrations/cloud/cloudflare-workers"]
|
||||
"pages": [
|
||||
"integrations/cloud/cloudflare-pages",
|
||||
"integrations/cloud/cloudflare-workers"
|
||||
]
|
||||
},
|
||||
"integrations/cloud/terraform-cloud",
|
||||
"integrations/cloud/databricks",
|
||||
@ -658,7 +667,11 @@
|
||||
"cli/commands/reset",
|
||||
{
|
||||
"group": "infisical scan",
|
||||
"pages": ["cli/commands/scan", "cli/commands/scan-git-changes", "cli/commands/scan-install"]
|
||||
"pages": [
|
||||
"cli/commands/scan",
|
||||
"cli/commands/scan-git-changes",
|
||||
"cli/commands/scan-install"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -982,7 +995,9 @@
|
||||
"pages": [
|
||||
{
|
||||
"group": "Kubernetes",
|
||||
"pages": ["api-reference/endpoints/dynamic-secrets/kubernetes/create-lease"]
|
||||
"pages": [
|
||||
"api-reference/endpoints/dynamic-secrets/kubernetes/create-lease"
|
||||
]
|
||||
},
|
||||
"api-reference/endpoints/dynamic-secrets/create",
|
||||
"api-reference/endpoints/dynamic-secrets/update",
|
||||
|
@ -29,10 +29,6 @@ export const ROUTE_PATHS = Object.freeze({
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback"
|
||||
)
|
||||
},
|
||||
SsoPage: setRoute(
|
||||
"/organization/sso",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/sso"
|
||||
),
|
||||
SecretSharing: setRoute(
|
||||
"/organization/secret-sharing",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/"
|
||||
|
@ -170,7 +170,7 @@ export type TCreateAccessPolicyDTO = {
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
approvals?: number;
|
||||
secretPath?: string;
|
||||
secretPath: string;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||
|
@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { organizationKeys } from "../organization/queries";
|
||||
import { subscriptionQueryKeys } from "../subscriptions/queries";
|
||||
import { identitiesKeys } from "./queries";
|
||||
import {
|
||||
AddIdentityAliCloudAuthDTO,
|
||||
@ -82,6 +83,9 @@ export const useCreateIdentity = () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -123,6 +127,9 @@ export const useDeleteIdentity = () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -49,7 +49,7 @@ export type TCreateSecretPolicyDTO = {
|
||||
workspaceId: string;
|
||||
name?: string;
|
||||
environment: string;
|
||||
secretPath?: string | null;
|
||||
secretPath: string;
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
approvals?: number;
|
||||
@ -62,7 +62,7 @@ export type TUpdateSecretPolicyDTO = {
|
||||
name?: string;
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
secretPath?: string | null;
|
||||
secretPath?: string;
|
||||
approvals?: number;
|
||||
allowedSelfApprovals?: boolean;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
|
@ -9,6 +9,7 @@ import { APIKeyDataV2 } from "../apiKeys/types";
|
||||
import { MfaMethod } from "../auth/types";
|
||||
import { TGroupWithProjectMemberships } from "../groups/types";
|
||||
import { setAuthToken } from "../reactQuery";
|
||||
import { subscriptionQueryKeys } from "../subscriptions/queries";
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { userKeys } from "./query-keys";
|
||||
import {
|
||||
@ -188,6 +189,9 @@ export const useAddUsersToOrg = () => {
|
||||
},
|
||||
onSuccess: (_, { organizationId, projects }) => {
|
||||
queryClient.invalidateQueries({ queryKey: userKeys.getOrgUsers(organizationId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
|
||||
});
|
||||
|
||||
projects?.forEach((project) => {
|
||||
if (project.slug) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
faBook,
|
||||
faCheckCircle,
|
||||
faCog,
|
||||
faCubes,
|
||||
faDoorClosed,
|
||||
@ -100,18 +99,6 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/sso">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="mr-4" />
|
||||
</div>
|
||||
SSO Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/settings">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
|
@ -39,10 +39,6 @@ export const OrgMembersSection = () => {
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteOrgMembership();
|
||||
const { mutateAsync: updateOrgMembership } = useUpdateOrgMembership();
|
||||
|
||||
const isMoreUsersAllowed = subscription?.memberLimit
|
||||
? subscription.membersUsed < subscription.memberLimit
|
||||
: true;
|
||||
|
||||
const isMoreIdentitiesAllowed = subscription?.identityLimit
|
||||
? subscription.identitiesUsed < subscription.identityLimit
|
||||
: true;
|
||||
@ -58,7 +54,7 @@ export const OrgMembersSection = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!isMoreUsersAllowed || !isMoreIdentitiesAllowed) && !isEnterprise) {
|
||||
if (!isMoreIdentitiesAllowed && !isEnterprise) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description: "You can add more members if you upgrade your Infisical plan."
|
||||
});
|
||||
|
@ -1,6 +1,3 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
|
||||
@ -11,21 +8,6 @@ export const OrgSecurityTab = withPermission(
|
||||
() => {
|
||||
return (
|
||||
<>
|
||||
<NoticeBannerV2
|
||||
className="mx-auto mb-4"
|
||||
titleClassName="text-base"
|
||||
title="Single Sign-On (SSO) Settings"
|
||||
>
|
||||
<p className="mt-1 text-mineshaft-300">
|
||||
SSO Settings have been relocated:{" "}
|
||||
<Link
|
||||
className="text-mineshaft-200 underline underline-offset-2"
|
||||
to="/organization/sso"
|
||||
>
|
||||
Click here to view SSO Settings
|
||||
</Link>
|
||||
</p>
|
||||
</NoticeBannerV2>
|
||||
<OrgGenericAuthSection />
|
||||
<OrgUserAccessTokenLimitSection />
|
||||
</>
|
||||
|
@ -9,8 +9,10 @@ import { ImportTab } from "../ImportTab";
|
||||
import { KmipTab } from "../KmipTab/OrgKmipTab";
|
||||
import { OrgEncryptionTab } from "../OrgEncryptionTab";
|
||||
import { OrgGeneralTab } from "../OrgGeneralTab";
|
||||
import { OrgProvisioningTab } from "../OrgProvisioningTab";
|
||||
import { OrgSecurityTab } from "../OrgSecurityTab";
|
||||
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab";
|
||||
import { OrgSsoTab } from "../OrgSsoTab";
|
||||
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab";
|
||||
import { ProjectTemplatesTab } from "../ProjectTemplatesTab";
|
||||
|
||||
export const OrgTabGroup = () => {
|
||||
@ -19,6 +21,16 @@ export const OrgTabGroup = () => {
|
||||
});
|
||||
const tabs = [
|
||||
{ name: "General", key: "tab-org-general", component: OrgGeneralTab },
|
||||
{
|
||||
name: "SSO",
|
||||
key: "sso-settings",
|
||||
component: OrgSsoTab
|
||||
},
|
||||
{
|
||||
name: "Provisioning",
|
||||
key: "provisioning-settings",
|
||||
component: OrgProvisioningTab
|
||||
},
|
||||
{ name: "Security", key: "tab-org-security", component: OrgSecurityTab },
|
||||
{ name: "Encryption", key: "tab-org-encryption", component: OrgEncryptionTab },
|
||||
{
|
||||
|
@ -0,0 +1 @@
|
||||
export * from "./OrgWorkflowIntegrationTab";
|
@ -1,21 +0,0 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
|
||||
import { SsoTabGroup } from "./components/SsoTabGroup";
|
||||
|
||||
export const SsoPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Single Sign-On (SSO)</title>
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="Single Sign-On (SSO)" />
|
||||
<SsoTabGroup />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
|
||||
import { OrgProvisioningTab } from "../OrgProvisioningTab";
|
||||
import { OrgSsoTab } from "../OrgSsoTab";
|
||||
|
||||
export const SsoTabGroup = () => {
|
||||
const search = useSearch({
|
||||
from: ROUTE_PATHS.Organization.SsoPage.id
|
||||
});
|
||||
const tabs = [
|
||||
{ name: "General", key: "tab-sso-auth", component: OrgSsoTab },
|
||||
{ name: "Provisioning", key: "tab-sso-identity", component: OrgProvisioningTab }
|
||||
];
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
|
||||
|
||||
return (
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab value={tab.key} key={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
{tabs.map(({ key, component: Component }) => (
|
||||
<TabPanel value={key} key={`tab-panel-${key}`}>
|
||||
<Component />
|
||||
</TabPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export { SsoTabGroup } from "./SsoTabGroup";
|
@ -1,26 +0,0 @@
|
||||
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SsoPage } from "./SsoPage";
|
||||
|
||||
const SettingsPageQueryParams = z.object({
|
||||
selectedTab: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/sso"
|
||||
)({
|
||||
component: SsoPage,
|
||||
validateSearch: zodValidator(SettingsPageQueryParams),
|
||||
search: {
|
||||
middlewares: [stripSearchParams({ selectedTab: "" })]
|
||||
},
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Single Sign-On (SSO)"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
@ -55,7 +55,7 @@ const formSchema = z
|
||||
.object({
|
||||
environment: z.object({ slug: z.string(), name: z.string() }),
|
||||
name: z.string().optional(),
|
||||
secretPath: z.string().optional(),
|
||||
secretPath: z.string().trim().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
userApprovers: z
|
||||
.object({ type: z.literal(ApproverType.User), id: z.string() })
|
||||
@ -93,20 +93,19 @@ const formSchema = z
|
||||
.optional()
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (
|
||||
data.policyType === PolicyType.ChangePolicy &&
|
||||
!(data.groupApprovers.length || data.userApprovers.length)
|
||||
) {
|
||||
ctx.addIssue({
|
||||
path: ["userApprovers"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one approver should be provided"
|
||||
});
|
||||
ctx.addIssue({
|
||||
path: ["groupApprovers"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one approver should be provided"
|
||||
});
|
||||
if (data.policyType === PolicyType.ChangePolicy) {
|
||||
if (!(data.groupApprovers.length || data.userApprovers.length)) {
|
||||
ctx.addIssue({
|
||||
path: ["userApprovers"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one approver should be provided"
|
||||
});
|
||||
ctx.addIssue({
|
||||
path: ["groupApprovers"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one approver should be provided"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -127,6 +126,7 @@ const Form = ({
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
resetField,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TFormSchema>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@ -177,6 +177,7 @@ const Form = ({
|
||||
: undefined,
|
||||
defaultValues: !editValues
|
||||
? {
|
||||
secretPath: "/",
|
||||
sequenceApprovers: [{ approvals: 1 }]
|
||||
}
|
||||
: undefined
|
||||
@ -405,7 +406,10 @@ const Form = ({
|
||||
<Select
|
||||
isDisabled={isEditMode}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val as PolicyType)}
|
||||
onValueChange={(val) => {
|
||||
onChange(val as PolicyType);
|
||||
resetField("secretPath");
|
||||
}}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{Object.values(PolicyType).map((policyType) => {
|
||||
@ -465,6 +469,7 @@ const Form = ({
|
||||
<FormControl
|
||||
tooltipText="Secret paths support glob patterns. For example, '/**' will match all paths."
|
||||
label="Secret Path"
|
||||
isRequired
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
className="flex-1"
|
||||
|
@ -338,8 +338,14 @@ export const SecretApprovalRequest = () => {
|
||||
</div>
|
||||
<span className="text-xs leading-3 text-gray-500">
|
||||
Opened {formatDistance(new Date(createdAt), new Date())} ago by{" "}
|
||||
{committerUser?.firstName || ""} {committerUser?.lastName || ""} (
|
||||
{committerUser?.email})
|
||||
{committerUser ? (
|
||||
<>
|
||||
{committerUser?.firstName || ""} {committerUser?.lastName || ""} (
|
||||
{committerUser?.email})
|
||||
</>
|
||||
) : (
|
||||
<span className="text-gray-600">Deleted User</span>
|
||||
)}
|
||||
{!isReviewed && status === "open" && " - Review required"}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -250,10 +250,17 @@ export const SecretApprovalRequestChanges = ({
|
||||
secretApprovalRequestDetails.isReplicated
|
||||
)}
|
||||
</div>
|
||||
<span className="-mt-1 flex items-center space-x-2 text-xs text-gray-400">
|
||||
By {secretApprovalRequestDetails?.committerUser?.firstName} (
|
||||
{secretApprovalRequestDetails?.committerUser?.email})
|
||||
</span>
|
||||
<p className="-mt-1 text-xs text-gray-400">
|
||||
By{" "}
|
||||
{secretApprovalRequestDetails?.committerUser ? (
|
||||
<>
|
||||
{secretApprovalRequestDetails?.committerUser?.firstName} (
|
||||
{secretApprovalRequestDetails?.committerUser?.email})
|
||||
</>
|
||||
) : (
|
||||
<span className="text-gray-500">Deleted User</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{!hasMerged &&
|
||||
secretApprovalRequestDetails.status === "open" &&
|
||||
|
@ -47,7 +47,6 @@ import { Route as adminEnvironmentPageRouteImport } from './pages/admin/Environm
|
||||
import { Route as adminEncryptionPageRouteImport } from './pages/admin/EncryptionPage/route'
|
||||
import { Route as adminCachingPageRouteImport } from './pages/admin/CachingPage/route'
|
||||
import { Route as adminAuthenticationPageRouteImport } from './pages/admin/AuthenticationPage/route'
|
||||
import { Route as organizationSsoPageRouteImport } from './pages/organization/SsoPage/route'
|
||||
import { Route as organizationProjectsPageRouteImport } from './pages/organization/ProjectsPage/route'
|
||||
import { Route as organizationBillingPageRouteImport } from './pages/organization/BillingPage/route'
|
||||
import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route'
|
||||
@ -591,12 +590,6 @@ const adminAuthenticationPageRouteRoute =
|
||||
getParentRoute: () => adminLayoutRoute,
|
||||
} as any)
|
||||
|
||||
const organizationSsoPageRouteRoute = organizationSsoPageRouteImport.update({
|
||||
id: '/sso',
|
||||
path: '/sso',
|
||||
getParentRoute: () => AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
|
||||
} as any)
|
||||
|
||||
const organizationProjectsPageRouteRoute =
|
||||
organizationProjectsPageRouteImport.update({
|
||||
id: '/projects',
|
||||
@ -2159,13 +2152,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof organizationProjectsPageRouteImport
|
||||
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
|
||||
}
|
||||
'/_authenticate/_inject-org-details/_org-layout/organization/sso': {
|
||||
id: '/_authenticate/_inject-org-details/_org-layout/organization/sso'
|
||||
path: '/sso'
|
||||
fullPath: '/organization/sso'
|
||||
preLoaderRoute: typeof organizationSsoPageRouteImport
|
||||
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
|
||||
}
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/authentication': {
|
||||
id: '/_authenticate/_inject-org-details/admin/_admin-layout/authentication'
|
||||
path: '/authentication'
|
||||
@ -3428,7 +3414,6 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren {
|
||||
organizationAuditLogsPageRouteRoute: typeof organizationAuditLogsPageRouteRoute
|
||||
organizationBillingPageRouteRoute: typeof organizationBillingPageRouteRoute
|
||||
organizationProjectsPageRouteRoute: typeof organizationProjectsPageRouteRoute
|
||||
organizationSsoPageRouteRoute: typeof organizationSsoPageRouteRoute
|
||||
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
|
||||
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
|
||||
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren
|
||||
@ -3447,7 +3432,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica
|
||||
organizationAuditLogsPageRouteRoute: organizationAuditLogsPageRouteRoute,
|
||||
organizationBillingPageRouteRoute: organizationBillingPageRouteRoute,
|
||||
organizationProjectsPageRouteRoute: organizationProjectsPageRouteRoute,
|
||||
organizationSsoPageRouteRoute: organizationSsoPageRouteRoute,
|
||||
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute:
|
||||
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren,
|
||||
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute:
|
||||
@ -4345,7 +4329,6 @@ export interface FileRoutesByFullPath {
|
||||
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
||||
'/organization/billing': typeof organizationBillingPageRouteRoute
|
||||
'/organization/projects': typeof organizationProjectsPageRouteRoute
|
||||
'/organization/sso': typeof organizationSsoPageRouteRoute
|
||||
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
||||
'/admin/caching': typeof adminCachingPageRouteRoute
|
||||
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
||||
@ -4542,7 +4525,6 @@ export interface FileRoutesByTo {
|
||||
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
||||
'/organization/billing': typeof organizationBillingPageRouteRoute
|
||||
'/organization/projects': typeof organizationProjectsPageRouteRoute
|
||||
'/organization/sso': typeof organizationSsoPageRouteRoute
|
||||
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
||||
'/admin/caching': typeof adminCachingPageRouteRoute
|
||||
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
||||
@ -4739,7 +4721,6 @@ export interface FileRoutesById {
|
||||
'/_authenticate/_inject-org-details/_org-layout/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/_org-layout/organization/billing': typeof organizationBillingPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/_org-layout/organization/projects': typeof organizationProjectsPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/_org-layout/organization/sso': typeof organizationSsoPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/authentication': typeof adminAuthenticationPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/caching': typeof adminCachingPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/encryption': typeof adminEncryptionPageRouteRoute
|
||||
@ -4949,7 +4930,6 @@ export interface FileRouteTypes {
|
||||
| '/organization/audit-logs'
|
||||
| '/organization/billing'
|
||||
| '/organization/projects'
|
||||
| '/organization/sso'
|
||||
| '/admin/authentication'
|
||||
| '/admin/caching'
|
||||
| '/admin/encryption'
|
||||
@ -5145,7 +5125,6 @@ export interface FileRouteTypes {
|
||||
| '/organization/audit-logs'
|
||||
| '/organization/billing'
|
||||
| '/organization/projects'
|
||||
| '/organization/sso'
|
||||
| '/admin/authentication'
|
||||
| '/admin/caching'
|
||||
| '/admin/encryption'
|
||||
@ -5340,7 +5319,6 @@ export interface FileRouteTypes {
|
||||
| '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs'
|
||||
| '/_authenticate/_inject-org-details/_org-layout/organization/billing'
|
||||
| '/_authenticate/_inject-org-details/_org-layout/organization/projects'
|
||||
| '/_authenticate/_inject-org-details/_org-layout/organization/sso'
|
||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/authentication'
|
||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/caching'
|
||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/encryption'
|
||||
@ -5732,7 +5710,6 @@ export const routeTree = rootRoute
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/audit-logs",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/billing",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/projects",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/sso",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/app-connections",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/gateways",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing",
|
||||
@ -5782,10 +5759,6 @@ export const routeTree = rootRoute
|
||||
"filePath": "organization/ProjectsPage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
|
||||
},
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/sso": {
|
||||
"filePath": "organization/SsoPage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
|
||||
},
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/authentication": {
|
||||
"filePath": "admin/AuthenticationPage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
||||
|
@ -31,7 +31,6 @@ const organizationRoutes = route("/organization", [
|
||||
index("organization/SettingsPage/route.tsx"),
|
||||
route("/oauth/callback", "organization/SettingsPage/OauthCallbackPage/route.tsx")
|
||||
]),
|
||||
route("/sso", "organization/SsoPage/route.tsx"),
|
||||
route("/groups/$groupId", "organization/GroupDetailsByIDPage/route.tsx"),
|
||||
route("/members/$membershipId", "organization/UserDetailsByIDPage/route.tsx"),
|
||||
route("/roles/$roleId", "organization/RoleByIDPage/route.tsx"),
|
||||
|
Reference in New Issue
Block a user