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

This commit is contained in:
=
2025-09-30 14:35:52 +05:30
parent e232282bea
commit 4d68d48d89
13 changed files with 714 additions and 244 deletions

View File

@@ -1,7 +1,6 @@
import slugify from "@sindresorhus/slugify";
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 { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { UnauthorizedError } from "@app/lib/errors";
@@ -16,6 +15,7 @@ import {
SanitizedIdentityPrivilegeSchema
} from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { AccessScope, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas";
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -56,6 +56,10 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
if (!permissions && !privilegePermission) {
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
? privilegePermission.actions.map((action) => ({
@@ -64,19 +68,35 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
conditions: privilegePermission.conditions
}))
: permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body,
slug: req.body.slug ?? slugify(alphaNumericNanoId(12)),
isTemporary: false,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: backfillPermissionV1SchemaToV2Schema(permission)
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: 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
).optional(),
temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
temporaryRange: z
.string()
@@ -138,19 +158,39 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}))
: permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
actorId: req.permission.id,
actor: req.permission.type,
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)
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.body.projectSlug
});
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(),
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
temporaryRange: z
.string()
@@ -216,18 +256,43 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
conditions: privilegePermission.conditions
}))
: permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.updateBySlug({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
slug: req.body.privilegeSlug,
identityId: req.body.identityId,
projectSlug: req.body.projectSlug,
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.body.projectSlug
});
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.updateAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.body.identityId,
actorType: MembershipActors.Identity,
id: privilegeId
},
data: {
...req.body,
isTemporary: true,
...updatedInfo,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @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
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
@@ -235,7 +300,16 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
: 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]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteBySlug({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.privilegeSlug,
identityId: req.body.identityId,
projectSlug: req.body.projectSlug
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: 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]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsBySlug({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
slug: req.params.privilegeSlug,
...req.query
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.query.projectSlug
});
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]),
handler: async (req) => {
const privileges = await server.services.identityProjectAdditionalPrivilege.listIdentityProjectPrivileges({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.query.projectSlug
});
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 {
privileges
privileges: privileges.map((privilege) => ({
...privilege,
identityId: req.query.identityId,
projectMembershipId: privilege.membershipId,
projectId,
slug: privilege.name
}))
};
}
});

View File

@@ -1,9 +1,9 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { AccessScope, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
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 { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -34,7 +34,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
z.object({
isTemporary: z.literal(true),
temporaryMode: z
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
temporaryRange: z
.string()
@@ -55,17 +55,31 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.create({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectMembershipId: req.body.projectMembershipId,
...req.body.type,
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
permissions: req.body.permissions
const { userId, membership } = await server.services.convertor.userMembershipIdToUserId(
req.body.projectMembershipId,
AccessScope.Project,
req.permission.orgId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
permission: req.permission,
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({
isTemporary: z.literal(true).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
temporaryMode: z
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
temporaryRange: z
.string()
@@ -113,21 +127,43 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.updateById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...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,
privilegeId: req.params.privilegeId
const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
req.params.privilegeId,
AccessScope.Project,
req.permission.orgId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
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]),
handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.deleteById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
privilegeId: req.params.privilegeId
const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
req.params.privilegeId,
AccessScope.Project,
req.permission.orgId
);
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]),
handler: async (req) => {
const privileges = await server.services.projectUserAdditionalPrivilege.listPrivileges({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectMembershipId: req.query.projectMembershipId
const { userId, membership } = await server.services.convertor.userMembershipIdToUserId(
req.query.projectMembershipId,
AccessScope.Project,
req.permission.orgId
);
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]),
handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.getPrivilegeDetailsById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
privilegeId: req.params.privilegeId
const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
req.params.privilegeId,
AccessScope.Project,
req.permission.orgId
);
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
}
};
}
});
};

View File

@@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify";
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 { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
@@ -43,7 +43,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
z.object({
isTemporary: z.literal(true),
temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.temporaryMode),
temporaryRange: z
.string()
@@ -64,18 +64,31 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.create({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
projectId: req.body.projectId,
identityId: req.body.identityId,
...req.body.type,
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
permissions: req.body.permissions
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: req.body.projectId,
orgId: req.permission.orgId
},
data: {
actorId: req.body.identityId,
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({
isTemporary: z.literal(true).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.isTemporary),
temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.temporaryMode),
temporaryRange: z
.string()
@@ -129,19 +142,40 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.updateById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.id,
const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
req.params.id,
AccessScope.Project,
req.permission.orgId
);
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: {
...req.body,
...req.body.type,
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]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.deleteById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
req.params.id,
AccessScope.Project,
req.permission.orgId
);
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]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.getPrivilegeDetailsById({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
id: req.params.id
const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId(
req.params.id,
AccessScope.Project,
req.permission.orgId
);
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]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.getPrivilegeDetailsBySlug({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
slug: req.params.privilegeSlug,
...req.query
const { id: projectId } = await server.services.convertor.projectSlugToId({
slug: req.query.projectSlug,
orgId: req.permission.orgId
});
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]),
handler: async (req) => {
const privileges = await server.services.identityProjectAdditionalPrivilegeV2.listIdentityProjectPrivileges({
actorId: req.permission.id,
actor: req.permission.type,
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: req.query.projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.query.identityId,
actorType: MembershipActors.Identity
}
});
return {
privileges
privileges: privileges.map((privilege) => ({
...privilege,
identityId: req.query.identityId,
projectMembershipId: privilege.membershipId,
projectId: req.query.projectId,
slug: privilege.name
}))
};
}
});

