diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js index 9c558919b..b23cf05ae 100644 --- a/backend/.eslintrc.js +++ b/backend/.eslintrc.js @@ -23,16 +23,17 @@ module.exports = { root: true, overrides: [ { - files: ["./e2e-test/**/*"], + files: ["./e2e-test/**/*", "./src/db/migrations/**/*"], rules: { "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-call": "off" } } ], + rules: { "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-unsafe-enum-comparison": "off", diff --git a/backend/src/db/migrations/20240414192520_drop-role-roleid-project-membership.ts b/backend/src/db/migrations/20240414192520_drop-role-roleid-project-membership.ts new file mode 100644 index 000000000..2dd58c5d1 --- /dev/null +++ b/backend/src/db/migrations/20240414192520_drop-role-roleid-project-membership.ts @@ -0,0 +1,47 @@ +import { Knex } from "knex"; + +import { ProjectMembershipRole, TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const doesProjectRoleFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "role"); + const doesProjectRoleIdFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "roleId"); + await knex.schema.alterTable(TableName.ProjectMembership, (t) => { + if (doesProjectRoleFieldExist) t.dropColumn("roleId"); + if (doesProjectRoleIdFieldExist) t.dropColumn("role"); + }); + + const doesIdentityProjectRoleFieldExist = await knex.schema.hasColumn(TableName.IdentityProjectMembership, "role"); + const doesIdentityProjectRoleIdFieldExist = await knex.schema.hasColumn( + TableName.IdentityProjectMembership, + "roleId" + ); + await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => { + if (doesIdentityProjectRoleFieldExist) t.dropColumn("roleId"); + if (doesIdentityProjectRoleIdFieldExist) t.dropColumn("role"); + }); +} + +export async function down(knex: Knex): Promise { + const doesProjectRoleFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "role"); + const doesProjectRoleIdFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "roleId"); + await knex.schema.alterTable(TableName.ProjectMembership, (t) => { + if (!doesProjectRoleFieldExist) t.string("role").defaultTo(ProjectMembershipRole.Member); + if (!doesProjectRoleIdFieldExist) { + t.uuid("roleId"); + t.foreign("roleId").references("id").inTable(TableName.ProjectRoles); + } + }); + + const doesIdentityProjectRoleFieldExist = await knex.schema.hasColumn(TableName.IdentityProjectMembership, "role"); + const doesIdentityProjectRoleIdFieldExist = await knex.schema.hasColumn( + TableName.IdentityProjectMembership, + "roleId" + ); + await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => { + if (!doesIdentityProjectRoleFieldExist) t.string("role").defaultTo(ProjectMembershipRole.Member); + if (!doesIdentityProjectRoleIdFieldExist) { + t.uuid("roleId"); + t.foreign("roleId").references("id").inTable(TableName.ProjectRoles); + } + }); +} diff --git a/backend/src/db/schemas/identity-project-memberships.ts b/backend/src/db/schemas/identity-project-memberships.ts index 276c9581e..2f17c36d8 100644 --- a/backend/src/db/schemas/identity-project-memberships.ts +++ b/backend/src/db/schemas/identity-project-memberships.ts @@ -9,8 +9,6 @@ import { TImmutableDBKeys } from "./models"; export const IdentityProjectMembershipsSchema = z.object({ id: z.string().uuid(), - role: z.string(), - roleId: z.string().uuid().nullable().optional(), projectId: z.string(), identityId: z.string().uuid(), createdAt: z.date(), diff --git a/backend/src/db/schemas/project-memberships.ts b/backend/src/db/schemas/project-memberships.ts index 8576a318e..e522d6280 100644 --- a/backend/src/db/schemas/project-memberships.ts +++ b/backend/src/db/schemas/project-memberships.ts @@ -9,12 +9,10 @@ import { TImmutableDBKeys } from "./models"; export const ProjectMembershipsSchema = z.object({ id: z.string().uuid(), - role: z.string(), createdAt: z.date(), updatedAt: z.date(), userId: z.string().uuid(), - projectId: z.string(), - roleId: z.string().uuid().nullable().optional() + projectId: z.string() }); export type TProjectMemberships = z.infer; diff --git a/backend/src/db/seeds/3-project.ts b/backend/src/db/seeds/3-project.ts index d41efb71c..934130494 100644 --- a/backend/src/db/seeds/3-project.ts +++ b/backend/src/db/seeds/3-project.ts @@ -33,8 +33,7 @@ export async function seed(knex: Knex): Promise { const projectMembership = await knex(TableName.ProjectMembership) .insert({ projectId: project.id, - userId: seedData1.id, - role: ProjectMembershipRole.Admin + userId: seedData1.id }) .returning("*"); await knex(TableName.ProjectUserMembershipRole).insert({ diff --git a/backend/src/db/seeds/4-machine-identity.ts b/backend/src/db/seeds/4-machine-identity.ts index 618c47114..662232e02 100644 --- a/backend/src/db/seeds/4-machine-identity.ts +++ b/backend/src/db/seeds/4-machine-identity.ts @@ -78,8 +78,7 @@ export async function seed(knex: Knex): Promise { const identityProjectMembership = await knex(TableName.IdentityProjectMembership) .insert({ identityId: seedData1.machineIdentity.id, - projectId: seedData1.project.id, - role: ProjectMembershipRole.Admin + projectId: seedData1.project.id }) .returning("*"); diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index b89a3aa3d..d8114388e 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -72,7 +72,6 @@ export const permissionDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.GroupProjectMembershipRole)) .select( db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"), - // TODO(roll-forward-migration): remove this field when we drop this in next migration after a week db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("membershipUpdatedAt"), db.ref("projectId").withSchema(TableName.GroupProjectMembership), @@ -105,7 +104,6 @@ export const permissionDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.ProjectUserMembershipRole)) .select( db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"), - // TODO(roll-forward-migration): remove this field when we drop this in next migration after a week db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"), db.ref("projectId").withSchema(TableName.ProjectMembership), @@ -131,11 +129,10 @@ export const permissionDALFactory = (db: TDbClient) => { const permission = sqlNestRelationships({ data: docs, key: "projectId", - parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt, role }) => ({ + parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({ orgId, orgAuthEnforced, userId, - role, id: membershipId, projectId, createdAt: membershipCreatedAt, @@ -179,18 +176,10 @@ export const permissionDALFactory = (db: TDbClient) => { ? sqlNestRelationships({ data: groupDocs, key: "projectId", - parentMapper: ({ - orgId, - orgAuthEnforced, - membershipId, - membershipCreatedAt, - membershipUpdatedAt, - role - }) => ({ + parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({ orgId, orgAuthEnforced, userId, - role, id: membershipId, projectId, createdAt: membershipCreatedAt, @@ -270,7 +259,6 @@ export const permissionDALFactory = (db: TDbClient) => { .select( db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"), db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project - db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"), db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"), db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"), @@ -299,11 +287,10 @@ export const permissionDALFactory = (db: TDbClient) => { const permission = sqlNestRelationships({ data: docs, key: "membershipId", - parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField, orgId }) => ({ + parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, orgId }) => ({ id: membershipId, identityId, projectId, - role: oldRoleField, createdAt: membershipCreatedAt, updatedAt: membershipUpdatedAt, orgId, diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index 2b281e6fe..d99b4ec08 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -35,31 +35,28 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider }), response: { 200: z.object({ - memberships: ProjectMembershipsSchema.omit({ role: true }) - .merge( + memberships: ProjectMembershipsSchema.extend({ + user: UsersSchema.pick({ + email: true, + firstName: true, + lastName: true, + id: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + roles: z.array( z.object({ - user: UsersSchema.pick({ - email: true, - firstName: true, - lastName: true, - id: true - }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), - roles: z.array( - z.object({ - 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() - }) - ) + 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() }) ) + }) .omit({ createdAt: true, updatedAt: true }) .array() }) diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index d2306e9a2..280e52680 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -70,32 +70,29 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - users: ProjectMembershipsSchema.omit({ role: true }) - .merge( + users: ProjectMembershipsSchema.extend({ + user: UsersSchema.pick({ + email: true, + username: true, + firstName: true, + lastName: true, + id: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + roles: z.array( z.object({ - user: UsersSchema.pick({ - username: true, - email: true, - firstName: true, - lastName: true, - id: true - }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), - roles: z.array( - z.object({ - 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() - }) - ) + 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() }) ) + }) .omit({ createdAt: true, updatedAt: true }) .array() }) diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index 2da2492c0..18a1803ac 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -93,9 +93,7 @@ export const identityProjectServiceFactory = ({ const identityProjectMembership = await identityProjectDAL.create( { identityId, - projectId: project.id, - role: isCustomRole ? ProjectMembershipRole.Custom : role, - roleId: customRole?.id + projectId: project.id }, tx ); diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index 4431855ce..81ecd6da1 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -232,8 +232,7 @@ export const projectQueueFactory = ({ const projectMembership = await projectMembershipDAL.create( { projectId: project.id, - userId: ghostUser.user.id, - role: ProjectMembershipRole.Admin + userId: ghostUser.user.id }, tx ); diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 9e8d70020..008dac593 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -141,8 +141,7 @@ export const projectServiceFactory = ({ const projectMembership = await projectMembershipDAL.create( { userId: ghostUser.user.id, - projectId: project.id, - role: ProjectMembershipRole.Admin + projectId: project.id }, tx ); @@ -244,8 +243,7 @@ export const projectServiceFactory = ({ const userProjectMembership = await projectMembershipDAL.create( { projectId: project.id, - userId: user.id, - role: projectAdmin.projectRole + userId: user.id }, tx ); @@ -302,9 +300,7 @@ export const projectServiceFactory = ({ const identityProjectMembership = await identityProjectDAL.create( { identityId: actorId, - projectId: project.id, - role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin, - roleId: customRole?.id + projectId: project.id }, tx ); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index f07aeb190..764761098 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -66,6 +66,8 @@ services: environment: - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable command: npm run migration:latest + volumes: + - ./backend/src:/app/src backend: container_name: infisical-dev-api