mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 19:28:35 +00:00
feat: almost done with most ts stuff
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { AccessScope, ProjectMembershipRole, TemporaryPermissionMode, TMembershipRolesInsert } from "@app/db/schemas";
|
||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@@ -7,10 +8,16 @@ import { ms } from "@app/lib/ms";
|
||||
import { SearchResourceOperators } from "@app/lib/search-resource/search";
|
||||
|
||||
import { AuthMethod } from "../auth/auth-type";
|
||||
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
import { deleteOrgMembershipsFn } from "../org/org-fns";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||
import { TRoleDALFactory } from "../role/role-dal";
|
||||
import { TSmtpService } from "../smtp/smtp-service";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
|
||||
import { TMembershipUserDALFactory } from "./membership-user-dal";
|
||||
import {
|
||||
TCreateMembershipUserDTO,
|
||||
@@ -26,23 +33,20 @@ import { newProjectMembershipUserFactory } from "./project/project-membership-us
|
||||
type TMembershipUserServiceFactoryDep = {
|
||||
membershipUserDAL: TMembershipUserDALFactory;
|
||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById" | "transaction">;
|
||||
roleDAL: Pick<TRoleDALFactory, "find">;
|
||||
userDAL: Pick<
|
||||
TUserDALFactory,
|
||||
| "findUserByUsername"
|
||||
| "find"
|
||||
| "transaction"
|
||||
| "create"
|
||||
| "createUserEncryption"
|
||||
| "findUserByEmail"
|
||||
| "findUserEncKeyByUserId"
|
||||
>;
|
||||
userDAL: TUserDALFactory;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getProjectPermission" | "getProjectPermissionByRoles" | "getOrgPermission"
|
||||
>;
|
||||
licenseService: TLicenseServiceFactory;
|
||||
projectKeyDAL: TProjectKeyDALFactory;
|
||||
userAliasDAL: TUserAliasDALFactory;
|
||||
smtpService: TSmtpService;
|
||||
tokenService: TAuthTokenServiceFactory;
|
||||
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
|
||||
projectDAL: TProjectDALFactory;
|
||||
};
|
||||
|
||||
export type TMembershipUserServiceFactory = ReturnType<typeof membershipUserServiceFactory>;
|
||||
@@ -50,20 +54,35 @@ export type TMembershipUserServiceFactory = ReturnType<typeof membershipUserServ
|
||||
export const membershipUserServiceFactory = ({
|
||||
membershipUserDAL,
|
||||
roleDAL,
|
||||
licenseService,
|
||||
membershipRoleDAL,
|
||||
userDAL,
|
||||
permissionService,
|
||||
orgDAL
|
||||
orgDAL,
|
||||
projectKeyDAL,
|
||||
userAliasDAL,
|
||||
licenseService,
|
||||
smtpService,
|
||||
tokenService,
|
||||
userGroupMembershipDAL,
|
||||
projectDAL
|
||||
}: TMembershipUserServiceFactoryDep) => {
|
||||
const scopeFactory = {
|
||||
[AccessScope.Organization]: newOrgMembershipUserFactory({
|
||||
permissionService
|
||||
permissionService,
|
||||
licenseService,
|
||||
smtpService,
|
||||
orgDAL,
|
||||
tokenService,
|
||||
userDAL,
|
||||
userGroupMembershipDAL
|
||||
}),
|
||||
[AccessScope.Namespace]: newNamespaceMembershipUserFactory({}),
|
||||
[AccessScope.Project]: newProjectMembershipUserFactory({
|
||||
orgDAL,
|
||||
permissionService
|
||||
permissionService,
|
||||
membershipUserDAL,
|
||||
projectDAL,
|
||||
smtpService
|
||||
})
|
||||
};
|
||||
|
||||
@@ -157,11 +176,11 @@ export const membershipUserServiceFactory = ({
|
||||
actorUserId: users.map((el) => el.id)
|
||||
}
|
||||
});
|
||||
// TODO(simp): do we really do this - check it out
|
||||
|
||||
if (existingMemberships.length === users.length) return { memberships: [] };
|
||||
|
||||
await factory.onCreateMembershipUserGuard(dto);
|
||||
const newMembershipUsers = users.filter((user) => !existingMemberships?.find((el) => el.actorUserId === user.id));
|
||||
await factory.onCreateMembershipUserGuard(dto, newMembershipUsers);
|
||||
const newMemberships = newMembershipUsers.map((user) => ({
|
||||
scope: scopeData.scope,
|
||||
...scopeDatabaseFields,
|
||||
@@ -230,7 +249,8 @@ export const membershipUserServiceFactory = ({
|
||||
return docs;
|
||||
});
|
||||
|
||||
return { memberships: membershipDoc };
|
||||
const { signUpTokens } = await factory.onCreateMembershipComplete(dto, newMembershipUsers);
|
||||
return { memberships: membershipDoc, signUpTokens };
|
||||
};
|
||||
|
||||
const updateMembership = async (dto: TUpdateMembershipUserDTO) => {
|
||||
@@ -364,10 +384,26 @@ export const membershipUserServiceFactory = ({
|
||||
|
||||
if (existingMembership.actorUserId === dto.permission.id)
|
||||
throw new BadRequestError({
|
||||
message: "You can delete you own membership"
|
||||
message: "You can't delete you own membership"
|
||||
});
|
||||
|
||||
const membershipDoc = await membershipUserDAL.transaction(async (tx) => {
|
||||
if (dto.scopeData.scope === AccessScope.Organization) {
|
||||
const [doc] = await deleteOrgMembershipsFn({
|
||||
orgMembershipIds: [],
|
||||
orgId: dto.permission.orgId,
|
||||
orgDAL,
|
||||
projectKeyDAL,
|
||||
userAliasDAL,
|
||||
licenseService,
|
||||
userId: dto.permission.id,
|
||||
membershipUserDAL,
|
||||
userGroupMembershipDAL,
|
||||
membershipRoleDAL
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
||||
await membershipRoleDAL.delete({ membershipId: existingMembership.id }, tx);
|
||||
const doc = await membershipUserDAL.deleteById(existingMembership.id, tx);
|
||||
return doc;
|
||||
|
||||
@@ -2,11 +2,18 @@ import { AccessScopeData, TemporaryPermissionMode } from "@app/db/schemas";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
export interface TMembershipUserScopeFactory {
|
||||
onCreateMembershipUserGuard: (arg: TCreateMembershipUserDTO) => Promise<void>;
|
||||
onCreateMembershipComplete: (arg: { id: string; email: string }[]) => Promise<void>;
|
||||
onCreateMembershipUserGuard: (
|
||||
arg: TCreateMembershipUserDTO,
|
||||
newMembers: { id: string; email?: string | null }[]
|
||||
) => Promise<void>;
|
||||
onCreateMembershipComplete: (
|
||||
arg: TCreateMembershipUserDTO,
|
||||
newMembers: { id: string; email?: string | null }[]
|
||||
) => Promise<{ signUpTokens: { email: string; link: string }[] }>;
|
||||
|
||||
onUpdateMembershipUserGuard: (arg: TUpdateMembershipUserDTO) => Promise<void>;
|
||||
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO) => Promise<void>;
|
||||
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO | TBulkDeleteMembershipByUsernameDTO) => Promise<void>;
|
||||
|
||||
onListMembershipUserGuard: (arg: TListMembershipUserDTO) => Promise<void>;
|
||||
onGetMembershipUserByUserIdGuard: (arg: TGetMembershipUserByUserIdDTO) => Promise<void>;
|
||||
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||
@@ -71,6 +78,14 @@ export type TDeleteMembershipUserDTO = {
|
||||
};
|
||||
};
|
||||
|
||||
export type TBulkDeleteMembershipByUsernameDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
data: {
|
||||
usernames: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export type TGetMembershipUserByUserIdDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
|
||||
@@ -1,19 +1,39 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { AccessScope } from "@app/db/schemas";
|
||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||
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-types";
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
|
||||
import { TMembershipUserScopeFactory } from "../membership-user-types";
|
||||
|
||||
type TOrgMembershipUserScopeFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||
userDAL: Pick<TUserDALFactory, "findById">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "delete">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export const newOrgMembershipUserFactory = ({
|
||||
permissionService
|
||||
permissionService,
|
||||
tokenService,
|
||||
userDAL,
|
||||
orgDAL,
|
||||
smtpService,
|
||||
licenseService
|
||||
}: TOrgMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => {
|
||||
const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => {
|
||||
if (dto.scope === AccessScope.Organization) {
|
||||
@@ -40,11 +60,75 @@ export const newOrgMembershipUserFactory = ({
|
||||
dto.permission.orgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||
|
||||
const plan = await licenseService.getPlan(dto.permission.orgId);
|
||||
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||
throw new BadRequestError({
|
||||
name: "InviteUser",
|
||||
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
||||
});
|
||||
}
|
||||
|
||||
const org = await orgDAL.findById(dto.permission.orgId);
|
||||
if (org?.authEnforced) {
|
||||
throw new ForbiddenRequestError({
|
||||
name: "InviteUser",
|
||||
message: "Failed to invite user due to org-level auth enforced for organization"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async () => {
|
||||
// TODO(simp): fix this
|
||||
throw new InternalServerError({ message: "Org membership user create complete not implemented" });
|
||||
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async (
|
||||
dto,
|
||||
newUsers
|
||||
) => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
const actorDetails =
|
||||
dto.permission.type === ActorType.USER
|
||||
? await userDAL.findById(dto.permission.id)
|
||||
: {
|
||||
firstName: "Platform Identity",
|
||||
email: "identity"
|
||||
};
|
||||
|
||||
const signUpTokens: { email: string; link: string }[] = [];
|
||||
const orgDetails = await orgDAL.findById(dto.permission.orgId);
|
||||
|
||||
await Promise.allSettled(
|
||||
newUsers.map(async (el) => {
|
||||
const token = await tokenService.createTokenForUser({
|
||||
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
||||
userId: el.id,
|
||||
orgId: dto.permission.orgId
|
||||
});
|
||||
|
||||
if (el.email) {
|
||||
signUpTokens.push({
|
||||
email: el.email,
|
||||
link: `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${el.email}&organization_id=${dto.permission.orgId}`
|
||||
});
|
||||
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.OrgInvite,
|
||||
subjectLine: "Infisical organization invitation",
|
||||
recipients: [el.email],
|
||||
substitutions: {
|
||||
inviterFirstName: actorDetails?.firstName,
|
||||
inviterUsername: actorDetails?.email,
|
||||
organizationName: orgDetails?.name,
|
||||
email: el.email,
|
||||
organizationId: orgDetails?.id.toString(),
|
||||
token,
|
||||
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return { signUpTokens };
|
||||
};
|
||||
|
||||
const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => {
|
||||
|
||||
@@ -11,19 +11,29 @@ import {
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
|
||||
import { TMembershipUserDALFactory } from "../membership-user-dal";
|
||||
import { TMembershipUserScopeFactory } from "../membership-user-types";
|
||||
|
||||
type TProjectMembershipUserScopeFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
membershipUserDAL: Pick<TMembershipUserDALFactory, "find">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
};
|
||||
|
||||
export const newProjectMembershipUserFactory = ({
|
||||
permissionService,
|
||||
orgDAL
|
||||
orgDAL,
|
||||
projectDAL,
|
||||
membershipUserDAL,
|
||||
smtpService
|
||||
}: TProjectMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => {
|
||||
const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => {
|
||||
if (dto.scope === AccessScope.Project) {
|
||||
@@ -41,7 +51,10 @@ export const newProjectMembershipUserFactory = ({
|
||||
|
||||
const isCustomRole: TMembershipUserScopeFactory["isCustomRole"] = (role) => isCustomProjectRole(role);
|
||||
|
||||
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (dto) => {
|
||||
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (
|
||||
dto,
|
||||
newUsers
|
||||
) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
@@ -53,6 +66,21 @@ export const newProjectMembershipUserFactory = ({
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member);
|
||||
|
||||
// TODO(namespace): this becomes tricky in namespace due to group flow
|
||||
const orgMemberships = await membershipUserDAL.find({
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: dto.permission.orgId,
|
||||
$in: {
|
||||
actorUserId: newUsers.map((el) => el.id)
|
||||
}
|
||||
});
|
||||
if (orgMemberships.length !== newUsers.length) {
|
||||
const missingUsers = newUsers
|
||||
.filter((el) => !orgMemberships.find((memb) => memb.actorUserId === el.id))
|
||||
.map((el) => el.email);
|
||||
throw new BadRequestError({ message: `Users ${missingUsers.join(",")} not part of organization` });
|
||||
}
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||
const permissionRoles = await permissionService.getProjectPermissionByRoles(
|
||||
dto.data.roles.filter((el) => el.role !== ProjectMembershipRole.NoAccess).map((el) => el.role),
|
||||
@@ -80,8 +108,24 @@ export const newProjectMembershipUserFactory = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async () => {
|
||||
throw new InternalServerError({ message: "Project membership user create complete not implemented" });
|
||||
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async (
|
||||
dto,
|
||||
newMembers
|
||||
) => {
|
||||
const appCfg = getConfig();
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const project = await projectDAL.findById(scope.value);
|
||||
const emails = newMembers.filter((el) => Boolean(el?.email)).map((el) => el?.email as string);
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.WorkspaceInvite,
|
||||
subjectLine: "Infisical project invitation",
|
||||
recipients: emails,
|
||||
substitutions: {
|
||||
workspaceName: project.name,
|
||||
callback_url: `${appCfg.SITE_URL}/login`
|
||||
}
|
||||
});
|
||||
return { signUpTokens: [] };
|
||||
};
|
||||
|
||||
const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => {
|
||||
|
||||
Reference in New Issue
Block a user