diff --git a/backend/package.json b/backend/package.json index ee8cafce3..6b4d32317 100644 --- a/backend/package.json +++ b/backend/package.json @@ -125,4 +125,4 @@ "zod": "^3.22.4", "zod-to-json-schema": "^3.22.0" } -} +} \ No newline at end of file diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index af6bf8b93..ef850bb8c 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -51,6 +51,7 @@ declare module "fastify" { // used for mfa session authentication mfa: { userId: string; + orgId?: string; user: TUsers; }; // identity injection. depending on which kinda of token the information is filled in auth @@ -58,6 +59,7 @@ declare module "fastify" { permission: { type: ActorType; id: string; + orgId?: string; }; // passport data passportUser: { diff --git a/backend/src/db/migrations/20240204171758_org-based-auth.ts b/backend/src/db/migrations/20240204171758_org-based-auth.ts new file mode 100644 index 000000000..f2b2f913c --- /dev/null +++ b/backend/src/db/migrations/20240204171758_org-based-auth.ts @@ -0,0 +1,25 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.boolean("authEnforced").defaultTo(false); + t.index("slug"); + }); + + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + t.datetime("lastUsed"); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.dropColumn("authEnforced"); + t.dropIndex("slug"); + }); + + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + t.dropColumn("lastUsed"); + }); +} diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index e0f70d1c0..087a1b7e0 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -13,7 +13,8 @@ export const OrganizationsSchema = z.object({ customerId: z.string().nullable().optional(), slug: z.string(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + authEnforced: z.boolean().default(false).nullable().optional() }); export type TOrganizations = z.infer; diff --git a/backend/src/db/schemas/saml-configs.ts b/backend/src/db/schemas/saml-configs.ts index 4633384b9..6891d8add 100644 --- a/backend/src/db/schemas/saml-configs.ts +++ b/backend/src/db/schemas/saml-configs.ts @@ -22,7 +22,8 @@ export const SamlConfigsSchema = z.object({ certTag: z.string().nullable().optional(), createdAt: z.date(), updatedAt: z.date(), - orgId: z.string().uuid() + orgId: z.string().uuid(), + lastUsed: z.date().nullable().optional() }); export type TSamlConfigs = z.infer; diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index e560fc842..41cd11f7d 100644 --- a/backend/src/ee/routes/v1/license-router.ts +++ b/backend/src/ee/routes/v1/license-router.ts @@ -22,6 +22,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgPlansTableByBillCycle({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, billingCycle: req.query.billingCycle }); @@ -43,6 +44,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const plan = await server.services.license.getOrgPlan({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return { plan }; @@ -85,6 +87,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.startOrgTrial({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, success_url: req.body.success_url }); @@ -106,6 +109,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.createOrganizationPortalSession({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -126,6 +130,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgBillingInfo({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -146,6 +151,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgPlanTable({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -166,6 +172,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgBillingDetails({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -190,6 +197,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.updateOrgBillingDetails({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, name: req.body.name, email: req.body.email @@ -212,6 +220,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgPmtMethods({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -236,6 +245,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.addOrgPmtMethods({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, success_url: req.body.success_url, cancel_url: req.body.cancel_url @@ -261,6 +271,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.delOrgPmtMethods({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, pmtMethodId: req.params.pmtMethodId }); @@ -284,6 +295,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgTaxIds({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -310,6 +322,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.addOrgTaxId({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, type: req.body.type, value: req.body.value @@ -335,6 +348,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.delOrgTaxId({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, taxId: req.params.taxId }); @@ -358,6 +372,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgTaxInvoices({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -380,6 +395,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgLicenses({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; diff --git a/backend/src/ee/routes/v1/org-role-router.ts b/backend/src/ee/routes/v1/org-role-router.ts index 4890c97a5..46b80e7a9 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -26,7 +26,12 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const role = await server.services.orgRole.createRole(req.permission.id, req.params.organizationId, req.body); + const role = await server.services.orgRole.createRole( + req.permission.id, + req.params.organizationId, + req.body, + req.permission.orgId + ); return { role }; } }); @@ -57,7 +62,8 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.organizationId, req.params.roleId, - req.body + req.body, + req.permission.orgId ); return { role }; } @@ -82,7 +88,8 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { const role = await server.services.orgRole.deleteRole( req.permission.id, req.params.organizationId, - req.params.roleId + req.params.roleId, + req.permission.orgId ); return { role }; } @@ -107,7 +114,11 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const roles = await server.services.orgRole.listRoles(req.permission.id, req.params.organizationId); + const roles = await server.services.orgRole.listRoles( + req.permission.id, + req.params.organizationId, + req.permission.orgId + ); return { data: { roles } }; } }); @@ -130,7 +141,8 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const { permissions, membership } = await server.services.orgRole.getUserPermission( req.permission.id, - req.params.organizationId + req.params.organizationId, + req.permission.orgId ); return { permissions, membership }; } diff --git a/backend/src/ee/routes/v1/project-role-router.ts b/backend/src/ee/routes/v1/project-role-router.ts index 86f2242e2..f6fd53e5e 100644 --- a/backend/src/ee/routes/v1/project-role-router.ts +++ b/backend/src/ee/routes/v1/project-role-router.ts @@ -30,7 +30,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.type, req.permission.id, req.params.projectId, - req.body + req.body, + req.permission.orgId ); return { role }; } @@ -63,7 +64,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.projectId, req.params.roleId, - req.body + req.body, + req.permission.orgId ); return { role }; } @@ -89,7 +91,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.type, req.permission.id, req.params.projectId, - req.params.roleId + req.params.roleId, + req.permission.orgId ); return { role }; } @@ -117,7 +120,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { const roles = await server.services.projectRole.listRoles( req.permission.type, req.permission.id, - req.params.projectId + req.params.projectId, + req.permission.orgId ); return { data: { roles } }; } @@ -143,7 +147,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const { permissions, membership } = await server.services.projectRole.getUserPermission( req.permission.id, - req.params.projectId + req.params.projectId, + req.permission.orgId ); return { data: { permissions, membership } }; } diff --git a/backend/src/ee/routes/v1/project-router.ts b/backend/src/ee/routes/v1/project-router.ts index 3870123fd..2a17f747d 100644 --- a/backend/src/ee/routes/v1/project-router.ts +++ b/backend/src/ee/routes/v1/project-router.ts @@ -31,6 +31,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const secretSnapshots = await server.services.snapshot.listSnapshots({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, ...req.query }); @@ -60,6 +61,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const count = await server.services.snapshot.projectSecretSnapshotCount({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, environment: req.query.environment, path: req.query.path @@ -112,6 +114,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const auditLogs = await server.services.auditLog.listProjectAuditLogs({ actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, ...req.query, auditLogActor: req.query.actor, diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index edea312fd..1ca24cae7 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -13,7 +13,7 @@ import { FastifyRequest } from "fastify"; import { z } from "zod"; import { SamlConfigsSchema } from "@app/db/schemas"; -import { SamlProviders } from "@app/ee/services/saml-config/saml-config-types"; +import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -44,17 +44,30 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { // eslint-disable-next-line getSamlOptions: async (req, done) => { try { - const { ssoIdentifier } = req.params; - if (!ssoIdentifier) throw new BadRequestError({ message: "Missing sso identitier" }); + const { samlConfigId, orgSlug } = req.params; - const ssoConfig = await server.services.saml.getSaml({ - type: "ssoId", - id: ssoIdentifier - }); - if (!ssoConfig) throw new BadRequestError({ message: "SSO config not found" }); + let ssoLookupDetails: TGetSamlCfgDTO; + + if (orgSlug) { + ssoLookupDetails = { + type: "orgSlug", + orgSlug + }; + } else if (samlConfigId) { + ssoLookupDetails = { + type: "ssoId", + id: samlConfigId + }; + } else { + throw new BadRequestError({ message: "Missing sso identitier or org slug" }); + } + + const ssoConfig = await server.services.saml.getSaml(ssoLookupDetails); + if (!ssoConfig || !ssoConfig.isActive) + throw new BadRequestError({ message: "Failed to authenticate with SAML SSO" }); const samlConfig: TSAMLConfig = { - callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoIdentifier}`, + callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${samlConfigId}`, entryPoint: ssoConfig.entryPoint, issuer: ssoConfig.issuer, cert: ssoConfig.cert, @@ -108,11 +121,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { ); server.route({ - url: "/redirect/saml2/:ssoIdentifier", + url: "/redirect/saml2/organizations/:orgSlug", method: "GET", schema: { params: z.object({ - ssoIdentifier: z.string().trim() + orgSlug: z.string().trim() }), querystring: z.object({ callback_port: z.string().optional() @@ -134,11 +147,37 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/saml2/:ssoIdentifier", + url: "/redirect/saml2/:samlConfigId", + method: "GET", + schema: { + params: z.object({ + samlConfigId: z.string().trim() + }), + querystring: z.object({ + callback_port: z.string().optional() + }) + }, + preValidation: (req, res) => + ( + passport.authenticate("saml", { + failureRedirect: "/", + additionalParams: { + RelayState: JSON.stringify({ + spInitiated: true, + callbackPort: req.query.callback_port ?? "" + }) + } + } as any) as any + )(req, res), + handler: () => {} + }); + + server.route({ + url: "/saml2/:samlConfigId", method: "POST", schema: { params: z.object({ - ssoIdentifier: z.string().trim() + samlConfigId: z.string().trim() }) }, preValidation: passport.authenticate("saml", { @@ -177,7 +216,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { isActive: z.boolean(), entryPoint: z.string(), issuer: z.string(), - cert: z.string() + cert: z.string(), + lastUsed: z.date().nullable().optional() }) .optional() } @@ -186,6 +226,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { const saml = await server.services.saml.getSaml({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.query.organizationId, type: "org" }); @@ -214,6 +255,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { const saml = await server.services.saml.createSamlCfg({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.body.organizationId, ...req.body }); @@ -244,6 +286,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { const saml = await server.services.saml.updateSamlCfg({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.body.organizationId, ...req.body }); diff --git a/backend/src/ee/routes/v1/secret-approval-policy-router.ts b/backend/src/ee/routes/v1/secret-approval-policy-router.ts index dda8dbe38..8fce232a7 100644 --- a/backend/src/ee/routes/v1/secret-approval-policy-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-policy-router.ts @@ -34,6 +34,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body, name: req.body.name ?? `${req.body.environment}-${nanoid(3)}` @@ -71,6 +72,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, secretPolicyId: req.params.sapId }); @@ -96,6 +98,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, secretPolicyId: req.params.sapId }); return { approval }; @@ -120,6 +123,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); return { approvals }; @@ -146,6 +150,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.query.workspaceId, ...req.query }); diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index f33e8b0d0..97eb89109 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -52,6 +52,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approvals = await server.services.secretApprovalRequest.getSecretApprovals({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId }); @@ -80,6 +81,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approvals = await server.services.secretApprovalRequest.requestCount({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); return { approvals }; @@ -104,6 +106,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, approvalId: req.params.id }); return { approval }; @@ -131,6 +134,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const review = await server.services.secretApprovalRequest.reviewApproval({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, approvalId: req.params.id, status: req.body.status }); @@ -159,6 +163,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approval = await server.services.secretApprovalRequest.updateApprovalStatus({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, approvalId: req.params.id, status: req.body.status }); @@ -266,6 +271,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.id }); return { approval }; diff --git a/backend/src/ee/routes/v1/secret-rotation-provider-router.ts b/backend/src/ee/routes/v1/secret-rotation-provider-router.ts index bcaf1ab39..e7201b73f 100644 --- a/backend/src/ee/routes/v1/secret-rotation-provider-router.ts +++ b/backend/src/ee/routes/v1/secret-rotation-provider-router.ts @@ -30,6 +30,7 @@ export const registerSecretRotationProviderRouter = async (server: FastifyZodPro const providers = await server.services.secretRotation.getProviderTemplates({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return providers; diff --git a/backend/src/ee/routes/v1/secret-rotation-router.ts b/backend/src/ee/routes/v1/secret-rotation-router.ts index 062c51980..8d2e90ac0 100644 --- a/backend/src/ee/routes/v1/secret-rotation-router.ts +++ b/backend/src/ee/routes/v1/secret-rotation-router.ts @@ -40,6 +40,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotation = await server.services.secretRotation.createRotation({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId }); @@ -73,6 +74,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotation = await server.services.secretRotation.restartById({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, rotationId: req.body.id }); return { secretRotation }; @@ -123,6 +125,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotations = await server.services.secretRotation.getByProjectId({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); return { secretRotations }; @@ -155,6 +158,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotation = await server.services.secretRotation.deleteById({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, rotationId: req.params.id }); return { secretRotation }; diff --git a/backend/src/ee/routes/v1/secret-scanning-router.ts b/backend/src/ee/routes/v1/secret-scanning-router.ts index 2970a4308..7d2c5f1ee 100644 --- a/backend/src/ee/routes/v1/secret-scanning-router.ts +++ b/backend/src/ee/routes/v1/secret-scanning-router.ts @@ -22,6 +22,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const session = await server.services.secretScanning.createInstallationSession({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.body.organizationId }); return session; @@ -45,6 +46,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { installatedApp } = await server.services.secretScanning.linkInstallationToOrg({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body }); return installatedApp; @@ -65,6 +67,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const appInstallationCompleted = await server.services.secretScanning.getOrgInstallationStatus({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return { appInstallationCompleted }; @@ -85,6 +88,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { risks } = await server.services.secretScanning.getRisksByOrg({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return { risks }; @@ -106,6 +110,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { risk } = await server.services.secretScanning.updateRiskStatus({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, riskId: req.params.riskId, ...req.body diff --git a/backend/src/ee/routes/v1/secret-version-router.ts b/backend/src/ee/routes/v1/secret-version-router.ts index 269ed8636..89ee4e011 100644 --- a/backend/src/ee/routes/v1/secret-version-router.ts +++ b/backend/src/ee/routes/v1/secret-version-router.ts @@ -27,6 +27,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) => const secretVersions = await server.services.secret.getSecretVersions({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, limit: req.query.limit, offset: req.query.offset, secretId: req.params.secretId diff --git a/backend/src/ee/routes/v1/snapshot-router.ts b/backend/src/ee/routes/v1/snapshot-router.ts index c3b9d2d98..0b9eadb0c 100644 --- a/backend/src/ee/routes/v1/snapshot-router.ts +++ b/backend/src/ee/routes/v1/snapshot-router.ts @@ -46,6 +46,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { const secretSnapshot = await server.services.snapshot.getSnapshotData({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.secretSnapshotId }); return { secretSnapshot }; @@ -70,6 +71,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { const secretSnapshot = await server.services.snapshot.rollbackSnapshot({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.secretSnapshotId }); return { secretSnapshot }; diff --git a/backend/src/ee/routes/v1/trusted-ip-router.ts b/backend/src/ee/routes/v1/trusted-ip-router.ts index fd56a2cda..53bc5b117 100644 --- a/backend/src/ee/routes/v1/trusted-ip-router.ts +++ b/backend/src/ee/routes/v1/trusted-ip-router.ts @@ -24,7 +24,8 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { const trustedIps = await server.services.trustedIp.listIpsByProjectId({ projectId: req.params.workspaceId, actor: req.permission.type, - actorId: req.permission.id + actorId: req.permission.id, + actorOrgId: req.permission.orgId }); return { trustedIps }; } @@ -54,6 +55,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body }); await server.services.auditLog.createAuditLog({ @@ -97,6 +99,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, trustedIpId: req.params.trustedIpId, ...req.body }); @@ -137,6 +140,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, trustedIpId: req.params.trustedIpId }); await server.services.auditLog.createAuditLog({ diff --git a/backend/src/ee/services/audit-log/audit-log-service.ts b/backend/src/ee/services/audit-log/audit-log-service.ts index c1d5c6925..1c7868fc2 100644 --- a/backend/src/ee/services/audit-log/audit-log-service.ts +++ b/backend/src/ee/services/audit-log/audit-log-service.ts @@ -30,10 +30,11 @@ export const auditLogServiceFactory = ({ startDate, actor, actorId, + actorOrgId, projectId, auditLogActor }: TListProjectAuditLogDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); const auditLogs = await auditLogDAL.find({ startDate, diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index 028af2339..6d97b537c 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -175,8 +175,14 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: }; // below all are api calls - const getOrgPlansTableByBillCycle = async ({ orgId, actor, actorId, billingCycle }: TOrgPlansTableDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgPlansTableByBillCycle = async ({ + orgId, + actor, + actorId, + actorOrgId, + billingCycle + }: TOrgPlansTableDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const { data } = await licenseServerCloudApi.request.get( `/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` @@ -184,15 +190,15 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgPlan = async ({ orgId, actor, actorId, projectId }: TOrgPlanDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, projectId }: TOrgPlanDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const plan = await getPlan(orgId, projectId); return plan; }; - const startOrgTrial = async ({ orgId, actorId, actor, success_url }: TStartOrgTrialDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const startOrgTrial = async ({ orgId, actorId, actor, actorOrgId, success_url }: TStartOrgTrialDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); @@ -213,8 +219,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return { url }; }; - const createOrganizationPortalSession = async ({ orgId, actorId, actor }: TCreateOrgPortalSession) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const createOrganizationPortalSession = async ({ orgId, actorId, actor, actorOrgId }: TCreateOrgPortalSession) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); @@ -260,8 +266,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return { url }; }; - const getOrgBillingInfo = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgBillingInfo = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -277,8 +283,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: }; // returns org current plan feature table - const getOrgPlanTable = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgPlanTable = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -293,8 +299,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgBillingDetails = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgBillingDetails = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -310,8 +316,15 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const updateOrgBillingDetails = async ({ actorId, actor, orgId, name, email }: TUpdateOrgBillingDetailsDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const updateOrgBillingDetails = async ({ + actorId, + actor, + actorOrgId, + orgId, + name, + email + }: TUpdateOrgBillingDetailsDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -330,8 +343,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgPmtMethods = async ({ orgId, actor, actorId }: TOrgPmtMethodsDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgPmtMethods = async ({ orgId, actor, actorId, actorOrgId }: TOrgPmtMethodsDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -349,8 +362,15 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return pmtMethods; }; - const addOrgPmtMethods = async ({ orgId, actor, actorId, success_url, cancel_url }: TAddOrgPmtMethodDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const addOrgPmtMethods = async ({ + orgId, + actor, + actorId, + actorOrgId, + success_url, + cancel_url + }: TAddOrgPmtMethodDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -371,8 +391,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return { url }; }; - const delOrgPmtMethods = async ({ actorId, actor, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const delOrgPmtMethods = async ({ actorId, actor, actorOrgId, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -388,8 +408,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgTaxIds = async ({ orgId, actor, actorId }: TGetOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgTaxIds = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -406,8 +426,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return taxIds; }; - const addOrgTaxId = async ({ actorId, actor, orgId, type, value }: TAddOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const addOrgTaxId = async ({ actorId, actor, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -427,8 +447,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const delOrgTaxId = async ({ orgId, actor, actorId, taxId }: TDelOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const delOrgTaxId = async ({ orgId, actor, actorId, actorOrgId, taxId }: TDelOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -444,8 +464,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgTaxInvoices = async ({ actorId, actor, orgId }: TOrgInvoiceDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, orgId }: TOrgInvoiceDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -461,8 +481,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return invoices; }; - const getOrgLicenses = async ({ orgId, actor, actorId }: TOrgLicensesDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgLicenses = async ({ orgId, actor, actorId, actorOrgId }: TOrgLicensesDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index cc35fb04e..ea195bc06 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -10,8 +10,10 @@ export const permissionDALFactory = (db: TDbClient) => { try { const membership = await db(TableName.OrgMembership) .leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`) + .join(TableName.Organization, `${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`) .where("userId", userId) .where(`${TableName.OrgMembership}.orgId`, orgId) + .select(db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced")) .select("permissions") .select(selectAllTableCols(TableName.OrgMembership)) .first(); @@ -26,9 +28,11 @@ export const permissionDALFactory = (db: TDbClient) => { try { const membership = await db(TableName.IdentityOrgMembership) .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) + .join(TableName.Organization, `${TableName.IdentityOrgMembership}.orgId`, `${TableName.Organization}.id`) .where("identityId", identityId) .where(`${TableName.IdentityOrgMembership}.orgId`, orgId) .select(selectAllTableCols(TableName.IdentityOrgMembership)) + .select(db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced")) .select("permissions") .first(); return membership; @@ -41,9 +45,15 @@ export const permissionDALFactory = (db: TDbClient) => { try { const membership = await db(TableName.ProjectMembership) .leftJoin(TableName.ProjectRoles, `${TableName.ProjectMembership}.roleId`, `${TableName.ProjectRoles}.id`) + .join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`) + .join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`) .where("userId", userId) .where(`${TableName.ProjectMembership}.projectId`, projectId) .select(selectAllTableCols(TableName.ProjectMembership)) + .select( + db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), + db.ref("orgId").withSchema(TableName.Project) + ) .select("permissions") .first(); diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 3daf1c20a..4735312e4 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -94,12 +94,15 @@ export const permissionServiceFactory = ({ /* * Get user permission in an organization * */ - const getUserOrgPermission = async (userId: string, orgId: string) => { + const getUserOrgPermission = async (userId: string, orgId: string, userOrgId?: string) => { const membership = await permissionDAL.getOrgPermission(userId, orgId); if (!membership) throw new UnauthorizedError({ name: "User not in org" }); if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } + if (membership.orgAuthEnforced && membership.orgId !== userOrgId) { + throw new BadRequestError({ name: "Cannot access org-scoped resource" }); + } return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; }; @@ -112,10 +115,10 @@ export const permissionServiceFactory = ({ return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; }; - const getOrgPermission = async (type: ActorType, id: string, orgId: string) => { + const getOrgPermission = async (type: ActorType, id: string, orgId: string, actorOrgId?: string) => { switch (type) { case ActorType.USER: - return getUserOrgPermission(id, orgId); + return getUserOrgPermission(id, orgId, actorOrgId); case ActorType.IDENTITY: return getIdentityOrgPermission(id, orgId); default: @@ -142,12 +145,17 @@ export const permissionServiceFactory = ({ }; // user permission for a project in an organization - const getUserProjectPermission = async (userId: string, projectId: string) => { + const getUserProjectPermission = async (userId: string, projectId: string, userOrgId?: string) => { const membership = await permissionDAL.getProjectPermission(userId, projectId); if (!membership) throw new UnauthorizedError({ name: "User not in project" }); if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } + + if (membership.orgAuthEnforced && membership.orgId !== userOrgId) { + throw new BadRequestError({ name: "Cannot access org-scoped resource" }); + } + return { permission: buildProjectPermission(membership.role, membership.permissions), membership @@ -160,6 +168,7 @@ 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 @@ -184,6 +193,8 @@ export const permissionServiceFactory = ({ : { permission: MongoAbility; membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & { + orgAuthEnforced: boolean; + orgId: string; permissions?: unknown; }; }; @@ -191,11 +202,12 @@ export const permissionServiceFactory = ({ const getProjectPermission = async ( type: T, id: string, - projectId: string + projectId: string, + actorOrgId?: string ): Promise> => { switch (type) { case ActorType.USER: - return getUserProjectPermission(id, projectId) as Promise>; + return getUserProjectPermission(id, projectId, actorOrgId) as Promise>; case ActorType.SERVICE: return getServiceTokenProjectPermission(id, projectId) as Promise>; case ActorType.IDENTITY: diff --git a/backend/src/ee/services/saml-config/saml-config-dal.ts b/backend/src/ee/services/saml-config/saml-config-dal.ts index 95f6828bc..1e7b9e47e 100644 --- a/backend/src/ee/services/saml-config/saml-config-dal.ts +++ b/backend/src/ee/services/saml-config/saml-config-dal.ts @@ -1,10 +1,31 @@ import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; import { ormify } from "@app/lib/knex"; export type TSamlConfigDALFactory = ReturnType; export const samlConfigDALFactory = (db: TDbClient) => { const samlCfgOrm = ormify(db, TableName.SamlConfig); - return samlCfgOrm; + + const findEnforceableSamlCfg = async (orgId: string) => { + try { + const samlCfg = await db(TableName.SamlConfig) + .where({ + orgId, + isActive: true + }) + .whereNotNull("lastUsed") + .first(); + + return samlCfg; + } catch (error) { + throw new DatabaseError({ error, name: "Find org by id" }); + } + }; + + return { + ...samlCfgOrm, + findEnforceableSamlCfg + }; }; diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 9da2dd122..132f93f52 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -18,7 +18,7 @@ import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; -import { AuthTokenType } from "@app/services/auth/auth-type"; +import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; @@ -27,18 +27,15 @@ import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { TPermissionServiceFactory } from "../permission/permission-service"; import { TSamlConfigDALFactory } from "./saml-config-dal"; -import { - SamlProviders, - TCreateSamlCfgDTO, - TGetSamlCfgDTO, - TSamlLoginDTO, - TUpdateSamlCfgDTO -} from "./saml-config-types"; +import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } from "./saml-config-types"; type TSamlConfigServiceFactoryDep = { samlConfigDAL: TSamlConfigDALFactory; userDAL: Pick; - orgDAL: Pick; + orgDAL: Pick< + TOrgDALFactory, + "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" + >; orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; @@ -57,6 +54,7 @@ export const samlConfigServiceFactory = ({ const createSamlCfg = async ({ cert, actor, + actorOrgId, orgId, issuer, actorId, @@ -64,7 +62,7 @@ export const samlConfigServiceFactory = ({ entryPoint, authProvider }: TCreateSamlCfgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); const plan = await licenseService.getPlan(orgId); @@ -140,12 +138,14 @@ export const samlConfigServiceFactory = ({ certIV, certTag }); + return samlConfig; }; const updateSamlCfg = async ({ orgId, actor, + actorOrgId, cert, actorId, issuer, @@ -153,7 +153,7 @@ export const samlConfigServiceFactory = ({ entryPoint, authProvider }: TUpdateSamlCfgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); const plan = await licenseService.getPlan(orgId); if (!plan.samlSSO) @@ -162,7 +162,7 @@ export const samlConfigServiceFactory = ({ "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." }); - const updateQuery: TSamlConfigsUpdate = { authProvider, isActive }; + const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null }; const orgBot = await orgBotDAL.findOne({ orgId }); if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" }); const key = infisicalSymmetricDecrypt({ @@ -195,6 +195,8 @@ export const samlConfigServiceFactory = ({ updateQuery.certTag = certTag; } const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery); + await orgDAL.updateById(orgId, { authEnforced: false }); + return ssoConfig; }; @@ -203,6 +205,10 @@ export const samlConfigServiceFactory = ({ if (dto.type === "org") { ssoConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); if (!ssoConfig) return; + } else if (dto.type === "orgSlug") { + const org = await orgDAL.findOne({ slug: dto.orgSlug }); + if (!org) return; + ssoConfig = await samlConfigDAL.findOne({ orgId: org.id }); } else if (dto.type === "ssoId") { // TODO: // We made this change because saml config ids were not moved over during the migration @@ -227,7 +233,12 @@ export const samlConfigServiceFactory = ({ // when dto is type id means it's internally used if (dto.type === "org") { - const { permission } = await permissionService.getOrgPermission(dto.actor, dto.actorId, ssoConfig.orgId); + const { permission } = await permissionService.getOrgPermission( + dto.actor, + dto.actorId, + ssoConfig.orgId, + dto.actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); } const { @@ -284,7 +295,8 @@ export const samlConfigServiceFactory = ({ isActive: ssoConfig.isActive, entryPoint, issuer, - cert + cert, + lastUsed: ssoConfig.lastUsed }; }; @@ -306,13 +318,7 @@ export const samlConfigServiceFactory = ({ if (!organization) throw new BadRequestError({ message: "Org not found" }); if (user) { - const hasSamlEnabled = (user.authMethods || []).some((method) => - Object.values(SamlProviders).includes(method as SamlProviders) - ); await userDAL.transaction(async (tx) => { - if (!hasSamlEnabled) { - await userDAL.updateById(user.id, { authMethods: [authProvider] }, tx); - } const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx }); if (!orgMembership) { await orgDAL.createMembership( @@ -342,7 +348,7 @@ export const samlConfigServiceFactory = ({ email, firstName, lastName, - authMethods: [authProvider] + authMethods: [AuthMethod.EMAIL] }, tx ); @@ -378,6 +384,9 @@ export const samlConfigServiceFactory = ({ expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME } ); + + await samlConfigDAL.update({ orgId }, { lastUsed: new Date() }); + return { isUserCompleted, providerAuthToken }; }; diff --git a/backend/src/ee/services/saml-config/saml-config-types.ts b/backend/src/ee/services/saml-config/saml-config-types.ts index 18a511af5..587ea52dc 100644 --- a/backend/src/ee/services/saml-config/saml-config-types.ts +++ b/backend/src/ee/services/saml-config/saml-config-types.ts @@ -25,7 +25,11 @@ export type TUpdateSamlCfgDTO = Partial<{ TOrgPermission; export type TGetSamlCfgDTO = - | { type: "org"; orgId: string; actor: ActorType; actorId: string } + | { type: "org"; orgId: string; actor: ActorType; actorId: string; actorOrgId?: string } + | { + type: "orgSlug"; + orgSlug: string; + } | { type: "ssoId"; id: string; diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts index b5688545c..9d65ec7cc 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts @@ -44,6 +44,7 @@ export const secretApprovalPolicyServiceFactory = ({ name, actor, actorId, + actorOrgId, approvals, approvers, projectId, @@ -53,7 +54,7 @@ export const secretApprovalPolicyServiceFactory = ({ if (approvals > approvers.length) throw new BadRequestError({ message: "Approvals cannot be greater than approvers" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval @@ -96,13 +97,19 @@ export const secretApprovalPolicyServiceFactory = ({ name, actorId, actor, + actorOrgId, approvals, secretPolicyId }: TUpdateSapDTO) => { const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId); if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, secretApprovalPolicy.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + secretApprovalPolicy.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => { @@ -145,11 +152,16 @@ export const secretApprovalPolicyServiceFactory = ({ }; }; - const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId }: TDeleteSapDTO) => { + const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId, actorOrgId }: TDeleteSapDTO) => { const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId); if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, sapPolicy.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + sapPolicy.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval @@ -159,8 +171,8 @@ export const secretApprovalPolicyServiceFactory = ({ return sapPolicy; }; - const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, projectId }: TListSapDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TListSapDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); const sapPolicies = await secretApprovalPolicyDAL.find({ projectId }); @@ -188,10 +200,11 @@ export const secretApprovalPolicyServiceFactory = ({ projectId, actor, actorId, + actorOrgId, environment, secretPath }: TGetBoardSapDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { secretPath, environment }) diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index d1ecd51ed..ef10db804 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -73,10 +73,15 @@ export const secretApprovalRequestServiceFactory = ({ secretVersionDAL, secretQueueService }: TSecretApprovalRequestServiceFactoryDep) => { - const requestCount = async ({ projectId, actor, actorId }: TApprovalRequestCountDTO) => { + const requestCount = async ({ projectId, actor, actorId, actorOrgId }: TApprovalRequestCountDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); - const { membership } = await permissionService.getProjectPermission(actor as ActorType.USER, actorId, projectId); + const { membership } = await permissionService.getProjectPermission( + actor as ActorType.USER, + actorId, + projectId, + actorOrgId + ); const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, membership.id); return count; @@ -86,6 +91,7 @@ export const secretApprovalRequestServiceFactory = ({ projectId, actorId, actor, + actorOrgId, status, environment, committer, @@ -94,7 +100,7 @@ export const secretApprovalRequestServiceFactory = ({ }: TListApprovalsDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); - const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const approvals = await secretApprovalRequestDAL.findByProjectId({ projectId, committer, @@ -107,7 +113,7 @@ export const secretApprovalRequestServiceFactory = ({ return approvals; }; - const getSecretApprovalDetails = async ({ actor, actorId, id }: TSecretApprovalDetailsDTO) => { + const getSecretApprovalDetails = async ({ actor, actorId, actorOrgId, id }: TSecretApprovalDetailsDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); const secretApprovalRequest = await secretApprovalRequestDAL.findById(id); @@ -117,7 +123,8 @@ export const secretApprovalRequestServiceFactory = ({ const { membership } = await permissionService.getProjectPermission( actor, actorId, - secretApprovalRequest.projectId + secretApprovalRequest.projectId, + actorOrgId ); if ( membership.role !== ProjectMembershipRole.Admin && @@ -134,7 +141,7 @@ export const secretApprovalRequestServiceFactory = ({ return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets }; }; - const reviewApproval = async ({ approvalId, actor, status, actorId }: TReviewRequestDTO) => { + const reviewApproval = async ({ approvalId, actor, status, actorId, actorOrgId }: TReviewRequestDTO) => { const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); @@ -143,7 +150,8 @@ export const secretApprovalRequestServiceFactory = ({ const { membership } = await permissionService.getProjectPermission( ActorType.USER, actorId, - secretApprovalRequest.projectId + secretApprovalRequest.projectId, + actorOrgId ); if ( membership.role !== ProjectMembershipRole.Admin && @@ -175,7 +183,7 @@ export const secretApprovalRequestServiceFactory = ({ return reviewStatus; }; - const updateApprovalStatus = async ({ actorId, status, approvalId, actor }: TStatusChangeDTO) => { + const updateApprovalStatus = async ({ actorId, status, approvalId, actor, actorOrgId }: TStatusChangeDTO) => { const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); @@ -184,7 +192,8 @@ export const secretApprovalRequestServiceFactory = ({ const { membership } = await permissionService.getProjectPermission( ActorType.USER, actorId, - secretApprovalRequest.projectId + secretApprovalRequest.projectId, + actorOrgId ); if ( membership.role !== ProjectMembershipRole.Admin && @@ -207,13 +216,18 @@ export const secretApprovalRequestServiceFactory = ({ return { ...secretApprovalRequest, ...updatedRequest }; }; - const mergeSecretApprovalRequest = async ({ approvalId, actor, actorId }: TMergeSecretApprovalRequestDTO) => { + const mergeSecretApprovalRequest = async ({ + approvalId, + actor, + actorId, + actorOrgId + }: TMergeSecretApprovalRequestDTO) => { const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); const { policy, folderId, projectId } = secretApprovalRequest; - const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId); + const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId, actorOrgId); if ( membership.role !== ProjectMembershipRole.Admin && secretApprovalRequest.committerId !== membership.id && @@ -401,6 +415,7 @@ export const secretApprovalRequestServiceFactory = ({ data, actorId, actor, + actorOrgId, policy, projectId, secretPath, @@ -408,7 +423,12 @@ export const secretApprovalRequestServiceFactory = ({ }: TGenerateSecretApprovalRequestDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); - const { permission, membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission, membership } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts index e10d7fa63..75c19c6e9 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -14,13 +14,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/pr import { TSecretRotationDALFactory } from "./secret-rotation-dal"; import { TSecretRotationQueueFactory } from "./secret-rotation-queue"; import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types"; -import { - TCreateSecretRotationDTO, - TDeleteDTO, - TGetByIdDTO, - TListByProjectIdDTO, - TRestartDTO -} from "./secret-rotation-types"; +import { TCreateSecretRotationDTO, TDeleteDTO, TListByProjectIdDTO, TRestartDTO } from "./secret-rotation-types"; import { rotationTemplates } from "./templates"; type TSecretRotationServiceFactoryDep = { @@ -45,8 +39,8 @@ export const secretRotationServiceFactory = ({ folderDAL, secretDAL }: TSecretRotationServiceFactoryDep) => { - const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProviderTemplates = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); return { @@ -59,6 +53,7 @@ export const secretRotationServiceFactory = ({ projectId, actorId, actor, + actorOrgId, inputs, outputs, interval, @@ -66,7 +61,7 @@ export const secretRotationServiceFactory = ({ secretPath, environment }: TCreateSecretRotationDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation @@ -144,23 +139,14 @@ export const secretRotationServiceFactory = ({ return secretRotation; }; - const getById = async ({ rotationId, actor, actorId }: TGetByIdDTO) => { - const [doc] = await secretRotationDAL.find({ id: rotationId }); - if (!doc) throw new BadRequestError({ message: "Rotation not found" }); - - const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); - return doc; - }; - - const getByProjectId = async ({ actorId, projectId, actor }: TListByProjectIdDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getByProjectId = async ({ actorId, projectId, actor, actorOrgId }: TListByProjectIdDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); const doc = await secretRotationDAL.find({ projectId }); return doc; }; - const restartById = async ({ actor, actorId, rotationId }: TRestartDTO) => { + const restartById = async ({ actor, actorId, actorOrgId, rotationId }: TRestartDTO) => { const doc = await secretRotationDAL.findById(rotationId); if (!doc) throw new BadRequestError({ message: "Rotation not found" }); @@ -171,18 +157,18 @@ export const secretRotationServiceFactory = ({ message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation." }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation); await secretRotationQueue.removeFromQueue(doc.id, doc.interval); await secretRotationQueue.addToQueue(doc.id, doc.interval); return doc; }; - const deleteById = async ({ actor, actorId, rotationId }: TDeleteDTO) => { + const deleteById = async ({ actor, actorId, actorOrgId, rotationId }: TDeleteDTO) => { const doc = await secretRotationDAL.findById(rotationId); if (!doc) throw new BadRequestError({ message: "Rotation not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation @@ -197,7 +183,6 @@ export const secretRotationServiceFactory = ({ return { getProviderTemplates, - getById, getByProjectId, createRotation, restartById, diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-types.ts b/backend/src/ee/services/secret-rotation/secret-rotation-types.ts index 52d248765..990bf3eca 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-types.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-types.ts @@ -18,7 +18,3 @@ export type TDeleteDTO = { export type TRestartDTO = { rotationId: string; } & Omit; - -export type TGetByIdDTO = { - rotationId: string; -} & Omit; diff --git a/backend/src/ee/services/secret-scanning/secret-scanning-service.ts b/backend/src/ee/services/secret-scanning/secret-scanning-service.ts index e150f30f3..7066fd485 100644 --- a/backend/src/ee/services/secret-scanning/secret-scanning-service.ts +++ b/backend/src/ee/services/secret-scanning/secret-scanning-service.ts @@ -39,8 +39,8 @@ export const secretScanningServiceFactory = ({ permissionService, secretScanningQueue }: TSecretScanningServiceFactoryDep) => { - const createInstallationSession = async ({ actor, orgId, actorId }: TInstallAppSessionDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const createInstallationSession = async ({ actor, orgId, actorId, actorOrgId }: TInstallAppSessionDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); const sessionId = crypto.randomBytes(16).toString("hex"); @@ -48,11 +48,17 @@ export const secretScanningServiceFactory = ({ return { sessionId }; }; - const linkInstallationToOrg = async ({ sessionId, actorId, installationId, actor }: TLinkInstallSessionDTO) => { + const linkInstallationToOrg = async ({ + sessionId, + actorId, + installationId, + actor, + actorOrgId + }: TLinkInstallSessionDTO) => { const session = await gitAppInstallSessionDAL.findOne({ sessionId }); if (!session) throw new UnauthorizedError({ message: "Session not found" }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, session.orgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, session.orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); const installatedApp = await gitAppOrgDAL.transaction(async (tx) => { await gitAppInstallSessionDAL.deleteById(session.id, tx); @@ -83,23 +89,23 @@ export const secretScanningServiceFactory = ({ return { installatedApp }; }; - const getOrgInstallationStatus = async ({ actorId, orgId, actor }: TGetOrgInstallStatusDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgInstallationStatus = async ({ actorId, orgId, actor, actorOrgId }: TGetOrgInstallStatusDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); const appInstallation = await gitAppOrgDAL.findOne({ orgId }); return Boolean(appInstallation); }; - const getRisksByOrg = async ({ actor, orgId, actorId }: TGetOrgRisksDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getRisksByOrg = async ({ actor, orgId, actorId, actorOrgId }: TGetOrgRisksDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] }); return { risks }; }; - const updateRiskStatus = async ({ actorId, orgId, actor, riskId, status }: TUpdateRiskStatusDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const updateRiskStatus = async ({ actorId, orgId, actor, actorOrgId, riskId, status }: TUpdateRiskStatusDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning); const isRiskResolved = Boolean( diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 26148958f..6ec7a23d5 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -58,9 +58,10 @@ export const secretSnapshotServiceFactory = ({ projectId, actorId, actor, + actorOrgId, path }: TProjectSnapshotCountDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const folder = await folderDAL.findBySecretPath(projectId, environment, path); @@ -75,11 +76,12 @@ export const secretSnapshotServiceFactory = ({ projectId, actorId, actor, + actorOrgId, path, limit = 20, offset = 0 }: TProjectSnapshotListDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const folder = await folderDAL.findBySecretPath(projectId, environment, path); @@ -89,10 +91,10 @@ export const secretSnapshotServiceFactory = ({ return snapshots; }; - const getSnapshotData = async ({ actorId, actor, id }: TGetSnapshotDataDTO) => { + const getSnapshotData = async ({ actorId, actor, actorOrgId, id }: TGetSnapshotDataDTO) => { const snapshot = await snapshotDAL.findSecretSnapshotDataById(id); if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); return snapshot; }; @@ -143,11 +145,11 @@ export const secretSnapshotServiceFactory = ({ } }; - const rollbackSnapshot = async ({ id: snapshotId, actor, actorId }: TRollbackSnapshotDTO) => { + const rollbackSnapshot = async ({ id: snapshotId, actor, actorId, actorOrgId }: TRollbackSnapshotDTO) => { const snapshot = await snapshotDAL.findById(snapshotId); if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback diff --git a/backend/src/ee/services/trusted-ip/trusted-ip-service.ts b/backend/src/ee/services/trusted-ip/trusted-ip-service.ts index a443a32f0..14c73db1f 100644 --- a/backend/src/ee/services/trusted-ip/trusted-ip-service.ts +++ b/backend/src/ee/services/trusted-ip/trusted-ip-service.ts @@ -26,8 +26,8 @@ export const trustedIpServiceFactory = ({ licenseService, projectDAL }: TTrustedIpServiceFactoryDep) => { - const listIpsByProjectId = async ({ projectId, actor, actorId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listIpsByProjectId = async ({ projectId, actor, actorId, actorOrgId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); const trustedIps = await trustedIpDAL.find({ projectId @@ -35,8 +35,16 @@ export const trustedIpServiceFactory = ({ return trustedIps; }; - const addProjectIp = async ({ projectId, actorId, actor, ipAddress: ip, comment, isActive }: TCreateIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const addProjectIp = async ({ + projectId, + actorId, + actor, + actorOrgId, + ipAddress: ip, + comment, + isActive + }: TCreateIpDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); @@ -65,8 +73,16 @@ export const trustedIpServiceFactory = ({ return { trustedIp, project }; // for audit log }; - const updateProjectIp = async ({ projectId, actorId, actor, ipAddress: ip, comment, trustedIpId }: TUpdateIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateProjectIp = async ({ + projectId, + actorId, + actor, + actorOrgId, + ipAddress: ip, + comment, + trustedIpId + }: TUpdateIpDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); @@ -97,8 +113,8 @@ export const trustedIpServiceFactory = ({ return { trustedIp, project }; // for audit log }; - const deleteProjectIp = async ({ projectId, actorId, actor, trustedIpId }: TDeleteIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteProjectIp = async ({ projectId, actorId, actor, actorOrgId, trustedIpId }: TDeleteIpDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); diff --git a/backend/src/lib/types/index.ts b/backend/src/lib/types/index.ts index 5c194d789..918322d62 100644 --- a/backend/src/lib/types/index.ts +++ b/backend/src/lib/types/index.ts @@ -4,12 +4,14 @@ export type TOrgPermission = { actor: ActorType; actorId: string; orgId: string; + actorOrgId?: string; }; export type TProjectPermission = { actor: ActorType; actorId: string; projectId: string; + actorOrgId?: string; }; export type RequiredKeys = { diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 04d4cbe0e..7abcd073c 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -10,6 +10,7 @@ import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-to export type TAuthMode = | { + orgId?: string; authMode: AuthMode.JWT; actor: ActorType.USER; userId: string; @@ -21,6 +22,7 @@ export type TAuthMode = actor: ActorType.USER; userId: string; user: TUsers; + orgId?: string; } | { authMode: AuthMode.SERVICE_TOKEN; @@ -82,8 +84,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { switch (authMode) { case AuthMode.JWT: { - const { user, tokenVersionId } = await server.services.authToken.fnValidateJwtIdentity(token); - req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor }; + const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); + req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; break; } case AuthMode.IDENTITY_ACCESS_TOKEN: { diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index 410621611..572814d64 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -9,7 +9,7 @@ export const injectPermission = fp(async (server) => { if (!req.auth) return; if (req.auth.actor === ActorType.USER) { - req.permission = { type: ActorType.USER, id: req.auth.userId }; + req.permission = { type: ActorType.USER, id: req.auth.userId, orgId: req.auth?.orgId }; } else if (req.auth.actor === ActorType.IDENTITY) { req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId }; } else if (req.auth.actor === ActorType.SERVICE) { diff --git a/backend/src/server/routes/v1/auth-router.ts b/backend/src/server/routes/v1/auth-router.ts index 2dc0a5d50..bd45f59d9 100644 --- a/backend/src/server/routes/v1/auth-router.ts +++ b/backend/src/server/routes/v1/auth-router.ts @@ -88,7 +88,8 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => { authTokenType: AuthTokenType.ACCESS_TOKEN, userId: decodedToken.userId, tokenVersionId: tokenVersion.id, - accessVersion: tokenVersion.accessVersion + accessVersion: tokenVersion.accessVersion, + organizationId: decodedToken.organizationId }, appCfg.AUTH_SECRET, { expiresIn: appCfg.JWT_AUTH_LIFETIME } diff --git a/backend/src/server/routes/v1/bot-router.ts b/backend/src/server/routes/v1/bot-router.ts index 4c6e07ffe..507423b0d 100644 --- a/backend/src/server/routes/v1/bot-router.ts +++ b/backend/src/server/routes/v1/bot-router.ts @@ -29,6 +29,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { const bot = await server.services.projectBot.findBotByProjectId({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.projectId }); return { bot }; @@ -68,6 +69,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { const bot = await server.services.projectBot.setBotActiveState({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, botId: req.params.botId, botKey: req.body.botKey, isActive: req.body.isActive diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index f86cc9528..055735f32 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -26,6 +26,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.createIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, orgId: req.body.organizationId }); @@ -68,6 +69,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.updateIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.identityId, ...req.body }); @@ -106,6 +108,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.deleteIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.identityId }); diff --git a/backend/src/server/routes/v1/identity-ua.ts b/backend/src/server/routes/v1/identity-ua.ts index d92d2a61a..8c56ab431 100644 --- a/backend/src/server/routes/v1/identity-ua.ts +++ b/backend/src/server/routes/v1/identity-ua.ts @@ -112,6 +112,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const identityUniversalAuth = await server.services.identityUa.attachUa({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, identityId: req.params.identityId }); @@ -178,6 +179,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const identityUniversalAuth = await server.services.identityUa.updateUa({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, identityId: req.params.identityId }); @@ -220,6 +222,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const identityUniversalAuth = await server.services.identityUa.getIdentityUa({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId }); @@ -262,6 +265,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const { clientSecret, clientSecretData, orgId } = await server.services.identityUa.createUaClientSecret({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, ...req.body }); @@ -300,6 +304,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUaClientSecrets({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId }); @@ -336,6 +341,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const clientSecretData = await server.services.identityUa.revokeUaClientSecret({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, clientSecretId: req.params.clientSecretId }); diff --git a/backend/src/server/routes/v1/integration-auth-router.ts b/backend/src/server/routes/v1/integration-auth-router.ts index 1d92813f2..4d7aa1b1e 100644 --- a/backend/src/server/routes/v1/integration-auth-router.ts +++ b/backend/src/server/routes/v1/integration-auth-router.ts @@ -53,6 +53,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.getIntegrationAuth({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); return { integrationAuth }; @@ -78,6 +79,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuths({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, integration: req.query.integration, projectId: req.query.projectId }); @@ -115,6 +117,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuthById({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); @@ -154,6 +157,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.oauthExchange({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body }); @@ -196,6 +200,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body }); @@ -242,6 +247,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const apps = await server.services.integrationAuth.getIntegrationApps({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, ...req.query }); @@ -272,6 +278,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const teams = await server.services.integrationAuth.getIntegrationAuthTeams({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); return { teams }; @@ -299,6 +306,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const branches = await server.services.integrationAuth.getVercelBranches({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); @@ -327,6 +335,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const groups = await server.services.integrationAuth.getChecklyGroups({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, accountId: req.query.accountId }); @@ -352,6 +361,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const orgs = await server.services.integrationAuth.getQoveryOrgs({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); return { orgs }; @@ -379,6 +389,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const projects = await server.services.integrationAuth.getQoveryProjects({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, orgId: req.query.orgId }); @@ -407,6 +418,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const environments = await server.services.integrationAuth.getQoveryEnvs({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, projectId: req.query.projectId }); @@ -435,6 +447,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const apps = await server.services.integrationAuth.getQoveryApps({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId }); @@ -463,6 +476,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const containers = await server.services.integrationAuth.getQoveryContainers({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId }); @@ -491,6 +505,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const jobs = await server.services.integrationAuth.getQoveryJobs({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId }); @@ -519,6 +534,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const environments = await server.services.integrationAuth.getRailwayEnvironments({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); @@ -547,6 +563,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const services = await server.services.integrationAuth.getRailwayServices({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); @@ -582,6 +599,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const workspaces = await server.services.integrationAuth.getBitbucketWorkspaces({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); return { workspaces }; @@ -614,6 +632,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const secretGroups = await server.services.integrationAuth.getNorthFlankSecretGroups({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); @@ -647,6 +666,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const buildConfigs = await server.services.integrationAuth.getTeamcityBuildConfigs({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index a7df57a54..ab0ba36eb 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -50,6 +50,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { const { integration, integrationAuth } = await server.services.integration.createIntegration({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body }); await server.services.auditLog.createAuditLog({ @@ -107,6 +108,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { const integration = await server.services.integration.updateIntegration({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationId, ...req.body }); @@ -132,6 +134,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { const integration = await server.services.integration.deleteIntegration({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationId }); diff --git a/backend/src/server/routes/v1/invite-org-router.ts b/backend/src/server/routes/v1/invite-org-router.ts index 67503ac02..0d1fe5070 100644 --- a/backend/src/server/routes/v1/invite-org-router.ts +++ b/backend/src/server/routes/v1/invite-org-router.ts @@ -26,7 +26,8 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => { const completeInviteLink = await server.services.org.inviteUserToOrganization({ orgId: req.body.organizationId, userId: req.permission.id, - inviteeEmail: req.body.inviteeEmail + inviteeEmail: req.body.inviteeEmail, + actorOrgId: req.permission.orgId }); return { diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 1d74e8b1a..bfda652f0 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -37,7 +37,11 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const organization = await server.services.org.findOrganizationById(req.permission.id, req.params.organizationId); + const organization = await server.services.org.findOrganizationById( + req.permission.id, + req.params.organizationId, + req.permission.orgId + ); return { organization }; } }); @@ -68,17 +72,29 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const users = await server.services.org.findAllOrgMembers(req.permission.id, req.params.organizationId); + const users = await server.services.org.findAllOrgMembers( + req.permission.id, + req.params.organizationId, + req.permission.orgId + ); return { users }; } }); server.route({ method: "PATCH", - url: "/:organizationId/name", + url: "/:organizationId", schema: { params: z.object({ organizationId: z.string().trim() }), - body: z.object({ name: z.string().trim() }), + body: z.object({ + name: z.string().trim().optional(), + slug: z + .string() + .trim() + .regex(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens") + .optional(), + authEnforced: z.boolean().optional() + }), response: { 200: z.object({ message: z.string(), @@ -88,11 +104,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const organization = await server.services.org.updateOrgName( - req.permission.id, - req.params.organizationId, - req.body.name - ); + const organization = await server.services.org.updateOrg({ + actor: req.permission.type, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + orgId: req.params.organizationId, + data: req.body + }); + return { message: "Successfully changed organization name", organization @@ -115,7 +134,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const incidentContactsOrg = await req.server.services.org.findIncidentContacts( req.permission.id, - req.params.organizationId + req.params.organizationId, + req.permission.orgId ); return { incidentContactsOrg }; } @@ -138,7 +158,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const incidentContactsOrg = await req.server.services.org.createIncidentContact( req.permission.id, req.params.organizationId, - req.body.email + req.body.email, + req.permission.orgId ); return { incidentContactsOrg }; } @@ -160,7 +181,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const incidentContactsOrg = await req.server.services.org.deleteIncidentContact( req.permission.id, req.params.organizationId, - req.params.incidentContactId + req.params.incidentContactId, + req.permission.orgId ); return { incidentContactsOrg }; } diff --git a/backend/src/server/routes/v1/project-env-router.ts b/backend/src/server/routes/v1/project-env-router.ts index 44be1a3d6..ccf77e765 100644 --- a/backend/src/server/routes/v1/project-env-router.ts +++ b/backend/src/server/routes/v1/project-env-router.ts @@ -30,6 +30,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { const environment = await server.services.projectEnv.createEnvironment({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, ...req.body }); @@ -79,6 +80,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { const { environment, old } = await server.services.projectEnv.updateEnvironment({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, id: req.params.id, ...req.body @@ -129,6 +131,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { const environment = await server.services.projectEnv.deleteEnvironment({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, id: req.params.id }); diff --git a/backend/src/server/routes/v1/project-key-router.ts b/backend/src/server/routes/v1/project-key-router.ts index 482392947..b34260117 100644 --- a/backend/src/server/routes/v1/project-key-router.ts +++ b/backend/src/server/routes/v1/project-key-router.ts @@ -30,6 +30,7 @@ export const registerProjectKeyRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, nonce: req.body.key.nonce, receiverId: req.body.key.userId, encryptedKey: req.body.key.encryptedKey diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index 7d28f470a..1d340c84c 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -35,6 +35,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const memberships = await server.services.projectMembership.getProjectMemberships({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { memberships }; @@ -70,6 +71,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const data = await server.services.projectMembership.addUsersToProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, members: req.body.members }); @@ -112,6 +114,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const membership = await server.services.projectMembership.updateProjectMembership({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, membershipId: req.params.membershipId, role: req.body.role @@ -153,6 +156,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const membership = await server.services.projectMembership.deleteProjectMembership({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, membershipId: req.params.membershipId }); diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index b7574e35c..f0faaa0f4 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -46,6 +46,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const publicKeys = await server.services.projectKey.getProjectPublicKeys({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { publicKeys }; @@ -81,7 +82,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const users = await server.services.projectMembership.getProjectMemberships({ actorId: req.permission.id, actor: req.permission.type, - projectId: req.params.workspaceId + projectId: req.params.workspaceId, + actorOrgId: req.permission.orgId }); return { users }; } @@ -122,6 +124,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.getAProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { workspace }; @@ -148,6 +151,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, orgId: req.body.organizationId, + actorOrgId: req.permission.orgId, workspaceName: req.body.workspaceName }); return { workspace }; @@ -172,6 +176,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.deleteProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { workspace }; @@ -200,6 +205,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.updateName({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, name: req.body.name }); @@ -232,6 +238,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.toggleAutoCapitalization({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, autoCapitalization: req.body.autoCapitalization }); @@ -264,6 +271,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const { invitee, latestKey } = await server.services.projectMembership.inviteUserToProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, email: req.body.email }); @@ -309,6 +317,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const integrations = await server.services.integration.listIntegrationByProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { integrations }; @@ -333,6 +342,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const authorizations = await server.services.integrationAuth.listIntegrationAuthByProjectId({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { authorizations }; @@ -357,6 +367,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const serviceTokenData = await server.services.serviceToken.getProjectServiceTokens({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { serviceTokenData }; diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 4a152f52e..cad682ddc 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -31,6 +31,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folder = await server.services.folder.createFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, path @@ -80,6 +81,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const { folder, old } = await server.services.folder.updateFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, id: req.params.folderId, @@ -129,6 +131,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folder = await server.services.folder.deleteFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, id: req.params.folderId, @@ -174,6 +177,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folders = await server.services.folder.getFolders({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId, path diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v1/secret-import-router.ts index 80f980a90..9f8c8174f 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v1/secret-import-router.ts @@ -36,6 +36,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.createImport({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, data: req.body.import @@ -97,6 +98,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.updateImport({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, projectId: req.body.workspaceId, @@ -150,6 +152,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.deleteImport({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, projectId: req.body.workspaceId @@ -201,6 +204,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImports = await server.services.secretImport.getImports({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId }); @@ -253,6 +257,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const importedSecrets = await server.services.secretImport.getSecretsFromImports({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId }); diff --git a/backend/src/server/routes/v1/secret-tag-router.ts b/backend/src/server/routes/v1/secret-tag-router.ts index 3cafb11d2..7ca3e4893 100644 --- a/backend/src/server/routes/v1/secret-tag-router.ts +++ b/backend/src/server/routes/v1/secret-tag-router.ts @@ -23,6 +23,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { const workspaceTags = await server.services.secretTag.getProjectTags({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.projectId }); return { workspaceTags }; @@ -52,6 +53,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { const workspaceTag = await server.services.secretTag.createTag({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.projectId, ...req.body }); @@ -78,6 +80,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { const workspaceTag = await server.services.secretTag.deleteTag({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.tagId }); return { workspaceTag }; diff --git a/backend/src/server/routes/v1/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 2b3e66398..9a20a5d22 100644 --- a/backend/src/server/routes/v1/webhook-router.ts +++ b/backend/src/server/routes/v1/webhook-router.ts @@ -47,6 +47,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.createWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body }); @@ -92,6 +93,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.updateWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.webhookId, isDisabled: req.body.isDisabled }); @@ -128,6 +130,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.deleteWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.webhookId }); @@ -169,6 +172,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.testWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.webhookId }); return { message: "Successfully tested webhook", webhook }; @@ -200,6 +204,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhooks = await server.services.webhook.listWebhooks({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId }); diff --git a/backend/src/server/routes/v2/identity-org-router.ts b/backend/src/server/routes/v2/identity-org-router.ts index f1beb4e86..ba2bde7cc 100644 --- a/backend/src/server/routes/v2/identity-org-router.ts +++ b/backend/src/server/routes/v2/identity-org-router.ts @@ -34,6 +34,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => { const identityMemberships = await server.services.identity.listOrgIdentities({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.orgId }); return { identityMemberships }; diff --git a/backend/src/server/routes/v2/identity-project-router.ts b/backend/src/server/routes/v2/identity-project-router.ts index ea797e0cb..7698474be 100644 --- a/backend/src/server/routes/v2/identity-project-router.ts +++ b/backend/src/server/routes/v2/identity-project-router.ts @@ -32,6 +32,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMembership = await server.services.identityProject.createProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId, role: req.body.role @@ -62,6 +63,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMembership = await server.services.identityProject.updateProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId, role: req.body.role @@ -89,6 +91,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMembership = await server.services.identityProject.deleteProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId }); @@ -125,6 +128,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMemberships = await server.services.identityProject.listProjectIdentities({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.projectId }); return { identityMemberships }; diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index cbe7f1cbf..2c9465aa8 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -26,7 +26,7 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { const user = await server.store.user.findById(decodedToken.userId); if (!user) throw new Error("User not found"); - req.mfa = { userId: user.id, user }; + req.mfa = { userId: user.id, user, orgId: decodedToken.organizationId }; }); server.route({ @@ -75,6 +75,7 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { userAgent, ip: req.realIp, userId: req.mfa.userId, + orgId: req.mfa.orgId, mfaToken: req.body.mfaToken }); diff --git a/backend/src/server/routes/v2/organization-router.ts b/backend/src/server/routes/v2/organization-router.ts index df94f65c7..8f71ad038 100644 --- a/backend/src/server/routes/v2/organization-router.ts +++ b/backend/src/server/routes/v2/organization-router.ts @@ -33,7 +33,11 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { handler: async (req) => { if (req.auth.actor !== ActorType.USER) return; - const users = await server.services.org.findAllOrgMembers(req.permission.id, req.params.organizationId); + const users = await server.services.org.findAllOrgMembers( + req.permission.id, + req.params.organizationId, + req.permission.orgId + ); return { users }; } }); @@ -68,6 +72,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const workspaces = await server.services.org.findAllWorkspaces({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); @@ -97,7 +102,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { userId: req.permission.id, role: req.body.role, orgId: req.params.organizationId, - membershipId: req.params.membershipId + membershipId: req.params.membershipId, + actorOrgId: req.permission.orgId }); return { membership }; } @@ -121,7 +127,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const membership = await server.services.org.deleteOrgMembership({ userId: req.permission.id, orgId: req.params.organizationId, - membershipId: req.params.membershipId + membershipId: req.params.membershipId, + actorOrgId: req.permission.orgId }); return { membership }; } @@ -172,7 +179,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const organization = await server.services.org.deleteOrganizationById( req.permission.id, - req.params.organizationId + req.params.organizationId, + req.permission.orgId ); return { organization }; } diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index e90a36060..13f7a6a21 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -28,7 +28,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const key = await server.services.projectKey.getLatestProjectKey({ actor: req.permission.type, actorId: req.permission.id, - projectId: req.params.workspaceId + projectId: req.params.workspaceId, + actorOrgId: req.permission.orgId }); await server.services.auditLog.createAuditLog({ diff --git a/backend/src/server/routes/v2/service-token-router.ts b/backend/src/server/routes/v2/service-token-router.ts index ea9912dda..3f2809584 100644 --- a/backend/src/server/routes/v2/service-token-router.ts +++ b/backend/src/server/routes/v2/service-token-router.ts @@ -92,6 +92,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => const { serviceToken, token } = await server.services.serviceToken.createServiceToken({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId }); @@ -129,6 +130,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => const serviceTokenData = await server.services.serviceToken.deleteServiceToken({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.serviceTokenId }); diff --git a/backend/src/server/routes/v3/secret-blind-index-router.ts b/backend/src/server/routes/v3/secret-blind-index-router.ts index 17bf4c4eb..94e6cab83 100644 --- a/backend/src/server/routes/v3/secret-blind-index-router.ts +++ b/backend/src/server/routes/v3/secret-blind-index-router.ts @@ -21,7 +21,8 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) const count = await server.services.secretBlindIndex.getSecretBlindIndexStatus({ projectId: req.params.projectId, actorId: req.permission.id, - actor: req.permission.type + actor: req.permission.type, + actorOrgId: req.permission.orgId }); return count === 0; } @@ -52,7 +53,8 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) const secrets = await server.services.secretBlindIndex.getProjectSecrets({ projectId: req.params.projectId, actorId: req.permission.id, - actor: req.permission.type + actor: req.permission.type, + actorOrgId: req.permission.orgId }); return { secrets }; } @@ -85,7 +87,8 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) projectId: req.params.projectId, secretsToUpdate: req.body.secretsToUpdate, actorId: req.permission.id, - actor: req.permission.type + actor: req.permission.type, + actorOrgId: req.permission.orgId }); return { message: "Successfully named workspace secrets" }; } diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 02f218b67..9c6e8917b 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -81,6 +81,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const { secrets, imports } = await server.services.secret.getSecretsRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment, projectId: workspaceId, path: secretPath, @@ -158,6 +159,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.getSecretByNameRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment, projectId: workspaceId, path: secretPath, @@ -225,6 +227,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.createSecretRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.body.environment, projectId: req.body.workspaceId, secretPath: req.body.secretPath, @@ -293,6 +296,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.updateSecretRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.body.environment, projectId: req.body.workspaceId, secretPath: req.body.secretPath, @@ -357,6 +361,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.deleteSecretRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.body.environment, projectId: req.body.workspaceId, secretPath: req.body.secretPath, @@ -451,6 +456,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const { secrets, imports } = await server.services.secret.getSecrets({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.query.environment, projectId: req.query.workspaceId, path: req.query.secretPath, @@ -536,6 +542,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.getSecretByName({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.query.environment, projectId: req.query.workspaceId, path: req.query.secretPath, @@ -646,6 +653,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -688,6 +696,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.createSecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, type, environment: req.body.environment, @@ -811,6 +820,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -819,6 +829,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -863,6 +874,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.updateSecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, type, environment, @@ -952,6 +964,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -960,6 +973,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -992,6 +1006,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.deleteSecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, type, environment, @@ -1074,6 +1089,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -1082,6 +1098,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -1110,6 +1127,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.createManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, environment, projectId, @@ -1192,6 +1210,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -1200,6 +1219,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -1227,6 +1247,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.updateManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, environment, projectId, @@ -1298,6 +1319,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -1306,6 +1328,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -1332,6 +1355,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.deleteManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: req.body.secretPath, environment, projectId, diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 7fae75c30..59f336e5a 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -141,7 +141,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); - return { user, tokenVersionId: token.tokenVersionId }; + return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; }; return { diff --git a/backend/src/services/auth/auth-fns.ts b/backend/src/services/auth/auth-fns.ts index 3e65f7e05..b46803b06 100644 --- a/backend/src/services/auth/auth-fns.ts +++ b/backend/src/services/auth/auth-fns.ts @@ -12,6 +12,12 @@ export const validateProviderAuthToken = (providerToken: string, email: string) if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError(); if (decodedToken.email !== email) throw new Error("Invalid auth credentials"); + + if (decodedToken.organizationId) { + return { orgId: decodedToken.organizationId }; + } + + return {}; }; export const validateSignUpAuthorization = (token: string, userId: string, validate = true) => { diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index d63baeaca..6e4d60bba 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -56,7 +56,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: * Private * Send mfa code via email * */ - const sendUserMfaCode = async (userId: string, email: string) => { + const sendUserMfaCode = async ({ userId, email }: { userId: string; email: string }) => { const code = await tokenService.createTokenForUser({ type: TokenType.TOKEN_EMAIL_MFA, userId @@ -76,7 +76,17 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: * Check user device and send mail if new device * generate the auth and refresh token. fn shared by mfa verification and login verification with mfa disabled */ - const generateUserTokens = async (user: TUsers, ip: string, userAgent: string) => { + const generateUserTokens = async ({ + user, + ip, + userAgent, + organizationId + }: { + user: TUsers; + ip: string; + userAgent: string; + organizationId?: string; + }) => { const cfg = getConfig(); await updateUserDeviceSession(user, ip, userAgent); const tokenSession = await tokenService.getUserTokenSession({ @@ -90,7 +100,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: authTokenType: AuthTokenType.ACCESS_TOKEN, userId: user.id, tokenVersionId: tokenSession.id, - accessVersion: tokenSession.accessVersion + accessVersion: tokenSession.accessVersion, + organizationId }, cfg.AUTH_SECRET, { expiresIn: cfg.JWT_AUTH_LIFETIME } @@ -101,7 +112,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: authTokenType: AuthTokenType.REFRESH_TOKEN, userId: user.id, tokenVersionId: tokenSession.id, - refreshVersion: tokenSession.refreshVersion + refreshVersion: tokenSession.refreshVersion, + organizationId }, cfg.AUTH_SECRET, { expiresIn: cfg.JWT_REFRESH_LIFETIME } @@ -149,8 +161,14 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (!userEnc) throw new Error("Failed to find user"); const cfg = getConfig(); + let organizationId; if (!userEnc.authMethods?.includes(AuthMethod.EMAIL)) { - validateProviderAuthToken(providerAuthToken as string, email); + const { orgId } = validateProviderAuthToken(providerAuthToken as string, email); + organizationId = orgId; + } else if (providerAuthToken) { + // SAML SSO + const { orgId } = validateProviderAuthToken(providerAuthToken, email); + organizationId = orgId; } if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?"); @@ -169,15 +187,36 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: }); // send multi factor auth token if they it enabled if (userEnc.isMfaEnabled) { - const mfaToken = jwt.sign({ authTokenType: AuthTokenType.MFA_TOKEN, userId: userEnc.userId }, cfg.AUTH_SECRET, { - expiresIn: cfg.JWT_MFA_LIFETIME + const mfaToken = jwt.sign( + { + authTokenType: AuthTokenType.MFA_TOKEN, + userId: userEnc.userId, + organizationId + }, + cfg.AUTH_SECRET, + { + expiresIn: cfg.JWT_MFA_LIFETIME + } + ); + + await sendUserMfaCode({ + userId: userEnc.userId, + email: userEnc.email }); - await sendUserMfaCode(userEnc.userId, userEnc.email); return { isMfaEnabled: true, token: mfaToken } as const; } - const token = await generateUserTokens({ ...userEnc, id: userEnc.userId }, ip, userAgent); + const token = await generateUserTokens({ + user: { + ...userEnc, + id: userEnc.userId + }, + ip, + userAgent, + organizationId + }); + return { token, isMfaEnabled: false, user: userEnc } as const; }; @@ -188,14 +227,17 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: const resendMfaToken = async (userId: string) => { const user = await userDAL.findById(userId); if (!user) return; - await sendUserMfaCode(user.id, user.email); + await sendUserMfaCode({ + userId: user.id, + email: user.email + }); }; /* * Multi factor authentication verification of code * Third step of login in which user completes with mfa * */ - const verifyMfaToken = async ({ userId, mfaToken, ip, userAgent }: TVerifyMfaTokenDTO) => { + const verifyMfaToken = async ({ userId, mfaToken, ip, userAgent, orgId }: TVerifyMfaTokenDTO) => { await tokenService.validateTokenForUser({ type: TokenType.TOKEN_EMAIL_MFA, userId, @@ -204,7 +246,16 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: const userEnc = await userDAL.findUserEncKeyByUserId(userId); if (!userEnc) throw new Error("Failed to authenticate user"); - const token = await generateUserTokens({ ...userEnc, id: userEnc.userId }, ip, userAgent); + const token = await generateUserTokens({ + user: { + ...userEnc, + id: userEnc.userId + }, + ip, + userAgent, + organizationId: orgId + }); + return { token, user: userEnc }; }; /* diff --git a/backend/src/services/auth/auth-login-type.ts b/backend/src/services/auth/auth-login-type.ts index 3d67fef87..67f640bc9 100644 --- a/backend/src/services/auth/auth-login-type.ts +++ b/backend/src/services/auth/auth-login-type.ts @@ -19,6 +19,7 @@ export type TVerifyMfaTokenDTO = { mfaToken: string; ip: string; userAgent: string; + orgId?: string; }; export type TOauthLoginDTO = { diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 6090a2129..752d5d563 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -120,8 +120,10 @@ export const authSignupServiceFactory = ({ throw new Error("Failed to complete account for complete user"); } + let organizationId; if (providerAuthToken) { - validateProviderAuthToken(providerAuthToken, user.email); + const { orgId } = validateProviderAuthToken(providerAuthToken, user.email); + organizationId = orgId; } else { validateSignUpAuthorization(authorization, user.id); } @@ -147,11 +149,7 @@ export const authSignupServiceFactory = ({ return { info: us, key: userEncKey }; }); - const hasSamlEnabled = user?.authMethods?.some((authMethod) => - [AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod as AuthMethod) - ); - - if (!hasSamlEnabled) { + if (!organizationId) { await orgService.createOrganization(user.id, user.email, organizationName); } @@ -175,7 +173,8 @@ export const authSignupServiceFactory = ({ authTokenType: AuthTokenType.ACCESS_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, - accessVersion: tokenSession.accessVersion + accessVersion: tokenSession.accessVersion, + organizationId }, appCfg.AUTH_SECRET, { expiresIn: appCfg.JWT_AUTH_LIFETIME } @@ -186,7 +185,8 @@ export const authSignupServiceFactory = ({ authTokenType: AuthTokenType.REFRESH_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, - refreshVersion: tokenSession.refreshVersion + refreshVersion: tokenSession.refreshVersion, + organizationId }, appCfg.AUTH_SECRET, { expiresIn: appCfg.JWT_REFRESH_LIFETIME } diff --git a/backend/src/services/auth/auth-type.ts b/backend/src/services/auth/auth-type.ts index 26417b0e8..bea0dbe10 100644 --- a/backend/src/services/auth/auth-type.ts +++ b/backend/src/services/auth/auth-type.ts @@ -39,11 +39,13 @@ export type AuthModeJwtTokenPayload = { userId: string; tokenVersionId: string; accessVersion: number; + organizationId?: string; }; export type AuthModeMfaJwtTokenPayload = { authTokenType: AuthTokenType.MFA_TOKEN; userId: string; + organizationId?: string; }; export type AuthModeRefreshJwtTokenPayload = { @@ -51,11 +53,13 @@ export type AuthModeRefreshJwtTokenPayload = { userId: string; tokenVersionId: string; refreshVersion: number; + organizationId?: string; }; export type AuthModeProviderJwtTokenPayload = { authTokenType: AuthTokenType.PROVIDER_TOKEN; email: string; + organizationId?: string; }; export type AuthModeProviderSignUpTokenPayload = { diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index 05e0bd68b..f9d21034f 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -32,8 +32,15 @@ export const identityProjectServiceFactory = ({ identityOrgMembershipDAL, projectDAL }: TIdentityProjectServiceFactoryDep) => { - const createProjectIdentity = async ({ identityId, actor, actorId, projectId, role }: TCreateProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createProjectIdentity = async ({ + identityId, + actor, + actorId, + actorOrgId, + projectId, + role + }: TCreateProjectIdentityDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Identity); const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId }); @@ -72,8 +79,15 @@ export const identityProjectServiceFactory = ({ return projectIdentity; }; - const updateProjectIdentity = async ({ projectId, identityId, role, actor, actorId }: TUpdateProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateProjectIdentity = async ({ + projectId, + identityId, + role, + actor, + actorId, + actorOrgId + }: TUpdateProjectIdentityDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId }); @@ -85,7 +99,8 @@ export const identityProjectServiceFactory = ({ const { permission: identityRolePermission } = await permissionService.getProjectPermission( ActorType.IDENTITY, projectIdentity.identityId, - projectIdentity.projectId + projectIdentity.projectId, + actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) @@ -115,7 +130,13 @@ export const identityProjectServiceFactory = ({ return updatedProjectIdentity; }; - const deleteProjectIdentity = async ({ identityId, actorId, actor, projectId }: TDeleteProjectIdentityDTO) => { + const deleteProjectIdentity = async ({ + identityId, + actorId, + actor, + actorOrgId, + projectId + }: TDeleteProjectIdentityDTO) => { const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId }); if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` }); @@ -123,13 +144,15 @@ export const identityProjectServiceFactory = ({ const { permission } = await permissionService.getProjectPermission( actor, actorId, - identityProjectMembership.projectId + identityProjectMembership.projectId, + actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity); const { permission: identityRolePermission } = await permissionService.getProjectPermission( ActorType.IDENTITY, identityId, - identityProjectMembership.projectId + identityProjectMembership.projectId, + actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) @@ -139,8 +162,8 @@ export const identityProjectServiceFactory = ({ return deletedIdentity; }; - const listProjectIdentities = async ({ projectId, actor, actorId }: TListProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listProjectIdentities = async ({ projectId, actor, actorId, actorOrgId }: TListProjectIdentityDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity); const identityMemberhips = await identityProjectDAL.findByProjectId(projectId); diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index aa5c3c895..60e748ff7 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -127,6 +127,7 @@ export const identityUaServiceFactory = ({ expiresIn: identityAccessToken.accessTokenMaxTTL === 0 ? undefined : identityAccessToken.accessTokenMaxTTL } ); + return { accessToken, identityUa, validClientSecretInfo, identityAccessToken }; }; @@ -138,7 +139,8 @@ export const identityUaServiceFactory = ({ accessTokenTrustedIps, clientSecretTrustedIps, actorId, - actor + actor, + actorOrgId }: TAttachUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); @@ -151,7 +153,12 @@ export const identityUaServiceFactory = ({ throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); } - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); @@ -221,7 +228,8 @@ export const identityUaServiceFactory = ({ accessTokenTrustedIps, clientSecretTrustedIps, actorId, - actor + actor, + actorOrgId }: TUpdateUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); @@ -239,7 +247,12 @@ export const identityUaServiceFactory = ({ throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); } - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); @@ -290,7 +303,7 @@ export const identityUaServiceFactory = ({ return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId }; }; - const getIdentityUa = async ({ identityId, actorId, actor }: TGetUaDTO) => { + const getIdentityUa = async ({ identityId, actorId, actor, actorOrgId }: TGetUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) @@ -300,7 +313,12 @@ export const identityUaServiceFactory = ({ const uaIdentityAuth = await identityUaDAL.findOne({ identityId }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId }; }; @@ -308,6 +326,7 @@ export const identityUaServiceFactory = ({ const createUaClientSecret = async ({ actor, actorId, + actorOrgId, identityId, ttl, description, @@ -319,13 +338,19 @@ export const identityUaServiceFactory = ({ throw new BadRequestError({ message: "The identity does not have universal auth" }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, identityMembershipOrg.identityId, - identityMembershipOrg.orgId + identityMembershipOrg.orgId, + actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); if (!hasPriviledge) @@ -358,20 +383,26 @@ export const identityUaServiceFactory = ({ }; }; - const getUaClientSecrets = async ({ actor, actorId, identityId }: TGetUaClientSecretsDTO) => { + const getUaClientSecrets = async ({ actor, actorId, actorOrgId, identityId }: TGetUaClientSecretsDTO) => { 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 { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, identityMembershipOrg.identityId, - identityMembershipOrg.orgId + identityMembershipOrg.orgId, + actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); if (!hasPriviledge) @@ -390,20 +421,32 @@ export const identityUaServiceFactory = ({ return { clientSecrets, orgId: identityMembershipOrg.orgId }; }; - const revokeUaClientSecret = async ({ identityId, actorId, actor, clientSecretId }: TRevokeUaClientSecretDTO) => { + const revokeUaClientSecret = async ({ + identityId, + actorId, + actor, + actorOrgId, + clientSecretId + }: TRevokeUaClientSecretDTO) => { 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 { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, identityMembershipOrg.identityId, - identityMembershipOrg.orgId + identityMembershipOrg.orgId, + actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); if (!hasPriviledge) diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index 3dd494034..e37a3a6dd 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -25,8 +25,8 @@ export const identityServiceFactory = ({ identityOrgMembershipDAL, permissionService }: TIdentityServiceFactoryDep) => { - const createIdentity = async ({ name, role, actor, orgId, actorId }: TCreateIdentityDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const createIdentity = async ({ name, role, actor, orgId, actorId, actorOrgId }: TCreateIdentityDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole( @@ -54,17 +54,23 @@ export const identityServiceFactory = ({ return identity; }; - const updateIdentity = async ({ id, role, name, actor, actorId }: TUpdateIdentityDTO) => { + const updateIdentity = async ({ id, role, name, actor, actorId, actorOrgId }: TUpdateIdentityDTO) => { const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id }); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityOrgMembership.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityOrgMembership.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); const { permission: identityRolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, id, - identityOrgMembership.orgId + identityOrgMembership.orgId, + actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) @@ -102,11 +108,16 @@ export const identityServiceFactory = ({ return { ...identity, orgId: identityOrgMembership.orgId }; }; - const deleteIdentity = async ({ actorId, actor, id }: TDeleteIdentityDTO) => { + const deleteIdentity = async ({ actorId, actor, actorOrgId, id }: TDeleteIdentityDTO) => { const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id }); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityOrgMembership.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityOrgMembership.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); const { permission: identityRolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -121,8 +132,8 @@ export const identityServiceFactory = ({ return { ...deletedIdentity, orgId: identityOrgMembership.orgId }; }; - const listOrgIdentities = async ({ orgId, actor, actorId }: TOrgPermission) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const listOrgIdentities = async ({ orgId, actor, actorId, actorOrgId }: TOrgPermission) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); const identityMemberhips = await identityOrgMembershipDAL.findByOrgId(orgId); diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 844e01f3c..0b3f9b4c3 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -59,27 +59,40 @@ export const integrationAuthServiceFactory = ({ projectBotDAL, projectBotService }: TIntegrationAuthServiceFactoryDep) => { - const listIntegrationAuthByProjectId = async ({ actorId, actor, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listIntegrationAuthByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const authorizations = await integrationAuthDAL.find({ projectId }); return authorizations; }; - const getIntegrationAuth = async ({ actor, id, actorId }: TGetIntegrationAuthDTO) => { + const getIntegrationAuth = async ({ actor, id, actorId, actorOrgId }: TGetIntegrationAuthDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); return integrationAuth; }; - const oauthExchange = async ({ projectId, actorId, actor, integration, url, code }: TOauthExchangeDTO) => { + const oauthExchange = async ({ + projectId, + actorId, + actor, + actorOrgId, + integration, + url, + code + }: TOauthExchangeDTO) => { if (!Object.values(Integrations).includes(integration as Integrations)) throw new BadRequestError({ message: "Invalid integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); const bot = await projectBotDAL.findOne({ isActive: true, projectId }); @@ -134,6 +147,7 @@ export const integrationAuthServiceFactory = ({ integration, url, actor, + actorOrgId, accessId, namespace, accessToken @@ -141,7 +155,7 @@ export const integrationAuthServiceFactory = ({ if (!Object.values(Integrations).includes(integration as Integrations)) throw new BadRequestError({ message: "Invalid integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); const bot = await projectBotDAL.findOne({ isActive: true, projectId }); @@ -254,11 +268,23 @@ export const integrationAuthServiceFactory = ({ return { accessId, accessToken }; }; - const getIntegrationApps = async ({ actor, actorId, teamId, id, workspaceSlug }: TIntegrationAuthAppsDTO) => { + const getIntegrationApps = async ({ + actor, + actorId, + actorOrgId, + teamId, + id, + workspaceSlug + }: TIntegrationAuthAppsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); @@ -274,11 +300,16 @@ export const integrationAuthServiceFactory = ({ return apps; }; - const getIntegrationAuthTeams = async ({ actor, actorId, id }: TIntegrationAuthTeamsDTO) => { + const getIntegrationAuthTeams = async ({ actor, actorId, actorOrgId, id }: TIntegrationAuthTeamsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); @@ -291,11 +322,16 @@ export const integrationAuthServiceFactory = ({ return teams; }; - const getVercelBranches = async ({ appId, id, actor, actorId }: TIntegrationAuthVercelBranchesDTO) => { + const getVercelBranches = async ({ appId, id, actor, actorId, actorOrgId }: TIntegrationAuthVercelBranchesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -319,11 +355,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getChecklyGroups = async ({ actorId, actor, id, accountId }: TIntegrationAuthChecklyGroupsDTO) => { + const getChecklyGroups = async ({ actorId, actor, actorOrgId, id, accountId }: TIntegrationAuthChecklyGroupsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -340,11 +381,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryOrgs = async ({ actorId, actor, id }: TIntegrationAuthQoveryOrgsDTO) => { + const getQoveryOrgs = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthQoveryOrgsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -361,11 +407,16 @@ export const integrationAuthServiceFactory = ({ return data.results.map(({ name, id: orgId }) => ({ name, orgId })); }; - const getQoveryProjects = async ({ actorId, actor, id, orgId }: TIntegrationAuthQoveryProjectDTO) => { + const getQoveryProjects = async ({ actorId, actor, actorOrgId, id, orgId }: TIntegrationAuthQoveryProjectDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -384,11 +435,22 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryEnvs = async ({ projectId, id, actor, actorId }: TIntegrationAuthQoveryEnvironmentsDTO) => { + const getQoveryEnvs = async ({ + projectId, + id, + actor, + actorId, + actorOrgId + }: TIntegrationAuthQoveryEnvironmentsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -412,11 +474,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryApps = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { + const getQoveryApps = async ({ id, actor, actorId, actorOrgId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -439,11 +506,22 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryContainers = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { + const getQoveryContainers = async ({ + id, + actor, + actorId, + actorOrgId, + environmentId + }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -466,11 +544,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryJobs = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { + const getQoveryJobs = async ({ id, actor, actorId, actorOrgId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -493,11 +576,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getRailwayEnvironments = async ({ id, actor, actorId, appId }: TIntegrationAuthRailwayEnvDTO) => { + const getRailwayEnvironments = async ({ id, actor, actorId, actorOrgId, appId }: TIntegrationAuthRailwayEnvDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -547,11 +635,17 @@ export const integrationAuthServiceFactory = ({ } return []; }; - const getRailwayServices = async ({ id, actor, actorId, appId }: TIntegrationAuthRailwayServicesDTO) => { + + const getRailwayServices = async ({ id, actor, actorId, actorOrgId, appId }: TIntegrationAuthRailwayServicesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -620,11 +714,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getBitbucketWorkspaces = async ({ actorId, actor, id }: TIntegrationAuthBitbucketWorkspaceDTO) => { + const getBitbucketWorkspaces = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthBitbucketWorkspaceDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -658,11 +757,22 @@ export const integrationAuthServiceFactory = ({ return workspaces; }; - const getNorthFlankSecretGroups = async ({ id, actor, actorId, appId }: TIntegrationAuthNorthflankSecretGroupDTO) => { + const getNorthFlankSecretGroups = async ({ + id, + actor, + actorId, + actorOrgId, + appId + }: TIntegrationAuthNorthflankSecretGroupDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -713,11 +823,22 @@ export const integrationAuthServiceFactory = ({ return secretGroups; }; - const getTeamcityBuildConfigs = async ({ appId, id, actorId, actor }: TGetIntegrationAuthTeamCityBuildConfigDTO) => { + const getTeamcityBuildConfigs = async ({ + appId, + id, + actorId, + actorOrgId, + actor + }: TGetIntegrationAuthTeamCityBuildConfigDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -742,19 +863,30 @@ export const integrationAuthServiceFactory = ({ return []; }; - const deleteIntegrationAuths = async ({ projectId, integration, actor, actorId }: TDeleteIntegrationAuthsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteIntegrationAuths = async ({ + projectId, + integration, + actor, + actorId, + actorOrgId + }: TDeleteIntegrationAuthsDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); const integrations = await integrationAuthDAL.delete({ integration, projectId }); return integrations; }; - const deleteIntegrationAuthById = async ({ id, actorId, actor }: TDeleteIntegrationAuthByIdDTO) => { + const deleteIntegrationAuthById = async ({ id, actorId, actor, actorOrgId }: TDeleteIntegrationAuthByIdDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); const delIntegrationAuth = await integrationAuthDAL.transaction(async (tx) => { diff --git a/backend/src/services/integration/integration-service.ts b/backend/src/services/integration/integration-service.ts index b7f74966e..4a6bed75f 100644 --- a/backend/src/services/integration/integration-service.ts +++ b/backend/src/services/integration/integration-service.ts @@ -31,6 +31,7 @@ export const integrationServiceFactory = ({ const createIntegration = async ({ app, actor, + actorOrgId, path, appId, owner, @@ -50,7 +51,12 @@ export const integrationServiceFactory = ({ const integrationAuth = await integrationAuthDAL.findById(integrationAuthId); if (!integrationAuth) throw new BadRequestError({ message: "Integration auth not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath); @@ -86,6 +92,7 @@ export const integrationServiceFactory = ({ const updateIntegration = async ({ actorId, actor, + actorOrgId, targetEnvironment, app, id, @@ -98,7 +105,12 @@ export const integrationServiceFactory = ({ const integration = await integrationDAL.findById(id); if (!integration) throw new BadRequestError({ message: "Integration auth not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integration.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integration.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); const folder = await folderDAL.findBySecretPath(integration.projectId, environment, secretPath); @@ -117,19 +129,24 @@ export const integrationServiceFactory = ({ return updatedIntegration; }; - const deleteIntegration = async ({ actorId, id, actor }: TDeleteIntegrationDTO) => { + const deleteIntegration = async ({ actorId, id, actor, actorOrgId }: TDeleteIntegrationDTO) => { const integration = await integrationDAL.findById(id); if (!integration) throw new BadRequestError({ message: "Integration auth not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integration.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integration.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); const deletedIntegration = await integrationDAL.deleteById(id); return { ...integration, ...deletedIntegration }; }; - const listIntegrationByProject = async ({ actor, actorId, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listIntegrationByProject = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const integrations = await integrationDAL.findByProjectId(projectId); diff --git a/backend/src/services/org/org-dal.ts b/backend/src/services/org/org-dal.ts index 6629030d2..e914aac42 100644 --- a/backend/src/services/org/org-dal.ts +++ b/backend/src/services/org/org-dal.ts @@ -11,11 +11,13 @@ import { TUserEncryptionKeys } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; -import { buildFindFilter, selectAllTableCols, TFindFilter, TFindOpt, withTransaction } from "@app/lib/knex"; +import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt, withTransaction } from "@app/lib/knex"; export type TOrgDALFactory = ReturnType; export const orgDALFactory = (db: TDbClient) => { + const orgOrm = ormify(db, TableName.Organization); + const findOrgById = async (orgId: string) => { try { const org = await db(TableName.Organization).where({ id: orgId }).first(); @@ -177,6 +179,7 @@ export const orgDALFactory = (db: TDbClient) => { }; return withTransaction(db, { + ...orgOrm, findOrgByProjectId, findAllOrgMembers, findOrgById, diff --git a/backend/src/services/org/org-role-service.ts b/backend/src/services/org/org-role-service.ts index c48002d89..b3d8121c3 100644 --- a/backend/src/services/org/org-role-service.ts +++ b/backend/src/services/org/org-role-service.ts @@ -22,8 +22,13 @@ type TOrgRoleServiceFactoryDep = { export type TOrgRoleServiceFactory = ReturnType; export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRoleServiceFactoryDep) => { - const createRole = async (userId: string, orgId: string, data: Omit) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const createRole = async ( + userId: string, + orgId: string, + data: Omit, + actorOrgId?: string + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Role); const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId }); if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" }); @@ -35,8 +40,14 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return role; }; - const updateRole = async (userId: string, orgId: string, roleId: string, data: Omit) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const updateRole = async ( + userId: string, + orgId: string, + roleId: string, + data: Omit, + actorOrgId?: string + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Role); if (data?.slug) { const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId }); @@ -51,8 +62,8 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return updatedRole; }; - const deleteRole = async (userId: string, orgId: string, roleId: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const deleteRole = async (userId: string, orgId: string, roleId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role); const [deletedRole] = await orgRoleDAL.delete({ id: roleId, orgId }); if (!deleteRole) throw new BadRequestError({ message: "Role not found", name: "Update role" }); @@ -60,8 +71,8 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return deletedRole; }; - const listRoles = async (userId: string, orgId: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const listRoles = async (userId: string, orgId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role); const customRoles = await orgRoleDAL.find({ orgId }); const roles = [ @@ -104,8 +115,8 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return roles; }; - const getUserPermission = async (userId: string, orgId: string) => { - const { permission, membership } = await permissionService.getUserOrgPermission(userId, orgId); + const getUserPermission = async (userId: string, orgId: string, actorOrgId?: string) => { + const { permission, membership } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); return { permissions: packRules(permission.rules), membership }; }; diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index a393edcb5..7fafca438 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -29,6 +29,7 @@ import { TDeleteOrgMembershipDTO, TFindAllWorkspacesDTO, TInviteUserToOrgDTO, + TUpdateOrgDTO, TUpdateOrgMembershipDTO, TVerifyUserToOrgDTO } from "./org-types"; @@ -40,7 +41,7 @@ type TOrgServiceFactoryDep = { userDAL: TUserDALFactory; projectDAL: TProjectDALFactory; incidentContactDAL: TIncidentContactsDALFactory; - samlConfigDAL: Pick; + samlConfigDAL: Pick; smtpService: TSmtpService; tokenService: TAuthTokenServiceFactory; permissionService: TPermissionServiceFactory; @@ -68,8 +69,8 @@ export const orgServiceFactory = ({ /* * Get organization details by the organization id * */ - const findOrganizationById = async (userId: string, orgId: string) => { - await permissionService.getUserOrgPermission(userId, orgId); + const findOrganizationById = async (userId: string, orgId: string, actorOrgId?: string) => { + await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); const org = await orgDAL.findOrgById(orgId); if (!org) throw new BadRequestError({ name: "Org not found", message: "Organization not found" }); return org; @@ -84,16 +85,16 @@ export const orgServiceFactory = ({ /* * Get all workspace members * */ - const findAllOrgMembers = async (userId: string, orgId: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const findAllOrgMembers = async (userId: string, orgId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); const members = await orgDAL.findAllOrgMembers(orgId); return members; }; - const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const findAllWorkspaces = async ({ actor, actorId, actorOrgId, orgId }: TFindAllWorkspacesDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace); const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id)); @@ -118,12 +119,36 @@ export const orgServiceFactory = ({ }; /* - * Update organization settings + * Update organization details * */ - const updateOrgName = async (userId: string, orgId: string, name: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const updateOrg = async ({ + actor, + actorId, + actorOrgId, + orgId, + data: { name, slug, authEnforced } + }: TUpdateOrgDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); - const org = await orgDAL.updateById(orgId, { name }); + + if (authEnforced !== undefined) { + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); + } + + if (authEnforced) { + const samlCfg = await samlConfigDAL.findEnforceableSamlCfg(orgId); + if (!samlCfg) + throw new BadRequestError({ + name: "No enforceable SAML config found", + message: "No enforceable SAML config found" + }); + } + + const org = await orgDAL.updateById(orgId, { + name, + slug: slug ? slugify(slug) : undefined, + authEnforced + }); if (!org) throw new BadRequestError({ name: "Org not found", message: "Organization not found" }); return org; }; @@ -191,8 +216,8 @@ export const orgServiceFactory = ({ /* * Delete organization by id * */ - const deleteOrganizationById = async (userId: string, orgId: string) => { - const { membership } = await permissionService.getUserOrgPermission(userId, orgId); + const deleteOrganizationById = async (userId: string, orgId: string, actorOrgId?: string) => { + const { membership } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); if ((membership.role as OrgMembershipRole) !== OrgMembershipRole.Admin) throw new UnauthorizedError({ name: "Delete org by id", message: "Not an admin" }); @@ -206,8 +231,8 @@ export const orgServiceFactory = ({ * Org membership management * Not another service because it has close ties with how an org works doesn't make sense to seperate them * */ - const updateOrgMembership = async ({ role, orgId, userId, membershipId }: TUpdateOrgMembershipDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const updateOrgMembership = async ({ role, orgId, userId, membershipId, actorOrgId }: TUpdateOrgMembershipDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member); const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole); @@ -237,16 +262,18 @@ export const orgServiceFactory = ({ /* * Invite user to organization */ - const inviteUserToOrganization = async ({ orgId, userId, inviteeEmail }: TInviteUserToOrgDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const inviteUserToOrganization = async ({ orgId, userId, inviteeEmail, actorOrgId }: TInviteUserToOrgDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member); - const samlCfg = await samlConfigDAL.findOne({ orgId }); - if (samlCfg && samlCfg.isActive) { + const org = await orgDAL.findOrgById(orgId); + + if (org?.authEnforced) { throw new BadRequestError({ - message: "Failed to invite member due to SAML SSO configured for organization" + message: "Failed to invite user due to org-level auth enforced for organization" }); } + const plan = await licenseService.getPlan(orgId); if (plan.memberLimit !== null && plan.membersUsed >= plan.memberLimit) { // case: limit imposed on number of members allowed @@ -317,7 +344,6 @@ export const orgServiceFactory = ({ orgId }); - const org = await orgDAL.findOrgById(orgId); const user = await userDAL.findById(userId); const appCfg = getConfig(); await smtpService.sendMail({ @@ -394,8 +420,8 @@ export const orgServiceFactory = ({ return { token, user }; }; - const deleteOrgMembership = async ({ orgId, userId, membershipId }: TDeleteOrgMembershipDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const deleteOrgMembership = async ({ orgId, userId, membershipId, actorOrgId }: TDeleteOrgMembershipDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member); const membership = await orgDAL.deleteMembershipById(membershipId, orgId); @@ -407,15 +433,15 @@ export const orgServiceFactory = ({ /* * CRUD operations of incident contacts * */ - const findIncidentContacts = async (userId: string, orgId: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const findIncidentContacts = async (userId: string, orgId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount); const incidentContacts = await incidentContactDAL.findByOrgId(orgId); return incidentContacts; }; - const createIncidentContact = async (userId: string, orgId: string, email: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const createIncidentContact = async (userId: string, orgId: string, email: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount); const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email }); if (doesIncidentContactExist) { @@ -429,8 +455,8 @@ export const orgServiceFactory = ({ return incidentContact; }; - const deleteIncidentContact = async (userId: string, orgId: string, id: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const deleteIncidentContact = async (userId: string, orgId: string, id: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount); const incidentContact = await incidentContactDAL.deleteById(id, orgId); @@ -443,7 +469,7 @@ export const orgServiceFactory = ({ findAllOrganizationOfUser, inviteUserToOrganization, verifyUserToOrg, - updateOrgName, + updateOrg, createOrganization, deleteOrganizationById, deleteOrgMembership, diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index b9dbd74ad..01b3c8e37 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -1,3 +1,5 @@ +import { TOrgPermission } from "@app/lib/types"; + import { ActorType } from "../auth/auth-type"; export type TUpdateOrgMembershipDTO = { @@ -5,17 +7,20 @@ export type TUpdateOrgMembershipDTO = { orgId: string; membershipId: string; role: string; + actorOrgId?: string; }; export type TDeleteOrgMembershipDTO = { userId: string; orgId: string; membershipId: string; + actorOrgId?: string; }; export type TInviteUserToOrgDTO = { userId: string; orgId: string; + actorOrgId?: string; inviteeEmail: string; }; @@ -28,5 +33,10 @@ export type TVerifyUserToOrgDTO = { export type TFindAllWorkspacesDTO = { actor: ActorType; actorId: string; + actorOrgId?: string; orgId: string; }; + +export type TUpdateOrgDTO = { + data: Partial<{ name: string; slug: string; authEnforced: boolean }>; +} & TOrgPermission; diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index 5478aadfa..5ead160f2 100644 --- a/backend/src/services/project-bot/project-bot-service.ts +++ b/backend/src/services/project-bot/project-bot-service.ts @@ -71,8 +71,8 @@ export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: T }); }; - const findBotByProjectId = async ({ actorId, actor, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const findBotByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const appCfg = getConfig(); @@ -120,11 +120,11 @@ export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: T return bot; }; - const setBotActiveState = async ({ actor, botId, botKey, actorId, isActive }: TSetActiveStateDTO) => { + const setBotActiveState = async ({ actor, botId, botKey, actorId, actorOrgId, isActive }: TSetActiveStateDTO) => { const bot = await projectBotDAL.findById(botId); if (!bot) throw new BadRequestError({ message: "Bot not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, bot.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, bot.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); if (isActive) { diff --git a/backend/src/services/project-env/project-env-service.ts b/backend/src/services/project-env/project-env-service.ts index 49657204e..6ebb3a3d6 100644 --- a/backend/src/services/project-env/project-env-service.ts +++ b/backend/src/services/project-env/project-env-service.ts @@ -27,8 +27,8 @@ export const projectEnvServiceFactory = ({ projectDAL, folderDAL }: TProjectEnvServiceFactoryDep) => { - const createEnvironment = async ({ projectId, actorId, actor, name, slug }: TCreateEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createEnvironment = async ({ projectId, actorId, actor, actorOrgId, name, slug }: TCreateEnvDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Environments); const envs = await projectEnvDAL.find({ projectId }); @@ -59,8 +59,17 @@ export const projectEnvServiceFactory = ({ return env; }; - const updateEnvironment = async ({ projectId, slug, actor, actorId, name, id, position }: TUpdateEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateEnvironment = async ({ + projectId, + slug, + actor, + actorId, + actorOrgId, + name, + id, + position + }: TUpdateEnvDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments); const oldEnv = await projectEnvDAL.findOne({ id, projectId }); @@ -85,8 +94,8 @@ export const projectEnvServiceFactory = ({ return { environment: env, old: oldEnv }; }; - const deleteEnvironment = async ({ projectId, actor, actorId, id }: TDeleteEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteEnvironment = async ({ projectId, actor, actorId, actorOrgId, id }: TDeleteEnvDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments); const env = await projectEnvDAL.transaction(async (tx) => { diff --git a/backend/src/services/project-key/project-key-service.ts b/backend/src/services/project-key/project-key-service.ts index e7ebc23ee..fa77760a4 100644 --- a/backend/src/services/project-key/project-key-service.ts +++ b/backend/src/services/project-key/project-key-service.ts @@ -25,11 +25,12 @@ export const projectKeyServiceFactory = ({ receiverId, actor, actorId, + actorOrgId, projectId, nonce, encryptedKey }: TUploadProjectKeyDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); const receiverMembership = await projectMembershipDAL.findOne({ @@ -45,14 +46,14 @@ export const projectKeyServiceFactory = ({ await projectKeyDAL.create({ projectId, receiverId, encryptedKey, nonce, senderId: actorId }); }; - const getLatestProjectKey = async ({ actorId, projectId, actor }: TGetLatestProjectKeyDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId); + const getLatestProjectKey = async ({ actorId, projectId, actor, actorOrgId }: TGetLatestProjectKeyDTO) => { + await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const latestKey = await projectKeyDAL.findLatestProjectKey(actorId, projectId); return latestKey; }; - const getProjectPublicKeys = async ({ actor, actorId, projectId }: TGetLatestProjectKeyDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectPublicKeys = async ({ actor, actorId, actorOrgId, projectId }: TGetLatestProjectKeyDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member); return projectKeyDAL.findAllProjectUserPubKeys(projectId); }; diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index 862c19384..6b2123e5e 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -48,15 +48,15 @@ export const projectMembershipServiceFactory = ({ projectKeyDAL, licenseService }: TProjectMembershipServiceFactoryDep) => { - const getProjectMemberships = async ({ actorId, actor, projectId }: TGetProjectMembershipDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectMemberships = async ({ actorId, actor, actorOrgId, projectId }: TGetProjectMembershipDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member); return projectMembershipDAL.findAllProjectMembers(projectId); }; - const inviteUserToProject = async ({ actorId, actor, projectId, email }: TInviteUserToProjectDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const inviteUserToProject = async ({ actorId, actor, actorOrgId, projectId, email }: TInviteUserToProjectDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); const invitee = await userDAL.findOne({ email }); @@ -112,11 +112,11 @@ export const projectMembershipServiceFactory = ({ return { invitee, latestKey }; }; - const addUsersToProject = async ({ projectId, actorId, actor, members }: TAddUsersToWorkspaceDTO) => { + const addUsersToProject = async ({ projectId, actorId, actor, actorOrgId, members }: TAddUsersToWorkspaceDTO) => { const project = await projectDAL.findById(projectId); if (!project) throw new BadRequestError({ message: "Project not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); const orgMembers = await orgDAL.findMembership({ orgId: project.orgId, @@ -172,11 +172,12 @@ export const projectMembershipServiceFactory = ({ const updateProjectMembership = async ({ actorId, actor, + actorOrgId, projectId, membershipId, role }: TUpdateProjectMembershipDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); const isCustomRole = !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole); @@ -204,8 +205,14 @@ export const projectMembershipServiceFactory = ({ return membership; }; - const deleteProjectMembership = async ({ actorId, actor, projectId, membershipId }: TDeleteProjectMembershipDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteProjectMembership = async ({ + actorId, + actor, + actorOrgId, + projectId, + membershipId + }: TDeleteProjectMembershipDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member); const membership = await projectMembershipDAL.transaction(async (tx) => { diff --git a/backend/src/services/project-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index 4c0633712..7da98b314 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -28,9 +28,10 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: actor: ActorType, actorId: string, projectId: string, - data: Omit + data: Omit, + actorOrgId?: string ) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role); const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId }); if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" }); @@ -47,9 +48,10 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: actorId: string, projectId: string, roleId: string, - data: Omit + data: Omit, + actorOrgId?: string ) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role); if (data?.slug) { const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId }); @@ -64,8 +66,14 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: return updatedRole; }; - const deleteRole = async (actor: ActorType, actorId: string, projectId: string, roleId: string) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteRole = async ( + actor: ActorType, + actorId: string, + projectId: string, + roleId: string, + actorOrgId?: string + ) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role); const [deletedRole] = await projectRoleDAL.delete({ id: roleId, projectId }); if (!deleteRole) throw new BadRequestError({ message: "Role not found", name: "Update role" }); @@ -73,8 +81,8 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: return deletedRole; }; - const listRoles = async (actor: ActorType, actorId: string, projectId: string) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listRoles = async (actor: ActorType, actorId: string, projectId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role); const customRoles = await projectRoleDAL.find({ projectId }); const roles = [ @@ -127,8 +135,8 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: return roles; }; - const getUserPermission = async (userId: string, projectId: string) => { - const { permission, membership } = await permissionService.getUserProjectPermission(userId, projectId); + const getUserPermission = async (userId: string, projectId: string, actorOrgId?: string) => { + const { permission, membership } = await permissionService.getUserProjectPermission(userId, projectId, actorOrgId); return { permissions: packRules(permission.rules), membership }; }; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 08d75b0dd..387c9bd78 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -48,8 +48,8 @@ export const projectServiceFactory = ({ /* * Create workspace. Make user the admin * */ - const createProject = async ({ orgId, actor, actorId, workspaceName }: TCreateProjectDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const createProject = async ({ orgId, actor, actorId, actorOrgId, workspaceName }: TCreateProjectDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); const appCfg = getConfig(); @@ -106,8 +106,8 @@ export const projectServiceFactory = ({ return newProject; }; - const deleteProject = async ({ actor, actorId, projectId }: TDeleteProjectDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteProject = async ({ actor, actorId, actorOrgId, projectId }: TDeleteProjectDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); const deletedProject = await projectDAL.deleteById(projectId); @@ -119,8 +119,8 @@ export const projectServiceFactory = ({ return workspaces; }; - const getAProject = async ({ actorId, projectId, actor }: TGetProjectDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId); + const getAProject = async ({ actorId, actorOrgId, projectId, actor }: TGetProjectDTO) => { + await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); return projectDAL.findProjectById(projectId); }; @@ -128,17 +128,18 @@ export const projectServiceFactory = ({ projectId, actor, actorId, + actorOrgId, autoCapitalization }: TGetProjectDTO & { autoCapitalization: boolean }) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(projectId, { autoCapitalization }); return updatedProject; }; - const updateName = async ({ projectId, actor, actorId, name }: TGetProjectDTO & { name: string }) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateName = async ({ projectId, actor, actorId, actorOrgId, name }: TGetProjectDTO & { name: string }) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(projectId, { name }); diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 2b8c5e908..2ffea1117 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -3,6 +3,7 @@ import { ActorType } from "../auth/auth-type"; export type TCreateProjectDTO = { actor: ActorType; actorId: string; + actorOrgId?: string; orgId: string; workspaceName: string; }; @@ -10,11 +11,13 @@ export type TCreateProjectDTO = { export type TDeleteProjectDTO = { actor: ActorType; actorId: string; + actorOrgId?: string; projectId: string; }; export type TGetProjectDTO = { actor: ActorType; actorId: string; + actorOrgId?: string; projectId: string; }; diff --git a/backend/src/services/secret-blind-index/secret-blind-index-service.ts b/backend/src/services/secret-blind-index/secret-blind-index-service.ts index bb565e295..da7a44df2 100644 --- a/backend/src/services/secret-blind-index/secret-blind-index-service.ts +++ b/backend/src/services/secret-blind-index/secret-blind-index-service.ts @@ -24,8 +24,13 @@ export const secretBlindIndexServiceFactory = ({ permissionService, secretDAL }: TSecretBlindIndexServiceFactoryDep) => { - const getSecretBlindIndexStatus = async ({ actor, projectId, actorId }: TGetProjectBlindIndexStatusDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId); + const getSecretBlindIndexStatus = async ({ + actor, + projectId, + actorId, + actorOrgId + }: TGetProjectBlindIndexStatusDTO) => { + await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId); return Number(secretCount); @@ -45,9 +50,10 @@ export const secretBlindIndexServiceFactory = ({ projectId, actor, actorId, + actorOrgId, secretsToUpdate }: TUpdateProjectSecretNameDTO) => { - const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); if (membership?.role !== ProjectMembershipRole.Admin) { throw new UnauthorizedError({ message: "User must be admin" }); } diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 082674485..53d2a9fdf 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -30,8 +30,16 @@ export const secretFolderServiceFactory = ({ projectEnvDAL, folderVersionDAL }: TSecretFolderServiceFactoryDep) => { - const createFolder = async ({ projectId, actor, actorId, name, environment, path: secretPath }: TCreateFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createFolder = async ({ + projectId, + actor, + actorId, + actorOrgId, + name, + environment, + path: secretPath + }: TCreateFolderDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -105,12 +113,13 @@ export const secretFolderServiceFactory = ({ projectId, actor, actorId, + actorOrgId, name, environment, path: secretPath, id }: TUpdateFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -148,8 +157,16 @@ export const secretFolderServiceFactory = ({ return { folder: newFolder, old: folder }; }; - const deleteFolder = async ({ projectId, actor, actorId, environment, path: secretPath, id }: TDeleteFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteFolder = async ({ + projectId, + actor, + actorId, + actorOrgId, + environment, + path: secretPath, + id + }: TDeleteFolderDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -171,10 +188,17 @@ export const secretFolderServiceFactory = ({ return folder; }; - const getFolders = async ({ projectId, actor, actorId, environment, path: secretPath }: TGetFolderDTO) => { + const getFolders = async ({ + projectId, + actor, + actorId, + actorOrgId, + environment, + path: secretPath + }: TGetFolderDTO) => { // folder list is allowed to be read by anyone // permission to check does user has access - await permissionService.getProjectPermission(actor, actorId, projectId); + await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const env = await projectEnvDAL.findOne({ projectId, slug: environment }); if (!env) throw new BadRequestError({ message: "Environment not found", name: "get folders" }); diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index 57142424f..a519c7820 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -36,8 +36,16 @@ export const secretImportServiceFactory = ({ folderDAL, secretDAL }: TSecretImportServiceFactoryDep) => { - const createImport = async ({ environment, data, actor, actorId, projectId, path }: TCreateSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createImport = async ({ + environment, + data, + actor, + actorId, + actorOrgId, + projectId, + path + }: TCreateSecretImportDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); // check if user has permission to import into destination path ForbiddenError.from(permission).throwUnlessCan( @@ -77,8 +85,17 @@ export const secretImportServiceFactory = ({ return { ...secImport, importEnv }; }; - const updateImport = async ({ path, environment, projectId, actor, actorId, data, id }: TUpdateSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateImport = async ({ + path, + environment, + projectId, + actor, + actorId, + actorOrgId, + data, + id + }: TUpdateSecretImportDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -115,8 +132,16 @@ export const secretImportServiceFactory = ({ return { ...updatedSecImport, importEnv: importedEnv }; }; - const deleteImport = async ({ path, environment, projectId, actor, actorId, id }: TDeleteSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteImport = async ({ + path, + environment, + projectId, + actor, + actorId, + actorOrgId, + id + }: TDeleteSecretImportDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -137,8 +162,8 @@ export const secretImportServiceFactory = ({ return secImport; }; - const getImports = async ({ path, environment, projectId, actor, actorId }: TGetSecretImportsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getImports = async ({ path, environment, projectId, actor, actorId, actorOrgId }: TGetSecretImportsDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -151,8 +176,15 @@ export const secretImportServiceFactory = ({ return secImports; }; - const getSecretsFromImports = async ({ path, environment, projectId, actor, actorId }: TGetSecretsFromImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getSecretsFromImports = async ({ + path, + environment, + projectId, + actor, + actorId, + actorOrgId + }: TGetSecretsFromImportDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) diff --git a/backend/src/services/secret-tag/secret-tag-service.ts b/backend/src/services/secret-tag/secret-tag-service.ts index 361f2d009..62ebd8a23 100644 --- a/backend/src/services/secret-tag/secret-tag-service.ts +++ b/backend/src/services/secret-tag/secret-tag-service.ts @@ -15,8 +15,8 @@ type TSecretTagServiceFactoryDep = { export type TSecretTagServiceFactory = ReturnType; export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => { - const createTag = async ({ name, slug, actor, color, actorId, projectId }: TCreateTagDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createTag = async ({ name, slug, actor, color, actorId, actorOrgId, projectId }: TCreateTagDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Tags); const existingTag = await secretTagDAL.findOne({ slug }); @@ -32,19 +32,19 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe return newTag; }; - const deleteTag = async ({ actorId, actor, id }: TDeleteTagDTO) => { + const deleteTag = async ({ actorId, actor, actorOrgId, id }: TDeleteTagDTO) => { const tag = await secretTagDAL.findById(id); if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, tag.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, tag.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags); const deletedTag = await secretTagDAL.deleteById(tag.id); return deletedTag; }; - const getProjectTags = async ({ actor, actorId, projectId }: TListProjectTagsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectTags = async ({ actor, actorId, actorOrgId, projectId }: TListProjectTagsDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); const tags = await secretTagDAL.find({ projectId }, { sort: [["createdAt", "asc"]] }); diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 0071216b1..ff9dce9a8 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -37,7 +37,6 @@ import { TGetSecretsDTO, TGetSecretsRawDTO, TGetSecretVersionsDTO, - TListSecretVersionDTO, TUpdateBulkSecretDTO, TUpdateSecretDTO, TUpdateSecretRawDTO @@ -267,8 +266,16 @@ export const secretServiceFactory = ({ return { secsGroupedByBlindIndex, secrets }; }; - const createSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TCreateSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createSecret = async ({ + path, + actor, + actorId, + actorOrgId, + environment, + projectId, + ...inputSecret + }: TCreateSecretDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -337,8 +344,16 @@ export const secretServiceFactory = ({ return { ...secret[0], environment, workspace: projectId, tags }; }; - const updateSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TUpdateSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateSecret = async ({ + path, + actor, + actorId, + actorOrgId, + environment, + projectId, + ...inputSecret + }: TUpdateSecretDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -429,8 +444,16 @@ export const secretServiceFactory = ({ return { ...updatedSecret[0], workspace: projectId, environment }; }; - const deleteSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TDeleteSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteSecret = async ({ + path, + actor, + actorId, + actorOrgId, + environment, + projectId, + ...inputSecret + }: TDeleteSecretDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -476,8 +499,16 @@ export const secretServiceFactory = ({ return { ...deletedSecret[0], _id: deletedSecret[0].id, workspace: projectId, environment }; }; - const getSecrets = async ({ actorId, path, environment, projectId, actor, includeImports }: TGetSecretsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getSecrets = async ({ + actorId, + path, + environment, + projectId, + actor, + actorOrgId, + includeImports + }: TGetSecretsDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -518,6 +549,7 @@ export const secretServiceFactory = ({ const getSecretByName = async ({ actorId, actor, + actorOrgId, projectId, environment, path, @@ -526,7 +558,7 @@ export const secretServiceFactory = ({ version, includeImports }: TGetASecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -607,11 +639,12 @@ export const secretServiceFactory = ({ path, actor, actorId, + actorOrgId, environment, projectId, secrets: inputSecrets }: TCreateBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -661,11 +694,12 @@ export const secretServiceFactory = ({ path, actor, actorId, + actorOrgId, environment, projectId, secrets: inputSecrets }: TUpdateBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -733,9 +767,10 @@ export const secretServiceFactory = ({ environment, projectId, actor, - actorId + actorId, + actorOrgId }: TDeleteBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -774,20 +809,15 @@ export const secretServiceFactory = ({ return secretsDeleted; }; - const listSecretVersionsBySecretId = async ({ actorId, actor, limit, offset, secretId }: TListSecretVersionDTO) => { - const secret = await secretDAL.findById(secretId); - if (!secret) throw new BadRequestError({ message: "Failed to find secret" }); - - const folder = await folderDAL.findById(secret.folderId); - if (!folder) throw new BadRequestError({ message: "Folder not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, folder.projectId); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); - - const secretVersions = await secretVersionDAL.find({ secretId }, { limit, offset, sort: [["createdAt", "desc"]] }); - return secretVersions; - }; - - const getSecretsRaw = async ({ projectId, path, actor, actorId, environment, includeImports }: TGetSecretsRawDTO) => { + const getSecretsRaw = async ({ + projectId, + path, + actor, + actorId, + actorOrgId, + environment, + includeImports + }: TGetSecretsRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); @@ -796,6 +826,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, path, includeImports }); @@ -818,6 +849,7 @@ export const secretServiceFactory = ({ environment, projectId, actorId, + actorOrgId, secretName, includeImports, version @@ -830,6 +862,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, path, secretName, type, @@ -845,6 +878,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, type, secretPath, secretValue, @@ -866,6 +900,7 @@ export const secretServiceFactory = ({ path: secretPath, actor, actorId, + actorOrgId, secretKeyCiphertext: secretKeyEncrypted.ciphertext, secretKeyIV: secretKeyEncrypted.iv, secretKeyTag: secretKeyEncrypted.tag, @@ -890,6 +925,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, type, secretPath, secretValue, @@ -908,6 +944,7 @@ export const secretServiceFactory = ({ path: secretPath, actor, actorId, + actorOrgId, secretValueCiphertext: secretValueEncrypted.ciphertext, secretValueIV: secretValueEncrypted.iv, secretValueTag: secretValueEncrypted.tag, @@ -926,6 +963,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, type, secretPath }: TDeleteSecretRawDTO) => { @@ -939,7 +977,8 @@ export const secretServiceFactory = ({ type, path: secretPath, actor, - actorId + actorId, + actorOrgId }); await snapshotService.performSnapshot(secret.folderId); @@ -948,14 +987,21 @@ export const secretServiceFactory = ({ return decryptSecretRaw(secret, botKey); }; - const getSecretVersions = async ({ actorId, actor, limit = 20, offset = 0, secretId }: TGetSecretVersionsDTO) => { + const getSecretVersions = async ({ + actorId, + actor, + actorOrgId, + limit = 20, + offset = 0, + secretId + }: TGetSecretVersionsDTO) => { const secret = await secretDAL.findById(secretId); if (!secret) throw new BadRequestError({ message: "Failed to find secret" }); const folder = await folderDAL.findById(secret.folderId); if (!folder) throw new BadRequestError({ message: "Failed to find secret" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, folder.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, folder.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] }); @@ -976,7 +1022,6 @@ export const secretServiceFactory = ({ createSecretRaw, updateSecretRaw, deleteSecretRaw, - listSecretVersionsBySecretId, getSecretVersions, // external services function fnSecretBulkDelete, diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index 89e16d77b..50f678172 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -128,12 +128,6 @@ export type TDeleteBulkSecretDTO = { }>; } & TProjectPermission; -export type TListSecretVersionDTO = { - secretId: string; - offset?: number; - limit?: number; -} & Omit; - export type TGetSecretsRawDTO = { path: string; environment: string; diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 9a4fd6afe..76d33bd6b 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -39,6 +39,7 @@ export const serviceTokenServiceFactory = ({ tag, name, actor, + actorOrgId, scopes, actorId, projectId, @@ -46,7 +47,7 @@ export const serviceTokenServiceFactory = ({ permissions, encryptedKey }: TCreateServiceTokenDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens); scopes.forEach(({ environment, secretPath }) => { @@ -90,11 +91,16 @@ export const serviceTokenServiceFactory = ({ return { token, serviceToken }; }; - const deleteServiceToken = async ({ actorId, actor, id }: TDeleteServiceTokenDTO) => { + const deleteServiceToken = async ({ actorId, actor, actorOrgId, id }: TDeleteServiceTokenDTO) => { const serviceToken = await serviceTokenDAL.findById(id); if (!serviceToken) throw new BadRequestError({ message: "Token not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, serviceToken.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + serviceToken.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens); const deletedServiceToken = await serviceTokenDAL.deleteById(id); @@ -113,8 +119,8 @@ export const serviceTokenServiceFactory = ({ return { serviceToken, user: serviceTokenUser }; }; - const getProjectServiceTokens = async ({ actorId, actor, projectId }: TProjectServiceTokensDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectServiceTokens = async ({ actorId, actor, actorOrgId, projectId }: TProjectServiceTokensDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens); const tokens = await serviceTokenDAL.find({ projectId }, { sort: [["createdAt", "desc"]] }); diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 48a192e16..1144bd414 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -99,7 +99,12 @@ export const superAdminServiceFactory = ({ await orgService.createOrganization(userInfo.user.id, userInfo.user.email, initialOrganizationName); await updateServerCfg({ initialized: true }); - const token = await authService.generateUserTokens(userInfo.user, ip, userAgent); + const token = await authService.generateUserTokens({ + user: userInfo.user, + ip, + userAgent, + organizationId: undefined + }); // TODO(akhilmhdh-pg): telemetry service return { token, user: userInfo }; }; diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index b700869c2..eebfd958f 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -30,15 +30,6 @@ export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => { const user = await userDAL.findById(userId); if (!user) throw new BadRequestError({ name: "Update auth methods" }); - const hasSamlEnabled = user?.authMethods?.some((method) => - [AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(method as AuthMethod) - ); - if (hasSamlEnabled) - throw new BadRequestError({ - name: "Update auth method", - message: "Failed to update auth methods due to SAML SSO " - }); - const updatedUser = await userDAL.updateById(userId, { authMethods }); return updatedUser; }; diff --git a/backend/src/services/webhook/webhook-service.ts b/backend/src/services/webhook/webhook-service.ts index c208ba472..c3919cc50 100644 --- a/backend/src/services/webhook/webhook-service.ts +++ b/backend/src/services/webhook/webhook-service.ts @@ -30,13 +30,14 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer const createWebhook = async ({ actor, actorId, + actorOrgId, projectId, webhookUrl, environment, secretPath, webhookSecretKey }: TCreateWebhookDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks); const env = await projectEnvDAL.findOne({ projectId, slug: environment }); if (!env) throw new BadRequestError({ message: "Env not found" }); @@ -72,33 +73,33 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer return { ...webhook, projectId, environment: env }; }; - const updateWebhook = async ({ actorId, actor, id, isDisabled }: TUpdateWebhookDTO) => { + const updateWebhook = async ({ actorId, actor, actorOrgId, id, isDisabled }: TUpdateWebhookDTO) => { const webhook = await webhookDAL.findById(id); if (!webhook) throw new BadRequestError({ message: "Webhook not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks); const updatedWebhook = await webhookDAL.updateById(id, { isDisabled }); return { ...webhook, ...updatedWebhook }; }; - const deleteWebhook = async ({ id, actor, actorId }: TDeleteWebhookDTO) => { + const deleteWebhook = async ({ id, actor, actorId, actorOrgId }: TDeleteWebhookDTO) => { const webhook = await webhookDAL.findById(id); if (!webhook) throw new BadRequestError({ message: "Webhook not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks); const deletedWebhook = await webhookDAL.deleteById(id); return { ...webhook, ...deletedWebhook }; }; - const testWebhook = async ({ id, actor, actorId }: TTestWebhookDTO) => { + const testWebhook = async ({ id, actor, actorId, actorOrgId }: TTestWebhookDTO) => { const webhook = await webhookDAL.findById(id); if (!webhook) throw new BadRequestError({ message: "Webhook not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); let webhookError: string | undefined; @@ -118,8 +119,8 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer return { ...webhook, ...updatedWebhook }; }; - const listWebhooks = async ({ actorId, actor, projectId, secretPath, environment }: TListWebhookDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listWebhooks = async ({ actorId, actor, actorOrgId, projectId, secretPath, environment }: TListWebhookDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); return webhookDAL.findAllWebhooks(projectId, environment, secretPath); diff --git a/docs/documentation/platform/sso/okta.mdx b/docs/documentation/platform/sso/okta.mdx index c07aca9ac..a892ebfb3 100644 --- a/docs/documentation/platform/sso/okta.mdx +++ b/docs/documentation/platform/sso/okta.mdx @@ -74,9 +74,16 @@ description: "Configure Okta SAML 2.0 for Infisical SSO" At this point, you have configured everything you need within the context of the Okta Admin Portal. - Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta. + Enabling SAML SSO allows members in your organization to log into Infisical via Okta. - ![SAML Okta assignment](../../../images/sso/okta/enable-saml.png) + ![SAML Okta enable SAML](../../../images/sso/okta/enable-saml.png) + + + Enforcing SAML SSO requires members in your organization can only access Infisical + by logging into the organization via Okta. + + To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Okta user with Infisical; + Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. diff --git a/frontend/src/components/signup/InitialSignupStep.tsx b/frontend/src/components/signup/InitialSignupStep.tsx index 7953ff0df..e5c23f333 100644 --- a/frontend/src/components/signup/InitialSignupStep.tsx +++ b/frontend/src/components/signup/InitialSignupStep.tsx @@ -1,9 +1,7 @@ import { useTranslation } from "react-i18next"; import Link from "next/link"; -import { useRouter } from "next/router"; import { faGithub, faGitlab, faGoogle } from "@fortawesome/free-brands-svg-icons"; import { faEnvelope } from "@fortawesome/free-regular-svg-icons"; -import { faLock } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button } from "../v2"; @@ -14,7 +12,6 @@ export default function InitialSignupStep({ setIsSignupWithEmail: (value: boolean) => void; }) { const { t } = useTranslation(); - const router = useRouter(); return (
@@ -76,17 +73,6 @@ export default function InitialSignupStep({ Continue with Email
-
- -
{t("signup.create-policy")}
diff --git a/frontend/src/context/OrganizationContext/OrganizationContext.tsx b/frontend/src/context/OrganizationContext/OrganizationContext.tsx index a4562ef82..e73578d13 100644 --- a/frontend/src/context/OrganizationContext/OrganizationContext.tsx +++ b/frontend/src/context/OrganizationContext/OrganizationContext.tsx @@ -25,7 +25,7 @@ export const OrgProvider = ({ children }: Props): JSX.Element => { const value = useMemo( () => ({ orgs: userOrgs, - currentOrg: (userOrgs || []).find(({ id }) => id === currentWsOrgID) || (userOrgs || [])[0], + currentOrg: (userOrgs || []).find(({ id }) => id === currentWsOrgID), isLoading }), [currentWsOrgID, userOrgs, isLoading] diff --git a/frontend/src/hooks/api/organization/index.ts b/frontend/src/hooks/api/organization/index.ts index c8a284836..6a8af3b67 100644 --- a/frontend/src/hooks/api/organization/index.ts +++ b/frontend/src/hooks/api/organization/index.ts @@ -17,6 +17,6 @@ export { useGetOrgPmtMethods, useGetOrgTaxIds, useGetOrgTrialUrl, - useRenameOrg, + useUpdateOrg, useUpdateOrgBillingDetails } from "./queries"; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 0edaf2c2f..018fa1edb 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -12,8 +12,8 @@ import { PlanBillingInfo, PmtMethod, ProductsTable, - RenameOrgDTO, - TaxID + TaxID, + UpdateOrgDTO } from "./types"; export const organizationKeys = { @@ -65,12 +65,20 @@ export const useCreateOrg = () => { }); }; -export const useRenameOrg = () => { +export const useUpdateOrg = () => { const queryClient = useQueryClient(); - - return useMutation<{}, {}, RenameOrgDTO>({ - mutationFn: ({ newOrgName, orgId }) => { - return apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }); + return useMutation<{}, {}, UpdateOrgDTO>({ + mutationFn: ({ + name, + authEnforced, + slug, + orgId + }) => { + return apiRequest.patch(`/api/v1/organization/${orgId}`, { + name, + authEnforced, + slug + }); }, onSuccess: () => { queryClient.invalidateQueries(organizationKeys.getUserOrganizations); diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 3bfbd7419..6492e3515 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -3,11 +3,15 @@ export type Organization = { name: string; createAt: string; updatedAt: string; + authEnforced: boolean; + slug: string; }; -export type RenameOrgDTO = { +export type UpdateOrgDTO = { orgId: string; - newOrgName: string; + name?: string; + authEnforced?: boolean; + slug?: string; }; export type BillingDetails = { diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 075c9d6a9..d175afe48 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -310,10 +310,25 @@ export const AppLayout = ({ children }: LayoutProps) => {
{user?.email}
- {orgs?.map((org) => ( + {orgs?.map((org) => { + return ( - ))} + ) + })} {/*
diff --git a/frontend/src/views/Login/components/SAMLSSOStep/SAMLSSOStep.tsx b/frontend/src/views/Login/components/SAMLSSOStep/SAMLSSOStep.tsx index b4da2ce92..4a4545ac8 100644 --- a/frontend/src/views/Login/components/SAMLSSOStep/SAMLSSOStep.tsx +++ b/frontend/src/views/Login/components/SAMLSSOStep/SAMLSSOStep.tsx @@ -18,14 +18,14 @@ export const SAMLSSOStep = ({ const handleSubmission = (e:React.FormEvent) => { e.preventDefault() const callbackPort = queryParams.get("callback_port"); - window.open(`/api/v1/sso/redirect/saml2/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`); + window.open(`/api/v1/sso/redirect/saml2/organizations/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`); window.close(); } return (

- What's your SSO Identifier? + What's your organization slug?

@@ -34,7 +34,7 @@ export const SAMLSSOStep = ({ value={ssoIdentifier} onChange={(e) => setSSOIdentifier(e.target.value)} type="text" - placeholder="Enter your SSO identifier..." + placeholder="acme-123" isRequired autoComplete="email" id="email" @@ -50,7 +50,7 @@ export const SAMLSSOStep = ({ isFullWidth className="h-14" > - {t("login.login")} + Continue with SAML
diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx index 3f2a8cc15..246922b58 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx @@ -16,7 +16,7 @@ import { useOrganization, useSubscription } from "@app/context"; -import { useDeleteOrgMembership, useGetSSOConfig } from "@app/hooks/api"; +import { useDeleteOrgMembership } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { AddOrgMemberModal } from "./AddOrgMemberModal"; @@ -27,10 +27,9 @@ export const OrgMembersSection = () => { const { subscription } = useSubscription(); const { currentOrg } = useOrganization(); const orgId = currentOrg?.id ?? ""; - + const [completeInviteLink, setCompleteInviteLink] = useState(""); - const { data: ssoConfig, isLoading: isLoadingSSOConfig } = useGetSSOConfig(orgId); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "addMember", "removeMember", @@ -45,9 +44,9 @@ export const OrgMembersSection = () => { : false; const handleAddMemberModal = () => { - if (!isLoadingSSOConfig && ssoConfig && ssoConfig.isActive) { + if (currentOrg?.authEnforced) { createNotification({ - text: "You cannot invite users when SAML SSO is configured for your organization", + text: "You cannot invite users when org-level auth is configured for your organization", type: "error" }); return; diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx deleted file mode 100644 index f163bd530..000000000 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx +++ /dev/null @@ -1,577 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { useRouter } from "next/router"; -import { - faCheck, - faCopy, - faMagnifyingGlass, - faPlus, - faTrash, - faUsers -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; - -import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import { OrgPermissionCan } from "@app/components/permissions"; -import { - decryptAssymmetric, - encryptAssymmetric -} from "@app/components/utilities/cryptography/crypto"; -import { - Button, - DeleteActionModal, - EmailServiceSetupModal, - EmptyState, - FormControl, - IconButton, - Input, - Modal, - ModalContent, - Select, - SelectItem, - Table, - TableContainer, - TableSkeleton, - Tag, - TBody, - Td, - Th, - THead, - Tr, - UpgradePlanModal -} from "@app/components/v2"; -import { - OrgPermissionActions, - OrgPermissionSubjects, - useOrganization, - useSubscription, - useUser, - useWorkspace -} from "@app/context"; -import { usePopUp, useToggle } from "@app/hooks"; -import { - useAddUserToOrg, - useDeleteOrgMembership, - useGetOrgUsers, - useGetSSOConfig, - useGetUserWorkspaceMemberships, - useGetUserWsKey, - useUpdateOrgUserRole, - useUploadWsKey -} from "@app/hooks/api"; -import { TProjectRole } from "@app/hooks/api/roles/types"; -import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; - -type Props = { - roles?: TProjectRole[]; - isRolesLoading?: boolean; -}; - -const addMemberFormSchema = yup.object({ - email: yup.string().email().required().label("Email").trim().lowercase() -}); - -type TAddMemberForm = yup.InferType; - -export const OrgMembersTable = ({ roles = [], isRolesLoading }: Props) => { - const router = useRouter(); - const { createNotification } = useNotificationContext(); - - const { currentOrg } = useOrganization(); - const { workspaces, currentWorkspace } = useWorkspace(); - const { user } = useUser(); - const userId = user?.id || ""; - const orgId = currentOrg?.id || ""; - const workspaceId = currentWorkspace?.id || ""; - - const { data: ssoConfig, isLoading: isLoadingSSOConfig } = useGetSSOConfig(orgId); - const [searchMemberFilter, setSearchMemberFilter] = useState(""); - const { data: serverDetails } = useFetchServerStatus(); - - const [isInviteLinkCopied, setInviteLinkCopied] = useToggle(false); - const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ - "addMember", - "removeMember", - "upgradePlan", - "setUpEmail" - ] as const); - const { subscription } = useSubscription(); - - const { data: members, isLoading: isMembersLoading } = useGetOrgUsers(orgId); - const { data: workspaceMemberships, isLoading: IsWsMembershipLoading } = - useGetUserWorkspaceMemberships(orgId); - const { data: wsKey } = useGetUserWsKey(workspaceId); - - const removeUserOrgMembership = useDeleteOrgMembership(); - const addUserToOrg = useAddUserToOrg(); - const updateOrgUserRole = useUpdateOrgUserRole(); - const uploadWsKey = useUploadWsKey(); - - const [completeInviteLink, setCompleteInviteLink] = useState(""); - - const isMoreUsersNotAllowed = subscription?.memberLimit - ? subscription.membersUsed >= subscription.memberLimit - : false; - - useEffect(() => { - if (router.query.action === "invite") { - handlePopUpOpen("addMember"); - } - }, []); - - const { - control, - handleSubmit, - reset, - formState: { isSubmitting } - } = useForm({ resolver: yupResolver(addMemberFormSchema) }); - - const onAddMember = async ({ email }: TAddMemberForm) => { - if (!currentOrg?.id) return; - - try { - const { data } = await addUserToOrg.mutateAsync({ - organizationId: currentOrg?.id, - inviteeEmail: email - }); - setCompleteInviteLink(data?.completeInviteLink); - // only show this notification when email is configured. - // A [completeInviteLink] will not be sent if smtp is configured - if (!data.completeInviteLink) { - createNotification({ - text: "Successfully invited user to the organization.", - type: "success" - }); - } - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to invite user to org", - type: "error" - }); - } - if (serverDetails?.emailConfigured) { - handlePopUpClose("addMember"); - } - reset(); - }; - - const onAddUserToOrg = async (email: string) => { - if (!currentOrg?.id) return; - - try { - const { data } = await addUserToOrg.mutateAsync({ - organizationId: currentOrg?.id, - inviteeEmail: email - }); - setCompleteInviteLink(data?.completeInviteLink); - - // only show this notification when email is configured. A [completeInviteLink] will not be sent if smtp is configured - if (!data.completeInviteLink) { - createNotification({ - text: "Successfully invited user to the organization.", - type: "success" - }); - } - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to invite user to org", - type: "error" - }); - } - }; - - const onRemoveOrgMemberApproved = async () => { - const membershipId = (popUp?.removeMember?.data as { id: string })?.id; - if (!currentOrg?.id) return; - - try { - await removeUserOrgMembership.mutateAsync({ orgId: currentOrg?.id, membershipId }); - createNotification({ - text: "Successfully removed user from org", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove user from the organization", - type: "error" - }); - } - handlePopUpClose("removeMember"); - }; - - const isIamOwner = useMemo( - () => members?.find(({ user: u }) => userId === u?.id)?.role === "owner", - [userId, members] - ); - - const findRoleFromId = useCallback( - (roleId: string) => { - return roles.find(({ id }) => id === roleId); - }, - [roles] - ); - - const filterdUser = useMemo( - () => - members?.filter( - ({ user: u, inviteEmail }) => - u?.firstName?.toLowerCase().includes(searchMemberFilter) || - u?.lastName?.toLowerCase().includes(searchMemberFilter) || - u?.email?.toLowerCase().includes(searchMemberFilter) || - inviteEmail?.includes(searchMemberFilter) - ), - [members, searchMemberFilter] - ); - - useEffect(() => { - let timer: NodeJS.Timeout; - if (isInviteLinkCopied) { - timer = setTimeout(() => setInviteLinkCopied.off(), 2000); - } - return () => clearTimeout(timer); - }, [isInviteLinkCopied]); - - const onRoleChange = async (membershipId: string, role: string) => { - if (!currentOrg?.id) return; - - try { - await updateOrgUserRole.mutateAsync({ organizationId: currentOrg?.id, membershipId, role }); - createNotification({ - text: "Successfully updated user role", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to update user role", - type: "error" - }); - } - }; - - const onGrantAccess = async (grantedUserId: string, publicKey: string) => { - try { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - if (!PRIVATE_KEY || !wsKey) return; - - // assymmetrically decrypt symmetric key with local private key - const key = decryptAssymmetric({ - ciphertext: wsKey.encryptedKey, - nonce: wsKey.nonce, - publicKey: wsKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: key, - publicKey, - privateKey: PRIVATE_KEY - }); - - await uploadWsKey.mutateAsync({ - userId: grantedUserId, - nonce, - encryptedKey: ciphertext, - workspaceId: currentWorkspace?.id || "" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to grant access to user", - type: "error" - }); - } - }; - - const copyTokenToClipboard = () => { - navigator.clipboard.writeText(completeInviteLink as string); - setInviteLinkCopied.on(); - }; - - const isLoading = isMembersLoading || IsWsMembershipLoading || isRolesLoading; - - return ( -
-
-
- setSearchMemberFilter(e.target.value)} - leftIcon={} - placeholder="Search members..." - /> -
- - {(isAllowed) => ( - - )} - -
-
- - - - - - - - - - - - {isLoading && } - {!isLoading && - filterdUser?.map( - ({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => { - const name = u ? `${u.firstName || "-"} ${u.lastName || ""}` : "-"; - const email = u?.email || inviteEmail; - const userWs = workspaceMemberships?.[u?.id]; - - return ( - - - - - - - - ); - } - )} - -
NameEmailRoleProjects -
{name}{email} - - {(isAllowed) => ( - <> - {status === "accepted" && ( - - )} - {(status === "invited" || status === "verified") && - serverDetails?.emailConfigured && ( - - )} - {status === "completed" && ( - - )} - - )} - - - {userWs ? ( - userWs?.map(({ name: wsName, id }) => ( - - {wsName} - - )) - ) : ( -
- {(status === "invited" || status === "verified") && - serverDetails?.emailConfigured ? ( - - This user hasn't accepted the invite yet - - ) : ( - - This user isn't part of any projects yet - - )} - {router.query.id !== "undefined" && - !( - (status === "invited" || status === "verified") && - serverDetails?.emailConfigured - ) && ( - - )} -
- )} -
- {userId !== u?.id && ( - - {(isAllowed) => ( - - handlePopUpOpen("removeMember", { id: orgMembershipId }) - } - > - - - )} - - )} -
- {!isLoading && filterdUser?.length === 0 && ( - - )} -
-
- { - handlePopUpToggle("addMember", isOpen); - setCompleteInviteLink(undefined); - }} - > - - {!completeInviteLink && ( -
- An invite is specific to an email address and expires after 1 day. -
- For security reasons, you will need to separately add members to projects. -
- )} - {completeInviteLink && - "This Infisical instance does not have a email provider setup. Please share this invite link with the invitee manually"} -
- } - > - {!completeInviteLink && ( -
- ( - - - - )} - /> -
- - -
- - )} - {completeInviteLink && ( -
-

{completeInviteLink}

- - - - click to copy - - -
- )} - - - handlePopUpToggle("removeMember", isOpen)} - onDeleteApproved={onRemoveOrgMemberApproved} - /> - handlePopUpToggle("upgradePlan", isOpen)} - text="You can add custom environments if you switch to Infisical's Team plan." - /> - handlePopUpToggle("setUpEmail", isOpen)} - /> -
- ); -}; diff --git a/frontend/src/views/Org/NonePage/NonePage.tsx b/frontend/src/views/Org/NonePage/NonePage.tsx index 9d6b4a65c..531ef823b 100644 --- a/frontend/src/views/Org/NonePage/NonePage.tsx +++ b/frontend/src/views/Org/NonePage/NonePage.tsx @@ -1,9 +1,15 @@ +import { useEffect } from "react"; + import { usePopUp } from "@app/hooks/usePopUp"; import { CreateOrgModal } from "../components"; export const NonePage = () => { const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const); + + useEffect(() => { + handlePopUpToggle("createOrg", true); + }, []); return (
@@ -13,4 +19,4 @@ export const NonePage = () => { />
); -}; +}; \ No newline at end of file diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx index c29c948fc..c4d78b6f0 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx @@ -1,12 +1,14 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { withPermission } from "@app/hoc"; +import { OrgGeneralAuthSection } from "./OrgGeneralAuthSection"; import { OrgSSOSection } from "./OrgSSOSection"; export const OrgAuthTab = withPermission( () => { return (
+
); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx new file mode 100644 index 000000000..9bc026cf8 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -0,0 +1,65 @@ +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { Switch } from "@app/components/v2"; +import { + OrgPermissionActions, + OrgPermissionSubjects, + useOrganization +} from "@app/context"; +import { useLogoutUser,useUpdateOrg } from "@app/hooks/api"; + +export const OrgGeneralAuthSection = () => { + const { createNotification } = useNotificationContext(); + const { currentOrg } = useOrganization(); + + const { mutateAsync } = useUpdateOrg(); + + const logout = useLogoutUser(); + + const handleEnforceOrgAuthToggle = async (value: boolean) => { + try { + if (!currentOrg?.id) return; + + await mutateAsync({ + orgId: currentOrg?.id, + authEnforced: value + }); + + createNotification({ + text: `Successfully ${value ? "enforced" : "un-enforced"} org-level auth`, + type: "success" + }); + + if (value) { + await logout.mutateAsync(); + window.open(`/api/v1/sso/redirect/saml2/organizations/${currentOrg.slug}`); + window.close(); + } + + } catch (err) { + console.error(err); + createNotification({ + text: `Failed to ${value ? "enforce" : "un-enforce"} org-level auth`, + type: "error" + }); + } + } + + return ( +
+

Settings

+ + {(isAllowed) => ( + handleEnforceOrgAuthToggle(value)} + isChecked={currentOrg?.authEnforced ?? false} + isDisabled={!isAllowed} + > + Enforce SAML SSO + + )} + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx index ab28ec127..23900708d 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx @@ -1,5 +1,6 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { format } from "date-fns"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { OrgPermissionCan } from "@app/components/permissions"; @@ -136,6 +137,10 @@ export const OrgSSOSection = (): JSX.Element => {

Issuer

{data && data.issuer !== "" ? data.issuer : "-"}

+
+

Last Logged In

+

{data?.lastUsed ? format(new Date(data?.lastUsed), "yyyy-MM-dd HH:mm:ss") : "-"}

+
{ const { membership } = useOrgPermission(); @@ -10,8 +11,9 @@ export const OrgGeneralTab = () => { return (
+ {membership && membership.role === "admin" && }
); -}; +}; \ No newline at end of file diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx index 42265505f..66b45e116 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx @@ -7,7 +7,7 @@ import { useNotificationContext } from "@app/components/context/Notifications/No import { OrgPermissionCan } from "@app/components/permissions"; import { Button, FormControl, Input } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { useRenameOrg } from "@app/hooks/api"; +import { useUpdateOrg } from "@app/hooks/api"; const formSchema = yup.object({ name: yup.string().required().label("Project Name") @@ -21,7 +21,7 @@ export const OrgNameChangeSection = (): JSX.Element => { const { handleSubmit, control, reset } = useForm({ resolver: yupResolver(formSchema) }); - const { mutateAsync, isLoading } = useRenameOrg(); + const { mutateAsync, isLoading } = useUpdateOrg(); useEffect(() => { if (currentOrg) { @@ -34,7 +34,7 @@ export const OrgNameChangeSection = (): JSX.Element => { if (!currentOrg?.id) return; if (name === "") return; - await mutateAsync({ orgId: currentOrg?.id, newOrgName: name }); + await mutateAsync({ orgId: currentOrg?.id, name }); createNotification({ text: "Successfully renamed organization", type: "success" @@ -53,7 +53,7 @@ export const OrgNameChangeSection = (): JSX.Element => { onSubmit={handleSubmit(onFormSubmit)} className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4" > -

Name

+

Organization Name

; + +export const OrgSlugChangeSection = (): JSX.Element => { + const { currentOrg } = useOrganization(); + const { createNotification } = useNotificationContext(); + const { handleSubmit, control, reset } = useForm({ + resolver: yupResolver(formSchema) + }); + const { mutateAsync, isLoading } = useUpdateOrg(); + + useEffect(() => { + if (currentOrg) { + reset({ slug: currentOrg.slug }); + } + }, [currentOrg]); + + const onFormSubmit = async ({ slug }: FormData) => { + try { + if (!currentOrg?.id) return; + if (slug === "") return; + + await mutateAsync({ orgId: currentOrg?.id, slug }); + createNotification({ + text: "Successfully updated organization slug", + type: "success" + }); + } catch (error) { + console.error(error); + createNotification({ + text: "Failed to update organization slug", + type: "error" + }); + } + }; + + return ( +
+

Organization Slug

+
+ ( + + + + )} + control={control} + name="slug" + /> +
+ + {(isAllowed) => ( + + )} + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgSlugChangeSection/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgSlugChangeSection/index.tsx new file mode 100644 index 000000000..a4218cbd3 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgSlugChangeSection/index.tsx @@ -0,0 +1 @@ +export { OrgSlugChangeSection } from "./OrgSlugChangeSection"; \ No newline at end of file diff --git a/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx index 73d266e2e..5167bef17 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx @@ -25,8 +25,6 @@ const authMethodOpts: AuthMethodOption[] = [ { label: "GitLab", value: AuthMethod.GITLAB, icon: faGitlab } ]; -const samlProviders = [AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML, AuthMethod.AZURE_SAML]; - const schema = yup.object({ authMethods: yup.array().required("Auth method is required") }); @@ -56,17 +54,6 @@ export const AuthMethodSection = () => { }, [user]); const onAuthMethodToggle = async (value: boolean, authMethodOpt: AuthMethodOption) => { - const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) => - samlProviders.includes(authMethod) - ); - - if (hasSamlEnabled) { - createNotification({ - text: "SAML authentication can only be configured in your organization settings", - type: "error" - }); - } - const newAuthMethods = value ? [...authMethods, authMethodOpt.value] : authMethods.filter((auth) => auth !== authMethodOpt.value);