View File

@@ -5,6 +5,7 @@ import {
ProjectMembershipRole,
ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
@@ -14,7 +15,6 @@ import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -134,18 +134,34 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
id: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const membership = await server.services.membershipUser.getMembershipByUserId({
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({
method: "POST",
url: "/:projectId/memberships/details",
@@ -317,7 +333,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
z.object({
role: z.string(),
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"),
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]),
handler: async (req) => {
const roles = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
membershipId: req.params.membershipId,
roles: req.body.roles
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.updateMembership({
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]),
handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
membershipId: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
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({
@@ -458,12 +494,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.projectId
}
};
}
});

View File

@@ -1,10 +1,21 @@
import { TDbClient } from "@app/db";
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 const additionalPrivilegeDALFactory = (db: TDbClient) => {
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 };
};

View File

@@ -16,6 +16,7 @@ import {
TCreateAdditionalPrivilegesDTO,
TDeleteAdditionalPrivilegesDTO,
TGetAdditionalPrivilegesByIdDTO,
TGetAdditionalPrivilegesByNameDTO,
TListAdditionalPrivilegesDTO,
TUpdateAdditionalPrivilegesDTO
} from "./additional-privilege-types";
@@ -71,7 +72,9 @@ export const additionalPrivilegeServiceFactory = ({
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) {
@@ -92,7 +95,9 @@ export const additionalPrivilegeServiceFactory = ({
temporaryRange: data.temporaryRange
});
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } };
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const updateAdditionalPrivilege = async (dto: TUpdateAdditionalPrivilegesDTO) => {
@@ -169,7 +174,7 @@ export const additionalPrivilegeServiceFactory = ({
const getAdditionalPrivilegeById = async (dto: TGetAdditionalPrivilegesByIdDTO) => {
const { scopeData, selector } = dto;
const factory = scopeFactory[scopeData.scope];
const { membershipId } = await factory.onListAdditionalPrivilegesGuard(dto);
const { membershipId } = await factory.onGetAdditionalPrivilegesByIdGuard(dto);
const additionalPrivilege = await additionalPrivilegeDAL.findOne({
id: selector.id,
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 { scopeData } = dto;
const factory = scopeFactory[scopeData.scope];
@@ -203,6 +224,7 @@ export const additionalPrivilegeServiceFactory = ({
updateAdditionalPrivilege,
deleteAdditionalPrivilege,
getAdditionalPrivilegeById,
getAdditionalPrivilegeByName,
listAdditionalPrivileges
};
};

View File

@@ -6,7 +6,9 @@ export interface TAdditionalPrivilegesScopeFactory {
onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => 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 };
}
@@ -71,3 +73,13 @@ export type TGetAdditionalPrivilegesByIdDTO = {
actorType: MembershipActors;
};
};
export type TGetAdditionalPrivilegesByNameDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
name: string;
actorId: string;
actorType: MembershipActors;
};
};

View File

@@ -1,88 +1,118 @@
import { AccessScope } from "@app/db/schemas";
import { NotFoundError } from "@app/lib/errors";
import { TMembershipDALFactory } from "../membership/membership-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { AccessScope, AccessScopeData } from "@app/db/schemas";
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
type TConvertorServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findOne">;
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "getMembershipById" | "findOne">;
};
export type TConvertorServiceFactory = ReturnType<typeof convertorServiceFactory>;
export const convertorServiceFactory = ({ projectDAL, membershipDAL }: TConvertorServiceFactoryDep) => {
const $getScopeDatabaseFields = (scopeData: AccessScopeData) => {
if (scopeData.scope === AccessScope.Organization) {
return { scopeOrgId: scopeData.orgId };
}
if (scopeData.scope === AccessScope.Namespace) {
return { scopeNamespaceId: scopeData.namespaceId, scopeOrgId: scopeData.orgId };
}
return { scopeProjectId: scopeData.projectId, scopeOrgId: scopeData.orgId };
};
export const convertorServiceFactory = ({
projectDAL,
membershipDAL,
additionalPrivilegeDAL
}: TConvertorServiceFactoryDep) => {
const projectSlugToId = async (dto: { slug: string; orgId: string }) => {
const project = await projectDAL.findOne({
orgId: dto.orgId,
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;
};
const userMembershipIdToUserId = async (scopeData: AccessScopeData, membershipId: string) => {
const dbFields = $getScopeDatabaseFields(scopeData);
const userMembershipIdToUserId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const membership = await membershipDAL.findOne({
scope: scopeData.scope,
scope,
id: membershipId,
...dbFields,
scopeOrgId: scopeData.orgId
scopeOrgId: orgId
});
if (!membership || !membership.actorUserId) {
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 dbFields = $getScopeDatabaseFields(scopeData);
const groupMembershipIdToGroupId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const membership = await membershipDAL.findOne({
scope: scopeData.scope,
scope,
id: membershipId,
...dbFields,
scopeOrgId: scopeData.orgId
scopeOrgId: orgId
});
if (!membership || !membership.actorGroupId) {
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 dbFields = $getScopeDatabaseFields(scopeData);
const identityMembershipIdToIdentityId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const membership = await membershipDAL.findOne({
scope: scopeData.scope,
scope,
id: membershipId,
...dbFields,
scopeOrgId: scopeData.orgId
scopeOrgId: orgId
});
if (!membership || !membership.actorIdentityId) {
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 {
projectSlugToId,
userMembershipIdToUserId,
groupMembershipIdToGroupId,
identityMembershipIdToGroupId
identityMembershipIdToIdentityId,
additionalPrivilegeIdToMembershipId,
additionalPrivilegeNameToId,
identityIdToMembershipId
};
};

View File

@@ -274,7 +274,7 @@ export const membershipUserServiceFactory = ({
const existingMembership = await membershipUserDAL.findOne({
scope: scopeData.scope,
...scopeDatabaseFields,
actorUserId: dto.selector.actorId
actorUserId: dto.selector.userId
});
if (!existingMembership)
throw new BadRequestError({
@@ -338,31 +338,31 @@ export const membershipUserServiceFactory = ({
},
tx
);
await membershipRoleDAL.insertMany(roleDocs, tx);
return doc;
const insertedRoles = await membershipRoleDAL.insertMany(roleDocs, tx);
return { ...doc, roles: insertedRoles };
});
return { memberships: membershipDoc };
return { membership: membershipDoc };
};
const deleteMembership = async (dto: TDeleteMembershipUserDTO) => {
const { scopeData } = dto;
const factory = scopeFactory[scopeData.scope];
const { actorIdOfDeletor } = await factory.onDeleteMembershipUserGuard(dto);
await factory.onDeleteMembershipUserGuard(dto);
const scopeDatabaseFields = factory.getScopeDatabaseFields(dto.scopeData);
const existingMembership = await membershipUserDAL.findOne({
scope: scopeData.scope,
...scopeDatabaseFields,
actorUserId: dto.selector.actorId
actorUserId: dto.selector.userId
});
if (!existingMembership)
throw new BadRequestError({
message: "User doesn't have membership"
});
if (existingMembership.actorUserId === actorIdOfDeletor)
if (existingMembership.actorUserId === dto.permission.id)
throw new BadRequestError({
message: "You can delete you own membership"
});
@@ -403,7 +403,7 @@ export const membershipUserServiceFactory = ({
await factory.onGetMembershipUserByUserIdGuard(dto);
const membership = await membershipUserDAL.getUserById({
scopeData,
userId: selector.actorId
userId: selector.userId
});
if (!membership) throw new NotFoundError({ message: `User membership not found` });

View File

@@ -6,7 +6,7 @@ export interface TMembershipUserScopeFactory {
onCreateMembershipComplete: (arg: { id: string; email: string }[]) => Promise<void>;
onUpdateMembershipUserGuard: (arg: TUpdateMembershipUserDTO) => Promise<void>;
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO) => Promise<{ actorIdOfDeletor: string }>;
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO) => Promise<void>;
onListMembershipUserGuard: (arg: TListMembershipUserDTO) => Promise<void>;
onGetMembershipUserByUserIdGuard: (arg: TGetMembershipUserByUserIdDTO) => Promise<void>;
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
@@ -37,7 +37,7 @@ export type TUpdateMembershipUserDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
actorId: string;
userId: string;
};
data: {
isActive?: boolean;
@@ -67,7 +67,7 @@ export type TDeleteMembershipUserDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
actorId: string;
userId: string;
};
};
@@ -75,6 +75,6 @@ export type TGetMembershipUserByUserIdDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
actorId: string;
userId: string;
};
};

View File

@@ -63,4 +63,4 @@ export const newNamespaceMembershipUserFactory = (
getScopeDatabaseFields,
isCustomRole
};
};
};

View File

@@ -67,7 +67,6 @@ export const newOrgMembershipUserFactory = ({
dto.permission.orgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
return { actorIdOfDeletor: dto.permission.id };
};
const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => {

View File

@@ -134,7 +134,6 @@ export const newProjectMembershipUserFactory = ({
actorOrgId: dto.permission.orgId
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member);
return { actorIdOfDeletor: dto.permission.id };
};
const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => {