Merge pull request #3550 from Infisical/project-specific-default-roles

Improvements: Refactor Project Templates and Project Type Policy Filtering/Specific Roles
This commit is contained in:
Scott Wilson
2025-05-07 12:50:01 -07:00
committed by GitHub
81 changed files with 1461 additions and 647 deletions

View File

@@ -0,0 +1,22 @@
import { Knex } from "knex";
import { ProjectType, TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.ProjectTemplates, "type"))) {
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
// defaulting to sm for migration to set existing, new ones will always be specified on creation
t.string("type").defaultTo(ProjectType.SecretManager).notNullable();
t.jsonb("environments").nullable().alter();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.ProjectTemplates, "type")) {
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
t.dropColumn("type");
// not reverting nullable environments
});
}
}

View File

@@ -185,11 +185,16 @@ export enum OrgMembershipStatus {
}
export enum ProjectMembershipRole {
// general
Admin = "admin",
Member = "member",
Custom = "custom",
Viewer = "viewer",
NoAccess = "no-access"
NoAccess = "no-access",
// ssh
SshHostBootstrapper = "ssh-host-bootstrapper",
// kms
KmsCryptographicOperator = "cryptographic-operator"
}
export enum SecretEncryptionAlgo {

View File

@@ -12,10 +12,11 @@ export const ProjectTemplatesSchema = z.object({
name: z.string(),
description: z.string().nullable().optional(),
roles: z.unknown(),
environments: z.unknown(),
environments: z.unknown().nullable().optional(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
type: z.string().default("secret-manager")
});
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;

View File

@@ -1,9 +1,8 @@
import { z } from "zod";
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas";
import { ProjectMembershipRole, ProjectTemplatesSchema, ProjectType } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
import { ApiDocsTags, ProjectTemplates } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -35,6 +34,7 @@ const SanitizedProjectTemplateSchema = ProjectTemplatesSchema.extend({
position: z.number().min(1)
})
.array()
.nullable()
});
const ProjectTemplateRolesSchema = z
@@ -104,6 +104,9 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
hide: false,
tags: [ApiDocsTags.ProjectTemplates],
description: "List project templates for the current organization.",
querystring: z.object({
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
}),
response: {
200: z.object({
projectTemplates: SanitizedProjectTemplateSchema.array()
@@ -112,7 +115,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission);
const { type } = req.query;
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission, type);
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
@@ -184,6 +188,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
tags: [ApiDocsTags.ProjectTemplates],
description: "Create a project template.",
body: z.object({
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
name: slugSchema({ field: "name" })
.refine((val) => !isInfisicalProjectTemplate(val), {
message: `The requested project template name is reserved.`
@@ -191,9 +196,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
.describe(ProjectTemplates.CREATE.name),
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
environments: ProjectTemplateEnvironmentsSchema.default(ProjectTemplateDefaultEnvironments).describe(
ProjectTemplates.CREATE.environments
)
environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
}),
response: {
200: z.object({

View File

@@ -0,0 +1,448 @@
import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability";
import {
ProjectPermissionActions,
ProjectPermissionCertificateActions,
ProjectPermissionCmekActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionGroupActions,
ProjectPermissionIdentityActions,
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionSecretActions,
ProjectPermissionSecretRotationActions,
ProjectPermissionSecretSyncActions,
ProjectPermissionSet,
ProjectPermissionSshHostActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
const buildAdminPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
// Admins get full access to everything
[
ProjectPermissionSub.SecretFolders,
ProjectPermissionSub.SecretImports,
ProjectPermissionSub.SecretApproval,
ProjectPermissionSub.Role,
ProjectPermissionSub.Integrations,
ProjectPermissionSub.Webhooks,
ProjectPermissionSub.ServiceTokens,
ProjectPermissionSub.Settings,
ProjectPermissionSub.Environments,
ProjectPermissionSub.Tags,
ProjectPermissionSub.AuditLogs,
ProjectPermissionSub.IpAllowList,
ProjectPermissionSub.CertificateAuthorities,
ProjectPermissionSub.CertificateTemplates,
ProjectPermissionSub.PkiAlerts,
ProjectPermissionSub.PkiCollections,
ProjectPermissionSub.SshCertificateAuthorities,
ProjectPermissionSub.SshCertificates,
ProjectPermissionSub.SshCertificateTemplates,
ProjectPermissionSub.SshHostGroups
].forEach((el) => {
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
el
);
});
can(
[
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.Edit,
ProjectPermissionCertificateActions.Create,
ProjectPermissionCertificateActions.Delete,
ProjectPermissionCertificateActions.ReadPrivateKey
],
ProjectPermissionSub.Certificates
);
can(
[
ProjectPermissionSshHostActions.Edit,
ProjectPermissionSshHostActions.Read,
ProjectPermissionSshHostActions.Create,
ProjectPermissionSshHostActions.Delete,
ProjectPermissionSshHostActions.IssueHostCert
],
ProjectPermissionSub.SshHosts
);
can(
[
ProjectPermissionMemberActions.Create,
ProjectPermissionMemberActions.Edit,
ProjectPermissionMemberActions.Delete,
ProjectPermissionMemberActions.Read,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionMemberActions.AssumePrivileges
],
ProjectPermissionSub.Member
);
can(
[
ProjectPermissionGroupActions.Create,
ProjectPermissionGroupActions.Edit,
ProjectPermissionGroupActions.Delete,
ProjectPermissionGroupActions.Read,
ProjectPermissionGroupActions.GrantPrivileges
],
ProjectPermissionSub.Groups
);
can(
[
ProjectPermissionIdentityActions.Create,
ProjectPermissionIdentityActions.Edit,
ProjectPermissionIdentityActions.Delete,
ProjectPermissionIdentityActions.Read,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionIdentityActions.AssumePrivileges
],
ProjectPermissionSub.Identity
);
can(
[
ProjectPermissionSecretActions.DescribeAndReadValue,
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Delete
],
ProjectPermissionSub.Secrets
);
can(
[
ProjectPermissionDynamicSecretActions.ReadRootCredential,
ProjectPermissionDynamicSecretActions.EditRootCredential,
ProjectPermissionDynamicSecretActions.CreateRootCredential,
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
can(
[
ProjectPermissionSecretSyncActions.Create,
ProjectPermissionSecretSyncActions.Edit,
ProjectPermissionSecretSyncActions.Delete,
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSecretSyncActions.SyncSecrets,
ProjectPermissionSecretSyncActions.ImportSecrets,
ProjectPermissionSecretSyncActions.RemoveSecrets
],
ProjectPermissionSub.SecretSyncs
);
can(
[
ProjectPermissionKmipActions.CreateClients,
ProjectPermissionKmipActions.UpdateClients,
ProjectPermissionKmipActions.DeleteClients,
ProjectPermissionKmipActions.ReadClients,
ProjectPermissionKmipActions.GenerateClientCertificates
],
ProjectPermissionSub.Kmip
);
can(
[
ProjectPermissionSecretRotationActions.Create,
ProjectPermissionSecretRotationActions.Edit,
ProjectPermissionSecretRotationActions.Delete,
ProjectPermissionSecretRotationActions.Read,
ProjectPermissionSecretRotationActions.ReadGeneratedCredentials,
ProjectPermissionSecretRotationActions.RotateSecrets
],
ProjectPermissionSub.SecretRotation
);
return rules;
};
const buildMemberPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[
ProjectPermissionSecretActions.DescribeAndReadValue,
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Delete
],
ProjectPermissionSub.Secrets
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.SecretFolders
);
can(
[
ProjectPermissionDynamicSecretActions.ReadRootCredential,
ProjectPermissionDynamicSecretActions.EditRootCredential,
ProjectPermissionDynamicSecretActions.CreateRootCredential,
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.SecretImports
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionMemberActions.Read, ProjectPermissionMemberActions.Create], ProjectPermissionSub.Member);
can([ProjectPermissionGroupActions.Read], ProjectPermissionSub.Groups);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Integrations
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Webhooks
);
can(
[
ProjectPermissionIdentityActions.Read,
ProjectPermissionIdentityActions.Edit,
ProjectPermissionIdentityActions.Create,
ProjectPermissionIdentityActions.Delete
],
ProjectPermissionSub.Identity
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.ServiceTokens
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Settings
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Environments
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Tags
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.Role);
can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs);
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
// double check if all CRUD are needed for CA and Certificates
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities);
can(
[
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.Edit,
ProjectPermissionCertificateActions.Create,
ProjectPermissionCertificateActions.Delete
],
ProjectPermissionSub.Certificates
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
can(
[
ProjectPermissionSecretSyncActions.Create,
ProjectPermissionSecretSyncActions.Edit,
ProjectPermissionSecretSyncActions.Delete,
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSecretSyncActions.SyncSecrets,
ProjectPermissionSecretSyncActions.ImportSecrets,
ProjectPermissionSecretSyncActions.RemoveSecrets
],
ProjectPermissionSub.SecretSyncs
);
return rules;
};
const buildViewerPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSub.Secrets);
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
return rules;
};
const buildNoAccessProjectPermission = () => {
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
return rules;
};
const buildSshHostBootstrapPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[ProjectPermissionSshHostActions.Create, ProjectPermissionSshHostActions.IssueHostCert],
ProjectPermissionSub.SshHosts
);
return rules;
};
const buildCryptographicOperatorPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
return rules;
};
// General
export const projectAdminPermissions = buildAdminPermissionRules();
export const projectMemberPermissions = buildMemberPermissionRules();
export const projectViewerPermission = buildViewerPermissionRules();
export const projectNoAccessPermissions = buildNoAccessProjectPermission();
// SSH
export const sshHostBootstrapPermissions = buildSshHostBootstrapPermissionRules();
// KMS
export const cryptographicOperatorPermissions = buildCryptographicOperatorPermissionRules();

View File

