diff --git a/backend-pg/scripts/create-backend-file.ts b/backend-pg/scripts/create-backend-file.ts index d6035a324f..907ebe2c47 100644 --- a/backend-pg/scripts/create-backend-file.ts +++ b/backend-pg/scripts/create-backend-file.ts @@ -12,6 +12,7 @@ Component List -------------- 1. Service component 2. DAL component +3. Router component `); const componentType = parseInt(prompt("Select a component: "), 10); @@ -88,6 +89,35 @@ export const ${dalName} = (db: TDbClient) => { return { }; }; +` + ); +} else if (componentType === 3) { + const name = prompt("Enter router name: "); + const version = prompt("Version number: "); + const pascalCase = name + .split("-") + .map((el) => `${el[0].toUpperCase()}${el.slice(1)}`) + .join(""); + writeFileSync( + path.join(__dirname, `../src/server/routes/v${Number(version)}/${name}-router.ts`), + `import { z } from "zod"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const register${pascalCase}Router = async (server: FastifyZodProvider) => { + server.route({ + url: "/", + method: "GET", + schema: { + params: z.object({}), + response: { + 200: z.object({}) + } + } + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => {} + }); +}; ` ); } diff --git a/backend-pg/src/@types/fastify.d.ts b/backend-pg/src/@types/fastify.d.ts index 6576953b9e..1060d16fc1 100644 --- a/backend-pg/src/@types/fastify.d.ts +++ b/backend-pg/src/@types/fastify.d.ts @@ -8,6 +8,10 @@ import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service"; import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service"; import { AuthMode } from "@app/services/auth/auth-signup-type"; import { ActorType } from "@app/services/auth/auth-type"; +import { TIdentityServiceFactory } from "@app/services/identity/identity-service"; +import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; +import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; +import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; import { TIntegrationServiceFactory } from "@app/services/integration/integration-service"; import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service"; @@ -21,10 +25,13 @@ import { TProjectRoleServiceFactory } from "@app/services/project-role/project-r import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-import-service"; +import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service"; +import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service"; import { TSuperAdminServiceFactory } from "@app/services/super-admin/super-admin-service"; import { TAuthTokenServiceFactory } from "@app/services/token/token-service"; import { TUserDalFactory } from "@app/services/user/user-dal"; import { TUserServiceFactory } from "@app/services/user/user-service"; +import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service"; declare module "fastify" { interface FastifyRequest { @@ -71,11 +78,18 @@ declare module "fastify" { projectKey: TProjectKeyServiceFactory; projectRole: TProjectRoleServiceFactory; secret: TSecretServiceFactory; + secretTag: TSecretTagServiceFactory; secretImport: TSecretImportServiceFactory; projectBot: TProjectBotServiceFactory; folder: TSecretFolderServiceFactory; integration: TIntegrationServiceFactory; integrationAuth: TIntegrationAuthServiceFactory; + webhook: TWebhookServiceFactory; + serviceToken: TServiceTokenServiceFactory; + identity: TIdentityServiceFactory; + identityAccessToken: TIdentityAccessTokenServiceFactory; + identityProject: TIdentityProjectServiceFactory; + identityUa: TIdentityUaServiceFactory; }; // this is exclusive use for middlewares in which we need to inject data diff --git a/backend-pg/src/ee/services/permission/permission-dal.ts b/backend-pg/src/ee/services/permission/permission-dal.ts index c6e4233efc..61d2310da1 100644 --- a/backend-pg/src/ee/services/permission/permission-dal.ts +++ b/backend-pg/src/ee/services/permission/permission-dal.ts @@ -1,43 +1,94 @@ import { TDbClient } from "@app/db"; -import { TableName, TOrgMemberships, TProjectMemberships } from "@app/db/schemas"; +import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { selectAllTableCols } from "@app/lib/knex"; export type TPermissionDalFactory = ReturnType; export const permissionDalFactory = (db: TDbClient) => { - const getOrgPermission = async ( - userId: string, - orgId: string - ): Promise<(TOrgMemberships & { permissions: string }) | undefined> => { - const membership = await db(TableName.OrgMembership) - .leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`) - .where("userId", userId) - .where(`${TableName.OrgMembership}.orgId`, orgId) - .select(`${TableName.OrgMembership}.*`, "permissions") - .first(); + const getOrgPermission = async (userId: string, orgId: string) => { + try { + const membership = await db(TableName.OrgMembership) + .leftJoin( + TableName.OrgRoles, + `${TableName.OrgMembership}.roleId`, + `${TableName.OrgRoles}.id` + ) + .where("userId", userId) + .where(`${TableName.OrgMembership}.orgId`, orgId) + .select("permissions") + .select(selectAllTableCols(TableName.OrgMembership)) + .first(); - return membership; + return membership; + } catch (error) { + throw new DatabaseError({ error, name: "GetOrgPermission" }); + } }; - const getProjectPermission = async ( - userId: string, - projectId: string - ): Promise<(TProjectMemberships & { permissions: string }) | undefined> => { - const membership = await db(TableName.ProjectMembership) - .leftJoin( - TableName.ProjectRoles, - `${TableName.ProjectMembership}.roleId`, - `${TableName.ProjectRoles}.id` - ) - .where("userId", userId) - .where(`${TableName.ProjectMembership}.projectId`, projectId) - .select(`${TableName.ProjectMembership}.*`, "permissions") - .first(); + const getOrgIdentityPermission = async (identityId: string, orgId: string) => { + try { + const membership = await db(TableName.IdentityOrgMembership) + .leftJoin( + TableName.OrgRoles, + `${TableName.IdentityOrgMembership}.roleId`, + `${TableName.OrgRoles}.id` + ) + .where("identityId", identityId) + .where(`${TableName.IdentityOrgMembership}.orgId`, orgId) + .select(selectAllTableCols(TableName.IdentityOrgMembership)) + .select("permissions") + .first(); + return membership; + } catch (error) { + throw new DatabaseError({ error, name: "GetOrgIdentityPermission" }); + } + }; - return membership; + const getProjectPermission = async (userId: string, projectId: string) => { + try { + const membership = await db(TableName.ProjectMembership) + .leftJoin( + TableName.ProjectRoles, + `${TableName.ProjectMembership}.roleId`, + `${TableName.ProjectRoles}.id` + ) + .where("userId", userId) + .where(`${TableName.ProjectMembership}.projectId`, projectId) + .select(selectAllTableCols(TableName.ProjectMembership)) + .select("permissions") + .first(); + + return membership; + } catch (error) { + throw new DatabaseError({ error, name: "GetProjectPermission" }); + } + }; + + const getProjectIdentityPermission = async (identityId: string, projectId: string) => { + try { + const membership = await db(TableName.IdentityProjectMembership) + .leftJoin( + TableName.ProjectRoles, + `${TableName.IdentityProjectMembership}.roleId`, + `${TableName.ProjectRoles}.id` + ) + .where("identityId", identityId) + .where(`${TableName.IdentityProjectMembership}.projectId`, projectId) + .select(selectAllTableCols(TableName.IdentityProjectMembership)) + .select("permissions") + .first(); + + return membership; + } catch (error) { + throw new DatabaseError({ error, name: "GetProjectIdentityPermission" }); + } }; return { getOrgPermission, - getProjectPermission + getOrgIdentityPermission, + getProjectPermission, + getProjectIdentityPermission }; }; diff --git a/backend-pg/src/ee/services/permission/permission-service.ts b/backend-pg/src/ee/services/permission/permission-service.ts index 9392efd56e..a025f450f8 100644 --- a/backend-pg/src/ee/services/permission/permission-service.ts +++ b/backend-pg/src/ee/services/permission/permission-service.ts @@ -19,8 +19,7 @@ import { projectAdminPermissions, projectMemberPermissions, projectNoAccessPermissions, - ProjectPermissionSet, - projectViewerPermission + ProjectPermissionSet } from "./project-permission"; type TPermissionServiceFactoryDep = { @@ -36,35 +35,101 @@ export const permissionServiceFactory = ({ orgRoleDal, projectRoleDal }: TPermissionServiceFactoryDep) => { + const buildOrgPermission = (role: string, permission?: unknown) => { + switch (role) { + case OrgMembershipRole.Admin: + return orgAdminPermissions; + case OrgMembershipRole.Member: + return orgMemberPermissions; + case OrgMembershipRole.NoAccess: + return orgNoAccessPermissions; + case OrgMembershipRole.Custom: + return createMongoAbility( + unpackRules>>( + permission as PackRule>>[] + ), + { + conditionsMatcher + } + ); + default: + throw new BadRequestError({ name: "OrgRoleInvalid", message: "Org role not found" }); + } + }; + + const buildProjectPermission = (role: string, permission?: unknown) => { + switch (role) { + case ProjectMembershipRole.Admin: + return projectAdminPermissions; + case ProjectMembershipRole.Member: + return projectMemberPermissions; + case ProjectMembershipRole.NoAccess: + return projectNoAccessPermissions; + case ProjectMembershipRole.Custom: + return createMongoAbility( + unpackRules>>( + permission as PackRule>>[] + ), + { + conditionsMatcher + } + ); + default: + throw new BadRequestError({ + name: "ProjectRoleInvalid", + message: "Project role not found" + }); + } + }; + /* * Get user permission in an organization * */ const getUserOrgPermission = async (userId: string, orgId: string) => { const membership = await permissionDal.getOrgPermission(userId, orgId); if (!membership) throw new UnauthorizedError({ name: "User not in org" }); - if (membership.role === "custom" && !membership.permissions) { + if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } + return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; + }; - if (membership.role === OrgMembershipRole.Admin) - return { permission: orgAdminPermissions, membership }; - if (membership.role === OrgMembershipRole.Member) - return { permission: orgMemberPermissions, membership }; - - if (membership.role === OrgMembershipRole.NoAccess) - return { permission: orgNoAccessPermissions, membership }; - if (membership.role === OrgMembershipRole.Custom) { - const permission = createMongoAbility( - // akhilmhdh: putting any due to ts incompatiable matching with string and the other - unpackRules>>(membership.permissions as any), - { - conditionsMatcher - } - ); - return { permission, membership }; + const getIdentityOrgPermission = async (identityId: string, orgId: string) => { + const membership = await permissionDal.getOrgIdentityPermission(identityId, orgId); + if (!membership) throw new UnauthorizedError({ name: "Identity not in org" }); + if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { + throw new BadRequestError({ name: "Custom permission not found" }); } + return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; + }; - throw new BadRequestError({ name: "Role missing", message: "User role not found" }); + const getOrgPermission = async (type: ActorType, id: string, orgId: string) => { + switch (type) { + case ActorType.USER: + return getUserOrgPermission(id, orgId); + case ActorType.IDENTITY: + return getIdentityOrgPermission(id, orgId); + default: + throw new UnauthorizedError({ + message: "Permission not defined", + name: "Get org permission" + }); + } + }; + + // instead of actor type this will fetch by role slug. meaning it can be the pre defined slugs like + // admin member or user defined ones like biller etc + const getOrgPermissionByRole = async (role: string, orgId: string) => { + const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole); + if (isCustomRole) { + const orgRole = await orgRoleDal.findOne({ slug: role, orgId }); + if (!orgRole) throw new BadRequestError({ message: "Role not found" }); + return { + permission: buildOrgPermission(OrgMembershipRole.Custom, orgRole.permissions), + role: orgRole + }; + } + return { permission: buildOrgPermission(role, []) }; }; // user permission for a project in an organization @@ -74,33 +139,30 @@ export const permissionServiceFactory = ({ if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } + return { + permission: buildProjectPermission(membership.role, membership.permissions), + membership + }; + }; - if (membership.role === ProjectMembershipRole.Admin) - return { permission: projectAdminPermissions, membership }; - if (membership.role === ProjectMembershipRole.Member) - return { permission: projectMemberPermissions, membership }; - if (membership.role === ProjectMembershipRole.Viewer) - return { permission: projectViewerPermission, membership }; - if (membership.role === ProjectMembershipRole.NoAccess) - return { permission: projectNoAccessPermissions, membership }; - if (membership.role === ProjectMembershipRole.Custom) { - const permission = createMongoAbility( - // akhilmhdh: putting any due to ts incompatiable matching with string and the other - unpackRules>>(membership.permissions as any), - { - conditionsMatcher - } - ); - return { permission, membership }; + const getIdentityProjectPermission = async (identityId: string, projectId: string) => { + const membership = await permissionDal.getProjectIdentityPermission(identityId, projectId); + if (!membership) throw new UnauthorizedError({ name: "Identity not in org" }); + if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { + throw new BadRequestError({ name: "Custom permission not found" }); } - - throw new BadRequestError({ name: "Role missing", message: "User role not found" }); + return { + permission: buildProjectPermission(membership.role, membership.permissions), + membership + }; }; const getProjectPermission = async (type: ActorType, id: string, projectId: string) => { switch (type) { case ActorType.USER: return getUserProjectPermission(id, projectId); + case ActorType.IDENTITY: + return getIdentityProjectPermission(id, projectId); default: throw new UnauthorizedError({ message: "Permission not defined", @@ -109,76 +171,19 @@ export const permissionServiceFactory = ({ } }; - const getOrgPermission = async (type: ActorType, id: string, orgId: string) => { - switch (type) { - case ActorType.USER: - return getUserOrgPermission(id, orgId); - default: - throw new UnauthorizedError({ - message: "Permission not defined", - name: "Get org permission" - }); - } - }; - - const getOrgPermissionByRole = async (role: string, orgId: string) => { - const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole); - if (isCustomRole) { - const orgRole = await orgRoleDal.findOne({ slug: role, orgId }); - if (!orgRole) throw new BadRequestError({ message: "Role not found" }); - return { - permission: createMongoAbility( - unpackRules>>( - (orgRole.permissions as PackRule>>[]) || [] - ), - { - conditionsMatcher - } - ), - role: orgRole - }; - } - switch (role) { - case OrgMembershipRole.Admin: - return { permission: orgAdminPermissions }; - case OrgMembershipRole.Member: - return { permission: orgMemberPermissions }; - case OrgMembershipRole.NoAccess: - return { permission: orgNoAccessPermissions }; - default: - throw new BadRequestError({ message: "Org role not found" }); - } - }; - const getProjectPermissionByRole = async (role: string, projectId: string) => { - const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole); + const isCustomRole = !Object.values(ProjectMembershipRole).includes( + role as ProjectMembershipRole + ); if (isCustomRole) { const projectRole = await projectRoleDal.findOne({ slug: role, projectId }); if (!projectRole) throw new BadRequestError({ message: "Role not found" }); return { - permission: createMongoAbility( - unpackRules>>( - (projectRole.permissions as PackRule< - RawRuleOf> - >[]) || [] - ), - { - conditionsMatcher - } - ), + permission: buildProjectPermission(ProjectMembershipRole.Custom, projectRole.permissions), role: projectRole }; } - switch (role) { - case ProjectMembershipRole.Admin: - return { permission: projectAdminPermissions }; - case ProjectMembershipRole.Member: - return { permission: projectMemberPermissions }; - case ProjectMembershipRole.NoAccess: - return { permission: projectNoAccessPermissions }; - default: - throw new BadRequestError({ message: "Org role not found" }); - } + return { permission: buildProjectPermission(role, []) }; }; return { @@ -187,6 +192,8 @@ export const permissionServiceFactory = ({ getUserProjectPermission, getProjectPermission, getOrgPermissionByRole, - getProjectPermissionByRole + getProjectPermissionByRole, + buildOrgPermission, + buildProjectPermission }; }; diff --git a/backend-pg/src/server/routes/index.ts b/backend-pg/src/server/routes/index.ts index 4469fadc63..54edd5580e 100644 --- a/backend-pg/src/server/routes/index.ts +++ b/backend-pg/src/server/routes/index.ts @@ -13,6 +13,16 @@ import { authPaswordServiceFactory } from "@app/services/auth/auth-password-serv import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service"; import { tokenDalFactory } from "@app/services/auth-token/auth-token-dal"; import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; +import { identityDalFactory } from "@app/services/identity/identity-dal"; +import { identityOrgDalFactory } from "@app/services/identity/identity-org-dal"; +import { identityServiceFactory } from "@app/services/identity/identity-service"; +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 { identityProjectDalFactory } from "@app/services/identity-project/identity-project-dal"; +import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; +import { identityUaClientSecretDalFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal"; +import { identityUaDalFactory } from "@app/services/identity-ua/identity-ua-dal"; +import { identityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; import { integrationDalFactory } from "@app/services/integration/integration-dal"; import { integrationServiceFactory } from "@app/services/integration/integration-service"; import { integrationAuthDalFactory } from "@app/services/integration-auth/integration-auth-dal"; @@ -43,11 +53,16 @@ import { secretFolderServiceFactory } from "@app/services/secret-folder/secret-f import { secretImportDalFactory } from "@app/services/secret-import/secret-import-dal"; import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service"; import { secretTagDalFactory } from "@app/services/secret-tag/secret-tag-dal"; +import { secretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service"; +import { serviceTokenDalFactory } from "@app/services/service-token/service-token-dal"; +import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service"; import { TSmtpService } from "@app/services/smtp/smtp-service"; import { superAdminDalFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminServiceFactory } from "@app/services/super-admin/super-admin-service"; import { userDalFactory } from "@app/services/user/user-dal"; import { userServiceFactory } from "@app/services/user/user-service"; +import { webhookDalFactory } from "@app/services/webhook/webhook-dal"; +import { webhookServiceFactory } from "@app/services/webhook/webhook-service"; import { injectIdentity } from "../plugins/auth/inject-identity"; import { injectPermission } from "../plugins/auth/inject-permission"; @@ -85,6 +100,16 @@ export const registerRoutes = async ( const integrationDal = integrationDalFactory(db); const integrationAuthDal = integrationAuthDalFactory(db); + const webhookDal = webhookDalFactory(db); + const serviceTokenDal = serviceTokenDalFactory(db); + + const identityDal = identityDalFactory(db); + const identityAccessTokenDal = identityAccessTokenDalFactory(db); + const identityOrgMembershipDal = identityOrgDalFactory(db); + const identityProjectDal = identityProjectDalFactory(db); + + const identityUaDal = identityUaDalFactory(db); + const identityUaClientSecretDal = identityUaClientSecretDalFactory(db); // ee db layer ops const permissionDal = permissionDalFactory(db); @@ -161,6 +186,7 @@ export const registerRoutes = async ( secretDal, secretTagDal }); + const secretTagService = secretTagServiceFactory({ secretTagDal, permissionService }); const folderService = secretFolderServiceFactory({ permissionService, folderDal, @@ -186,6 +212,37 @@ export const registerRoutes = async ( projectBotDal, projectBotService }); + const webhookService = webhookServiceFactory({ + permissionService, + webhookDal, + projectEnvDal + }); + const serviceTokenService = serviceTokenServiceFactory({ + projectEnvDal, + serviceTokenDal, + permissionService + }); + + const identityService = identityServiceFactory({ + permissionService, + identityDal, + identityOrgMembershipDal + }); + const identityAccessTokenService = identityAccessTokenServiceFactory({ identityAccessTokenDal }); + const identityProjectService = identityProjectServiceFactory({ + permissionService, + projectDal, + identityProjectDal, + identityOrgMembershipDal + }); + const identityUaService = identityUaServiceFactory({ + identityOrgMembershipDal, + permissionService, + identityDal, + identityAccessTokenDal, + identityUaClientSecretDal, + identityUaDal + }); await superAdminService.initServerCfg(); // inject all services @@ -206,11 +263,18 @@ export const registerRoutes = async ( projectEnv: projectEnvService, projectRole: projectRoleService, secret: secretService, + secretTag: secretTagService, folder: folderService, secretImport: secretImportService, projectBot: projectBotService, integration: integrationService, - integrationAuth: integrationAuthService + integrationAuth: integrationAuthService, + webhook: webhookService, + serviceToken: serviceTokenService, + identity: identityService, + identityAccessToken: identityAccessTokenService, + identityProject: identityProjectService, + identityUa: identityUaService }); server.decorate("store", { diff --git a/backend-pg/src/server/routes/sanitizedSchemas.ts b/backend-pg/src/server/routes/sanitizedSchemas.ts new file mode 100644 index 0000000000..dedc6e3baa --- /dev/null +++ b/backend-pg/src/server/routes/sanitizedSchemas.ts @@ -0,0 +1,15 @@ +import { IntegrationAuthsSchema } from "@app/db/schemas"; + +// sometimes the return data must be santizied to avoid leaking important values +// always prefer pick over omit in zod +export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({ + projectId: true, + integration: true, + teamId: true, + url: true, + namespace: true, + accountId: true, + metadata: true, + createdAt: true, + updatedAt: true +}); diff --git a/backend-pg/src/server/routes/v1/bot-router.ts b/backend-pg/src/server/routes/v1/bot-router.ts index c01418da63..4c6e07ffea 100644 --- a/backend-pg/src/server/routes/v1/bot-router.ts +++ b/backend-pg/src/server/routes/v1/bot-router.ts @@ -10,17 +10,16 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { method: "GET", schema: { params: z.object({ - workspaceId: z.string().trim() + projectId: z.string().trim() }), response: { 200: z.object({ - bot: ProjectBotsSchema.pick({ - name: true, - projectId: true, - isActive: true, - publicKey: true, - createdAt: true, - updatedAt: true + bot: ProjectBotsSchema.omit({ + iv: true, + encryptedPrivateKey: true, + tag: true, + algorithm: true, + keyEncoding: true }) }) } @@ -30,7 +29,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { const bot = await server.services.projectBot.findBotByProjectId({ actor: req.permission.type, actorId: req.permission.id, - projectId: req.params.workspaceId + projectId: req.params.projectId }); return { bot }; } @@ -54,13 +53,12 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - bot: ProjectBotsSchema.pick({ - name: true, - projectId: true, - isActive: true, - publicKey: true, - createdAt: true, - updatedAt: true + bot: ProjectBotsSchema.omit({ + iv: true, + encryptedPrivateKey: true, + tag: true, + algorithm: true, + keyEncoding: true }) }) } diff --git a/backend-pg/src/server/routes/v1/identity-access-token-router.ts b/backend-pg/src/server/routes/v1/identity-access-token-router.ts new file mode 100644 index 0000000000..82a92c708f --- /dev/null +++ b/backend-pg/src/server/routes/v1/identity-access-token-router.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; + +export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/token/renew", + method: "POST", + schema: { + body: z.object({ + accessToken: z.string().trim() + }), + response: { + 200: z.object({ + accessToken: z.string(), + expiresIn: z.number(), + accessTokenMaxTTL: z.number(), + tokenType: z.literal("Bearer") + }) + } + }, + handler: async (req) => { + const { accessToken, identityAccessToken } = + await server.services.identityAccessToken.renewAccessToken({ + accessToken: req.body.accessToken + }); + return { + accessToken, + tokenType: "Bearer" as const, + expiresIn: identityAccessToken.accessTokenTTL, + accessTokenMaxTTL: identityAccessToken.accessTokenMaxTTL + }; + } + }); +}; diff --git a/backend-pg/src/server/routes/v1/identity-router.ts b/backend-pg/src/server/routes/v1/identity-router.ts new file mode 100644 index 0000000000..998ed9b430 --- /dev/null +++ b/backend-pg/src/server/routes/v1/identity-router.ts @@ -0,0 +1,87 @@ +import { z } from "zod"; + +import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerIdentityRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + body: z.object({ + name: z.string().trim(), + organizationId: z.string().trim(), + role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess) + }), + response: { + 200: z.object({ + identity: IdentitiesSchema + }) + } + }, + handler: async (req) => { + const identity = await server.services.identity.createIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + ...req.body, + orgId: req.body.organizationId + }); + return { identity }; + } + }); + + server.route({ + method: "PATCH", + url: "/:identityId", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + identityId: z.string() + }), + body: z.object({ + name: z.string().trim().optional(), + role: z.string().trim().min(1).optional() + }), + response: { + 200: z.object({ + identity: IdentitiesSchema + }) + } + }, + handler: async (req) => { + const identity = await server.services.identity.updateIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + id: req.params.identityId, + ...req.body + }); + return { identity }; + } + }); + + server.route({ + method: "DELETE", + url: "/:identityId", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + identityId: z.string() + }), + response: { + 200: z.object({ + identity: IdentitiesSchema + }) + } + }, + handler: async (req) => { + const identity = await server.services.identity.deleteIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + id: req.params.identityId + }); + return { identity }; + } + }); +}; diff --git a/backend-pg/src/server/routes/v1/identity-ua.ts b/backend-pg/src/server/routes/v1/identity-ua.ts new file mode 100644 index 0000000000..ed7ab3f67c --- /dev/null +++ b/backend-pg/src/server/routes/v1/identity-ua.ts @@ -0,0 +1,264 @@ +import { z } from "zod"; + +import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({ + id: true, + createdAt: true, + updatedAt: true, + description: true, + clientSecretPrefix: true, + clientSecretNumUses: true, + clientSecretNumUsesLimit: true, + clientSecretTTL: true, + identityUAId: true, + isClientSecretRevoked: true +}); + +export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/universal-auth/login", + method: "POST", + schema: { + body: z.object({ + clientId: z.string().trim(), + clientSecret: z.string().trim() + }), + response: { + 200: z.object({ + accessToken: z.string(), + expiresIn: z.number(), + accessTokenMaxTTL: z.number(), + tokenType: z.literal("Bearer") + }) + } + }, + handler: async (req) => { + const { identityUa, accessToken } = await server.services.identityUa.login( + req.body.clientId, + req.body.clientSecret + ); + return { + accessToken, + tokenType: "Bearer" as const, + expiresIn: identityUa.accessTokenTTL, + accessTokenMaxTTL: identityUa.accessTokenMaxTTL + }; + } + }); + + server.route({ + url: "/universal-auth/identities/:identityId", + method: "POST", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + identityId: z.string().trim() + }), + body: z.object({ + clientSecretTrustedIps: z + .object({ + ipAddress: z.string().trim() + }) + .array() + .min(1) + .default([{ ipAddress: "0.0.0.0/0" }]), + accessTokenTrustedIps: z + .object({ + ipAddress: z.string().trim() + }) + .array() + .min(1) + .default([{ ipAddress: "0.0.0.0/0" }]), + accessTokenTTL: z + .number() + .int() + .min(1) + .refine((value) => value !== 0, { + message: "accessTokenTTL must have a non zero number" + }) + .default(2592000), + accessTokenMaxTTL: z + .number() + .int() + .refine((value) => value !== 0, { + message: "accessTokenMaxTTL must have a non zero number" + }) + .default(2592000), // 30 days + accessTokenNumUsesLimit: z.number().int().min(0).default(0) + }), + response: { + 200: z.object({ + identityUniversalAuth: IdentityUniversalAuthsSchema + }) + } + }, + handler: async (req) => { + const identityUniversalAuth = await server.services.identityUa.attachUa({ + actor: req.permission.type, + actorId: req.permission.id, + ...req.body, + identityId: req.params.identityId + }); + return { identityUniversalAuth }; + } + }); + + server.route({ + url: "/universal-auth/identities/:identityId", + method: "PATCH", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + identityId: z.string() + }), + body: z.object({ + clientSecretTrustedIps: z + .object({ + ipAddress: z.string().trim() + }) + .array() + .min(1) + .optional(), + accessTokenTrustedIps: z + .object({ + ipAddress: z.string().trim() + }) + .array() + .min(1) + .optional(), + accessTokenTTL: z.number().int().min(0).optional(), + accessTokenNumUsesLimit: z.number().int().min(0).optional(), + accessTokenMaxTTL: z + .number() + .int() + .refine((value) => value !== 0, { + message: "accessTokenMaxTTL must have a non zero number" + }) + .optional() + }), + response: { + 200: z.object({ + identityUniversalAuth: IdentityUniversalAuthsSchema + }) + } + }, + handler: async (req) => { + const identityUniversalAuth = await server.services.identityUa.updateUa({ + actor: req.permission.type, + actorId: req.permission.id, + ...req.body, + identityId: req.params.identityId + }); + return { identityUniversalAuth }; + } + }); + + server.route({ + url: "/universal-auth/identities/:identityId", + method: "GET", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + identityId: z.string() + }), + response: { + 200: z.object({ + identityUniversalAuth: IdentityUniversalAuthsSchema + }) + } + }, + handler: async (req) => { + const identityUniversalAuth = await server.services.identityUa.getIdentityUa({ + actor: req.permission.type, + actorId: req.permission.id, + identityId: req.params.identityId + }); + return { identityUniversalAuth }; + } + }); + + server.route({ + url: "/universal-auth/identities/:identityId/client-secrets", + method: "POST", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + identityId: z.string() + }), + body: z.object({ + description: z.string().trim().default(""), + numUsesLimit: z.number().min(0).default(0), + ttl: z.number().min(0).default(0) + }), + response: { + 200: z.object({ + clientSecret: z.string(), + clientSecretData: sanitizedClientSecretSchema + }) + } + }, + handler: async (req) => { + const { clientSecret, clientSecretData } = + await server.services.identityUa.createUaClientSecret({ + actor: req.permission.type, + actorId: req.permission.id, + identityId: req.params.identityId, + ...req.body + }); + return { clientSecret, clientSecretData }; + } + }); + + server.route({ + url: "/universal-auth/identities/:identityId/client-secrets", + method: "GET", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + identityId: z.string() + }), + response: { + 200: z.object({ + clientSecretData: sanitizedClientSecretSchema.array() + }) + } + }, + handler: async (req) => { + const clientSecretData = await server.services.identityUa.getUaClientSecrets({ + actor: req.permission.type, + actorId: req.permission.id, + identityId: req.params.identityId + }); + return { clientSecretData }; + } + }); + + server.route({ + url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke", + method: "POST", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + identityId: z.string(), + clientSecretId: z.string() + }), + response: { + 200: z.object({ + clientSecretData: sanitizedClientSecretSchema + }) + } + }, + handler: async (req) => { + const clientSecretData = await server.services.identityUa.revokeUaClientSecret({ + actor: req.permission.type, + actorId: req.permission.id, + identityId: req.params.identityId, + clientSecretId: req.params.clientSecretId + }); + return { clientSecretData }; + } + }); +}; diff --git a/backend-pg/src/server/routes/v1/index.ts b/backend-pg/src/server/routes/v1/index.ts index 7af65d176f..6a17935ff9 100644 --- a/backend-pg/src/server/routes/v1/index.ts +++ b/backend-pg/src/server/routes/v1/index.ts @@ -1,6 +1,9 @@ import { registerAdminRouter } from "./admin-router"; import { registerAuthRoutes } from "./auth-router"; import { registerProjectBotRouter } from "./bot-router"; +import { registerIdentityAccessTokenRouter } from "./identity-access-token-router"; +import { registerIdentityRouter } from "./identity-router"; +import { registerIdentityUaRouter } from "./identity-ua"; import { registerIntegrationAuthRouter } from "./integration-auth-router"; import { registerIntegrationRouter } from "./integration-router"; import { registerInviteOrgRouter } from "./invite-org-router"; @@ -12,13 +15,22 @@ import { registerProjectMembershipRouter } from "./project-membership-router"; import { registerProjectRouter } from "./project-router"; import { registerSecretFolderRouter } from "./secret-folder-router"; import { registerSecretImportRouter } from "./secret-import-router"; +import { registerSecretTagRouter } from "./secret-tag-router"; import { registerSsoRouter } from "./sso-router"; import { registerUserActionRouter } from "./user-action-router"; import { registerUserRouter } from "./user-router"; +import { registerWebhookRouter } from "./webhook-router"; export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerSsoRouter, { prefix: "/sso" }); - await server.register(registerAuthRoutes, { prefix: "/auth" }); + await server.register( + async (authServer) => { + await authServer.register(registerAuthRoutes); + await authServer.register(registerIdentityUaRouter); + await authServer.register(registerIdentityAccessTokenRouter); + }, + { prefix: "/auth" } + ); await server.register(registerPasswordRouter, { prefix: "/password" }); await server.register(registerOrgRouter, { prefix: "/organization" }); await server.register(registerAdminRouter, { prefix: "/admin" }); @@ -34,6 +46,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await projectServer.register(registerProjectEnvRouter); await projectServer.register(registerProjectKeyRouter); await projectServer.register(registerProjectMembershipRouter); + await projectServer.register(registerSecretTagRouter); }, { prefix: "/workspace" } ); @@ -41,4 +54,6 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerProjectBotRouter, { prefix: "/bot" }); await server.register(registerIntegrationRouter, { prefix: "/integration" }); await server.register(registerIntegrationAuthRouter, { prefix: "/integration-auth" }); + await server.register(registerWebhookRouter, { prefix: "/webhooks" }); + await server.register(registerIdentityRouter, { prefix: "/identities" }); }; diff --git a/backend-pg/src/server/routes/v1/integration-auth-router.ts b/backend-pg/src/server/routes/v1/integration-auth-router.ts index b3266b58ae..ed8397946d 100644 --- a/backend-pg/src/server/routes/v1/integration-auth-router.ts +++ b/backend-pg/src/server/routes/v1/integration-auth-router.ts @@ -1,9 +1,10 @@ import { z } from "zod"; -import { IntegrationAuthsSchema } from "@app/db/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +import { integrationAuthPubSchema } from "../sanitizedSchemas"; + export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => { server.route({ url: "/integration-options", @@ -32,18 +33,6 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) } }); - const integrationPublicSchema = IntegrationAuthsSchema.pick({ - projectId: true, - integration: true, - teamId: true, - url: true, - namespace: true, - accountId: true, - metadata: true, - createdAt: true, - updatedAt: true - }); - server.route({ url: "/:integrationAuthId", method: "GET", @@ -54,7 +43,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }), response: { 200: z.object({ - integrationAuth: integrationPublicSchema + integrationAuth: integrationAuthPubSchema }) } }, @@ -78,7 +67,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }), response: { 200: z.object({ - integrationAuth: integrationPublicSchema + integrationAuth: integrationAuthPubSchema }) } }, @@ -108,7 +97,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }), response: { 200: z.object({ - integrationAuth: integrationPublicSchema + integrationAuth: integrationAuthPubSchema }) } }, diff --git a/backend-pg/src/server/routes/v1/project-router.ts b/backend-pg/src/server/routes/v1/project-router.ts index cf3e7fb214..08d30ecfe8 100644 --- a/backend-pg/src/server/routes/v1/project-router.ts +++ b/backend-pg/src/server/routes/v1/project-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { + IntegrationsSchema, ProjectKeysSchema, ProjectMembershipsSchema, ProjectsSchema, @@ -10,6 +11,9 @@ import { import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +import { integrationAuthPubSchema } from "../sanitizedSchemas"; +import { sanitizedServiceTokenSchema } from "../v2/service-token-router"; + const projectWithEnv = ProjectsSchema.merge( z.object({ environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array() @@ -264,4 +268,76 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { return { invitee, latestKey }; } }); + + server.route({ + url: "/:workspaceId/integrations", + method: "GET", + schema: { + params: z.object({ + workspaceId: z.string().trim() + }), + response: { + 200: z.object({ + integrations: IntegrationsSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const integrations = await server.services.integration.listIntegrationByProject({ + actorId: req.permission.id, + actor: req.permission.type, + projectId: req.params.workspaceId + }); + return { integrations }; + } + }); + + server.route({ + url: "/:workspaceId/authorizations", + method: "GET", + schema: { + params: z.object({ + workspaceId: z.string().trim() + }), + response: { + 200: z.object({ + authorizations: integrationAuthPubSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const authorizations = await server.services.integration.listIntegrationByProject({ + actorId: req.permission.id, + actor: req.permission.type, + projectId: req.params.workspaceId + }); + return { authorizations }; + } + }); + + server.route({ + url: "/:workspaceId/service-token-data", + method: "GET", + schema: { + params: z.object({ + workspaceId: z.string().trim() + }), + response: { + 200: z.object({ + serviceTokenData: sanitizedServiceTokenSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const serviceTokenData = await server.services.serviceToken.getProjectServiceTokens({ + actorId: req.permission.id, + actor: req.permission.type, + projectId: req.params.workspaceId + }); + return { serviceTokenData }; + } + }); }; diff --git a/backend-pg/src/server/routes/v1/secret-tag-router.ts b/backend-pg/src/server/routes/v1/secret-tag-router.ts new file mode 100644 index 0000000000..3cafb11d26 --- /dev/null +++ b/backend-pg/src/server/routes/v1/secret-tag-router.ts @@ -0,0 +1,86 @@ +import { z } from "zod"; + +import { SecretTagsSchema } from "@app/db/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerSecretTagRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/:projectId/tags", + method: "GET", + schema: { + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.object({ + workspaceTags: SecretTagsSchema.array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const workspaceTags = await server.services.secretTag.getProjectTags({ + actor: req.permission.type, + actorId: req.permission.id, + projectId: req.params.projectId + }); + return { workspaceTags }; + } + }); + + server.route({ + url: "/:projectId/tags", + method: "POST", + schema: { + params: z.object({ + projectId: z.string().trim() + }), + body: z.object({ + name: z.string().trim(), + slug: z.string().trim(), + color: z.string() + }), + response: { + 200: z.object({ + workspaceTag: SecretTagsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const workspaceTag = await server.services.secretTag.createTag({ + actor: req.permission.type, + actorId: req.permission.id, + projectId: req.params.projectId, + ...req.body + }); + return { workspaceTag }; + } + }); + + server.route({ + url: "/:projectId/tags/:tagId", + method: "DELETE", + schema: { + params: z.object({ + projectId: z.string().trim(), + tagId: z.string().trim() + }), + response: { + 200: z.object({ + workspaceTag: SecretTagsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const workspaceTag = await server.services.secretTag.deleteTag({ + actor: req.permission.type, + actorId: req.permission.id, + id: req.params.tagId + }); + return { workspaceTag }; + } + }); +}; diff --git a/backend-pg/src/server/routes/v1/webhook-router.ts b/backend-pg/src/server/routes/v1/webhook-router.ts new file mode 100644 index 0000000000..f1146aa42a --- /dev/null +++ b/backend-pg/src/server/routes/v1/webhook-router.ts @@ -0,0 +1,155 @@ +import { z } from "zod"; + +import { WebhooksSchema } from "@app/db/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const sanitizedWebhookSchema = WebhooksSchema.omit({ + encryptedSecretKey: true, + iv: true, + tag: true, + algorithm: true, + keyEncoding: true, +}).merge( + z.object({ + projectId:z.string(), + environment: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }) + }) +); + +export const registerWebhookRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + body: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim(), + webhookUrl: z.string().url().trim(), + webhookSecretKey: z.string().trim().optional(), + secretPath: z.string().trim().default("/") + }), + response: { + 200: z.object({ + message: z.string(), + webhook: sanitizedWebhookSchema + }) + } + }, + handler: async (req) => { + const webhook = await server.services.webhook.createWebhook({ + actor: req.permission.type, + actorId: req.permission.id, + projectId: req.body.workspaceId, + ...req.body + }); + return { message: "Successfully created webhook", webhook }; + } + }); + + server.route({ + method: "PATCH", + url: "/:webhookId", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + webhookId: z.string().trim() + }), + body: z.object({ + isDisabled: z.boolean().default(false) + }), + response: { + 200: z.object({ + message: z.string(), + webhook: sanitizedWebhookSchema + }) + } + }, + handler: async (req) => { + const webhook = await server.services.webhook.updateWebhook({ + actor: req.permission.type, + actorId: req.permission.id, + id: req.params.webhookId, + isDisabled: req.body.isDisabled + }); + return { message: "Successfully updated webhook", webhook }; + } + }); + + server.route({ + method: "DELETE", + url: "/:webhookId", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + webhookId: z.string().trim() + }) + }, + handler: async (req) => { + const webhook = await server.services.webhook.deleteWebhook({ + actor: req.permission.type, + actorId: req.permission.id, + id: req.params.webhookId + }); + return { message: "Successfully deleted webhook", webhook }; + } + }); + + server.route({ + method: "POST", + url: "/:webhookId/test", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + webhookId: z.string().trim() + }), + response: { + 200: z.object({ + message: z.string(), + webhook: sanitizedWebhookSchema + }) + } + }, + handler: async (req) => { + const webhook = await server.services.webhook.testWebhook({ + actor: req.permission.type, + actorId: req.permission.id, + id: req.params.webhookId + }); + return { message: "Successfully tested webhook", webhook }; + } + }); + + server.route({ + method: "GET", + url: "/", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + querystring: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim().optional(), + secretPath: z.string().trim().optional() + }), + response: { + 200: z.object({ + message: z.string(), + webhooks: sanitizedWebhookSchema.array() + }) + } + }, + handler: async (req) => { + const webhooks = await server.services.webhook.listWebhooks({ + actor: req.permission.type, + actorId: req.permission.id, + ...req.query, + projectId: req.query.workspaceId + }); + return { message: "Successfully fetched webhook", webhooks }; + } + }); +}; diff --git a/backend-pg/src/server/routes/v2/identity-org-router.ts b/backend-pg/src/server/routes/v2/identity-org-router.ts new file mode 100644 index 0000000000..f1beb4e86a --- /dev/null +++ b/backend-pg/src/server/routes/v2/identity-org-router.ts @@ -0,0 +1,42 @@ +import { z } from "zod"; + +import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "GET", + url: "/:orgId/identity-memberships", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + orgId: z.string().trim() + }), + response: { + 200: z.object({ + identityMemberships: IdentityOrgMembershipsSchema.merge( + z.object({ + customRole: OrgRolesSchema.pick({ + id: true, + name: true, + slug: true, + permissions: true, + description: true + }).optional(), + identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }) + }) + ).array() + }) + } + }, + handler: async (req) => { + const identityMemberships = await server.services.identity.listOrgIdentities({ + actor: req.permission.type, + actorId: req.permission.id, + orgId: req.params.orgId + }); + return { identityMemberships }; + } + }); +}; diff --git a/backend-pg/src/server/routes/v2/identity-project-router.ts b/backend-pg/src/server/routes/v2/identity-project-router.ts new file mode 100644 index 0000000000..ea797e0cb4 --- /dev/null +++ b/backend-pg/src/server/routes/v2/identity-project-router.ts @@ -0,0 +1,133 @@ +import { z } from "zod"; + +import { + IdentitiesSchema, + IdentityProjectMembershipsSchema, + ProjectMembershipRole, + ProjectRolesSchema +} from "@app/db/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerIdentityProjectRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/:projectId/identity-memberships/:identityId", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + projectId: z.string().trim(), + identityId: z.string().trim() + }), + body: z.object({ + role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess) + }), + response: { + 200: z.object({ + identityMembership: IdentityProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const identityMembership = await server.services.identityProject.createProjectIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + identityId: req.params.identityId, + projectId: req.params.projectId, + role: req.body.role + }); + return { identityMembership }; + } + }); + + server.route({ + method: "PATCH", + url: "/:projectId/identity-memberships/:identityId", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + projectId: z.string().trim(), + identityId: z.string().trim() + }), + body: z.object({ + role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess) + }), + response: { + 200: z.object({ + identityMembership: IdentityProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const identityMembership = await server.services.identityProject.updateProjectIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + identityId: req.params.identityId, + projectId: req.params.projectId, + role: req.body.role + }); + return { identityMembership }; + } + }); + + server.route({ + method: "DELETE", + url: "/:projectId/identity-memberships/:identityId", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + projectId: z.string().trim(), + identityId: z.string().trim() + }), + response: { + 200: z.object({ + identityMembership: IdentityProjectMembershipsSchema + }) + } + }, + handler: async (req) => { + const identityMembership = await server.services.identityProject.deleteProjectIdentity({ + actor: req.permission.type, + actorId: req.permission.id, + identityId: req.params.identityId, + projectId: req.params.projectId + }); + return { identityMembership }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/identity-memberships", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.object({ + identityMemberships: IdentityProjectMembershipsSchema.merge( + z.object({ + customRole: ProjectRolesSchema.pick({ + id: true, + name: true, + slug: true, + permissions: true, + description: true + }).optional(), + identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }) + }) + ).array() + }) + } + }, + handler: async (req) => { + const identityMemberships = await server.services.identityProject.listProjectIdentities({ + actor: req.permission.type, + actorId: req.permission.id, + projectId: req.params.projectId + }); + return { identityMemberships }; + } + }); +}; diff --git a/backend-pg/src/server/routes/v2/index.ts b/backend-pg/src/server/routes/v2/index.ts index 32661105f1..a73f9de528 100644 --- a/backend-pg/src/server/routes/v2/index.ts +++ b/backend-pg/src/server/routes/v2/index.ts @@ -1,13 +1,26 @@ +import { registerIdentityOrgRouter } from "./identity-org-router"; +import { registerIdentityProjectRouter } from "./identity-project-router"; import { registerMfaRouter } from "./mfa-router"; +import { registerOrgRouter } from "./organization-router"; import { registerProjectRouter } from "./project-router"; +import { registerServiceTokenRouter } from "./service-token-router"; import { registerUserRouter } from "./user-router"; export const registerV2Routes = async (server: FastifyZodProvider) => { await server.register(registerMfaRouter, { prefix: "/auth" }); await server.register(registerUserRouter, { prefix: "/users" }); + await server.register(registerServiceTokenRouter, { prefix: "/service-token" }); + await server.register( + async (orgRouter) => { + await orgRouter.register(registerOrgRouter); + await orgRouter.register(registerIdentityOrgRouter); + }, + { prefix: "/organizations" } + ); await server.register( async (projectServer) => { projectServer.register(registerProjectRouter); + projectServer.register(registerIdentityProjectRouter); }, { prefix: "/workspace" } ); diff --git a/backend-pg/src/server/routes/v2/service-token-router.ts b/backend-pg/src/server/routes/v2/service-token-router.ts new file mode 100644 index 0000000000..6531e16e0a --- /dev/null +++ b/backend-pg/src/server/routes/v2/service-token-router.ts @@ -0,0 +1,98 @@ +import { z } from "zod"; + +import { ServiceTokensSchema } from "@app/db/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const sanitizedServiceTokenSchema = ServiceTokensSchema.omit({ + secretHash: true, + encryptedKey: true, + iv: true, + tag: true +}); + +export const registerServiceTokenRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/", + method: "GET", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + serviceTokenId: z.string().trim() + }), + response: { + 200: sanitizedServiceTokenSchema + } + }, + handler: async (req) => { + const serviceTokenData = await server.services.serviceToken.getServiceToken({ + actorId: req.permission.id, + actor: req.permission.type + }); + return serviceTokenData; + } + }); + + server.route({ + url: "/", + method: "POST", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + body: z.object({ + name: z.string().trim(), + workspaceId: z.string().trim(), + scopes: z + .object({ + environment: z.string().trim(), + secretPath: z.string().trim() + }) + .array() + .min(1), + encryptedKey: z.string().trim(), + iv: z.string().trim(), + tag: z.string().trim(), + expiresIn: z.number(), + permissions: z.enum(["read", "write"]).array() + }), + response: { + 200: z.object({ + serviceToken: z.string(), + serviceTokenData: sanitizedServiceTokenSchema + }) + } + }, + handler: async (req) => { + const { serviceToken, token } = await server.services.serviceToken.createServiceToken({ + actorId: req.permission.id, + actor: req.permission.type, + ...req.body, + projectId: req.body.workspaceId + }); + return { serviceToken: token, serviceTokenData: serviceToken }; + } + }); + + server.route({ + url: "/:serviceTokenId", + method: "DELETE", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + serviceTokenId: z.string().trim() + }), + response: { + 200: z.object({ + serviceTokenData: sanitizedServiceTokenSchema + }) + } + }, + handler: async (req) => { + const serviceTokenData = await server.services.serviceToken.deleteServiceToken({ + actorId: req.permission.id, + actor: req.permission.type, + id: req.params.serviceTokenId + }); + return { serviceTokenData }; + } + }); +}; diff --git a/backend-pg/src/services/auth/auth-type.ts b/backend-pg/src/services/auth/auth-type.ts index dc9840d96c..df4ca4523f 100644 --- a/backend-pg/src/services/auth/auth-type.ts +++ b/backend-pg/src/services/auth/auth-type.ts @@ -25,7 +25,8 @@ export enum AuthMode { SERVICE_TOKEN = "serviceToken", SERVICE_ACCESS_TOKEN = "serviceAccessToken", API_KEY = "apiKey", - API_KEY_V2 = "apiKeyV2" + API_KEY_V2 = "apiKeyV2", + IDENTITY_ACCESS_TOKEN = "identityAccessToken" } export enum ActorType { // would extend to AWS, Azure, ... diff --git a/backend-pg/src/services/identity-access-token/identity-access-token-service.ts b/backend-pg/src/services/identity-access-token/identity-access-token-service.ts index f66e3b7e7b..4e4d910f39 100644 --- a/backend-pg/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend-pg/src/services/identity-access-token/identity-access-token-service.ts @@ -82,11 +82,14 @@ export const identityAccessTokenServiceFactory = ({ }); } - await identityAccessTokenDal.updateById(identityAccessToken.id, { - accessTokenLastRenewedAt: new Date() - }); + const updatedIdentityAccessToken = await identityAccessTokenDal.updateById( + identityAccessToken.id, + { + accessTokenLastRenewedAt: new Date() + } + ); - return identityAccessTokenDal; + return { accessToken, identityAccessToken: updatedIdentityAccessToken }; }; return { renewAccessToken }; diff --git a/backend-pg/src/services/identity-project/identity-project-dal.ts b/backend-pg/src/services/identity-project/identity-project-dal.ts index 7622cc6dff..38bd179579 100644 --- a/backend-pg/src/services/identity-project/identity-project-dal.ts +++ b/backend-pg/src/services/identity-project/identity-project-dal.ts @@ -1,10 +1,74 @@ +import { Knex } from "knex"; + import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; -import { ormify } from "@app/lib/knex"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify, selectAllTableCols } from "@app/lib/knex"; export type TIdentityProjectDalFactory = ReturnType; export const identityProjectDalFactory = (db: TDbClient) => { const identityProjectOrm = ormify(db, TableName.IdentityProjectMembership); - return identityProjectOrm; + + const findByProjectId = async (projectId: string, tx?: Knex) => { + try { + const docs = await (tx || db)(TableName.IdentityProjectMembership) + .where(`${TableName.IdentityProjectMembership}.projectId`, projectId) + .join( + TableName.Identity, + `${TableName.IdentityProjectMembership}.identityId`, + `${TableName.Identity}.id` + ) + .leftJoin( + TableName.ProjectRoles, + `${TableName.IdentityProjectMembership}.roleId`, + `${TableName.ProjectRoles}.id` + ) + .select(selectAllTableCols(TableName.IdentityProjectMembership)) + // cr stands for custom role + .select(db.ref("id").as("crId").withSchema(TableName.ProjectRoles)) + .select(db.ref("name").as("crName").withSchema(TableName.ProjectRoles)) + .select(db.ref("slug").as("crSlug").withSchema(TableName.ProjectRoles)) + .select(db.ref("description").as("crDescription").withSchema(TableName.ProjectRoles)) + .select(db.ref("permissions").as("crPermission").withSchema(TableName.ProjectRoles)) + .select(db.ref("permissions").as("crPermission").withSchema(TableName.ProjectRoles)) + .select(db.ref("id").as("identityId").withSchema(TableName.Identity)) + .select(db.ref("name").as("identityName").withSchema(TableName.Identity)) + .select(db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity)); + return docs.map( + ({ + crId, + crDescription, + crSlug, + crPermission, + crName, + identityId, + identityName, + identityAuthMethod, + ...el + }) => ({ + ...el, + identityId, + identity: { + id: identityId, + name: identityName, + authMethod: identityAuthMethod + }, + customRole: el.roleId + ? { + id: crId, + name: crName, + slug: crSlug, + permissions: crPermission, + description: crDescription + } + : undefined + }) + ); + } catch (error) { + throw new DatabaseError({ error, name: "FindByProjectId" }); + } + }; + + return { ...identityProjectOrm, findByProjectId }; }; diff --git a/backend-pg/src/services/identity-project/identity-project-service.ts b/backend-pg/src/services/identity-project/identity-project-service.ts index 4effd7420e..9b682c7d42 100644 --- a/backend-pg/src/services/identity-project/identity-project-service.ts +++ b/backend-pg/src/services/identity-project/identity-project-service.ts @@ -138,9 +138,10 @@ export const identityProjectServiceFactory = ({ const deleteProjectIdentity = async ({ identityId, actorId, - actor + actor, + projectId }: TDeleteProjectIdentityDTO) => { - const identityProjectMembership = await identityProjectDal.findById(identityId); + const identityProjectMembership = await identityProjectDal.findOne({ identityId, projectId }); if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` }); @@ -173,7 +174,7 @@ export const identityProjectServiceFactory = ({ ProjectPermissionSub.Identity ); - const identityMemberhips = await identityProjectDal.find({ projectId }); + const identityMemberhips = await identityProjectDal.findByProjectId(projectId); return identityMemberhips; }; diff --git a/backend-pg/src/services/identity-project/identity-project-types.ts b/backend-pg/src/services/identity-project/identity-project-types.ts index 5e9e972c13..71e048c199 100644 --- a/backend-pg/src/services/identity-project/identity-project-types.ts +++ b/backend-pg/src/services/identity-project/identity-project-types.ts @@ -12,6 +12,6 @@ export type TUpdateProjectIdentityDTO = { export type TDeleteProjectIdentityDTO = { identityId: string; -} & Omit; +} & TProjectPermission; export type TListProjectIdentityDTO = TProjectPermission; diff --git a/backend-pg/src/services/universal-auth/ua-client-secret-dal.ts b/backend-pg/src/services/identity-ua/identity-ua-client-secret-dal.ts similarity index 81% rename from backend-pg/src/services/universal-auth/ua-client-secret-dal.ts rename to backend-pg/src/services/identity-ua/identity-ua-client-secret-dal.ts index ac4c220c37..c7b0a8fa85 100644 --- a/backend-pg/src/services/universal-auth/ua-client-secret-dal.ts +++ b/backend-pg/src/services/identity-ua/identity-ua-client-secret-dal.ts @@ -5,9 +5,9 @@ import { TableName } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify } from "@app/lib/knex"; -export type TUaClientSecretDalFactory = ReturnType; +export type TIdentityUaClientSecretDalFactory = ReturnType; -export const uaClientSecretDalFactory = (db: TDbClient) => { +export const identityUaClientSecretDalFactory = (db: TDbClient) => { const uaClientSecretOrm = ormify(db, TableName.IdentityUaClientSecret); const incrementUsage = async (id: string, tx?: Knex) => { diff --git a/backend-pg/src/services/universal-auth/universal-auth-dal.ts b/backend-pg/src/services/identity-ua/identity-ua-dal.ts similarity index 61% rename from backend-pg/src/services/universal-auth/universal-auth-dal.ts rename to backend-pg/src/services/identity-ua/identity-ua-dal.ts index 59044de24c..4cf1920d63 100644 --- a/backend-pg/src/services/universal-auth/universal-auth-dal.ts +++ b/backend-pg/src/services/identity-ua/identity-ua-dal.ts @@ -2,9 +2,9 @@ import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; import { ormify } from "@app/lib/knex"; -export type TUniversalAuthDalFactory = ReturnType; +export type TIdentityUaDalFactory = ReturnType; -export const universalAuthDalFactory = (db: TDbClient) => { +export const identityUaDalFactory = (db: TDbClient) => { const universalAuthOrm = ormify(db, TableName.IdentityUniversalAuth); return universalAuthOrm; diff --git a/backend-pg/src/services/universal-auth/universal-auth-service.ts b/backend-pg/src/services/identity-ua/identity-ua-service.ts similarity index 85% rename from backend-pg/src/services/universal-auth/universal-auth-service.ts rename to backend-pg/src/services/identity-ua/identity-ua-service.ts index a41c96a740..f4e6b9944d 100644 --- a/backend-pg/src/services/universal-auth/universal-auth-service.ts +++ b/backend-pg/src/services/identity-ua/identity-ua-service.ts @@ -19,8 +19,8 @@ import { ActorType, AuthTokenType } from "../auth/auth-type"; import { TIdentityDalFactory } from "../identity/identity-dal"; import { TIdentityOrgDalFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDalFactory } from "../identity-access-token/identity-access-token-dal"; -import { TUaClientSecretDalFactory } from "./ua-client-secret-dal"; -import { TUniversalAuthDalFactory } from "./universal-auth-dal"; +import { TIdentityUaClientSecretDalFactory } from "./identity-ua-client-secret-dal"; +import { TIdentityUaDalFactory } from "./identity-ua-dal"; import { TAttachUaDTO, TCreateUaClientSecretDTO, @@ -28,33 +28,33 @@ import { TGetUaDTO, TRevokeUaClientSecretDTO, TUpdateUaDTO -} from "./universal-auth-types"; +} from "./identity-ua-types"; -type TUniversalAuthServiceFactoryDep = { - universalAuthDal: TUniversalAuthDalFactory; - uaClientSecretDal: TUaClientSecretDalFactory; +type TIdentityUaServiceFactoryDep = { + identityUaDal: TIdentityUaDalFactory; + identityUaClientSecretDal: TIdentityUaClientSecretDalFactory; identityAccessTokenDal: TIdentityAccessTokenDalFactory; - identityOrgDal: TIdentityOrgDalFactory; + identityOrgMembershipDal: TIdentityOrgDalFactory; identityDal: Pick; permissionService: Pick; }; -export type TUniversalAuthServiceFactory = ReturnType; +export type TIdentityUaServiceFactory = ReturnType; -export const universalAuthServiceFactory = ({ - universalAuthDal, - uaClientSecretDal, +export const identityUaServiceFactory = ({ + identityUaDal, + identityUaClientSecretDal, identityAccessTokenDal, - identityOrgDal, + identityOrgMembershipDal, identityDal, permissionService -}: TUniversalAuthServiceFactoryDep) => { +}: TIdentityUaServiceFactoryDep) => { const login = async (clientId: string, clientSecret: string) => { - const identityUa = await universalAuthDal.findOne({ clientId }); + const identityUa = await identityUaDal.findOne({ clientId }); if (!identityUa) throw new UnauthorizedError(); // TODO(akhilmhdh-pg): add ip checking - const clientSecrtInfo = await uaClientSecretDal.find({ + const clientSecrtInfo = await identityUaClientSecretDal.find({ identityUAId: identityUa.id, isClientSecretRevoked: false }); @@ -73,7 +73,7 @@ export const universalAuthServiceFactory = ({ const expirationTime = new Date(clientSecretCreated.getTime() + ttlInMilliseconds); if (currentDate > expirationTime) { - await uaClientSecretDal.updateById(validClientSecretInfo.id, { + await identityUaClientSecretDal.updateById(validClientSecretInfo.id, { isClientSecretRevoked: true }); @@ -86,15 +86,17 @@ export const universalAuthServiceFactory = ({ if (clientSecretNumUsesLimit > 0 && clientSecretNumUses === clientSecretNumUsesLimit) { // number of times client secret can be used for // a login operation reached - await uaClientSecretDal.updateById(validClientSecretInfo.id, { isClientSecretRevoked: true }); + await identityUaClientSecretDal.updateById(validClientSecretInfo.id, { + isClientSecretRevoked: true + }); throw new UnauthorizedError({ message: "Failed to authenticate identity credentials due to client secret number of uses limit reached" }); } - const identityAccessToken = await universalAuthDal.transaction(async (tx) => { - const uaClientSecretDoc = await uaClientSecretDal.incrementUsage( + const identityAccessToken = await identityUaDal.transaction(async (tx) => { + const uaClientSecretDoc = await identityUaClientSecretDal.incrementUsage( validClientSecretInfo.id, tx ); @@ -143,7 +145,7 @@ export const universalAuthServiceFactory = ({ actorId, actor }: TAttachUaDTO) => { - const identityMembershipOrg = await identityOrgDal.findOne({ identityId }); + const identityMembershipOrg = await identityOrgMembershipDal.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity.authMethod) throw new BadRequestError({ @@ -192,8 +194,8 @@ export const universalAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); - const identityUa = await universalAuthDal.transaction(async (tx) => { - const doc = await universalAuthDal.create( + const identityUa = await identityUaDal.transaction(async (tx) => { + const doc = await identityUaDal.create( { identityId: identityMembershipOrg.identityId, clientId: crypto.randomUUID(), @@ -227,14 +229,14 @@ export const universalAuthServiceFactory = ({ actorId, actor }: TUpdateUaDTO) => { - const identityMembershipOrg = await identityOrgDal.findOne({ identityId }); + const identityMembershipOrg = await identityOrgMembershipDal.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) throw new BadRequestError({ message: "Failed to updated universal auth" }); - const uaIdentityAuth = await universalAuthDal.findOne({ identityId }); + const uaIdentityAuth = await identityUaDal.findOne({ identityId }); if ( (accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) > 0 && @@ -282,7 +284,7 @@ export const universalAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); - const updatedUaAuth = await universalAuthDal.updateById(uaIdentityAuth.id, { + const updatedUaAuth = await identityUaDal.updateById(uaIdentityAuth.id, { clientSecretTrustedIps: reformattedClientSecretTrustedIps ? JSON.stringify(reformattedClientSecretTrustedIps) : undefined, @@ -297,14 +299,14 @@ export const universalAuthServiceFactory = ({ }; const getIdentityUa = async ({ identityId, actorId, actor }: TGetUaDTO) => { - const identityMembershipOrg = await identityOrgDal.findOne({ identityId }); + const identityMembershipOrg = await identityOrgMembershipDal.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) throw new BadRequestError({ message: "The identity does not have universal auth" }); - const uaIdentityAuth = await universalAuthDal.findOne({ identityId }); + const uaIdentityAuth = await identityUaDal.findOne({ identityId }); const { permission } = await permissionService.getOrgPermission( actor, @@ -326,7 +328,7 @@ export const universalAuthServiceFactory = ({ description, numUsesLimit }: TCreateUaClientSecretDTO) => { - const identityMembershipOrg = await identityOrgDal.findOne({ identityId }); + const identityMembershipOrg = await identityOrgMembershipDal.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) throw new BadRequestError({ @@ -356,11 +358,11 @@ export const universalAuthServiceFactory = ({ const appCfg = getConfig(); const clientSecret = crypto.randomBytes(32).toString("hex"); const clientSecretHash = await bcrypt.hash(clientSecret, appCfg.SALT_ROUNDS); - const identityUniversalAuth = await universalAuthDal.findOne({ + const identityUniversalAuth = await identityUaDal.findOne({ identityId }); - const identityUaClientSecret = await uaClientSecretDal.create({ + const identityUaClientSecret = await identityUaClientSecretDal.create({ identityUAId: identityUniversalAuth.id, description, clientSecretPrefix: clientSecret.slice(0, 4), @@ -370,11 +372,15 @@ export const universalAuthServiceFactory = ({ clientSecretTTL: ttl, isClientSecretRevoked: false }); - return { clientSecret: identityUaClientSecret, uaAuth: identityUniversalAuth }; + return { + clientSecret, + clientSecretData: identityUaClientSecret, + uaAuth: identityUniversalAuth + }; }; const getUaClientSecrets = async ({ actor, actorId, identityId }: TGetUaClientSecretsDTO) => { - const identityMembershipOrg = await identityOrgDal.findOne({ identityId }); + const identityMembershipOrg = await identityOrgMembershipDal.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) throw new BadRequestError({ @@ -401,11 +407,11 @@ export const universalAuthServiceFactory = ({ message: "Failed to add identity to project with more privileged role" }); - const identityUniversalAuth = await universalAuthDal.findOne({ + const identityUniversalAuth = await identityUaDal.findOne({ identityId }); - const clientSecrets = await uaClientSecretDal.findOne({ + const clientSecrets = await identityUaClientSecretDal.find({ identityUAId: identityUniversalAuth.id, isClientSecretRevoked: false }); @@ -418,7 +424,7 @@ export const universalAuthServiceFactory = ({ actor, clientSecretId }: TRevokeUaClientSecretDTO) => { - const identityMembershipOrg = await identityOrgDal.findOne({ identityId }); + const identityMembershipOrg = await identityOrgMembershipDal.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) throw new BadRequestError({ @@ -445,7 +451,7 @@ export const universalAuthServiceFactory = ({ message: "Failed to add identity to project with more privileged role" }); - const clientSecret = await uaClientSecretDal.updateById(clientSecretId, { + const clientSecret = await identityUaClientSecretDal.updateById(clientSecretId, { isClientSecretRevoked: true }); return clientSecret; diff --git a/backend-pg/src/services/universal-auth/universal-auth-types.ts b/backend-pg/src/services/identity-ua/identity-ua-types.ts similarity index 100% rename from backend-pg/src/services/universal-auth/universal-auth-types.ts rename to backend-pg/src/services/identity-ua/identity-ua-types.ts diff --git a/backend-pg/src/services/identity/identity-org-dal.ts b/backend-pg/src/services/identity/identity-org-dal.ts index b4e63f29ab..074ef99e31 100644 --- a/backend-pg/src/services/identity/identity-org-dal.ts +++ b/backend-pg/src/services/identity/identity-org-dal.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName,TIdentityOrgMemberships } from "@app/db/schemas"; +import { TableName, TIdentityOrgMemberships } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols } from "@app/lib/knex"; @@ -31,5 +31,65 @@ export const identityOrgDalFactory = (db: TDbClient) => { } }; - return { ...identityOrgOrm, findOne }; + const findByOrgId = async (orgId: string, tx?: Knex) => { + try { + const docs = await (tx || db)(TableName.IdentityOrgMembership) + .where(`${TableName.IdentityOrgMembership}.orgId`, orgId) + .join( + TableName.Identity, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.Identity}.id` + ) + .leftJoin( + TableName.OrgRoles, + `${TableName.IdentityOrgMembership}.roleId`, + `${TableName.OrgRoles}.id` + ) + .select(selectAllTableCols(TableName.IdentityOrgMembership)) + // cr stands for custom role + .select(db.ref("id").as("crId").withSchema(TableName.OrgRoles)) + .select(db.ref("name").as("crName").withSchema(TableName.OrgRoles)) + .select(db.ref("slug").as("crSlug").withSchema(TableName.OrgRoles)) + .select(db.ref("description").as("crDescription").withSchema(TableName.OrgRoles)) + .select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles)) + .select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles)) + .select(db.ref("id").as("identityId").withSchema(TableName.Identity)) + .select(db.ref("name").as("identityName").withSchema(TableName.Identity)) + .select(db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity)); + return docs.map( + ({ + crId, + crDescription, + crSlug, + crPermission, + crName, + identityId, + identityName, + identityAuthMethod, + ...el + }) => ({ + ...el, + identityId, + identity: { + id: identityId, + name: identityName, + authMethod: identityAuthMethod + }, + customRole: el.roleId + ? { + id: crId, + name: crName, + slug: crSlug, + permissions: crPermission, + description: crDescription + } + : undefined + }) + ); + } catch (error) { + throw new DatabaseError({ error, name: "FindByOrgId" }); + } + }; + + return { ...identityOrgOrm, findOne, findByOrgId }; }; diff --git a/backend-pg/src/services/identity/identity-service.ts b/backend-pg/src/services/identity/identity-service.ts index 0139d8f49c..7ce2d32ece 100644 --- a/backend-pg/src/services/identity/identity-service.ts +++ b/backend-pg/src/services/identity/identity-service.ts @@ -8,6 +8,7 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors"; +import { TOrgPermission } from "@app/lib/types"; import { ActorType } from "../auth/auth-type"; import { TIdentityDalFactory } from "./identity-dal"; @@ -16,7 +17,7 @@ import { TCreateIdentityDTO, TDeleteIdentityDTO, TUpdateIdentityDTO } from "./id type TIdentityServiceFactoryDep = { identityDal: TIdentityDalFactory; - identityOrgDal: TIdentityOrgDalFactory; + identityOrgMembershipDal: TIdentityOrgDalFactory; permissionService: Pick; }; @@ -24,7 +25,7 @@ export type TIdentityServiceFactory = ReturnType; export const identityServiceFactory = ({ identityDal, - identityOrgDal, + identityOrgMembershipDal, permissionService }: TIdentityServiceFactoryDep) => { const createIdentity = async ({ name, role, actor, orgId, actorId }: TCreateIdentityDTO) => { @@ -43,7 +44,7 @@ export const identityServiceFactory = ({ const identity = await identityDal.transaction(async (tx) => { const newIdentity = await identityDal.create({ name }, tx); - await identityOrgDal.create( + await identityOrgMembershipDal.create( { identityId: newIdentity.id, orgId, @@ -60,7 +61,7 @@ export const identityServiceFactory = ({ }; const updateIdentity = async ({ id, role, name, actor, actorId }: TUpdateIdentityDTO) => { - const identityOrgMembership = await identityOrgDal.findById(id); + const identityOrgMembership = await identityOrgMembershipDal.findById(id); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); @@ -98,7 +99,7 @@ export const identityServiceFactory = ({ const identity = await identityDal.transaction(async (tx) => { const newIdentity = await identityDal.updateById(id, { name }, tx); if (role) { - await identityOrgDal.update( + await identityOrgMembershipDal.update( { identityId: id }, { role: customRole ? OrgMembershipRole.Custom : role, @@ -115,7 +116,7 @@ export const identityServiceFactory = ({ }; const deleteIdentity = async ({ actorId, actor, id }: TDeleteIdentityDTO) => { - const identityOrgMembership = await identityOrgDal.findById(id); + const identityOrgMembership = await identityOrgMembershipDal.findById(id); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); @@ -141,9 +142,21 @@ export const identityServiceFactory = ({ return deletedIdentity; }; + const listOrgIdentities = async ({ orgId, actor, actorId }: TOrgPermission) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionActions.Read, + OrgPermissionSubjects.Identity + ); + + const identityMemberhips = await identityOrgMembershipDal.findByOrgId(orgId); + return identityMemberhips; + }; + return { createIdentity, updateIdentity, - deleteIdentity + deleteIdentity, + listOrgIdentities }; }; diff --git a/backend-pg/src/services/identity/identity-types.ts b/backend-pg/src/services/identity/identity-types.ts index 4339790616..f104fdcda8 100644 --- a/backend-pg/src/services/identity/identity-types.ts +++ b/backend-pg/src/services/identity/identity-types.ts @@ -7,8 +7,8 @@ export type TCreateIdentityDTO = { export type TUpdateIdentityDTO = { id: string; - role: string; - name: string; + role?: string; + name?: string; } & Omit; export type TDeleteIdentityDTO = { diff --git a/backend-pg/src/services/integration-auth/integration-auth-service.ts b/backend-pg/src/services/integration-auth/integration-auth-service.ts index bb5b634e3d..b290e964d4 100644 --- a/backend-pg/src/services/integration-auth/integration-auth-service.ts +++ b/backend-pg/src/services/integration-auth/integration-auth-service.ts @@ -17,6 +17,7 @@ import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; import { BadRequestError } from "@app/lib/errors"; +import { TProjectPermission } from "@app/lib/types"; import { TIntegrationDalFactory } from "../integration/integration-dal"; import { TProjectBotDalFactory } from "../project-bot/project-bot-dal"; @@ -47,7 +48,7 @@ import { TTeamCityBuildConfig, TVercelBranches } from "./integration-auth-types"; -import { getIntegrationOptions,Integrations, IntegrationUrls } from "./integration-list"; +import { getIntegrationOptions, Integrations, IntegrationUrls } from "./integration-list"; import { getTeams } from "./integration-team"; import { exchangeCode, exchangeRefresh } from "./integration-token"; @@ -68,6 +69,20 @@ export const integrationAuthServiceFactory = ({ projectBotDal, projectBotService }: TIntegrationAuthServiceFactoryDep) => { + const listIntegrationAuthByProjectId = async ({ + actorId, + actor, + projectId + }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.Integrations + ); + const authorizations = await integrationAuthDal.find({ projectId }); + return authorizations; + }; + const getIntegrationAuth = async ({ actor, id, actorId }: TGetIntegrationAuthDTO) => { const integrationAuth = await integrationAuthDal.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -983,6 +998,7 @@ export const integrationAuthServiceFactory = ({ }; return { +listIntegrationAuthByProjectId, getIntegrationOptions, getIntegrationAuth, oauthExchange, diff --git a/backend-pg/src/services/integration/integration-dal.ts b/backend-pg/src/services/integration/integration-dal.ts index 1319068efa..f426845464 100644 --- a/backend-pg/src/services/integration/integration-dal.ts +++ b/backend-pg/src/services/integration/integration-dal.ts @@ -60,5 +60,34 @@ export const integrationDalFactory = (db: TDbClient) => { } }; - return { ...integrationOrm, find, findOne, findById }; + const findByProjectId = async (projectId: string, tx?: Knex) => { + try { + const integrations = await (tx || db)(TableName.Integration) + .where(`${TableName.Environment}.projectId`, projectId) + .join( + TableName.Environment, + `${TableName.Integration}.envId`, + `${TableName.Environment}.id` + ) + .select(db.ref("name").withSchema(TableName.Environment).as("envName")) + .select(db.ref("slug").withSchema(TableName.Environment).as("envSlug")) + .select(db.ref("id").withSchema(TableName.Environment).as("envId")) + .select(db.ref("projectId").withSchema(TableName.Environment)) + .select(selectAllTableCols(TableName.Integration)); + + return integrations.map(({ envId, envSlug, envName, ...el }) => ({ + ...el, + envId, + environment: { + id: envId, + slug: envSlug, + name: envName + } + })); + } catch (error) { + throw new DatabaseError({ error, name: "FindByProjectId" }); + } + }; + + return { ...integrationOrm, find, findOne, findById, findByProjectId }; }; diff --git a/backend-pg/src/services/integration/integration-service.ts b/backend-pg/src/services/integration/integration-service.ts index 90025b4622..44b34f0007 100644 --- a/backend-pg/src/services/integration/integration-service.ts +++ b/backend-pg/src/services/integration/integration-service.ts @@ -6,6 +6,7 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError } from "@app/lib/errors"; +import { TProjectPermission } from "@app/lib/types"; import { TIntegrationAuthDalFactory } from "../integration-auth/integration-auth-dal"; import { TSecretFolderDalFactory } from "../secret-folder/secret-folder-dal"; @@ -152,9 +153,21 @@ export const integrationServiceFactory = ({ return deletedIntegration; }; + const listIntegrationByProject = async ({ actor, actorId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.Integrations + ); + + const integrations = await integrationDal.findByProjectId(projectId); + return integrations; + }; + return { createIntegration, updateIntegration, - deleteIntegration + deleteIntegration, + listIntegrationByProject }; }; diff --git a/backend-pg/src/services/project-bot/project-bot-dal.ts b/backend-pg/src/services/project-bot/project-bot-dal.ts index e829f19cab..66e66b49e0 100644 --- a/backend-pg/src/services/project-bot/project-bot-dal.ts +++ b/backend-pg/src/services/project-bot/project-bot-dal.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName,TProjectBots } from "@app/db/schemas"; +import { TableName, TProjectBots } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols } from "@app/lib/knex"; @@ -14,8 +14,8 @@ export const projectBotDalFactory = (db: TDbClient) => { try { const bot = await (tx || db)(TableName.ProjectBot) .where(filter) - .join(TableName.Users, `${TableName.ProjectBot}.senderId`, `${TableName.Users}.id`) - .join( + .leftJoin(TableName.Users, `${TableName.ProjectBot}.senderId`, `${TableName.Users}.id`) + .leftJoin( TableName.UserEncryptionKey, `${TableName.UserEncryptionKey}.userId`, `${TableName.Users}.id` diff --git a/backend-pg/src/services/project-bot/project-bot-service.ts b/backend-pg/src/services/project-bot/project-bot-service.ts index 8fb1a75c00..387da3354e 100644 --- a/backend-pg/src/services/project-bot/project-bot-service.ts +++ b/backend-pg/src/services/project-bot/project-bot-service.ts @@ -139,7 +139,7 @@ export const projectBotServiceFactory = ({ actorId, isActive }: TSetActiveStateDTO) => { - const bot = await projectBotDal.findOne({ id: botId }); + const bot = await projectBotDal.findById(botId); if (!bot) throw new BadRequestError({ message: "Bot not found" }); const { permission } = await permissionService.getProjectPermission( @@ -153,12 +153,12 @@ export const projectBotServiceFactory = ({ ); if (isActive) { - if (!botKey?.nonce || !botKey?.encryptionKey) { + if (!botKey?.nonce || !botKey?.encryptedKey) { throw new BadRequestError({ message: "Failed to set bot active - missing bot key" }); } const doc = await projectBotDal.updateById(botId, { isActive: true, - encryptedProjectKey: botKey.encryptionKey, + encryptedProjectKey: botKey.encryptedKey, encryptedProjectKeyNonce: botKey.nonce, senderId: actorId }); diff --git a/backend-pg/src/services/project-bot/project-bot-types.ts b/backend-pg/src/services/project-bot/project-bot-types.ts index e983ee39fa..94a2943eb9 100644 --- a/backend-pg/src/services/project-bot/project-bot-types.ts +++ b/backend-pg/src/services/project-bot/project-bot-types.ts @@ -4,7 +4,7 @@ export type TSetActiveStateDTO = { isActive: boolean; botKey?: { nonce?: string; - encryptionKey?: string; + encryptedKey?: string; }; botId: string; } & Omit; diff --git a/backend-pg/src/services/secret/secret-dal.ts b/backend-pg/src/services/secret/secret-dal.ts index abf6a7728e..12e19357ad 100644 --- a/backend-pg/src/services/secret/secret-dal.ts +++ b/backend-pg/src/services/secret/secret-dal.ts @@ -76,12 +76,12 @@ export const secretDalFactory = (db: TDbClient) => { .where((bd) => { bd.whereNull("userId").orWhere({ userId: userId || null }); }) - .join( + .leftJoin( TableName.JnSecretTag, `${TableName.Secret}.id`, `${TableName.JnSecretTag}.${TableName.Secret}Id` ) - .join( + .leftJoin( TableName.SecretTag, `${TableName.JnSecretTag}.${TableName.SecretTag}Id`, `${TableName.SecretTag}.id` diff --git a/backend-pg/src/services/secret/secret-service.ts b/backend-pg/src/services/secret/secret-service.ts index 2fe2d3e0da..02c2ac6df0 100644 --- a/backend-pg/src/services/secret/secret-service.ts +++ b/backend-pg/src/services/secret/secret-service.ts @@ -6,7 +6,8 @@ import { SecretType, TableName, TSecretBlindIndexes, - TSecrets} from "@app/db/schemas"; + TSecrets +} from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, @@ -156,10 +157,12 @@ export const secretServiceFactory = ({ }, tx ); - await secretTagDal.saveTagsToSecret( - tags.map(({ id }) => ({ secretsId: doc.id, secret_tagsId: id })), - tx - ); + if (tags.length) { + await secretTagDal.saveTagsToSecret( + tags.map(({ id }) => ({ secretsId: doc.id, secret_tagsId: id })), + tx + ); + } await secretVersionDal.create( { secretBlindIndex, diff --git a/backend-pg/src/services/service-token/service-token-types.ts b/backend-pg/src/services/service-token/service-token-types.ts index b712860004..f1a700908b 100644 --- a/backend-pg/src/services/service-token/service-token-types.ts +++ b/backend-pg/src/services/service-token/service-token-types.ts @@ -7,7 +7,7 @@ export type TCreateServiceTokenDTO = { iv: string; tag: string; expiresIn?: number | null; - permissions: ["read" | "write"]; + permissions: ("read" | "write")[]; } & TProjectPermission; export type TGetServiceTokenInfoDTO = Omit; diff --git a/backend-pg/src/services/webhook/webhook-dal.ts b/backend-pg/src/services/webhook/webhook-dal.ts index 38bdd94f6e..e5afceabdc 100644 --- a/backend-pg/src/services/webhook/webhook-dal.ts +++ b/backend-pg/src/services/webhook/webhook-dal.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName,TWebhooks } from "@app/db/schemas"; +import { TableName, TWebhooks } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols } from "@app/lib/knex"; @@ -18,13 +18,14 @@ export const webhookDalFactory = (db: TDbClient) => { .select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug")) .select(tx.ref("id").withSchema(TableName.Environment).as("envId")) .select(tx.ref("projectId").withSchema(TableName.Environment)) - .select(selectAllTableCols(TableName.Integration)); + .select(selectAllTableCols(TableName.Webhook)); const find = async (filter: Partial, tx?: Knex) => { try { const docs = await webhookFindQuery(tx || db, filter); return docs.map(({ envId, envSlug, envName, ...el }) => ({ ...el, + envId, environment: { id: envId, slug: envSlug, @@ -50,11 +51,13 @@ export const webhookDalFactory = (db: TDbClient) => { const findById = async (id: string, tx?: Knex) => { try { - const doc = await webhookFindQuery(tx || db, { id }).first(); + const doc = await webhookFindQuery(tx || db, { + [`${TableName.Webhook}.id` as "id"]: id + }).first(); if (!doc) return; const { envName: name, envSlug: slug, envId, ...el } = doc; - return { ...el, environment: { id: envId, name, slug } }; + return { ...el, envId, environment: { id: envId, name, slug } }; } catch (error) { throw new DatabaseError({ error, name: "Find by id webhook" }); } @@ -82,9 +85,17 @@ export const webhookDalFactory = (db: TDbClient) => { .select(db.ref("slug").withSchema(TableName.Environment).as("envSlug")) .select(db.ref("id").withSchema(TableName.Environment).as("envId")) .select(db.ref("projectId").withSchema(TableName.Environment)) - .select(selectAllTableCols(TableName.Integration)); + .select(selectAllTableCols(TableName.Webhook)); - return webhooks; + return webhooks.map(({ envId, envSlug, envName, ...el }) => ({ + ...el, + envId, + environment: { + id: envId, + slug: envSlug, + name: envName + } + })); } catch (error) { throw new DatabaseError({ error, name: "Find all webhooks" }); } diff --git a/backend-pg/src/services/webhook/webhook-service.ts b/backend-pg/src/services/webhook/webhook-service.ts index 75413129f0..7fb916c328 100644 --- a/backend-pg/src/services/webhook/webhook-service.ts +++ b/backend-pg/src/services/webhook/webhook-service.ts @@ -83,7 +83,7 @@ export const webhookServiceFactory = ({ const webhook = await webhookDal.create(insertDoc); // TODO(akhilmhdh-pg): add audit log - return webhook; + return { ...webhook,projectId, environment: env }; }; const updateWebhook = async ({ actorId, actor, id, isDisabled }: TUpdateWebhookDTO) => { @@ -101,7 +101,7 @@ export const webhookServiceFactory = ({ ); const updatedWebhook = await webhookDal.updateById(id, { isDisabled }); - return updatedWebhook; + return { ...webhook,...updatedWebhook }; }; const deleteWebhook = async ({ id, actor, actorId }: TDeleteWebhookDTO) => { @@ -119,7 +119,7 @@ export const webhookServiceFactory = ({ ); const deletedWebhook = await webhookDal.deleteById(id); - return deletedWebhook; + return { ...webhook,...deletedWebhook }; }; const testWebhook = async ({ id, actor, actorId }: TTestWebhookDTO) => { @@ -150,7 +150,7 @@ export const webhookServiceFactory = ({ lastStatus: isSuccess ? "success" : "failed", lastRunErrorMessage: isSuccess ? null : webhookError }); - return updatedWebhook; + return {...webhook,...updatedWebhook} }; const listWebhooks = async ({ diff --git a/frontend/src/components/AddTagPopoverContent/AddTagPopoverContent.tsx b/frontend/src/components/AddTagPopoverContent/AddTagPopoverContent.tsx index 56bee047fb..4a1d3c8036 100644 --- a/frontend/src/components/AddTagPopoverContent/AddTagPopoverContent.tsx +++ b/frontend/src/components/AddTagPopoverContent/AddTagPopoverContent.tsx @@ -63,7 +63,7 @@ const AddTagPopoverContent = ({
{" "}
diff --git a/frontend/src/hooks/api/identities/enums.tsx b/frontend/src/hooks/api/identities/enums.tsx index 71ef886d6b..85c14f299a 100644 --- a/frontend/src/hooks/api/identities/enums.tsx +++ b/frontend/src/hooks/api/identities/enums.tsx @@ -1,3 +1,3 @@ export enum IdentityAuthMethod { - UNIVERSAL_AUTH = "universal-auth" -} \ No newline at end of file + UNIVERSAL_AUTH = "universal" +} diff --git a/frontend/src/hooks/api/integrationAuth/types.ts b/frontend/src/hooks/api/integrationAuth/types.ts index 1711436a85..6e0fc4a3dd 100644 --- a/frontend/src/hooks/api/integrationAuth/types.ts +++ b/frontend/src/hooks/api/integrationAuth/types.ts @@ -1,7 +1,7 @@ export type IntegrationAuth = { id: string; integration: string; - workspace: string; + projectId: string; __v: number; createdAt: string; updatedAt: string; diff --git a/frontend/src/hooks/api/integrations/types.ts b/frontend/src/hooks/api/integrations/types.ts index 716f4604c3..f63257f849 100644 --- a/frontend/src/hooks/api/integrations/types.ts +++ b/frontend/src/hooks/api/integrations/types.ts @@ -11,8 +11,9 @@ export type TCloudIntegration = { export type TIntegration = { id: string; - workspace: string; - environment: string; + projectId: string; + envId: string; + environment: { slug: string; name: string; id: string }; isActive: boolean; url: any; app: string; diff --git a/frontend/src/hooks/api/serviceTokens/queries.tsx b/frontend/src/hooks/api/serviceTokens/queries.tsx index 0f67af47b5..d3f129cc75 100644 --- a/frontend/src/hooks/api/serviceTokens/queries.tsx +++ b/frontend/src/hooks/api/serviceTokens/queries.tsx @@ -15,7 +15,7 @@ const serviceTokenKeys = { const fetchWorkspaceServiceTokens = async (workspaceID: string) => { const { data } = await apiRequest.get<{ serviceTokenData: ServiceToken[] }>( - `/api/v2/workspace/${workspaceID}/service-token-data` + `/api/v1/workspace/${workspaceID}/service-token-data` ); return data.serviceTokenData; @@ -29,10 +29,11 @@ export const useGetUserWsServiceTokens = ({ workspaceID }: UseGetWorkspaceServic queryFn: () => fetchWorkspaceServiceTokens(workspaceID), enabled: Boolean(workspaceID) }); -} +}; // mutation -export const useCreateServiceToken = () => { // TODO: deprecate +export const useCreateServiceToken = () => { + // TODO: deprecate const queryClient = useQueryClient(); return useMutation({ @@ -41,8 +42,8 @@ export const useCreateServiceToken = () => { // TODO: deprecate data.serviceToken += `.${body.randomBytes}`; return data; }, - onSuccess: ({ serviceTokenData: { workspace } }) => { - queryClient.invalidateQueries(serviceTokenKeys.getAllWorkspaceServiceToken(workspace)); + onSuccess: ({ serviceTokenData: { projectId } }) => { + queryClient.invalidateQueries(serviceTokenKeys.getAllWorkspaceServiceToken(projectId)); } }); }; @@ -55,8 +56,8 @@ export const useDeleteServiceToken = () => { const { data } = await apiRequest.delete(`/api/v2/service-token/${serviceTokenId}`); return data; }, - onSuccess: ({ serviceTokenData: { workspace } }) => { - queryClient.invalidateQueries(serviceTokenKeys.getAllWorkspaceServiceToken(workspace)); + onSuccess: ({ serviceTokenData: { projectId } }) => { + queryClient.invalidateQueries(serviceTokenKeys.getAllWorkspaceServiceToken(projectId)); } }); -}; \ No newline at end of file +}; diff --git a/frontend/src/hooks/api/serviceTokens/types.ts b/frontend/src/hooks/api/serviceTokens/types.ts index 489661d8b6..2dd61b1b3c 100644 --- a/frontend/src/hooks/api/serviceTokens/types.ts +++ b/frontend/src/hooks/api/serviceTokens/types.ts @@ -6,7 +6,7 @@ export type ServiceTokenScope = { export type ServiceToken = { id: string; name: string; - workspace: string; + projectId: string; scopes: ServiceTokenScope[]; user: string; expiresAt: string; diff --git a/frontend/src/hooks/api/tags/queries.tsx b/frontend/src/hooks/api/tags/queries.tsx index 7c230e02a8..311ed09416 100644 --- a/frontend/src/hooks/api/tags/queries.tsx +++ b/frontend/src/hooks/api/tags/queries.tsx @@ -2,13 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { - CreateTagDTO, - CreateTagRes, - DeleteTagDTO, - DeleteWsTagRes, - UserWsTags, -} from "./types"; +import { CreateTagDTO, DeleteTagDTO, UserWsTags, WsTag } from "./types"; const workspaceTags = { getWsTags: (workspaceID: string) => ["workspace-tags", { workspaceID }] as const @@ -16,7 +10,7 @@ const workspaceTags = { const fetchWsTag = async (workspaceID: string) => { const { data } = await apiRequest.get<{ workspaceTags: UserWsTags }>( - `/api/v2/workspace/${workspaceID}/tags` + `/api/v1/workspace/${workspaceID}/tags` ); return data.workspaceTags; @@ -28,38 +22,41 @@ export const useGetWsTags = (workspaceID: string) => { queryFn: () => fetchWsTag(workspaceID), enabled: Boolean(workspaceID) }); -} - +}; export const useCreateWsTag = () => { const queryClient = useQueryClient(); - return useMutation({ + return useMutation({ mutationFn: async ({ workspaceID, tagName, tagColor, tagSlug }) => { - const { data } = await apiRequest.post(`/api/v2/workspace/${workspaceID}/tags`, { - name: tagName, - tagColor: tagColor || "", - slug: tagSlug - }) - return data; + const { data } = await apiRequest.post<{ workspaceTag: WsTag }>( + `/api/v1/workspace/${workspaceID}/tags`, + { + name: tagName, + color: tagColor || "", + slug: tagSlug + } + ); + return data.workspaceTag; }, onSuccess: (tagData) => { - queryClient.invalidateQueries(workspaceTags.getWsTags(tagData?.workspace)); + queryClient.invalidateQueries(workspaceTags.getWsTags(tagData?.projectId)); } }); }; - export const useDeleteWsTag = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async ({ tagID }) => { - const { data } = await apiRequest.delete(`/api/v2/workspace/tags/${tagID}`); - return data + return useMutation({ + mutationFn: async ({ tagID, projectId }) => { + const { data } = await apiRequest.delete<{ workspaceTag: WsTag }>( + `/api/v1/workspace/${projectId}/tags/${tagID}` + ); + return data.workspaceTag; }, onSuccess: (tagData) => { - queryClient.invalidateQueries(workspaceTags.getWsTags(tagData?.workspace)); + queryClient.invalidateQueries(workspaceTags.getWsTags(tagData?.projectId)); } }); -}; \ No newline at end of file +}; diff --git a/frontend/src/hooks/api/tags/types.ts b/frontend/src/hooks/api/tags/types.ts index 161e0327bc..9b4f70587d 100644 --- a/frontend/src/hooks/api/tags/types.ts +++ b/frontend/src/hooks/api/tags/types.ts @@ -4,8 +4,8 @@ export type WsTag = { id: string; name: string; slug: string; - tagColor?: string; - workspace: string; + color?: string; + projectId: string; createdAt: string; updatedAt: string; __v: number; @@ -20,26 +20,7 @@ export type CreateTagDTO = { tagColor: string; }; -export type CreateTagRes = { - name: string; - slug: string; - workspace: string; - createdAt: string; - tagColor?: string; - user: string; - id: string; -}; - -export type DeleteTagDTO = { tagID: string }; - -export type DeleteWsTagRes = { - name: string; - slug: string; - workspace: string; - createdAt: string; - user: string; - id: string; -}; +export type DeleteTagDTO = { tagID: string; projectId: string }; export type SecretTags = { id: string; diff --git a/frontend/src/hooks/api/webhooks/types.ts b/frontend/src/hooks/api/webhooks/types.ts index f9ae1ba432..447ed4fc56 100644 --- a/frontend/src/hooks/api/webhooks/types.ts +++ b/frontend/src/hooks/api/webhooks/types.ts @@ -1,7 +1,12 @@ export type TWebhook = { id: string; - workspace: string; - environment: string; + projectId: string; + environment: { + slug: string; + name: string; + id: string; + }; + envId: string; secretPath: string; url: string; lastStatus: "success" | "failed"; diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx index 75b0113a8a..e2a06da167 100644 --- a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx @@ -87,7 +87,7 @@ export const IntegrationsSection = ({