mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 09:35:37 +00:00
More gohst user
This commit is contained in:
@@ -1,17 +1,11 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import {
|
||||
decryptAsymmetric,
|
||||
encryptSymmetric,
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
generateAsymmetricKeyPair
|
||||
} from "@app/lib/crypto";
|
||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { decryptAsymmetric, generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { TProjectBotDALFactory } from "./project-bot-dal";
|
||||
@@ -60,7 +54,6 @@ export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: T
|
||||
}: TFindBotByProjectIdDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
const appCfg = getConfig();
|
||||
|
||||
const bot = await projectBotDAL.transaction(async (tx) => {
|
||||
const doc = await projectBotDAL.findOne({ projectId }, tx);
|
||||
@@ -68,41 +61,22 @@ export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: T
|
||||
|
||||
const keys = privateKey && publicKey ? { privateKey, publicKey } : generateAsymmetricKeyPair();
|
||||
|
||||
if (appCfg.ROOT_ENCRYPTION_KEY) {
|
||||
const { iv, tag, ciphertext } = encryptSymmetric(keys.privateKey, appCfg.ROOT_ENCRYPTION_KEY);
|
||||
return projectBotDAL.create(
|
||||
{
|
||||
name: "Infisical Bot",
|
||||
projectId,
|
||||
tag,
|
||||
iv,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
isActive: false,
|
||||
publicKey: keys.publicKey,
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
keyEncoding: SecretKeyEncoding.BASE64
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
if (appCfg.ENCRYPTION_KEY) {
|
||||
const { iv, tag, ciphertext } = encryptSymmetric128BitHexKeyUTF8(keys.privateKey, appCfg.ENCRYPTION_KEY);
|
||||
return projectBotDAL.create(
|
||||
{
|
||||
name: "Infisical Bot",
|
||||
projectId,
|
||||
tag,
|
||||
iv,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
isActive: false,
|
||||
publicKey: keys.publicKey,
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
keyEncoding: SecretKeyEncoding.UTF8
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
throw new BadRequestError({ message: "Failed to create bot due to missing encryption key" });
|
||||
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(keys.privateKey);
|
||||
|
||||
return projectBotDAL.create(
|
||||
{
|
||||
name: "Infisical Bot",
|
||||
projectId,
|
||||
tag,
|
||||
iv,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
isActive: false,
|
||||
publicKey: keys.publicKey,
|
||||
algorithm,
|
||||
keyEncoding: encoding
|
||||
},
|
||||
tx
|
||||
);
|
||||
});
|
||||
return bot;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName, TProjectKeys } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
@@ -10,10 +12,11 @@ export const projectKeyDALFactory = (db: TDbClient) => {
|
||||
|
||||
const findLatestProjectKey = async (
|
||||
userId: string,
|
||||
projectId: string
|
||||
projectId: string,
|
||||
tx?: Knex
|
||||
): Promise<(TProjectKeys & { sender: { publicKey: string } }) | undefined> => {
|
||||
try {
|
||||
const projectKey = await db(TableName.ProjectKeys)
|
||||
const projectKey = await (tx || db)(TableName.ProjectKeys)
|
||||
.join(TableName.Users, `${TableName.ProjectKeys}.senderId`, `${TableName.Users}.id`)
|
||||
.join(TableName.UserEncryptionKey, `${TableName.UserEncryptionKey}.userId`, `${TableName.Users}.id`)
|
||||
.where({ projectId, receiverId: userId })
|
||||
@@ -29,9 +32,9 @@ export const projectKeyDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findAllProjectUserPubKeys = async (projectId: string) => {
|
||||
const findAllProjectUserPubKeys = async (projectId: string, tx?: Knex) => {
|
||||
try {
|
||||
const pubKeys = await db(TableName.ProjectMembership)
|
||||
const pubKeys = await (tx || db)(TableName.ProjectMembership)
|
||||
.where({ projectId })
|
||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { OrgMembershipStatus, ProjectMembershipRole, TableName, TUsers } from "@app/db/schemas";
|
||||
import {
|
||||
OrgMembershipStatus,
|
||||
ProjectMembershipRole,
|
||||
SecretKeyEncoding,
|
||||
TableName,
|
||||
TProjectMemberships,
|
||||
TUsers
|
||||
} from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
import { createWsMembers } from "@app/lib/project";
|
||||
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||
@@ -18,6 +28,7 @@ import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
||||
import {
|
||||
TAddUsersToWorkspaceDTO,
|
||||
TAddUsersToWorkspaceNonE2EEDTO,
|
||||
TDeleteProjectMembershipDTO,
|
||||
TGetProjectMembershipDTO,
|
||||
TInviteUserToProjectDTO,
|
||||
@@ -27,11 +38,12 @@ import {
|
||||
type TProjectMembershipServiceFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
smtpService: TSmtpService;
|
||||
projectBotDAL: TProjectBotDALFactory;
|
||||
projectMembershipDAL: TProjectMembershipDALFactory;
|
||||
userDAL: Pick<TUserDALFactory, "findById" | "findOne" | "findUserByProjectMembershipId">;
|
||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "findOne">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findMembership">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "findOrgMembersByEmail">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
@@ -43,6 +55,7 @@ export const projectMembershipServiceFactory = ({
|
||||
projectMembershipDAL,
|
||||
smtpService,
|
||||
projectRoleDAL,
|
||||
projectBotDAL,
|
||||
orgDAL,
|
||||
userDAL,
|
||||
projectDAL,
|
||||
@@ -98,15 +111,12 @@ export const projectMembershipServiceFactory = ({
|
||||
role: ProjectMembershipRole.Member
|
||||
});
|
||||
|
||||
const sender = await userDAL.findById(actorId);
|
||||
const appCfg = getConfig();
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.WorkspaceInvite,
|
||||
subjectLine: "Infisical workspace invitation",
|
||||
recipients: [invitee.email],
|
||||
substitutions: {
|
||||
inviterFirstName: sender.firstName,
|
||||
inviterEmail: sender.email,
|
||||
workspaceName: project.name,
|
||||
callback_url: `${appCfg.SITE_URL}/login`
|
||||
}
|
||||
@@ -175,15 +185,12 @@ export const projectMembershipServiceFactory = ({
|
||||
});
|
||||
|
||||
if (sendEmails) {
|
||||
const sender = await userDAL.findById(actorId);
|
||||
const appCfg = getConfig();
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.WorkspaceInvite,
|
||||
subjectLine: "Infisical workspace invitation",
|
||||
recipients: orgMembers.map(({ email }) => email).filter(Boolean),
|
||||
substitutions: {
|
||||
inviterFirstName: sender.firstName,
|
||||
inviterEmail: sender.email,
|
||||
workspaceName: project.name,
|
||||
callback_url: `${appCfg.SITE_URL}/login`
|
||||
}
|
||||
@@ -192,6 +199,117 @@ export const projectMembershipServiceFactory = ({
|
||||
return orgMembers;
|
||||
};
|
||||
|
||||
const addUsersToProjectNonE2EE = async ({
|
||||
projectId,
|
||||
actorId,
|
||||
actor,
|
||||
emails,
|
||||
sendEmails = true
|
||||
}: TAddUsersToWorkspaceNonE2EEDTO) => {
|
||||
const project = await projectDAL.findById(projectId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
||||
|
||||
const orgMembers = await orgDAL.findOrgMembersByEmail(project.orgId, emails);
|
||||
|
||||
if (orgMembers.length !== emails.length) throw new BadRequestError({ message: "Some users are not part of org" });
|
||||
|
||||
const existingMembers = await projectMembershipDAL.find({
|
||||
projectId,
|
||||
$in: { userId: orgMembers.map(({ user }) => user.id).filter(Boolean) }
|
||||
});
|
||||
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
|
||||
|
||||
const ghostUser = await projectDAL.findProjectGhostUser(projectId);
|
||||
|
||||
if (!ghostUser) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find ghost user"
|
||||
});
|
||||
}
|
||||
|
||||
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId);
|
||||
|
||||
if (!ghostUserLatestKey) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find ghost user latest key"
|
||||
});
|
||||
}
|
||||
|
||||
const bot = await projectBotDAL.findOne({ projectId });
|
||||
|
||||
if (!bot) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find bot"
|
||||
});
|
||||
}
|
||||
|
||||
const botPrivateKey = infisicalSymmetricDecrypt({
|
||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
ciphertext: bot.encryptedPrivateKey
|
||||
});
|
||||
|
||||
const newWsMembers = createWsMembers({
|
||||
decryptKey: ghostUserLatestKey,
|
||||
userPrivateKey: botPrivateKey,
|
||||
members: orgMembers.map((membership) => ({
|
||||
orgMembershipId: membership.id,
|
||||
projectMembershipRole: ProjectMembershipRole.Member,
|
||||
userPublicKey: membership.user.publicKey
|
||||
}))
|
||||
});
|
||||
|
||||
const members: TProjectMemberships[] = [];
|
||||
|
||||
await projectMembershipDAL.transaction(async (tx) => {
|
||||
const result = await projectMembershipDAL.insertMany(
|
||||
orgMembers.map(({ user, id: membershipId }) => {
|
||||
const role =
|
||||
orgMembers.find((membership) => membership.id === membershipId)?.role || ProjectMembershipRole.Member;
|
||||
|
||||
return {
|
||||
projectId,
|
||||
userId: user.id,
|
||||
role
|
||||
};
|
||||
}),
|
||||
tx
|
||||
);
|
||||
|
||||
members.push(...result);
|
||||
|
||||
const encKeyGroupByOrgMembId = groupBy(newWsMembers, (i) => i.orgMembershipId);
|
||||
await projectKeyDAL.insertMany(
|
||||
orgMembers.map(({ user, id }) => ({
|
||||
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
||||
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
||||
senderId: ghostUser.id,
|
||||
receiverId: user.id,
|
||||
projectId
|
||||
})),
|
||||
tx
|
||||
);
|
||||
});
|
||||
|
||||
if (sendEmails) {
|
||||
const appCfg = getConfig();
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.WorkspaceInvite,
|
||||
subjectLine: "Infisical workspace invitation",
|
||||
recipients: orgMembers.map(({ user }) => user.email).filter(Boolean),
|
||||
substitutions: {
|
||||
workspaceName: project.name,
|
||||
callback_url: `${appCfg.SITE_URL}/login`
|
||||
}
|
||||
});
|
||||
}
|
||||
return members;
|
||||
};
|
||||
|
||||
const updateProjectMembership = async ({
|
||||
actorId,
|
||||
actor,
|
||||
@@ -268,6 +386,7 @@ export const projectMembershipServiceFactory = ({
|
||||
getProjectMemberships,
|
||||
inviteUserToProject,
|
||||
updateProjectMembership,
|
||||
addUsersToProjectNonE2EE,
|
||||
deleteProjectMembership,
|
||||
addUsersToProject
|
||||
};
|
||||
|
||||
@@ -25,3 +25,8 @@ export type TAddUsersToWorkspaceDTO = {
|
||||
projectRole: ProjectMembershipRole;
|
||||
}[];
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TAddUsersToWorkspaceNonE2EEDTO = {
|
||||
sendEmails?: boolean;
|
||||
emails: string[];
|
||||
} & TProjectPermission;
|
||||
|
||||
@@ -52,6 +52,20 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findProjectGhostUser = async (projectId: string) => {
|
||||
try {
|
||||
const ghostUser = await db(TableName.ProjectMembership)
|
||||
.where({ projectId })
|
||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||
.select(selectAllTableCols(TableName.Users))
|
||||
.where({ ghost: true })
|
||||
.first();
|
||||
return ghostUser;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find project ghost user" });
|
||||
}
|
||||
};
|
||||
|
||||
const findAllProjectsByIdentity = async (identityId: string) => {
|
||||
try {
|
||||
const workspaces = await db(TableName.IdentityProjectMembership)
|
||||
@@ -136,6 +150,7 @@ export const projectDALFactory = (db: TDbClient) => {
|
||||
...projectOrm,
|
||||
findAllProjects,
|
||||
findAllProjectsByIdentity,
|
||||
findProjectGhostUser,
|
||||
findProjectById
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,16 +6,25 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { createSecretBlindIndex } from "@app/lib/crypto";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { createWorkspaceKey, createWsMembers } from "@app/lib/project";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
|
||||
import { TOrgServiceFactory } from "../org/org-service";
|
||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
||||
import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-index-dal";
|
||||
import { ROOT_FOLDER_NAME, TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TProjectDALFactory } from "./project-dal";
|
||||
import { TCreateProjectDTO, TDeleteProjectDTO, TGetProjectDTO } from "./project-types";
|
||||
|
||||
@@ -27,8 +36,13 @@ export const DEFAULT_PROJECT_ENVS = [
|
||||
|
||||
type TProjectServiceFactoryDep = {
|
||||
projectDAL: TProjectDALFactory;
|
||||
userDAL: TUserDALFactory;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "insertMany">;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "insertMany">;
|
||||
identityOrgMembershipDAL: TIdentityOrgDALFactory;
|
||||
identityProjectDAL: TIdentityProjectDALFactory;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "create">;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser">;
|
||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "create">;
|
||||
@@ -40,9 +54,14 @@ export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>;
|
||||
|
||||
export const projectServiceFactory = ({
|
||||
projectDAL,
|
||||
projectKeyDAL,
|
||||
permissionService,
|
||||
userDAL,
|
||||
folderDAL,
|
||||
orgService,
|
||||
identityProjectDAL,
|
||||
projectBotDAL,
|
||||
identityOrgMembershipDAL,
|
||||
secretBlindIndexDAL,
|
||||
projectMembershipDAL,
|
||||
projectEnvDAL,
|
||||
@@ -52,7 +71,12 @@ export const projectServiceFactory = ({
|
||||
* Create workspace. Make user the admin
|
||||
* */
|
||||
const createProject = async ({ orgId, actor, actorId, actorOrgId, workspaceName }: TCreateProjectDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
orgId,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||
|
||||
const appCfg = getConfig();
|
||||
@@ -110,14 +134,141 @@ export const projectServiceFactory = ({
|
||||
envs.map(({ id }) => ({ name: ROOT_FOLDER_NAME, envId: id, version: 1 })),
|
||||
tx
|
||||
);
|
||||
// _id for backward compat
|
||||
return {
|
||||
project: {
|
||||
...project,
|
||||
environments: envs,
|
||||
_id: project.id
|
||||
|
||||
// 3. Create a random key that we'll use as the project key.
|
||||
const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createWorkspaceKey({
|
||||
publicKey: ghostUser.keys.publicKey,
|
||||
privateKey: ghostUser.keys.plainPrivateKey
|
||||
});
|
||||
|
||||
// 4. Save the project key for the ghost user.
|
||||
await projectKeyDAL.create(
|
||||
{
|
||||
projectId: project.id,
|
||||
receiverId: ghostUser.user.id,
|
||||
encryptedKey: encryptedProjectKey,
|
||||
nonce: encryptedProjectKeyIv,
|
||||
senderId: ghostUser.user.id
|
||||
},
|
||||
ghostUser
|
||||
tx
|
||||
);
|
||||
|
||||
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey);
|
||||
|
||||
// 5. Create & a bot for the project
|
||||
await projectBotDAL.create(
|
||||
{
|
||||
name: "Infisical Bot (Ghost)",
|
||||
projectId: project.id,
|
||||
tag,
|
||||
iv,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
isActive: true,
|
||||
publicKey: ghostUser.keys.publicKey,
|
||||
algorithm,
|
||||
keyEncoding: encoding
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
// Find the ghost users latest key
|
||||
const latestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.user.id, project.id, tx);
|
||||
|
||||
if (!latestKey) {
|
||||
throw new Error("Latest key not found for user");
|
||||
}
|
||||
|
||||
// If the project is being created by a user, add the user to the project as an admin
|
||||
if (actor === ActorType.USER) {
|
||||
// Find public key of user
|
||||
const user = await userDAL.findUserEncKeyByUserId(actorId);
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
const [projectAdmin] = createWsMembers({
|
||||
decryptKey: latestKey,
|
||||
userPrivateKey: ghostUser.keys.plainPrivateKey,
|
||||
members: [
|
||||
{
|
||||
userPublicKey: user.publicKey,
|
||||
orgMembershipId: orgMembership.id,
|
||||
projectMembershipRole: ProjectMembershipRole.Admin
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Create a membership for the user
|
||||
await projectMembershipDAL.create(
|
||||
{
|
||||
projectId: project.id,
|
||||
userId: user.id,
|
||||
role: projectAdmin.projectRole
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
// Create a project key for the user
|
||||
await projectKeyDAL.create(
|
||||
{
|
||||
encryptedKey: projectAdmin.workspaceEncryptedKey,
|
||||
nonce: projectAdmin.workspaceEncryptedNonce,
|
||||
senderId: ghostUser.user.id,
|
||||
receiverId: user.id,
|
||||
projectId: project.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
// If the project is being created by an identity, add the identity to the project as an admin
|
||||
else if (actor === ActorType.IDENTITY) {
|
||||
// Find identity org membership
|
||||
const identityOrgMembership = await identityOrgMembershipDAL.findOne(
|
||||
{
|
||||
identityId: actorId,
|
||||
orgId: project.orgId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
// If identity org membership not found, throw error
|
||||
if (!identityOrgMembership) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to find identity with id ${actorId}`
|
||||
});
|
||||
}
|
||||
|
||||
// Get the role permission for the identity
|
||||
// IS THIS CORRECT?
|
||||
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
|
||||
ProjectMembershipRole.Admin,
|
||||
orgId
|
||||
);
|
||||
|
||||
const hasPrivilege = isAtLeastAsPrivileged(permission, rolePermission);
|
||||
if (!hasPrivilege)
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Failed to add identity to project with more privileged role"
|
||||
});
|
||||
const isCustomRole = Boolean(customRole);
|
||||
|
||||
await identityProjectDAL.create(
|
||||
{
|
||||
identityId: actorId,
|
||||
projectId: project.id,
|
||||
role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin,
|
||||
roleId: customRole?.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...project,
|
||||
environments: envs,
|
||||
_id: project.id
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Project Invitation</title>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Join your team on Infisical</h2>
|
||||
<p>{{inviterFirstName}} ({{inviterEmail}}) has invited you to their Infisical project — {{workspaceName}}</p>
|
||||
<p>You have been invited to a new Infisical project — {{workspaceName}}</p>
|
||||
<a href="{{callback_url}}">Join now</a>
|
||||
<h3>What is Infisical?</h3>
|
||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
|
||||
</body>
|
||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets
|
||||
and configs.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user