@@ -12,6 +12,14 @@ import {
TIdentityProjectMemberships,
TProjectMemberships
} from "@app/db/schemas";
import {
cryptographicOperatorPermissions,
projectAdminPermissions,
projectMemberPermissions,
projectNoAccessPermissions,
projectViewerPermission,
sshHostBootstrapPermissions
} from "@app/ee/services/permission/default-roles";
import { conditionsMatcher } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { objectify } from "@app/lib/fn";
@@ -32,14 +40,7 @@ import {
TGetServiceTokenProjectPermissionArg,
TGetUserProjectPermissionArg
} from "./permission-service-types";
import {
buildServiceTokenProjectPermission,
projectAdminPermissions,
projectMemberPermissions,
projectNoAccessPermissions,
ProjectPermissionSet,
projectViewerPermission
} from "./project-permission";
import { buildServiceTokenProjectPermission, ProjectPermissionSet } from "./project-permission";
type TPermissionServiceFactoryDep = {
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
@@ -95,6 +96,10 @@ export const permissionServiceFactory = ({
return projectViewerPermission;
case ProjectMembershipRole.NoAccess:
return projectNoAccessPermissions;
case ProjectMembershipRole.SshHostBootstrapper:
return sshHostBootstrapPermissions;
case ProjectMembershipRole.KmsCryptographicOperator:
return cryptographicOperatorPermissions;
case ProjectMembershipRole.Custom: {
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]

View File

@@ -678,403 +678,6 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
export type TProjectPermissionV2Schema = z.infer<typeof ProjectPermissionV2Schema>;
const buildAdminPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
// Admins get full access to everything
[
ProjectPermissionSub.SecretFolders,
ProjectPermissionSub.SecretImports,
ProjectPermissionSub.SecretApproval,
ProjectPermissionSub.Role,
ProjectPermissionSub.Integrations,
ProjectPermissionSub.Webhooks,
ProjectPermissionSub.ServiceTokens,
ProjectPermissionSub.Settings,
ProjectPermissionSub.Environments,
ProjectPermissionSub.Tags,
ProjectPermissionSub.AuditLogs,
ProjectPermissionSub.IpAllowList,
ProjectPermissionSub.CertificateAuthorities,
ProjectPermissionSub.CertificateTemplates,
ProjectPermissionSub.PkiAlerts,
ProjectPermissionSub.PkiCollections,
ProjectPermissionSub.SshCertificateAuthorities,
ProjectPermissionSub.SshCertificates,
ProjectPermissionSub.SshCertificateTemplates,
ProjectPermissionSub.SshHostGroups
].forEach((el) => {
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
el
);
});
can(
[
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.Edit,
ProjectPermissionCertificateActions.Create,
ProjectPermissionCertificateActions.Delete,
ProjectPermissionCertificateActions.ReadPrivateKey
],
ProjectPermissionSub.Certificates
);
can(
[
ProjectPermissionSshHostActions.Edit,
ProjectPermissionSshHostActions.Read,
ProjectPermissionSshHostActions.Create,
ProjectPermissionSshHostActions.Delete,
ProjectPermissionSshHostActions.IssueHostCert
],
ProjectPermissionSub.SshHosts
);
can(
[
ProjectPermissionMemberActions.Create,
ProjectPermissionMemberActions.Edit,
ProjectPermissionMemberActions.Delete,
ProjectPermissionMemberActions.Read,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionMemberActions.AssumePrivileges
],
ProjectPermissionSub.Member
);
can(
[
ProjectPermissionGroupActions.Create,
ProjectPermissionGroupActions.Edit,
ProjectPermissionGroupActions.Delete,
ProjectPermissionGroupActions.Read,
ProjectPermissionGroupActions.GrantPrivileges
],
ProjectPermissionSub.Groups
);
can(
[
ProjectPermissionIdentityActions.Create,
ProjectPermissionIdentityActions.Edit,
ProjectPermissionIdentityActions.Delete,
ProjectPermissionIdentityActions.Read,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionIdentityActions.AssumePrivileges
],
ProjectPermissionSub.Identity
);
can(
[
ProjectPermissionSecretActions.DescribeAndReadValue,
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Delete
],
ProjectPermissionSub.Secrets
);
can(
[
ProjectPermissionDynamicSecretActions.ReadRootCredential,
ProjectPermissionDynamicSecretActions.EditRootCredential,
ProjectPermissionDynamicSecretActions.CreateRootCredential,
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
can(
[
ProjectPermissionSecretSyncActions.Create,
ProjectPermissionSecretSyncActions.Edit,
ProjectPermissionSecretSyncActions.Delete,
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSecretSyncActions.SyncSecrets,
ProjectPermissionSecretSyncActions.ImportSecrets,
ProjectPermissionSecretSyncActions.RemoveSecrets
],
ProjectPermissionSub.SecretSyncs
);
can(
[
ProjectPermissionKmipActions.CreateClients,
ProjectPermissionKmipActions.UpdateClients,
ProjectPermissionKmipActions.DeleteClients,
ProjectPermissionKmipActions.ReadClients,
ProjectPermissionKmipActions.GenerateClientCertificates
],
ProjectPermissionSub.Kmip
);
can(
[
ProjectPermissionSecretRotationActions.Create,
ProjectPermissionSecretRotationActions.Edit,
ProjectPermissionSecretRotationActions.Delete,
ProjectPermissionSecretRotationActions.Read,
ProjectPermissionSecretRotationActions.ReadGeneratedCredentials,
ProjectPermissionSecretRotationActions.RotateSecrets
],
ProjectPermissionSub.SecretRotation
);
return rules;
};
export const projectAdminPermissions = buildAdminPermissionRules();
const buildMemberPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[
ProjectPermissionSecretActions.DescribeAndReadValue,
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Delete
],
ProjectPermissionSub.Secrets
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.SecretFolders
);
can(
[
ProjectPermissionDynamicSecretActions.ReadRootCredential,
ProjectPermissionDynamicSecretActions.EditRootCredential,
ProjectPermissionDynamicSecretActions.CreateRootCredential,
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.SecretImports
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionMemberActions.Read, ProjectPermissionMemberActions.Create], ProjectPermissionSub.Member);
can([ProjectPermissionGroupActions.Read], ProjectPermissionSub.Groups);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Integrations
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Webhooks
);
can(
[
ProjectPermissionIdentityActions.Read,
ProjectPermissionIdentityActions.Edit,
ProjectPermissionIdentityActions.Create,
ProjectPermissionIdentityActions.Delete
],
ProjectPermissionSub.Identity
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.ServiceTokens
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Settings
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Environments
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Tags
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.Role);
can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs);
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
// double check if all CRUD are needed for CA and Certificates
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities);
can(
[
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.Edit,
ProjectPermissionCertificateActions.Create,
ProjectPermissionCertificateActions.Delete
],
ProjectPermissionSub.Certificates
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
can(
[
ProjectPermissionSecretSyncActions.Create,
ProjectPermissionSecretSyncActions.Edit,
ProjectPermissionSecretSyncActions.Delete,
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSecretSyncActions.SyncSecrets,
ProjectPermissionSecretSyncActions.ImportSecrets,
ProjectPermissionSecretSyncActions.RemoveSecrets
],
ProjectPermissionSub.SecretSyncs
);
return rules;
};
export const projectMemberPermissions = buildMemberPermissionRules();
const buildViewerPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSub.Secrets);
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
return rules;
};
export const projectViewerPermission = buildViewerPermissionRules();
const buildNoAccessProjectPermission = () => {
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
return rules;
};
export const buildServiceTokenProjectPermission = (
scopes: Array<{ secretPath: string; environment: string }>,
permission: string[]
@@ -1116,8 +719,6 @@ export const buildServiceTokenProjectPermission = (
return build({ conditionsMatcher });
};
export const projectNoAccessPermissions = buildNoAccessProjectPermission();
/* eslint-disable */
/**

View File

@@ -1,22 +1,27 @@
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
import { ProjectType } from "@app/db/schemas";
import {
InfisicalProjectTemplate,
TUnpackedPermission
} from "@app/ee/services/project-template/project-template-types";
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns";
export const getDefaultProjectTemplate = (orgId: string) => ({
import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
type,
name: InfisicalProjectTemplate.Default,
createdAt: new Date(),
updatedAt: new Date(),
description: "Infisical's default project template",
environments: ProjectTemplateDefaultEnvironments,
roles: [...getPredefinedRoles("project-template")].map(({ name, slug, permissions }) => ({
name,
slug,
permissions: permissions as TUnpackedPermission[]
})),
description: `Infisical's ${type} default project template`,
environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
({ name, slug, permissions }) => ({
name,
slug,
permissions: permissions as TUnpackedPermission[]
})
),
orgId
});

View File

@@ -1,10 +1,11 @@
import { ForbiddenError } from "@casl/ability";
import { packRules } from "@casl/ability/extra";
import { TProjectTemplates } from "@app/db/schemas";
import { ProjectType, TProjectTemplates } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
import { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
import {
TCreateProjectTemplateDTO,
@@ -32,11 +33,13 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
...rest,
environments: environments as TProjectTemplateEnvironment[],
roles: [
...getPredefinedRoles("project-template").map(({ name, slug, permissions }) => ({
name,
slug,
permissions: permissions as TUnpackedPermission[]
})),
...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
({ name, slug, permissions }) => ({
name,
slug,
permissions: permissions as TUnpackedPermission[]
})
),
...(roles as TProjectTemplateRole[]).map((role) => ({
...role,
permissions: unpackPermissions(role.permissions)
@@ -49,7 +52,7 @@ export const projectTemplateServiceFactory = ({
permissionService,
projectTemplateDAL
}: TProjectTemplatesServiceFactoryDep) => {
const listProjectTemplatesByOrg = async (actor: OrgServiceActor) => {
const listProjectTemplatesByOrg = async (actor: OrgServiceActor, type?: ProjectType) => {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
@@ -68,11 +71,14 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
const projectTemplates = await projectTemplateDAL.find({
orgId: actor.orgId
orgId: actor.orgId,
...(type ? { type } : {})
});
return [
getDefaultProjectTemplate(actor.orgId),
...(type
? [getDefaultProjectTemplate(actor.orgId, type)]
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
...projectTemplates.map((template) => $unpackProjectTemplate(template))
];
};
@@ -134,7 +140,7 @@ export const projectTemplateServiceFactory = ({
};
const createProjectTemplate = async (
{ roles, environments, ...params }: TCreateProjectTemplateDTO,
{ roles, environments, type, ...params }: TCreateProjectTemplateDTO,
actor: OrgServiceActor
) => {
const plan = await licenseService.getPlan(actor.orgId);
@@ -154,6 +160,17 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
if (environments && type !== ProjectType.SecretManager) {
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
}
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
message: `Failed to create project template due to environment count exceeding your current limit of ${plan.environmentLimit}. Contact Infisical to increase limit.`
});
}
const isConflictingName = Boolean(
await projectTemplateDAL.findOne({
name: params.name,
@@ -169,8 +186,10 @@ export const projectTemplateServiceFactory = ({
const projectTemplate = await projectTemplateDAL.create({
...params,
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
environments: JSON.stringify(environments),
orgId: actor.orgId
environments:
type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
orgId: actor.orgId,
type
});
return $unpackProjectTemplate(projectTemplate);
@@ -202,6 +221,19 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
if (projectTemplate.type !== ProjectType.SecretManager && environments)
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
if (projectTemplate.type === ProjectType.SecretManager && environments === null)
throw new BadRequestError({ message: "Environments cannot be removed for SecretManager project templates" });
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
message: `Failed to update project template due to environment count exceeding your current limit of ${plan.environmentLimit}. Contact Infisical to increase limit.`
});
}
if (params.name && projectTemplate.name !== params.name) {
const isConflictingName = Boolean(
await projectTemplateDAL.findOne({

View File

@@ -1,6 +1,6 @@
import { z } from "zod";
import { TProjectEnvironments } from "@app/db/schemas";
import { ProjectType, TProjectEnvironments } from "@app/db/schemas";
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
@@ -15,8 +15,9 @@ export type TProjectTemplateRole = {
export type TCreateProjectTemplateDTO = {
name: string;
description?: string;
type: ProjectType;
roles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[];
environments?: TProjectTemplateEnvironment[] | null;
};
export type TUpdateProjectTemplateDTO = Partial<TCreateProjectTemplateDTO>;

View File

@@ -1822,8 +1822,12 @@ export const KMS = {
};
export const ProjectTemplates = {
LIST: {
type: "The type of project template to list."
},
CREATE: {
name: "The name of the project template to be created. Must be slug-friendly.",
type: "The type of project template to be created.",
description: "An optional description of the project template.",
roles: "The roles to be created when the template is applied to a project.",
environments: "The environments to be created when the template is applied to a project."

View File

@@ -1,15 +1,20 @@
import { ProjectMembershipRole } from "@app/db/schemas";
import { v4 as uuidv4 } from "uuid";
import { ProjectMembershipRole, ProjectType } from "@app/db/schemas";
import {
cryptographicOperatorPermissions,
projectAdminPermissions,
projectMemberPermissions,
projectNoAccessPermissions,
projectViewerPermission
} from "@app/ee/services/permission/project-permission";
projectViewerPermission,
sshHostBootstrapPermissions
} from "@app/ee/services/permission/default-roles";
import { TGetPredefinedRolesDTO } from "@app/services/project-role/project-role-types";
export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMembershipRole) => {
export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetPredefinedRolesDTO) => {
return [
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // dummy userid
id: uuidv4(),
projectId,
name: "Admin",
slug: ProjectMembershipRole.Admin,
@@ -19,7 +24,7 @@ export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMember
updatedAt: new Date()
},
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c70", // dummy user for zod validation in response
id: uuidv4(),
projectId,
name: "Developer",
slug: ProjectMembershipRole.Member,
@@ -29,7 +34,29 @@ export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMember
updatedAt: new Date()
},
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c71", // dummy user for zod validation in response
id: uuidv4(),
projectId,
name: "SSH Host Bootstrapper",
slug: ProjectMembershipRole.SshHostBootstrapper,
permissions: sshHostBootstrapPermissions,
description: "Create and issue SSH Hosts in a project",
createdAt: new Date(),
updatedAt: new Date(),
type: ProjectType.SSH
},
{
id: uuidv4(),
projectId,
name: "Cryptographic Operator",
slug: ProjectMembershipRole.KmsCryptographicOperator,
permissions: cryptographicOperatorPermissions,
description: "Perform cryptographic operations, such as encryption and signing, in a project",
createdAt: new Date(),
updatedAt: new Date(),
type: ProjectType.KMS
},
{
id: uuidv4(),
projectId,
name: "Viewer",
slug: ProjectMembershipRole.Viewer,
@@ -39,7 +66,7 @@ export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMember
updatedAt: new Date()
},
{
id: "b11b49a9-09a9-4443-916a-4246f9ff2c72", // dummy user for zod validation in response
id: uuidv4(),
projectId,
name: "No Access",
slug: ProjectMembershipRole.NoAccess,
@@ -48,5 +75,5 @@ export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMember
createdAt: new Date(),
updatedAt: new Date()
}
].filter(({ slug }) => !roleFilter || roleFilter.includes(slug));
].filter(({ slug, type }) => (type ? type === projectType : true) && (!roleFilter || roleFilter === slug));
};

View File

@@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { requestContext } from "@fastify/request-context";
import { ActionProjectType, ProjectMembershipRole, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, ProjectType, TableName, TProjects } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
@@ -34,7 +34,7 @@ type TProjectRoleServiceFactoryDep = {
projectRoleDAL: TProjectRoleDALFactory;
identityDAL: Pick<TIdentityDALFactory, "findById">;
userDAL: Pick<TUserDALFactory, "findById">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findProjectById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory;
projectUserMembershipRoleDAL: TProjectUserMembershipRoleDALFactory;
@@ -98,30 +98,37 @@ export const projectRoleServiceFactory = ({
roleSlug,
filter
}: TGetRoleDetailsDTO) => {
let projectId = "";
let project: TProjects;
if (filter.type === ProjectRoleServiceIdentifierType.SLUG) {
const project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
projectId = project.id;
project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
} else {
projectId = filter.projectId;
project = await projectDAL.findProjectById(filter.projectId);
}
if (!project) throw new NotFoundError({ message: "Project not found" });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
projectId: project.id,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
if (roleSlug !== "custom" && Object.values(ProjectMembershipRole).includes(roleSlug as ProjectMembershipRole)) {
const predefinedRole = getPredefinedRoles(projectId, roleSlug as ProjectMembershipRole)[0];
const [predefinedRole] = getPredefinedRoles({
projectId: project.id,
projectType: project.type as ProjectType,
roleFilter: roleSlug as ProjectMembershipRole
});
if (!predefinedRole) throw new NotFoundError({ message: `Default role with slug '${roleSlug}' not found` });
return { ...predefinedRole, permissions: UnpackedPermissionSchema.array().parse(predefinedRole.permissions) };
}
const customRole = await projectRoleDAL.findOne({ slug: roleSlug, projectId });
const customRole = await projectRoleDAL.findOne({ slug: roleSlug, projectId: project.id });
if (!customRole) throw new NotFoundError({ message: `Project role with slug '${roleSlug}' not found` });
return { ...customRole, permissions: unpackPermissions(customRole.permissions) };
};
@@ -194,29 +201,32 @@ export const projectRoleServiceFactory = ({
};
const listRoles = async ({ actorOrgId, actorAuthMethod, actorId, actor, filter }: TListRolesDTO) => {
let projectId = "";
let project: TProjects;
if (filter.type === ProjectRoleServiceIdentifierType.SLUG) {
const project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
projectId = project.id;
project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
} else {
projectId = filter.projectId;
project = await projectDAL.findProjectById(filter.projectId);
}
if (!project) throw new BadRequestError({ message: "Project not found" });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
projectId: project.id,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
const customRoles = await projectRoleDAL.find(
{ projectId },
{ projectId: project.id },
{ sort: [[`${TableName.ProjectRoles}.slug` as "slug", "asc"]] }
);
const roles = [...getPredefinedRoles(projectId), ...(customRoles || [])];
const roles = [
...getPredefinedRoles({ projectId: project.id, projectType: project.type as ProjectType }),
...(customRoles || [])
];
return roles;
};

View File

@@ -1,4 +1,4 @@
import { TOrgRolesUpdate, TProjectRolesInsert } from "@app/db/schemas";
import { ProjectMembershipRole, ProjectType, TOrgRolesUpdate, TProjectRolesInsert } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
export enum ProjectRoleServiceIdentifierType {
@@ -34,3 +34,9 @@ export type TListRolesDTO = {
| { type: ProjectRoleServiceIdentifierType.SLUG; projectSlug: string }
| { type: ProjectRoleServiceIdentifierType.ID; projectId: string };
} & Omit<TProjectPermission, "projectId">;
export type TGetPredefinedRolesDTO = {
projectId: string;
projectType: ProjectType;
roleFilter?: ProjectMembershipRole;
};

View File

@@ -329,14 +329,16 @@ export const projectServiceFactory = ({
// set default environments and root folder for provided environments
let envs: TProjectEnvironments[] = [];
if (projectTemplate) {
envs = await projectEnvDAL.insertMany(
projectTemplate.environments.map((env) => ({ ...env, projectId: project.id })),
tx
);
await folderDAL.insertMany(
envs.map(({ id }) => ({ name: ROOT_FOLDER_NAME, envId: id, version: 1 })),
tx
);
if (projectTemplate.environments) {
envs = await projectEnvDAL.insertMany(
projectTemplate.environments.map((env) => ({ ...env, projectId: project.id })),
tx
);
await folderDAL.insertMany(
envs.map(({ id }) => ({ name: ROOT_FOLDER_NAME, envId: id, version: 1 })),
tx
);
}
await projectRoleDAL.insertMany(
projectTemplate.packedRoles.map((role) => ({
...role,
@@ -592,7 +594,10 @@ export const projectServiceFactory = ({
workspaces.map(async (workspace) => {
return {
...workspace,
roles: [...(workspaceMappedToRoles[workspace.id] || []), ...getPredefinedRoles(workspace.id)]
roles: [
...(workspaceMappedToRoles[workspace.id] || []),
...getPredefinedRoles({ projectId: workspace.id, projectType: workspace.type as ProjectType })
]
};
})
);

View File

@@ -33,7 +33,7 @@ In the following steps, we'll explore how to set up a project template.
<Tab title="Infisical UI">
<Steps>
<Step title="Creating a Project Template">
Navigate to the Project Templates tab on the Organization Settings page and tap on the **Add Template** button.
Navigate to the **Project Templates** tab on the Feature Settings page for the project type you want to create a template for and tap on the **Add Template** button.
![project template add button](/images/platform/project-templates/project-template-add-button.png)
Specify your template details. Here's some guidance on each field:
@@ -67,6 +67,7 @@ In the following steps, we'll explore how to set up a project template.
--header 'Content-Type: application/json' \
--data '{
"name": "my-project-template",
"type": "secret-manager",
"description": "...",
"environments": "[...]",
"roles": "[...]",

View File

@@ -31,16 +31,9 @@ we will register a remote host with Infisical through a [machine identity](/docu
<Steps>
<Step title="Create an Infisical SSH project">
1.1. Start by creating a new Infisical SSH project in Infisical.
Start by creating a new Infisical SSH project in Infisical.
![ssh project create](/images/platform/ssh/v2/ssh-create-project.png)
1.2. Create a custom role in the project under Access Control > Project Roles to grant the machine identity that we will create in step 2 the ability to **Create** and **Issue Host Certificates** on the **SSH Host** resource; this will enable the linked machine identity to bootstrap a remote host with Infisical
and establish the necessary configuration on it.
![ssh custom role bootstrap 1](/images/platform/ssh/v2/ssh-add-bootstrap-role-1.png)
![ssh custom role bootstrap 2](/images/platform/ssh/v2/ssh-add-bootstrap-role-2.png)
</Step>
<Step title="Create a machine identity for bootstrapping Infisical SSH">
2.1. Follow the instructions [here](/documentation/platform/identities/universal-auth) to configure a [machine identity](/documentation/platform/identities/machine-identities) in Infisical with Universal Auth.
@@ -52,7 +45,14 @@ we will register a remote host with Infisical through a [machine identity](/docu
You may use other authentication methods as suitable (e.g. [AWS Auth](/documentation/platform/identities/aws-auth), [Azure Auth](/documentation/platform/identities/azure-auth), [GCP Auth](/documentation/platform/identities/gcp-auth), etc.) as part of the machine identity configuration but, to keep this example simple, we will be using Universal Auth.
</Note>
2.2. Add the machine identity to the Infisical SSH project you created in the previous step and assign it the custom role you created in step 1.2.
2.2. Add the machine identity to the Infisical SSH project you created in the previous step and assign it the **SSH Host Bootstrapper** role.
This role grants the ability to **Create** and **Issue Host Certificates** on the **SSH Host** resource; this will enable the linked machine identity to bootstrap a remote host with Infisical
and establish the necessary configuration on it.
<Tip>
If you plan to use a custom role to bootstrap SSH hosts, ensure the role has the **Create** and **Issue Host Certificates** on the **SSH Host** resource.
</Tip>
![ssh add identity to project](/images/platform/ssh/v2/ssh-add-identity-to-project.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 951 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 579 KiB

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 KiB

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -72,7 +72,7 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
OrgPermissionSubjects.ProjectTemplates
);
const { data: projectTemplates = [] } = useListProjectTemplates({
const { data: projectTemplates = [] } = useListProjectTemplates(projectType, {
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates)
});

View File

@@ -0,0 +1,30 @@
import { useState } from "react";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectTemplatesTab } from "./components";
const tabs = [
{ name: "Project Templates", key: "project-templates", component: ProjectTemplatesTab }
];
export const ProjectSettings = () => {
const [selectedTab, setSelectedTab] = useState(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>
);
};

View File

@@ -7,6 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { usePopUp } from "@app/hooks";
import { TProjectTemplate, useDeleteProjectTemplate } from "@app/hooks/api/projectTemplates";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProjectTemplateDetailsModal } from "../../ProjectTemplateDetailsModal";
import { ProjectTemplateEnvironmentsForm } from "./ProjectTemplateEnvironmentsForm";
@@ -24,7 +25,7 @@ export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBa
"editDetails"
] as const);
const { id: templateId, name, description } = projectTemplate;
const { id: templateId, name, description, type } = projectTemplate;
const deleteProjectTemplate = useDeleteProjectTemplate();
@@ -94,10 +95,12 @@ export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBa
</div>
)}
</div>
<ProjectTemplateEnvironmentsForm
isInfisicalTemplate={isInfisicalTemplate}
projectTemplate={projectTemplate}
/>
{type === ProjectType.SecretManager && (
<ProjectTemplateEnvironmentsForm
isInfisicalTemplate={isInfisicalTemplate}
projectTemplate={projectTemplate}
/>
)}
<ProjectTemplateRolesSection
isInfisicalTemplate={isInfisicalTemplate}
projectTemplate={projectTemplate}

View File

@@ -19,7 +19,7 @@ import {
THead,
Tr
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
import { slugSchema } from "@app/lib/schemas";
@@ -35,6 +35,7 @@ const formSchema = z.object({
slug: slugSchema({ min: 1, max: 32 })
})
.array()
.nullish()
});
type TFormSchema = z.infer<typeof formSchema>;
@@ -55,6 +56,8 @@ export const ProjectTemplateEnvironmentsForm = ({
resolver: zodResolver(formSchema)
});
const { subscription } = useSubscription();
const {
fields: environments,
move,
@@ -67,7 +70,7 @@ export const ProjectTemplateEnvironmentsForm = ({
const onFormSubmit = async (form: TFormSchema) => {
try {
const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({
environments: form.environments.map((env, index) => ({
environments: form.environments?.map((env, index) => ({
...env,
position: index + 1
})),
@@ -89,6 +92,9 @@ export const ProjectTemplateEnvironmentsForm = ({
}
};
const isEnvironmentLimitExceeded =
Boolean(subscription.environmentLimit) && environments.length >= subscription.environmentLimit;
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
@@ -138,6 +144,8 @@ export const ProjectTemplateEnvironmentsForm = ({
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.ProjectTemplates}
renderTooltip={isEnvironmentLimitExceeded ? true : undefined}
allowedLabel={`Plan environment limit of ${subscription.environmentLimit} exceeded. Contact Infisical to increase limit.`}
>
{(isAllowed) => (
<Button
@@ -147,7 +155,7 @@ export const ProjectTemplateEnvironmentsForm = ({
variant="solid"
size="xs"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
isDisabled={!isAllowed}
isDisabled={!isAllowed || isEnvironmentLimitExceeded}
>
Add Environment
</Button>

View File

@@ -12,6 +12,7 @@ import {
ModalContent,
TextArea
} from "@app/components/v2";
import { useGetProjectTypeFromRoute } from "@app/hooks";
import {
TProjectTemplate,
useCreateProjectTemplate,
@@ -41,6 +42,7 @@ type FormProps = {
const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => {
const createProjectTemplate = useCreateProjectTemplate();
const updateProjectTemplate = useUpdateProjectTemplate();
const projectType = useGetProjectTypeFromRoute();
const {
handleSubmit,
@@ -55,9 +57,17 @@ const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => {
});
const onFormSubmit = async (data: FormData) => {
if (!projectType) {
createNotification({
text: "Failed to determine project type",
type: "error"
});
return;
}
const mutation = projectTemplate
? updateProjectTemplate.mutateAsync({ templateId: projectTemplate.id, ...data })
: createProjectTemplate.mutateAsync(data);
: createProjectTemplate.mutateAsync({ ...data, type: projectType });
try {
const template = await mutation;
createNotification({

View File

@@ -50,7 +50,7 @@ export const ProjectTemplatesSection = () => {
className="absolute min-h-[10rem] w-full"
>
<div>
<p className="mb-6 text-bunker-300">
<p className="mb-6 font-inter text-bunker-300">
Create and configure templates with predefined roles and environments to streamline
project setup
</p>

View File

@@ -23,7 +23,7 @@ import {
Tr
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useGetProjectTypeFromRoute, usePopUp } from "@app/hooks";
import { TProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
import { DeleteProjectTemplateModal } from "./DeleteProjectTemplateModal";
@@ -35,8 +35,10 @@ type Props = {
export const ProjectTemplatesTable = ({ onEdit }: Props) => {
const { subscription } = useSubscription();
const { isPending, data: projectTemplates = [] } = useListProjectTemplates({
enabled: subscription?.projectTemplates
const projectType = useGetProjectTypeFromRoute();
const { isPending, data: projectTemplates = [] } = useListProjectTemplates(projectType, {
enabled: subscription?.projectTemplates && Boolean(projectType)
});
const [search, setSearch] = useState("");
@@ -50,6 +52,8 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
[search, projectTemplates]
);
const isSecretManagerTemplates = projectType === "secret-manager";
return (
<div>
<Input
@@ -64,7 +68,7 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
<Tr>
<Th>Name</Th>
<Th>Roles</Th>
<Th>Environments</Th>
{isSecretManagerTemplates && <Th>Environments</Th>}
<Th />
</Tr>
</THead>
@@ -77,7 +81,7 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
/>
)}
{filteredTemplates.map((template) => {
const { id, name, roles, environments, description } = template;
const { id, name, roles, environments = [], description } = template;
return (
<Tr
onClick={() => onEdit(template)}
@@ -116,28 +120,30 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
</Tooltip>
)}
</Td>
<Td className="pl-14">
{environments.length}
{environments.length > 0 && (
<Tooltip
content={
<ul className="ml-2 list-disc">
{environments
.sort((a, b) => (a.position > b.position ? 1 : -1))
.map((env) => (
<li key={env.slug}>{env.name}</li>
))}
</ul>
}
>
<FontAwesomeIcon
size="sm"
className="ml-2 text-mineshaft-400"
icon={faCircleInfo}
/>
</Tooltip>
)}
</Td>
{isSecretManagerTemplates && environments && (
<Td className="pl-14">
{environments.length}
{environments.length > 0 && (
<Tooltip
content={
<ul className="ml-2 list-disc">
{environments
.sort((a, b) => (a.position > b.position ? 1 : -1))
.map((env) => (
<li key={env.slug}>{env.name}</li>
))}
</ul>
}
>
<FontAwesomeIcon
size="sm"
className="ml-2 text-mineshaft-400"
icon={faCircleInfo}
/>
</Tooltip>
)}
</Td>
)}
<Td className="w-5">
{name !== "default" && (
<OrgPermissionCan

View File

@@ -0,0 +1 @@
export * from "./ProjectTemplatesTab";

View File

@@ -0,0 +1 @@
export * from "./ProjectSettings";

View File

@@ -6,21 +6,9 @@ export enum OrgMembershipRole {
NoAccess = "no-access"
}
enum ProjectMemberRole {
Admin = "admin",
Member = "member",
Viewer = "viewer",
NoAccess = "no-access"
}
export const isCustomOrgRole = (slug: string) =>
!Object.values(OrgMembershipRole).includes(slug as OrgMembershipRole);
export const formatProjectRoleName = (name: string) => {
if (name === ProjectMemberRole.Member) return "developer";
return name;
};
export const isCustomProjectRole = (slug: string) =>
!Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
@@ -28,3 +16,24 @@ export const findOrgMembershipRole = (roles: TOrgRole[], roleIdOrSlug: string) =
isCustomOrgRole(roleIdOrSlug)
? roles.find((r) => r.id === roleIdOrSlug)
: roles.find((r) => r.slug === roleIdOrSlug);
export const formatProjectRoleName = (role: string, customRoleName?: string) => {
switch (role) {
case ProjectMembershipRole.Admin:
return "Admin";
case ProjectMembershipRole.Member:
return "Developer";
case ProjectMembershipRole.Viewer:
return "Viewer";
case ProjectMembershipRole.NoAccess:
return "No Access";
case ProjectMembershipRole.Custom:
return customRoleName ?? role;
case ProjectMembershipRole.SshHostBootstrapper:
return "SSH Host Bootstrapper";
case ProjectMembershipRole.KmsCryptographicOperator:
return "Cryptographic Operator";
default:
return role;
}
};

View File

@@ -6,14 +6,17 @@ import {
TProjectTemplate,
TProjectTemplateResponse
} from "@app/hooks/api/projectTemplates/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const projectTemplateKeys = {
all: ["project-template"] as const,
list: () => [...projectTemplateKeys.all, "list"] as const,
list: (projectType?: ProjectType) =>
[...projectTemplateKeys.all, "list", ...(projectType ? [projectType] : [])] as const,
byId: (templateId: string) => [...projectTemplateKeys.all, templateId] as const
};
export const useListProjectTemplates = (
type?: ProjectType,
options?: Omit<
UseQueryOptions<
TProjectTemplate[],
@@ -25,9 +28,11 @@ export const useListProjectTemplates = (
>
) => {
return useQuery({
queryKey: projectTemplateKeys.list(),
queryKey: projectTemplateKeys.list(type),
queryFn: async () => {
const { data } = await apiRequest.get<TListProjectTemplates>("/api/v1/project-templates");
const { data } = await apiRequest.get<TListProjectTemplates>("/api/v1/project-templates", {
params: { type }
});
return data.projectTemplates;
},

View File

@@ -1,11 +1,13 @@
import { TProjectRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
export type TProjectTemplate = {
id: string;
name: string;
type: ProjectType;
description?: string;
roles: Pick<TProjectRole, "slug" | "name" | "permissions">[];
environments: { name: string; slug: string; position: number }[];
environments?: { name: string; slug: string; position: number }[] | null;
createdAt: string;
updatedAt: string;
};
@@ -14,6 +16,7 @@ export type TListProjectTemplates = { projectTemplates: TProjectTemplate[] };
export type TProjectTemplateResponse = { projectTemplate: TProjectTemplate };
export type TCreateProjectTemplateDTO = {
type: ProjectType;
name: string;
description?: string;
};

View File

@@ -3,7 +3,9 @@ export enum ProjectMembershipRole {
Member = "member",
Custom = "custom",
Viewer = "viewer",
NoAccess = "no-access"
NoAccess = "no-access",
SshHostBootstrapper = "ssh-host-bootstrapper",
KmsCryptographicOperator = "cryptographic-operator"
}
export type TGetProjectRolesDTO = {

View File

@@ -1,4 +1,5 @@
export { useDebounce } from "./useDebounce";
export * from "./useGetProjectTypeFromRoute";
export { usePagination } from "./usePagination";
export { usePersistentState } from "./usePersistentState";
export { usePopUp } from "./usePopUp";

View File

@@ -0,0 +1,13 @@
import { useRouterState } from "@tanstack/react-router";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const useGetProjectTypeFromRoute = () => {
const { location } = useRouterState();
const segment = location.pathname.split("/")[2];
if (!Object.values(ProjectType).includes(segment as ProjectType)) return undefined;
return segment as ProjectType;
};

View File

@@ -11,11 +11,12 @@ import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2";
import { OrgPermissionSubjects, useOrgPermission, useServerConfig } from "@app/context";
import { OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types";
import { usePopUp } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar";
import { SidebarHeader } from "./components/SidebarHeader";
import { DefaultSideBar, SecretSharingSideBar } from "./ProductsSideBar";
import { DefaultSideBar, ProjectOverviewSideBar, SecretSharingSideBar } from "./ProductsSideBar";
export const OrganizationLayout = () => {
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
@@ -45,21 +46,38 @@ export const OrganizationLayout = () => {
] as string[]
).includes(location.pathname);
const isProjectOverviewOrSettingsPage = (
[
linkOptions({ to: "/organization/secret-manager/overview" }).to,
linkOptions({ to: "/organization/secret-manager/settings" }).to,
linkOptions({ to: "/organization/cert-manager/overview" }).to,
linkOptions({ to: "/organization/cert-manager/settings" }).to,
linkOptions({ to: "/organization/kms/overview" }).to,
linkOptions({ to: "/organization/kms/settings" }).to,
linkOptions({ to: "/organization/ssh/overview" }).to,
linkOptions({ to: "/organization/ssh/settings" }).to
] as string[]
).includes(location.pathname);
const shouldShowOrgSidebar =
location.pathname.startsWith("/organization") &&
(!isSecretSharingPage || shouldShowProductsSidebar) &&
!(
[
linkOptions({ to: "/organization/secret-manager/overview" }).to,
linkOptions({ to: "/organization/cert-manager/overview" }).to,
linkOptions({ to: "/organization/ssh/overview" }).to,
linkOptions({ to: "/organization/kms/overview" }).to,
linkOptions({ to: "/organization/secret-scanning" }).to
] as string[]
).includes(location.pathname);
!([linkOptions({ to: "/organization/secret-scanning" }).to] as string[]).includes(
location.pathname
);
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
let SideBarComponent = <DefaultSideBar />;
if (isSecretSharingPage) {
SideBarComponent = <SecretSharingSideBar />;
} else if (isProjectOverviewOrSettingsPage) {
SideBarComponent = (
<ProjectOverviewSideBar type={location.pathname.split("/")[2] as ProjectType} />
);
}
return (
<>
<Banner />
@@ -80,10 +98,12 @@ export const OrganizationLayout = () => {
className="dark w-60 overflow-hidden border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="p-2 pt-3">
<SidebarHeader />
</div>
{isSecretSharingPage ? <SecretSharingSideBar /> : <DefaultSideBar />}
{!isProjectOverviewOrSettingsPage && (
<div className="p-2 pt-3">
<SidebarHeader />
</div>
)}
{SideBarComponent}
</nav>
</motion.div>
)}

View File

@@ -0,0 +1,94 @@
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useMatchRoute } from "@tanstack/react-router";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { ProjectType } from "@app/hooks/api/workspace/types";
type TProjectOverviewSideBarProps = {
type: ProjectType;
};
export const ProjectOverviewSideBar = ({ type }: TProjectOverviewSideBarProps) => {
const matchRoute = useMatchRoute();
const isOverviewActive = !!matchRoute({
to: `/organization/${type}/overview`,
fuzzy: false
});
let label: string;
let icon: string;
let link: string;
switch (type) {
case ProjectType.CertificateManager:
label = "Cert Management";
icon = "note";
link = "https://infisical.com/docs/documentation/platform/pki/overview";
break;
case ProjectType.SecretManager:
label = "Secret Management";
icon = "sliding-carousel";
link = "https://infisical.com/docs/documentation/getting-started/introduction";
break;
case ProjectType.KMS:
label = "KMS";
icon = "unlock";
link = "https://infisical.com/docs/documentation/platform/kms/overview";
break;
case ProjectType.SSH:
label = "SSH";
icon = "verified";
link = "https://infisical.com/docs/documentation/platform/ssh/overview";
break;
default:
throw new Error("Unknown project type");
}
return (
<>
<div className="p-2 pt-3">
<a
target="_blank"
rel="noopener noreferrer"
href={link}
className="flex w-full items-center rounded-md border border-mineshaft-600 p-2 pl-3 transition-all duration-150 hover:bg-mineshaft-700"
>
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-mineshaft-500 p-4">
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="h-3.5 w-3.5 text-mineshaft-300"
/>
</div>
<div className="-mt-1 flex flex-grow flex-col text-white">
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
Infisical Docs
</div>
<div className="text-xs leading-[10px] text-mineshaft-400">
{type === ProjectType.SecretManager ? "Get Started" : `${label} Overview`}
</div>
</div>
</a>
</div>
<Menu>
<MenuGroup title="Overview">
<Link to={`/organization/${type}/overview`}>
<MenuItem isSelected={isOverviewActive} icon={icon}>
{label}
</MenuItem>
</Link>
</MenuGroup>
<MenuGroup title="Other">
<Link to={`/organization/${type}/settings`}>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Settings
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</>
);
};

View File

@@ -1,2 +1,3 @@
export * from "./DefaultSideBar";
export * from "./ProjectOverviewSideBar";
export * from "./SecretSharingSideBar";

View File

@@ -269,7 +269,9 @@ export const MinimizedOrgSidebar = () => {
<MenuIconButton
isSelected={
isActive ||
window.location.pathname.startsWith(`/${ProjectType.SecretManager}`)
window.location.pathname.startsWith(
`/organization/${ProjectType.SecretManager}`
)
}
icon="sliding-carousel"
>
@@ -282,7 +284,9 @@ export const MinimizedOrgSidebar = () => {
<MenuIconButton
isSelected={
isActive ||
window.location.pathname.startsWith(`/${ProjectType.CertificateManager}`)
window.location.pathname.startsWith(
`/organization/${ProjectType.CertificateManager}`
)
}
icon="note"
>
@@ -294,7 +298,8 @@ export const MinimizedOrgSidebar = () => {
{({ isActive }) => (
<MenuIconButton
isSelected={
isActive || window.location.pathname.startsWith(`/${ProjectType.KMS}`)
isActive ||
window.location.pathname.startsWith(`/organization/${ProjectType.KMS}`)
}
icon="unlock"
>
@@ -306,7 +311,8 @@ export const MinimizedOrgSidebar = () => {
{({ isActive }) => (
<MenuIconButton
isSelected={
isActive || window.location.pathname.startsWith(`/${ProjectType.SSH}`)
isActive ||
window.location.pathname.startsWith(`/organization/${ProjectType.SSH}`)
}
icon="verified"
>

View File

@@ -0,0 +1,20 @@
import { Helmet } from "react-helmet";
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
import { PageHeader } from "@app/components/v2";
export const CertManagerSettingsPage = () => {
return (
<>
<Helmet>
<title>Cert Management Settings</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader title="Cert Management Settings" />
<ProjectSettings />
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,26 @@
import { faHome } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { CertManagerSettingsPage } from "./CertManagerSettingsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/settings"
)({
component: CertManagerSettingsPage,
context: () => ({
breadcrumbs: [
{
label: "Products",
icon: () => <FontAwesomeIcon icon={faHome} />
},
{
label: "Cert Management",
link: linkOptions({ to: "/organization/cert-manager/overview" })
},
{
label: "Settings"
}
]
})
});

View File

@@ -6,9 +6,9 @@ import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
import { formatProjectRoleName } from "@app/helpers/roles";
import { useGetUserWorkspaces } from "@app/hooks/api";
import { IdentityMembership } from "@app/hooks/api/identities/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
export enum TabSections {
@@ -26,15 +26,6 @@ type Props = {
) => void;
};
const formatRoleName = (role: string, customRoleName?: string) => {
if (role === ProjectMembershipRole.Custom) return customRoleName;
if (role === ProjectMembershipRole.Admin) return "Admin";
if (role === ProjectMembershipRole.Member) return "Developer";
if (role === ProjectMembershipRole.Viewer) return "Viewer";
if (role === ProjectMembershipRole.NoAccess) return "No Access";
return role;
};
export const IdentityProjectRow = ({
membership: { id, createdAt, identity, project, roles },
handlePopUpOpen
@@ -80,7 +71,7 @@ export const IdentityProjectRow = ({
<Td>
<Tag size="xs">{project.type}</Tag>
</Td>
<Td>{`${formatRoleName(roles[0].role, roles[0].customRoleName)}${
<Td>{`${formatProjectRoleName(roles[0].role, roles[0].customRoleName)}${
roles.length > 1 ? ` (+${roles.length - 1})` : ""
}`}</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>

View File

@@ -0,0 +1,20 @@
import { Helmet } from "react-helmet";
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
import { PageHeader } from "@app/components/v2";
export const KmsSettingsPage = () => {
return (
<>
<Helmet>
<title>KMS Settings</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader title="KMS Settings" />
<ProjectSettings />
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,26 @@
import { faHome } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { KmsSettingsPage } from "./KmsSettingsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/kms/settings"
)({
component: KmsSettingsPage,
context: () => ({
breadcrumbs: [
{
label: "Products",
icon: () => <FontAwesomeIcon icon={faHome} />
},
{
label: "KMS",
link: linkOptions({ to: "/organization/kms/overview" })
},
{
label: "Settings"
}
]
})
});

View File

@@ -117,12 +117,8 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
});
reset();
} catch (err) {
console.error(err);
const error = err as any;
const text =
error?.response?.data?.message ??
`Failed to ${popUp?.role?.data ? "update" : "create"} role`;
} catch {
const text = `Failed to ${popUp?.role?.data ? "update" : "create"} role`;
createNotification({
text,

View File

@@ -27,6 +27,7 @@ export const ProjectListToggle = ({ value, onChange }: Props) => {
value={value}
onValueChange={onChange}
className="absolute left-0 top-0 h-full w-full cursor-pointer opacity-0"
dropdownContainerClassName="mt-10 ml-5"
>
<SelectItem value={ProjectListView.MyProjects}>My Projects</SelectItem>
<SelectItem value={ProjectListView.AllProjects}>All Projects</SelectItem>

View File

@@ -0,0 +1,20 @@
import { Helmet } from "react-helmet";
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
import { PageHeader } from "@app/components/v2";
export const SecretManagerSettingsPage = () => {
return (
<>
<Helmet>
<title>Secret Management Settings</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader title="Secret Management Settings" />
<ProjectSettings />
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,26 @@
import { faHome } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { SecretManagerSettingsPage } from "./SecretManagerSettingsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/settings"
)({
component: SecretManagerSettingsPage,
context: () => ({
breadcrumbs: [
{
label: "Products",
icon: () => <FontAwesomeIcon icon={faHome} />
},
{
label: "Secret Management",
link: linkOptions({ to: "/organization/secret-manager/overview" })
},
{
label: "Settings"
}
]
})
});

View File

@@ -2,6 +2,7 @@ import { useState } from "react";
import { useSearch } from "@tanstack/react-router";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
import { ROUTE_PATHS } from "@app/const/routes";
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
@@ -11,7 +12,6 @@ import { OrgAuthTab } from "../OrgAuthTab";
import { OrgEncryptionTab } from "../OrgEncryptionTab";
import { OrgGeneralTab } from "../OrgGeneralTab";
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab";
import { ProjectTemplatesTab } from "../ProjectTemplatesTab";
export const OrgTabGroup = () => {
const search = useSearch({
@@ -28,7 +28,34 @@ export const OrgTabGroup = () => {
},
{ name: "Audit Log Streams", key: "tag-audit-log-streams", component: AuditLogStreamsTab },
{ name: "Import", key: "tab-import", component: ImportTab },
{ name: "Project Templates", key: "project-templates", component: ProjectTemplatesTab },
{
name: "Project Templates",
key: "project-templates",
// scott: temporary, remove once users have adjusted
// eslint-disable-next-line react/no-unstable-nested-components
component: () => (
<div>
<NoticeBannerV2
className="mx-auto mt-10 max-w-4xl"
title="Project Templates New Location"
>
<p className="text-sm text-bunker-200">
Project templates have been moved to the Feature Settings page, under the &#34;Project
Templates&#34; tab.
</p>
<p className="mt-2 text-sm text-bunker-200">
Project templates are now product-specific, and can be configured for each project
type.
</p>
<img
src="/images/project-templates/project-templates-new-location.png"
className="mt-4 w-full max-w-4xl rounded"
alt="Project Templates New Location"
/>
</NoticeBannerV2>
</div>
)
},
{ name: "KMIP", key: "kmip", component: KmipTab }
];

View File

@@ -0,0 +1,20 @@
import { Helmet } from "react-helmet";
import { ProjectSettings } from "@app/components/projects/ProjectSettings";
import { PageHeader } from "@app/components/v2";
export const SshSettingsPage = () => {
return (
<>
<Helmet>
<title>SSH Settings</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader title="SSH Settings" />
<ProjectSettings />
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,26 @@
import { faHome } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { SshSettingsPage } from "./SshSettingsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/ssh/settings"
)({
component: SshSettingsPage,
context: () => ({
breadcrumbs: [
{
label: "Products",
icon: () => <FontAwesomeIcon icon={faHome} />
},
{
label: "SSH",
link: linkOptions({ to: "/organization/ssh/overview" })
},
{
label: "Settings"
}
]
})
});

View File

@@ -5,8 +5,8 @@ import { useNavigate } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
import { formatProjectRoleName } from "@app/helpers/roles";
import { useGetUserWorkspaces } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { TWorkspaceUser } from "@app/hooks/api/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { OrgAccessControlTabSections } from "@app/types/org";
@@ -19,15 +19,6 @@ type Props = {
) => void;
};
const formatRoleName = (role: string, customRoleName?: string) => {
if (role === ProjectMembershipRole.Custom) return customRoleName;
if (role === ProjectMembershipRole.Admin) return "Admin";
if (role === ProjectMembershipRole.Member) return "Developer";
if (role === ProjectMembershipRole.Viewer) return "Viewer";
if (role === ProjectMembershipRole.NoAccess) return "No Access";
return role;
};
export const UserProjectRow = ({
membership: { id, project, user, roles },
handlePopUpOpen
@@ -73,7 +64,7 @@ export const UserProjectRow = ({
<Td>
<Tag size="xs">{project.type}</Tag>
</Td>
<Td>{`${formatRoleName(roles[0].role, roles[0].customRoleName)}${
<Td>{`${formatProjectRoleName(roles[0].role, roles[0].customRoleName)}${
roles.length > 1 ? ` (+${roles.length - 1})` : ""
}`}</Td>
<Td>

View File

@@ -24,10 +24,10 @@ import {
Tooltip
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles";
import { usePopUp } from "@app/hooks";
import { useGetProjectRoles, useUpdateGroupWorkspaceRole } from "@app/hooks/api";
import { TGroupMembership } from "@app/hooks/api/groups/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/workspace/types";
import { groupBy } from "@app/lib/fn/array";
@@ -260,12 +260,6 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember
}
};
const formatRoleName = (role: string, customRoleName?: string) => {
if (role === ProjectMembershipRole.Custom) return customRoleName;
if (role === ProjectMembershipRole.Member) return "Developer";
return role;
};
return (
<div className="flex items-center space-x-2">
{roles
@@ -275,7 +269,7 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember
return (
<Tag key={id} className="capitalize">
<div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div>
<div>{formatProjectRoleName(role, customRoleName)}</div>
{isTemporary && (
<div>
<Tooltip content={isExpired ? "Expired Temporary Access" : "Temporary Access"}>
@@ -303,7 +297,7 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember
return (
<Tag key={id} className="capitalize">
<div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div>
<div>{formatProjectRoleName(role, customRoleName)}</div>
{isTemporary && (
<div>
<Tooltip

View File

@@ -41,11 +41,11 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles";
import { withProjectPermission } from "@app/hoc";
import { usePagination, useResetPageHelper } from "@app/hooks";
import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectIdentityOrderBy } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
@@ -53,12 +53,6 @@ import { IdentityModal } from "./components/IdentityModal";
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
const formatRoleName = (role: string, customRoleName?: string) => {
if (role === ProjectMembershipRole.Custom) return customRoleName;
if (role === ProjectMembershipRole.Member) return "Developer";
if (role === ProjectMembershipRole.NoAccess) return "No access";
return role;
};
export const IdentityTab = withProjectPermission(
() => {
const { currentWorkspace } = useWorkspace();
@@ -286,7 +280,7 @@ export const IdentityTab = withProjectPermission(
<Tag key={roleId}>
<div className="flex items-center space-x-2">
<div className="capitalize">
{formatRoleName(role, customRoleName)}
{formatProjectRoleName(role, customRoleName)}
</div>
{isTemporary && (
<div>
@@ -331,7 +325,9 @@ export const IdentityTab = withProjectPermission(
return (
<Tag key={roleId} className="capitalize">
<div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div>
<div>
{formatProjectRoleName(role, customRoleName)}
</div>
{isTemporary && (
<div>
<Tooltip

View File

@@ -50,19 +50,13 @@ import {
useUser,
useWorkspace
} from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles";
import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
const formatRoleName = (role: string, customRoleName?: string) => {
if (role === ProjectMembershipRole.Custom) return customRoleName;
if (role === ProjectMembershipRole.Member) return "Developer";
if (role === ProjectMembershipRole.NoAccess) return "No access";
return role;
};
type Props = {
handlePopUpOpen: (
@@ -343,7 +337,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
<Tag key={id}>
<div className="flex items-center space-x-2">
<div className="capitalize">
{formatRoleName(role, customRoleName)}
{formatProjectRoleName(role, customRoleName)}
</div>
{isTemporary && (
<div>
@@ -386,7 +380,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
return (
<Tag key={id} className="capitalize">
<div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div>
<div>{formatProjectRoleName(role, customRoleName)}</div>
{isTemporary && (
<div>
<Tooltip

View File

@@ -24,7 +24,7 @@ import {
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteProjectRole, useGetProjectRoles } from "@app/hooks/api";
import { TProjectRole } from "@app/hooks/api/roles/types";
import { ProjectMembershipRole, TProjectRole } from "@app/hooks/api/roles/types";
import { RoleModal } from "@app/pages/project/RoleDetailsBySlugPage/components/RoleModal";
export const ProjectRoleList = () => {
@@ -86,7 +86,9 @@ export const ProjectRoleList = () => {
{isRolesLoading && <TableSkeleton columns={4} innerKey="org-roles" />}
{roles?.map((role) => {
const { id, name, slug } = role;
const isNonMutatable = ["admin", "member", "viewer", "no-access"].includes(slug);
const isNonMutatable = Object.values(ProjectMembershipRole).includes(
slug as ProjectMembershipRole
);
return (
<Tr

View File

@@ -50,6 +50,7 @@ import {
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema,
ProjectTypePermissionSubjects,
rolePermission2Form
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
@@ -269,6 +270,12 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
</DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(sub) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
sub as ProjectPermissionSub
]
)
.sort((a, b) =>
PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title
.toLowerCase()

View File

@@ -49,6 +49,7 @@ import {
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema,
ProjectTypePermissionSubjects,
rolePermission2Form
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
@@ -265,6 +266,12 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
</DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(subject) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
subject as ProjectPermissionSub
]
)
.sort((a, b) =>
PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title
.toLowerCase()

View File

@@ -17,6 +17,7 @@ import {
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useDeleteProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { ProjectAccessControlTabs } from "@app/types/project";
@@ -77,7 +78,9 @@ const Page = () => {
}
};
const isCustomRole = !["admin", "member", "viewer", "no-access"].includes(data?.slug ?? "");
const isCustomRole = !Object.values(ProjectMembershipRole).includes(
(data?.slug ?? "") as ProjectMembershipRole
);
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">

View File

@@ -12,11 +12,14 @@ import {
SelectItem
} from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { useGetProjectTypeFromRoute } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import {
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema,
ProjectTypePermissionSubjects,
TFormSchema
} from "./ProjectRoleModifySection.utils";
@@ -37,18 +40,20 @@ export const NewPermissionRule = ({ onClose }: Props) => {
.extend({ type: z.nativeEnum(ProjectPermissionSub) })
),
defaultValues: {
type: ProjectPermissionSub.Secrets
type: ProjectPermissionSub.Project
}
});
const selectedSubject = form.watch("type");
const projectType = useGetProjectTypeFromRoute();
return (
<div>
<Controller
control={form.control}
name="type"
defaultValue={ProjectPermissionSub.Secrets}
defaultValue={ProjectPermissionSub.Project}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Subject" errorText={error?.message} isError={Boolean(error)}>
<Select
@@ -57,11 +62,18 @@ export const NewPermissionRule = ({ onClose }: Props) => {
onValueChange={(e) => onChange(e)}
className="w-full"
>
{Object.keys(PROJECT_PERMISSION_OBJECT).map((subject) => (
<SelectItem value={subject} key={`permission-create-${subject}`}>
{PROJECT_PERMISSION_OBJECT[subject as ProjectPermissionSub].title}
</SelectItem>
))}
{Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(subject) =>
ProjectTypePermissionSubjects[projectType ?? ProjectType.SecretManager][
subject as ProjectPermissionSub
]
)
.map((subject) => (
<SelectItem value={subject} key={`permission-create-${subject}`}>
{PROJECT_PERMISSION_OBJECT[subject as ProjectPermissionSub].title}
</SelectItem>
))}
</Select>
</FormControl>
)}

View File

@@ -25,6 +25,7 @@ import {
TPermissionConditionOperators
} from "@app/context/ProjectPermissionContext/types";
import { TProjectPermission } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
const GeneralPolicyActionSchema = z.object({
read: z.boolean().optional(),
@@ -1122,7 +1123,7 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
]
},
[ProjectPermissionSub.SecretApproval]: {
title: "Secret Protect policy",
title: "Secret Approval Policies",
actions: [
{ label: "Read", value: "read" },
{ label: "Create", value: "create" },
@@ -1195,3 +1196,87 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
]
}
};
const SharedPermissionSubjects = {
[ProjectPermissionSub.AuditLogs]: true,
[ProjectPermissionSub.Groups]: true,
[ProjectPermissionSub.Member]: true,
[ProjectPermissionSub.Identity]: true,
[ProjectPermissionSub.Project]: true,
[ProjectPermissionSub.Role]: true,
[ProjectPermissionSub.Settings]: true
};
const SecretsManagerPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.SecretFolders]: enabled,
[ProjectPermissionSub.SecretImports]: enabled,
[ProjectPermissionSub.DynamicSecrets]: enabled,
[ProjectPermissionSub.Secrets]: enabled,
[ProjectPermissionSub.SecretApproval]: enabled,
[ProjectPermissionSub.Integrations]: enabled,
[ProjectPermissionSub.SecretSyncs]: enabled,
[ProjectPermissionSub.Kms]: enabled,
[ProjectPermissionSub.Environments]: enabled,
[ProjectPermissionSub.Tags]: enabled,
[ProjectPermissionSub.Webhooks]: enabled,
[ProjectPermissionSub.IpAllowList]: enabled,
[ProjectPermissionSub.SecretRollback]: enabled,
[ProjectPermissionSub.SecretRotation]: enabled,
[ProjectPermissionSub.ServiceTokens]: enabled
});
const KmsPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.Cmek]: enabled,
[ProjectPermissionSub.Kmip]: enabled
});
const CertificateManagerPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.PkiCollections]: enabled,
[ProjectPermissionSub.PkiAlerts]: enabled,
[ProjectPermissionSub.CertificateAuthorities]: enabled,
[ProjectPermissionSub.CertificateTemplates]: enabled,
[ProjectPermissionSub.Certificates]: enabled
});
const SshPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.SshCertificateAuthorities]: enabled,
[ProjectPermissionSub.SshCertificates]: enabled,
[ProjectPermissionSub.SshCertificateTemplates]: enabled,
[ProjectPermissionSub.SshHosts]: enabled,
[ProjectPermissionSub.SshHostGroups]: enabled
});
// scott: this structure ensures we don't forget to add project permissions to their relevant project type
export const ProjectTypePermissionSubjects: Record<
ProjectType,
Record<ProjectPermissionSub, boolean>
> = {
[ProjectType.SecretManager]: {
...SharedPermissionSubjects,
...SecretsManagerPermissionSubjects(true),
...KmsPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...SshPermissionSubjects()
},
[ProjectType.KMS]: {
...SharedPermissionSubjects,
...KmsPermissionSubjects(true),
...SecretsManagerPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...SshPermissionSubjects()
},
[ProjectType.CertificateManager]: {
...SharedPermissionSubjects,
...CertificateManagerPermissionSubjects(true),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects(),
...SshPermissionSubjects()
},
[ProjectType.SSH]: {
...SharedPermissionSubjects,
...SshPermissionSubjects(true),
...CertificateManagerPermissionSubjects(),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects()
}
};

View File

@@ -6,6 +6,7 @@ import { IconButton, Tooltip } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useTimedReset } from "@app/hooks";
import { useGetProjectRoleBySlug } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -21,7 +22,9 @@ export const RoleDetailsSection = ({ roleSlug, handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const { data } = useGetProjectRoleBySlug(currentWorkspace?.id ?? "", roleSlug as string);
const isCustomRole = !["admin", "member", "viewer", "no-access"].includes(data?.slug ?? "");
const isCustomRole = !Object.values(ProjectMembershipRole).includes(
(data?.slug ?? "") as ProjectMembershipRole
);
return data ? (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">

View File

@@ -115,12 +115,8 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
});
reset();
} catch (err) {
console.error(err);
const error = err as any;
const text =
error?.response?.data?.message ??
`Failed to ${popUp?.role?.data ? "update" : "create"} role`;
} catch {
const text = `Failed to ${popUp?.role?.data ? "update" : "create"} role`;
createNotification({
text,

View File

@@ -19,6 +19,7 @@ import { ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
import { evaluatePermissionsAbility } from "@app/helpers/permissions";
import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions";
@@ -31,6 +32,7 @@ import {
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema,
ProjectTypePermissionSubjects,
rolePermission2Form,
TFormSchema
} from "./ProjectRoleModifySection.utils";
@@ -104,7 +106,9 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
}
};
const isCustomRole = !["admin", "member", "viewer", "no-access"].includes(role?.slug ?? "");
const isCustomRole = !Object.values(ProjectMembershipRole).includes(
(role?.slug ?? "") as ProjectMembershipRole
);
const onNewPolicy = (selectedSubject: ProjectPermissionSub) => {
const rootPolicyValue = form.getValues(`permissions.${selectedSubject}`);
@@ -192,6 +196,12 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
</DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(subject) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
subject as ProjectPermissionSub
]
)
.sort((a, b) =>
PROJECT_PERMISSION_OBJECT[
a as keyof typeof PROJECT_PERMISSION_OBJECT
@@ -221,17 +231,19 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
</div>
<div className="py-4">
{!isPending && <PermissionEmptyState />}
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map((subject) => (
<GeneralPermissionPolicies
subject={subject}
actions={PROJECT_PERMISSION_OBJECT[subject].actions}
title={PROJECT_PERMISSION_OBJECT[subject].title}
key={`project-permission-${subject}`}
isDisabled={isDisabled}
>
{renderConditionalComponents(subject, isDisabled)}
</GeneralPermissionPolicies>
))}
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[])
.filter((subject) => ProjectTypePermissionSubjects[currentWorkspace.type][subject])
.map((subject) => (
<GeneralPermissionPolicies
subject={subject}
actions={PROJECT_PERMISSION_OBJECT[subject].actions}
title={PROJECT_PERMISSION_OBJECT[subject].title}
key={`project-permission-${subject}`}
isDisabled={isDisabled}
>
{renderConditionalComponents(subject, isDisabled)}
</GeneralPermissionPolicies>
))}
</div>
</FormProvider>
</form>

View File

@@ -52,14 +52,18 @@ import { Route as sshLayoutImport } from './pages/ssh/layout'
import { Route as secretManagerLayoutImport } from './pages/secret-manager/layout'
import { Route as kmsLayoutImport } from './pages/kms/layout'
import { Route as certManagerLayoutImport } from './pages/cert-manager/layout'
import { Route as organizationSshSettingsPageRouteImport } from './pages/organization/SshSettingsPage/route'
import { Route as organizationSshOverviewPageRouteImport } from './pages/organization/SshOverviewPage/route'
import { Route as organizationSecretSharingSettingsPageRouteImport } from './pages/organization/SecretSharingSettingsPage/route'
import { Route as organizationSecretManagerSettingsPageRouteImport } from './pages/organization/SecretManagerSettingsPage/route'
import { Route as organizationSecretManagerOverviewPageRouteImport } from './pages/organization/SecretManagerOverviewPage/route'
import { Route as organizationRoleByIDPageRouteImport } from './pages/organization/RoleByIDPage/route'
import { Route as organizationUserDetailsByIDPageRouteImport } from './pages/organization/UserDetailsByIDPage/route'
import { Route as organizationKmsSettingsPageRouteImport } from './pages/organization/KmsSettingsPage/route'
import { Route as organizationKmsOverviewPageRouteImport } from './pages/organization/KmsOverviewPage/route'
import { Route as organizationIdentityDetailsByIDPageRouteImport } from './pages/organization/IdentityDetailsByIDPage/route'
import { Route as organizationGroupDetailsByIDPageRouteImport } from './pages/organization/GroupDetailsByIDPage/route'
import { Route as organizationCertManagerSettingsPageRouteImport } from './pages/organization/CertManagerSettingsPage/route'
import { Route as organizationCertManagerOverviewPageRouteImport } from './pages/organization/CertManagerOverviewPage/route'
import { Route as organizationSettingsPageRouteImport } from './pages/organization/SettingsPage/route'
import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route'
@@ -604,6 +608,14 @@ const certManagerLayoutRoute = certManagerLayoutImport.update({
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRoute,
} as any)
const organizationSshSettingsPageRouteRoute =
organizationSshSettingsPageRouteImport.update({
id: '/ssh/settings',
path: '/ssh/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSshOverviewPageRouteRoute =
organizationSshOverviewPageRouteImport.update({
id: '/ssh/overview',
@@ -620,6 +632,14 @@ const organizationSecretSharingSettingsPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute,
} as any)
const organizationSecretManagerSettingsPageRouteRoute =
organizationSecretManagerSettingsPageRouteImport.update({
id: '/secret-manager/settings',
path: '/secret-manager/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSecretManagerOverviewPageRouteRoute =
organizationSecretManagerOverviewPageRouteImport.update({
id: '/secret-manager/overview',
@@ -644,6 +664,14 @@ const organizationUserDetailsByIDPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationKmsSettingsPageRouteRoute =
organizationKmsSettingsPageRouteImport.update({
id: '/kms/settings',
path: '/kms/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationKmsOverviewPageRouteRoute =
organizationKmsOverviewPageRouteImport.update({
id: '/kms/overview',
@@ -668,6 +696,14 @@ const organizationGroupDetailsByIDPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationCertManagerSettingsPageRouteRoute =
organizationCertManagerSettingsPageRouteImport.update({
id: '/cert-manager/settings',
path: '/cert-manager/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationCertManagerOverviewPageRouteRoute =
organizationCertManagerOverviewPageRouteImport.update({
id: '/cert-manager/overview',
@@ -2072,6 +2108,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationCertManagerOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/settings': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/settings'
path: '/cert-manager/settings'
fullPath: '/organization/cert-manager/settings'
preLoaderRoute: typeof organizationCertManagerSettingsPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId'
path: '/groups/$groupId'
@@ -2093,6 +2136,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationKmsOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/kms/settings': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/kms/settings'
path: '/kms/settings'
fullPath: '/organization/kms/settings'
preLoaderRoute: typeof organizationKmsSettingsPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId'
path: '/members/$membershipId'
@@ -2114,6 +2164,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationSecretManagerOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/settings': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/settings'
path: '/secret-manager/settings'
fullPath: '/organization/secret-manager/settings'
preLoaderRoute: typeof organizationSecretManagerSettingsPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings'
path: '/settings'
@@ -2128,6 +2185,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationSshOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/ssh/settings': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/ssh/settings'
path: '/ssh/settings'
fullPath: '/organization/ssh/settings'
preLoaderRoute: typeof organizationSshSettingsPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout'
path: ''
@@ -3168,13 +3232,17 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren {
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren
organizationCertManagerOverviewPageRouteRoute: typeof organizationCertManagerOverviewPageRouteRoute
organizationCertManagerSettingsPageRouteRoute: typeof organizationCertManagerSettingsPageRouteRoute
organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute
organizationIdentityDetailsByIDPageRouteRoute: typeof organizationIdentityDetailsByIDPageRouteRoute
organizationKmsOverviewPageRouteRoute: typeof organizationKmsOverviewPageRouteRoute
organizationKmsSettingsPageRouteRoute: typeof organizationKmsSettingsPageRouteRoute
organizationUserDetailsByIDPageRouteRoute: typeof organizationUserDetailsByIDPageRouteRoute
organizationRoleByIDPageRouteRoute: typeof organizationRoleByIDPageRouteRoute
organizationSecretManagerOverviewPageRouteRoute: typeof organizationSecretManagerOverviewPageRouteRoute
organizationSecretManagerSettingsPageRouteRoute: typeof organizationSecretManagerSettingsPageRouteRoute
organizationSshOverviewPageRouteRoute: typeof organizationSshOverviewPageRouteRoute
organizationSshSettingsPageRouteRoute: typeof organizationSshSettingsPageRouteRoute
}
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren =
@@ -3196,19 +3264,27 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren,
organizationCertManagerOverviewPageRouteRoute:
organizationCertManagerOverviewPageRouteRoute,
organizationCertManagerSettingsPageRouteRoute:
organizationCertManagerSettingsPageRouteRoute,
organizationGroupDetailsByIDPageRouteRoute:
organizationGroupDetailsByIDPageRouteRoute,
organizationIdentityDetailsByIDPageRouteRoute:
organizationIdentityDetailsByIDPageRouteRoute,
organizationKmsOverviewPageRouteRoute:
organizationKmsOverviewPageRouteRoute,
organizationKmsSettingsPageRouteRoute:
organizationKmsSettingsPageRouteRoute,
organizationUserDetailsByIDPageRouteRoute:
organizationUserDetailsByIDPageRouteRoute,
organizationRoleByIDPageRouteRoute: organizationRoleByIDPageRouteRoute,
organizationSecretManagerOverviewPageRouteRoute:
organizationSecretManagerOverviewPageRouteRoute,
organizationSecretManagerSettingsPageRouteRoute:
organizationSecretManagerSettingsPageRouteRoute,
organizationSshOverviewPageRouteRoute:
organizationSshOverviewPageRouteRoute,
organizationSshSettingsPageRouteRoute:
organizationSshSettingsPageRouteRoute,
}
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren =
@@ -3893,14 +3969,18 @@ export interface FileRoutesByFullPath {
'/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute
'/organization/settings/': typeof organizationSettingsPageRouteRoute
'/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute
'/organization/cert-manager/settings': typeof organizationCertManagerSettingsPageRouteRoute
'/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute
'/organization/kms/overview': typeof organizationKmsOverviewPageRouteRoute
'/organization/kms/settings': typeof organizationKmsSettingsPageRouteRoute
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-manager/settings': typeof organizationSecretManagerSettingsPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/organization/ssh/settings': typeof organizationSshSettingsPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
@@ -4072,14 +4152,18 @@ export interface FileRoutesByTo {
'/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
'/organization/settings': typeof organizationSettingsPageRouteRoute
'/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute
'/organization/cert-manager/settings': typeof organizationCertManagerSettingsPageRouteRoute
'/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute
'/organization/kms/overview': typeof organizationKmsOverviewPageRouteRoute
'/organization/kms/settings': typeof organizationKmsSettingsPageRouteRoute
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-manager/settings': typeof organizationSecretManagerSettingsPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/organization/ssh/settings': typeof organizationSshSettingsPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
@@ -4264,14 +4348,18 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/settings/': typeof organizationSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/settings': typeof organizationCertManagerSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/kms/overview': typeof organizationKmsOverviewPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/kms/settings': typeof organizationKmsSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/settings': typeof organizationSecretManagerSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/ssh/settings': typeof organizationSshSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout': typeof certManagerLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout': typeof secretManagerLayoutRouteWithChildren
@@ -4457,14 +4545,18 @@ export interface FileRouteTypes {
| '/organization/secret-sharing/'
| '/organization/settings/'
| '/organization/cert-manager/overview'
| '/organization/cert-manager/settings'
| '/organization/groups/$groupId'
| '/organization/identities/$identityId'
| '/organization/kms/overview'
| '/organization/kms/settings'
| '/organization/members/$membershipId'
| '/organization/roles/$roleId'
| '/organization/secret-manager/overview'
| '/organization/secret-manager/settings'
| '/organization/secret-sharing/settings'
| '/organization/ssh/overview'
| '/organization/ssh/settings'
| '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview'
@@ -4635,14 +4727,18 @@ export interface FileRouteTypes {
| '/organization/secret-sharing'
| '/organization/settings'
| '/organization/cert-manager/overview'
| '/organization/cert-manager/settings'
| '/organization/groups/$groupId'
| '/organization/identities/$identityId'
| '/organization/kms/overview'
| '/organization/kms/settings'
| '/organization/members/$membershipId'
| '/organization/roles/$roleId'
| '/organization/secret-manager/overview'
| '/organization/secret-manager/settings'
| '/organization/secret-sharing/settings'
| '/organization/ssh/overview'
| '/organization/ssh/settings'
| '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview'
@@ -4825,14 +4921,18 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/'
| '/_authenticate/_inject-org-details/_org-layout/organization/settings/'
| '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview'
| '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/settings'
| '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId'
| '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId'
| '/_authenticate/_inject-org-details/_org-layout/organization/kms/overview'
| '/_authenticate/_inject-org-details/_org-layout/organization/kms/settings'
| '/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId'
| '/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/settings'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings'
| '/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview'
| '/_authenticate/_inject-org-details/_org-layout/organization/ssh/settings'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout'
@@ -5209,13 +5309,17 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing",
"/_authenticate/_inject-org-details/_org-layout/organization/settings",
"/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview",
"/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/settings",
"/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId",
"/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId",
"/_authenticate/_inject-org-details/_org-layout/organization/kms/overview",
"/_authenticate/_inject-org-details/_org-layout/organization/kms/settings",
"/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId",
"/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview",
"/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview"
"/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/settings",
"/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview",
"/_authenticate/_inject-org-details/_org-layout/organization/ssh/settings"
]
},
"/_authenticate/_inject-org-details/admin/_admin-layout": {
@@ -5328,6 +5432,10 @@ export const routeTree = rootRoute
"filePath": "organization/CertManagerOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/settings": {
"filePath": "organization/CertManagerSettingsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId": {
"filePath": "organization/GroupDetailsByIDPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
@@ -5340,6 +5448,10 @@ export const routeTree = rootRoute
"filePath": "organization/KmsOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/kms/settings": {
"filePath": "organization/KmsSettingsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId": {
"filePath": "organization/UserDetailsByIDPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
@@ -5352,6 +5464,10 @@ export const routeTree = rootRoute
"filePath": "organization/SecretManagerOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/settings": {
"filePath": "organization/SecretManagerSettingsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings": {
"filePath": "organization/SecretSharingSettingsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing"
@@ -5360,6 +5476,10 @@ export const routeTree = rootRoute
"filePath": "organization/SshOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/ssh/settings": {
"filePath": "organization/SshSettingsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout": {
"filePath": "cert-manager/layout.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId",

View File

@@ -9,9 +9,13 @@ const adminRoute = route("/admin", [
const organizationRoutes = route("/organization", [
route("/secret-manager/overview", "organization/SecretManagerOverviewPage/route.tsx"),
route("/secret-manager/settings", "organization/SecretManagerSettingsPage/route.tsx"),
route("/cert-manager/overview", "organization/CertManagerOverviewPage/route.tsx"),
route("/cert-manager/settings", "organization/CertManagerSettingsPage/route.tsx"),
route("/ssh/overview", "organization/SshOverviewPage/route.tsx"),
route("/ssh/settings", "organization/SshSettingsPage/route.tsx"),
route("/kms/overview", "organization/KmsOverviewPage/route.tsx"),
route("/kms/settings", "organization/KmsSettingsPage/route.tsx"),
route("/access-management", "organization/AccessManagementPage/route.tsx"),
route("/admin", "organization/AdminPage/route.tsx"),
route("/audit-logs", "organization/AuditLogsPage/route.tsx"),