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
@@ -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
});
}
}
+6 -1
View File
@@ -185,11 +185,16 @@ export enum OrgMembershipStatus {
} }
export enum ProjectMembershipRole { export enum ProjectMembershipRole {
// general
Admin = "admin", Admin = "admin",
Member = "member", Member = "member",
Custom = "custom", Custom = "custom",
Viewer = "viewer", Viewer = "viewer",
NoAccess = "no-access" NoAccess = "no-access",
// ssh
SshHostBootstrapper = "ssh-host-bootstrapper",
// kms
KmsCryptographicOperator = "cryptographic-operator"
} }
export enum SecretEncryptionAlgo { export enum SecretEncryptionAlgo {
+3 -2
View File
@@ -12,10 +12,11 @@ export const ProjectTemplatesSchema = z.object({
name: z.string(), name: z.string(),
description: z.string().nullable().optional(), description: z.string().nullable().optional(),
roles: z.unknown(), roles: z.unknown(),
environments: z.unknown(), environments: z.unknown().nullable().optional(),
orgId: z.string().uuid(), orgId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
type: z.string().default("secret-manager")
}); });
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>; export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;
@@ -1,9 +1,8 @@
import { z } from "zod"; 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 { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; 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 { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
import { ApiDocsTags, ProjectTemplates } from "@app/lib/api-docs"; import { ApiDocsTags, ProjectTemplates } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -35,6 +34,7 @@ const SanitizedProjectTemplateSchema = ProjectTemplatesSchema.extend({
position: z.number().min(1) position: z.number().min(1)
}) })
.array() .array()
.nullable()
}); });
const ProjectTemplateRolesSchema = z const ProjectTemplateRolesSchema = z
@@ -104,6 +104,9 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
hide: false, hide: false,
tags: [ApiDocsTags.ProjectTemplates], tags: [ApiDocsTags.ProjectTemplates],
description: "List project templates for the current organization.", description: "List project templates for the current organization.",
querystring: z.object({
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
}),
response: { response: {
200: z.object({ 200: z.object({
projectTemplates: SanitizedProjectTemplateSchema.array() projectTemplates: SanitizedProjectTemplateSchema.array()
@@ -112,7 +115,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { 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)); const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
@@ -184,6 +188,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
tags: [ApiDocsTags.ProjectTemplates], tags: [ApiDocsTags.ProjectTemplates],
description: "Create a project template.", description: "Create a project template.",
body: z.object({ body: z.object({
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
name: slugSchema({ field: "name" }) name: slugSchema({ field: "name" })
.refine((val) => !isInfisicalProjectTemplate(val), { .refine((val) => !isInfisicalProjectTemplate(val), {
message: `The requested project template name is reserved.` message: `The requested project template name is reserved.`
@@ -191,9 +196,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
.describe(ProjectTemplates.CREATE.name), .describe(ProjectTemplates.CREATE.name),
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description), description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles), roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
environments: ProjectTemplateEnvironmentsSchema.default(ProjectTemplateDefaultEnvironments).describe( environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
ProjectTemplates.CREATE.environments
)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -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();
@@ -12,6 +12,14 @@ import {
TIdentityProjectMemberships, TIdentityProjectMemberships,
TProjectMemberships TProjectMemberships
} from "@app/db/schemas"; } 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 { conditionsMatcher } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { objectify } from "@app/lib/fn"; import { objectify } from "@app/lib/fn";
@@ -32,14 +40,7 @@ import {
TGetServiceTokenProjectPermissionArg, TGetServiceTokenProjectPermissionArg,
TGetUserProjectPermissionArg TGetUserProjectPermissionArg
} from "./permission-service-types"; } from "./permission-service-types";
import { import { buildServiceTokenProjectPermission, ProjectPermissionSet } from "./project-permission";
buildServiceTokenProjectPermission,
projectAdminPermissions,
projectMemberPermissions,
projectNoAccessPermissions,
ProjectPermissionSet,
projectViewerPermission
} from "./project-permission";
type TPermissionServiceFactoryDep = { type TPermissionServiceFactoryDep = {
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">; orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
@@ -95,6 +96,10 @@ export const permissionServiceFactory = ({
return projectViewerPermission; return projectViewerPermission;
case ProjectMembershipRole.NoAccess: case ProjectMembershipRole.NoAccess:
return projectNoAccessPermissions; return projectNoAccessPermissions;
case ProjectMembershipRole.SshHostBootstrapper:
return sshHostBootstrapPermissions;
case ProjectMembershipRole.KmsCryptographicOperator:
return cryptographicOperatorPermissions;
case ProjectMembershipRole.Custom: { case ProjectMembershipRole.Custom: {
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>( return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[] permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
@@ -678,403 +678,6 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
export type TProjectPermissionV2Schema = z.infer<typeof ProjectPermissionV2Schema>; 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 = ( export const buildServiceTokenProjectPermission = (
scopes: Array<{ secretPath: string; environment: string }>, scopes: Array<{ secretPath: string; environment: string }>,
permission: string[] permission: string[]
@@ -1116,8 +719,6 @@ export const buildServiceTokenProjectPermission = (
return build({ conditionsMatcher }); return build({ conditionsMatcher });
}; };
export const projectNoAccessPermissions = buildNoAccessProjectPermission();
/* eslint-disable */ /* eslint-disable */
/** /**
@@ -1,22 +1,27 @@
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants"; import { ProjectType } from "@app/db/schemas";
import { import {
InfisicalProjectTemplate, InfisicalProjectTemplate,
TUnpackedPermission TUnpackedPermission
} from "@app/ee/services/project-template/project-template-types"; } from "@app/ee/services/project-template/project-template-types";
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"; 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 id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
type,
name: InfisicalProjectTemplate.Default, name: InfisicalProjectTemplate.Default,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
description: "Infisical's default project template", description: `Infisical's ${type} default project template`,
environments: ProjectTemplateDefaultEnvironments, environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
roles: [...getPredefinedRoles("project-template")].map(({ name, slug, permissions }) => ({ roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
name, ({ name, slug, permissions }) => ({
slug, name,
permissions: permissions as TUnpackedPermission[] slug,
})), permissions: permissions as TUnpackedPermission[]
})
),
orgId orgId
}); });
@@ -1,10 +1,11 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; 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 { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; 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 { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
import { import {
TCreateProjectTemplateDTO, TCreateProjectTemplateDTO,
@@ -32,11 +33,13 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
...rest, ...rest,
environments: environments as TProjectTemplateEnvironment[], environments: environments as TProjectTemplateEnvironment[],
roles: [ roles: [
...getPredefinedRoles("project-template").map(({ name, slug, permissions }) => ({ ...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
name, ({ name, slug, permissions }) => ({
slug, name,
permissions: permissions as TUnpackedPermission[] slug,
})), permissions: permissions as TUnpackedPermission[]
})
),
...(roles as TProjectTemplateRole[]).map((role) => ({ ...(roles as TProjectTemplateRole[]).map((role) => ({
...role, ...role,
permissions: unpackPermissions(role.permissions) permissions: unpackPermissions(role.permissions)
@@ -49,7 +52,7 @@ export const projectTemplateServiceFactory = ({
permissionService, permissionService,
projectTemplateDAL projectTemplateDAL
}: TProjectTemplatesServiceFactoryDep) => { }: TProjectTemplatesServiceFactoryDep) => {
const listProjectTemplatesByOrg = async (actor: OrgServiceActor) => { const listProjectTemplatesByOrg = async (actor: OrgServiceActor, type?: ProjectType) => {
const plan = await licenseService.getPlan(actor.orgId); const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates) if (!plan.projectTemplates)
@@ -68,11 +71,14 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
const projectTemplates = await projectTemplateDAL.find({ const projectTemplates = await projectTemplateDAL.find({
orgId: actor.orgId orgId: actor.orgId,
...(type ? { type } : {})
}); });
return [ return [
getDefaultProjectTemplate(actor.orgId), ...(type
? [getDefaultProjectTemplate(actor.orgId, type)]
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
...projectTemplates.map((template) => $unpackProjectTemplate(template)) ...projectTemplates.map((template) => $unpackProjectTemplate(template))
]; ];
}; };
@@ -134,7 +140,7 @@ export const projectTemplateServiceFactory = ({
}; };
const createProjectTemplate = async ( const createProjectTemplate = async (
{ roles, environments, ...params }: TCreateProjectTemplateDTO, { roles, environments, type, ...params }: TCreateProjectTemplateDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
const plan = await licenseService.getPlan(actor.orgId); const plan = await licenseService.getPlan(actor.orgId);
@@ -154,6 +160,17 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates); 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( const isConflictingName = Boolean(
await projectTemplateDAL.findOne({ await projectTemplateDAL.findOne({
name: params.name, name: params.name,
@@ -169,8 +186,10 @@ export const projectTemplateServiceFactory = ({
const projectTemplate = await projectTemplateDAL.create({ const projectTemplate = await projectTemplateDAL.create({
...params, ...params,
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))), roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
environments: JSON.stringify(environments), environments:
orgId: actor.orgId type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
orgId: actor.orgId,
type
}); });
return $unpackProjectTemplate(projectTemplate); return $unpackProjectTemplate(projectTemplate);
@@ -202,6 +221,19 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates); 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) { if (params.name && projectTemplate.name !== params.name) {
const isConflictingName = Boolean( const isConflictingName = Boolean(
await projectTemplateDAL.findOne({ await projectTemplateDAL.findOne({
@@ -1,6 +1,6 @@
import { z } from "zod"; 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 { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
@@ -15,8 +15,9 @@ export type TProjectTemplateRole = {
export type TCreateProjectTemplateDTO = { export type TCreateProjectTemplateDTO = {
name: string; name: string;
description?: string; description?: string;
type: ProjectType;
roles: TProjectTemplateRole[]; roles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[]; environments?: TProjectTemplateEnvironment[] | null;
}; };
export type TUpdateProjectTemplateDTO = Partial<TCreateProjectTemplateDTO>; export type TUpdateProjectTemplateDTO = Partial<TCreateProjectTemplateDTO>;
+4
View File
@@ -1822,8 +1822,12 @@ export const KMS = {
}; };
export const ProjectTemplates = { export const ProjectTemplates = {
LIST: {
type: "The type of project template to list."
},
CREATE: { CREATE: {
name: "The name of the project template to be created. Must be slug-friendly.", 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.", description: "An optional description of the project template.",
roles: "The roles to be created when the template is applied to a project.", 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." environments: "The environments to be created when the template is applied to a project."
@@ -1,15 +1,20 @@
import { ProjectMembershipRole } from "@app/db/schemas"; import { v4 as uuidv4 } from "uuid";
import { ProjectMembershipRole, ProjectType } from "@app/db/schemas";
import { import {
cryptographicOperatorPermissions,
projectAdminPermissions, projectAdminPermissions,
projectMemberPermissions, projectMemberPermissions,
projectNoAccessPermissions, projectNoAccessPermissions,
projectViewerPermission projectViewerPermission,
} from "@app/ee/services/permission/project-permission"; 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 [ return [
{ {
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // dummy userid id: uuidv4(),
projectId, projectId,
name: "Admin", name: "Admin",
slug: ProjectMembershipRole.Admin, slug: ProjectMembershipRole.Admin,
@@ -19,7 +24,7 @@ export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMember
updatedAt: new Date() updatedAt: new Date()
}, },
{ {
id: "b11b49a9-09a9-4443-916a-4246f9ff2c70", // dummy user for zod validation in response id: uuidv4(),
projectId, projectId,
name: "Developer", name: "Developer",
slug: ProjectMembershipRole.Member, slug: ProjectMembershipRole.Member,
@@ -29,7 +34,29 @@ export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMember
updatedAt: new Date() 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, projectId,
name: "Viewer", name: "Viewer",
slug: ProjectMembershipRole.Viewer, slug: ProjectMembershipRole.Viewer,
@@ -39,7 +66,7 @@ export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMember
updatedAt: new Date() updatedAt: new Date()
}, },
{ {
id: "b11b49a9-09a9-4443-916a-4246f9ff2c72", // dummy user for zod validation in response id: uuidv4(),
projectId, projectId,
name: "No Access", name: "No Access",
slug: ProjectMembershipRole.NoAccess, slug: ProjectMembershipRole.NoAccess,
@@ -48,5 +75,5 @@ export const getPredefinedRoles = (projectId: string, roleFilter?: ProjectMember
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date() updatedAt: new Date()
} }
].filter(({ slug }) => !roleFilter || roleFilter.includes(slug)); ].filter(({ slug, type }) => (type ? type === projectType : true) && (!roleFilter || roleFilter === slug));
}; };
@@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { requestContext } from "@fastify/request-context"; 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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
@@ -34,7 +34,7 @@ type TProjectRoleServiceFactoryDep = {
projectRoleDAL: TProjectRoleDALFactory; projectRoleDAL: TProjectRoleDALFactory;
identityDAL: Pick<TIdentityDALFactory, "findById">; identityDAL: Pick<TIdentityDALFactory, "findById">;
userDAL: Pick<TUserDALFactory, "findById">; userDAL: Pick<TUserDALFactory, "findById">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">; projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findProjectById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory; identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory;
projectUserMembershipRoleDAL: TProjectUserMembershipRoleDALFactory; projectUserMembershipRoleDAL: TProjectUserMembershipRoleDALFactory;
@@ -98,30 +98,37 @@ export const projectRoleServiceFactory = ({
roleSlug, roleSlug,
filter filter
}: TGetRoleDetailsDTO) => { }: TGetRoleDetailsDTO) => {
let projectId = ""; let project: TProjects;
if (filter.type === ProjectRoleServiceIdentifierType.SLUG) { if (filter.type === ProjectRoleServiceIdentifierType.SLUG) {
const project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId); project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
projectId = project.id;
} else { } else {
projectId = filter.projectId; project = await projectDAL.findProjectById(filter.projectId);
} }
if (!project) throw new NotFoundError({ message: "Project not found" });
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
projectId, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
actionProjectType: ActionProjectType.Any actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
if (roleSlug !== "custom" && Object.values(ProjectMembershipRole).includes(roleSlug as ProjectMembershipRole)) { 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) }; 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` }); if (!customRole) throw new NotFoundError({ message: `Project role with slug '${roleSlug}' not found` });
return { ...customRole, permissions: unpackPermissions(customRole.permissions) }; return { ...customRole, permissions: unpackPermissions(customRole.permissions) };
}; };
@@ -194,29 +201,32 @@ export const projectRoleServiceFactory = ({
}; };
const listRoles = async ({ actorOrgId, actorAuthMethod, actorId, actor, filter }: TListRolesDTO) => { const listRoles = async ({ actorOrgId, actorAuthMethod, actorId, actor, filter }: TListRolesDTO) => {
let projectId = ""; let project: TProjects;
if (filter.type === ProjectRoleServiceIdentifierType.SLUG) { if (filter.type === ProjectRoleServiceIdentifierType.SLUG) {
const project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId); project = await projectDAL.findProjectBySlug(filter.projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
projectId = project.id;
} else { } else {
projectId = filter.projectId; project = await projectDAL.findProjectById(filter.projectId);
} }
if (!project) throw new BadRequestError({ message: "Project not found" });
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
projectId, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
actionProjectType: ActionProjectType.Any actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
const customRoles = await projectRoleDAL.find( const customRoles = await projectRoleDAL.find(
{ projectId }, { projectId: project.id },
{ sort: [[`${TableName.ProjectRoles}.slug` as "slug", "asc"]] } { 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; return roles;
}; };
@@ -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"; import { TProjectPermission } from "@app/lib/types";
export enum ProjectRoleServiceIdentifierType { export enum ProjectRoleServiceIdentifierType {
@@ -34,3 +34,9 @@ export type TListRolesDTO = {
| { type: ProjectRoleServiceIdentifierType.SLUG; projectSlug: string } | { type: ProjectRoleServiceIdentifierType.SLUG; projectSlug: string }
| { type: ProjectRoleServiceIdentifierType.ID; projectId: string }; | { type: ProjectRoleServiceIdentifierType.ID; projectId: string };
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetPredefinedRolesDTO = {
projectId: string;
projectType: ProjectType;
roleFilter?: ProjectMembershipRole;
};
@@ -329,14 +329,16 @@ export const projectServiceFactory = ({
// set default environments and root folder for provided environments // set default environments and root folder for provided environments
let envs: TProjectEnvironments[] = []; let envs: TProjectEnvironments[] = [];
if (projectTemplate) { if (projectTemplate) {
envs = await projectEnvDAL.insertMany( if (projectTemplate.environments) {
projectTemplate.environments.map((env) => ({ ...env, projectId: project.id })), envs = await projectEnvDAL.insertMany(
tx projectTemplate.environments.map((env) => ({ ...env, projectId: project.id })),
); tx
await folderDAL.insertMany( );
envs.map(({ id }) => ({ name: ROOT_FOLDER_NAME, envId: id, version: 1 })), await folderDAL.insertMany(
tx envs.map(({ id }) => ({ name: ROOT_FOLDER_NAME, envId: id, version: 1 })),
); tx
);
}
await projectRoleDAL.insertMany( await projectRoleDAL.insertMany(
projectTemplate.packedRoles.map((role) => ({ projectTemplate.packedRoles.map((role) => ({
...role, ...role,
@@ -592,7 +594,10 @@ export const projectServiceFactory = ({
workspaces.map(async (workspace) => { workspaces.map(async (workspace) => {
return { return {
...workspace, ...workspace,
roles: [...(workspaceMappedToRoles[workspace.id] || []), ...getPredefinedRoles(workspace.id)] roles: [
...(workspaceMappedToRoles[workspace.id] || []),
...getPredefinedRoles({ projectId: workspace.id, projectType: workspace.type as ProjectType })
]
}; };
}) })
); );
@@ -33,7 +33,7 @@ In the following steps, we'll explore how to set up a project template.
<Tab title="Infisical UI"> <Tab title="Infisical UI">
<Steps> <Steps>
<Step title="Creating a Project Template"> <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) ![project template add button](/images/platform/project-templates/project-template-add-button.png)
Specify your template details. Here's some guidance on each field: 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' \ --header 'Content-Type: application/json' \
--data '{ --data '{
"name": "my-project-template", "name": "my-project-template",
"type": "secret-manager",
"description": "...", "description": "...",
"environments": "[...]", "environments": "[...]",
"roles": "[...]", "roles": "[...]",
+9 -9
View File
@@ -31,16 +31,9 @@ we will register a remote host with Infisical through a [machine identity](/docu
<Steps> <Steps>
<Step title="Create an Infisical SSH project"> <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) ![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>
<Step title="Create a machine identity for bootstrapping Infisical SSH"> <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. 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. 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> </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) ![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

@@ -72,7 +72,7 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
OrgPermissionSubjects.ProjectTemplates OrgPermissionSubjects.ProjectTemplates
); );
const { data: projectTemplates = [] } = useListProjectTemplates({ const { data: projectTemplates = [] } = useListProjectTemplates(projectType, {
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates) enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates)
}); });
@@ -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>
);
};
@@ -7,6 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { TProjectTemplate, useDeleteProjectTemplate } from "@app/hooks/api/projectTemplates"; import { TProjectTemplate, useDeleteProjectTemplate } from "@app/hooks/api/projectTemplates";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProjectTemplateDetailsModal } from "../../ProjectTemplateDetailsModal"; import { ProjectTemplateDetailsModal } from "../../ProjectTemplateDetailsModal";
import { ProjectTemplateEnvironmentsForm } from "./ProjectTemplateEnvironmentsForm"; import { ProjectTemplateEnvironmentsForm } from "./ProjectTemplateEnvironmentsForm";
@@ -24,7 +25,7 @@ export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBa
"editDetails" "editDetails"
] as const); ] as const);
const { id: templateId, name, description } = projectTemplate; const { id: templateId, name, description, type } = projectTemplate;
const deleteProjectTemplate = useDeleteProjectTemplate(); const deleteProjectTemplate = useDeleteProjectTemplate();
@@ -94,10 +95,12 @@ export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBa
</div> </div>
)} )}
</div> </div>
<ProjectTemplateEnvironmentsForm {type === ProjectType.SecretManager && (
isInfisicalTemplate={isInfisicalTemplate} <ProjectTemplateEnvironmentsForm
projectTemplate={projectTemplate} isInfisicalTemplate={isInfisicalTemplate}
/> projectTemplate={projectTemplate}
/>
)}
<ProjectTemplateRolesSection <ProjectTemplateRolesSection
isInfisicalTemplate={isInfisicalTemplate} isInfisicalTemplate={isInfisicalTemplate}
projectTemplate={projectTemplate} projectTemplate={projectTemplate}
@@ -19,7 +19,7 @@ import {
THead, THead,
Tr Tr
} from "@app/components/v2"; } 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 { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
import { slugSchema } from "@app/lib/schemas"; import { slugSchema } from "@app/lib/schemas";
@@ -35,6 +35,7 @@ const formSchema = z.object({
slug: slugSchema({ min: 1, max: 32 }) slug: slugSchema({ min: 1, max: 32 })
}) })
.array() .array()
.nullish()
}); });
type TFormSchema = z.infer<typeof formSchema>; type TFormSchema = z.infer<typeof formSchema>;
@@ -55,6 +56,8 @@ export const ProjectTemplateEnvironmentsForm = ({
resolver: zodResolver(formSchema) resolver: zodResolver(formSchema)
}); });
const { subscription } = useSubscription();
const { const {
fields: environments, fields: environments,
move, move,
@@ -67,7 +70,7 @@ export const ProjectTemplateEnvironmentsForm = ({
const onFormSubmit = async (form: TFormSchema) => { const onFormSubmit = async (form: TFormSchema) => {
try { try {
const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({ const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({
environments: form.environments.map((env, index) => ({ environments: form.environments?.map((env, index) => ({
...env, ...env,
position: index + 1 position: index + 1
})), })),
@@ -89,6 +92,9 @@ export const ProjectTemplateEnvironmentsForm = ({
} }
}; };
const isEnvironmentLimitExceeded =
Boolean(subscription.environmentLimit) && environments.length >= subscription.environmentLimit;
return ( return (
<form <form
onSubmit={handleSubmit(onFormSubmit)} onSubmit={handleSubmit(onFormSubmit)}
@@ -138,6 +144,8 @@ export const ProjectTemplateEnvironmentsForm = ({
<OrgPermissionCan <OrgPermissionCan
I={OrgPermissionActions.Edit} I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.ProjectTemplates} a={OrgPermissionSubjects.ProjectTemplates}
renderTooltip={isEnvironmentLimitExceeded ? true : undefined}
allowedLabel={`Plan environment limit of ${subscription.environmentLimit} exceeded. Contact Infisical to increase limit.`}
> >
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
@@ -147,7 +155,7 @@ export const ProjectTemplateEnvironmentsForm = ({
variant="solid" variant="solid"
size="xs" size="xs"
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
isDisabled={!isAllowed} isDisabled={!isAllowed || isEnvironmentLimitExceeded}
> >
Add Environment Add Environment
</Button> </Button>
@@ -12,6 +12,7 @@ import {
ModalContent, ModalContent,
TextArea TextArea
} from "@app/components/v2"; } from "@app/components/v2";
import { useGetProjectTypeFromRoute } from "@app/hooks";
import { import {
TProjectTemplate, TProjectTemplate,
useCreateProjectTemplate, useCreateProjectTemplate,
@@ -41,6 +42,7 @@ type FormProps = {
const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => { const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => {
const createProjectTemplate = useCreateProjectTemplate(); const createProjectTemplate = useCreateProjectTemplate();
const updateProjectTemplate = useUpdateProjectTemplate(); const updateProjectTemplate = useUpdateProjectTemplate();
const projectType = useGetProjectTypeFromRoute();
const { const {
handleSubmit, handleSubmit,
@@ -55,9 +57,17 @@ const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => {
}); });
const onFormSubmit = async (data: FormData) => { const onFormSubmit = async (data: FormData) => {
if (!projectType) {
createNotification({
text: "Failed to determine project type",
type: "error"
});
return;
}
const mutation = projectTemplate const mutation = projectTemplate
? updateProjectTemplate.mutateAsync({ templateId: projectTemplate.id, ...data }) ? updateProjectTemplate.mutateAsync({ templateId: projectTemplate.id, ...data })
: createProjectTemplate.mutateAsync(data); : createProjectTemplate.mutateAsync({ ...data, type: projectType });
try { try {
const template = await mutation; const template = await mutation;
createNotification({ createNotification({
@@ -50,7 +50,7 @@ export const ProjectTemplatesSection = () => {
className="absolute min-h-[10rem] w-full" className="absolute min-h-[10rem] w-full"
> >
<div> <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 Create and configure templates with predefined roles and environments to streamline
project setup project setup
</p> </p>
@@ -23,7 +23,7 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context"; 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 { TProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
import { DeleteProjectTemplateModal } from "./DeleteProjectTemplateModal"; import { DeleteProjectTemplateModal } from "./DeleteProjectTemplateModal";
@@ -35,8 +35,10 @@ type Props = {
export const ProjectTemplatesTable = ({ onEdit }: Props) => { export const ProjectTemplatesTable = ({ onEdit }: Props) => {
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { isPending, data: projectTemplates = [] } = useListProjectTemplates({ const projectType = useGetProjectTypeFromRoute();
enabled: subscription?.projectTemplates
const { isPending, data: projectTemplates = [] } = useListProjectTemplates(projectType, {
enabled: subscription?.projectTemplates && Boolean(projectType)
}); });
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@@ -50,6 +52,8 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
[search, projectTemplates] [search, projectTemplates]
); );
const isSecretManagerTemplates = projectType === "secret-manager";
return ( return (
<div> <div>
<Input <Input
@@ -64,7 +68,7 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
<Tr> <Tr>
<Th>Name</Th> <Th>Name</Th>
<Th>Roles</Th> <Th>Roles</Th>
<Th>Environments</Th> {isSecretManagerTemplates && <Th>Environments</Th>}
<Th /> <Th />
</Tr> </Tr>
</THead> </THead>
@@ -77,7 +81,7 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
/> />
)} )}
{filteredTemplates.map((template) => { {filteredTemplates.map((template) => {
const { id, name, roles, environments, description } = template; const { id, name, roles, environments = [], description } = template;
return ( return (
<Tr <Tr
onClick={() => onEdit(template)} onClick={() => onEdit(template)}
@@ -116,28 +120,30 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
</Tooltip> </Tooltip>
)} )}
</Td> </Td>
<Td className="pl-14"> {isSecretManagerTemplates && environments && (
{environments.length} <Td className="pl-14">
{environments.length > 0 && ( {environments.length}
<Tooltip {environments.length > 0 && (
content={ <Tooltip
<ul className="ml-2 list-disc"> content={
{environments <ul className="ml-2 list-disc">
.sort((a, b) => (a.position > b.position ? 1 : -1)) {environments
.map((env) => ( .sort((a, b) => (a.position > b.position ? 1 : -1))
<li key={env.slug}>{env.name}</li> .map((env) => (
))} <li key={env.slug}>{env.name}</li>
</ul> ))}
} </ul>
> }
<FontAwesomeIcon >
size="sm" <FontAwesomeIcon
className="ml-2 text-mineshaft-400" size="sm"
icon={faCircleInfo} className="ml-2 text-mineshaft-400"
/> icon={faCircleInfo}
</Tooltip> />
)} </Tooltip>
</Td> )}
</Td>
)}
<Td className="w-5"> <Td className="w-5">
{name !== "default" && ( {name !== "default" && (
<OrgPermissionCan <OrgPermissionCan
@@ -0,0 +1 @@
export * from "./ProjectTemplatesTab";
@@ -0,0 +1 @@
export * from "./ProjectSettings";
+21 -12
View File
@@ -6,21 +6,9 @@ export enum OrgMembershipRole {
NoAccess = "no-access" NoAccess = "no-access"
} }
enum ProjectMemberRole {
Admin = "admin",
Member = "member",
Viewer = "viewer",
NoAccess = "no-access"
}
export const isCustomOrgRole = (slug: string) => export const isCustomOrgRole = (slug: string) =>
!Object.values(OrgMembershipRole).includes(slug as OrgMembershipRole); !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) => export const isCustomProjectRole = (slug: string) =>
!Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole); !Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
@@ -28,3 +16,24 @@ export const findOrgMembershipRole = (roles: TOrgRole[], roleIdOrSlug: string) =
isCustomOrgRole(roleIdOrSlug) isCustomOrgRole(roleIdOrSlug)
? roles.find((r) => r.id === roleIdOrSlug) ? roles.find((r) => r.id === roleIdOrSlug)
: roles.find((r) => r.slug === 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;
}
};
@@ -6,14 +6,17 @@ import {
TProjectTemplate, TProjectTemplate,
TProjectTemplateResponse TProjectTemplateResponse
} from "@app/hooks/api/projectTemplates/types"; } from "@app/hooks/api/projectTemplates/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const projectTemplateKeys = { export const projectTemplateKeys = {
all: ["project-template"] as const, 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 byId: (templateId: string) => [...projectTemplateKeys.all, templateId] as const
}; };
export const useListProjectTemplates = ( export const useListProjectTemplates = (
type?: ProjectType,
options?: Omit< options?: Omit<
UseQueryOptions< UseQueryOptions<
TProjectTemplate[], TProjectTemplate[],
@@ -25,9 +28,11 @@ export const useListProjectTemplates = (
> >
) => { ) => {
return useQuery({ return useQuery({
queryKey: projectTemplateKeys.list(), queryKey: projectTemplateKeys.list(type),
queryFn: async () => { 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; return data.projectTemplates;
}, },
@@ -1,11 +1,13 @@
import { TProjectRole } from "@app/hooks/api/roles/types"; import { TProjectRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
export type TProjectTemplate = { export type TProjectTemplate = {
id: string; id: string;
name: string; name: string;
type: ProjectType;
description?: string; description?: string;
roles: Pick<TProjectRole, "slug" | "name" | "permissions">[]; roles: Pick<TProjectRole, "slug" | "name" | "permissions">[];
environments: { name: string; slug: string; position: number }[]; environments?: { name: string; slug: string; position: number }[] | null;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
}; };
@@ -14,6 +16,7 @@ export type TListProjectTemplates = { projectTemplates: TProjectTemplate[] };
export type TProjectTemplateResponse = { projectTemplate: TProjectTemplate }; export type TProjectTemplateResponse = { projectTemplate: TProjectTemplate };
export type TCreateProjectTemplateDTO = { export type TCreateProjectTemplateDTO = {
type: ProjectType;
name: string; name: string;
description?: string; description?: string;
}; };
+3 -1
View File
@@ -3,7 +3,9 @@ export enum ProjectMembershipRole {
Member = "member", Member = "member",
Custom = "custom", Custom = "custom",
Viewer = "viewer", Viewer = "viewer",
NoAccess = "no-access" NoAccess = "no-access",
SshHostBootstrapper = "ssh-host-bootstrapper",
KmsCryptographicOperator = "cryptographic-operator"
} }
export type TGetProjectRolesDTO = { export type TGetProjectRolesDTO = {
+1
View File
@@ -1,4 +1,5 @@
export { useDebounce } from "./useDebounce"; export { useDebounce } from "./useDebounce";
export * from "./useGetProjectTypeFromRoute";
export { usePagination } from "./usePagination"; export { usePagination } from "./usePagination";
export { usePersistentState } from "./usePersistentState"; export { usePersistentState } from "./usePersistentState";
export { usePopUp } from "./usePopUp"; export { usePopUp } from "./usePopUp";
@@ -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;
};
@@ -11,11 +11,12 @@ import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2";
import { OrgPermissionSubjects, useOrgPermission, useServerConfig } from "@app/context"; import { OrgPermissionSubjects, useOrgPermission, useServerConfig } from "@app/context";
import { OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types"; import { OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar"; import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar";
import { SidebarHeader } from "./components/SidebarHeader"; import { SidebarHeader } from "./components/SidebarHeader";
import { DefaultSideBar, SecretSharingSideBar } from "./ProductsSideBar"; import { DefaultSideBar, ProjectOverviewSideBar, SecretSharingSideBar } from "./ProductsSideBar";
export const OrganizationLayout = () => { export const OrganizationLayout = () => {
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context }); const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
@@ -45,21 +46,38 @@ export const OrganizationLayout = () => {
] as string[] ] as string[]
).includes(location.pathname); ).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 = const shouldShowOrgSidebar =
location.pathname.startsWith("/organization") && location.pathname.startsWith("/organization") &&
(!isSecretSharingPage || shouldShowProductsSidebar) && (!isSecretSharingPage || shouldShowProductsSidebar) &&
!( !([linkOptions({ to: "/organization/secret-scanning" }).to] as string[]).includes(
[ location.pathname
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);
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen"; 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 ( return (
<> <>
<Banner /> <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" 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]"> <nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="p-2 pt-3"> {!isProjectOverviewOrSettingsPage && (
<SidebarHeader /> <div className="p-2 pt-3">
</div> <SidebarHeader />
{isSecretSharingPage ? <SecretSharingSideBar /> : <DefaultSideBar />} </div>
)}
{SideBarComponent}
</nav> </nav>
</motion.div> </motion.div>
)} )}
@@ -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>
</>
);
};
@@ -1,2 +1,3 @@
export * from "./DefaultSideBar"; export * from "./DefaultSideBar";
export * from "./ProjectOverviewSideBar";
export * from "./SecretSharingSideBar"; export * from "./SecretSharingSideBar";
@@ -269,7 +269,9 @@ export const MinimizedOrgSidebar = () => {
<MenuIconButton <MenuIconButton
isSelected={ isSelected={
isActive || isActive ||
window.location.pathname.startsWith(`/${ProjectType.SecretManager}`) window.location.pathname.startsWith(
`/organization/${ProjectType.SecretManager}`
)
} }
icon="sliding-carousel" icon="sliding-carousel"
> >
@@ -282,7 +284,9 @@ export const MinimizedOrgSidebar = () => {
<MenuIconButton <MenuIconButton
isSelected={ isSelected={
isActive || isActive ||
window.location.pathname.startsWith(`/${ProjectType.CertificateManager}`) window.location.pathname.startsWith(
`/organization/${ProjectType.CertificateManager}`
)
} }
icon="note" icon="note"
> >
@@ -294,7 +298,8 @@ export const MinimizedOrgSidebar = () => {
{({ isActive }) => ( {({ isActive }) => (
<MenuIconButton <MenuIconButton
isSelected={ isSelected={
isActive || window.location.pathname.startsWith(`/${ProjectType.KMS}`) isActive ||
window.location.pathname.startsWith(`/organization/${ProjectType.KMS}`)
} }
icon="unlock" icon="unlock"
> >
@@ -306,7 +311,8 @@ export const MinimizedOrgSidebar = () => {
{({ isActive }) => ( {({ isActive }) => (
<MenuIconButton <MenuIconButton
isSelected={ isSelected={
isActive || window.location.pathname.startsWith(`/${ProjectType.SSH}`) isActive ||
window.location.pathname.startsWith(`/organization/${ProjectType.SSH}`)
} }
icon="verified" icon="verified"
> >
@@ -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>
</>
);
};
@@ -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"
}
]
})
});
@@ -6,9 +6,9 @@ import { format } from "date-fns";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2"; import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
import { formatProjectRoleName } from "@app/helpers/roles";
import { useGetUserWorkspaces } from "@app/hooks/api"; import { useGetUserWorkspaces } from "@app/hooks/api";
import { IdentityMembership } from "@app/hooks/api/identities/types"; import { IdentityMembership } from "@app/hooks/api/identities/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
export enum TabSections { export enum TabSections {
@@ -26,15 +26,6 @@ type Props = {
) => void; ) => 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 = ({ export const IdentityProjectRow = ({
membership: { id, createdAt, identity, project, roles }, membership: { id, createdAt, identity, project, roles },
handlePopUpOpen handlePopUpOpen
@@ -80,7 +71,7 @@ export const IdentityProjectRow = ({
<Td> <Td>
<Tag size="xs">{project.type}</Tag> <Tag size="xs">{project.type}</Tag>
</Td> </Td>
<Td>{`${formatRoleName(roles[0].role, roles[0].customRoleName)}${ <Td>{`${formatProjectRoleName(roles[0].role, roles[0].customRoleName)}${
roles.length > 1 ? ` (+${roles.length - 1})` : "" roles.length > 1 ? ` (+${roles.length - 1})` : ""
}`}</Td> }`}</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td> <Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
@@ -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>
</>
);
};
@@ -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"
}
]
})
});
@@ -117,12 +117,8 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
}); });
reset(); reset();
} catch (err) { } catch {
console.error(err); const text = `Failed to ${popUp?.role?.data ? "update" : "create"} role`;
const error = err as any;
const text =
error?.response?.data?.message ??
`Failed to ${popUp?.role?.data ? "update" : "create"} role`;
createNotification({ createNotification({
text, text,
@@ -27,6 +27,7 @@ export const ProjectListToggle = ({ value, onChange }: Props) => {
value={value} value={value}
onValueChange={onChange} onValueChange={onChange}
className="absolute left-0 top-0 h-full w-full cursor-pointer opacity-0" 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.MyProjects}>My Projects</SelectItem>
<SelectItem value={ProjectListView.AllProjects}>All Projects</SelectItem> <SelectItem value={ProjectListView.AllProjects}>All Projects</SelectItem>
@@ -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>
</>
);
};
@@ -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"
}
]
})
});
@@ -2,6 +2,7 @@ import { useState } from "react";
import { useSearch } from "@tanstack/react-router"; import { useSearch } from "@tanstack/react-router";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; 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 { ROUTE_PATHS } from "@app/const/routes";
import { AuditLogStreamsTab } from "../AuditLogStreamTab"; import { AuditLogStreamsTab } from "../AuditLogStreamTab";
@@ -11,7 +12,6 @@ import { OrgAuthTab } from "../OrgAuthTab";
import { OrgEncryptionTab } from "../OrgEncryptionTab"; import { OrgEncryptionTab } from "../OrgEncryptionTab";
import { OrgGeneralTab } from "../OrgGeneralTab"; import { OrgGeneralTab } from "../OrgGeneralTab";
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab"; import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab";
import { ProjectTemplatesTab } from "../ProjectTemplatesTab";
export const OrgTabGroup = () => { export const OrgTabGroup = () => {
const search = useSearch({ const search = useSearch({
@@ -28,7 +28,34 @@ export const OrgTabGroup = () => {
}, },
{ name: "Audit Log Streams", key: "tag-audit-log-streams", component: AuditLogStreamsTab }, { name: "Audit Log Streams", key: "tag-audit-log-streams", component: AuditLogStreamsTab },
{ name: "Import", key: "tab-import", component: ImportTab }, { 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 } { name: "KMIP", key: "kmip", component: KmipTab }
]; ];
@@ -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>
</>
);
};
@@ -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"
}
]
})
});
@@ -5,8 +5,8 @@ import { useNavigate } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2"; import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
import { formatProjectRoleName } from "@app/helpers/roles";
import { useGetUserWorkspaces } from "@app/hooks/api"; import { useGetUserWorkspaces } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { TWorkspaceUser } from "@app/hooks/api/types"; import { TWorkspaceUser } from "@app/hooks/api/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { OrgAccessControlTabSections } from "@app/types/org"; import { OrgAccessControlTabSections } from "@app/types/org";
@@ -19,15 +19,6 @@ type Props = {
) => void; ) => 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 = ({ export const UserProjectRow = ({
membership: { id, project, user, roles }, membership: { id, project, user, roles },
handlePopUpOpen handlePopUpOpen
@@ -73,7 +64,7 @@ export const UserProjectRow = ({
<Td> <Td>
<Tag size="xs">{project.type}</Tag> <Tag size="xs">{project.type}</Tag>
</Td> </Td>
<Td>{`${formatRoleName(roles[0].role, roles[0].customRoleName)}${ <Td>{`${formatProjectRoleName(roles[0].role, roles[0].customRoleName)}${
roles.length > 1 ? ` (+${roles.length - 1})` : "" roles.length > 1 ? ` (+${roles.length - 1})` : ""
}`}</Td> }`}</Td>
<Td> <Td>
@@ -24,10 +24,10 @@ import {
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useGetProjectRoles, useUpdateGroupWorkspaceRole } from "@app/hooks/api"; import { useGetProjectRoles, useUpdateGroupWorkspaceRole } from "@app/hooks/api";
import { TGroupMembership } from "@app/hooks/api/groups/types"; 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 { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/workspace/types";
import { groupBy } from "@app/lib/fn/array"; 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 ( return (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{roles {roles
@@ -275,7 +269,7 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember
return ( return (
<Tag key={id} className="capitalize"> <Tag key={id} className="capitalize">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div> <div>{formatProjectRoleName(role, customRoleName)}</div>
{isTemporary && ( {isTemporary && (
<div> <div>
<Tooltip content={isExpired ? "Expired Temporary Access" : "Temporary Access"}> <Tooltip content={isExpired ? "Expired Temporary Access" : "Temporary Access"}>
@@ -303,7 +297,7 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember
return ( return (
<Tag key={id} className="capitalize"> <Tag key={id} className="capitalize">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div> <div>{formatProjectRoleName(role, customRoleName)}</div>
{isTemporary && ( {isTemporary && (
<div> <div>
<Tooltip <Tooltip
@@ -41,11 +41,11 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles";
import { withProjectPermission } from "@app/hoc"; import { withProjectPermission } from "@app/hoc";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api"; import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; 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 { ProjectIdentityOrderBy } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp"; 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 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( export const IdentityTab = withProjectPermission(
() => { () => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@@ -286,7 +280,7 @@ export const IdentityTab = withProjectPermission(
<Tag key={roleId}> <Tag key={roleId}>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="capitalize"> <div className="capitalize">
{formatRoleName(role, customRoleName)} {formatProjectRoleName(role, customRoleName)}
</div> </div>
{isTemporary && ( {isTemporary && (
<div> <div>
@@ -331,7 +325,9 @@ export const IdentityTab = withProjectPermission(
return ( return (
<Tag key={roleId} className="capitalize"> <Tag key={roleId} className="capitalize">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div> <div>
{formatProjectRoleName(role, customRoleName)}
</div>
{isTemporary && ( {isTemporary && (
<div> <div>
<Tooltip <Tooltip
@@ -50,19 +50,13 @@ import {
useUser, useUser,
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api"; import { useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2; 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 = { type Props = {
handlePopUpOpen: ( handlePopUpOpen: (
@@ -343,7 +337,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
<Tag key={id}> <Tag key={id}>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="capitalize"> <div className="capitalize">
{formatRoleName(role, customRoleName)} {formatProjectRoleName(role, customRoleName)}
</div> </div>
{isTemporary && ( {isTemporary && (
<div> <div>
@@ -386,7 +380,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
return ( return (
<Tag key={id} className="capitalize"> <Tag key={id} className="capitalize">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div>{formatRoleName(role, customRoleName)}</div> <div>{formatProjectRoleName(role, customRoleName)}</div>
{isTemporary && ( {isTemporary && (
<div> <div>
<Tooltip <Tooltip
@@ -24,7 +24,7 @@ import {
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteProjectRole, useGetProjectRoles } from "@app/hooks/api"; 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"; import { RoleModal } from "@app/pages/project/RoleDetailsBySlugPage/components/RoleModal";
export const ProjectRoleList = () => { export const ProjectRoleList = () => {
@@ -86,7 +86,9 @@ export const ProjectRoleList = () => {
{isRolesLoading && <TableSkeleton columns={4} innerKey="org-roles" />} {isRolesLoading && <TableSkeleton columns={4} innerKey="org-roles" />}
{roles?.map((role) => { {roles?.map((role) => {
const { id, name, slug } = 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 ( return (
<Tr <Tr
@@ -50,6 +50,7 @@ import {
isConditionalSubjects, isConditionalSubjects,
PROJECT_PERMISSION_OBJECT, PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema, projectRoleFormSchema,
ProjectTypePermissionSubjects,
rolePermission2Form rolePermission2Form
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils"; } from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection"; import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
@@ -269,6 +270,12 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end"> <DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT) {Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(sub) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
sub as ProjectPermissionSub
]
)
.sort((a, b) => .sort((a, b) =>
PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title
.toLowerCase() .toLowerCase()
@@ -49,6 +49,7 @@ import {
isConditionalSubjects, isConditionalSubjects,
PROJECT_PERMISSION_OBJECT, PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema, projectRoleFormSchema,
ProjectTypePermissionSubjects,
rolePermission2Form rolePermission2Form
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils"; } from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection"; import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
@@ -265,6 +266,12 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end"> <DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT) {Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(subject) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
subject as ProjectPermissionSub
]
)
.sort((a, b) => .sort((a, b) =>
PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title
.toLowerCase() .toLowerCase()
@@ -17,6 +17,7 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useDeleteProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api"; import { useDeleteProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { ProjectAccessControlTabs } from "@app/types/project"; 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 ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
@@ -12,11 +12,14 @@ import {
SelectItem SelectItem
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSub } from "@app/context";
import { useGetProjectTypeFromRoute } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { import {
isConditionalSubjects, isConditionalSubjects,
PROJECT_PERMISSION_OBJECT, PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema, projectRoleFormSchema,
ProjectTypePermissionSubjects,
TFormSchema TFormSchema
} from "./ProjectRoleModifySection.utils"; } from "./ProjectRoleModifySection.utils";
@@ -37,18 +40,20 @@ export const NewPermissionRule = ({ onClose }: Props) => {
.extend({ type: z.nativeEnum(ProjectPermissionSub) }) .extend({ type: z.nativeEnum(ProjectPermissionSub) })
), ),
defaultValues: { defaultValues: {
type: ProjectPermissionSub.Secrets type: ProjectPermissionSub.Project
} }
}); });
const selectedSubject = form.watch("type"); const selectedSubject = form.watch("type");
const projectType = useGetProjectTypeFromRoute();
return ( return (
<div> <div>
<Controller <Controller
control={form.control} control={form.control}
name="type" name="type"
defaultValue={ProjectPermissionSub.Secrets} defaultValue={ProjectPermissionSub.Project}
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Subject" errorText={error?.message} isError={Boolean(error)}> <FormControl label="Subject" errorText={error?.message} isError={Boolean(error)}>
<Select <Select
@@ -57,11 +62,18 @@ export const NewPermissionRule = ({ onClose }: Props) => {
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full" className="w-full"
> >
{Object.keys(PROJECT_PERMISSION_OBJECT).map((subject) => ( {Object.keys(PROJECT_PERMISSION_OBJECT)
<SelectItem value={subject} key={`permission-create-${subject}`}> .filter(
{PROJECT_PERMISSION_OBJECT[subject as ProjectPermissionSub].title} (subject) =>
</SelectItem> 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> </Select>
</FormControl> </FormControl>
)} )}
@@ -25,6 +25,7 @@ import {
TPermissionConditionOperators TPermissionConditionOperators
} from "@app/context/ProjectPermissionContext/types"; } from "@app/context/ProjectPermissionContext/types";
import { TProjectPermission } from "@app/hooks/api/roles/types"; import { TProjectPermission } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
const GeneralPolicyActionSchema = z.object({ const GeneralPolicyActionSchema = z.object({
read: z.boolean().optional(), read: z.boolean().optional(),
@@ -1122,7 +1123,7 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
] ]
}, },
[ProjectPermissionSub.SecretApproval]: { [ProjectPermissionSub.SecretApproval]: {
title: "Secret Protect policy", title: "Secret Approval Policies",
actions: [ actions: [
{ label: "Read", value: "read" }, { label: "Read", value: "read" },
{ label: "Create", value: "create" }, { 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()
}
};
@@ -6,6 +6,7 @@ import { IconButton, Tooltip } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useTimedReset } from "@app/hooks"; import { useTimedReset } from "@app/hooks";
import { useGetProjectRoleBySlug } from "@app/hooks/api"; import { useGetProjectRoleBySlug } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = { type Props = {
@@ -21,7 +22,9 @@ export const RoleDetailsSection = ({ roleSlug, handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data } = useGetProjectRoleBySlug(currentWorkspace?.id ?? "", roleSlug as string); 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 ? ( return data ? (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
@@ -115,12 +115,8 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
}); });
reset(); reset();
} catch (err) { } catch {
console.error(err); const text = `Failed to ${popUp?.role?.data ? "update" : "create"} role`;
const error = err as any;
const text =
error?.response?.data?.message ??
`Failed to ${popUp?.role?.data ? "update" : "create"} role`;
createNotification({ createNotification({
text, text,
@@ -19,6 +19,7 @@ import { ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext"; import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
import { evaluatePermissionsAbility } from "@app/helpers/permissions"; import { evaluatePermissionsAbility } from "@app/helpers/permissions";
import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api"; import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types"; import { ProjectType } from "@app/hooks/api/workspace/types";
import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions"; import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions";
@@ -31,6 +32,7 @@ import {
isConditionalSubjects, isConditionalSubjects,
PROJECT_PERMISSION_OBJECT, PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema, projectRoleFormSchema,
ProjectTypePermissionSubjects,
rolePermission2Form, rolePermission2Form,
TFormSchema TFormSchema
} from "./ProjectRoleModifySection.utils"; } 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 onNewPolicy = (selectedSubject: ProjectPermissionSub) => {
const rootPolicyValue = form.getValues(`permissions.${selectedSubject}`); const rootPolicyValue = form.getValues(`permissions.${selectedSubject}`);
@@ -192,6 +196,12 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end"> <DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT) {Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(subject) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
subject as ProjectPermissionSub
]
)
.sort((a, b) => .sort((a, b) =>
PROJECT_PERMISSION_OBJECT[ PROJECT_PERMISSION_OBJECT[
a as keyof typeof PROJECT_PERMISSION_OBJECT a as keyof typeof PROJECT_PERMISSION_OBJECT
@@ -221,17 +231,19 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
</div> </div>
<div className="py-4"> <div className="py-4">
{!isPending && <PermissionEmptyState />} {!isPending && <PermissionEmptyState />}
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map((subject) => ( {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[])
<GeneralPermissionPolicies .filter((subject) => ProjectTypePermissionSubjects[currentWorkspace.type][subject])
subject={subject} .map((subject) => (
actions={PROJECT_PERMISSION_OBJECT[subject].actions} <GeneralPermissionPolicies
title={PROJECT_PERMISSION_OBJECT[subject].title} subject={subject}
key={`project-permission-${subject}`} actions={PROJECT_PERMISSION_OBJECT[subject].actions}
isDisabled={isDisabled} title={PROJECT_PERMISSION_OBJECT[subject].title}
> key={`project-permission-${subject}`}
{renderConditionalComponents(subject, isDisabled)} isDisabled={isDisabled}
</GeneralPermissionPolicies> >
))} {renderConditionalComponents(subject, isDisabled)}
</GeneralPermissionPolicies>
))}
</div> </div>
</FormProvider> </FormProvider>
</form> </form>
+121 -1
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 secretManagerLayoutImport } from './pages/secret-manager/layout'
import { Route as kmsLayoutImport } from './pages/kms/layout' import { Route as kmsLayoutImport } from './pages/kms/layout'
import { Route as certManagerLayoutImport } from './pages/cert-manager/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 organizationSshOverviewPageRouteImport } from './pages/organization/SshOverviewPage/route'
import { Route as organizationSecretSharingSettingsPageRouteImport } from './pages/organization/SecretSharingSettingsPage/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 organizationSecretManagerOverviewPageRouteImport } from './pages/organization/SecretManagerOverviewPage/route'
import { Route as organizationRoleByIDPageRouteImport } from './pages/organization/RoleByIDPage/route' import { Route as organizationRoleByIDPageRouteImport } from './pages/organization/RoleByIDPage/route'
import { Route as organizationUserDetailsByIDPageRouteImport } from './pages/organization/UserDetailsByIDPage/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 organizationKmsOverviewPageRouteImport } from './pages/organization/KmsOverviewPage/route'
import { Route as organizationIdentityDetailsByIDPageRouteImport } from './pages/organization/IdentityDetailsByIDPage/route' import { Route as organizationIdentityDetailsByIDPageRouteImport } from './pages/organization/IdentityDetailsByIDPage/route'
import { Route as organizationGroupDetailsByIDPageRouteImport } from './pages/organization/GroupDetailsByIDPage/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 organizationCertManagerOverviewPageRouteImport } from './pages/organization/CertManagerOverviewPage/route'
import { Route as organizationSettingsPageRouteImport } from './pages/organization/SettingsPage/route' import { Route as organizationSettingsPageRouteImport } from './pages/organization/SettingsPage/route'
import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route' import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route'
@@ -604,6 +608,14 @@ const certManagerLayoutRoute = certManagerLayoutImport.update({
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRoute, AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRoute,
} as any) } as any)
const organizationSshSettingsPageRouteRoute =
organizationSshSettingsPageRouteImport.update({
id: '/ssh/settings',
path: '/ssh/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSshOverviewPageRouteRoute = const organizationSshOverviewPageRouteRoute =
organizationSshOverviewPageRouteImport.update({ organizationSshOverviewPageRouteImport.update({
id: '/ssh/overview', id: '/ssh/overview',
@@ -620,6 +632,14 @@ const organizationSecretSharingSettingsPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute,
} as any) } as any)
const organizationSecretManagerSettingsPageRouteRoute =
organizationSecretManagerSettingsPageRouteImport.update({
id: '/secret-manager/settings',
path: '/secret-manager/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSecretManagerOverviewPageRouteRoute = const organizationSecretManagerOverviewPageRouteRoute =
organizationSecretManagerOverviewPageRouteImport.update({ organizationSecretManagerOverviewPageRouteImport.update({
id: '/secret-manager/overview', id: '/secret-manager/overview',
@@ -644,6 +664,14 @@ const organizationUserDetailsByIDPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any) } as any)
const organizationKmsSettingsPageRouteRoute =
organizationKmsSettingsPageRouteImport.update({
id: '/kms/settings',
path: '/kms/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationKmsOverviewPageRouteRoute = const organizationKmsOverviewPageRouteRoute =
organizationKmsOverviewPageRouteImport.update({ organizationKmsOverviewPageRouteImport.update({
id: '/kms/overview', id: '/kms/overview',
@@ -668,6 +696,14 @@ const organizationGroupDetailsByIDPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any) } as any)
const organizationCertManagerSettingsPageRouteRoute =
organizationCertManagerSettingsPageRouteImport.update({
id: '/cert-manager/settings',
path: '/cert-manager/settings',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationCertManagerOverviewPageRouteRoute = const organizationCertManagerOverviewPageRouteRoute =
organizationCertManagerOverviewPageRouteImport.update({ organizationCertManagerOverviewPageRouteImport.update({
id: '/cert-manager/overview', id: '/cert-manager/overview',
@@ -2072,6 +2108,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationCertManagerOverviewPageRouteImport preLoaderRoute: typeof organizationCertManagerOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport 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': { '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId' id: '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId'
path: '/groups/$groupId' path: '/groups/$groupId'
@@ -2093,6 +2136,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationKmsOverviewPageRouteImport preLoaderRoute: typeof organizationKmsOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport 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': { '/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId' id: '/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId'
path: '/members/$membershipId' path: '/members/$membershipId'
@@ -2114,6 +2164,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationSecretManagerOverviewPageRouteImport preLoaderRoute: typeof organizationSecretManagerOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport 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': { '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings' id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings'
path: '/settings' path: '/settings'
@@ -2128,6 +2185,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationSshOverviewPageRouteImport preLoaderRoute: typeof organizationSshOverviewPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport 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': { '/_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' id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout'
path: '' path: ''
@@ -3168,13 +3232,17 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren {
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren
organizationCertManagerOverviewPageRouteRoute: typeof organizationCertManagerOverviewPageRouteRoute organizationCertManagerOverviewPageRouteRoute: typeof organizationCertManagerOverviewPageRouteRoute
organizationCertManagerSettingsPageRouteRoute: typeof organizationCertManagerSettingsPageRouteRoute
organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute
organizationIdentityDetailsByIDPageRouteRoute: typeof organizationIdentityDetailsByIDPageRouteRoute organizationIdentityDetailsByIDPageRouteRoute: typeof organizationIdentityDetailsByIDPageRouteRoute
organizationKmsOverviewPageRouteRoute: typeof organizationKmsOverviewPageRouteRoute organizationKmsOverviewPageRouteRoute: typeof organizationKmsOverviewPageRouteRoute
organizationKmsSettingsPageRouteRoute: typeof organizationKmsSettingsPageRouteRoute
organizationUserDetailsByIDPageRouteRoute: typeof organizationUserDetailsByIDPageRouteRoute organizationUserDetailsByIDPageRouteRoute: typeof organizationUserDetailsByIDPageRouteRoute
organizationRoleByIDPageRouteRoute: typeof organizationRoleByIDPageRouteRoute organizationRoleByIDPageRouteRoute: typeof organizationRoleByIDPageRouteRoute
organizationSecretManagerOverviewPageRouteRoute: typeof organizationSecretManagerOverviewPageRouteRoute organizationSecretManagerOverviewPageRouteRoute: typeof organizationSecretManagerOverviewPageRouteRoute
organizationSecretManagerSettingsPageRouteRoute: typeof organizationSecretManagerSettingsPageRouteRoute
organizationSshOverviewPageRouteRoute: typeof organizationSshOverviewPageRouteRoute organizationSshOverviewPageRouteRoute: typeof organizationSshOverviewPageRouteRoute
organizationSshSettingsPageRouteRoute: typeof organizationSshSettingsPageRouteRoute
} }
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren = const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren =
@@ -3196,19 +3264,27 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren,
organizationCertManagerOverviewPageRouteRoute: organizationCertManagerOverviewPageRouteRoute:
organizationCertManagerOverviewPageRouteRoute, organizationCertManagerOverviewPageRouteRoute,
organizationCertManagerSettingsPageRouteRoute:
organizationCertManagerSettingsPageRouteRoute,
organizationGroupDetailsByIDPageRouteRoute: organizationGroupDetailsByIDPageRouteRoute:
organizationGroupDetailsByIDPageRouteRoute, organizationGroupDetailsByIDPageRouteRoute,
organizationIdentityDetailsByIDPageRouteRoute: organizationIdentityDetailsByIDPageRouteRoute:
organizationIdentityDetailsByIDPageRouteRoute, organizationIdentityDetailsByIDPageRouteRoute,
organizationKmsOverviewPageRouteRoute: organizationKmsOverviewPageRouteRoute:
organizationKmsOverviewPageRouteRoute, organizationKmsOverviewPageRouteRoute,
organizationKmsSettingsPageRouteRoute:
organizationKmsSettingsPageRouteRoute,
organizationUserDetailsByIDPageRouteRoute: organizationUserDetailsByIDPageRouteRoute:
organizationUserDetailsByIDPageRouteRoute, organizationUserDetailsByIDPageRouteRoute,
organizationRoleByIDPageRouteRoute: organizationRoleByIDPageRouteRoute, organizationRoleByIDPageRouteRoute: organizationRoleByIDPageRouteRoute,
organizationSecretManagerOverviewPageRouteRoute: organizationSecretManagerOverviewPageRouteRoute:
organizationSecretManagerOverviewPageRouteRoute, organizationSecretManagerOverviewPageRouteRoute,
organizationSecretManagerSettingsPageRouteRoute:
organizationSecretManagerSettingsPageRouteRoute,
organizationSshOverviewPageRouteRoute: organizationSshOverviewPageRouteRoute:
organizationSshOverviewPageRouteRoute, organizationSshOverviewPageRouteRoute,
organizationSshSettingsPageRouteRoute:
organizationSshSettingsPageRouteRoute,
} }
const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren = const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren =
@@ -3893,14 +3969,18 @@ export interface FileRoutesByFullPath {
'/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute '/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute
'/organization/settings/': typeof organizationSettingsPageRouteRoute '/organization/settings/': typeof organizationSettingsPageRouteRoute
'/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute
'/organization/cert-manager/settings': typeof organizationCertManagerSettingsPageRouteRoute
'/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute '/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute
'/organization/kms/overview': typeof organizationKmsOverviewPageRouteRoute '/organization/kms/overview': typeof organizationKmsOverviewPageRouteRoute
'/organization/kms/settings': typeof organizationKmsSettingsPageRouteRoute
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute '/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute '/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute '/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-manager/settings': typeof organizationSecretManagerSettingsPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute '/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute '/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/organization/ssh/settings': typeof organizationSshSettingsPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute '/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute '/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute '/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
@@ -4072,14 +4152,18 @@ export interface FileRoutesByTo {
'/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute '/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
'/organization/settings': typeof organizationSettingsPageRouteRoute '/organization/settings': typeof organizationSettingsPageRouteRoute
'/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute
'/organization/cert-manager/settings': typeof organizationCertManagerSettingsPageRouteRoute
'/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute '/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute
'/organization/kms/overview': typeof organizationKmsOverviewPageRouteRoute '/organization/kms/overview': typeof organizationKmsOverviewPageRouteRoute
'/organization/kms/settings': typeof organizationKmsSettingsPageRouteRoute
'/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute '/organization/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute '/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute '/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-manager/settings': typeof organizationSecretManagerSettingsPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute '/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute '/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/organization/ssh/settings': typeof organizationSshSettingsPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute '/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute '/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute '/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/secret-sharing/': typeof organizationSecretSharingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/settings/': typeof organizationSettingsPageRouteRoute '/_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/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/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute '/_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/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/members/$membershipId': typeof organizationUserDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId': typeof organizationRoleByIDPageRouteRoute '/_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/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/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute '/_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/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/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout': typeof secretManagerLayoutRouteWithChildren '/_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/secret-sharing/'
| '/organization/settings/' | '/organization/settings/'
| '/organization/cert-manager/overview' | '/organization/cert-manager/overview'
| '/organization/cert-manager/settings'
| '/organization/groups/$groupId' | '/organization/groups/$groupId'
| '/organization/identities/$identityId' | '/organization/identities/$identityId'
| '/organization/kms/overview' | '/organization/kms/overview'
| '/organization/kms/settings'
| '/organization/members/$membershipId' | '/organization/members/$membershipId'
| '/organization/roles/$roleId' | '/organization/roles/$roleId'
| '/organization/secret-manager/overview' | '/organization/secret-manager/overview'
| '/organization/secret-manager/settings'
| '/organization/secret-sharing/settings' | '/organization/secret-sharing/settings'
| '/organization/ssh/overview' | '/organization/ssh/overview'
| '/organization/ssh/settings'
| '/cert-manager/$projectId/alerting' | '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities' | '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview' | '/cert-manager/$projectId/overview'
@@ -4635,14 +4727,18 @@ export interface FileRouteTypes {
| '/organization/secret-sharing' | '/organization/secret-sharing'
| '/organization/settings' | '/organization/settings'
| '/organization/cert-manager/overview' | '/organization/cert-manager/overview'
| '/organization/cert-manager/settings'
| '/organization/groups/$groupId' | '/organization/groups/$groupId'
| '/organization/identities/$identityId' | '/organization/identities/$identityId'
| '/organization/kms/overview' | '/organization/kms/overview'
| '/organization/kms/settings'
| '/organization/members/$membershipId' | '/organization/members/$membershipId'
| '/organization/roles/$roleId' | '/organization/roles/$roleId'
| '/organization/secret-manager/overview' | '/organization/secret-manager/overview'
| '/organization/secret-manager/settings'
| '/organization/secret-sharing/settings' | '/organization/secret-sharing/settings'
| '/organization/ssh/overview' | '/organization/ssh/overview'
| '/organization/ssh/settings'
| '/cert-manager/$projectId/alerting' | '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities' | '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview' | '/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/secret-sharing/'
| '/_authenticate/_inject-org-details/_org-layout/organization/settings/' | '/_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/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/groups/$groupId'
| '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId' | '/_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/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/members/$membershipId'
| '/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId' | '/_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/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/secret-sharing/settings'
| '/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview' | '/_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/cert-manager/$projectId/_cert-manager-layout'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-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/secret-sharing",
"/_authenticate/_inject-org-details/_org-layout/organization/settings", "/_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/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/groups/$groupId",
"/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId", "/_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/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/members/$membershipId",
"/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId", "/_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/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": { "/_authenticate/_inject-org-details/admin/_admin-layout": {
@@ -5328,6 +5432,10 @@ export const routeTree = rootRoute
"filePath": "organization/CertManagerOverviewPage/route.tsx", "filePath": "organization/CertManagerOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization" "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": { "/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId": {
"filePath": "organization/GroupDetailsByIDPage/route.tsx", "filePath": "organization/GroupDetailsByIDPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization" "parent": "/_authenticate/_inject-org-details/_org-layout/organization"
@@ -5340,6 +5448,10 @@ export const routeTree = rootRoute
"filePath": "organization/KmsOverviewPage/route.tsx", "filePath": "organization/KmsOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization" "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": { "/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId": {
"filePath": "organization/UserDetailsByIDPage/route.tsx", "filePath": "organization/UserDetailsByIDPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization" "parent": "/_authenticate/_inject-org-details/_org-layout/organization"
@@ -5352,6 +5464,10 @@ export const routeTree = rootRoute
"filePath": "organization/SecretManagerOverviewPage/route.tsx", "filePath": "organization/SecretManagerOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization" "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": { "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings": {
"filePath": "organization/SecretSharingSettingsPage/route.tsx", "filePath": "organization/SecretSharingSettingsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" "parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing"
@@ -5360,6 +5476,10 @@ export const routeTree = rootRoute
"filePath": "organization/SshOverviewPage/route.tsx", "filePath": "organization/SshOverviewPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization" "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": { "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout": {
"filePath": "cert-manager/layout.tsx", "filePath": "cert-manager/layout.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId", "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId",
+4
View File
@@ -9,9 +9,13 @@ const adminRoute = route("/admin", [
const organizationRoutes = route("/organization", [ const organizationRoutes = route("/organization", [
route("/secret-manager/overview", "organization/SecretManagerOverviewPage/route.tsx"), 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/overview", "organization/CertManagerOverviewPage/route.tsx"),
route("/cert-manager/settings", "organization/CertManagerSettingsPage/route.tsx"),
route("/ssh/overview", "organization/SshOverviewPage/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/overview", "organization/KmsOverviewPage/route.tsx"),
route("/kms/settings", "organization/KmsSettingsPage/route.tsx"),
route("/access-management", "organization/AccessManagementPage/route.tsx"), route("/access-management", "organization/AccessManagementPage/route.tsx"),
route("/admin", "organization/AdminPage/route.tsx"), route("/admin", "organization/AdminPage/route.tsx"),
route("/audit-logs", "organization/AuditLogsPage/route.tsx"), route("/audit-logs", "organization/AuditLogsPage/route.tsx"),