feat: completed ap, org roles and project roles and project membership

This commit is contained in:
=
2025-10-09 23:58:10 +05:30
parent e232282bea
commit 4d68d48d89
13 changed files with 714 additions and 244 deletions
@@ -1,7 +1,6 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { z } from "zod"; import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission"; import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { UnauthorizedError } from "@app/lib/errors"; import { UnauthorizedError } from "@app/lib/errors";
@@ -16,6 +15,7 @@ import {
SanitizedIdentityPrivilegeSchema SanitizedIdentityPrivilegeSchema
} from "@app/server/routes/sanitizedSchemas"; } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { AccessScope, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas";
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
@@ -56,6 +56,10 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
if (!permissions && !privilegePermission) { if (!permissions && !privilegePermission) {
throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" }); throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" });
} }
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.body.projectSlug
});
const permission = privilegePermission const permission = privilegePermission
? privilegePermission.actions.map((action) => ({ ? privilegePermission.actions.map((action) => ({
@@ -64,19 +68,35 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
conditions: privilegePermission.conditions conditions: privilegePermission.conditions
})) }))
: permissions!; : permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
actorId: req.permission.id, const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
actor: req.permission.type, permission: req.permission,
actorOrgId: req.permission.orgId, scopeData: {
actorAuthMethod: req.permission.authMethod, scope: AccessScope.Project,
...req.body, projectId,
slug: req.body.slug ?? slugify(alphaNumericNanoId(12)), orgId: req.permission.orgId
isTemporary: false, },
// eslint-disable-next-line @typescript-eslint/ban-ts-comment data: {
// @ts-ignore-error this is valid ts actorId: req.body.identityId,
permissions: backfillPermissionV1SchemaToV2Schema(permission) actorType: MembershipActors.Identity,
...req.body,
isTemporary: false,
name: req.body.slug || slugify(alphaNumericNanoId(8)),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: backfillPermissionV1SchemaToV2Schema(permission)
}
}); });
return { privilege };
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: privilege.membershipId,
projectId,
slug: privilege.name
}
};
} }
}); });
@@ -106,7 +126,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.privilegePermission IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.privilegePermission
).optional(), ).optional(),
temporaryMode: z temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode) .nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode), .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
temporaryRange: z temporaryRange: z
.string() .string()
@@ -138,19 +158,39 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
})) }))
: permissions!; : permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.create({ const { id: projectId } = await server.services.convertor.projectSlugToId({
actorId: req.permission.id, orgId: req.permission.orgId,
actor: req.permission.type, slug: req.body.projectSlug
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body,
slug: req.body.slug ?? slugify(alphaNumericNanoId(12)),
isTemporary: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: backfillPermissionV1SchemaToV2Schema(permission)
}); });
return { privilege };
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
data: {
actorId: req.body.identityId,
actorType: MembershipActors.Identity,
...req.body,
isTemporary: true,
name: req.body.slug || slugify(alphaNumericNanoId(8)),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: backfillPermissionV1SchemaToV2Schema(permission)
}
});
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: privilege.membershipId,
projectId,
slug: privilege.name
}
};
} }
}); });
@@ -183,7 +223,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
).optional(), ).optional(),
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary), isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
temporaryMode: z temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode) .nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode), .describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
temporaryRange: z temporaryRange: z
.string() .string()
@@ -216,18 +256,43 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
conditions: privilegePermission.conditions conditions: privilegePermission.conditions
})) }))
: permissions!; : permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.updateBySlug({
actorId: req.permission.id, const { id: projectId } = await server.services.convertor.projectSlugToId({
actor: req.permission.type, orgId: req.permission.orgId,
actorOrgId: req.permission.orgId, slug: req.body.projectSlug
actorAuthMethod: req.permission.authMethod, });
slug: req.body.privilegeSlug,
identityId: req.body.identityId, const { membershipId } = await server.services.convertor.identityIdToMembershipId(
projectSlug: req.body.projectSlug, req.body.identityId,
AccessScope.Project,
projectId
);
const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToId(
req.body.privilegeSlug,
membershipId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.body.identityId,
actorType: MembershipActors.Identity,
id: privilegeId
},
data: { data: {
...req.body,
isTemporary: true,
...updatedInfo, ...updatedInfo,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts // @ts-ignore-error this is valid ts
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: permission permissions: permission
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts // @ts-ignore-error this is valid ts
@@ -235,7 +300,16 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
: undefined : undefined
} }
}); });
return { privilege };
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: privilege.membershipId,
projectId,
slug: privilege.name
}
};
} }
}); });
@@ -267,16 +341,45 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteBySlug({ const { id: projectId } = await server.services.convertor.projectSlugToId({
actorId: req.permission.id, orgId: req.permission.orgId,
actor: req.permission.type, slug: req.body.projectSlug
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.privilegeSlug,
identityId: req.body.identityId,
projectSlug: req.body.projectSlug
}); });
return { privilege };
const { membershipId } = await server.services.convertor.identityIdToMembershipId(
req.body.identityId,
AccessScope.Project,
projectId
);
const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToId(
req.body.privilegeSlug,
membershipId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.body.identityId,
actorType: MembershipActors.Identity,
id: privilegeId
}
});
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: privilege.membershipId,
projectId,
slug: privilege.name
}
};
} }
}); });
@@ -310,15 +413,45 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsBySlug({ const { id: projectId } = await server.services.convertor.projectSlugToId({
actorId: req.permission.id, orgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod, slug: req.query.projectSlug
actor: req.permission.type,
actorOrgId: req.permission.orgId,
slug: req.params.privilegeSlug,
...req.query
}); });
return { privilege };
const { membershipId } = await server.services.convertor.identityIdToMembershipId(
req.query.identityId,
AccessScope.Project,
projectId
);
const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToId(
req.params.privilegeSlug,
membershipId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.query.identityId,
actorType: MembershipActors.Identity,
id: privilegeId
}
});
return {
privilege: {
...privilege,
identityId: req.query.identityId,
projectMembershipId: privilege.membershipId,
projectId,
slug: privilege.name
}
};
} }
}); });
@@ -349,15 +482,32 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privileges = await server.services.identityProjectAdditionalPrivilege.listIdentityProjectPrivileges({ const { id: projectId } = await server.services.convertor.projectSlugToId({
actorId: req.permission.id, orgId: req.permission.orgId,
actor: req.permission.type, slug: req.query.projectSlug
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
}); });
const { additionalPrivileges: privileges } = await server.services.additionalPrivilege.listAdditionalPrivileges({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.query.identityId,
actorType: MembershipActors.Identity
}
});
return { return {
privileges privileges: privileges.map((privilege) => ({
...privilege,
identityId: req.query.identityId,
projectMembershipId: privilege.membershipId,
projectId,
slug: privilege.name
}))
}; };
} }
}); });
@@ -1,9 +1,9 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { z } from "zod"; import { z } from "zod";
import { AccessScope, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns"; import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -34,7 +34,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
z.object({ z.object({
isTemporary: z.literal(true), isTemporary: z.literal(true),
temporaryMode: z temporaryMode: z
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode) .nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode), .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
temporaryRange: z temporaryRange: z
.string() .string()
@@ -55,17 +55,31 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.create({ const { userId, membership } = await server.services.convertor.userMembershipIdToUserId(
actorId: req.permission.id, req.body.projectMembershipId,
actor: req.permission.type, AccessScope.Project,
actorOrgId: req.permission.orgId, req.permission.orgId
actorAuthMethod: req.permission.authMethod, );
projectMembershipId: req.body.projectMembershipId,
...req.body.type, const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
slug: req.body.slug || slugify(alphaNumericNanoId(8)), permission: req.permission,
permissions: req.body.permissions scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
data: {
actorId: userId,
actorType: MembershipActors.User,
...req.body.type,
name: req.body.slug || slugify(alphaNumericNanoId(8)),
permissions: req.body.permissions
}
}); });
return { privilege };
return {
privilege: { ...privilege, userId, projectId: membership.scopeProjectId as string, slug: privilege.name }
};
} }
}); });
@@ -91,7 +105,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
z.object({ z.object({
isTemporary: z.literal(true).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary), isTemporary: z.literal(true).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
temporaryMode: z temporaryMode: z
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode) .nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode), .describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
temporaryRange: z temporaryRange: z
.string() .string()
@@ -113,21 +127,43 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.updateById({ const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
actorId: req.permission.id, req.params.privilegeId,
actor: req.permission.type, AccessScope.Project,
actorOrgId: req.permission.orgId, req.permission.orgId
actorAuthMethod: req.permission.authMethod, );
...req.body,
...req.body.type, const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({
permissions: req.body.permissions permission: req.permission,
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment scopeData: {
// @ts-ignore-error this is valid ts scope: AccessScope.Project,
req.body.permissions projectId: membership.scopeProjectId as string,
: undefined, orgId: req.permission.orgId
privilegeId: req.params.privilegeId },
data: {
...req.body,
...req.body.type,
permissions: req.body.permissions
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
req.body.permissions
: undefined
},
selector: {
id: req.params.privilegeId,
actorId: membership.actorUserId as string,
actorType: MembershipActors.User
}
}); });
return { privilege };
return {
privilege: {
...privilege,
userId: membership.actorUserId as string,
projectId: membership.scopeProjectId as string,
slug: privilege.name
}
};
} }
}); });
@@ -149,14 +185,34 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.deleteById({ const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
actorId: req.permission.id, req.params.privilegeId,
actor: req.permission.type, AccessScope.Project,
actorOrgId: req.permission.orgId, req.permission.orgId
actorAuthMethod: req.permission.authMethod, );
privilegeId: req.params.privilegeId
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.privilegeId,
actorId: membership.actorUserId as string,
actorType: MembershipActors.User
}
}); });
return { privilege };
return {
privilege: {
...privilege,
userId: membership.actorUserId as string,
projectId: membership.scopeProjectId as string,
slug: privilege.name
}
};
} }
}); });
@@ -178,14 +234,33 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const privileges = await server.services.projectUserAdditionalPrivilege.listPrivileges({ const { userId, membership } = await server.services.convertor.userMembershipIdToUserId(
actorId: req.permission.id, req.query.projectMembershipId,
actor: req.permission.type, AccessScope.Project,
actorOrgId: req.permission.orgId, req.permission.orgId
actorAuthMethod: req.permission.authMethod, );
projectMembershipId: req.query.projectMembershipId
const { additionalPrivileges: privileges } = await server.services.additionalPrivilege.listAdditionalPrivileges({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
selector: {
actorId: userId,
actorType: MembershipActors.User
}
}); });
return { privileges };
return {
privileges: privileges.map((privilege) => ({
...privilege,
userId: membership.actorUserId as string,
projectId: membership.scopeProjectId as string,
slug: privilege.name
}))
};
} }
}); });
@@ -207,14 +282,34 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.getPrivilegeDetailsById({ const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
actorId: req.permission.id, req.params.privilegeId,
actor: req.permission.type, AccessScope.Project,
actorOrgId: req.permission.orgId, req.permission.orgId
actorAuthMethod: req.permission.authMethod, );
privilegeId: req.params.privilegeId
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.privilegeId,
actorId: membership.actorUserId as string,
actorType: MembershipActors.User
}
}); });
return { privilege };
return {
privilege: {
...privilege,
userId: membership.actorUserId as string,
projectId: membership.scopeProjectId as string,
slug: privilege.name
}
};
} }
}); });
}; };
@@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { z } from "zod"; import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-types"; import { AccessScope, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns"; import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs"; import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
@@ -43,7 +43,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
z.object({ z.object({
isTemporary: z.literal(true), isTemporary: z.literal(true),
temporaryMode: z temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode) .nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.temporaryMode), .describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.temporaryMode),
temporaryRange: z temporaryRange: z
.string() .string()
@@ -64,18 +64,31 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.create({ const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
actorAuthMethod: req.permission.authMethod, permission: req.permission,
actorId: req.permission.id, scopeData: {
actorOrgId: req.permission.orgId, scope: AccessScope.Project,
actor: req.permission.type, projectId: req.body.projectId,
projectId: req.body.projectId, orgId: req.permission.orgId
identityId: req.body.identityId, },
...req.body.type, data: {
slug: req.body.slug || slugify(alphaNumericNanoId(8)), actorId: req.body.identityId,
permissions: req.body.permissions actorType: MembershipActors.Identity,
...req.body.type,
name: req.body.slug || slugify(alphaNumericNanoId(8)),
permissions: req.body.permissions
}
}); });
return { privilege };
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: privilege.membershipId,
projectId: req.body.projectId,
slug: privilege.name
}
};
} }
}); });
@@ -108,7 +121,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
z.object({ z.object({
isTemporary: z.literal(true).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.isTemporary), isTemporary: z.literal(true).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.isTemporary),
temporaryMode: z temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode) .nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.temporaryMode), .describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.temporaryMode),
temporaryRange: z temporaryRange: z
.string() .string()
@@ -129,19 +142,40 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.updateById({ const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
actorId: req.permission.id, req.params.id,
actor: req.permission.type, AccessScope.Project,
actorOrgId: req.permission.orgId, req.permission.orgId
actorAuthMethod: req.permission.authMethod, );
id: req.params.id,
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.id,
actorId: membership.actorIdentityId as string,
actorType: MembershipActors.Identity
},
data: { data: {
...req.body, ...req.body,
...req.body.type, ...req.body.type,
permissions: req.body.permissions || undefined permissions: req.body.permissions || undefined
} }
}); });
return { privilege };
return {
privilege: {
...privilege,
identityId: membership.actorIdentityId as string,
projectMembershipId: privilege.membershipId,
projectId: membership.scopeProjectId as string,
slug: privilege.name
}
};
} }
}); });
@@ -171,14 +205,35 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.deleteById({ const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
actorId: req.permission.id, req.params.id,
actor: req.permission.type, AccessScope.Project,
actorAuthMethod: req.permission.authMethod, req.permission.orgId
actorOrgId: req.permission.orgId, );
id: req.params.id
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.id,
actorId: membership.actorIdentityId as string,
actorType: MembershipActors.Identity
}
}); });
return { privilege };
return {
privilege: {
...privilege,
identityId: membership.actorIdentityId as string,
projectMembershipId: privilege.membershipId,
projectId: membership.scopeProjectId as string,
slug: privilege.name
}
};
} }
}); });
@@ -208,14 +263,35 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.getPrivilegeDetailsById({ const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
actorId: req.permission.id, req.params.id,
actorAuthMethod: req.permission.authMethod, AccessScope.Project,
actor: req.permission.type, req.permission.orgId
actorOrgId: req.permission.orgId, );
id: req.params.id
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.id,
actorId: membership.actorIdentityId as string,
actorType: MembershipActors.Identity
}
}); });
return { privilege };
return {
privilege: {
...privilege,
identityId: membership.actorIdentityId as string,
projectMembershipId: privilege.membershipId,
projectId: membership.scopeProjectId as string,
slug: privilege.name
}
};
} }
}); });
@@ -249,15 +325,36 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.getPrivilegeDetailsBySlug({ const { id: projectId } = await server.services.convertor.projectSlugToId({
actorId: req.permission.id, slug: req.query.projectSlug,
actorAuthMethod: req.permission.authMethod, orgId: req.permission.orgId
actor: req.permission.type,
actorOrgId: req.permission.orgId,
slug: req.params.privilegeSlug,
...req.query
}); });
return { privilege };
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeByName(
{
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
name: req.params.privilegeSlug,
actorId: req.query.identityId,
actorType: MembershipActors.Identity
}
}
);
return {
privilege: {
...privilege,
identityId: req.query.identityId,
projectMembershipId: privilege.membershipId,
projectId,
slug: privilege.name
}
};
} }
}); });
@@ -288,15 +385,27 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const privileges = await server.services.identityProjectAdditionalPrivilegeV2.listIdentityProjectPrivileges({ const { additionalPrivileges: privileges } = await server.services.additionalPrivilege.listAdditionalPrivileges({
actorId: req.permission.id, permission: req.permission,
actor: req.permission.type, scopeData: {
actorAuthMethod: req.permission.authMethod, scope: AccessScope.Project,
actorOrgId: req.permission.orgId, projectId: req.query.projectId,
...req.query orgId: req.permission.orgId
},
selector: {
actorId: req.query.identityId,
actorType: MembershipActors.Identity
}
}); });
return { return {
privileges privileges: privileges.map((privilege) => ({
...privilege,
identityId: req.query.identityId,
projectMembershipId: privilege.membershipId,
projectId: req.query.projectId,
slug: privilege.name
}))
}; };
} }
}); });
@@ -5,6 +5,7 @@ import {
ProjectMembershipRole, ProjectMembershipRole,
ProjectMembershipsSchema, ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema, ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UserEncryptionKeysSchema, UserEncryptionKeysSchema,
UsersSchema UsersSchema
} from "@app/db/schemas"; } from "@app/db/schemas";
@@ -14,7 +15,6 @@ import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => { export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
@@ -134,18 +134,34 @@ export const registerProjectMembershipRouter = 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 membership = await server.services.projectMembership.getProjectMembershipById({ const { userId } = await server.services.convertor.userMembershipIdToUserId(
actorId: req.permission.id, req.params.membershipId,
actor: req.permission.type, AccessScope.Project,
actorAuthMethod: req.permission.authMethod, req.permission.orgId
actorOrgId: req.permission.orgId, );
projectId: req.params.projectId, const membership = await server.services.membershipUser.getMembershipByUserId({
id: req.params.membershipId permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
}
}); });
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.projectId
}
};
} }
}); });
// TODO(simp): look into multi deletion and this as well
server.route({ server.route({
method: "POST", method: "POST",
url: "/:projectId/memberships/details", url: "/:projectId/memberships/details",
@@ -317,7 +333,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
z.object({ z.object({
role: z.string(), role: z.string(),
isTemporary: z.literal(true), isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode), temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"), temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime() temporaryAccessStartTime: z.string().datetime()
}) })
@@ -335,17 +351,28 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const roles = await server.services.projectMembership.updateProjectMembership({ const { userId } = await server.services.convertor.userMembershipIdToUserId(
actorId: req.permission.id, req.params.membershipId,
actor: req.permission.type, AccessScope.Project,
actorAuthMethod: req.permission.authMethod, req.permission.orgId
actorOrgId: req.permission.orgId, );
projectId: req.params.projectId,
membershipId: req.params.membershipId, const { membership } = await server.services.membershipUser.updateMembership({
roles: req.body.roles permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
},
data: {
roles: req.body.roles
}
}); });
return { roles }; return { roles: membership.roles.map((el) => ({ ...el, projectMembershipId: req.params.membershipId })) };
} }
}); });
@@ -443,13 +470,22 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({ const { userId } = await server.services.convertor.userMembershipIdToUserId(
actorId: req.permission.id, req.params.membershipId,
actor: req.permission.type, AccessScope.Project,
actorAuthMethod: req.permission.authMethod, req.permission.orgId
actorOrgId: req.permission.orgId, );
projectId: req.params.projectId,
membershipId: req.params.membershipId const { membership } = await server.services.membershipUser.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
}
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
@@ -458,12 +494,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
event: { event: {
type: EventType.REMOVE_PROJECT_MEMBER, type: EventType.REMOVE_PROJECT_MEMBER,
metadata: { metadata: {
userId: membership.userId, userId: membership.actorUserId as string,
email: "" email: ""
} }
} }
}); });
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.projectId
}
};
} }
}); });
@@ -1,10 +1,21 @@
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas"; import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex"; import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TAdditionalPrivilegeDALFactory = ReturnType<typeof additionalPrivilegeDALFactory>; export type TAdditionalPrivilegeDALFactory = ReturnType<typeof additionalPrivilegeDALFactory>;
export const additionalPrivilegeDALFactory = (db: TDbClient) => { export const additionalPrivilegeDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.AdditionalPrivilege); const orm = ormify(db, TableName.AdditionalPrivilege);
return orm;
const getMembershipById = async (privilegeId: string) => {
const doc = await db
.replicaNode()(TableName.AdditionalPrivilege)
.join(TableName.Membership, `${TableName.AdditionalPrivilege}.membershipId`, `${TableName.Membership}.id`)
.select(selectAllTableCols(TableName.Membership))
.where(`${TableName.AdditionalPrivilege}.id`, privilegeId)
.first();
return doc;
};
return { ...orm, getMembershipById };
}; };
@@ -16,6 +16,7 @@ import {
TCreateAdditionalPrivilegesDTO, TCreateAdditionalPrivilegesDTO,
TDeleteAdditionalPrivilegesDTO, TDeleteAdditionalPrivilegesDTO,
TGetAdditionalPrivilegesByIdDTO, TGetAdditionalPrivilegesByIdDTO,
TGetAdditionalPrivilegesByNameDTO,
TListAdditionalPrivilegesDTO, TListAdditionalPrivilegesDTO,
TUpdateAdditionalPrivilegesDTO TUpdateAdditionalPrivilegesDTO
} from "./additional-privilege-types"; } from "./additional-privilege-types";
@@ -71,7 +72,9 @@ export const additionalPrivilegeServiceFactory = ({
permissions: JSON.stringify(packRules(data.permissions as RawRule[])) permissions: JSON.stringify(packRules(data.permissions as RawRule[]))
}); });
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } }; return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
} }
if (!data.temporaryAccessStartTime || !data.temporaryRange) { if (!data.temporaryAccessStartTime || !data.temporaryRange) {
@@ -92,7 +95,9 @@ export const additionalPrivilegeServiceFactory = ({
temporaryRange: data.temporaryRange temporaryRange: data.temporaryRange
}); });
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } }; return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
}; };
const updateAdditionalPrivilege = async (dto: TUpdateAdditionalPrivilegesDTO) => { const updateAdditionalPrivilege = async (dto: TUpdateAdditionalPrivilegesDTO) => {
@@ -169,7 +174,7 @@ export const additionalPrivilegeServiceFactory = ({
const getAdditionalPrivilegeById = async (dto: TGetAdditionalPrivilegesByIdDTO) => { const getAdditionalPrivilegeById = async (dto: TGetAdditionalPrivilegesByIdDTO) => {
const { scopeData, selector } = dto; const { scopeData, selector } = dto;
const factory = scopeFactory[scopeData.scope]; const factory = scopeFactory[scopeData.scope];
const { membershipId } = await factory.onListAdditionalPrivilegesGuard(dto); const { membershipId } = await factory.onGetAdditionalPrivilegesByIdGuard(dto);
const additionalPrivilege = await additionalPrivilegeDAL.findOne({ const additionalPrivilege = await additionalPrivilegeDAL.findOne({
id: selector.id, id: selector.id,
membershipId membershipId
@@ -182,6 +187,22 @@ export const additionalPrivilegeServiceFactory = ({
}; };
}; };
const getAdditionalPrivilegeByName = async (dto: TGetAdditionalPrivilegesByNameDTO) => {
const { scopeData, selector } = dto;
const factory = scopeFactory[scopeData.scope];
const { membershipId } = await factory.onGetAdditionalPrivilegesByIdGuard(dto);
const additionalPrivilege = await additionalPrivilegeDAL.findOne({
name: selector.name,
membershipId
});
if (!additionalPrivilege)
throw new NotFoundError({ message: `Additional privilege with name ${selector.name} doesn't exist` });
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const listAdditionalPrivileges = async (dto: TListAdditionalPrivilegesDTO) => { const listAdditionalPrivileges = async (dto: TListAdditionalPrivilegesDTO) => {
const { scopeData } = dto; const { scopeData } = dto;
const factory = scopeFactory[scopeData.scope]; const factory = scopeFactory[scopeData.scope];
@@ -203,6 +224,7 @@ export const additionalPrivilegeServiceFactory = ({
updateAdditionalPrivilege, updateAdditionalPrivilege,
deleteAdditionalPrivilege, deleteAdditionalPrivilege,
getAdditionalPrivilegeById, getAdditionalPrivilegeById,
getAdditionalPrivilegeByName,
listAdditionalPrivileges listAdditionalPrivileges
}; };
}; };
@@ -6,7 +6,9 @@ export interface TAdditionalPrivilegesScopeFactory {
onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>; onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>; onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
onListAdditionalPrivilegesGuard: (arg: TListAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>; onListAdditionalPrivilegesGuard: (arg: TListAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
onGetAdditionalPrivilegesByIdGuard: (arg: TGetAdditionalPrivilegesByIdDTO) => Promise<{ membershipId: string }>; onGetAdditionalPrivilegesByIdGuard: (
arg: TGetAdditionalPrivilegesByIdDTO | TGetAdditionalPrivilegesByNameDTO
) => Promise<{ membershipId: string }>;
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string }; getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
} }
@@ -71,3 +73,13 @@ export type TGetAdditionalPrivilegesByIdDTO = {
actorType: MembershipActors; actorType: MembershipActors;
}; };
}; };
export type TGetAdditionalPrivilegesByNameDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
name: string;
actorId: string;
actorType: MembershipActors;
};
};
@@ -1,88 +1,118 @@
import { AccessScope } from "@app/db/schemas";
import { NotFoundError } from "@app/lib/errors"; import { NotFoundError } from "@app/lib/errors";
import { TMembershipDALFactory } from "../membership/membership-dal"; import { TMembershipDALFactory } from "../membership/membership-dal";
import { TProjectDALFactory } from "../project/project-dal"; import { TProjectDALFactory } from "../project/project-dal";
import { AccessScope, AccessScopeData } from "@app/db/schemas"; import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
type TConvertorServiceFactoryDep = { type TConvertorServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findOne">; projectDAL: Pick<TProjectDALFactory, "findOne">;
membershipDAL: Pick<TMembershipDALFactory, "findOne">; membershipDAL: Pick<TMembershipDALFactory, "findOne">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "getMembershipById" | "findOne">;
}; };
export type TConvertorServiceFactory = ReturnType<typeof convertorServiceFactory>; export type TConvertorServiceFactory = ReturnType<typeof convertorServiceFactory>;
export const convertorServiceFactory = ({ projectDAL, membershipDAL }: TConvertorServiceFactoryDep) => { export const convertorServiceFactory = ({
const $getScopeDatabaseFields = (scopeData: AccessScopeData) => { projectDAL,
if (scopeData.scope === AccessScope.Organization) { membershipDAL,
return { scopeOrgId: scopeData.orgId }; additionalPrivilegeDAL
} }: TConvertorServiceFactoryDep) => {
if (scopeData.scope === AccessScope.Namespace) {
return { scopeNamespaceId: scopeData.namespaceId, scopeOrgId: scopeData.orgId };
}
return { scopeProjectId: scopeData.projectId, scopeOrgId: scopeData.orgId };
};
const projectSlugToId = async (dto: { slug: string; orgId: string }) => { const projectSlugToId = async (dto: { slug: string; orgId: string }) => {
const project = await projectDAL.findOne({ const project = await projectDAL.findOne({
orgId: dto.orgId, orgId: dto.orgId,
slug: dto.slug slug: dto.slug
}); });
if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` }); if (!project) throw new NotFoundError({ message: `Project with slug ${dto.slug} not found` });
return project; return project;
}; };
const userMembershipIdToUserId = async (scopeData: AccessScopeData, membershipId: string) => { const userMembershipIdToUserId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const dbFields = $getScopeDatabaseFields(scopeData);
const membership = await membershipDAL.findOne({ const membership = await membershipDAL.findOne({
scope: scopeData.scope, scope,
id: membershipId, id: membershipId,
...dbFields, scopeOrgId: orgId
scopeOrgId: scopeData.orgId
}); });
if (!membership || !membership.actorUserId) { if (!membership || !membership.actorUserId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` }); throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
} }
return membership.actorUserId; return { userId: membership.actorUserId, membership };
}; };
const groupMembershipIdToGroupId = async (scopeData: AccessScopeData, membershipId: string) => { const groupMembershipIdToGroupId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const dbFields = $getScopeDatabaseFields(scopeData);
const membership = await membershipDAL.findOne({ const membership = await membershipDAL.findOne({
scope: scopeData.scope, scope,
id: membershipId, id: membershipId,
...dbFields, scopeOrgId: orgId
scopeOrgId: scopeData.orgId
}); });
if (!membership || !membership.actorGroupId) { if (!membership || !membership.actorGroupId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` }); throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
} }
return membership.actorGroupId; return { groupId: membership.actorGroupId, membership };
}; };
const identityMembershipIdToGroupId = async (scopeData: AccessScopeData, membershipId: string) => { const identityMembershipIdToIdentityId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const dbFields = $getScopeDatabaseFields(scopeData);
const membership = await membershipDAL.findOne({ const membership = await membershipDAL.findOne({
scope: scopeData.scope, scope,
id: membershipId, id: membershipId,
...dbFields, scopeOrgId: orgId
scopeOrgId: scopeData.orgId
}); });
if (!membership || !membership.actorIdentityId) { if (!membership || !membership.actorIdentityId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` }); throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
} }
return membership.actorIdentityId; return { identityId: membership.actorIdentityId, membership };
};
const identityIdToMembershipId = async (identityId: string, scope: AccessScope, scopeId: string) => {
let fieldName = "scopeOrgId";
if (scope === AccessScope.Project) {
fieldName = "scopeProjectId";
} else if (scope === AccessScope.Namespace) {
fieldName = "scopeNamespaceId";
}
const membership = await membershipDAL.findOne({
scope,
actorIdentityId: identityId,
[fieldName]: scopeId
});
if (!membership) {
throw new NotFoundError({ message: `Identity with id ${identityId} not found` });
}
return { membershipId: membership.id, membership };
};
const additionalPrivilegeIdToMembershipId = async (privilegeId: string, scope: AccessScope, orgId: string) => {
const membership = await additionalPrivilegeDAL.getMembershipById(privilegeId);
if (!membership || membership.scope !== scope || membership.scopeOrgId !== orgId) {
throw new NotFoundError({ message: `Privilege with id ${privilegeId} not found` });
}
return { membership, membershipId: membership.id };
};
const additionalPrivilegeNameToId = async (privilegeName: string, membershipId: string) => {
const privilege = await additionalPrivilegeDAL.findOne({
name: privilegeName,
membershipId
});
if (!privilege) {
throw new NotFoundError({ message: `Privilege with slug ${privilegeName} not found` });
}
return { privilegeId: privilege.id, privilege };
}; };
return { return {
projectSlugToId, projectSlugToId,
userMembershipIdToUserId, userMembershipIdToUserId,
groupMembershipIdToGroupId, groupMembershipIdToGroupId,
identityMembershipIdToGroupId identityMembershipIdToIdentityId,
additionalPrivilegeIdToMembershipId,
additionalPrivilegeNameToId,
identityIdToMembershipId
}; };
}; };
@@ -274,7 +274,7 @@ export const membershipUserServiceFactory = ({
const existingMembership = await membershipUserDAL.findOne({ const existingMembership = await membershipUserDAL.findOne({
scope: scopeData.scope, scope: scopeData.scope,
...scopeDatabaseFields, ...scopeDatabaseFields,
actorUserId: dto.selector.actorId actorUserId: dto.selector.userId
}); });
if (!existingMembership) if (!existingMembership)
throw new BadRequestError({ throw new BadRequestError({
@@ -338,31 +338,31 @@ export const membershipUserServiceFactory = ({
}, },
tx tx
); );
await membershipRoleDAL.insertMany(roleDocs, tx); const insertedRoles = await membershipRoleDAL.insertMany(roleDocs, tx);
return doc; return { ...doc, roles: insertedRoles };
}); });
return { memberships: membershipDoc }; return { membership: membershipDoc };
}; };
const deleteMembership = async (dto: TDeleteMembershipUserDTO) => { const deleteMembership = async (dto: TDeleteMembershipUserDTO) => {
const { scopeData } = dto; const { scopeData } = dto;
const factory = scopeFactory[scopeData.scope]; const factory = scopeFactory[scopeData.scope];
const { actorIdOfDeletor } = await factory.onDeleteMembershipUserGuard(dto); await factory.onDeleteMembershipUserGuard(dto);
const scopeDatabaseFields = factory.getScopeDatabaseFields(dto.scopeData); const scopeDatabaseFields = factory.getScopeDatabaseFields(dto.scopeData);
const existingMembership = await membershipUserDAL.findOne({ const existingMembership = await membershipUserDAL.findOne({
scope: scopeData.scope, scope: scopeData.scope,
...scopeDatabaseFields, ...scopeDatabaseFields,
actorUserId: dto.selector.actorId actorUserId: dto.selector.userId
}); });
if (!existingMembership) if (!existingMembership)
throw new BadRequestError({ throw new BadRequestError({
message: "User doesn't have membership" message: "User doesn't have membership"
}); });
if (existingMembership.actorUserId === actorIdOfDeletor) if (existingMembership.actorUserId === dto.permission.id)
throw new BadRequestError({ throw new BadRequestError({
message: "You can delete you own membership" message: "You can delete you own membership"
}); });
@@ -403,7 +403,7 @@ export const membershipUserServiceFactory = ({
await factory.onGetMembershipUserByUserIdGuard(dto); await factory.onGetMembershipUserByUserIdGuard(dto);
const membership = await membershipUserDAL.getUserById({ const membership = await membershipUserDAL.getUserById({
scopeData, scopeData,
userId: selector.actorId userId: selector.userId
}); });
if (!membership) throw new NotFoundError({ message: `User membership not found` }); if (!membership) throw new NotFoundError({ message: `User membership not found` });
@@ -6,7 +6,7 @@ export interface TMembershipUserScopeFactory {
onCreateMembershipComplete: (arg: { id: string; email: string }[]) => Promise<void>; onCreateMembershipComplete: (arg: { id: string; email: string }[]) => Promise<void>;
onUpdateMembershipUserGuard: (arg: TUpdateMembershipUserDTO) => Promise<void>; onUpdateMembershipUserGuard: (arg: TUpdateMembershipUserDTO) => Promise<void>;
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO) => Promise<{ actorIdOfDeletor: string }>; onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO) => Promise<void>;
onListMembershipUserGuard: (arg: TListMembershipUserDTO) => Promise<void>; onListMembershipUserGuard: (arg: TListMembershipUserDTO) => Promise<void>;
onGetMembershipUserByUserIdGuard: (arg: TGetMembershipUserByUserIdDTO) => Promise<void>; onGetMembershipUserByUserIdGuard: (arg: TGetMembershipUserByUserIdDTO) => Promise<void>;
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string }; getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
@@ -37,7 +37,7 @@ export type TUpdateMembershipUserDTO = {
permission: OrgServiceActor; permission: OrgServiceActor;
scopeData: AccessScopeData; scopeData: AccessScopeData;
selector: { selector: {
actorId: string; userId: string;
}; };
data: { data: {
isActive?: boolean; isActive?: boolean;
@@ -67,7 +67,7 @@ export type TDeleteMembershipUserDTO = {
permission: OrgServiceActor; permission: OrgServiceActor;
scopeData: AccessScopeData; scopeData: AccessScopeData;
selector: { selector: {
actorId: string; userId: string;
}; };
}; };
@@ -75,6 +75,6 @@ export type TGetMembershipUserByUserIdDTO = {
permission: OrgServiceActor; permission: OrgServiceActor;
scopeData: AccessScopeData; scopeData: AccessScopeData;
selector: { selector: {
actorId: string; userId: string;
}; };
}; };
@@ -63,4 +63,4 @@ export const newNamespaceMembershipUserFactory = (
getScopeDatabaseFields, getScopeDatabaseFields,
isCustomRole isCustomRole
}; };
}; };
@@ -67,7 +67,6 @@ export const newOrgMembershipUserFactory = ({
dto.permission.orgId dto.permission.orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
return { actorIdOfDeletor: dto.permission.id };
}; };
const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => { const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => {
@@ -134,7 +134,6 @@ export const newProjectMembershipUserFactory = ({
actorOrgId: dto.permission.orgId actorOrgId: dto.permission.orgId
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member);
return { actorIdOfDeletor: dto.permission.id };
}; };
const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => { const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => {