Merge branch 'heads/main' into daniel/hsm-improvements-prep-work

This commit is contained in:
Daniel Hougaard
2025-10-22 15:11:39 +04:00
200 changed files with 5621 additions and 2275 deletions
+66 -1
View File
@@ -26,6 +26,7 @@ import {
} from "@app/lib/knex";
import { generateKnexQueryFromScim } from "@app/lib/knex/scim";
import { ActorType } from "../auth/auth-type";
import { OrgAuthMethod } from "./org-types";
export type TOrgDALFactory = ReturnType<typeof orgDALFactory>;
@@ -64,6 +65,7 @@ export const orgDALFactory = (db: TDbClient) => {
const buildBaseQuery = (orgIdSubquery: Knex.QueryBuilder) => {
return db
.replicaNode()(TableName.Organization)
.whereNull(`${TableName.Organization}.rootOrgId`)
.whereIn(`${TableName.Organization}.id`, orgIdSubquery)
.leftJoin(TableName.Project, `${TableName.Organization}.id`, `${TableName.Project}.orgId`)
.leftJoin(TableName.Membership, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
@@ -154,11 +156,49 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
const listSubOrganizations = async (dto: {
actorId: string;
actorType: ActorType;
orgId: string;
isAccessible?: boolean;
limit?: number;
offset?: number;
}) => {
try {
// TODO(sub-org:group): check this when implement group support
const query = db
.replicaNode()(TableName.Organization)
.where(`${TableName.Organization}.rootOrgId`, dto.orgId)
.select(selectAllTableCols(TableName.Organization));
if (dto.isAccessible) {
void query
.leftJoin(`${TableName.Membership}`, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
.where((qb) => {
void qb.where(`${TableName.Membership}.scope`, AccessScope.Organization);
if (dto.actorType === ActorType.IDENTITY) {
void qb.andWhere(`${TableName.Membership}.actorIdentityId`, dto.actorId);
} else {
void qb.andWhere(`${TableName.Membership}.actorUserId`, dto.actorId);
}
});
}
if (dto.limit) void query.limit(dto.limit);
if (dto.offset) void query.offset(dto.offset);
const orgs = await query;
return orgs;
} catch (error) {
throw new DatabaseError({ error, name: "List sub organization" });
}
};
const findOrgById = async (orgId: string) => {
try {
const org = (await db
.replicaNode()(TableName.Organization)
.where({ [`${TableName.Organization}.id` as "id"]: orgId })
.whereNull(`${TableName.Organization}.rootOrgId`)
.leftJoin(TableName.SamlConfig, (qb) => {
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
`${TableName.SamlConfig}.isActive`,
@@ -195,6 +235,7 @@ export const orgDALFactory = (db: TDbClient) => {
try {
const org = (await db
.replicaNode()(TableName.Organization)
.whereNull(`${TableName.Organization}.rootOrgId`)
.where({ [`${TableName.Organization}.slug` as "slug"]: orgSlug })
.leftJoin(TableName.SamlConfig, (qb) => {
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
@@ -240,6 +281,7 @@ export const orgDALFactory = (db: TDbClient) => {
.whereNotNull(`${TableName.Membership}.actorUserId`)
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
.join(TableName.Organization, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
.whereNull(`${TableName.Organization}.rootOrgId`)
.leftJoin(TableName.SamlConfig, (qb) => {
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
`${TableName.SamlConfig}.isActive`,
@@ -337,6 +379,7 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
// TODO(sub-org): updated this logic later
const countAllOrgMembers = async (orgId: string) => {
try {
interface CountResult {
@@ -610,6 +653,7 @@ export const orgDALFactory = (db: TDbClient) => {
})
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.Membership}.actorUserId`)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
.whereNull(`${TableName.Organization}.rootOrgId`)
.leftJoin(TableName.UserAliases, function joinUserAlias() {
this.on(`${TableName.UserAliases}.userId`, "=", `${TableName.Membership}.actorUserId`)
.andOn(`${TableName.UserAliases}.orgId`, "=", `${TableName.Membership}.scopeOrgId`)
@@ -648,6 +692,7 @@ export const orgDALFactory = (db: TDbClient) => {
.replicaNode()(TableName.Membership)
.where({ actorIdentityId: identityId })
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.whereNull(`${TableName.Organization}.rootOrgId`)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
.join(TableName.Organization, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
@@ -662,11 +707,30 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
const findRootOrgDetails = async (orgId: string, tx?: Knex): Promise<TOrganizations | undefined> => {
try {
const org = await (tx ?? db.replicaNode())(TableName.Organization)
.select(selectAllTableCols(TableName.Organization))
.where(
"id",
db(TableName.Organization)
.select(db.raw(`CASE WHEN "rootOrgId" IS NULL THEN id ELSE "rootOrgId" END`))
.where("id", orgId)
)
.first();
return org;
} catch (error) {
throw new DatabaseError({ error, name: "FindRootOrgDetails" });
}
};
return withTransaction(db, {
...orgOrm,
findOrgByProjectId,
findAllOrgMembers,
countAllOrgMembers,
listSubOrganizations,
findOrgById,
findOrgBySlug,
findAllOrgsByUserId,
@@ -684,6 +748,7 @@ export const orgDALFactory = (db: TDbClient) => {
deleteMembershipById,
deleteMembershipsById,
updateMembership,
findIdentityOrganization
findIdentityOrganization,
findRootOrgDetails
});
};
+6 -13
View File
@@ -13,7 +13,7 @@ import { TMembershipUserDALFactory } from "../membership-user/membership-user-da
type TDeleteOrgMemberships = {
orgMembershipIds: string[];
orgId: string;
orgDAL: Pick<TOrgDALFactory, "transaction">;
orgDAL: Pick<TOrgDALFactory, "transaction" | "find">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "delete">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete" | "find">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "delete">;
@@ -34,19 +34,9 @@ export const deleteOrgMembershipsFn = async ({
userId,
membershipUserDAL,
userGroupMembershipDAL,
membershipRoleDAL,
additionalPrivilegeDAL
}: TDeleteOrgMemberships) => {
const deletedMemberships = await orgDAL.transaction(async (tx) => {
await membershipRoleDAL.delete(
{
$in: {
membershipId: orgMembershipIds
}
},
tx
);
const orgMemberships = await membershipUserDAL.delete(
{
scopeOrgId: orgId,
@@ -83,12 +73,13 @@ export const deleteOrgMembershipsFn = async ({
);
// Get all the project memberships of the users in the organization
const childOrgs = await orgDAL.find({ rootOrgId: orgId }, { tx });
// Delete all the project memberships of the users in the organization
const otherMemberships = await membershipUserDAL.delete(
{
scopeOrgId: orgId,
$in: {
scopeOrgId: [orgId].concat(childOrgs.map((el) => el.id)),
actorUserId: membershipUserIds
}
},
@@ -96,7 +87,9 @@ export const deleteOrgMembershipsFn = async ({
);
const orgGroups = await membershipUserDAL.find({
scopeOrgId: orgId,
$in: {
scopeOrgId: [orgId].concat(childOrgs.map((el) => el.id))
},
$notNull: ["actorGroupId"]
});
+134 -61
View File
@@ -4,6 +4,7 @@ import { Knex } from "knex";
import {
AccessScope,
OrganizationActionScope,
OrgMembershipRole,
OrgMembershipStatus,
TableName,
@@ -153,16 +154,31 @@ export const orgServiceFactory = ({
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
rootOrgId: string,
actorOrgId: string
) => {
await permissionService.getOrgPermission(ActorType.USER, userId, orgId, actorAuthMethod, actorOrgId);
await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId: rootOrgId,
scope: OrganizationActionScope.Any
});
const appCfg = getConfig();
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
if (!org.userTokenExpiration) {
return { ...org, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME };
const hasSubOrg = actorOrgId !== rootOrgId;
let subOrg;
if (hasSubOrg) {
subOrg = await orgDAL.findOne({ rootOrgId, id: actorOrgId });
}
return org;
if (!org.userTokenExpiration) {
return { ...org, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME, subOrganization: subOrg };
}
return { ...org, subOrganization: subOrg };
};
/*
* Get all organization a user part of
@@ -189,15 +205,16 @@ export const orgServiceFactory = ({
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
actorOrgId: string
) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const members = await orgDAL.findAllOrgMembers(orgId);
@@ -205,7 +222,14 @@ export const orgServiceFactory = ({
};
const getOrgGroups = async ({ actor, actorId, orgId, actorAuthMethod, actorOrgId }: TGetOrgGroupsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
const groups = await groupDAL.findByOrgId(orgId);
return groups;
@@ -219,7 +243,14 @@ export const orgServiceFactory = ({
orgId,
emails
}: TFindOrgMembersByEmailDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const members = await orgDAL.findOrgMembersByUsername(orgId, emails);
@@ -306,13 +337,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
orgId
}: TUpgradePrivilegeSystemDTO) => {
const { hasRole } = await permissionService.getOrgPermission(
ActorType.USER,
const { hasRole } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({
@@ -377,7 +409,14 @@ export const orgServiceFactory = ({
}
}: TUpdateOrgDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
if (allowSecretSharingOutsideOrganization !== undefined) {
@@ -474,6 +513,12 @@ export const orgServiceFactory = ({
}
}
if (slug) {
const existingOrg = await orgDAL.findOne({ slug, rootOrgId: null });
if (existingOrg && existingOrg?.id !== orgId)
throw new BadRequestError({ message: `Organization with slug ${slug} already exist` });
}
if (googleSsoAuthEnforced) {
if (googleSsoAuthEnforced && currentOrg.authEnforced) {
throw new BadRequestError({
@@ -597,7 +642,7 @@ export const orgServiceFactory = ({
const organization = await (trx ? createOrg(trx) : orgDAL.transaction(createOrg));
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
await licenseService.updateSubscriptionOrgMemberCount(organization.id, trx);
return organization;
};
@@ -619,15 +664,16 @@ export const orgServiceFactory = ({
ipAddress: string;
orgId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined;
actorOrgId: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { hasRole } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({
name: "DeleteOrganizationById",
@@ -707,13 +753,14 @@ export const orgServiceFactory = ({
actorOrgId,
metadata
}: TUpdateOrgMembershipDTO) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
const foundMembership = await membershipUserDAL.findOne({
@@ -794,7 +841,14 @@ export const orgServiceFactory = ({
membershipId
}: TResendOrgMemberInvitationDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
@@ -930,7 +984,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TGetOrgMembershipDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const membership = await orgMembershipDAL.findOrgMembershipById(membershipId);
@@ -951,13 +1012,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TDeleteOrgMembershipDTO) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
const [deletedMembership] = await deleteOrgMembershipsFn({
@@ -984,13 +1046,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TDeleteOrgMembershipsDTO) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
if (membershipIds.includes(userId)) {
@@ -1022,7 +1085,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TListProjectMembershipsByOrgMembershipIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const membership = await orgMembershipDAL.findOrgMembershipById(orgMembershipId);
@@ -1043,15 +1113,16 @@ export const orgServiceFactory = ({
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
actorOrgId: string
) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
const incidentContacts = await incidentContactDAL.findByOrgId(orgId);
return incidentContacts;
@@ -1062,15 +1133,16 @@ export const orgServiceFactory = ({
orgId: string,
email: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
actorOrgId: string
) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email });
if (doesIncidentContactExist) {
@@ -1089,15 +1161,16 @@ export const orgServiceFactory = ({
orgId: string,
id: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
actorOrgId: string
) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount);
const incidentContact = await incidentContactDAL.deleteById(id, orgId);
+5 -5
View File
@@ -8,7 +8,7 @@ export type TUpdateOrgMembershipDTO = {
membershipId: string;
role?: string;
isActive?: boolean;
actorOrgId: string | undefined;
actorOrgId: string;
metadata?: { key: string; value: string }[];
actorAuthMethod: ActorAuthMethod;
};
@@ -21,7 +21,7 @@ export type TDeleteOrgMembershipDTO = {
userId: string;
orgId: string;
membershipId: string;
actorOrgId: string | undefined;
actorOrgId: string;
actorAuthMethod: ActorAuthMethod;
};
@@ -29,7 +29,7 @@ export type TDeleteOrgMembershipsDTO = {
userId: string;
orgId: string;
membershipIds: string[];
actorOrgId: string | undefined;
actorOrgId: string;
actorAuthMethod: ActorAuthMethod;
};
@@ -54,7 +54,7 @@ export type TVerifyUserToOrgDTO = {
export type TFindOrgMembersByEmailDTO = {
actor: ActorType;
actorOrgId: string | undefined;
actorOrgId: string;
actorId: string;
actorAuthMethod: ActorAuthMethod;
orgId: string;
@@ -64,7 +64,7 @@ export type TFindOrgMembersByEmailDTO = {
export type TFindAllWorkspacesDTO = {
actor: ActorType;
actorId: string;
actorOrgId: string | undefined;
actorOrgId: string;
actorAuthMethod: ActorAuthMethod;
orgId: string;
};