mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 19:44:52 +00:00
merge and deconflict main
This commit is contained in:
@@ -12,17 +12,22 @@ import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TAccessProjectDTO, TListOrgProjectsDTO } from "./org-admin-types";
|
||||
|
||||
type TOrgAdminServiceFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
projectDAL: Pick<TProjectDALFactory, "find" | "findById" | "findProjectGhostUser">;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findOne" | "create" | "transaction" | "delete">;
|
||||
projectMembershipDAL: Pick<
|
||||
TProjectMembershipDALFactory,
|
||||
"findOne" | "create" | "transaction" | "delete" | "findAllProjectMembers"
|
||||
>;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "create">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
userDAL: Pick<TUserDALFactory, "findUserEncKeyByUserId">;
|
||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create" | "delete">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
};
|
||||
|
||||
export type TOrgAdminServiceFactory = ReturnType<typeof orgAdminServiceFactory>;
|
||||
@@ -34,7 +39,8 @@ export const orgAdminServiceFactory = ({
|
||||
projectKeyDAL,
|
||||
projectBotDAL,
|
||||
userDAL,
|
||||
projectUserMembershipRoleDAL
|
||||
projectUserMembershipRoleDAL,
|
||||
smtpService
|
||||
}: TOrgAdminServiceFactoryDep) => {
|
||||
const listOrgProjects = async ({
|
||||
actor,
|
||||
@@ -184,6 +190,23 @@ export const orgAdminServiceFactory = ({
|
||||
);
|
||||
return newProjectMembership;
|
||||
});
|
||||
|
||||
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
|
||||
const filteredProjectMembers = projectMembers
|
||||
.filter(
|
||||
(member) => member.roles.some((role) => role.role === ProjectMembershipRole.Admin) && member.userId !== actorId
|
||||
)
|
||||
.map((el) => el.user.email!);
|
||||
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.OrgAdminProjectDirectAccess,
|
||||
recipients: filteredProjectMembers,
|
||||
subjectLine: "Organization Admin Project Direct Access Issued",
|
||||
substitutions: {
|
||||
projectName: project.name,
|
||||
email: projectMembers.find((el) => el.userId === actorId)?.user?.username
|
||||
}
|
||||
});
|
||||
return { isExistingMember: false, membership: updatedMembership };
|
||||
};
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ export const orgServiceFactory = ({
|
||||
|
||||
const findAllWorkspaces = async ({ actor, actorId, orgId, type }: TFindAllWorkspacesDTO) => {
|
||||
if (actor === ActorType.USER) {
|
||||
const workspaces = await projectDAL.findAllProjects(actorId, orgId, type || "all");
|
||||
const workspaces = await projectDAL.findUserProjects(actorId, orgId, type || "all");
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,20 +6,23 @@ import {
|
||||
ProjectType,
|
||||
ProjectUpgradeStatus,
|
||||
ProjectVersion,
|
||||
SortDirection,
|
||||
TableName,
|
||||
TProjects,
|
||||
TProjectsUpdate
|
||||
} from "@app/db/schemas";
|
||||
import { BadRequestError, DatabaseError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
|
||||
import { Filter, ProjectFilterType } from "./project-types";
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { Filter, ProjectFilterType, SearchProjectSortBy } from "./project-types";
|
||||
|
||||
export type TProjectDALFactory = ReturnType<typeof projectDALFactory>;
|
||||
|
||||
export const projectDALFactory = (db: TDbClient) => {
|
||||
const projectOrm = ormify(db, TableName.Project);
|
||||
|
||||
const findAllProjects = async (userId: string, orgId: string, projectType: ProjectType | "all") => {
|
||||
const findUserProjects = async (userId: string, orgId: string, projectType: ProjectType | "all") => {
|
||||
try {
|
||||
const workspaces = await db
|
||||
.replicaNode()(TableName.ProjectMembership)
|
||||
@@ -352,9 +355,79 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const searchProjects = async (dto: {
|
||||
orgId: string;
|
||||
actor: ActorType;
|
||||
actorId: string;
|
||||
type?: ProjectType;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
name?: string;
|
||||
sortBy?: SearchProjectSortBy;
|
||||
sortDir?: SortDirection;
|
||||
}) => {
|
||||
const { limit = 20, offset = 0, sortBy = SearchProjectSortBy.NAME, sortDir = SortDirection.ASC } = dto;
|
||||
|
||||
const userMembershipSubquery = db(TableName.ProjectMembership).where({ userId: dto.actorId }).select("projectId");
|
||||
const groups = db(TableName.UserGroupMembership).where({ userId: dto.actorId }).select("groupId");
|
||||
const groupMembershipSubquery = db(TableName.GroupProjectMembership).whereIn("groupId", groups).select("projectId");
|
||||
|
||||
const identityMembershipSubQuery = db(TableName.IdentityProjectMembership)
|
||||
.where({ identityId: dto.actorId })
|
||||
.select("projectId");
|
||||
|
||||
// Get the SQL strings for the subqueries
|
||||
const userMembershipSql = userMembershipSubquery.toQuery();
|
||||
const groupMembershipSql = groupMembershipSubquery.toQuery();
|
||||
const identityMembershipSql = identityMembershipSubQuery.toQuery();
|
||||
|
||||
const query = db
|
||||
.replicaNode()(TableName.Project)
|
||||
.where(`${TableName.Project}.orgId`, dto.orgId)
|
||||
.select(selectAllTableCols(TableName.Project))
|
||||
.select(db.raw("COUNT(*) OVER() AS count"))
|
||||
.select<(TProjects & { isMember: boolean; count: number })[]>(
|
||||
dto.actor === ActorType.USER
|
||||
? db.raw(
|
||||
`
|
||||
CASE
|
||||
WHEN ${TableName.Project}.id IN (?) THEN TRUE
|
||||
WHEN ${TableName.Project}.id IN (?) THEN TRUE
|
||||
ELSE FALSE
|
||||
END as "isMember"
|
||||
`,
|
||||
[db.raw(userMembershipSql), db.raw(groupMembershipSql)]
|
||||
)
|
||||
: db.raw(
|
||||
`
|
||||
CASE
|
||||
WHEN ${TableName.Project}.id IN (?) THEN TRUE
|
||||
ELSE FALSE
|
||||
END as "isMember"
|
||||
`,
|
||||
[db.raw(identityMembershipSql)]
|
||||
)
|
||||
)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
if (sortBy === SearchProjectSortBy.NAME) {
|
||||
void query.orderBy([{ column: `${TableName.Project}.name`, order: sortDir }]);
|
||||
}
|
||||
|
||||
if (dto.type) {
|
||||
void query.where(`${TableName.Project}.type`, dto.type);
|
||||
}
|
||||
if (dto.name) {
|
||||
void query.whereILike(`${TableName.Project}.name`, `%${dto.name}%`);
|
||||
}
|
||||
const docs = await query;
|
||||
|
||||
return { docs, totalCount: Number(docs?.[0]?.count ?? 0) };
|
||||
};
|
||||
|
||||
return {
|
||||
...projectOrm,
|
||||
findAllProjects,
|
||||
findUserProjects,
|
||||
setProjectUpgradeStatus,
|
||||
findAllProjectsByIdentity,
|
||||
findProjectGhostUser,
|
||||
@@ -363,6 +436,7 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
findProjectBySlug,
|
||||
findProjectWithOrg,
|
||||
checkProjectUpgradeStatus,
|
||||
getProjectFromSplitId
|
||||
getProjectFromSplitId,
|
||||
searchProjects
|
||||
};
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-cer
|
||||
import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
|
||||
import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal";
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
@@ -57,6 +58,7 @@ import { ROOT_FOLDER_NAME, TSecretFolderDALFactory } from "../secret-folder/secr
|
||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||
import { TProjectSlackConfigDALFactory } from "../slack/project-slack-config-dal";
|
||||
import { TSlackIntegrationDALFactory } from "../slack/slack-integration-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TProjectDALFactory } from "./project-dal";
|
||||
import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns";
|
||||
@@ -76,6 +78,8 @@ import {
|
||||
TListProjectSshCertificatesDTO,
|
||||
TListProjectSshCertificateTemplatesDTO,
|
||||
TLoadProjectKmsBackupDTO,
|
||||
TProjectAccessRequestDTO,
|
||||
TSearchProjectsDTO,
|
||||
TToggleProjectAutoCapitalizationDTO,
|
||||
TUpdateAuditLogsRetentionDTO,
|
||||
TUpdateProjectDTO,
|
||||
@@ -106,7 +110,10 @@ type TProjectServiceFactoryDep = {
|
||||
identityProjectDAL: TIdentityProjectDALFactory;
|
||||
identityProjectMembershipRoleDAL: Pick<TIdentityProjectMembershipRoleDALFactory, "create">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne" | "delete">;
|
||||
projectMembershipDAL: Pick<
|
||||
TProjectMembershipDALFactory,
|
||||
"create" | "findProjectGhostUser" | "findOne" | "delete" | "findAllProjectMembers"
|
||||
>;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "delete">;
|
||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "findOne" | "transaction" | "updateById" | "create">;
|
||||
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "findById" | "findByIdWithWorkflowIntegrationDetails">;
|
||||
@@ -123,6 +130,7 @@ type TProjectServiceFactoryDep = {
|
||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
queueService: Pick<TQueueServiceFactory, "stopRepeatableJob">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
|
||||
orgDAL: Pick<TOrgDALFactory, "findOne">;
|
||||
keyStore: Pick<TKeyStoreFactory, "deleteItem">;
|
||||
@@ -177,7 +185,8 @@ export const projectServiceFactory = ({
|
||||
projectSlackConfigDAL,
|
||||
slackIntegrationDAL,
|
||||
projectTemplateService,
|
||||
groupProjectDAL
|
||||
groupProjectDAL,
|
||||
smtpService
|
||||
}: TProjectServiceFactoryDep) => {
|
||||
/*
|
||||
* Create workspace. Make user the admin
|
||||
@@ -506,7 +515,7 @@ export const projectServiceFactory = ({
|
||||
actorOrgId,
|
||||
type = ProjectType.SecretManager
|
||||
}: TListProjectsDTO) => {
|
||||
const workspaces = await projectDAL.findAllProjects(actorId, actorOrgId, type);
|
||||
const workspaces = await projectDAL.findUserProjects(actorId, actorOrgId, type);
|
||||
|
||||
if (includeRoles) {
|
||||
const { permission } = await permissionService.getUserOrgPermission(
|
||||
@@ -1339,6 +1348,85 @@ export const projectServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
const searchProjects = async ({
|
||||
name,
|
||||
offset,
|
||||
permission,
|
||||
limit,
|
||||
type,
|
||||
orderBy,
|
||||
orderDirection
|
||||
}: TSearchProjectsDTO) => {
|
||||
// check user belong to org
|
||||
await permissionService.getOrgPermission(
|
||||
permission.type,
|
||||
permission.id,
|
||||
permission.orgId,
|
||||
permission.authMethod,
|
||||
permission.orgId
|
||||
);
|
||||
|
||||
return projectDAL.searchProjects({
|
||||
limit,
|
||||
offset,
|
||||
name,
|
||||
type,
|
||||
orgId: permission.orgId,
|
||||
actor: permission.type,
|
||||
actorId: permission.id,
|
||||
sortBy: orderBy,
|
||||
sortDir: orderDirection
|
||||
});
|
||||
};
|
||||
|
||||
const requestProjectAccess = async ({ permission, comment, projectId }: TProjectAccessRequestDTO) => {
|
||||
// check user belong to org
|
||||
await permissionService.getOrgPermission(
|
||||
permission.type,
|
||||
permission.id,
|
||||
permission.orgId,
|
||||
permission.authMethod,
|
||||
permission.orgId
|
||||
);
|
||||
|
||||
const projectMember = await permissionService
|
||||
.getProjectPermission({
|
||||
actor: permission.type,
|
||||
actorId: permission.id,
|
||||
projectId,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: permission.authMethod,
|
||||
actorOrgId: permission.orgId
|
||||
})
|
||||
.catch(() => {
|
||||
return null;
|
||||
});
|
||||
if (projectMember) throw new BadRequestError({ message: "User already has access to the project" });
|
||||
|
||||
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
|
||||
const filteredProjectMembers = projectMembers
|
||||
.filter((member) => member.roles.some((role) => role.role === ProjectMembershipRole.Admin))
|
||||
.map((el) => el.user.email!);
|
||||
const org = await orgDAL.findOne({ id: permission.orgId });
|
||||
const project = await projectDAL.findById(projectId);
|
||||
const userDetails = await userDAL.findById(permission.id);
|
||||
const appCfg = getConfig();
|
||||
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.ProjectAccessRequest,
|
||||
recipients: filteredProjectMembers,
|
||||
subjectLine: "Project Access Request",
|
||||
substitutions: {
|
||||
requesterName: `${userDetails.firstName} ${userDetails.lastName}`,
|
||||
requesterEmail: userDetails.email,
|
||||
projectName: project?.name,
|
||||
orgName: org?.name,
|
||||
note: comment,
|
||||
callback_url: `${appCfg.SITE_URL}/${project.type}/${project.id}/access-management?selectedTab=members&requesterEmail=${userDetails.email}`
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createProject,
|
||||
deleteProject,
|
||||
@@ -1364,6 +1452,8 @@ export const projectServiceFactory = ({
|
||||
loadProjectKmsBackup,
|
||||
getProjectKmsKeys,
|
||||
getProjectSlackConfig,
|
||||
updateProjectSlackConfig
|
||||
updateProjectSlackConfig,
|
||||
requestProjectAccess,
|
||||
searchProjects
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { ProjectType, TProjectKeys } from "@app/db/schemas";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { ProjectType, SortDirection, TProjectKeys } from "@app/db/schemas";
|
||||
import { OrgServiceActor, TProjectPermission } from "@app/lib/types";
|
||||
|
||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||
|
||||
@@ -158,3 +158,23 @@ export type TUpdateProjectSlackConfig = {
|
||||
isSecretRequestNotificationEnabled: boolean;
|
||||
secretRequestChannels: string;
|
||||
} & TProjectPermission;
|
||||
|
||||
export enum SearchProjectSortBy {
|
||||
NAME = "name"
|
||||
}
|
||||
|
||||
export type TSearchProjectsDTO = {
|
||||
permission: OrgServiceActor;
|
||||
name?: string;
|
||||
type?: ProjectType;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
orderBy?: SearchProjectSortBy;
|
||||
orderDirection?: SortDirection;
|
||||
};
|
||||
|
||||
export type TProjectAccessRequestDTO = {
|
||||
permission: OrgServiceActor;
|
||||
projectId: string;
|
||||
comment?: string;
|
||||
};
|
||||
|
||||
@@ -41,7 +41,9 @@ export enum SmtpTemplates {
|
||||
ExternalImportFailed = "externalImportFailed.handlebars",
|
||||
ExternalImportStarted = "externalImportStarted.handlebars",
|
||||
SecretRequestCompleted = "secretRequestCompleted.handlebars",
|
||||
SecretRotationFailed = "secretRotationFailed.handlebars"
|
||||
SecretRotationFailed = "secretRotationFailed.handlebars",
|
||||
ProjectAccessRequest = "projectAccess.handlebars",
|
||||
OrgAdminProjectDirectAccess = "orgAdminProjectGrantAccess.handlebars"
|
||||
}
|
||||
|
||||
export enum SmtpHost {
|
||||
|
||||
@@ -49,4 +49,4 @@
|
||||
{{emailFooter}}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Organization admin issued direct access to project</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Infisical</h2>
|
||||
<p>The organization admin {{email}} has granted direct access to the project "{{projectName}}".</p>
|
||||
|
||||
{{emailFooter}}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Project Access Request</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Infisical</h2>
|
||||
<h2>You have a new project access request!</h2>
|
||||
<ul>
|
||||
<li>Requester Name: "{{requesterName}}"</li>
|
||||
<li>Requester Email: "{{requesterEmail}}"</li>
|
||||
<li>Project Name: "{{projectName}}"</li>
|
||||
<li>Organization Name: "{{orgName}}"</li>
|
||||
<li>User Note: "{{note}}"</li>
|
||||
</ul>
|
||||
<p>
|
||||
Please click on the link below to grant access
|
||||
</p>
|
||||
<a href="{{callback_url}}">Grant Access</a>
|
||||
{{emailFooter}}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user