mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
feat(server): multi role with temporary support for identity
This commit is contained in:
Vendored
+8
@@ -32,6 +32,9 @@ import {
|
|||||||
TIdentityOrgMemberships,
|
TIdentityOrgMemberships,
|
||||||
TIdentityOrgMembershipsInsert,
|
TIdentityOrgMembershipsInsert,
|
||||||
TIdentityOrgMembershipsUpdate,
|
TIdentityOrgMembershipsUpdate,
|
||||||
|
TIdentityProjectMembershipRole,
|
||||||
|
TIdentityProjectMembershipRoleInsert,
|
||||||
|
TIdentityProjectMembershipRoleUpdate,
|
||||||
TIdentityProjectMemberships,
|
TIdentityProjectMemberships,
|
||||||
TIdentityProjectMembershipsInsert,
|
TIdentityProjectMembershipsInsert,
|
||||||
TIdentityProjectMembershipsUpdate,
|
TIdentityProjectMembershipsUpdate,
|
||||||
@@ -280,6 +283,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityProjectMembershipsInsert,
|
TIdentityProjectMembershipsInsert,
|
||||||
TIdentityProjectMembershipsUpdate
|
TIdentityProjectMembershipsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityProjectMembershipRole]: Knex.CompositeTableType<
|
||||||
|
TIdentityProjectMembershipRole,
|
||||||
|
TIdentityProjectMembershipRoleInsert,
|
||||||
|
TIdentityProjectMembershipRoleUpdate
|
||||||
|
>;
|
||||||
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
||||||
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
||||||
TSecretApprovalPolicies,
|
TSecretApprovalPolicies,
|
||||||
|
|||||||
@@ -21,37 +21,37 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.datetime("temporaryAccessEndTime");
|
t.datetime("temporaryAccessEndTime");
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.ProjectUserMembershipRole);
|
|
||||||
|
|
||||||
const projectMembershipStream = knex.select("*").from(TableName.ProjectMembership).stream();
|
|
||||||
const chunkSize = 1000;
|
|
||||||
let rows: TProjectUserMembershipRolesInsert[] = [];
|
|
||||||
for await (const row of projectMembershipStream) {
|
|
||||||
// disabling eslint just this part because the latest ts type doesn't have these values after migration as they are removed
|
|
||||||
/* eslint-disable */
|
|
||||||
// @ts-ignore - created at is inserted from old data
|
|
||||||
rows = rows.concat({
|
|
||||||
// @ts-ignore - missing in ts type post migration
|
|
||||||
role: row.role,
|
|
||||||
// @ts-ignore - missing in ts type post migration
|
|
||||||
customRoleId: row.roleId,
|
|
||||||
projectMembershipId: row.id,
|
|
||||||
createdAt: row.createdAt,
|
|
||||||
updatedAt: row.updatedAt
|
|
||||||
});
|
|
||||||
/* eslint-disable */
|
|
||||||
if (rows.length >= chunkSize) {
|
|
||||||
await knex(TableName.ProjectUserMembershipRole).insert(rows);
|
|
||||||
rows.splice(0, rows.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await knex(TableName.ProjectUserMembershipRole).insert(rows);
|
|
||||||
await knex.schema.alterTable(TableName.ProjectMembership, (t) => {
|
|
||||||
t.dropColumn("roleId");
|
|
||||||
t.dropColumn("role");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ProjectUserMembershipRole);
|
||||||
|
|
||||||
|
const projectMembershipStream = knex.select("*").from(TableName.ProjectMembership).stream();
|
||||||
|
const chunkSize = 1000;
|
||||||
|
let rows: TProjectUserMembershipRolesInsert[] = [];
|
||||||
|
for await (const row of projectMembershipStream) {
|
||||||
|
// disabling eslint just this part because the latest ts type doesn't have these values after migration as they are removed
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore - created at is inserted from old data
|
||||||
|
rows = rows.concat({
|
||||||
|
// @ts-ignore - missing in ts type post migration
|
||||||
|
role: row.role,
|
||||||
|
// @ts-ignore - missing in ts type post migration
|
||||||
|
customRoleId: row.roleId,
|
||||||
|
projectMembershipId: row.id,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
updatedAt: row.updatedAt
|
||||||
|
});
|
||||||
|
/* eslint-disable */
|
||||||
|
if (rows.length >= chunkSize) {
|
||||||
|
await knex(TableName.ProjectUserMembershipRole).insert(rows);
|
||||||
|
rows.splice(0, rows.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rows.length) await knex(TableName.ProjectUserMembershipRole).insert(rows);
|
||||||
|
await knex.schema.alterTable(TableName.ProjectMembership, (t) => {
|
||||||
|
t.dropColumn("roleId");
|
||||||
|
t.dropColumn("role");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName, TIdentityProjectMembershipRoleInsert } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesTableExist = await knex.schema.hasTable(TableName.IdentityProjectMembershipRole);
|
||||||
|
if (!doesTableExist) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityProjectMembershipRole, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("role").notNullable();
|
||||||
|
t.uuid("projectMembershipId").notNullable();
|
||||||
|
t.foreign("projectMembershipId")
|
||||||
|
.references("id")
|
||||||
|
.inTable(TableName.IdentityProjectMembership)
|
||||||
|
.onDelete("CASCADE");
|
||||||
|
// until role is changed/removed the role should not deleted
|
||||||
|
t.uuid("customRoleId");
|
||||||
|
t.foreign("customRoleId").references("id").inTable(TableName.ProjectRoles);
|
||||||
|
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||||
|
t.string("temporaryMode");
|
||||||
|
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||||
|
t.datetime("temporaryAccessStartTime");
|
||||||
|
t.datetime("temporaryAccessEndTime");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IdentityProjectMembershipRole);
|
||||||
|
|
||||||
|
const projectMembershipStream = knex.select("*").from(TableName.IdentityProjectMembership).stream();
|
||||||
|
const chunkSize = 1000;
|
||||||
|
let rows: TIdentityProjectMembershipRoleInsert[] = [];
|
||||||
|
for await (const row of projectMembershipStream) {
|
||||||
|
// disabling eslint just this part because the latest ts type doesn't have these values after migration as they are removed
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore - created at is inserted from old data
|
||||||
|
rows = rows.concat({
|
||||||
|
// @ts-ignore - missing in ts type post migration
|
||||||
|
role: row.role,
|
||||||
|
// @ts-ignore - missing in ts type post migration
|
||||||
|
customRoleId: row.roleId,
|
||||||
|
projectMembershipId: row.id,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
updatedAt: row.updatedAt
|
||||||
|
});
|
||||||
|
/* eslint-disable */
|
||||||
|
if (rows.length >= chunkSize) {
|
||||||
|
await knex(TableName.IdentityProjectMembershipRole).insert(rows);
|
||||||
|
rows.splice(0, rows.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(rows.length) await knex(TableName.IdentityProjectMembershipRole).insert(rows);
|
||||||
|
await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => {
|
||||||
|
t.dropColumn("roleId");
|
||||||
|
t.dropColumn("role");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const projectIdentityMembershipRoleStream = knex.select("*").from(TableName.IdentityProjectMembershipRole).stream();
|
||||||
|
await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => {
|
||||||
|
t.string("role");
|
||||||
|
t.uuid("roleId");
|
||||||
|
t.foreign("roleId").references("id").inTable(TableName.ProjectRoles);
|
||||||
|
});
|
||||||
|
for await (const row of projectIdentityMembershipRoleStream) {
|
||||||
|
await knex(TableName.IdentityProjectMembership).where({ id: row.projectMembershipId }).update({
|
||||||
|
// @ts-ignore - since the latest one doesn't have roleId anymore there will be type error here
|
||||||
|
roleId: row.customRoleId,
|
||||||
|
role: row.role
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => {
|
||||||
|
t.string("role").notNullable().alter({ alterNullable: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityProjectMembershipRole);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityProjectMembershipRole);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const IdentityProjectMembershipRoleSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
role: z.string(),
|
||||||
|
projectMembershipId: z.string().uuid(),
|
||||||
|
customRoleId: z.string().uuid().nullable().optional(),
|
||||||
|
isTemporary: z.boolean().default(false),
|
||||||
|
temporaryMode: z.string().nullable().optional(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityProjectMembershipRole = z.infer<typeof IdentityProjectMembershipRoleSchema>;
|
||||||
|
export type TIdentityProjectMembershipRoleInsert = Omit<
|
||||||
|
z.input<typeof IdentityProjectMembershipRoleSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TIdentityProjectMembershipRoleUpdate = Partial<
|
||||||
|
Omit<z.input<typeof IdentityProjectMembershipRoleSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -9,8 +9,6 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const IdentityProjectMembershipsSchema = z.object({
|
export const IdentityProjectMembershipsSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
role: z.string(),
|
|
||||||
roleId: z.string().uuid().nullable().optional(),
|
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export * from "./git-app-org";
|
|||||||
export * from "./identities";
|
export * from "./identities";
|
||||||
export * from "./identity-access-tokens";
|
export * from "./identity-access-tokens";
|
||||||
export * from "./identity-org-memberships";
|
export * from "./identity-org-memberships";
|
||||||
|
export * from "./identity-project-membership-role";
|
||||||
export * from "./identity-project-memberships";
|
export * from "./identity-project-memberships";
|
||||||
export * from "./identity-ua-client-secrets";
|
export * from "./identity-ua-client-secrets";
|
||||||
export * from "./identity-universal-auths";
|
export * from "./identity-universal-auths";
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export enum TableName {
|
|||||||
IdentityUaClientSecret = "identity_ua_client_secrets",
|
IdentityUaClientSecret = "identity_ua_client_secrets",
|
||||||
IdentityOrgMembership = "identity_org_memberships",
|
IdentityOrgMembership = "identity_org_memberships",
|
||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
ScimToken = "scim_tokens",
|
ScimToken = "scim_tokens",
|
||||||
SecretApprovalPolicy = "secret_approval_policies",
|
SecretApprovalPolicy = "secret_approval_policies",
|
||||||
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
||||||
|
|||||||
@@ -75,9 +75,15 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await knex(TableName.IdentityProjectMembership).insert({
|
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
|
||||||
identityId: seedData1.machineIdentity.id,
|
.insert({
|
||||||
|
identityId: seedData1.machineIdentity.id,
|
||||||
|
projectId: seedData1.project.id
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
await knex(TableName.IdentityProjectMembershipRole).insert({
|
||||||
role: ProjectMembershipRole.Admin,
|
role: ProjectMembershipRole.Admin,
|
||||||
projectId: seedData1.project.id
|
projectMembershipId: identityProjectMembership[0].id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
secretVersioning: true,
|
secretVersioning: true,
|
||||||
pitRecovery: false,
|
pitRecovery: false,
|
||||||
ipAllowlisting: false,
|
ipAllowlisting: false,
|
||||||
rbac: true,
|
rbac: false,
|
||||||
customRateLimits: false,
|
customRateLimits: false,
|
||||||
customAlerts: false,
|
customAlerts: false,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { ProjectUserMembershipRolesSchema, TableName } from "@app/db/schemas";
|
import { IdentityProjectMembershipRoleSchema, ProjectUserMembershipRolesSchema, TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
|
||||||
@@ -108,18 +108,60 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
const membership = await db(TableName.IdentityProjectMembership)
|
const docs = await db(TableName.IdentityProjectMembership)
|
||||||
|
.join(
|
||||||
|
TableName.IdentityProjectMembershipRole,
|
||||||
|
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.ProjectRoles,
|
TableName.ProjectRoles,
|
||||||
`${TableName.IdentityProjectMembership}.roleId`,
|
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
.where("identityId", identityId)
|
.where("identityId", identityId)
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.select(selectAllTableCols(TableName.IdentityProjectMembership))
|
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
||||||
.select("permissions")
|
.select(
|
||||||
.first();
|
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
||||||
return membership;
|
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
||||||
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
||||||
|
)
|
||||||
|
.select("permissions");
|
||||||
|
|
||||||
|
const permission = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "membershipId",
|
||||||
|
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({
|
||||||
|
id: membershipId,
|
||||||
|
identityId,
|
||||||
|
projectId,
|
||||||
|
createdAt: membershipCreatedAt,
|
||||||
|
updatedAt: membershipUpdatedAt,
|
||||||
|
// just a prefilled value
|
||||||
|
orgAuthEnforced: false,
|
||||||
|
orgId: ""
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "id",
|
||||||
|
label: "roles" as const,
|
||||||
|
mapper: (data) =>
|
||||||
|
IdentityProjectMembershipRoleSchema.extend({
|
||||||
|
permissions: z.unknown(),
|
||||||
|
customRoleSlug: z.string().optional().nullable()
|
||||||
|
}).parse(data)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// when introducting cron mode change it here
|
||||||
|
const activeRoles = permission?.[0]?.roles.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
);
|
||||||
|
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const permissionServiceFactory = ({
|
|||||||
|
|
||||||
const buildProjectPermission = (projectUserRoles: TBuildProjectPermissionDTO) => {
|
const buildProjectPermission = (projectUserRoles: TBuildProjectPermissionDTO) => {
|
||||||
const rules = projectUserRoles
|
const rules = projectUserRoles
|
||||||
.map(({ role, permission }) => {
|
.map(({ role, permissions }) => {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case ProjectMembershipRole.Admin:
|
case ProjectMembershipRole.Admin:
|
||||||
return projectAdminPermissions;
|
return projectAdminPermissions;
|
||||||
@@ -77,10 +77,11 @@ export const permissionServiceFactory = ({
|
|||||||
return projectViewerPermission;
|
return projectViewerPermission;
|
||||||
case ProjectMembershipRole.NoAccess:
|
case ProjectMembershipRole.NoAccess:
|
||||||
return projectNoAccessPermissions;
|
return projectNoAccessPermissions;
|
||||||
case ProjectMembershipRole.Custom:
|
case ProjectMembershipRole.Custom: {
|
||||||
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
||||||
permission as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
name: "ProjectRoleInvalid",
|
name: "ProjectRoleInvalid",
|
||||||
@@ -149,7 +150,11 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// user permission for a project in an organization
|
// user permission for a project in an organization
|
||||||
const getUserProjectPermission = async (userId: string, projectId: string, userOrgId?: string) => {
|
const getUserProjectPermission = async (
|
||||||
|
userId: string,
|
||||||
|
projectId: string,
|
||||||
|
userOrgId?: string
|
||||||
|
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" });
|
if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" });
|
||||||
|
|
||||||
@@ -173,16 +178,28 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIdentityProjectPermission = async (identityId: string, projectId: string) => {
|
const getIdentityProjectPermission = async (
|
||||||
const membership = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
identityId: string,
|
||||||
if (!membership) throw new UnauthorizedError({ name: "Identity not in project" });
|
projectId: string
|
||||||
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||||
|
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
|
if (!identityProjectPermission) throw new UnauthorizedError({ name: "Identity not in project" });
|
||||||
|
|
||||||
|
if (
|
||||||
|
identityProjectPermission.roles.some(
|
||||||
|
({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions
|
||||||
|
)
|
||||||
|
) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission([{ role: membership.role, permission: membership.permissions }]),
|
permission: buildProjectPermission(identityProjectPermission.roles),
|
||||||
membership
|
membership: identityProjectPermission,
|
||||||
|
hasRole: (role: string) =>
|
||||||
|
identityProjectPermission.roles.findIndex(
|
||||||
|
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||||
|
) !== -1
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -202,7 +219,11 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TProjectPermissionRT<T extends ActorType> = T extends ActorType.SERVICE
|
type TProjectPermissionRT<T extends ActorType> = T extends ActorType.SERVICE
|
||||||
? { permission: MongoAbility<ProjectPermissionSet, MongoQuery>; membership: undefined; hasRole: () => false } // service token doesn't have both membership and roles
|
? {
|
||||||
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
|
membership: undefined;
|
||||||
|
hasRole: (arg: string) => boolean;
|
||||||
|
} // service token doesn't have both membership and roles
|
||||||
: {
|
: {
|
||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & {
|
membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & {
|
||||||
@@ -241,12 +262,12 @@ export const permissionServiceFactory = ({
|
|||||||
if (!projectRole) throw new BadRequestError({ message: "Role not found" });
|
if (!projectRole) throw new BadRequestError({ message: "Role not found" });
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission([
|
permission: buildProjectPermission([
|
||||||
{ role: ProjectMembershipRole.Custom, permission: projectRole.permissions }
|
{ role: ProjectMembershipRole.Custom, permissions: projectRole.permissions }
|
||||||
]),
|
]),
|
||||||
role: projectRole
|
role: projectRole
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { permission: buildProjectPermission([{ role, permission: [] }]) };
|
return { permission: buildProjectPermission([{ role, permissions: [] }]) };
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type TBuildProjectPermissionDTO = {
|
export type TBuildProjectPermissionDTO = {
|
||||||
permission?: unknown;
|
permissions?: unknown;
|
||||||
role: string;
|
role: string;
|
||||||
}[];
|
}[];
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import { identityServiceFactory } from "@app/services/identity/identity-service"
|
|||||||
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
||||||
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal";
|
||||||
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
||||||
import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal";
|
import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal";
|
||||||
import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal";
|
import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal";
|
||||||
@@ -166,6 +167,7 @@ export const registerRoutes = async (
|
|||||||
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
|
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
|
||||||
const identityOrgMembershipDAL = identityOrgDALFactory(db);
|
const identityOrgMembershipDAL = identityOrgDALFactory(db);
|
||||||
const identityProjectDAL = identityProjectDALFactory(db);
|
const identityProjectDAL = identityProjectDALFactory(db);
|
||||||
|
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
|
||||||
|
|
||||||
const identityUaDAL = identityUaDALFactory(db);
|
const identityUaDAL = identityUaDALFactory(db);
|
||||||
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
||||||
@@ -374,8 +376,10 @@ export const registerRoutes = async (
|
|||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectUserMembershipRoleDAL
|
projectUserMembershipRoleDAL,
|
||||||
|
identityProjectMembershipRoleDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectEnvService = projectEnvServiceFactory({
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
@@ -526,7 +530,9 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
identityProjectDAL,
|
identityProjectDAL,
|
||||||
identityOrgMembershipDAL
|
identityOrgMembershipDAL,
|
||||||
|
identityProjectMembershipRoleDAL,
|
||||||
|
projectRoleDAL
|
||||||
});
|
});
|
||||||
const identityUaService = identityUaServiceFactory({
|
const identityUaService = identityUaServiceFactory({
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
|
|||||||
@@ -136,21 +136,24 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
membershipId: z.string().trim()
|
membershipId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
roles: z.array(
|
roles: z
|
||||||
z.union([
|
.array(
|
||||||
z.object({
|
z.union([
|
||||||
role: z.string(),
|
z.object({
|
||||||
isTemporary: z.literal(false).default(false)
|
role: z.string(),
|
||||||
}),
|
isTemporary: z.literal(false).default(false)
|
||||||
z.object({
|
}),
|
||||||
role: z.string(),
|
z.object({
|
||||||
isTemporary: z.literal(true),
|
role: z.string(),
|
||||||
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
isTemporary: z.literal(true),
|
||||||
temporaryRange: z.string(),
|
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
||||||
temporaryAccessStartTime: z.string().datetime()
|
temporaryRange: z.string(),
|
||||||
})
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
])
|
})
|
||||||
)
|
])
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "Atleast one permanent role required")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import {
|
|||||||
IdentitiesSchema,
|
IdentitiesSchema,
|
||||||
IdentityProjectMembershipsSchema,
|
IdentityProjectMembershipsSchema,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectRolesSchema
|
ProjectUserMembershipRolesSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
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 registerIdentityProjectRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityProjectRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -57,24 +58,41 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
identityId: z.string().trim()
|
identityId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess)
|
roles: z
|
||||||
|
.array(
|
||||||
|
z.union([
|
||||||
|
z.object({
|
||||||
|
role: z.string(),
|
||||||
|
isTemporary: z.literal(false).default(false)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
role: z.string(),
|
||||||
|
isTemporary: z.literal(true),
|
||||||
|
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
||||||
|
temporaryRange: z.string(),
|
||||||
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "Atleast one permanent role required")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityMembership: IdentityProjectMembershipsSchema
|
roles: ProjectUserMembershipRolesSchema.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identityMembership = await server.services.identityProject.updateProjectIdentity({
|
const roles = await server.services.identityProject.updateProjectIdentity({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId,
|
identityId: req.params.identityId,
|
||||||
projectId: req.params.projectId,
|
projectId: req.params.projectId,
|
||||||
role: req.body.role
|
roles: req.body.roles
|
||||||
});
|
});
|
||||||
return { identityMembership };
|
return { roles };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,18 +145,29 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityMemberships: IdentityProjectMembershipsSchema.merge(
|
identityMemberships: z
|
||||||
z.object({
|
.object({
|
||||||
customRole: ProjectRolesSchema.pick({
|
id: z.string(),
|
||||||
id: true,
|
identityId: z.string(),
|
||||||
name: true,
|
createdAt: z.date(),
|
||||||
slug: true,
|
updatedAt: z.date(),
|
||||||
permissions: true,
|
roles: z.array(
|
||||||
description: true
|
z.object({
|
||||||
}).optional(),
|
id: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
customRoleId: z.string().optional().nullable(),
|
||||||
|
customRoleName: z.string().optional().nullable(),
|
||||||
|
customRoleSlug: z.string().optional().nullable(),
|
||||||
|
isTemporary: z.boolean(),
|
||||||
|
temporaryMode: z.string().optional().nullable(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional()
|
||||||
|
})
|
||||||
|
),
|
||||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||||
})
|
})
|
||||||
).array()
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
||||||
|
|
||||||
@@ -15,52 +15,81 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
const docs = await (tx || db)(TableName.IdentityProjectMembership)
|
const docs = await (tx || db)(TableName.IdentityProjectMembership)
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
||||||
|
.join(
|
||||||
|
TableName.IdentityProjectMembershipRole,
|
||||||
|
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.ProjectRoles,
|
TableName.ProjectRoles,
|
||||||
`${TableName.IdentityProjectMembership}.roleId`,
|
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
.select(selectAllTableCols(TableName.IdentityProjectMembership))
|
.select(
|
||||||
// cr stands for custom role
|
db.ref("id").withSchema(TableName.IdentityProjectMembership),
|
||||||
.select(db.ref("id").as("crId").withSchema(TableName.ProjectRoles))
|
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership),
|
||||||
.select(db.ref("name").as("crName").withSchema(TableName.ProjectRoles))
|
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership),
|
||||||
.select(db.ref("slug").as("crSlug").withSchema(TableName.ProjectRoles))
|
db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity),
|
||||||
.select(db.ref("description").as("crDescription").withSchema(TableName.ProjectRoles))
|
db.ref("id").as("identityId").withSchema(TableName.Identity),
|
||||||
.select(db.ref("permissions").as("crPermission").withSchema(TableName.ProjectRoles))
|
db.ref("name").as("identityName").withSchema(TableName.Identity),
|
||||||
.select(db.ref("permissions").as("crPermission").withSchema(TableName.ProjectRoles))
|
db.ref("id").withSchema(TableName.IdentityProjectMembership),
|
||||||
.select(db.ref("id").as("identityId").withSchema(TableName.Identity))
|
db.ref("role").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
.select(db.ref("name").as("identityName").withSchema(TableName.Identity))
|
db.ref("id").withSchema(TableName.IdentityProjectMembershipRole).as("membershipRoleId"),
|
||||||
.select(db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity));
|
db.ref("customRoleId").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
return docs.map(
|
db.ref("name").withSchema(TableName.ProjectRoles).as("customRoleName"),
|
||||||
({
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||||
crId,
|
db.ref("temporaryMode").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
crDescription,
|
db.ref("isTemporary").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
crSlug,
|
db.ref("temporaryRange").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
crPermission,
|
db.ref("temporaryAccessStartTime").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
crName,
|
db.ref("temporaryAccessEndTime").withSchema(TableName.IdentityProjectMembershipRole)
|
||||||
identityId,
|
);
|
||||||
identityName,
|
|
||||||
identityAuthMethod,
|
const members = sqlNestRelationships({
|
||||||
...el
|
data: docs,
|
||||||
}) => ({
|
parentMapper: ({ identityId, identityName, identityAuthMethod, id, createdAt, updatedAt }) => ({
|
||||||
...el,
|
id,
|
||||||
identityId,
|
identityId,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
identity: {
|
identity: {
|
||||||
id: identityId,
|
id: identityId,
|
||||||
name: identityName,
|
name: identityName,
|
||||||
authMethod: identityAuthMethod
|
authMethod: identityAuthMethod
|
||||||
},
|
}
|
||||||
customRole: el.roleId
|
}),
|
||||||
? {
|
key: "id",
|
||||||
id: crId,
|
childrenMapper: [
|
||||||
name: crName,
|
{
|
||||||
slug: crSlug,
|
label: "roles" as const,
|
||||||
permissions: crPermission,
|
key: "membershipRoleId",
|
||||||
description: crDescription
|
mapper: ({
|
||||||
}
|
role,
|
||||||
: undefined
|
customRoleId,
|
||||||
})
|
customRoleName,
|
||||||
);
|
customRoleSlug,
|
||||||
|
membershipRoleId,
|
||||||
|
temporaryRange,
|
||||||
|
temporaryMode,
|
||||||
|
temporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime,
|
||||||
|
isTemporary
|
||||||
|
}) => ({
|
||||||
|
id: membershipRoleId,
|
||||||
|
role,
|
||||||
|
customRoleId,
|
||||||
|
customRoleName,
|
||||||
|
customRoleSlug,
|
||||||
|
temporaryRange,
|
||||||
|
temporaryMode,
|
||||||
|
temporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime,
|
||||||
|
isTemporary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return members;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindByProjectId" });
|
throw new DatabaseError({ error, name: "FindByProjectId" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TIdentityProjectMembershipRoleDALFactory = ReturnType<typeof identityProjectMembershipRoleDALFactory>;
|
||||||
|
|
||||||
|
export const identityProjectMembershipRoleDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.IdentityProjectMembershipRole);
|
||||||
|
return orm;
|
||||||
|
};
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectMembershipRole, TProjectRoles } from "@app/db/schemas";
|
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||||
|
import { groupBy } from "@app/lib/fn";
|
||||||
|
|
||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
|
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
||||||
|
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
||||||
import { TIdentityProjectDALFactory } from "./identity-project-dal";
|
import { TIdentityProjectDALFactory } from "./identity-project-dal";
|
||||||
|
import { TIdentityProjectMembershipRoleDALFactory } from "./identity-project-membership-role-dal";
|
||||||
import {
|
import {
|
||||||
TCreateProjectIdentityDTO,
|
TCreateProjectIdentityDTO,
|
||||||
TDeleteProjectIdentityDTO,
|
TDeleteProjectIdentityDTO,
|
||||||
@@ -19,7 +24,12 @@ import {
|
|||||||
|
|
||||||
type TIdentityProjectServiceFactoryDep = {
|
type TIdentityProjectServiceFactoryDep = {
|
||||||
identityProjectDAL: TIdentityProjectDALFactory;
|
identityProjectDAL: TIdentityProjectDALFactory;
|
||||||
|
identityProjectMembershipRoleDAL: Pick<
|
||||||
|
TIdentityProjectMembershipRoleDALFactory,
|
||||||
|
"create" | "transaction" | "insertMany" | "delete"
|
||||||
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||||
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
|
||||||
};
|
};
|
||||||
@@ -30,7 +40,9 @@ export const identityProjectServiceFactory = ({
|
|||||||
identityProjectDAL,
|
identityProjectDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
projectDAL
|
identityProjectMembershipRoleDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectRoleDAL
|
||||||
}: TIdentityProjectServiceFactoryDep) => {
|
}: TIdentityProjectServiceFactoryDep) => {
|
||||||
const createProjectIdentity = async ({
|
const createProjectIdentity = async ({
|
||||||
identityId,
|
identityId,
|
||||||
@@ -70,11 +82,24 @@ export const identityProjectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
const isCustomRole = Boolean(customRole);
|
const isCustomRole = Boolean(customRole);
|
||||||
|
|
||||||
const projectIdentity = await identityProjectDAL.create({
|
const projectIdentity = await identityProjectDAL.transaction(async (tx) => {
|
||||||
identityId,
|
const identityProjectMembership = await identityProjectDAL.create(
|
||||||
projectId: project.id,
|
{
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : role,
|
identityId,
|
||||||
roleId: customRole?.id
|
projectId: project.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await identityProjectMembershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : role,
|
||||||
|
customRoleId: customRole?.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return identityProjectMembership;
|
||||||
});
|
});
|
||||||
return projectIdentity;
|
return projectIdentity;
|
||||||
};
|
};
|
||||||
@@ -82,7 +107,7 @@ export const identityProjectServiceFactory = ({
|
|||||||
const updateProjectIdentity = async ({
|
const updateProjectIdentity = async ({
|
||||||
projectId,
|
projectId,
|
||||||
identityId,
|
identityId,
|
||||||
role,
|
roles,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
@@ -106,28 +131,54 @@ export const identityProjectServiceFactory = ({
|
|||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
|
||||||
|
|
||||||
let customRole: TProjectRoles | undefined;
|
// validate custom roles input
|
||||||
if (role) {
|
const customInputRoles = roles.filter(
|
||||||
const { permission: rolePermission, role: customOrgRole } = await permissionService.getProjectPermissionByRole(
|
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
||||||
role,
|
|
||||||
projectIdentity.projectId
|
|
||||||
);
|
|
||||||
|
|
||||||
const isCustomRole = Boolean(customOrgRole);
|
|
||||||
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
|
|
||||||
if (!hasRequiredNewRolePermission)
|
|
||||||
throw new BadRequestError({ message: "Failed to create a more privileged identity" });
|
|
||||||
if (isCustomRole) customRole = customOrgRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [updatedProjectIdentity] = await identityProjectDAL.update(
|
|
||||||
{ projectId, identityId: projectIdentity.identityId },
|
|
||||||
{
|
|
||||||
role: customRole ? ProjectMembershipRole.Custom : role,
|
|
||||||
roleId: customRole ? customRole.id : null
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
return updatedProjectIdentity;
|
const hasCustomRole = Boolean(customInputRoles.length);
|
||||||
|
const customRoles = hasCustomRole
|
||||||
|
? await projectRoleDAL.find({
|
||||||
|
projectId,
|
||||||
|
$in: { slug: customInputRoles.map(({ role }) => role) }
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
if (customRoles.length !== customInputRoles.length) throw new BadRequestError({ message: "Custom role not found" });
|
||||||
|
|
||||||
|
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
||||||
|
|
||||||
|
const santiziedProjectMembershipRoles = roles.map((inputRole) => {
|
||||||
|
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
|
||||||
|
if (!inputRole.isTemporary) {
|
||||||
|
return {
|
||||||
|
projectMembershipId: projectIdentity.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
||||||
|
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// check cron or relative here later for now its just relative
|
||||||
|
const relativeTimeInMs = ms(inputRole.temporaryRange);
|
||||||
|
if (relativeTimeInMs <= 0) {
|
||||||
|
throw new BadRequestError({ message: "Temporary relative time range must be positive" });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
projectMembershipId: projectIdentity.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
||||||
|
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: ProjectUserMembershipTemporaryMode.Relative,
|
||||||
|
temporaryRange: inputRole.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(inputRole.temporaryAccessStartTime),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(inputRole.temporaryAccessStartTime).getTime() + relativeTimeInMs)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedRoles = await identityProjectMembershipRoleDAL.transaction(async (tx) => {
|
||||||
|
await identityProjectMembershipRoleDAL.delete({ projectMembershipId: projectIdentity.id }, tx);
|
||||||
|
return identityProjectMembershipRoleDAL.insertMany(santiziedProjectMembershipRoles, tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedRoles;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteProjectIdentity = async ({
|
const deleteProjectIdentity = async ({
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
||||||
|
|
||||||
export type TCreateProjectIdentityDTO = {
|
export type TCreateProjectIdentityDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
role: string;
|
role: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TUpdateProjectIdentityDTO = {
|
export type TUpdateProjectIdentityDTO = {
|
||||||
role: string;
|
roles: (
|
||||||
|
| {
|
||||||
|
role: string;
|
||||||
|
isTemporary?: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
role: string;
|
||||||
|
isTemporary: true;
|
||||||
|
temporaryMode: ProjectUserMembershipTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}
|
||||||
|
)[];
|
||||||
identityId: string;
|
identityId: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { TProjectPermission } from "@app/lib/types";
|
|||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
|
||||||
|
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TOrgServiceFactory } from "../org/org-service";
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||||
@@ -51,6 +52,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "insertMany" | "find">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "insertMany" | "find">;
|
||||||
identityOrgMembershipDAL: TIdentityOrgDALFactory;
|
identityOrgMembershipDAL: TIdentityOrgDALFactory;
|
||||||
identityProjectDAL: TIdentityProjectDALFactory;
|
identityProjectDAL: TIdentityProjectDALFactory;
|
||||||
|
identityProjectMembershipRoleDAL: Pick<TIdentityProjectMembershipRoleDALFactory, "create">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "create" | "findById" | "delete" | "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "create" | "findById" | "delete" | "findOne">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne">;
|
||||||
@@ -78,7 +80,8 @@ export const projectServiceFactory = ({
|
|||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectUserMembershipRoleDAL
|
projectUserMembershipRoleDAL,
|
||||||
|
identityProjectMembershipRoleDAL
|
||||||
}: TProjectServiceFactoryDep) => {
|
}: TProjectServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Create workspace. Make user the admin
|
* Create workspace. Make user the admin
|
||||||
@@ -275,12 +278,19 @@ export const projectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
const isCustomRole = Boolean(customRole);
|
const isCustomRole = Boolean(customRole);
|
||||||
|
|
||||||
await identityProjectDAL.create(
|
const identityProjectMembership = await identityProjectDAL.create(
|
||||||
{
|
{
|
||||||
identityId: actorId,
|
identityId: actorId,
|
||||||
projectId: project.id,
|
projectId: project.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await identityProjectMembershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin,
|
role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin,
|
||||||
roleId: customRole?.id
|
customRoleId: customRole?.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user