diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index c1c750225e..09ab054436 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -10,7 +10,7 @@ import { seedData1 } from "@app/db/seed-data"; import { initEnvConfig } from "@app/lib/config/env"; import { initLogger } from "@app/lib/logger"; import { main } from "@app/server/app"; -import { AuthTokenType } from "@app/services/auth/auth-type"; +import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { mockQueue } from "./mocks/queue"; import { mockSmtpServer } from "./mocks/smtp"; @@ -52,6 +52,8 @@ export default { authTokenType: AuthTokenType.ACCESS_TOKEN, userId: seedData1.id, tokenVersionId: seedData1.token.id, + authMethod: AuthMethod.EMAIL, + organizationId: seedData1.organization.id, accessVersion: 1 }, cfg.AUTH_SECRET, diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 649c54c547..0b59aa8a9a 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -19,7 +19,7 @@ import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { TAuthLoginFactory } from "@app/services/auth/auth-login-service"; import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service"; import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service"; -import { ActorType } from "@app/services/auth/auth-type"; +import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TIdentityServiceFactory } from "@app/services/identity/identity-service"; import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; @@ -59,6 +59,7 @@ declare module "fastify" { // identity injection. depending on which kinda of token the information is filled in auth auth: TAuthMode; permission: { + authMethod: ActorAuthMethod; type: ActorType; id: string; orgId?: string; diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index de472ff297..d160f97c66 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -122,6 +122,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, orgId: req.query.organizationId, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId }); return ldap; @@ -151,6 +152,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, orgId: req.body.organizationId, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body }); @@ -184,6 +186,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, orgId: req.body.organizationId, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body }); diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index 41cd11f7d2..bd3fdca184 100644 --- a/backend/src/ee/routes/v1/license-router.ts +++ b/backend/src/ee/routes/v1/license-router.ts @@ -24,6 +24,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId, orgId: req.params.organizationId, + actorAuthMethod: req.permission.authMethod, billingCycle: req.query.billingCycle }); return data; @@ -45,6 +46,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); return { plan }; @@ -66,6 +68,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgPlan({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); return data; @@ -89,6 +93,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId, orgId: req.params.organizationId, + actorAuthMethod: req.permission.authMethod, success_url: req.body.success_url }); return data; @@ -110,6 +115,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); return data; @@ -131,6 +137,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); return data; @@ -152,6 +159,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); return data; @@ -173,6 +181,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); return data; @@ -198,6 +207,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId, name: req.body.name, email: req.body.email @@ -221,6 +231,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); return data; @@ -246,6 +257,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId, success_url: req.body.success_url, cancel_url: req.body.cancel_url @@ -271,6 +283,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.delOrgPmtMethods({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.params.organizationId, pmtMethodId: req.params.pmtMethodId @@ -295,6 +308,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgTaxIds({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); @@ -322,6 +336,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.addOrgTaxId({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.params.organizationId, type: req.body.type, @@ -348,6 +363,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.delOrgTaxId({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.params.organizationId, taxId: req.params.taxId @@ -373,7 +389,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, - orgId: req.params.organizationId + orgId: req.params.organizationId, + actorAuthMethod: req.permission.authMethod }); return data; } @@ -396,6 +413,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, 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 1e40d2d806..148a843000 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -41,6 +41,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.organizationId, req.body, + req.permission.authMethod, req.permission.orgId ); return { role }; @@ -84,6 +85,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { req.params.organizationId, req.params.roleId, req.body, + req.permission.authMethod, req.permission.orgId ); return { role }; @@ -110,6 +112,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.organizationId, req.params.roleId, + req.permission.authMethod, req.permission.orgId ); return { role }; @@ -138,6 +141,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { const roles = await server.services.orgRole.listRoles( req.permission.id, req.params.organizationId, + req.permission.authMethod, req.permission.orgId ); return { data: { roles } }; @@ -163,6 +167,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { const { permissions, membership } = await server.services.orgRole.getUserPermission( req.permission.id, req.params.organizationId, + req.permission.authMethod, 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 f6fd53e5e8..a08a9851b1 100644 --- a/backend/src/ee/routes/v1/project-role-router.ts +++ b/backend/src/ee/routes/v1/project-role-router.ts @@ -31,6 +31,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.projectId, req.body, + req.permission.authMethod, req.permission.orgId ); return { role }; @@ -65,6 +66,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.params.projectId, req.params.roleId, req.body, + req.permission.authMethod, req.permission.orgId ); return { role }; @@ -92,6 +94,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.projectId, req.params.roleId, + req.permission.authMethod, req.permission.orgId ); return { role }; @@ -121,6 +124,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.type, req.permission.id, req.params.projectId, + req.permission.authMethod, req.permission.orgId ); return { data: { roles } }; @@ -148,6 +152,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { const { permissions, membership } = await server.services.projectRole.getUserPermission( req.permission.id, req.params.projectId, + req.permission.authMethod, 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 6064483723..3fbe3f0262 100644 --- a/backend/src/ee/routes/v1/project-router.ts +++ b/backend/src/ee/routes/v1/project-router.ts @@ -38,6 +38,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const secretSnapshots = await server.services.snapshot.listSnapshots({ actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, @@ -69,6 +70,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const count = await server.services.snapshot.projectSecretSnapshotCount({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, environment: req.query.environment, @@ -130,6 +132,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const auditLogs = await server.services.auditLog.listProjectAuditLogs({ actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, 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 fe387143ee..b30afda13b 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -231,6 +231,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.query.organizationId, type: "org" }); @@ -259,6 +260,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { const saml = await server.services.saml.createSamlCfg({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.body.organizationId, ...req.body @@ -290,6 +292,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { const saml = await server.services.saml.updateSamlCfg({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.body.organizationId, ...req.body diff --git a/backend/src/ee/routes/v1/scim-router.ts b/backend/src/ee/routes/v1/scim-router.ts index 2a3772cd6e..965aa94e1f 100644 --- a/backend/src/ee/routes/v1/scim-router.ts +++ b/backend/src/ee/routes/v1/scim-router.ts @@ -39,6 +39,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorOrgId: req.permission.orgId, orgId: req.body.organizationId, + actorAuthMethod: req.permission.authMethod, description: req.body.description, ttlDays: req.body.ttlDays }); @@ -65,6 +66,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { const scimTokens = await server.services.scim.listScimTokens({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.query.organizationId }); @@ -92,6 +94,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => { scimTokenId: req.params.scimTokenId, actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId }); 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 8fce232a7d..1ea16178f9 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body, @@ -72,6 +73,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, secretPolicyId: req.params.sapId @@ -98,6 +100,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPolicyId: req.params.sapId }); @@ -123,6 +126,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); @@ -150,6 +154,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, 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 97eb891097..16b6d205b6 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId @@ -81,6 +82,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approvals = await server.services.secretApprovalRequest.requestCount({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); @@ -106,6 +108,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, approvalId: req.params.id }); @@ -134,6 +137,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const review = await server.services.secretApprovalRequest.reviewApproval({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, approvalId: req.params.id, status: req.body.status @@ -163,6 +167,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approval = await server.services.secretApprovalRequest.updateApprovalStatus({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, approvalId: req.params.id, status: req.body.status @@ -271,6 +276,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.id }); 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 e7201b73fe..516885936c 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); diff --git a/backend/src/ee/routes/v1/secret-rotation-router.ts b/backend/src/ee/routes/v1/secret-rotation-router.ts index 8d2e90ac0d..447280cf8b 100644 --- a/backend/src/ee/routes/v1/secret-rotation-router.ts +++ b/backend/src/ee/routes/v1/secret-rotation-router.ts @@ -39,6 +39,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = handler: async (req) => { const secretRotation = await server.services.secretRotation.createRotation({ actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actorOrgId: req.permission.orgId, ...req.body, @@ -74,6 +75,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotation = await server.services.secretRotation.restartById({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, rotationId: req.body.id }); @@ -125,6 +127,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotations = await server.services.secretRotation.getByProjectId({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); @@ -158,6 +161,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotation = await server.services.secretRotation.deleteById({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, rotationId: req.params.id }); diff --git a/backend/src/ee/routes/v1/secret-scanning-router.ts b/backend/src/ee/routes/v1/secret-scanning-router.ts index 7d2c5f1ee6..1f8d56c747 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.body.organizationId }); @@ -46,6 +47,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { installatedApp } = await server.services.secretScanning.linkInstallationToOrg({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body }); @@ -67,6 +69,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const appInstallationCompleted = await server.services.secretScanning.getOrgInstallationStatus({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); @@ -88,6 +91,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { risks } = await server.services.secretScanning.getRisksByOrg({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); @@ -110,6 +114,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { risk } = await server.services.secretScanning.updateRiskStatus({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.params.organizationId, riskId: req.params.riskId, diff --git a/backend/src/ee/routes/v1/secret-version-router.ts b/backend/src/ee/routes/v1/secret-version-router.ts index 89ee4e011e..630af10cf9 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, limit: req.query.limit, offset: req.query.offset, diff --git a/backend/src/ee/routes/v1/snapshot-router.ts b/backend/src/ee/routes/v1/snapshot-router.ts index 79161bfd30..902d707af9 100644 --- a/backend/src/ee/routes/v1/snapshot-router.ts +++ b/backend/src/ee/routes/v1/snapshot-router.ts @@ -47,6 +47,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { const secretSnapshot = await server.services.snapshot.getSnapshotData({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.secretSnapshotId }); @@ -79,6 +80,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { const secretSnapshot = await server.services.snapshot.rollbackSnapshot({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.secretSnapshotId }); diff --git a/backend/src/ee/routes/v1/trusted-ip-router.ts b/backend/src/ee/routes/v1/trusted-ip-router.ts index 53bc5b117f..3d4ac8010b 100644 --- a/backend/src/ee/routes/v1/trusted-ip-router.ts +++ b/backend/src/ee/routes/v1/trusted-ip-router.ts @@ -22,6 +22,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const trustedIps = await server.services.trustedIp.listIpsByProjectId({ + actorAuthMethod: req.permission.authMethod, projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, @@ -52,6 +53,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const { trustedIp, project } = await server.services.trustedIp.addProjectIp({ + actorAuthMethod: req.permission.authMethod, projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, @@ -99,6 +101,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, trustedIpId: req.params.trustedIpId, ...req.body @@ -140,6 +143,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, trustedIpId: req.params.trustedIpId }); 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 c4d4aabc01..1564c6dcb5 100644 --- a/backend/src/ee/services/audit-log/audit-log-service.ts +++ b/backend/src/ee/services/audit-log/audit-log-service.ts @@ -31,10 +31,17 @@ export const auditLogServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, projectId, auditLogActor }: TListProjectAuditLogDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); const auditLogs = await auditLogDAL.find({ startDate, diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index e9ae0264aa..0316052cd3 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -55,6 +55,7 @@ export const ldapConfigServiceFactory = ({ actorId, orgId, actorOrgId, + actorAuthMethod, isActive, url, bindDN, @@ -62,7 +63,7 @@ export const ldapConfigServiceFactory = ({ searchBase, caCert }: TCreateLdapCfgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap); const plan = await licenseService.getPlan(orgId); @@ -149,13 +150,14 @@ export const ldapConfigServiceFactory = ({ orgId, actorOrgId, isActive, + actorAuthMethod, url, bindDN, bindPass, searchBase, caCert }: TUpdateLdapCfgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap); const plan = await licenseService.getPlan(orgId); @@ -274,8 +276,14 @@ export const ldapConfigServiceFactory = ({ }; }; - const getLdapCfgWithPermissionCheck = async ({ actor, actorId, orgId, actorOrgId }: TOrgPermission) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getLdapCfgWithPermissionCheck = async ({ + actor, + actorId, + orgId, + actorAuthMethod, + actorOrgId + }: TOrgPermission) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap); return getLdapCfg({ orgId diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index a55f2edffd..e81f6dc12e 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -224,9 +224,10 @@ export const licenseServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, billingCycle }: TOrgPlansTableDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const { data } = await licenseServerCloudApi.request.get( `/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` @@ -234,15 +235,22 @@ export const licenseServiceFactory = ({ return data; }; - const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, projectId }: TOrgPlanDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const plan = await getPlan(orgId, projectId); return plan; }; - const startOrgTrial = async ({ orgId, actorId, actor, actorOrgId, success_url }: TStartOrgTrialDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const startOrgTrial = async ({ + orgId, + actorId, + actor, + actorOrgId, + actorAuthMethod, + success_url + }: TStartOrgTrialDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); @@ -263,8 +271,14 @@ export const licenseServiceFactory = ({ return { url }; }; - const createOrganizationPortalSession = async ({ orgId, actorId, actor, actorOrgId }: TCreateOrgPortalSession) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const createOrganizationPortalSession = async ({ + orgId, + actorId, + actor, + actorAuthMethod, + actorOrgId + }: TCreateOrgPortalSession) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); @@ -310,8 +324,8 @@ export const licenseServiceFactory = ({ return { url }; }; - const getOrgBillingInfo = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -327,8 +341,8 @@ export const licenseServiceFactory = ({ }; // returns org current plan feature table - const getOrgPlanTable = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -343,8 +357,8 @@ export const licenseServiceFactory = ({ return data; }; - const getOrgBillingDetails = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -364,11 +378,12 @@ export const licenseServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, orgId, name, email }: TUpdateOrgBillingDetailsDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -387,8 +402,8 @@ export const licenseServiceFactory = ({ return data; }; - const getOrgPmtMethods = async ({ orgId, actor, actorId, actorOrgId }: TOrgPmtMethodsDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -410,11 +425,12 @@ export const licenseServiceFactory = ({ orgId, actor, actorId, + actorAuthMethod, actorOrgId, success_url, cancel_url }: TAddOrgPmtMethodDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -435,8 +451,15 @@ export const licenseServiceFactory = ({ return { url }; }; - const delOrgPmtMethods = async ({ actorId, actor, actorOrgId, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const delOrgPmtMethods = async ({ + actorId, + actor, + actorAuthMethod, + actorOrgId, + orgId, + pmtMethodId + }: TDelOrgPmtMethodDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -452,8 +475,8 @@ export const licenseServiceFactory = ({ return data; }; - const getOrgTaxIds = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -470,8 +493,8 @@ export const licenseServiceFactory = ({ return taxIds; }; - const addOrgTaxId = async ({ actorId, actor, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -491,8 +514,8 @@ export const licenseServiceFactory = ({ return data; }; - const delOrgTaxId = async ({ orgId, actor, actorId, actorOrgId, taxId }: TDelOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -508,8 +531,8 @@ export const licenseServiceFactory = ({ return data; }; - const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, orgId }: TOrgInvoiceDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -525,8 +548,8 @@ export const licenseServiceFactory = ({ return invoices; }; - const getOrgLicenses = async ({ orgId, actor, actorId, actorOrgId }: TOrgLicensesDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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 d94589b43d..fc0a85ac60 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -129,11 +129,18 @@ export const permissionDALFactory = (db: TDbClient) => { `${TableName.IdentityProjectMembershipRole}.customRoleId`, `${TableName.ProjectRoles}.id` ) + .join( + // Join the Project table to later select orgId + TableName.Project, + `${TableName.IdentityProjectMembership}.projectId`, + `${TableName.Project}.id` + ) .where("identityId", identityId) .where(`${TableName.IdentityProjectMembership}.projectId`, projectId) .select(selectAllTableCols(TableName.IdentityProjectMembershipRole)) .select( db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"), + db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"), db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"), @@ -144,16 +151,16 @@ export const permissionDALFactory = (db: TDbClient) => { const permission = sqlNestRelationships({ data: docs, key: "membershipId", - parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField }) => ({ + parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField, orgId }) => ({ id: membershipId, identityId, projectId, role: oldRoleField, createdAt: membershipCreatedAt, updatedAt: membershipUpdatedAt, + orgId, // just a prefilled value - orgAuthEnforced: false, - orgId: "" + orgAuthEnforced: false }), childrenMapper: [ { diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts new file mode 100644 index 0000000000..78b8ad8f24 --- /dev/null +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -0,0 +1,23 @@ +import { TOrganizations } from "@app/db/schemas"; +import { UnauthorizedError } from "@app/lib/errors"; +import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type"; + +function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) { + if (!actorAuthMethod) return false; + + return [AuthMethod.AZURE_SAML, AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML, AuthMethod.GOOGLE_SAML].includes( + actorAuthMethod + ); +} + +function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) { + if (actorAuthMethod === undefined) { + throw new UnauthorizedError({ name: "No auth method defined" }); + } + + if (isSamlEnforced && actorAuthMethod !== null && !isAuthMethodSaml(actorAuthMethod)) { + throw new UnauthorizedError({ name: "Cannot access org-scoped resource" }); + } +} + +export { isAuthMethodSaml, validateOrgSAML }; diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index c7dcf4b8cd..d632be3dfc 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -11,13 +11,15 @@ import { } from "@app/db/schemas"; import { conditionsMatcher } from "@app/lib/casl"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; -import { ActorType } from "@app/services/auth/auth-type"; +import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal"; +import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal"; import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission"; import { TPermissionDALFactory } from "./permission-dal"; +import { validateOrgSAML } from "./permission-fns"; import { TBuildProjectPermissionDTO } from "./permission-types"; import { buildServiceTokenProjectPermission, @@ -32,6 +34,7 @@ type TPermissionServiceFactoryDep = { orgRoleDAL: Pick; projectRoleDAL: Pick; serviceTokenDAL: Pick; + projectDAL: Pick; permissionDAL: TPermissionDALFactory; }; @@ -41,7 +44,8 @@ export const permissionServiceFactory = ({ permissionDAL, orgRoleDAL, projectRoleDAL, - serviceTokenDAL + serviceTokenDAL, + projectDAL }: TPermissionServiceFactoryDep) => { const buildOrgPermission = (role: string, permission?: unknown) => { switch (role) { @@ -98,16 +102,30 @@ export const permissionServiceFactory = ({ /* * Get user permission in an organization - * */ - const getUserOrgPermission = async (userId: string, orgId: string, userOrgId?: string) => { + */ + const getUserOrgPermission = async ( + userId: string, + orgId: string, + authMethod: ActorAuthMethod, + 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" }); + + // If the org ID is API_KEY, the request is being made with an API Key. + // Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization. + + // Extra: This means that when users are using API keys to make requests, they can't use slug-based routes. + // Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization. + if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) { + throw new UnauthorizedError({ name: "You are not logged into this organization" }); } + + validateOrgSAML(authMethod, membership.orgAuthEnforced); + return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; }; @@ -120,10 +138,16 @@ export const permissionServiceFactory = ({ return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; }; - const getOrgPermission = async (type: ActorType, id: string, orgId: string, actorOrgId?: string) => { + const getOrgPermission = async ( + type: ActorType, + id: string, + orgId: string, + authMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { switch (type) { case ActorType.USER: - return getUserOrgPermission(id, orgId, actorOrgId); + return getUserOrgPermission(id, orgId, authMethod, actorOrgId); case ActorType.IDENTITY: return getIdentityOrgPermission(id, orgId); default: @@ -153,34 +177,39 @@ export const permissionServiceFactory = ({ const getUserProjectPermission = async ( userId: string, projectId: string, + authMethod: ActorAuthMethod, userOrgId?: string ): Promise> => { - const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId); - if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" }); + const membership = await permissionDAL.getProjectPermission(userId, projectId); + if (!membership) throw new UnauthorizedError({ name: "User not in project" }); - if ( - userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions) - ) { + if (membership.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)) { throw new BadRequestError({ name: "Custom permission not found" }); } - if (userProjectPermission.orgAuthEnforced && userProjectPermission.orgId !== userOrgId) { - throw new BadRequestError({ name: "Cannot access org-scoped resource" }); + // If the org ID is API_KEY, the request is being made with an API Key. + // Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization. + + // Extra: This means that when users are using API keys to make requests, they can't use slug-based routes. + // Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization. + if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) { + throw new UnauthorizedError({ name: "You are not logged into this organization" }); } + validateOrgSAML(authMethod, membership.orgAuthEnforced); + return { - permission: buildProjectPermission(userProjectPermission.roles), - membership: userProjectPermission, + permission: buildProjectPermission(membership.roles), + membership, hasRole: (role: string) => - userProjectPermission.roles.findIndex( - ({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug - ) !== -1 + membership.roles.findIndex(({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug) !== -1 }; }; const getIdentityProjectPermission = async ( identityId: string, - projectId: string + projectId: string, + identityOrgId: string | undefined ): Promise> => { const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId); if (!identityProjectPermission) throw new UnauthorizedError({ name: "Identity not in project" }); @@ -193,6 +222,10 @@ export const permissionServiceFactory = ({ throw new BadRequestError({ name: "Custom permission not found" }); } + if (identityProjectPermission.orgId !== identityOrgId) { + throw new UnauthorizedError({ name: "You are not a member of this organization" }); + } + return { permission: buildProjectPermission(identityProjectPermission.roles), membership: identityProjectPermission, @@ -203,14 +236,32 @@ export const permissionServiceFactory = ({ }; }; - const getServiceTokenProjectPermission = async (serviceTokenId: string, projectId: string) => { + const getServiceTokenProjectPermission = async ( + serviceTokenId: string, + projectId: string, + actorOrgId: string | undefined + ) => { const serviceToken = await serviceTokenDAL.findById(serviceTokenId); if (!serviceToken) throw new BadRequestError({ message: "Service token not found" }); + const serviceTokenProject = await projectDAL.findById(serviceToken.projectId); + + if (!serviceTokenProject) throw new BadRequestError({ message: "Service token not linked to a project" }); + + if (serviceTokenProject.orgId !== actorOrgId) { + throw new UnauthorizedError({ message: "Service token not a part of this organization" }); + } + if (serviceToken.projectId !== projectId) throw new UnauthorizedError({ message: "Failed to find service authorization for given project" }); + + if (serviceTokenProject.orgId !== actorOrgId) + throw new UnauthorizedError({ + message: "Failed to find service authorization for given project" + }); + const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []); return { permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions), @@ -238,15 +289,16 @@ export const permissionServiceFactory = ({ type: T, id: string, projectId: string, - actorOrgId?: string + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined ): Promise> => { switch (type) { case ActorType.USER: - return getUserProjectPermission(id, projectId, actorOrgId) as Promise>; + return getUserProjectPermission(id, projectId, actorAuthMethod, actorOrgId) as Promise>; case ActorType.SERVICE: - return getServiceTokenProjectPermission(id, projectId) as Promise>; + return getServiceTokenProjectPermission(id, projectId, actorOrgId) as Promise>; case ActorType.IDENTITY: - return getIdentityProjectPermission(id, projectId) as Promise>; + return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise>; default: throw new UnauthorizedError({ message: "Permission not defined", 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 e9249d4aa4..dc97289573 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -55,6 +55,7 @@ export const samlConfigServiceFactory = ({ const createSamlCfg = async ({ cert, actor, + actorAuthMethod, actorOrgId, orgId, issuer, @@ -63,7 +64,7 @@ export const samlConfigServiceFactory = ({ entryPoint, authProvider }: TCreateSamlCfgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); const plan = await licenseService.getPlan(orgId); @@ -146,6 +147,7 @@ export const samlConfigServiceFactory = ({ orgId, actor, actorOrgId, + actorAuthMethod, cert, actorId, issuer, @@ -153,7 +155,7 @@ export const samlConfigServiceFactory = ({ entryPoint, authProvider }: TUpdateSamlCfgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); const plan = await licenseService.getPlan(orgId); if (!plan.samlSSO) @@ -238,6 +240,7 @@ export const samlConfigServiceFactory = ({ dto.actor, dto.actorId, ssoConfig.orgId, + dto.actorAuthMethod, dto.actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); 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 ec7c066fca..9aedf5d193 100644 --- a/backend/src/ee/services/saml-config/saml-config-types.ts +++ b/backend/src/ee/services/saml-config/saml-config-types.ts @@ -1,5 +1,5 @@ import { TOrgPermission } from "@app/lib/types"; -import { ActorType } from "@app/services/auth/auth-type"; +import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; export enum SamlProviders { OKTA_SAML = "okta-saml", @@ -26,7 +26,14 @@ export type TUpdateSamlCfgDTO = Partial<{ TOrgPermission; export type TGetSamlCfgDTO = - | { type: "org"; orgId: string; actor: ActorType; actorId: string; actorOrgId?: string } + | { + type: "org"; + orgId: string; + actor: ActorType; + actorId: string; + actorAuthMethod: ActorAuthMethod; + actorOrgId: string | undefined; + } | { type: "orgSlug"; orgSlug: string; diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index c542b23401..6ed594777f 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -56,8 +56,16 @@ export const scimServiceFactory = ({ permissionService, smtpService }: TScimServiceFactoryDep) => { - const createScimToken = async ({ actor, actorId, actorOrgId, orgId, description, ttlDays }: TCreateScimTokenDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const createScimToken = async ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + orgId, + description, + ttlDays + }: TCreateScimTokenDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim); const plan = await licenseService.getPlan(orgId); @@ -85,8 +93,8 @@ export const scimServiceFactory = ({ return { scimToken }; }; - const listScimTokens = async ({ actor, actorId, actorOrgId, orgId }: TOrgPermission) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const listScimTokens = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TOrgPermission) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim); const plan = await licenseService.getPlan(orgId); @@ -99,11 +107,17 @@ export const scimServiceFactory = ({ return scimTokens; }; - const deleteScimToken = async ({ scimTokenId, actor, actorId, actorOrgId }: TDeleteScimTokenDTO) => { + const deleteScimToken = async ({ scimTokenId, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteScimTokenDTO) => { let scimToken = await scimDAL.findById(scimTokenId); if (!scimToken) throw new BadRequestError({ message: "Failed to find SCIM token to delete" }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, scimToken.orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + scimToken.orgId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim); const plan = await licenseService.getPlan(scimToken.orgId); 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 9d65ec7ccb..8ddadb9bf6 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 @@ -45,6 +45,7 @@ export const secretApprovalPolicyServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, approvals, approvers, projectId, @@ -54,7 +55,13 @@ 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval @@ -98,6 +105,7 @@ export const secretApprovalPolicyServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, approvals, secretPolicyId }: TUpdateSapDTO) => { @@ -108,6 +116,7 @@ export const secretApprovalPolicyServiceFactory = ({ actor, actorId, secretApprovalPolicy.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); @@ -152,7 +161,13 @@ export const secretApprovalPolicyServiceFactory = ({ }; }; - const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId, actorOrgId }: TDeleteSapDTO) => { + const deleteSecretApprovalPolicy = async ({ + secretPolicyId, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TDeleteSapDTO) => { const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId); if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" }); @@ -160,6 +175,7 @@ export const secretApprovalPolicyServiceFactory = ({ actor, actorId, sapPolicy.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( @@ -171,8 +187,20 @@ export const secretApprovalPolicyServiceFactory = ({ return sapPolicy; }; - const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TListSapDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getSecretApprovalPolicyByProjectId = async ({ + actorId, + actor, + actorOrgId, + actorAuthMethod, + projectId + }: TListSapDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); const sapPolicies = await secretApprovalPolicyDAL.find({ projectId }); @@ -201,10 +229,17 @@ export const secretApprovalPolicyServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, environment, secretPath }: TGetBoardSapDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + 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 b48b6bf951..b8ad89e456 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 @@ -82,13 +82,14 @@ export const secretApprovalRequestServiceFactory = ({ secretVersionDAL, secretQueueService }: TSecretApprovalRequestServiceFactoryDep) => { - const requestCount = async ({ projectId, actor, actorId, actorOrgId }: TApprovalRequestCountDTO) => { + const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); const { membership } = await permissionService.getProjectPermission( actor as ActorType.USER, actorId, projectId, + actorAuthMethod, actorOrgId ); @@ -100,6 +101,7 @@ export const secretApprovalRequestServiceFactory = ({ projectId, actorId, actor, + actorAuthMethod, actorOrgId, status, environment, @@ -109,7 +111,13 @@ export const secretApprovalRequestServiceFactory = ({ }: TListApprovalsDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); - const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { membership } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); const approvals = await secretApprovalRequestDAL.findByProjectId({ projectId, committer, @@ -122,7 +130,13 @@ export const secretApprovalRequestServiceFactory = ({ return approvals; }; - const getSecretApprovalDetails = async ({ actor, actorId, actorOrgId, id }: TSecretApprovalDetailsDTO) => { + const getSecretApprovalDetails = async ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + id + }: TSecretApprovalDetailsDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); const secretApprovalRequest = await secretApprovalRequestDAL.findById(id); @@ -133,6 +147,7 @@ export const secretApprovalRequestServiceFactory = ({ actor, actorId, secretApprovalRequest.projectId, + actorAuthMethod, actorOrgId ); if ( @@ -150,7 +165,14 @@ export const secretApprovalRequestServiceFactory = ({ return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets }; }; - const reviewApproval = async ({ approvalId, actor, status, actorId, actorOrgId }: TReviewRequestDTO) => { + const reviewApproval = async ({ + approvalId, + actor, + status, + actorId, + actorAuthMethod, + 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" }); @@ -160,6 +182,7 @@ export const secretApprovalRequestServiceFactory = ({ ActorType.USER, actorId, secretApprovalRequest.projectId, + actorAuthMethod, actorOrgId ); if ( @@ -192,7 +215,14 @@ export const secretApprovalRequestServiceFactory = ({ return reviewStatus; }; - const updateApprovalStatus = async ({ actorId, status, approvalId, actor, actorOrgId }: TStatusChangeDTO) => { + const updateApprovalStatus = async ({ + actorId, + status, + approvalId, + actor, + actorOrgId, + actorAuthMethod + }: 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" }); @@ -202,6 +232,7 @@ export const secretApprovalRequestServiceFactory = ({ ActorType.USER, actorId, secretApprovalRequest.projectId, + actorAuthMethod, actorOrgId ); if ( @@ -229,7 +260,8 @@ export const secretApprovalRequestServiceFactory = ({ approvalId, actor, actorId, - actorOrgId + actorOrgId, + actorAuthMethod }: TMergeSecretApprovalRequestDTO) => { const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); @@ -240,8 +272,10 @@ export const secretApprovalRequestServiceFactory = ({ ActorType.USER, actorId, projectId, + actorAuthMethod, actorOrgId ); + if ( !hasRole(ProjectMembershipRole.Admin) && secretApprovalRequest.committerId !== membership.id && @@ -438,6 +472,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, policy, projectId, secretPath, @@ -449,6 +484,7 @@ export const secretApprovalRequestServiceFactory = ({ actor, actorId, projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( 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 75c19c6e96..1e1648a662 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -39,8 +39,20 @@ export const secretRotationServiceFactory = ({ folderDAL, secretDAL }: TSecretRotationServiceFactoryDep) => { - const getProviderTemplates = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getProviderTemplates = async ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + projectId + }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); return { @@ -54,6 +66,7 @@ export const secretRotationServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, inputs, outputs, interval, @@ -61,7 +74,13 @@ export const secretRotationServiceFactory = ({ secretPath, environment }: TCreateSecretRotationDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation @@ -139,14 +158,20 @@ export const secretRotationServiceFactory = ({ return secretRotation; }; - const getByProjectId = async ({ actorId, projectId, actor, actorOrgId }: TListByProjectIdDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); const doc = await secretRotationDAL.find({ projectId }); return doc; }; - const restartById = async ({ actor, actorId, actorOrgId, rotationId }: TRestartDTO) => { + const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => { const doc = await secretRotationDAL.findById(rotationId); if (!doc) throw new BadRequestError({ message: "Rotation not found" }); @@ -157,18 +182,30 @@ 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + doc.projectId, + actorAuthMethod, + 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, actorOrgId, rotationId }: TDeleteDTO) => { + const deleteById = async ({ actor, actorId, actorOrgId, actorAuthMethod, 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + doc.projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation 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 7066fd4854..9b78da3af4 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,14 @@ export const secretScanningServiceFactory = ({ permissionService, secretScanningQueue }: TSecretScanningServiceFactoryDep) => { - const createInstallationSession = async ({ actor, orgId, actorId, actorOrgId }: TInstallAppSessionDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const createInstallationSession = async ({ + actor, + orgId, + actorId, + actorAuthMethod, + actorOrgId + }: TInstallAppSessionDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); const sessionId = crypto.randomBytes(16).toString("hex"); @@ -53,12 +59,19 @@ export const secretScanningServiceFactory = ({ actorId, installationId, actor, + actorAuthMethod, 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, actorOrgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + session.orgId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); const installatedApp = await gitAppOrgDAL.transaction(async (tx) => { await gitAppInstallSessionDAL.deleteById(session.id, tx); @@ -89,23 +102,37 @@ export const secretScanningServiceFactory = ({ return { installatedApp }; }; - const getOrgInstallationStatus = async ({ actorId, orgId, actor, actorOrgId }: TGetOrgInstallStatusDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getOrgInstallationStatus = async ({ + actorId, + orgId, + actor, + actorAuthMethod, + actorOrgId + }: TGetOrgInstallStatusDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); const appInstallation = await gitAppOrgDAL.findOne({ orgId }); return Boolean(appInstallation); }; - const getRisksByOrg = async ({ actor, orgId, actorId, actorOrgId }: TGetOrgRisksDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetOrgRisksDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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, actorOrgId, riskId, status }: TUpdateRiskStatusDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const updateRiskStatus = async ({ + actorId, + orgId, + actor, + actorOrgId, + actorAuthMethod, + riskId, + status + }: TUpdateRiskStatusDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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 6ec7a23d56..de2f6efcb5 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -59,9 +59,16 @@ export const secretSnapshotServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, path }: TProjectSnapshotCountDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const folder = await folderDAL.findBySecretPath(projectId, environment, path); @@ -77,11 +84,18 @@ export const secretSnapshotServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, path, limit = 20, offset = 0 }: TProjectSnapshotListDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const folder = await folderDAL.findBySecretPath(projectId, environment, path); @@ -91,10 +105,16 @@ export const secretSnapshotServiceFactory = ({ return snapshots; }; - const getSnapshotData = async ({ actorId, actor, actorOrgId, id }: TGetSnapshotDataDTO) => { + const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + snapshot.projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); return snapshot; }; @@ -145,11 +165,23 @@ export const secretSnapshotServiceFactory = ({ } }; - const rollbackSnapshot = async ({ id: snapshotId, actor, actorId, actorOrgId }: TRollbackSnapshotDTO) => { + const rollbackSnapshot = async ({ + id: snapshotId, + actor, + actorId, + actorAuthMethod, + 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + snapshot.projectId, + actorAuthMethod, + 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 14c73db1f2..ecd2b3070b 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,14 @@ export const trustedIpServiceFactory = ({ licenseService, projectDAL }: TTrustedIpServiceFactoryDep) => { - const listIpsByProjectId = async ({ projectId, actor, actorId, actorOrgId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); const trustedIps = await trustedIpDAL.find({ projectId @@ -38,13 +44,20 @@ export const trustedIpServiceFactory = ({ const addProjectIp = async ({ projectId, actorId, + actorAuthMethod, actor, actorOrgId, ipAddress: ip, comment, isActive }: TCreateIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); @@ -78,11 +91,18 @@ export const trustedIpServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, ipAddress: ip, comment, trustedIpId }: TUpdateIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); @@ -113,8 +133,21 @@ export const trustedIpServiceFactory = ({ return { trustedIp, project }; // for audit log }; - const deleteProjectIp = async ({ projectId, actorId, actor, actorOrgId, trustedIpId }: TDeleteIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const deleteProjectIp = async ({ + projectId, + actorId, + actor, + actorOrgId, + actorAuthMethod, + trustedIpId + }: TDeleteIpDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 6f5cc22963..2e42f3388f 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -84,7 +84,7 @@ export const ORGANIZATIONS = { export const PROJECTS = { CREATE: { - organizationId: "The ID of the organization to create the project in.", + organizationSlug: "The slug of the organization to create the project in.", projectName: "The name of the project to create.", slug: "An optional slug for the project." }, @@ -197,6 +197,7 @@ export const FOLDERS = { export const RAW_SECRETS = { LIST: { workspaceId: "The ID of the project to list secrets from.", + workspaceSlug: "The slug of the project to list secrets from. This parameter is only usable by machine identities.", environment: "The slug of the environment to list secrets from.", secretPath: "The secret path to list secrets from.", includeImports: "Weather to include imported secrets or not." diff --git a/backend/src/lib/types/index.ts b/backend/src/lib/types/index.ts index 918322d62e..7a34222a6e 100644 --- a/backend/src/lib/types/index.ts +++ b/backend/src/lib/types/index.ts @@ -1,17 +1,19 @@ -import { ActorType } from "@app/services/auth/auth-type"; +import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; export type TOrgPermission = { actor: ActorType; actorId: string; orgId: string; - actorOrgId?: string; + actorAuthMethod: ActorAuthMethod; + actorOrgId: string | undefined; }; export type TProjectPermission = { actor: ActorType; actorId: string; projectId: string; - actorOrgId?: string; + actorAuthMethod: ActorAuthMethod; + actorOrgId: string | undefined; }; export type RequiredKeys = { diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 3a0a0ab39f..dceb31c038 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -6,42 +6,49 @@ import { TServiceTokens, TUsers } from "@app/db/schemas"; import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types"; import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; -import { ActorType, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; +import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types"; export type TAuthMode = | { - orgId?: string; authMode: AuthMode.JWT; actor: ActorType.USER; userId: string; tokenVersionId: string; // the session id of token used user: TUsers; + orgId?: string; + authMethod: AuthMethod; } | { authMode: AuthMode.API_KEY; + authMethod: null; actor: ActorType.USER; userId: string; user: TUsers; - orgId?: string; + orgId: string; } | { authMode: AuthMode.SERVICE_TOKEN; serviceToken: TServiceTokens & { createdByEmail: string }; actor: ActorType.SERVICE; serviceTokenId: string; + orgId: string; + authMethod: null; } | { authMode: AuthMode.IDENTITY_ACCESS_TOKEN; actor: ActorType.IDENTITY; identityId: string; identityName: string; + orgId: string; + authMethod: null; } | { authMode: AuthMode.SCIM_TOKEN; actor: ActorType.SCIM_CLIENT; scimTokenId: string; orgId: string; + authMethod: null; }; const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { @@ -50,6 +57,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { return { authMode: AuthMode.API_KEY, token: apiKey, actor: ActorType.USER } as const; } const authHeader = req.headers?.authorization; + if (!authHeader) return { authMode: null, token: null }; const authTokenValue = authHeader.slice(7); // slice of after Bearer @@ -71,6 +79,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { actor: ActorType.USER } as const; case AuthTokenType.API_KEY: + // throw new Error("API Key auth is no longer supported."); return { authMode: AuthMode.API_KEY, token: decodedToken, actor: ActorType.USER } as const; case AuthTokenType.IDENTITY_ACCESS_TOKEN: return { @@ -89,17 +98,30 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { } }; +// ! Important: You can only 100% count on the `req.permission.orgId` field being present when the auth method is Identity Access Token (Machine Identity). export const injectIdentity = fp(async (server: FastifyZodProvider) => { server.decorateRequest("auth", null); server.addHook("onRequest", async (req) => { const appCfg = getConfig(); const { authMode, token, actor } = await extractAuth(req, appCfg.AUTH_SECRET); + + if (req.url.includes("/api/v3/auth/")) { + return; + } if (!authMode) return; switch (authMode) { case AuthMode.JWT: { const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); - req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; + req.auth = { + authMode: AuthMode.JWT, + user, + userId: user.id, + tokenVersionId, + actor, + orgId, + authMethod: token.authMethod + }; break; } case AuthMode.IDENTITY_ACCESS_TOKEN: { @@ -107,29 +129,40 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { req.auth = { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, actor, + orgId: identity.orgId, identityId: identity.identityId, - identityName: identity.name + identityName: identity.name, + authMethod: null }; break; } case AuthMode.SERVICE_TOKEN: { const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); req.auth = { + orgId: serviceToken.orgId, authMode: AuthMode.SERVICE_TOKEN as const, serviceToken, serviceTokenId: serviceToken.id, - actor + actor, + authMethod: null }; break; } case AuthMode.API_KEY: { const user = await server.services.apiKey.fnValidateApiKey(token as string); - req.auth = { authMode: AuthMode.API_KEY as const, userId: user.id, actor, user }; + req.auth = { + authMode: AuthMode.API_KEY as const, + userId: user.id, + actor, + user, + orgId: "API_KEY", // We set the orgId to an arbitrary value, since we can't link an API key to a specific org. We have to deprecate API keys soon! + authMethod: null + }; break; } case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); - req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId }; + req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId, authMethod: null }; break; } default: diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index 2d61647e8e..084f181981 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -9,13 +9,33 @@ 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, orgId: req.auth?.orgId }; + req.permission = { + type: ActorType.USER, + id: req.auth.userId, + orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY" + authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null + }; } else if (req.auth.actor === ActorType.IDENTITY) { - req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId }; + req.permission = { + type: ActorType.IDENTITY, + id: req.auth.identityId, + orgId: req.auth.orgId, + authMethod: null + }; } else if (req.auth.actor === ActorType.SERVICE) { - req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId }; + req.permission = { + type: ActorType.SERVICE, + id: req.auth.serviceTokenId, + orgId: req.auth.orgId, + authMethod: null + }; } else if (req.auth.actor === ActorType.SCIM_CLIENT) { - req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId }; + req.permission = { + type: ActorType.SCIM_CLIENT, + id: req.auth.scimTokenId, + orgId: req.auth.orgId, + authMethod: null + }; } }); }); diff --git a/backend/src/server/plugins/auth/verify-auth.ts b/backend/src/server/plugins/auth/verify-auth.ts index a1274f356e..3b3a239f73 100644 --- a/backend/src/server/plugins/auth/verify-auth.ts +++ b/backend/src/server/plugins/auth/verify-auth.ts @@ -3,15 +3,26 @@ import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify"; import { UnauthorizedError } from "@app/lib/errors"; import { AuthMode } from "@app/services/auth/auth-type"; +interface TAuthOptions { + requireOrg: boolean; +} + export const verifyAuth = - (authStrats: AuthMode[]) => + (authStrategies: AuthMode[], options: TAuthOptions = { requireOrg: true }) => (req: T, _res: FastifyReply, done: HookHandlerDoneFunction) => { - if (!Array.isArray(authStrats)) throw new Error("Auth strategy must be array"); + if (!Array.isArray(authStrategies)) throw new Error("Auth strategy must be array"); if (!req.auth) throw new UnauthorizedError({ name: "Unauthorized access", message: "Token missing" }); - const isAccessAllowed = authStrats.some((strat) => strat === req.auth.authMode); + const isAccessAllowed = authStrategies.some((strategy) => strategy === req.auth.authMode); if (!isAccessAllowed) { throw new UnauthorizedError({ name: `${req.url} Unauthorized Access` }); } + + // New optional option. There are some routes which do not require an organization ID to be present on the request. + // An example of this is the /v1 auth routes. + if (req.auth.authMode === AuthMode.JWT && options.requireOrg === true && !req.permission.orgId) { + throw new UnauthorizedError({ name: `${req.url} Unauthorized Access, no organization found in request` }); + } + done(); }; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 0a49806bd7..c452bfbd37 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -201,7 +201,8 @@ export const registerRoutes = async ( permissionDAL, orgRoleDAL, projectRoleDAL, - serviceTokenDAL + serviceTokenDAL, + projectDAL }); const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore }); const trustedIpService = trustedIpServiceFactory({ @@ -266,7 +267,7 @@ export const registerRoutes = async ( const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); const userService = userServiceFactory({ userDAL }); - const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService }); + const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL }); const passwordService = authPaswordServiceFactory({ tokenService, smtpService, @@ -372,6 +373,7 @@ export const registerRoutes = async ( projectKeyDAL, userDAL, projectEnvDAL, + orgDAL, orgService, projectMembershipDAL, folderDAL, @@ -517,7 +519,8 @@ export const registerRoutes = async ( projectEnvDAL, serviceTokenDAL, userDAL, - permissionService + permissionService, + projectDAL }); const identityService = identityServiceFactory({ @@ -525,7 +528,10 @@ export const registerRoutes = async ( identityDAL, identityOrgMembershipDAL }); - const identityAccessTokenService = identityAccessTokenServiceFactory({ identityAccessTokenDAL }); + const identityAccessTokenService = identityAccessTokenServiceFactory({ + identityAccessTokenDAL, + identityOrgMembershipDAL + }); const identityProjectService = identityProjectServiceFactory({ permissionService, projectDAL, diff --git a/backend/src/server/routes/v1/auth-router.ts b/backend/src/server/routes/v1/auth-router.ts index bd45f59d9c..16448b8ca0 100644 --- a/backend/src/server/routes/v1/auth-router.ts +++ b/backend/src/server/routes/v1/auth-router.ts @@ -21,7 +21,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }), handler: async (req, res) => { const appCfg = getConfig(); if (req.auth.authMode === AuthMode.JWT) { @@ -85,6 +85,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => { const token = jwt.sign( { + authMethod: decodedToken.authMethod, authTokenType: AuthTokenType.ACCESS_TOKEN, userId: decodedToken.userId, tokenVersionId: tokenVersion.id, diff --git a/backend/src/server/routes/v1/bot-router.ts b/backend/src/server/routes/v1/bot-router.ts index 507423b0db..7e1d6ec941 100644 --- a/backend/src/server/routes/v1/bot-router.ts +++ b/backend/src/server/routes/v1/bot-router.ts @@ -30,6 +30,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, projectId: req.params.projectId }); return { bot }; @@ -70,6 +71,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, 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 ac389b4784..3ce13ca0ae 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -35,6 +35,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.createIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, orgId: req.body.organizationId @@ -95,6 +96,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.updateIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.identityId, ...req.body @@ -140,6 +142,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.deleteIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, 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 6146fa2422..ac5c78a085 100644 --- a/backend/src/server/routes/v1/identity-ua.ts +++ b/backend/src/server/routes/v1/identity-ua.ts @@ -131,6 +131,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, ...req.body, identityId: req.params.identityId }); @@ -212,6 +213,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, ...req.body, identityId: req.params.identityId }); @@ -260,6 +262,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const identityUniversalAuth = await server.services.identityUa.getIdentityUa({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, identityId: req.params.identityId }); @@ -309,6 +312,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const { clientSecret, clientSecretData, orgId } = await server.services.identityUa.createUaClientSecret({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, identityId: req.params.identityId, ...req.body @@ -354,6 +358,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUaClientSecrets({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, identityId: req.params.identityId }); @@ -397,6 +402,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const clientSecretData = await server.services.identityUa.revokeUaClientSecret({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, 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 7dfda4bee9..004ca298d0 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); @@ -80,6 +81,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, integration: req.query.integration, projectId: req.query.projectId }); @@ -117,6 +119,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuthById({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); @@ -157,6 +160,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.oauthExchange({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body @@ -200,6 +204,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body @@ -247,6 +252,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const apps = await server.services.integrationAuth.getIntegrationApps({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, ...req.query @@ -278,6 +284,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const teams = await server.services.integrationAuth.getIntegrationAuthTeams({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); @@ -306,6 +313,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const branches = await server.services.integrationAuth.getVercelBranches({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId @@ -335,6 +343,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const groups = await server.services.integrationAuth.getChecklyGroups({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, accountId: req.query.accountId @@ -362,6 +371,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, id: req.params.integrationAuthId }); if (!orgs) throw new Error("No organization found."); @@ -394,6 +404,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) actor: req.permission.type, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, + actorAuthMethod: req.permission.authMethod, repoName: req.query.repoName, repoOwner: req.query.repoOwner }); @@ -421,6 +432,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const orgs = await server.services.integrationAuth.getQoveryOrgs({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); @@ -449,6 +461,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const projects = await server.services.integrationAuth.getQoveryProjects({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, orgId: req.query.orgId @@ -478,6 +491,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const environments = await server.services.integrationAuth.getQoveryEnvs({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, projectId: req.query.projectId @@ -507,6 +521,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const apps = await server.services.integrationAuth.getQoveryApps({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId @@ -536,6 +551,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const containers = await server.services.integrationAuth.getQoveryContainers({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId @@ -565,6 +581,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const jobs = await server.services.integrationAuth.getQoveryJobs({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId @@ -597,6 +614,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const pipelines = await server.services.integrationAuth.getHerokuPipelines({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); @@ -625,6 +643,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const environments = await server.services.integrationAuth.getRailwayEnvironments({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId @@ -654,6 +673,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const services = await server.services.integrationAuth.getRailwayServices({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId @@ -690,6 +710,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const workspaces = await server.services.integrationAuth.getBitbucketWorkspaces({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); @@ -723,6 +744,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const secretGroups = await server.services.integrationAuth.getNorthFlankSecretGroups({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId @@ -757,6 +779,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const buildConfigs = await server.services.integrationAuth.getTeamcityBuildConfigs({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, 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 670f838845..4859ca5843 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -54,6 +54,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { const { integration, integrationAuth } = await server.services.integration.createIntegration({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body }); @@ -124,6 +125,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { const integration = await server.services.integration.updateIntegration({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.integrationId, ...req.body @@ -149,6 +151,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const integration = await server.services.integration.deleteIntegration({ actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, 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 5956b53df9..212020034d 100644 --- a/backend/src/server/routes/v1/invite-org-router.ts +++ b/backend/src/server/routes/v1/invite-org-router.ts @@ -29,6 +29,7 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => { orgId: req.body.organizationId, userId: req.permission.id, inviteeEmail: req.body.inviteeEmail, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId }); diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index d31682d88e..42e64e9d77 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -15,7 +15,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }), handler: async (req) => { const organizations = await server.services.org.findAllOrganizationOfUser(req.permission.id); return { organizations }; @@ -40,6 +40,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const organization = await server.services.org.findOrganizationById( req.permission.id, req.params.organizationId, + req.permission.authMethod, req.permission.orgId ); return { organization }; @@ -76,6 +77,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const users = await server.services.org.findAllOrgMembers( req.permission.id, req.params.organizationId, + req.permission.authMethod, req.permission.orgId ); return { users }; @@ -111,6 +113,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId, data: req.body }); @@ -138,6 +141,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const incidentContactsOrg = await req.server.services.org.findIncidentContacts( req.permission.id, req.params.organizationId, + req.permission.authMethod, req.permission.orgId ); return { incidentContactsOrg }; @@ -162,6 +166,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.organizationId, req.body.email, + req.permission.authMethod, req.permission.orgId ); return { incidentContactsOrg }; @@ -185,6 +190,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.organizationId, req.params.incidentContactId, + req.permission.authMethod, 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 cb5be173e7..68a5a90013 100644 --- a/backend/src/server/routes/v1/project-env-router.ts +++ b/backend/src/server/routes/v1/project-env-router.ts @@ -39,6 +39,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, projectId: req.params.workspaceId, ...req.body }); @@ -95,6 +96,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { const { environment, old } = await server.services.projectEnv.updateEnvironment({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, id: req.params.id, @@ -153,6 +155,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { const environment = await server.services.projectEnv.deleteEnvironment({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, 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 b34260117c..440d451403 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, nonce: req.body.key.nonce, receiverId: req.body.key.userId, diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index aece95a5d4..9e2f5bd22c 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -66,6 +66,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const memberships = await server.services.projectMembership.getProjectMemberships({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); @@ -102,6 +103,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const data = await server.services.projectMembership.addUsersToProject({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, members: req.body.members @@ -170,6 +172,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const roles = await server.services.projectMembership.updateProjectMembership({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, membershipId: req.params.membershipId, @@ -219,6 +222,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const membership = await server.services.projectMembership.deleteProjectMembership({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.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 3ffedf98d7..55f1fdda9e 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -10,6 +10,7 @@ import { import { PROJECTS } from "@app/lib/api-docs"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +import { ProjectFilterType } from "@app/services/project/project-types"; import { integrationAuthPubSchema } from "../sanitizedSchemas"; import { sanitizedServiceTokenSchema } from "../v2/service-token-router"; @@ -45,6 +46,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const publicKeys = await server.services.projectKey.getProjectPublicKeys({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); @@ -97,6 +99,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const users = await server.services.projectMembership.getProjectMemberships({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, projectId: req.params.workspaceId, actorOrgId: req.permission.orgId }); @@ -137,37 +140,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const workspace = await server.services.project.getAProject({ + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actor: req.permission.type, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId - }); - return { workspace }; - } - }); - - server.route({ - url: "/", - method: "POST", - schema: { - body: z.object({ - workspaceName: z.string().trim(), - organizationId: z.string().trim() - }), - response: { - 200: z.object({ - workspace: projectWithEnv - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const workspace = await server.services.project.createProject({ - actorId: req.permission.id, - actor: req.permission.type, - orgId: req.body.organizationId, - actorOrgId: req.permission.orgId, - workspaceName: req.body.workspaceName + actorOrgId: req.permission.orgId }); return { workspace }; } @@ -189,10 +169,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const workspace = await server.services.project.deleteProject({ + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + actorOrgId: req.permission.orgId }); return { workspace }; } @@ -220,6 +204,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.updateName({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, name: req.body.name @@ -253,17 +238,21 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const workspace = await server.services.project.updateProject({ - actorId: req.permission.id, - actor: req.permission.type, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, update: { name: req.body.name, autoCapitalization: req.body.autoCapitalization - } + }, + actorAuthMethod: req.permission.authMethod, + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId }); return { workspace @@ -293,6 +282,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.toggleAutoCapitalization({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, autoCapitalization: req.body.autoCapitalization @@ -329,6 +319,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const integrations = await server.services.integration.listIntegrationByProject({ actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId @@ -354,6 +345,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const authorizations = await server.services.integrationAuth.listIntegrationAuthByProjectId({ actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId @@ -379,6 +371,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const serviceTokenData = await server.services.serviceToken.getProjectServiceTokens({ actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index dee075943e..bd202b70b6 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -39,6 +39,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folder = await server.services.folder.createFolder({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, @@ -96,6 +97,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const { folder, old } = await server.services.folder.updateFolder({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, @@ -154,6 +156,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folder = await server.services.folder.deleteFolder({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, @@ -207,6 +210,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folders = await server.services.folder.getFolders({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId, diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v1/secret-import-router.ts index 823e7dbee0..bd47a74242 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v1/secret-import-router.ts @@ -44,6 +44,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.createImport({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, @@ -114,6 +115,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.updateImport({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, @@ -175,6 +177,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.deleteImport({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, @@ -234,6 +237,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImports = await server.services.secretImport.getImports({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId @@ -287,6 +291,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const importedSecrets = await server.services.secretImport.getSecretsFromImports({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, 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 7ca3e48937..c60f2b9baa 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.projectId }); @@ -53,6 +54,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { const workspaceTag = await server.services.secretTag.createTag({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.projectId, ...req.body @@ -80,6 +82,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { const workspaceTag = await server.services.secretTag.deleteTag({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.tagId }); diff --git a/backend/src/server/routes/v1/user-router.ts b/backend/src/server/routes/v1/user-router.ts index ca5148659f..031bcc9415 100644 --- a/backend/src/server/routes/v1/user-router.ts +++ b/backend/src/server/routes/v1/user-router.ts @@ -15,7 +15,7 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }), handler: async (req) => { const user = await server.services.user.getMe(req.permission.id); return { user }; diff --git a/backend/src/server/routes/v1/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 9a20a5d227..2a5ab49fb2 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, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body @@ -93,6 +94,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.updateWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.webhookId, isDisabled: req.body.isDisabled @@ -130,6 +132,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.deleteWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.webhookId }); @@ -172,6 +175,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.testWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.webhookId }); @@ -204,6 +208,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhooks = await server.services.webhook.listWebhooks({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, 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 97477b0332..440130e130 100644 --- a/backend/src/server/routes/v2/identity-org-router.ts +++ b/backend/src/server/routes/v2/identity-org-router.ts @@ -42,6 +42,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => { const identityMemberships = await server.services.identity.listOrgIdentities({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, orgId: req.params.orgId }); diff --git a/backend/src/server/routes/v2/identity-project-router.ts b/backend/src/server/routes/v2/identity-project-router.ts index 67dccb5e3c..d170f87e62 100644 --- a/backend/src/server/routes/v2/identity-project-router.ts +++ b/backend/src/server/routes/v2/identity-project-router.ts @@ -35,6 +35,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMembership = await server.services.identityProject.createProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId, @@ -89,6 +90,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const roles = await server.services.identityProject.updateProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId, @@ -123,6 +125,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMembership = await server.services.identityProject.deleteProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId @@ -177,6 +180,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMemberships = await server.services.identityProject.listProjectIdentities({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.projectId }); diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index 2c9465aa81..d8e46d0da0 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -68,11 +68,14 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { }, handler: async (req, res) => { const userAgent = req.headers["user-agent"]; + const mfaJwtToken = req.headers.authorization?.replace("Bearer ", ""); if (!userAgent) throw new Error("user agent header is required"); + if (!mfaJwtToken) throw new Error("authorization header is required"); const appCfg = getConfig(); const { user, token } = await server.services.login.verifyMfaToken({ userAgent, + mfaJwtToken, ip: req.realIp, userId: req.mfa.userId, orgId: req.mfa.orgId, diff --git a/backend/src/server/routes/v2/organization-router.ts b/backend/src/server/routes/v2/organization-router.ts index 7d5ba3da7a..83ab6792e5 100644 --- a/backend/src/server/routes/v2/organization-router.ts +++ b/backend/src/server/routes/v2/organization-router.ts @@ -45,6 +45,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const users = await server.services.org.findAllOrgMembers( req.permission.id, req.params.organizationId, + req.permission.authMethod, req.permission.orgId ); return { users }; @@ -89,6 +90,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); @@ -127,6 +129,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const membership = await server.services.org.updateOrgMembership({ userId: req.permission.id, role: req.body.role, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId, membershipId: req.params.membershipId, actorOrgId: req.permission.orgId @@ -162,6 +165,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const membership = await server.services.org.deleteOrgMembership({ userId: req.permission.id, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId, membershipId: req.params.membershipId, actorOrgId: req.permission.orgId @@ -183,7 +187,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY], { requireOrg: false }), handler: async (req) => { if (req.auth.actor !== ActorType.USER) return; @@ -217,6 +221,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const organization = await server.services.org.deleteOrganizationById( req.permission.id, req.params.organizationId, + req.permission.authMethod, req.permission.orgId ); return { organization }; diff --git a/backend/src/server/routes/v2/project-membership-router.ts b/backend/src/server/routes/v2/project-membership-router.ts index 6f81f83920..51bae6d2bb 100644 --- a/backend/src/server/routes/v2/project-membership-router.ts +++ b/backend/src/server/routes/v2/project-membership-router.ts @@ -28,7 +28,9 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider handler: async (req) => { const memberships = await server.services.projectMembership.addUsersToProjectNonE2EE({ projectId: req.params.projectId, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, + actorOrgId: req.permission.orgId, actor: req.permission.type, emails: req.body.emails, usernames: req.body.usernames @@ -74,6 +76,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const memberships = await server.services.projectMembership.deleteProjectMemberships({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.projectId, emails: req.body.emails, diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 208edba7b9..7ae5b35087 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -8,6 +8,7 @@ import { authRateLimit } from "@app/server/config/rateLimiter"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +import { ProjectFilterType } from "@app/services/project/project-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; const projectWithEnv = ProjectsSchema.merge( @@ -17,6 +18,14 @@ const projectWithEnv = ProjectsSchema.merge( }) ); +const slugSchema = z + .string() + .min(5) + .max(36) + .refine((v) => slugify(v) === v, { + message: "Slug must be at least 5 character but no more than 36" + }); + export const registerProjectRouter = async (server: FastifyZodProvider) => { /* Get project key */ server.route({ @@ -47,6 +56,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const key = await server.services.projectKey.getLatestProjectKey({ actor: req.permission.type, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); @@ -74,7 +84,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { params: z.object({ projectId: z.string().trim() }), - body: z.object({ userPrivateKey: z.string().trim() }), @@ -82,11 +91,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { 200: z.void() } }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), + onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { await server.services.project.upgradeProject({ actorId: req.permission.id, + actorOrgId: req.permission.orgId, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, projectId: req.params.projectId, userPrivateKey: req.body.userPrivateKey }); @@ -107,9 +118,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), + onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const status = await server.services.project.getProjectUpgradeStatus({ + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, projectId: req.params.projectId, actor: req.permission.type, actorId: req.permission.id @@ -138,7 +151,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }) .optional() .describe(PROJECTS.CREATE.slug), - organizationId: z.string().trim().describe(PROJECTS.CREATE.organizationId) + organizationSlug: z.string().trim().describe(PROJECTS.CREATE.organizationSlug) }), response: { 200: z.object({ @@ -146,12 +159,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const project = await server.services.project.createProject({ actorId: req.permission.id, actor: req.permission.type, - orgId: req.body.organizationId, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + orgSlug: req.body.organizationSlug, workspaceName: req.body.projectName, slug: req.body.slug }); @@ -160,7 +175,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { event: PostHogEventTypes.ProjectCreated, distinctId: getTelemetryDistinctId(req), properties: { - orgId: req.body.organizationId, + orgId: project.orgId, name: project.name, ...req.auditLogInfo } @@ -169,4 +184,104 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { return { project }; } }); + + /* Delete a project by slug */ + server.route({ + method: "DELETE", + url: "/:slug", + schema: { + params: z.object({ + slug: slugSchema.describe("The slug of the project to delete.") + }), + response: { + 200: ProjectsSchema + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + + handler: async (req) => { + const project = await server.services.project.deleteProject({ + filter: { + type: ProjectFilterType.SLUG, + slug: req.params.slug, + orgId: req.permission.orgId + }, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + actor: req.permission.type + }); + + return project; + } + }); + + /* Get a project by slug */ + server.route({ + method: "GET", + url: "/:slug", + schema: { + params: z.object({ + slug: slugSchema.describe("The slug of the project to get.") + }), + response: { + 200: projectWithEnv + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const project = await server.services.project.getAProject({ + filter: { + slug: req.params.slug, + orgId: req.permission.orgId, + type: ProjectFilterType.SLUG + }, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type + }); + + return project; + } + }); + + /* Update a project by slug */ + server.route({ + method: "PATCH", + url: "/:slug", + schema: { + params: z.object({ + slug: slugSchema.describe("The slug of the project to update.") + }), + body: z.object({ + name: z.string().trim().optional().describe("The new name of the project."), + autoCapitalization: z.boolean().optional().describe("The new auto-capitalization setting.") + }), + response: { + 200: ProjectsSchema + } + }, + + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const project = await server.services.project.updateProject({ + filter: { + type: ProjectFilterType.SLUG, + slug: req.params.slug, + orgId: req.permission.orgId + }, + update: { + name: req.body.name, + autoCapitalization: req.body.autoCapitalization + }, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId + }); + + return project; + } + }); }; diff --git a/backend/src/server/routes/v2/service-token-router.ts b/backend/src/server/routes/v2/service-token-router.ts index 2b6445deac..c4d08d104c 100644 --- a/backend/src/server/routes/v2/service-token-router.ts +++ b/backend/src/server/routes/v2/service-token-router.ts @@ -46,6 +46,8 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => handler: async (req) => { const { serviceToken, user } = await server.services.serviceToken.getServiceToken({ actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, actor: req.permission.type }); @@ -98,6 +100,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => const { serviceToken, token } = await server.services.serviceToken.createServiceToken({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId @@ -136,6 +139,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => const serviceTokenData = await server.services.serviceToken.deleteServiceToken({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, id: req.params.serviceTokenId }); diff --git a/backend/src/server/routes/v2/user-router.ts b/backend/src/server/routes/v2/user-router.ts index 97bc3d864b..12c9b703df 100644 --- a/backend/src/server/routes/v2/user-router.ts +++ b/backend/src/server/routes/v2/user-router.ts @@ -60,7 +60,7 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { }) } }, - preHandler: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), + preHandler: verifyAuth([AuthMode.JWT, AuthMode.API_KEY], { requireOrg: false }), handler: async (req) => { const user = await server.services.user.updateAuthMethods(req.permission.id, req.body.authMethods); return { user }; diff --git a/backend/src/server/routes/v3/login-router.ts b/backend/src/server/routes/v3/login-router.ts index 240aa21b1e..900ad56d27 100644 --- a/backend/src/server/routes/v3/login-router.ts +++ b/backend/src/server/routes/v3/login-router.ts @@ -34,6 +34,42 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "POST", + url: "/select-organization", + config: { + rateLimit: authRateLimit + }, + schema: { + body: z.object({ + organizationId: z.string().trim() + }), + response: { + 200: z.object({ + token: z.string() + }) + } + }, + handler: async (req, res) => { + const cfg = getConfig(); + const tokens = await server.services.login.selectOrganization({ + userAgent: req.headers["user-agent"], + authJwtToken: req.headers.authorization, + organizationId: req.body.organizationId, + ipAddress: req.realIp + }); + + void res.setCookie("jid", tokens.refresh, { + httpOnly: true, + path: "/", + sameSite: "strict", + secure: cfg.HTTPS_ENABLED + }); + + return { token: tokens.access }; + } + }); + server.route({ method: "POST", url: "/login2", 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 94e6cab83c..664eaa2c94 100644 --- a/backend/src/server/routes/v3/secret-blind-index-router.ts +++ b/backend/src/server/routes/v3/secret-blind-index-router.ts @@ -20,6 +20,7 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) handler: async (req) => { const count = await server.services.secretBlindIndex.getSecretBlindIndexStatus({ projectId: req.params.projectId, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId @@ -52,6 +53,7 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) handler: async (req) => { const secrets = await server.services.secretBlindIndex.getProjectSecrets({ projectId: req.params.projectId, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId @@ -86,6 +88,7 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) await server.services.secretBlindIndex.updateProjectSecretName({ projectId: req.params.projectId, secretsToUpdate: req.body.secretsToUpdate, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 65219d0ab9..1e224aa3b6 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -17,6 +17,7 @@ import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { getUserAgentType } from "@app/server/plugins/audit-log"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { ActorType, AuthMode } from "@app/services/auth/auth-type"; +import { ProjectFilterType } from "@app/services/project/project-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { secretRawSchema } from "../sanitizedSchemas"; @@ -35,6 +36,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { ], querystring: z.object({ workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId), + workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug), environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment), secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath), include_imports: z @@ -70,6 +72,22 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { environment = scope[0].environment; workspaceId = req.auth.serviceToken.projectId; } + } else if (req.permission.type === ActorType.IDENTITY && req.query.workspaceSlug && !workspaceId) { + const workspace = await server.services.project.getAProject({ + filter: { + type: ProjectFilterType.SLUG, + orgId: req.permission.orgId, + slug: req.query.workspaceSlug + }, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + actorOrgId: req.permission.orgId + }); + + if (!workspace) throw new BadRequestError({ message: `No project found with slug ${req.query.workspaceSlug}` }); + + workspaceId = workspace.id; } if (!workspaceId || !environment) throw new BadRequestError({ message: "Missing workspace id or environment" }); @@ -79,13 +97,14 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId, environment, + actorAuthMethod: req.permission.authMethod, projectId: workspaceId, path: secretPath, includeImports: req.query.include_imports }); await server.services.auditLog.createAuditLog({ - projectId: req.query.workspaceId, + projectId: workspaceId, ...req.auditLogInfo, event: { type: EventType.GET_SECRETS, @@ -163,6 +182,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.getSecretByNameRaw({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, environment, projectId: workspaceId, @@ -248,6 +268,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId, environment: req.body.environment, + actorAuthMethod: req.permission.authMethod, projectId: req.body.workspaceId, secretPath: req.body.secretPath, secretName: req.params.secretName, @@ -331,6 +352,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, environment: req.body.environment, projectId: req.body.workspaceId, secretPath: req.body.secretPath, @@ -407,6 +429,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.deleteSecretRaw({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, environment: req.body.environment, projectId: req.body.workspaceId, @@ -502,6 +525,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const { secrets, imports } = await server.services.secret.getSecrets({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, environment: req.query.environment, projectId: req.query.workspaceId, @@ -588,6 +612,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.getSecretByName({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, environment: req.query.environment, projectId: req.query.workspaceId, @@ -690,6 +715,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, secretPath, environment, @@ -699,6 +726,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -742,6 +770,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.createSecret({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, path: secretPath, type, @@ -866,6 +895,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -875,6 +905,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -920,6 +951,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.updateSecret({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, path: secretPath, type, @@ -1010,6 +1042,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -1019,6 +1052,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -1052,6 +1086,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.deleteSecret({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, path: secretPath, type, @@ -1134,6 +1169,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -1143,6 +1179,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -1172,6 +1209,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.createManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, path: secretPath, environment, @@ -1255,6 +1293,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -1264,6 +1303,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -1292,6 +1332,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.updateManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, path: secretPath, environment, @@ -1364,6 +1405,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -1373,6 +1415,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, secretPath, environment, @@ -1400,6 +1443,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.deleteManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, path: req.body.secretPath, environment, diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index 17787be843..ac43df36de 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -108,7 +108,8 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { 200: z.object({ message: z.string(), user: UsersSchema, - token: z.string() + token: z.string(), + organizationId: z.string().nullish() }) } }, @@ -124,12 +125,13 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { }); } - const { user, accessToken, refreshToken } = await server.services.signup.completeEmailAccountSignup({ - ...req.body, - ip: req.realIp, - userAgent, - authorization: req.headers.authorization as string - }); + const { user, accessToken, refreshToken, organizationId } = + await server.services.signup.completeEmailAccountSignup({ + ...req.body, + ip: req.realIp, + userAgent, + authorization: req.headers.authorization as string + }); if (user.email) { void server.services.telemetry.sendLoopsEvent(user.email, user.firstName || "", user.lastName || ""); @@ -152,7 +154,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { secure: appCfg.HTTPS_ENABLED }); - return { message: "Successfully set up account", user, token: accessToken }; + return { message: "Successfully set up account", user, token: accessToken, organizationId }; } }); diff --git a/backend/src/services/auth/auth-fns.ts b/backend/src/services/auth/auth-fns.ts index 0b78ab438a..80fb0b3258 100644 --- a/backend/src/services/auth/auth-fns.ts +++ b/backend/src/services/auth/auth-fns.ts @@ -15,10 +15,10 @@ export const validateProviderAuthToken = (providerToken: string, username?: stri if (decodedToken.username !== username) throw new Error("Invalid auth credentials"); if (decodedToken.organizationId) { - return { orgId: decodedToken.organizationId }; + return { orgId: decodedToken.organizationId, authMethod: decodedToken.authMethod }; } - return {}; + return { authMethod: decodedToken.authMethod, orgId: null }; }; 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 786e69d735..e1b10530d2 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -1,13 +1,16 @@ import jwt from "jsonwebtoken"; import { TUsers, UserDeviceSchema } from "@app/db/schemas"; +import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns"; import { getConfig } from "@app/lib/config/env"; import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto"; -import { BadRequestError } from "@app/lib/errors"; +import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { getServerCfg } from "@app/services/super-admin/super-admin-service"; +import { TTokenDALFactory } from "../auth-token/auth-token-dal"; import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; +import { TOrgDALFactory } from "../org/org-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { TUserDALFactory } from "../user/user-dal"; import { validateProviderAuthToken } from "./auth-fns"; @@ -17,16 +20,24 @@ import { TOauthLoginDTO, TVerifyMfaTokenDTO } from "./auth-login-type"; -import { AuthMethod, AuthTokenType } from "./auth-type"; +import { AuthMethod, AuthModeJwtTokenPayload, AuthModeMfaJwtTokenPayload, AuthTokenType } from "./auth-type"; type TAuthLoginServiceFactoryDep = { userDAL: TUserDALFactory; + orgDAL: TOrgDALFactory; tokenService: TAuthTokenServiceFactory; smtpService: TSmtpService; + tokenDAL: TTokenDALFactory; }; export type TAuthLoginFactory = ReturnType; -export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: TAuthLoginServiceFactoryDep) => { +export const authLoginServiceFactory = ({ + userDAL, + tokenService, + smtpService, + orgDAL, + tokenDAL +}: TAuthLoginServiceFactoryDep) => { /* * Private * Not exported. This is to update user device list @@ -83,12 +94,14 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: user, ip, userAgent, - organizationId + organizationId, + authMethod }: { user: TUsers; ip: string; userAgent: string; - organizationId?: string; + organizationId: string | undefined; + authMethod: AuthMethod; }) => { const cfg = getConfig(); await updateUserDeviceSession(user, ip, userAgent); @@ -98,8 +111,10 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: userId: user.id }); if (!tokenSession) throw new Error("Failed to create token"); + const accessToken = jwt.sign( { + authMethod, authTokenType: AuthTokenType.ACCESS_TOKEN, userId: user.id, tokenVersionId: tokenSession.id, @@ -112,6 +127,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: const refreshToken = jwt.sign( { + authMethod, authTokenType: AuthTokenType.REFRESH_TOKEN, userId: user.id, tokenVersionId: tokenSession.id, @@ -158,9 +174,9 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: const loginExchangeClientProof = async ({ email, clientProof, - providerAuthToken, ip, - userAgent + userAgent, + providerAuthToken }: TLoginClientProofDTO) => { const userEnc = await userDAL.findUserEncKeyByUsername({ username: email @@ -168,14 +184,16 @@ 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)) { - const { orgId } = validateProviderAuthToken(providerAuthToken as string, email); - organizationId = orgId; - } else if (providerAuthToken) { - // SAML SSO - const { orgId } = validateProviderAuthToken(providerAuthToken, email); - organizationId = orgId; + let authMethod = AuthMethod.EMAIL; + let organizationId: string | undefined; + + if (providerAuthToken) { + const decodedProviderToken = validateProviderAuthToken(providerAuthToken, email); + + authMethod = decodedProviderToken.authMethod; + if (isAuthMethodSaml(authMethod) && decodedProviderToken.orgId) { + organizationId = decodedProviderToken.orgId; + } } if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?"); @@ -196,9 +214,9 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (userEnc.isMfaEnabled && userEnc.email) { const mfaToken = jwt.sign( { + authMethod, authTokenType: AuthTokenType.MFA_TOKEN, - userId: userEnc.userId, - organizationId + userId: userEnc.userId }, cfg.AUTH_SECRET, { @@ -221,12 +239,60 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: }, ip, userAgent, + authMethod, organizationId }); return { token, isMfaEnabled: false, user: userEnc } as const; }; + const selectOrganization = async ({ + userAgent, + authJwtToken, + ipAddress, + organizationId + }: { + userAgent: string | undefined; + authJwtToken: string | undefined; + ipAddress: string; + organizationId: string; + }) => { + const cfg = getConfig(); + + if (!authJwtToken) throw new UnauthorizedError({ name: "Authorization header is required" }); + if (!userAgent) throw new UnauthorizedError({ name: "user agent header is required" }); + + // eslint-disable-next-line no-param-reassign + authJwtToken = authJwtToken.replace("Bearer ", ""); // remove bearer from token + + // The decoded JWT token, which contains the auth method. + const decodedToken = jwt.verify(authJwtToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; + if (!decodedToken.authMethod) throw new UnauthorizedError({ name: "Auth method not found on existing token" }); + + const user = await userDAL.findUserEncKeyByUserId(decodedToken.userId); + if (!user) throw new BadRequestError({ message: "User not found", name: "Find user from token" }); + + // Check if the user actually has access to the specified organization. + const userOrgs = await orgDAL.findAllOrgsByUserId(user.id); + const hasOrganizationMembership = userOrgs.some((org) => org.id === organizationId); + + if (!hasOrganizationMembership) { + throw new UnauthorizedError({ message: "User does not have access to the organization" }); + } + + await tokenDAL.incrementTokenSessionVersion(user.id, decodedToken.tokenVersionId); + + const tokens = await generateUserTokens({ + authMethod: decodedToken.authMethod, + user, + userAgent, + ip: ipAddress, + organizationId + }); + + return tokens; + }; + /* * Multi factor authentication re-send code, Get user id from token * saved in frontend @@ -244,12 +310,15 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: * Multi factor authentication verification of code * Third step of login in which user completes with mfa * */ - const verifyMfaToken = async ({ userId, mfaToken, ip, userAgent, orgId }: TVerifyMfaTokenDTO) => { + const verifyMfaToken = async ({ userId, mfaToken, mfaJwtToken, ip, userAgent, orgId }: TVerifyMfaTokenDTO) => { await tokenService.validateTokenForUser({ type: TokenType.TOKEN_EMAIL_MFA, userId, code: mfaToken }); + + const decodedToken = jwt.verify(mfaJwtToken, getConfig().AUTH_SECRET) as AuthModeMfaJwtTokenPayload; + const userEnc = await userDAL.findUserEncKeyByUserId(userId); if (!userEnc) throw new Error("Failed to authenticate user"); @@ -260,7 +329,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: }, ip, userAgent, - organizationId: orgId + organizationId: orgId, + authMethod: decodedToken.authMethod }); return { token, user: userEnc }; @@ -339,6 +409,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: oauth2Login, resendMfaToken, verifyMfaToken, + selectOrganization, generateUserTokens }; }; diff --git a/backend/src/services/auth/auth-login-type.ts b/backend/src/services/auth/auth-login-type.ts index 86af5a5f9e..37b90f548b 100644 --- a/backend/src/services/auth/auth-login-type.ts +++ b/backend/src/services/auth/auth-login-type.ts @@ -17,6 +17,7 @@ export type TLoginClientProofDTO = { export type TVerifyMfaTokenDTO = { userId: string; mfaToken: string; + mfaJwtToken: string; ip: string; userAgent: string; orgId?: string; diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 39bfec8b12..3db935769f 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -150,11 +150,15 @@ export const authSignupServiceFactory = ({ }); if (!organizationId) { - await orgService.createOrganization({ + const newOrganization = await orgService.createOrganization({ userId: user.id, userEmail: user.email ?? user.username, orgName: organizationName }); + + if (!newOrganization) throw new Error("Failed to create organization"); + + organizationId = newOrganization.id; } const updatedMembersips = await orgDAL.updateMembership( @@ -174,6 +178,7 @@ export const authSignupServiceFactory = ({ const accessToken = jwt.sign( { + authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.ACCESS_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, @@ -186,6 +191,7 @@ export const authSignupServiceFactory = ({ const refreshToken = jwt.sign( { + authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.REFRESH_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, @@ -196,7 +202,7 @@ export const authSignupServiceFactory = ({ { expiresIn: appCfg.JWT_REFRESH_LIFETIME } ); - return { user: updateduser.info, accessToken, refreshToken }; + return { user: updateduser.info, accessToken, refreshToken, organizationId }; }; /* @@ -277,6 +283,7 @@ export const authSignupServiceFactory = ({ const accessToken = jwt.sign( { + authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.ACCESS_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, @@ -288,6 +295,7 @@ export const authSignupServiceFactory = ({ const refreshToken = jwt.sign( { + authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.REFRESH_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, diff --git a/backend/src/services/auth/auth-type.ts b/backend/src/services/auth/auth-type.ts index 57c86158f4..a3c53658c2 100644 --- a/backend/src/services/auth/auth-type.ts +++ b/backend/src/services/auth/auth-type.ts @@ -6,6 +6,7 @@ export enum AuthMethod { OKTA_SAML = "okta-saml", AZURE_SAML = "azure-saml", JUMPCLOUD_SAML = "jumpcloud-saml", + GOOGLE_SAML = "google-saml", LDAP = "ldap" } @@ -38,8 +39,12 @@ export enum ActorType { // would extend to AWS, Azure, ... SCIM_CLIENT = "scimClient" } +// This will be null unless the token-type is JWT +export type ActorAuthMethod = AuthMethod | null; + export type AuthModeJwtTokenPayload = { authTokenType: AuthTokenType.ACCESS_TOKEN; + authMethod: AuthMethod; userId: string; tokenVersionId: string; accessVersion: number; @@ -48,12 +53,15 @@ export type AuthModeJwtTokenPayload = { export type AuthModeMfaJwtTokenPayload = { authTokenType: AuthTokenType.MFA_TOKEN; + authMethod: AuthMethod; userId: string; organizationId?: string; }; export type AuthModeRefreshJwtTokenPayload = { + // authMode authTokenType: AuthTokenType.REFRESH_TOKEN; + authMethod: AuthMethod; userId: string; tokenVersionId: string; refreshVersion: number; @@ -63,6 +71,8 @@ export type AuthModeRefreshJwtTokenPayload = { export type AuthModeProviderJwtTokenPayload = { authTokenType: AuthTokenType.PROVIDER_TOKEN; username: string; + authMethod: AuthMethod; + email: string; organizationId?: string; }; diff --git a/backend/src/services/identity-access-token/identity-access-token-service.ts b/backend/src/services/identity-access-token/identity-access-token-service.ts index 32774ccbb0..4b53c81742 100644 --- a/backend/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend/src/services/identity-access-token/identity-access-token-service.ts @@ -6,17 +6,20 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip"; import { AuthTokenType } from "../auth/auth-type"; +import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "./identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload, TRenewAccessTokenDTO } from "./identity-access-token-types"; type TIdentityAccessTokenServiceFactoryDep = { identityAccessTokenDAL: TIdentityAccessTokenDALFactory; + identityOrgMembershipDAL: TIdentityOrgDALFactory; }; export type TIdentityAccessTokenServiceFactory = ReturnType; export const identityAccessTokenServiceFactory = ({ - identityAccessTokenDAL + identityAccessTokenDAL, + identityOrgMembershipDAL }: TIdentityAccessTokenServiceFactoryDep) => { const validateAccessTokenExp = (identityAccessToken: TIdentityAccessTokens) => { const { @@ -117,8 +120,16 @@ export const identityAccessTokenServiceFactory = ({ }); } + const identityOrgMembership = await identityOrgMembershipDAL.findOne({ + identityId: identityAccessToken.identityId + }); + + if (!identityOrgMembership) { + throw new UnauthorizedError({ message: "Identity does not belong to any organization" }); + } + validateAccessTokenExp(identityAccessToken); - return identityAccessToken; + return { ...identityAccessToken, orgId: identityOrgMembership.orgId }; }; return { renewAccessToken, fnValidateIdentityAccessToken }; diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index b6f6e4343d..d554c81ac5 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -49,10 +49,17 @@ export const identityProjectServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, projectId, role }: TCreateProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Identity); const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId }); @@ -112,9 +119,16 @@ export const identityProjectServiceFactory = ({ roles, actor, actorId, + actorAuthMethod, actorOrgId }: TUpdateProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId }); @@ -127,6 +141,7 @@ export const identityProjectServiceFactory = ({ ActorType.IDENTITY, projectIdentity.identityId, projectIdentity.projectId, + actorAuthMethod, actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); @@ -185,6 +200,7 @@ export const identityProjectServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, projectId }: TDeleteProjectIdentityDTO) => { const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId }); @@ -195,6 +211,7 @@ export const identityProjectServiceFactory = ({ actor, actorId, identityProjectMembership.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity); @@ -202,6 +219,7 @@ export const identityProjectServiceFactory = ({ ActorType.IDENTITY, identityId, identityProjectMembership.projectId, + actorAuthMethod, actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); @@ -212,8 +230,20 @@ export const identityProjectServiceFactory = ({ return deletedIdentity; }; - const listProjectIdentities = async ({ projectId, actor, actorId, actorOrgId }: TListProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const listProjectIdentities = async ({ + projectId, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TListProjectIdentityDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + 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 d375a8fa54..54a074073e 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -144,6 +144,7 @@ export const identityUaServiceFactory = ({ accessTokenTrustedIps, clientSecretTrustedIps, actorId, + actorAuthMethod, actor, actorOrgId }: TAttachUaDTO) => { @@ -162,6 +163,7 @@ export const identityUaServiceFactory = ({ actor, actorId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); @@ -233,6 +235,7 @@ export const identityUaServiceFactory = ({ accessTokenTrustedIps, clientSecretTrustedIps, actorId, + actorAuthMethod, actor, actorOrgId }: TUpdateUaDTO) => { @@ -256,6 +259,7 @@ export const identityUaServiceFactory = ({ actor, actorId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); @@ -308,7 +312,7 @@ export const identityUaServiceFactory = ({ return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId }; }; - const getIdentityUa = async ({ identityId, actorId, actor, actorOrgId }: TGetUaDTO) => { + const getIdentityUa = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) @@ -322,6 +326,7 @@ export const identityUaServiceFactory = ({ actor, actorId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); @@ -334,6 +339,7 @@ export const identityUaServiceFactory = ({ actorOrgId, identityId, ttl, + actorAuthMethod, description, numUsesLimit }: TCreateUaClientSecretDTO) => { @@ -347,6 +353,7 @@ export const identityUaServiceFactory = ({ actor, actorId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); @@ -355,6 +362,7 @@ export const identityUaServiceFactory = ({ ActorType.IDENTITY, identityMembershipOrg.identityId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); @@ -388,7 +396,13 @@ export const identityUaServiceFactory = ({ }; }; - const getUaClientSecrets = async ({ actor, actorId, actorOrgId, identityId }: TGetUaClientSecretsDTO) => { + const getUaClientSecrets = async ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + identityId + }: TGetUaClientSecretsDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) @@ -399,6 +413,7 @@ export const identityUaServiceFactory = ({ actor, actorId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); @@ -407,6 +422,7 @@ export const identityUaServiceFactory = ({ ActorType.IDENTITY, identityMembershipOrg.identityId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); @@ -431,6 +447,7 @@ export const identityUaServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, clientSecretId }: TRevokeUaClientSecretDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); @@ -443,6 +460,7 @@ export const identityUaServiceFactory = ({ actor, actorId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); @@ -451,6 +469,7 @@ export const identityUaServiceFactory = ({ ActorType.IDENTITY, identityMembershipOrg.identityId, identityMembershipOrg.orgId, + actorAuthMethod, actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index e37a3a6dd1..cdea456313 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -25,8 +25,16 @@ export const identityServiceFactory = ({ identityOrgMembershipDAL, permissionService }: TIdentityServiceFactoryDep) => { - const createIdentity = async ({ name, role, actor, orgId, actorId, actorOrgId }: TCreateIdentityDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const createIdentity = async ({ + name, + role, + actor, + orgId, + actorId, + actorAuthMethod, + actorOrgId + }: TCreateIdentityDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole( @@ -54,7 +62,15 @@ export const identityServiceFactory = ({ return identity; }; - const updateIdentity = async ({ id, role, name, actor, actorId, actorOrgId }: TUpdateIdentityDTO) => { + const updateIdentity = async ({ + id, + role, + name, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TUpdateIdentityDTO) => { const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id }); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); @@ -62,6 +78,7 @@ export const identityServiceFactory = ({ actor, actorId, identityOrgMembership.orgId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); @@ -70,6 +87,7 @@ export const identityServiceFactory = ({ ActorType.IDENTITY, id, identityOrgMembership.orgId, + actorAuthMethod, actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); @@ -108,7 +126,7 @@ export const identityServiceFactory = ({ return { ...identity, orgId: identityOrgMembership.orgId }; }; - const deleteIdentity = async ({ actorId, actor, actorOrgId, id }: TDeleteIdentityDTO) => { + const deleteIdentity = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteIdentityDTO) => { const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id }); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); @@ -116,13 +134,16 @@ export const identityServiceFactory = ({ actor, actorId, identityOrgMembership.orgId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); const { permission: identityRolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, id, - identityOrgMembership.orgId + identityOrgMembership.orgId, + actorAuthMethod, + actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) @@ -132,8 +153,8 @@ export const identityServiceFactory = ({ return { ...deletedIdentity, orgId: identityOrgMembership.orgId }; }; - const listOrgIdentities = async ({ orgId, actor, actorId, actorOrgId }: TOrgPermission) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const listOrgIdentities = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPermission) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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 35ff27a9af..c8551df7d4 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -64,14 +64,26 @@ export const integrationAuthServiceFactory = ({ projectBotDAL, projectBotService }: TIntegrationAuthServiceFactoryDep) => { - const listIntegrationAuthByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const listIntegrationAuthByProjectId = async ({ + actorId, + actor, + actorOrgId, + actorAuthMethod, + projectId + }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const authorizations = await integrationAuthDAL.find({ projectId }); return authorizations; }; - const getIntegrationAuth = async ({ actor, id, actorId, actorOrgId }: TGetIntegrationAuthDTO) => { + const getIntegrationAuth = async ({ actor, id, actorId, actorAuthMethod, actorOrgId }: TGetIntegrationAuthDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -79,6 +91,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -90,6 +103,7 @@ export const integrationAuthServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, integration, url, code @@ -97,7 +111,13 @@ 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); const bot = await projectBotDAL.findOne({ isActive: true, projectId }); @@ -153,6 +173,7 @@ export const integrationAuthServiceFactory = ({ url, actor, actorOrgId, + actorAuthMethod, accessId, namespace, accessToken @@ -160,7 +181,13 @@ 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); const bot = await projectBotDAL.findOne({ isActive: true, projectId }); @@ -277,6 +304,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, teamId, id, workspaceSlug @@ -288,6 +316,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -305,7 +334,13 @@ export const integrationAuthServiceFactory = ({ return apps; }; - const getIntegrationAuthTeams = async ({ actor, actorId, actorOrgId, id }: TIntegrationAuthTeamsDTO) => { + const getIntegrationAuthTeams = async ({ + actor, + actorId, + actorAuthMethod, + actorOrgId, + id + }: TIntegrationAuthTeamsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -313,6 +348,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -327,7 +363,14 @@ export const integrationAuthServiceFactory = ({ return teams; }; - const getVercelBranches = async ({ appId, id, actor, actorId, actorOrgId }: TIntegrationAuthVercelBranchesDTO) => { + const getVercelBranches = async ({ + appId, + id, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TIntegrationAuthVercelBranchesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -335,6 +378,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -360,7 +404,14 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getChecklyGroups = async ({ actorId, actor, actorOrgId, id, accountId }: TIntegrationAuthChecklyGroupsDTO) => { + const getChecklyGroups = async ({ + actorId, + actor, + actorOrgId, + actorAuthMethod, + id, + accountId + }: TIntegrationAuthChecklyGroupsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -368,6 +419,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -386,7 +438,7 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getGithubOrgs = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthGithubOrgsDTO) => { + const getGithubOrgs = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TIntegrationAuthGithubOrgsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -394,6 +446,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -418,6 +471,7 @@ export const integrationAuthServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, id, repoOwner, repoName @@ -429,6 +483,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -452,7 +507,7 @@ export const integrationAuthServiceFactory = ({ return environments.map(({ id: envId, name }) => ({ name, envId: String(envId) })); }; - const getQoveryOrgs = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthQoveryOrgsDTO) => { + const getQoveryOrgs = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TIntegrationAuthQoveryOrgsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -460,6 +515,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -478,7 +534,14 @@ export const integrationAuthServiceFactory = ({ return data.results.map(({ name, id: orgId }) => ({ name, orgId })); }; - const getQoveryProjects = async ({ actorId, actor, actorOrgId, id, orgId }: TIntegrationAuthQoveryProjectDTO) => { + const getQoveryProjects = async ({ + actorId, + actor, + actorOrgId, + actorAuthMethod, + id, + orgId + }: TIntegrationAuthQoveryProjectDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -486,6 +549,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -511,6 +575,7 @@ export const integrationAuthServiceFactory = ({ id, actor, actorId, + actorAuthMethod, actorOrgId }: TIntegrationAuthQoveryEnvironmentsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); @@ -520,6 +585,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -545,7 +611,14 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryApps = async ({ id, actor, actorId, actorOrgId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { + const getQoveryApps = async ({ + id, + actor, + actorId, + actorOrgId, + actorAuthMethod, + environmentId + }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -553,6 +626,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -582,6 +656,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, environmentId }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); @@ -591,6 +666,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -615,7 +691,14 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryJobs = async ({ id, actor, actorId, actorOrgId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { + const getQoveryJobs = async ({ + id, + actor, + actorId, + actorOrgId, + actorAuthMethod, + environmentId + }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -623,6 +706,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -647,7 +731,13 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getHerokuPipelines = async ({ id, actor, actorId, actorOrgId }: TIntegrationAuthHerokuPipelinesDTO) => { + const getHerokuPipelines = async ({ + id, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TIntegrationAuthHerokuPipelinesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -655,6 +745,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -679,7 +770,14 @@ export const integrationAuthServiceFactory = ({ })); }; - const getRailwayEnvironments = async ({ id, actor, actorId, actorOrgId, appId }: TIntegrationAuthRailwayEnvDTO) => { + const getRailwayEnvironments = async ({ + id, + actor, + actorId, + actorOrgId, + actorAuthMethod, + appId + }: TIntegrationAuthRailwayEnvDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -687,6 +785,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -739,7 +838,14 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getRailwayServices = async ({ id, actor, actorId, actorOrgId, appId }: TIntegrationAuthRailwayServicesDTO) => { + const getRailwayServices = async ({ + id, + actor, + actorId, + actorOrgId, + actorAuthMethod, + appId + }: TIntegrationAuthRailwayServicesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -747,6 +853,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -806,7 +913,13 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getBitbucketWorkspaces = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthBitbucketWorkspaceDTO) => { + const getBitbucketWorkspaces = async ({ + actorId, + actor, + actorOrgId, + actorAuthMethod, + id + }: TIntegrationAuthBitbucketWorkspaceDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -814,6 +927,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -852,6 +966,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, appId }: TIntegrationAuthNorthflankSecretGroupDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); @@ -861,6 +976,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -918,6 +1034,7 @@ export const integrationAuthServiceFactory = ({ id, actorId, actorOrgId, + actorAuthMethod, actor }: TGetIntegrationAuthTeamCityBuildConfigDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); @@ -927,6 +1044,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -958,16 +1076,29 @@ export const integrationAuthServiceFactory = ({ integration, actor, actorId, + actorAuthMethod, actorOrgId }: TDeleteIntegrationAuthsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); const integrations = await integrationAuthDAL.delete({ integration, projectId }); return integrations; }; - const deleteIntegrationAuthById = async ({ id, actorId, actor, actorOrgId }: TDeleteIntegrationAuthByIdDTO) => { + const deleteIntegrationAuthById = async ({ + id, + actorId, + actor, + actorAuthMethod, + actorOrgId + }: TDeleteIntegrationAuthByIdDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -975,6 +1106,7 @@ export const integrationAuthServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); diff --git a/backend/src/services/integration/integration-service.ts b/backend/src/services/integration/integration-service.ts index 4a6bed75f4..5bf412c9be 100644 --- a/backend/src/services/integration/integration-service.ts +++ b/backend/src/services/integration/integration-service.ts @@ -42,6 +42,7 @@ export const integrationServiceFactory = ({ metadata, secretPath, targetService, + actorAuthMethod, targetServiceId, integrationAuthId, sourceEnvironment, @@ -55,6 +56,7 @@ export const integrationServiceFactory = ({ actor, actorId, integrationAuth.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); @@ -93,6 +95,7 @@ export const integrationServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, targetEnvironment, app, id, @@ -109,6 +112,7 @@ export const integrationServiceFactory = ({ actor, actorId, integration.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); @@ -129,7 +133,7 @@ export const integrationServiceFactory = ({ return updatedIntegration; }; - const deleteIntegration = async ({ actorId, id, actor, actorOrgId }: TDeleteIntegrationDTO) => { + const deleteIntegration = async ({ actorId, id, actor, actorAuthMethod, actorOrgId }: TDeleteIntegrationDTO) => { const integration = await integrationDAL.findById(id); if (!integration) throw new BadRequestError({ message: "Integration auth not found" }); @@ -137,6 +141,7 @@ export const integrationServiceFactory = ({ actor, actorId, integration.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); @@ -145,8 +150,20 @@ export const integrationServiceFactory = ({ return { ...integration, ...deletedIntegration }; }; - const listIntegrationByProject = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const listIntegrationByProject = async ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + projectId + }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const integrations = await integrationDAL.findByProjectId(projectId); diff --git a/backend/src/services/org/org-role-service.ts b/backend/src/services/org/org-role-service.ts index fb8a574405..70c54ff183 100644 --- a/backend/src/services/org/org-role-service.ts +++ b/backend/src/services/org/org-role-service.ts @@ -12,6 +12,7 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { BadRequestError } from "@app/lib/errors"; +import { ActorAuthMethod } from "../auth/auth-type"; import { TOrgRoleDALFactory } from "./org-role-dal"; type TOrgRoleServiceFactoryDep = { @@ -26,9 +27,10 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol userId: string, orgId: string, data: Omit, - actorOrgId?: string + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined ) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, 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" }); @@ -45,9 +47,10 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol orgId: string, roleId: string, data: Omit, - actorOrgId?: string + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined ) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Role); if (data?.slug) { const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId }); @@ -62,8 +65,14 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return updatedRole; }; - const deleteRole = async (userId: string, orgId: string, roleId: string, actorOrgId?: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const deleteRole = async ( + userId: string, + orgId: string, + roleId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role); const [deletedRole] = await orgRoleDAL.delete({ id: roleId, orgId }); if (!deletedRole) throw new BadRequestError({ message: "Role not found", name: "Update role" }); @@ -71,8 +80,13 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return deletedRole; }; - const listRoles = async (userId: string, orgId: string, actorOrgId?: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const listRoles = async ( + userId: string, + orgId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role); const customRoles = await orgRoleDAL.find({ orgId }); const roles = [ @@ -115,8 +129,18 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return roles; }; - const getUserPermission = async (userId: string, orgId: string, actorOrgId?: string) => { - const { permission, membership } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const getUserPermission = async ( + userId: string, + orgId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission, membership } = await permissionService.getUserOrgPermission( + userId, + orgId, + actorAuthMethod, + 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 db6d9654d7..64cd51e098 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -18,7 +18,7 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { isDisposableEmail } from "@app/lib/validator"; -import { ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type"; +import { ActorAuthMethod, ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type"; import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; import { TProjectDALFactory } from "../project/project-dal"; @@ -79,8 +79,13 @@ export const orgServiceFactory = ({ /* * Get organization details by the organization id * */ - const findOrganizationById = async (userId: string, orgId: string, actorOrgId?: string) => { - await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const findOrganizationById = async ( + userId: string, + orgId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); const org = await orgDAL.findOrgById(orgId); if (!org) throw new BadRequestError({ name: "Org not found", message: "Organization not found" }); return org; @@ -95,16 +100,28 @@ export const orgServiceFactory = ({ /* * Get all workspace members * */ - const findAllOrgMembers = async (userId: string, orgId: string, actorOrgId?: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const findAllOrgMembers = async ( + userId: string, + orgId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); const members = await orgDAL.findAllOrgMembers(orgId); return members; }; - const findOrgMembersByUsername = async ({ actor, actorId, orgId, emails }: TFindOrgMembersByEmailDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const findOrgMembersByUsername = async ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + orgId, + emails + }: TFindOrgMembersByEmailDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); const members = await orgDAL.findOrgMembersByUsername(orgId, emails); @@ -112,8 +129,8 @@ export const orgServiceFactory = ({ return members; }; - const findAllWorkspaces = async ({ actor, actorId, actorOrgId, orgId }: TFindAllWorkspacesDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const findAllWorkspaces = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TFindAllWorkspacesDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace); const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id)); @@ -193,10 +210,11 @@ export const orgServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, orgId, data: { name, slug, authEnforced, scimEnabled } }: TUpdateOrgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); const plan = await licenseService.getPlan(orgId); @@ -309,8 +327,13 @@ export const orgServiceFactory = ({ /* * Delete organization by id * */ - const deleteOrganizationById = async (userId: string, orgId: string, actorOrgId?: string) => { - const { membership } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const deleteOrganizationById = async ( + userId: string, + orgId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { membership } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); if ((membership.role as OrgMembershipRole) !== OrgMembershipRole.Admin) throw new UnauthorizedError({ name: "Delete org by id", message: "Not an admin" }); @@ -324,8 +347,15 @@ 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, actorOrgId }: TUpdateOrgMembershipDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const updateOrgMembership = async ({ + role, + orgId, + userId, + membershipId, + actorAuthMethod, + actorOrgId + }: TUpdateOrgMembershipDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member); const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole); @@ -355,8 +385,14 @@ export const orgServiceFactory = ({ /* * Invite user to organization */ - const inviteUserToOrganization = async ({ orgId, userId, inviteeEmail, actorOrgId }: TInviteUserToOrgDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const inviteUserToOrganization = async ({ + orgId, + userId, + inviteeEmail, + actorAuthMethod, + actorOrgId + }: TInviteUserToOrgDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member); const org = await orgDAL.findOrgById(orgId); @@ -515,8 +551,14 @@ export const orgServiceFactory = ({ return { token, user }; }; - const deleteOrgMembership = async ({ orgId, userId, membershipId, actorOrgId }: TDeleteOrgMembershipDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const deleteOrgMembership = async ({ + orgId, + userId, + membershipId, + actorAuthMethod, + actorOrgId + }: TDeleteOrgMembershipDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member); const deletedMembership = await orgDAL.transaction(async (tx) => { @@ -568,15 +610,26 @@ export const orgServiceFactory = ({ /* * CRUD operations of incident contacts * */ - const findIncidentContacts = async (userId: string, orgId: string, actorOrgId?: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const findIncidentContacts = async ( + userId: string, + orgId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, 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, actorOrgId?: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const createIncidentContact = async ( + userId: string, + orgId: string, + email: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount); const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email }); if (doesIncidentContactExist) { @@ -590,8 +643,14 @@ export const orgServiceFactory = ({ return incidentContact; }; - const deleteIncidentContact = async (userId: string, orgId: string, id: string, actorOrgId?: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); + const deleteIncidentContact = async ( + userId: string, + orgId: string, + id: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount); const incidentContact = await incidentContactDAL.deleteById(id, orgId); diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index bd8fe2e953..c811f9bc0b 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -1,26 +1,29 @@ import { TOrgPermission } from "@app/lib/types"; -import { ActorType } from "../auth/auth-type"; +import { ActorAuthMethod, ActorType } from "../auth/auth-type"; export type TUpdateOrgMembershipDTO = { userId: string; orgId: string; membershipId: string; role: string; - actorOrgId?: string; + actorOrgId: string | undefined; + actorAuthMethod: ActorAuthMethod; }; export type TDeleteOrgMembershipDTO = { userId: string; orgId: string; membershipId: string; - actorOrgId?: string; + actorOrgId: string | undefined; + actorAuthMethod: ActorAuthMethod; }; export type TInviteUserToOrgDTO = { userId: string; orgId: string; - actorOrgId?: string; + actorOrgId: string | undefined; + actorAuthMethod: ActorAuthMethod; inviteeEmail: string; }; @@ -32,7 +35,9 @@ export type TVerifyUserToOrgDTO = { export type TFindOrgMembersByEmailDTO = { actor: ActorType; + actorOrgId: string | undefined; actorId: string; + actorAuthMethod: ActorAuthMethod; orgId: string; emails: string[]; }; @@ -40,7 +45,8 @@ export type TFindOrgMembersByEmailDTO = { export type TFindAllWorkspacesDTO = { actor: ActorType; actorId: string; - actorOrgId?: string; + actorOrgId: string | undefined; + actorAuthMethod: ActorAuthMethod; orgId: string; }; diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index 6e281e69d0..23667ef679 100644 --- a/backend/src/services/project-bot/project-bot-service.ts +++ b/backend/src/services/project-bot/project-bot-service.ts @@ -37,10 +37,17 @@ export const projectBotServiceFactory = ({ projectId, actorOrgId, privateKey, + actorAuthMethod, botKey, publicKey }: TFindBotByProjectIdDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const bot = await projectBotDAL.transaction(async (tx) => { @@ -88,11 +95,25 @@ export const projectBotServiceFactory = ({ } }; - const setBotActiveState = async ({ actor, botId, botKey, actorId, actorOrgId, isActive }: TSetActiveStateDTO) => { + const setBotActiveState = async ({ + actor, + botId, + botKey, + actorId, + actorOrgId, + actorAuthMethod, + 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + bot.projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); const project = await projectBotDAL.findProjectByBotId(botId); diff --git a/backend/src/services/project-env/project-env-service.ts b/backend/src/services/project-env/project-env-service.ts index 6ebb3a3d69..2acda33c00 100644 --- a/backend/src/services/project-env/project-env-service.ts +++ b/backend/src/services/project-env/project-env-service.ts @@ -27,8 +27,22 @@ export const projectEnvServiceFactory = ({ projectDAL, folderDAL }: TProjectEnvServiceFactoryDep) => { - const createEnvironment = async ({ projectId, actorId, actor, actorOrgId, name, slug }: TCreateEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const createEnvironment = async ({ + projectId, + actorId, + actor, + actorOrgId, + actorAuthMethod, + name, + slug + }: TCreateEnvDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Environments); const envs = await projectEnvDAL.find({ projectId }); @@ -65,11 +79,18 @@ export const projectEnvServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, name, id, position }: TUpdateEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments); const oldEnv = await projectEnvDAL.findOne({ id, projectId }); @@ -94,8 +115,14 @@ export const projectEnvServiceFactory = ({ return { environment: env, old: oldEnv }; }; - const deleteEnvironment = async ({ projectId, actor, actorId, actorOrgId, id }: TDeleteEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const deleteEnvironment = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod, id }: TDeleteEnvDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + 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 fa77760a44..70c8365ee4 100644 --- a/backend/src/services/project-key/project-key-service.ts +++ b/backend/src/services/project-key/project-key-service.ts @@ -26,11 +26,18 @@ export const projectKeyServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, projectId, nonce, encryptedKey }: TUploadProjectKeyDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); const receiverMembership = await projectMembershipDAL.findOne({ @@ -46,14 +53,32 @@ export const projectKeyServiceFactory = ({ await projectKeyDAL.create({ projectId, receiverId, encryptedKey, nonce, senderId: actorId }); }; - const getLatestProjectKey = async ({ actorId, projectId, actor, actorOrgId }: TGetLatestProjectKeyDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getLatestProjectKey = async ({ + actorId, + projectId, + actor, + actorOrgId, + actorAuthMethod + }: TGetLatestProjectKeyDTO) => { + await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId); const latestKey = await projectKeyDAL.findLatestProjectKey(actorId, projectId); return latestKey; }; - const getProjectPublicKeys = async ({ actor, actorId, actorOrgId, projectId }: TGetLatestProjectKeyDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getProjectPublicKeys = async ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + projectId + }: TGetLatestProjectKeyDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + 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 1f751ee891..059c932107 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -67,8 +67,20 @@ export const projectMembershipServiceFactory = ({ projectKeyDAL, licenseService }: TProjectMembershipServiceFactoryDep) => { - const getProjectMemberships = async ({ actorId, actor, actorOrgId, projectId }: TGetProjectMembershipDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getProjectMemberships = async ({ + actorId, + actor, + actorOrgId, + actorAuthMethod, + projectId + }: TGetProjectMembershipDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member); return projectMembershipDAL.findAllProjectMembers(projectId); @@ -79,13 +91,20 @@ export const projectMembershipServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, members, sendEmails = true }: TAddUsersToWorkspaceDTO) => { const project = await projectDAL.findById(projectId); if (!project) throw new BadRequestError({ message: "Project not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); const orgMembers = await orgDAL.findMembership({ orgId: project.orgId, @@ -145,7 +164,9 @@ export const projectMembershipServiceFactory = ({ const addUsersToProjectNonE2EE = async ({ projectId, actorId, + actorAuthMethod, actor, + actorOrgId, emails, usernames, sendEmails = true @@ -157,7 +178,13 @@ export const projectMembershipServiceFactory = ({ throw new BadRequestError({ message: "Please upgrade your project on your dashboard" }); } - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); const usernamesAndEmails = [...emails, ...usernames]; @@ -273,11 +300,18 @@ export const projectMembershipServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, projectId, membershipId, roles }: TUpdateProjectMembershipDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); const membershipUser = await userDAL.findUserByProjectMembershipId(membershipId); @@ -347,10 +381,17 @@ export const projectMembershipServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, projectId, membershipId }: TDeleteProjectMembershipOldDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member); const member = await userDAL.findUserByProjectMembershipId(membershipId); @@ -374,11 +415,18 @@ export const projectMembershipServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, projectId, emails, usernames }: TDeleteProjectMembershipsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member); const project = await projectDAL.findById(projectId); diff --git a/backend/src/services/project-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index b45a6e8a52..5c8ecdcfff 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -13,7 +13,7 @@ import { } from "@app/ee/services/permission/project-permission"; import { BadRequestError } from "@app/lib/errors"; -import { ActorType } from "../auth/auth-type"; +import { ActorAuthMethod, ActorType } from "../auth/auth-type"; import { TProjectRoleDALFactory } from "./project-role-dal"; type TProjectRoleServiceFactoryDep = { @@ -29,9 +29,16 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: actorId: string, projectId: string, data: Omit, - actorOrgId?: string + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined ) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + 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" }); @@ -49,9 +56,16 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: projectId: string, roleId: string, data: Omit, - actorOrgId?: string + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined ) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role); if (data?.slug) { const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId }); @@ -71,9 +85,16 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: actorId: string, projectId: string, roleId: string, - actorOrgId?: string + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined ) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role); const [deletedRole] = await projectRoleDAL.delete({ id: roleId, projectId }); if (!deletedRole) throw new BadRequestError({ message: "Role not found", name: "Update role" }); @@ -81,8 +102,20 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: return deletedRole; }; - const listRoles = async (actor: ActorType, actorId: string, projectId: string, actorOrgId?: string) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const listRoles = async ( + actor: ActorType, + actorId: string, + projectId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role); const customRoles = await projectRoleDAL.find({ projectId }); const roles = [ @@ -135,8 +168,18 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: return roles; }; - const getUserPermission = async (userId: string, projectId: string, actorOrgId?: string) => { - const { permission, membership } = await permissionService.getUserProjectPermission(userId, projectId, actorOrgId); + const getUserPermission = async ( + userId: string, + projectId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission, membership } = await permissionService.getUserProjectPermission( + userId, + projectId, + actorAuthMethod, + actorOrgId + ); return { permissions: packRules(permission.rules), membership }; }; diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 7d0826e129..369e005acc 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -5,6 +5,8 @@ import { ProjectsSchema, ProjectUpgradeStatus, ProjectVersion, TableName, TProje import { BadRequestError, DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; +import { Filter, ProjectFilterType } from "./project-types"; + export type TProjectDALFactory = ReturnType; export const projectDALFactory = (db: TDbClient) => { @@ -139,7 +141,7 @@ export const projectDALFactory = (db: TDbClient) => { { column: `${TableName.Project}.name`, order: "asc" }, { column: `${TableName.Environment}.position`, order: "asc" } ]); - return sqlNestRelationships({ + const project = sqlNestRelationships({ data: workspaces, key: "id", parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }), @@ -155,11 +157,86 @@ export const projectDALFactory = (db: TDbClient) => { } ] })?.[0]; + + if (!project) { + throw new BadRequestError({ message: "Project not found" }); + } + + return project; } catch (error) { throw new DatabaseError({ error, name: "Find all projects" }); } }; + const findProjectBySlug = async (slug: string, orgId: string) => { + try { + const projects = await db(TableName.ProjectMembership) + .where(`${TableName.Project}.slug`, slug) + .where(`${TableName.Project}.orgId`, orgId) + .join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`) + .join(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`) + .select( + selectAllTableCols(TableName.Project), + db.ref("id").withSchema(TableName.Project).as("_id"), + db.ref("id").withSchema(TableName.Environment).as("envId"), + db.ref("slug").withSchema(TableName.Environment).as("envSlug"), + db.ref("name").withSchema(TableName.Environment).as("envName") + ) + .orderBy([ + { column: `${TableName.Project}.name`, order: "asc" }, + { column: `${TableName.Environment}.position`, order: "asc" } + ]); + + const project = sqlNestRelationships({ + data: projects, + key: "id", + parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }), + childrenMapper: [ + { + key: "envId", + label: "environments" as const, + mapper: ({ envId, envSlug, envName: name }) => ({ + id: envId, + slug: envSlug, + name + }) + } + ] + })?.[0]; + + if (!project) { + throw new BadRequestError({ message: "Project not found" }); + } + + return project; + } catch (error) { + throw new DatabaseError({ error, name: "Find project by slug" }); + } + }; + + const findProjectByFilter = async (filter: Filter) => { + try { + if (filter.type === ProjectFilterType.ID) { + return await findProjectById(filter.projectId); + } + if (filter.type === ProjectFilterType.SLUG) { + if (!filter.orgId) { + throw new BadRequestError({ + message: "Organization ID is required when querying with slugs" + }); + } + + return await findProjectBySlug(filter.slug, filter.orgId); + } + throw new BadRequestError({ message: "Invalid filter type" }); + } catch (error) { + if (error instanceof BadRequestError) { + throw error; + } + throw new DatabaseError({ error, name: `Failed to find project by ${filter.type}` }); + } + }; + const checkProjectUpgradeStatus = async (projectId: string) => { const project = await projectOrm.findById(projectId); const upgradeInProgress = @@ -179,6 +256,8 @@ export const projectDALFactory = (db: TDbClient) => { findAllProjectsByIdentity, findProjectGhostUser, findProjectById, + findProjectByFilter, + findProjectBySlug, checkProjectUpgradeStatus }; }; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 873a7d36f8..71703a50cb 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -18,6 +18,7 @@ import { ActorType } from "../auth/auth-type"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal"; import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal"; +import { TOrgDALFactory } from "../org/org-dal"; import { TOrgServiceFactory } from "../org/org-service"; import { TProjectBotDALFactory } from "../project-bot/project-bot-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; @@ -34,7 +35,9 @@ import { TCreateProjectDTO, TDeleteProjectDTO, TGetProjectDTO, + TToggleProjectAutoCapitalizationDTO, TUpdateProjectDTO, + TUpdateProjectNameDTO, TUpgradeProjectDTO } from "./project-types"; @@ -61,6 +64,7 @@ type TProjectServiceFactoryDep = { permissionService: TPermissionServiceFactory; orgService: Pick; licenseService: Pick; + orgDAL: Pick; }; export type TProjectServiceFactory = ReturnType; @@ -70,6 +74,7 @@ export const projectServiceFactory = ({ projectQueue, projectKeyDAL, permissionService, + orgDAL, userDAL, folderDAL, orgService, @@ -86,11 +91,28 @@ export const projectServiceFactory = ({ /* * Create workspace. Make user the admin * */ - const createProject = async ({ orgId, actor, actorId, actorOrgId, workspaceName, slug }: TCreateProjectDTO) => { + const createProject = async ({ + orgSlug, + actor, + actorId, + actorOrgId, + actorAuthMethod, + workspaceName, + slug: projectSlug + }: TCreateProjectDTO) => { + if (!orgSlug) { + throw new BadRequestError({ + message: "Must provide organization slug to create project" + }); + } + + const organization = await orgDAL.findOne({ slug: orgSlug }); + const { permission, membership: orgMembership } = await permissionService.getOrgPermission( actor, actorId, - orgId, + organization.id, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); @@ -98,7 +120,7 @@ export const projectServiceFactory = ({ const appCfg = getConfig(); const blindIndex = createSecretBlindIndex(appCfg.ROOT_ENCRYPTION_KEY, appCfg.ENCRYPTION_KEY); - const plan = await licenseService.getPlan(orgId); + const plan = await licenseService.getPlan(organization.id); if (plan.workspaceLimit !== null && plan.workspacesUsed >= plan.workspaceLimit) { // case: limit imposed on number of workspaces allowed // case: number of workspaces used exceeds the number of workspaces allowed @@ -108,13 +130,13 @@ export const projectServiceFactory = ({ } const results = await projectDAL.transaction(async (tx) => { - const ghostUser = await orgService.addGhostUser(orgId, tx); + const ghostUser = await orgService.addGhostUser(organization.id, tx); const project = await projectDAL.create( { name: workspaceName, - orgId, - slug: slug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), + orgId: organization.id, + slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), version: ProjectVersion.V2 }, tx @@ -270,7 +292,7 @@ export const projectServiceFactory = ({ // Get the role permission for the identity const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole( ProjectMembershipRole.Admin, - orgId + organization.id ); const hasPrivilege = isAtLeastAsPrivileged(permission, rolePermission); @@ -310,20 +332,28 @@ export const projectServiceFactory = ({ return results; }; - const deleteProject = async ({ actor, actorId, actorOrgId, projectId }: TDeleteProjectDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const deleteProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, filter }: TDeleteProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter); + + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + project.id, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); const deletedProject = await projectDAL.transaction(async (tx) => { - const project = await projectDAL.deleteById(projectId, tx); - const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(projectId).catch(() => null); + const delProject = await projectDAL.deleteById(project.id, tx); + const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id).catch(() => null); // Delete the org membership for the ghost user if it's found. if (projectGhostUser) { await userDAL.deleteById(projectGhostUser.id, tx); } - return project; + return delProject; }); return deletedProject; @@ -334,16 +364,26 @@ export const projectServiceFactory = ({ return workspaces; }; - const getAProject = async ({ actorId, actorOrgId, projectId, actor }: TGetProjectDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); - return projectDAL.findProjectById(projectId); + const getAProject = async ({ actorId, actorOrgId, actorAuthMethod, filter, actor }: TGetProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter); + + await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId); + return project; }; - const updateProject = async ({ projectId, actor, actorId, actorOrgId, update }: TUpdateProjectDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const updateProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, update, filter }: TUpdateProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter); + + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + project.id, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); - const updatedProject = await projectDAL.updateById(projectId, { + const updatedProject = await projectDAL.updateById(project.id, { name: update.name, autoCapitalization: update.autoCapitalization }); @@ -355,25 +395,58 @@ export const projectServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, autoCapitalization - }: TGetProjectDTO & { autoCapitalization: boolean }) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + }: TToggleProjectAutoCapitalizationDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(projectId, { autoCapitalization }); return updatedProject; }; - const updateName = async ({ projectId, actor, actorId, actorOrgId, name }: TGetProjectDTO & { name: string }) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const updateName = async ({ + projectId, + actor, + actorId, + actorOrgId, + actorAuthMethod, + name + }: TUpdateProjectNameDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(projectId, { name }); return updatedProject; }; - const upgradeProject = async ({ projectId, actor, actorId, userPrivateKey }: TUpgradeProjectDTO) => { - const { permission, hasRole } = await permissionService.getProjectPermission(actor, actorId, projectId); + const upgradeProject = async ({ + projectId, + actor, + actorId, + actorAuthMethod, + actorOrgId, + userPrivateKey + }: TUpgradeProjectDTO) => { + const { permission, hasRole } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); @@ -397,8 +470,20 @@ export const projectServiceFactory = ({ }); }; - const getProjectUpgradeStatus = async ({ projectId, actor, actorId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectUpgradeStatus = async ({ + projectId, + actor, + actorAuthMethod, + actorOrgId, + actorId + }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); const project = await projectDAL.findProjectById(projectId); diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 3843450c28..5b2b635f2d 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -1,37 +1,67 @@ import { ProjectMembershipRole, TProjectKeys } from "@app/db/schemas"; import { TProjectPermission } from "@app/lib/types"; -import { ActorType } from "../auth/auth-type"; +import { ActorAuthMethod, ActorType } from "../auth/auth-type"; + +export enum ProjectFilterType { + ID = "id", + SLUG = "slug" +} + +export type Filter = + | { + type: ProjectFilterType.ID; + projectId: string; + } + | { + type: ProjectFilterType.SLUG; + slug: string; + orgId: string | undefined; + }; export type TCreateProjectDTO = { actor: ActorType; + actorAuthMethod: ActorAuthMethod; actorId: string; actorOrgId?: string; - orgId: string; + orgSlug: string; workspaceName: string; slug?: string; }; -export type TDeleteProjectDTO = { +export type TDeleteProjectBySlugDTO = { + slug: string; actor: ActorType; actorId: string; - actorOrgId?: string; - projectId: string; + actorOrgId: string | undefined; }; export type TGetProjectDTO = { - actor: ActorType; - actorId: string; - actorOrgId?: string; - projectId: string; -}; + filter: Filter; +} & Omit; + +export type TToggleProjectAutoCapitalizationDTO = { + autoCapitalization: boolean; +} & TProjectPermission; + +export type TUpdateProjectNameDTO = { + name: string; +} & TProjectPermission; export type TUpdateProjectDTO = { + filter: Filter; update: { name?: string; autoCapitalization?: boolean; }; -} & TProjectPermission; +} & Omit; + +export type TDeleteProjectDTO = { + filter: Filter; + actor: ActorType; + actorId: string; + actorOrgId: string | undefined; +} & Omit; export type TUpgradeProjectDTO = { userPrivateKey: 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 b681266fd9..bf2728e951 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 @@ -28,16 +28,29 @@ export const secretBlindIndexServiceFactory = ({ actor, projectId, actorId, + actorAuthMethod, actorOrgId }: TGetProjectBlindIndexStatusDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId); const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId); return Number(secretCount); }; - const getProjectSecrets = async ({ projectId, actorId, actor }: TGetProjectSecretsDTO) => { - const { hasRole } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectSecrets = async ({ + projectId, + actorId, + actorAuthMethod, + actorOrgId, + actor + }: TGetProjectSecretsDTO) => { + const { hasRole } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); if (!hasRole(ProjectMembershipRole.Admin)) { throw new UnauthorizedError({ message: "User must be admin" }); } @@ -50,10 +63,17 @@ export const secretBlindIndexServiceFactory = ({ projectId, actor, actorId, + actorAuthMethod, actorOrgId, secretsToUpdate }: TUpdateProjectSecretNameDTO) => { - const { hasRole } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { hasRole } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); if (!hasRole(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 26c1c1f4f0..baa1484b1f 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -34,12 +34,19 @@ export const secretFolderServiceFactory = ({ projectId, actor, actorId, + actorAuthMethod, actorOrgId, name, environment, path: secretPath }: TCreateFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -114,12 +121,19 @@ export const secretFolderServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, name, environment, path: secretPath, id }: TUpdateFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -162,11 +176,18 @@ export const secretFolderServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, environment, path: secretPath, idOrName }: TDeleteFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -196,12 +217,13 @@ export const secretFolderServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, 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, actorOrgId); + await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, 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 1beae9be69..40f9797e4f 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -45,10 +45,17 @@ export const secretImportServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, projectId, path }: TCreateSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); // check if user has permission to import into destination path ForbiddenError.from(permission).throwUnlessCan( @@ -97,10 +104,17 @@ export const secretImportServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, data, id }: TUpdateSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -144,9 +158,16 @@ export const secretImportServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, id }: TDeleteSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -167,8 +188,22 @@ export const secretImportServiceFactory = ({ return secImport; }; - const getImports = async ({ path, environment, projectId, actor, actorId, actorOrgId }: TGetSecretImportsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getImports = async ({ + path, + environment, + projectId, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TGetSecretImportsDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -186,10 +221,17 @@ export const secretImportServiceFactory = ({ environment, projectId, actor, + actorAuthMethod, actorId, actorOrgId }: TGetSecretsFromImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + 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 1007ec4c3d..ed8f5fec78 100644 --- a/backend/src/services/secret-tag/secret-tag-service.ts +++ b/backend/src/services/secret-tag/secret-tag-service.ts @@ -15,8 +15,23 @@ type TSecretTagServiceFactoryDep = { export type TSecretTagServiceFactory = ReturnType; export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => { - const createTag = async ({ name, slug, actor, color, actorId, actorOrgId, projectId }: TCreateTagDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const createTag = async ({ + name, + slug, + actor, + color, + actorId, + actorOrgId, + actorAuthMethod, + projectId + }: TCreateTagDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Tags); const existingTag = await secretTagDAL.findOne({ slug, projectId }); @@ -32,19 +47,31 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe return newTag; }; - const deleteTag = async ({ actorId, actor, actorOrgId, id }: TDeleteTagDTO) => { + const deleteTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + tag.projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags); const deletedTag = await secretTagDAL.deleteById(tag.id); return deletedTag; }; - const getProjectTags = async ({ actor, actorId, actorOrgId, projectId }: TListProjectTagsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + 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 51136e40f5..f47428fc7f 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -145,10 +145,17 @@ export const secretServiceFactory = ({ actorId, actorOrgId, environment, + actorAuthMethod, projectId, ...inputSecret }: TCreateSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -230,10 +237,17 @@ export const secretServiceFactory = ({ actorId, actorOrgId, environment, + actorAuthMethod, projectId, ...inputSecret }: TUpdateSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -341,11 +355,18 @@ export const secretServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, environment, projectId, ...inputSecret }: TDeleteSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -401,9 +422,16 @@ export const secretServiceFactory = ({ projectId, actor, actorOrgId, + actorAuthMethod, includeImports }: TGetSecretsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -445,6 +473,7 @@ export const secretServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, projectId, environment, path, @@ -453,7 +482,13 @@ export const secretServiceFactory = ({ version, includeImports }: TGetASecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -534,12 +569,19 @@ export const secretServiceFactory = ({ path, actor, actorId, + actorAuthMethod, actorOrgId, environment, projectId, secrets: inputSecrets }: TCreateBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -597,11 +639,18 @@ export const secretServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, environment, projectId, secrets: inputSecrets }: TUpdateBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -678,9 +727,16 @@ export const secretServiceFactory = ({ projectId, actor, actorId, + actorAuthMethod, actorOrgId }: TDeleteBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -728,6 +784,7 @@ export const secretServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, environment, includeImports }: TGetSecretsRawDTO) => { @@ -740,6 +797,7 @@ export const secretServiceFactory = ({ environment, actor, actorOrgId, + actorAuthMethod, path, includeImports }); @@ -763,6 +821,7 @@ export const secretServiceFactory = ({ projectId, actorId, actorOrgId, + actorAuthMethod, secretName, includeImports, version @@ -773,6 +832,7 @@ export const secretServiceFactory = ({ const secret = await getSecretByName({ actorId, projectId, + actorAuthMethod, environment, actor, actorOrgId, @@ -792,6 +852,7 @@ export const secretServiceFactory = ({ environment, actor, actorOrgId, + actorAuthMethod, type, secretPath, secretValue, @@ -813,6 +874,7 @@ export const secretServiceFactory = ({ path: secretPath, actor, actorId, + actorAuthMethod, actorOrgId, secretKeyCiphertext: secretKeyEncrypted.ciphertext, secretKeyIV: secretKeyEncrypted.iv, @@ -839,6 +901,7 @@ export const secretServiceFactory = ({ environment, actor, actorOrgId, + actorAuthMethod, type, secretPath, secretValue, @@ -858,6 +921,7 @@ export const secretServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, secretValueCiphertext: secretValueEncrypted.ciphertext, secretValueIV: secretValueEncrypted.iv, secretValueTag: secretValueEncrypted.tag, @@ -877,6 +941,7 @@ export const secretServiceFactory = ({ environment, actor, actorOrgId, + actorAuthMethod, type, secretPath }: TDeleteSecretRawDTO) => { @@ -891,7 +956,8 @@ export const secretServiceFactory = ({ path: secretPath, actor, actorId, - actorOrgId + actorOrgId, + actorAuthMethod }); await snapshotService.performSnapshot(secret.folderId); @@ -904,6 +970,7 @@ export const secretServiceFactory = ({ actorId, actor, actorOrgId, + actorAuthMethod, limit = 20, offset = 0, secretId @@ -914,7 +981,13 @@ export const secretServiceFactory = ({ 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + folder.projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] }); diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index cce0d3780f..e434bd91fd 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -9,6 +9,7 @@ import { getConfig } from "@app/lib/config/env"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { ActorType } from "../auth/auth-type"; +import { TProjectDALFactory } from "../project/project-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TServiceTokenDALFactory } from "./service-token-dal"; @@ -24,6 +25,7 @@ type TServiceTokenServiceFactoryDep = { userDAL: TUserDALFactory; permissionService: Pick; projectEnvDAL: Pick; + projectDAL: Pick; }; export type TServiceTokenServiceFactory = ReturnType; @@ -32,7 +34,8 @@ export const serviceTokenServiceFactory = ({ serviceTokenDAL, userDAL, permissionService, - projectEnvDAL + projectEnvDAL, + projectDAL }: TServiceTokenServiceFactoryDep) => { const createServiceToken = async ({ iv, @@ -40,6 +43,7 @@ export const serviceTokenServiceFactory = ({ name, actor, actorOrgId, + actorAuthMethod, scopes, actorId, projectId, @@ -47,7 +51,13 @@ export const serviceTokenServiceFactory = ({ permissions, encryptedKey }: TCreateServiceTokenDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens); scopes.forEach(({ environment, secretPath }) => { @@ -91,7 +101,7 @@ export const serviceTokenServiceFactory = ({ return { token, serviceToken }; }; - const deleteServiceToken = async ({ actorId, actor, actorOrgId, id }: TDeleteServiceTokenDTO) => { + const deleteServiceToken = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteServiceTokenDTO) => { const serviceToken = await serviceTokenDAL.findById(id); if (!serviceToken) throw new BadRequestError({ message: "Token not found" }); @@ -99,6 +109,7 @@ export const serviceTokenServiceFactory = ({ actor, actorId, serviceToken.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens); @@ -119,8 +130,20 @@ export const serviceTokenServiceFactory = ({ return { serviceToken, user: serviceTokenUser }; }; - const getProjectServiceTokens = async ({ actorId, actor, actorOrgId, projectId }: TProjectServiceTokensDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const getProjectServiceTokens = async ({ + actorId, + actor, + actorOrgId, + actorAuthMethod, + projectId + }: TProjectServiceTokensDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens); const tokens = await serviceTokenDAL.find({ projectId }, { sort: [["createdAt", "desc"]] }); @@ -130,7 +153,11 @@ export const serviceTokenServiceFactory = ({ const fnValidateServiceToken = async (token: string) => { const [, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>token.split(".", 3); const serviceToken = await serviceTokenDAL.findById(TOKEN_IDENTIFIER); + if (!serviceToken) throw new UnauthorizedError(); + const project = await projectDAL.findById(serviceToken.projectId); + + if (!project) throw new UnauthorizedError({ message: "Service token project not found" }); if (serviceToken.expiresAt && new Date(serviceToken.expiresAt) < new Date()) { await serviceTokenDAL.deleteById(serviceToken.id); @@ -142,7 +169,8 @@ export const serviceTokenServiceFactory = ({ const updatedToken = await serviceTokenDAL.updateById(serviceToken.id, { lastUsed: new Date() }); - return { ...serviceToken, lastUsed: updatedToken.lastUsed }; + + return { ...serviceToken, lastUsed: updatedToken.lastUsed, orgId: project.orgId }; }; return { diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index d76ad3ec37..07fc2e9917 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -136,6 +136,7 @@ export const superAdminServiceFactory = ({ await updateServerCfg({ initialized: true }); const token = await authService.generateUserTokens({ user: userInfo.user, + authMethod: AuthMethod.EMAIL, ip, userAgent, organizationId: undefined diff --git a/backend/src/services/webhook/webhook-service.ts b/backend/src/services/webhook/webhook-service.ts index c3919cc503..4a05ad219e 100644 --- a/backend/src/services/webhook/webhook-service.ts +++ b/backend/src/services/webhook/webhook-service.ts @@ -31,13 +31,20 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer actor, actorId, actorOrgId, + actorAuthMethod, projectId, webhookUrl, environment, secretPath, webhookSecretKey }: TCreateWebhookDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + 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" }); @@ -73,33 +80,51 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer return { ...webhook, projectId, environment: env }; }; - const updateWebhook = async ({ actorId, actor, actorOrgId, id, isDisabled }: TUpdateWebhookDTO) => { + const updateWebhook = async ({ actorId, actor, actorOrgId, actorAuthMethod, 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + webhook.projectId, + actorAuthMethod, + 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, actorOrgId }: TDeleteWebhookDTO) => { + const deleteWebhook = async ({ id, actor, actorId, actorAuthMethod, 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + webhook.projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks); const deletedWebhook = await webhookDAL.deleteById(id); return { ...webhook, ...deletedWebhook }; }; - const testWebhook = async ({ id, actor, actorId, actorOrgId }: TTestWebhookDTO) => { + const testWebhook = async ({ id, actor, actorId, actorAuthMethod, 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + webhook.projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); let webhookError: string | undefined; @@ -119,8 +144,22 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer return { ...webhook, ...updatedWebhook }; }; - const listWebhooks = async ({ actorId, actor, actorOrgId, projectId, secretPath, environment }: TListWebhookDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); + const listWebhooks = async ({ + actorId, + actor, + actorOrgId, + actorAuthMethod, + projectId, + secretPath, + environment + }: TListWebhookDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); return webhookDAL.findAllWebhooks(projectId, environment, secretPath); diff --git a/cli/packages/api/api.go b/cli/packages/api/api.go index 1b69b8f23d..dfb0cf7bcc 100644 --- a/cli/packages/api/api.go +++ b/cli/packages/api/api.go @@ -164,6 +164,28 @@ func CallGetAllOrganizations(httpClient *resty.Client) (GetOrganizationsResponse return orgResponse, nil } +func CallSelectOrganization(httpClient *resty.Client, request SelectOrganizationRequest) (SelectOrganizationResponse, error) { + var selectOrgResponse SelectOrganizationResponse + + response, err := httpClient. + R(). + SetBody(request). + SetResult(&selectOrgResponse). + SetHeader("User-Agent", USER_AGENT). + Post(fmt.Sprintf("%v/v3/auth/select-organization", config.INFISICAL_URL)) + + if err != nil { + return SelectOrganizationResponse{}, err + } + + if response.IsError() { + return SelectOrganizationResponse{}, fmt.Errorf("CallSelectOrganization: Unsuccessful response: [response=%v]", response) + } + + return selectOrgResponse, nil + +} + func CallGetAllWorkSpacesUserBelongsTo(httpClient *resty.Client) (GetWorkSpacesResponse, error) { var workSpacesResponse GetWorkSpacesResponse response, err := httpClient. diff --git a/cli/packages/api/model.go b/cli/packages/api/model.go index 3109760787..f80c17a92b 100644 --- a/cli/packages/api/model.go +++ b/cli/packages/api/model.go @@ -135,6 +135,14 @@ type GetOrganizationsResponse struct { } `json:"organizations"` } +type SelectOrganizationResponse struct { + Token string `json:"token"` +} + +type SelectOrganizationRequest struct { + OrganizationId string `json:"organizationId"` +} + type Secret struct { SecretKeyCiphertext string `json:"secretKeyCiphertext,omitempty"` SecretKeyIV string `json:"secretKeyIV,omitempty"` diff --git a/cli/packages/cmd/init.go b/cli/packages/cmd/init.go index e47eb447e2..99d2ef5027 100644 --- a/cli/packages/cmd/init.go +++ b/cli/packages/cmd/init.go @@ -74,6 +74,21 @@ var initCmd = &cobra.Command{ selectedOrganization := organizations[index] + tokenResponse, err := api.CallSelectOrganization(httpClient, api.SelectOrganizationRequest{OrganizationId: selectedOrganization.ID}) + + if err != nil { + util.HandleError(err, "Unable to select organization") + } + + // set the config jwt token to the new token + userCreds.UserCredentials.JTWToken = tokenResponse.Token + err = util.StoreUserCredsInKeyRing(&userCreds.UserCredentials) + httpClient.SetAuthToken(tokenResponse.Token) + + if err != nil { + util.HandleError(err, "Unable to store your user credentials") + } + workspaceResponse, err := api.CallGetAllWorkSpacesUserBelongsTo(httpClient) if err != nil { util.HandleError(err, "Unable to pull projects that belong to you") diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index 5cff7770f7..f0337f658e 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -301,11 +301,13 @@ func cliDefaultLogin(userCredentialsToBeStored *models.UserCredentials) { log.Debug().Msgf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token) util.PrintErrorMessageAndExit("We were unable to fetch required details to complete your login. Run with -d to see more info") } + // Login is successful so ask user to choose organization + newJwtToken := GetJwtTokenWithOrganizationId(loginTwoResponse.Token) //updating usercredentials userCredentialsToBeStored.Email = email userCredentialsToBeStored.PrivateKey = string(decryptedPrivateKey) - userCredentialsToBeStored.JTWToken = loginTwoResponse.Token + userCredentialsToBeStored.JTWToken = newJwtToken } func init() { @@ -480,6 +482,44 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R return &loginOneResponseResult, &loginTwoResponseResult, nil } +func GetJwtTokenWithOrganizationId(oldJwtToken string) string { + log.Debug().Msg(fmt.Sprint("GetJwtTokenWithOrganizationId: ", "oldJwtToken", oldJwtToken)) + + httpClient := resty.New() + httpClient.SetAuthToken(oldJwtToken) + + organizationResponse, err := api.CallGetAllOrganizations(httpClient) + + if err != nil { + util.HandleError(err, "Unable to pull organizations that belong to you") + } + + organizations := organizationResponse.Organizations + + organizationNames := util.GetOrganizationsNameList(organizationResponse) + + prompt := promptui.Select{ + Label: "Which Infisical organization would you like to log into?", + Items: organizationNames, + } + + index, _, err := prompt.Run() + if err != nil { + util.HandleError(err) + } + + selectedOrganization := organizations[index] + + selectedOrgRes, err := api.CallSelectOrganization(httpClient, api.SelectOrganizationRequest{OrganizationId: selectedOrganization.ID}) + + if err != nil { + util.HandleError(err) + } + + return selectedOrgRes.Token + +} + func userLoginMenu(currentLoggedInUserEmail string) (bool, error) { label := fmt.Sprintf("Current logged in user email: %s on domain: %s", currentLoggedInUserEmail, config.INFISICAL_URL) diff --git a/frontend/src/components/permissions/PermissionDeniedBanner.tsx b/frontend/src/components/permissions/PermissionDeniedBanner.tsx index 40e17577f6..067ee9c4a9 100644 --- a/frontend/src/components/permissions/PermissionDeniedBanner.tsx +++ b/frontend/src/components/permissions/PermissionDeniedBanner.tsx @@ -13,13 +13,13 @@ export const PermissionDeniedBanner = ({ containerClassName, className, children return (
@@ -27,10 +27,11 @@ export const PermissionDeniedBanner = ({ containerClassName, className, children
-
Access Restricted
+
Access Restricted
{children || (
- Your role has limited permissions, please
contact your administrator to gain access + Your role has limited permissions, please
contact your administrator to gain + access
)}
diff --git a/frontend/src/components/signup/UserInfoStep.tsx b/frontend/src/components/signup/UserInfoStep.tsx index 994ad5a724..a7d11d10c4 100644 --- a/frontend/src/components/signup/UserInfoStep.tsx +++ b/frontend/src/components/signup/UserInfoStep.tsx @@ -8,7 +8,7 @@ import jsrp from "jsrp"; import nacl from "tweetnacl"; import { encodeBase64 } from "tweetnacl-util"; -import { completeAccountSignup } from "@app/hooks/api/auth/queries"; +import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import ProjectService from "@app/services/ProjectService"; @@ -79,6 +79,7 @@ export default function UserInfoStep({ const [errors, setErrors] = useState({}); + const { mutateAsync: selectOrganization } = useSelectOrganization(); const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); @@ -181,6 +182,10 @@ export default function UserInfoStep({ SecurityClient.setToken(response.token); SecurityClient.setProviderAuthToken(""); + if (response.organizationId) { + await selectOrganization({ organizationId: response.organizationId }); + } + saveTokenToLocalStorage({ publicKey, encryptedPrivateKey, @@ -191,9 +196,10 @@ export default function UserInfoStep({ const userOrgs = await fetchOrganizations(); + const orgSlug = userOrgs[0]?.slug; const orgId = userOrgs[0]?.id; const project = await ProjectService.initProject({ - organizationId: orgId, + organizationSlug: orgSlug, projectName: "Example Project" }); diff --git a/frontend/src/components/utilities/attemptCliLogin.ts b/frontend/src/components/utilities/attemptCliLogin.ts index b8812918aa..e95f5bf885 100644 --- a/frontend/src/components/utilities/attemptCliLogin.ts +++ b/frontend/src/components/utilities/attemptCliLogin.ts @@ -11,14 +11,14 @@ import SecurityClient from "./SecurityClient"; // eslint-disable-next-line new-cap const client = new jsrp.client(); -interface IsCliLoginSuccessful { - mfaEnabled: boolean; - loginResponse?: { - email: string; - privateKey: string; - JTWToken: string; - }; - success: boolean; +export interface IsCliLoginSuccessful { + mfaEnabled: boolean; + loginResponse?: { + email: string; + privateKey: string; + JTWToken: string; + }; + success: boolean; } /** @@ -27,123 +27,117 @@ interface IsCliLoginSuccessful { * @param {string} email - email of user to log in * @param {string} password - password of user to log in */ -const attemptLogin = async ( - { - email, - password, - providerAuthToken, - }: { - email: string; - password: string; - providerAuthToken?: string; - } -): Promise => { - - const telemetry = new Telemetry().getInstance(); - return new Promise((resolve, reject) => { - client.init( - { - username: email, - password - }, - async () => { - try { - const clientPublicKey = client.getPublicKey(); - const { serverPublicKey, salt } = await login1({ - email, - clientPublicKey, - providerAuthToken, - }); - - client.setSalt(salt); - client.setServerPublicKey(serverPublicKey); - const clientProof = client.getProof(); // called M1 - - const { - mfaEnabled, - encryptionVersion, - protectedKey, - protectedKeyIV, - protectedKeyTag, - token, - publicKey, - encryptedPrivateKey, - iv, - tag - } = await login2( - { - email, - clientProof, - providerAuthToken, - } - ); - if (mfaEnabled) { - // case: MFA is enabled - - // set temporary (MFA) JWT token - SecurityClient.setMfaToken(token); - - resolve({ - mfaEnabled, - success: true - }); - } else if ( - !mfaEnabled && - encryptionVersion && - encryptedPrivateKey && - iv && - tag && - token - ) { - // case: MFA is not enabled - - // unset provider auth token in case it was used - SecurityClient.setProviderAuthToken(""); - // set JWT token - SecurityClient.setToken(token); - - const privateKey = await KeyService.decryptPrivateKey({ - encryptionVersion, - encryptedPrivateKey, - iv, - tag, - password, - salt, - protectedKey, - protectedKeyIV, - protectedKeyTag - }); - - saveTokenToLocalStorage({ - publicKey, - encryptedPrivateKey, - iv, - tag, - privateKey - }); - - if (email) { - telemetry.identify(email, email); - telemetry.capture("User Logged In"); - } - - resolve({ - mfaEnabled: false, - loginResponse: { - email, - privateKey, - JTWToken: token - }, - success: true - }) - - } - } catch (err) { - reject(err); - } +const attemptLogin = async ({ + email, + password, + providerAuthToken +}: { + email: string; + password: string; + providerAuthToken?: string; +}): Promise => { + const telemetry = new Telemetry().getInstance(); + return new Promise((resolve, reject) => { + client.init( + { + username: email, + password + }, + async () => { + try { + const clientPublicKey = client.getPublicKey(); + const { serverPublicKey, salt } = await login1({ + email, + clientPublicKey, + providerAuthToken + }); + + client.setSalt(salt); + client.setServerPublicKey(serverPublicKey); + const clientProof = client.getProof(); // called M1 + + const { + mfaEnabled, + encryptionVersion, + protectedKey, + protectedKeyIV, + protectedKeyTag, + token, + publicKey, + encryptedPrivateKey, + iv, + tag + } = await login2({ + email, + clientProof, + providerAuthToken + }); + if (mfaEnabled) { + // case: MFA is enabled + + // set temporary (MFA) JWT token + SecurityClient.setMfaToken(token); + + resolve({ + mfaEnabled, + success: true + }); + } else if ( + !mfaEnabled && + encryptionVersion && + encryptedPrivateKey && + iv && + tag && + token + ) { + // case: MFA is not enabled + + // unset provider auth token in case it was used + SecurityClient.setProviderAuthToken(""); + // set JWT token + SecurityClient.setToken(token); + + const privateKey = await KeyService.decryptPrivateKey({ + encryptionVersion, + encryptedPrivateKey, + iv, + tag, + password, + salt, + protectedKey, + protectedKeyIV, + protectedKeyTag + }); + + saveTokenToLocalStorage({ + publicKey, + encryptedPrivateKey, + iv, + tag, + privateKey + }); + + if (email) { + telemetry.identify(email, email); + telemetry.capture("User Logged In"); } - ); - }); + + resolve({ + mfaEnabled: false, + loginResponse: { + email, + privateKey, + JTWToken: token + }, + success: true + }); + } + } catch (err) { + reject(err); + } + } + ); + }); }; export default attemptLogin; diff --git a/frontend/src/components/v2/Button/Button.tsx b/frontend/src/components/v2/Button/Button.tsx index 63a735d655..5536d699aa 100644 --- a/frontend/src/components/v2/Button/Button.tsx +++ b/frontend/src/components/v2/Button/Button.tsx @@ -39,7 +39,8 @@ const buttonVariants = cva( selected: "", outline_bg: "", // a constant color not in use on hover or click goes colorSchema color - star: "text-bunker-200 bg-mineshaft-700 border-mineshaft-600" + star: "text-bunker-200 bg-mineshaft-700 border-mineshaft-600", + link: "text-primary !p-0 bg-transparent outline-none border-none" }, isDisabled: { true: "bg-mineshaft-700 border border-mineshaft-600 text-white opacity-50 cursor-not-allowed", diff --git a/frontend/src/config/request.ts b/frontend/src/config/request.ts index 8f86690102..b29f19f5e7 100644 --- a/frontend/src/config/request.ts +++ b/frontend/src/config/request.ts @@ -1,11 +1,7 @@ import axios from "axios"; import SecurityClient from "@app/components/utilities/SecurityClient"; -import { - getAuthToken, - getMfaTempToken, - getSignupTempToken -} from "@app/reactQuery"; +import { getAuthToken, getMfaTempToken, getSignupTempToken } from "@app/reactQuery"; export const apiRequest = axios.create({ baseURL: "/", @@ -19,19 +15,22 @@ apiRequest.interceptors.request.use((config) => { const mfaTempToken = getMfaTempToken(); const token = getAuthToken(); const providerAuthToken = SecurityClient.getProviderAuthToken(); - - if (signupTempToken && config.headers) { - // eslint-disable-next-line no-param-reassign - config.headers.Authorization = `Bearer ${signupTempToken}`; - } else if (mfaTempToken && config.headers) { - // eslint-disable-next-line no-param-reassign - config.headers.Authorization = `Bearer ${mfaTempToken}`; - } else if (token && config.headers) { - // eslint-disable-next-line no-param-reassign - config.headers.Authorization = `Bearer ${token}`; - } else if(providerAuthToken && config.headers) { - // eslint-disable-next-line no-param-reassign - config.headers.Authorization = `Bearer ${providerAuthToken}`; + + if (config.headers) { + if (signupTempToken) { + // eslint-disable-next-line no-param-reassign + config.headers.Authorization = `Bearer ${signupTempToken}`; + } else if (mfaTempToken) { + // eslint-disable-next-line no-param-reassign + config.headers.Authorization = `Bearer ${mfaTempToken}`; + } else if (token) { + // eslint-disable-next-line no-param-reassign + config.headers.Authorization = `Bearer ${token}`; + } else if (providerAuthToken) { + // eslint-disable-next-line no-param-reassign + config.headers.Authorization = `Bearer ${providerAuthToken}`; + } } + return config; }); diff --git a/frontend/src/helpers/project.ts b/frontend/src/helpers/project.ts index 06f13dc656..8379b0fa59 100644 --- a/frontend/src/helpers/project.ts +++ b/frontend/src/helpers/project.ts @@ -78,17 +78,17 @@ const secretsToBeAdded = [ * @returns {Project} project - new project */ const initProjectHelper = async ({ - organizationId, + organizationSlug, projectName }: { - organizationId: string; + organizationSlug: string; projectName: string; }) => { // create new project const { data: { project } } = await createWorkspace({ - organizationId, + organizationSlug, projectName }); @@ -99,27 +99,31 @@ const initProjectHelper = async ({ env: "dev" }); - secrets?.forEach((secret) => { - createSecret({ - workspaceId: project.id, - environment: secret.environment, - type: secret.type, - secretKey: secret.secretName, - secretKeyCiphertext: secret.secretKeyCiphertext, - secretKeyIV: secret.secretKeyIV, - secretKeyTag: secret.secretKeyTag, - secretValueCiphertext: secret.secretValueCiphertext, - secretValueIV: secret.secretValueIV, - secretValueTag: secret.secretValueTag, - secretCommentCiphertext: secret.secretCommentCiphertext, - secretCommentIV: secret.secretCommentIV, - secretCommentTag: secret.secretCommentTag, - secretPath: "/", - metadata: { - source: "signup" - } + try { + secrets?.forEach((secret) => { + createSecret({ + workspaceId: project.id, + environment: secret.environment, + type: secret.type, + secretKey: secret.secretName, + secretKeyCiphertext: secret.secretKeyCiphertext, + secretKeyIV: secret.secretKeyIV, + secretKeyTag: secret.secretKeyTag, + secretValueCiphertext: secret.secretValueCiphertext, + secretValueIV: secret.secretValueIV, + secretValueTag: secret.secretValueTag, + secretCommentCiphertext: secret.secretCommentCiphertext, + secretCommentIV: secret.secretCommentIV, + secretCommentTag: secret.secretCommentTag, + secretPath: "/", + metadata: { + source: "signup" + } + }); }); - }); + } catch (err) { + console.error("Failed to upload secrets", err); + } return project; }; diff --git a/frontend/src/hooks/api/auth/index.tsx b/frontend/src/hooks/api/auth/index.tsx index 66208a4871..8b918c7ab5 100644 --- a/frontend/src/hooks/api/auth/index.tsx +++ b/frontend/src/hooks/api/auth/index.tsx @@ -1,6 +1,7 @@ export { useGetAuthToken, useResetPassword, + useSelectOrganization, useSendMfaToken, useSendPasswordResetEmail, useSendVerificationEmail, diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index 41f1b02f51..4d05fb9639 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -1,8 +1,10 @@ -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import SecurityClient from "@app/components/utilities/SecurityClient"; import { apiRequest } from "@app/config/request"; import { setAuthToken } from "@app/reactQuery"; +import { organizationKeys } from "../organization/queries"; import { ChangePasswordDTO, CompleteAccountDTO, @@ -42,7 +44,7 @@ export const login2 = async (loginDetails: Login2DTO) => { export const loginLDAPRedirect = async (loginLDAPDetails: LoginLDAPDTO) => { const { data } = await apiRequest.post("/api/v1/ldap/login", loginLDAPDetails); // return if account is complete or not + provider auth token return data; -} +}; export const useLogin1 = () => { return useMutation({ @@ -56,6 +58,31 @@ export const useLogin1 = () => { }); }; +export const selectOrganization = async (data: { organizationId: string }) => { + const { data: res } = await apiRequest.post<{ token: string }>( + "/api/v3/auth/select-organization", + data + ); + return res; +}; + +export const useSelectOrganization = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (details: { organizationId: string }) => { + const data = await selectOrganization(details); + + SecurityClient.setToken(data.token); + SecurityClient.setProviderAuthToken(""); + + return data; + }, + onSuccess: () => { + queryClient.invalidateQueries(organizationKeys.getUserOrganizations); + } + }); +}; + export const useLogin2 = () => { return useMutation({ mutationFn: async (details: { diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 441aa591c8..9e763c7718 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -27,7 +27,8 @@ export const organizationKeys = { getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const, getOrgInvoices: (orgId: string) => [{ orgId }, "organization-invoices"] as const, getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const, - getOrgIdentityMemberships: (orgId: string) => [{ orgId }, "organization-identity-memberships"] as const, + getOrgIdentityMemberships: (orgId: string) => + [{ orgId }, "organization-identity-memberships"] as const }; export const fetchOrganizations = async () => { @@ -46,7 +47,7 @@ export const useGetOrganizations = () => { }); }; -export const useCreateOrg = () => { +export const useCreateOrg = (options: { invalidate: boolean } = { invalidate: true }) => { const queryClient = useQueryClient(); return useMutation({ @@ -60,7 +61,9 @@ export const useCreateOrg = () => { return organization; }, onSuccess: () => { - queryClient.invalidateQueries(organizationKeys.getUserOrganizations); + if (options?.invalidate) { + queryClient.invalidateQueries(organizationKeys.getUserOrganizations); + } } }); }; @@ -68,15 +71,9 @@ export const useCreateOrg = () => { export const useUpdateOrg = () => { const queryClient = useQueryClient(); return useMutation<{}, {}, UpdateOrgDTO>({ - mutationFn: ({ - name, - authEnforced, - scimEnabled, - slug, - orgId - }) => { - return apiRequest.patch(`/api/v1/organization/${orgId}`, { - name, + mutationFn: ({ name, authEnforced, scimEnabled, slug, orgId }) => { + return apiRequest.patch(`/api/v1/organization/${orgId}`, { + name, authEnforced, scimEnabled, slug diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index 3d5e80d744..a443c67500 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -194,7 +194,7 @@ export const useRegisterUserAction = () => { }); }; -export const useLogoutUser = () => { +export const useLogoutUser = (keepQueryClient?: boolean) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => { @@ -214,7 +214,9 @@ export const useLogoutUser = () => { localStorage.removeItem("orgData.id"); localStorage.removeItem("projectData.id"); - queryClient.clear(); + if (!keepQueryClient) { + queryClient.clear(); + } } }); }; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 37e512ac8e..24a7e57946 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -199,19 +199,19 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) => }); export const createWorkspace = ({ - organizationId, + organizationSlug, projectName }: CreateWorkspaceDTO): Promise<{ data: { project: Workspace } }> => { - return apiRequest.post("/api/v2/workspace", { projectName, organizationId }); + return apiRequest.post("/api/v2/workspace", { projectName, organizationSlug }); }; export const useCreateWorkspace = () => { const queryClient = useQueryClient(); return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({ - mutationFn: async ({ organizationId, projectName }) => + mutationFn: async ({ organizationSlug, projectName }) => createWorkspace({ - organizationId, + organizationSlug, projectName }), onSuccess: () => { diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts index 828a7a8904..657b076939 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/workspace/types.ts @@ -45,7 +45,7 @@ export type TGetUpgradeProjectStatusDTO = { // mutation dto export type CreateWorkspaceDTO = { projectName: string; - organizationId: string; + organizationSlug: string; }; export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string }; diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 7b0eeddbf5..e78913f6ec 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -68,8 +68,10 @@ import { useGetSecretApprovalRequestCount, useGetUserAction, useLogoutUser, - useRegisterUserAction + useRegisterUserAction, + useSelectOrganization } from "@app/hooks/api"; +import { navigateUserToOrg } from "@app/views/Login/Login.utils"; import { CreateOrgModal } from "@app/views/Org/components"; interface LayoutProps { @@ -100,7 +102,12 @@ const supportOptions = [ ]; const formSchema = yup.object({ - name: yup.string().required().label("Project Name").trim().max(64, "Too long, maximum length is 64 characters"), + name: yup + .string() + .required() + .label("Project Name") + .trim() + .max(64, "Too long, maximum length is 64 characters"), addMembers: yup.bool().required().label("Add Members") }); @@ -111,7 +118,6 @@ export const AppLayout = ({ children }: LayoutProps) => { const { createNotification } = useNotificationContext(); const { mutateAsync } = useGetOrgTrialUrl(); - // eslint-disable-next-line prefer-const const { workspaces, currentWorkspace } = useWorkspace(); const { orgs, currentOrg } = useOrganization(); @@ -148,6 +154,7 @@ export const AppLayout = ({ children }: LayoutProps) => { const { t } = useTranslation(); const registerUserAction = useRegisterUserAction(); + const { mutateAsync: selectOrganization } = useSelectOrganization(); const closeUpdate = async () => { await registerUserAction.mutateAsync("december_update_closed"); @@ -165,8 +172,11 @@ export const AppLayout = ({ children }: LayoutProps) => { }; const changeOrg = async (orgId: string) => { - localStorage.setItem("orgData.id", orgId); - router.push(`/org/${orgId}/overview`); + await selectOrganization({ + organizationId: orgId + }); + + await navigateUserToOrg(router, orgId); }; // TODO(akhilmhdh): This entire logic will be rechecked and will try to avoid @@ -226,7 +236,7 @@ export const AppLayout = ({ children }: LayoutProps) => { project: { id: newProjectId } } } = await createWs.mutateAsync({ - organizationId: currentOrg.id, + organizationSlug: currentOrg.slug, projectName: name }); @@ -426,7 +436,7 @@ export const AppLayout = ({ children }: LayoutProps) => { setPassword(e.target.value)} + type="password" + placeholder="Enter your password..." + isRequired + autoComplete="current-password" + id="current-password" + className="h-12" + /> +
+ +
+ +
+
+ + Infisical Master Password serves as a decryption mechanism so that even Google is not able + to access your secrets. + + + + {t("login.forgot-password")} + + +
+
+ -
-
- - Infisical Master Password serves as a decryption mechanism so that even Google is not able to access your secrets. - - - {t("login.forgot-password")} - -
-
- -
- - ); -} \ No newline at end of file + {t("login.other-option")} + + + + ); +}; diff --git a/frontend/src/views/Org/components/CreateOrgModal.tsx b/frontend/src/views/Org/components/CreateOrgModal.tsx index b806b2089b..e7a17bb0be 100644 --- a/frontend/src/views/Org/components/CreateOrgModal.tsx +++ b/frontend/src/views/Org/components/CreateOrgModal.tsx @@ -6,7 +6,7 @@ import z from "zod"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; -import { useCreateOrg } from "@app/hooks/api"; +import { useCreateOrg, useSelectOrganization } from "@app/hooks/api"; const schema = z .object({ @@ -37,14 +37,21 @@ export const CreateOrgModal: FC = ({ isOpen, onClose }) => } }); - const { mutateAsync } = useCreateOrg(); + const { mutateAsync: createOrg } = useCreateOrg({ + invalidate: false + }); + const { mutateAsync: selectOrg } = useSelectOrganization(); const onFormSubmit = async ({ name }: FormData) => { try { - const organization = await mutateAsync({ + const organization = await createOrg({ name }); + await selectOrg({ + organizationId: organization.id + }); + createNotification({ text: "Successfully created organization", type: "success" diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index 918a625516..c2ad81efe3 100644 --- a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -15,7 +15,7 @@ import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button, Input } from "@app/components/v2"; -import { completeAccountSignup } from "@app/hooks/api/auth/queries"; +import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import ProjectService from "@app/services/ProjectService"; @@ -72,6 +72,7 @@ export const UserInfoSSOStep = ({ const [errors, setErrors] = useState({}); const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); + const { mutateAsync: selectOrganization } = useSelectOrganization(); useEffect(() => { if (providerOrganizationName !== undefined) { @@ -188,8 +189,14 @@ export const UserInfoSSOStep = ({ const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]?.id; + const orgSlug = userOrgs[0]?.slug; + + await selectOrganization({ + organizationId: orgId + }); + const project = await ProjectService.initProject({ - organizationId: orgId, + organizationSlug: orgSlug, projectName: "Example Project" }); diff --git a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx index 8a26030ce2..29134e50e3 100644 --- a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx +++ b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx @@ -12,7 +12,7 @@ import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLo import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button, ContentLoader, FormControl, Input } from "@app/components/v2"; import { useServerConfig } from "@app/context"; -import { useCreateAdminUser } from "@app/hooks/api"; +import { useCreateAdminUser, useSelectOrganization } from "@app/hooks/api"; import { generateUserBackupKey, generateUserPassKey } from "@app/lib/crypto"; import { isLoggedIn } from "@app/reactQuery"; @@ -64,6 +64,7 @@ export const SignUpPage = () => { }, []); const { mutateAsync: createAdminUser } = useCreateAdminUser(); + const { mutateAsync: selectOrganization } = useSelectOrganization(); const handleFormSubmit = async ({ email, password, firstName, lastName }: TFormSchema) => { // avoid multi submission @@ -76,6 +77,7 @@ export const SignUpPage = () => { lastName, ...userPass }); + SecurityClient.setToken(res.token); saveTokenToLocalStorage({ publicKey: userPass.publicKey, @@ -84,6 +86,8 @@ export const SignUpPage = () => { tag: userPass.encryptedPrivateKeyTag, privateKey }); + await selectOrganization({ organizationId: res.organization.id }); + // TODO(akhilmhdh): This is such a confusing pattern and too unreliable // Will be refactored in next iteration to make it url based rather than local storage ones // Part of migration to nextjs 14