From c8cfb433165b8ff994710664ee9d24a6c5ec8bf9 Mon Sep 17 00:00:00 2001 From: Salman Date: Tue, 13 Feb 2024 05:42:29 +0530 Subject: [PATCH 001/582] Update github integration refactored to react-hook-form --- .../src/pages/integrations/github/create.tsx | 423 ++++++++++-------- 1 file changed, 247 insertions(+), 176 deletions(-) diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index 23296cefd..f09602384 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; @@ -12,9 +13,13 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { yupResolver } from "@hookform/resolvers/yup"; +import axios from "axios"; import { motion } from "framer-motion"; import queryString from "query-string"; +import * as yup from "yup"; +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useCreateIntegration } from "@app/hooks/api"; import { @@ -45,9 +50,22 @@ enum TabSections { Options = "options" } +const schema = yup.object({ + selectedSourceEnvironment: yup.string().trim().required("Project Environment is required"), + secretPath: yup.string().trim().required("Secrets Path is required"), + targetAppIds: yup + .array(yup.string().required()) + .min(1, "Select atleast one repo") // .min() not working showing error for empty array + .required("Select atleast one repo"), + secretSuffix: yup.string().trim().optional() +}); + +type FormData = yup.InferType; + export default function GitHubCreateIntegrationPage() { const router = useRouter(); const { mutateAsync } = useCreateIntegration(); + const { createNotification } = useNotificationContext(); const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); @@ -58,37 +76,44 @@ export default function GitHubCreateIntegrationPage() { integrationAuthId: (integrationAuthId as string) ?? "" }); - const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); - const [secretPath, setSecretPath] = useState("/"); - const [targetAppIds, setTargetAppIds] = useState([]); - const [secretSuffix, setSecretSuffix] = useState(""); + const { control, handleSubmit, watch, setValue } = useForm({ + resolver: yupResolver(schema), + defaultValues: { + selectedSourceEnvironment: "", + secretPath: "/", + targetAppIds: [], + secretSuffix: "" + } + }); + + const targetAppIds = watch("targetAppIds"); const [isLoading, setIsLoading] = useState(false); useEffect(() => { if (workspace) { - setSelectedSourceEnvironment(workspace.environments[0].slug); + setValue("selectedSourceEnvironment", workspace.environments[0].slug); } }, [workspace]); useEffect(() => { if (integrationAuthApps) { if (integrationAuthApps.length > 0) { - setTargetAppIds([String(integrationAuthApps[0].appId)]); + setValue("targetAppIds", [String(integrationAuthApps[0].appId)]); } else { - setTargetAppIds(["none"]); + setValue("targetAppIds", ["none"]); } } }, [integrationAuthApps]); - const handleButtonClick = async () => { + const onFormSubmit = async (data: FormData) => { try { setIsLoading(true); if (!integrationAuth?.id) return; const targetApps = integrationAuthApps?.filter((integrationAuthApp) => - targetAppIds.includes(String(integrationAuthApp.appId)) + data.targetAppIds.includes(String(integrationAuthApp.appId)) ); if (!targetApps) return; @@ -99,11 +124,11 @@ export default function GitHubCreateIntegrationPage() { integrationAuthId: integrationAuth?.id, isActive: true, app: targetApp.name, - sourceEnvironment: selectedSourceEnvironment, owner: targetApp.owner, - secretPath, + secretPath: data.secretPath, + sourceEnvironment: data.selectedSourceEnvironment, metadata: { - secretSuffix + secretSuffix: data.secretSuffix } }); }) @@ -113,183 +138,229 @@ export default function GitHubCreateIntegrationPage() { router.push(`/integrations/${localStorage.getItem("projectData.id")}`); } catch (err) { console.error(err); + if (axios.isAxiosError(err)) { + const { message } = err?.response?.data as { message: string }; + createNotification({ + text: message, + type: "error" + }); + } + setIsLoading(false); } }; - return integrationAuth && - workspace && - selectedSourceEnvironment && - integrationAuthApps && - targetAppIds ? ( + return integrationAuth && workspace && integrationAuthApps ? (
Set Up GitHub Integration - -
-
- GitHub logo +
+ +
+
+ GitHub logo +
+ GitHub Integration + + +
+ + Docs + +
+
+
- GitHub Integration - - -
- - Docs - -
-
- -
- - - -
- Connection - Options -
-
- - - - - - - setSecretPath(evt.target.value)} - placeholder="Provide a path, default is /" - /> - - - - - {integrationAuthApps.length > 0 ? ( -
- {targetAppIds.length === 1 - ? integrationAuthApps?.find( - (integrationAuthApp) => - targetAppIds[0] === String(integrationAuthApp.appId) - )?.name - : `${targetAppIds.length} repositories selected`} - -
- ) : ( -
- No repositories found -
- )} -
- - {integrationAuthApps.length > 0 ? ( - integrationAuthApps.map((integrationAuthApp) => { - const isSelected = targetAppIds.includes(String(integrationAuthApp.appId)); - - return ( - { - if (targetAppIds.includes(String(integrationAuthApp.appId))) { - setTargetAppIds( - targetAppIds.filter( - (appId) => appId !== String(integrationAuthApp.appId) - ) - ); - } else { - setTargetAppIds([ - ...targetAppIds, - String(integrationAuthApp.appId) - ]); - } - }} - key={integrationAuthApp.appId} - icon={ - isSelected ? ( - - ) : ( -
- ) - } - iconPos="left" - className="w-[28.4rem] text-sm" + setSecretSuffix(evt.target.value)} - placeholder="Provide a suffix for secret names, default is no suffix" + {sourceEnvironment.name} + + ))} + + + )} /> - - - - - + + ( + + + + )} + /> + + ( + + + + {integrationAuthApps.length > 0 ? ( +
+ {targetAppIds.length === 1 + ? integrationAuthApps?.find( + (integrationAuthApp) => + targetAppIds[0] === String(integrationAuthApp.appId) + )?.name + : `${targetAppIds.length} repositories selected`} + +
+ ) : ( +
+ No repositories found +
+ )} +
+ + {integrationAuthApps.length > 0 ? ( + integrationAuthApps.map((integrationAuthApp) => { + const isSelected = targetAppIds.includes( + String(integrationAuthApp.appId) + ); + + return ( + { + if (targetAppIds.includes(String(integrationAuthApp.appId))) { + onChange( + targetAppIds.filter( + (appId) => appId !== String(integrationAuthApp.appId) + ) + ); + } else { + onChange([...targetAppIds, String(integrationAuthApp.appId)]); + } + }} + key={integrationAuthApp.appId} + icon={ + isSelected ? ( + + ) : ( +
+ ) + } + iconPos="left" + className="w-[28.4rem] text-sm" + > + {integrationAuthApp.name} + + ); + }) + ) : ( +
+ )} + + + + )} + /> + + + + + ( + + + + )} + /> + + + +
+ +
+
From df459d456aac06b194693d484110e4a563e0fe28 Mon Sep 17 00:00:00 2001 From: Salman Date: Wed, 14 Feb 2024 04:21:50 +0530 Subject: [PATCH 002/582] Update github form ui add scope and init org api --- .../routes/v1/integration-auth-router.ts | 32 ++ .../integration-auth-service.ts | 30 ++ .../integration-auth-types.ts | 4 + docs/spec.yaml | 12 + .../src/hooks/api/integrationAuth/index.tsx | 1 + .../src/hooks/api/integrationAuth/queries.tsx | 20 ++ .../src/pages/integrations/github/create.tsx | 314 +++++++++++++----- 7 files changed, 325 insertions(+), 88 deletions(-) diff --git a/backend/src/server/routes/v1/integration-auth-router.ts b/backend/src/server/routes/v1/integration-auth-router.ts index 4d7aa1b1e..871b2ec65 100644 --- a/backend/src/server/routes/v1/integration-auth-router.ts +++ b/backend/src/server/routes/v1/integration-auth-router.ts @@ -343,6 +343,38 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) } }); + server.route({ + url: "/:integrationAuthId/github/orgs", + method: "GET", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + integrationAuthId: z.string().trim() + }), + response: { + 200: z.object({ + orgs: z.object({ name: z.string(), orgId: z.string() }).array() + }) + } + }, + handler: async (req) => { + try { + const orgs = await server.services.integrationAuth.getGithubOrgs({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + id: req.params.integrationAuthId + }); + if (!orgs) throw new Error("No organization found."); + + return { orgs: orgs || [] }; + } catch (e) { + console.error(e); + return { orgs: [] }; + } + } + }); + server.route({ url: "/:integrationAuthId/qovery/orgs", method: "GET", diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 0b3f9b4c3..c158567c3 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -1,4 +1,5 @@ import { ForbiddenError } from "@casl/ability"; +import { Octokit } from "@octokit/rest"; import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; @@ -23,6 +24,7 @@ import { TIntegrationAuthAppsDTO, TIntegrationAuthBitbucketWorkspaceDTO, TIntegrationAuthChecklyGroupsDTO, + TIntegrationAuthGithubOrgsDTO, TIntegrationAuthNorthflankSecretGroupDTO, TIntegrationAuthQoveryEnvironmentsDTO, TIntegrationAuthQoveryOrgsDTO, @@ -381,6 +383,33 @@ export const integrationAuthServiceFactory = ({ return []; }; + const getGithubOrgs = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthGithubOrgsDTO) => { + const integrationAuth = await integrationAuthDAL.findById(id); + if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); + + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); + const botKey = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + + const octokit = new Octokit({ + auth: accessToken + }); + + const { data } = await octokit.request("GET /organizations", { + headers: { + "X-GitHub-Api-Version": "2022-11-28" + } + }); + if (!data) return []; + return data.map(({ login: name, id: orgId }) => ({ name, orgId })); + }; + const getQoveryOrgs = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthQoveryOrgsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); @@ -911,6 +940,7 @@ export const integrationAuthServiceFactory = ({ getIntegrationApps, getVercelBranches, getApps, + getGithubOrgs, getChecklyGroups, getQoveryApps, getQoveryEnvs, diff --git a/backend/src/services/integration-auth/integration-auth-types.ts b/backend/src/services/integration-auth/integration-auth-types.ts index 34c5d995a..8cf20d747 100644 --- a/backend/src/services/integration-auth/integration-auth-types.ts +++ b/backend/src/services/integration-auth/integration-auth-types.ts @@ -44,6 +44,10 @@ export type TIntegrationAuthChecklyGroupsDTO = { accountId: string; } & Omit; +export type TIntegrationAuthGithubOrgsDTO = { + id: string; +} & Omit; + export type TIntegrationAuthQoveryOrgsDTO = { id: string; } & Omit; diff --git a/docs/spec.yaml b/docs/spec.yaml index c3d050395..d7442497a 100644 --- a/docs/spec.yaml +++ b/docs/spec.yaml @@ -1869,6 +1869,18 @@ paths: responses: '200': description: OK + /api/v1/integration-auth/{integrationAuthId}/github/orgs: + get: + description: '' + parameters: + - name: integrationAuthId + in: path + required: true + schema: + type: string + responses: + '200': + description: OK /api/v1/integration-auth/{integrationAuthId}/qovery/orgs: get: description: '' diff --git a/frontend/src/hooks/api/integrationAuth/index.tsx b/frontend/src/hooks/api/integrationAuth/index.tsx index e70ffdccb..358ed1a63 100644 --- a/frontend/src/hooks/api/integrationAuth/index.tsx +++ b/frontend/src/hooks/api/integrationAuth/index.tsx @@ -6,6 +6,7 @@ export { useGetIntegrationAuthBitBucketWorkspaces, useGetIntegrationAuthById, useGetIntegrationAuthChecklyGroups, + useGetIntegrationAuthGithubOrgs, useGetIntegrationAuthNorthflankSecretGroups, useGetIntegrationAuthRailwayEnvironments, useGetIntegrationAuthRailwayServices, diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index 83dfb5d1a..eb675c628 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -38,6 +38,8 @@ const integrationAuthKeys = { integrationAuthId: string; accountId: string; }) => [{ integrationAuthId, accountId }, "integrationAuthChecklyGroups"] as const, + getIntegrationAuthGithubOrgs: (integrationAuthId: string) => + [{ integrationAuthId }, "integrationAuthGithubOrgs"] as const, getIntegrationAuthQoveryOrgs: (integrationAuthId: string) => [{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const, getIntegrationAuthQoveryProjects: ({ @@ -174,6 +176,16 @@ const fetchIntegrationAuthVercelBranches = async ({ return branches; }; +const fetchIntegrationAuthGithubOrgs = async (integrationAuthId: string) => { + const { + data: { orgs } + } = await apiRequest.get<{ orgs: Org[] }>( + `/api/v1/integration-auth/${integrationAuthId}/github/orgs` + ); + + return orgs; +}; + const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => { const { data: { orgs } @@ -465,6 +477,14 @@ export const useGetIntegrationAuthChecklyGroups = ({ }); }; +export const useGetIntegrationAuthGithubOrgs = (integrationAuthId: string) => { + return useQuery({ + queryKey: integrationAuthKeys.getIntegrationAuthGithubOrgs(integrationAuthId), + queryFn: () => fetchIntegrationAuthGithubOrgs(integrationAuthId), + enabled: true + }); +}; + export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => { return useQuery({ queryKey: integrationAuthKeys.getIntegrationAuthQoveryOrgs(integrationAuthId), diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index f09602384..6aea82cf3 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -20,8 +20,6 @@ import queryString from "query-string"; import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import { useCreateIntegration } from "@app/hooks/api"; - import { Button, Card, @@ -38,26 +36,50 @@ import { TabList, TabPanel, Tabs -} from "../../../components/v2"; +} from "@app/components/v2"; import { + useCreateIntegration, useGetIntegrationAuthApps, - useGetIntegrationAuthById -} from "../../../hooks/api/integrationAuth"; -import { useGetWorkspaceById } from "../../../hooks/api/workspace"; + useGetIntegrationAuthById, + useGetIntegrationAuthGithubOrgs, + useGetWorkspaceById +} from "@app/hooks/api"; enum TabSections { Connection = "connection", Options = "options" } +const targetEnv = [ + "github-repo", + "github-org", + "github-env" +] as const; +type TargetEnv = typeof targetEnv[number]; + + const schema = yup.object({ selectedSourceEnvironment: yup.string().trim().required("Project Environment is required"), secretPath: yup.string().trim().required("Secrets Path is required"), - targetAppIds: yup + secretSuffix: yup.string().trim().optional(), + + scope: yup.mixed().oneOf(targetEnv.slice()).required(), + repoIds: yup .array(yup.string().required()) .min(1, "Select atleast one repo") // .min() not working showing error for empty array - .required("Select atleast one repo"), - secretSuffix: yup.string().trim().optional() + .optional(), + repoId: yup + .string() + .optional(), + + envId: yup + .string() + .optional(), + + orgId: yup + .string() + .optional(), + }); type FormData = yup.InferType; @@ -75,19 +97,25 @@ export default function GitHubCreateIntegrationPage() { useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "" }); + + const { data: integrationAuthOrgs } = + useGetIntegrationAuthGithubOrgs(integrationAuthId as string); const { control, handleSubmit, watch, setValue } = useForm({ resolver: yupResolver(schema), defaultValues: { selectedSourceEnvironment: "", secretPath: "/", - targetAppIds: [], - secretSuffix: "" + repoIds: [], + secretSuffix: "", + scope: "github-repo" } }); - const targetAppIds = watch("targetAppIds"); + const scope = watch("scope"); + const repoIds = watch("repoIds"); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { @@ -99,9 +127,9 @@ export default function GitHubCreateIntegrationPage() { useEffect(() => { if (integrationAuthApps) { if (integrationAuthApps.length > 0) { - setValue("targetAppIds", [String(integrationAuthApps[0].appId)]); + setValue("repoIds", [String(integrationAuthApps[0].appId)]); } else { - setValue("targetAppIds", ["none"]); + setValue("repoIds", ["none"]); } } }, [integrationAuthApps]); @@ -113,7 +141,7 @@ export default function GitHubCreateIntegrationPage() { if (!integrationAuth?.id) return; const targetApps = integrationAuthApps?.filter((integrationAuthApp) => - data.targetAppIds.includes(String(integrationAuthApp.appId)) + data.repoIds?.includes(String(integrationAuthApp.appId)) ); if (!targetApps) return; @@ -217,7 +245,7 @@ export default function GitHubCreateIntegrationPage() { {workspace?.environments.map((sourceEnvironment) => ( {sourceEnvironment.name} @@ -226,7 +254,6 @@ export default function GitHubCreateIntegrationPage() { )} /> - ( + name="scope" + render={({ field: { onChange, ...field }, fieldState: { error } }) => ( - - - {integrationAuthApps.length > 0 ? ( -
- {targetAppIds.length === 1 - ? integrationAuthApps?.find( - (integrationAuthApp) => - targetAppIds[0] === String(integrationAuthApp.appId) - )?.name - : `${targetAppIds.length} repositories selected`} - -
- ) : ( -
- No repositories found -
- )} -
- - {integrationAuthApps.length > 0 ? ( - integrationAuthApps.map((integrationAuthApp) => { - const isSelected = targetAppIds.includes( - String(integrationAuthApp.appId) - ); - - return ( - { - if (targetAppIds.includes(String(integrationAuthApp.appId))) { - onChange( - targetAppIds.filter( - (appId) => appId !== String(integrationAuthApp.appId) - ) - ); - } else { - onChange([...targetAppIds, String(integrationAuthApp.appId)]); - } - }} - key={integrationAuthApp.appId} - icon={ - isSelected ? ( - - ) : ( -
- ) - } - iconPos="left" - className="w-[28.4rem] text-sm" - > - {integrationAuthApp.name} - - ); - }) - ) : ( -
- )} - - + )} /> + + {scope === "github-repo" && repoIds && ( + ( + + + + {integrationAuthApps.length > 0 ? ( +
+ {repoIds.length === 1 + ? integrationAuthApps?.find( + (integrationAuthApp) => + repoIds[0] === String(integrationAuthApp.appId) + )?.name + : `${repoIds.length} repositories selected`} + +
+ ) : ( +
+ No repositories found +
+ )} +
+ + {integrationAuthApps.length > 0 ? ( + integrationAuthApps.map((integrationAuthApp) => { + const isSelected = repoIds.includes( + String(integrationAuthApp.appId) + ); + + return ( + { + if (repoIds.includes(String(integrationAuthApp.appId))) { + onChange( + repoIds.filter( + (appId) => appId !== String(integrationAuthApp.appId) + ) + ); + } else { + onChange([...repoIds, String(integrationAuthApp.appId)]); + } + }} + key={`repos-id-${integrationAuthApp.appId}`} + icon={ + isSelected ? ( + + ) : ( +
+ ) + } + iconPos="left" + className="w-[28.4rem] text-sm" + > + {integrationAuthApp.name} + + ); + }) + ) : ( +
+ )} + + + + )} + /> + + )} + {scope === "github-org" && ( + ( + + + + )} + /> + )} + {scope === "github-env" && ( + ( + + + + )} + /> + )} + {scope === "github-env" && ( + ( + + + + )} + /> + )} @@ -355,7 +493,7 @@ export default function GitHubCreateIntegrationPage() { variant="outline_bg" className="mb-6" isLoading={isLoading} - isDisabled={integrationAuthApps.length === 0 || targetAppIds.length === 0} + isDisabled={integrationAuthApps.length === 0 || repoIds?.length === 0} > Create Integration From c8f0796952f2ecff1c29f6b1ecda0f23455f278d Mon Sep 17 00:00:00 2001 From: Salman Date: Thu, 15 Feb 2024 09:41:57 +0530 Subject: [PATCH 003/582] Update github integrations ui for organization and environment --- .../routes/v1/integration-auth-router.ts | 52 ++- .../integration-auth-service.ts | 48 ++- .../integration-auth-types.ts | 6 + .../src/hooks/api/integrationAuth/index.tsx | 1 + .../src/hooks/api/integrationAuth/queries.tsx | 34 ++ .../src/pages/integrations/github/create.tsx | 313 +++++++++++------- 6 files changed, 311 insertions(+), 143 deletions(-) diff --git a/backend/src/server/routes/v1/integration-auth-router.ts b/backend/src/server/routes/v1/integration-auth-router.ts index 871b2ec65..0decaf119 100644 --- a/backend/src/server/routes/v1/integration-auth-router.ts +++ b/backend/src/server/routes/v1/integration-auth-router.ts @@ -358,20 +358,48 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) } }, handler: async (req) => { - try { - const orgs = await server.services.integrationAuth.getGithubOrgs({ - actorId: req.permission.id, - actor: req.permission.type, - actorOrgId: req.permission.orgId, - id: req.params.integrationAuthId - }); - if (!orgs) throw new Error("No organization found."); + const orgs = await server.services.integrationAuth.getGithubOrgs({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + id: req.params.integrationAuthId + }); + if (!orgs) throw new Error("No organization found."); - return { orgs: orgs || [] }; - } catch (e) { - console.error(e); - return { orgs: [] }; + return { orgs }; + } + }); + + server.route({ + url: "/:integrationAuthId/github/envs", + method: "GET", + onRequest: verifyAuth([AuthMode.JWT]), + schema: { + params: z.object({ + integrationAuthId: z.string().trim() + }), + querystring: z.object({ + repoOwner: z.string().trim(), + repoName: z.string().trim() + }), + response: { + 200: z.object({ + envs: z.object({ name: z.string(), envId: z.string() }).array() + }) } + }, + handler: async (req) => { + const envs = await server.services.integrationAuth.getGithubEnvs({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + id: req.params.integrationAuthId, + repoName: req.query.repoName, + repoOwner: req.query.repoOwner + }); + if (!envs) throw new Error("No organization found."); + + return { envs }; } }); diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index c158567c3..4b8d0ee80 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -24,6 +24,7 @@ import { TIntegrationAuthAppsDTO, TIntegrationAuthBitbucketWorkspaceDTO, TIntegrationAuthChecklyGroupsDTO, + TIntegrationAuthGithubEnvsDTO, TIntegrationAuthGithubOrgsDTO, TIntegrationAuthNorthflankSecretGroupDTO, TIntegrationAuthQoveryEnvironmentsDTO, @@ -401,13 +402,51 @@ export const integrationAuthServiceFactory = ({ auth: accessToken }); - const { data } = await octokit.request("GET /organizations", { + const { data } = await octokit.request("GET /user/orgs", { headers: { "X-GitHub-Api-Version": "2022-11-28" } }); if (!data) return []; - return data.map(({ login: name, id: orgId }) => ({ name, orgId })); + return data.map(({ login: name, id: orgId }) => ({ name, orgId: String(orgId) })); + }; + + const getGithubEnvs = async ({ + actorId, + actor, + actorOrgId, + id, + repoOwner, + repoName + }: TIntegrationAuthGithubEnvsDTO) => { + const integrationAuth = await integrationAuthDAL.findById(id); + if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); + + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); + const botKey = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + + const octokit = new Octokit({ + auth: accessToken + }); + + const { + data: { environments } + } = await octokit.request("GET /repos/{owner}/{repo}/environments", { + headers: { + "X-GitHub-Api-Version": "2022-11-28" + }, + owner: repoOwner, + repo: repoName + }); + if (!environments) return []; + return environments.map(({ id: envId, name }) => ({ name, envId: String(envId) })); }; const getQoveryOrgs = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthQoveryOrgsDTO) => { @@ -762,9 +801,7 @@ export const integrationAuthServiceFactory = ({ while (hasNextPage) { // eslint-disable-next-line - const { data }: { data: { values: TBitbucketWorkspace[]; next: string } } = await request.get( - workspaceUrl, - { + const { data }: { data: { values: TBitbucketWorkspace[]; next: string } } = await request.get(workspaceUrl, { headers: { Authorization: `Bearer ${accessToken}`, "Accept-Encoding": "application/json" @@ -941,6 +978,7 @@ export const integrationAuthServiceFactory = ({ getVercelBranches, getApps, getGithubOrgs, + getGithubEnvs, getChecklyGroups, getQoveryApps, getQoveryEnvs, diff --git a/backend/src/services/integration-auth/integration-auth-types.ts b/backend/src/services/integration-auth/integration-auth-types.ts index 8cf20d747..0c411b573 100644 --- a/backend/src/services/integration-auth/integration-auth-types.ts +++ b/backend/src/services/integration-auth/integration-auth-types.ts @@ -48,6 +48,12 @@ export type TIntegrationAuthGithubOrgsDTO = { id: string; } & Omit; +export type TIntegrationAuthGithubEnvsDTO = { + id: string; + repoName: string; + repoOwner: string; +} & Omit; + export type TIntegrationAuthQoveryOrgsDTO = { id: string; } & Omit; diff --git a/frontend/src/hooks/api/integrationAuth/index.tsx b/frontend/src/hooks/api/integrationAuth/index.tsx index 358ed1a63..545985fa9 100644 --- a/frontend/src/hooks/api/integrationAuth/index.tsx +++ b/frontend/src/hooks/api/integrationAuth/index.tsx @@ -6,6 +6,7 @@ export { useGetIntegrationAuthBitBucketWorkspaces, useGetIntegrationAuthById, useGetIntegrationAuthChecklyGroups, + useGetIntegrationAuthGithubEnvs, useGetIntegrationAuthGithubOrgs, useGetIntegrationAuthNorthflankSecretGroups, useGetIntegrationAuthRailwayEnvironments, diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index eb675c628..dc697bb36 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -40,6 +40,8 @@ const integrationAuthKeys = { }) => [{ integrationAuthId, accountId }, "integrationAuthChecklyGroups"] as const, getIntegrationAuthGithubOrgs: (integrationAuthId: string) => [{ integrationAuthId }, "integrationAuthGithubOrgs"] as const, + getIntegrationAuthGithubEnvs: (integrationAuthId: string, repoName: string, repoOwner: string) => + [{ integrationAuthId, repoName, repoOwner }, "integrationAuthGithubOrgs"] as const, getIntegrationAuthQoveryOrgs: (integrationAuthId: string) => [{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const, getIntegrationAuthQoveryProjects: ({ @@ -186,6 +188,22 @@ const fetchIntegrationAuthGithubOrgs = async (integrationAuthId: string) => { return orgs; }; +const fetchIntegrationAuthGithubEnvs = async ( + integrationAuthId: string, + repoName: string, + repoOwner: string +) => { + if (!repoName || !repoOwner) return []; + + const { + data: { envs } + } = await apiRequest.get<{ envs: Array<{ name: string; envId: string }> }>( + `/api/v1/integration-auth/${integrationAuthId}/github/envs?repoName=${repoName}&repoOwner=${repoOwner}` + ); + + return envs; +}; + const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => { const { data: { orgs } @@ -485,6 +503,22 @@ export const useGetIntegrationAuthGithubOrgs = (integrationAuthId: string) => { }); }; +export const useGetIntegrationAuthGithubEnvs = ( + integrationAuthId: string, + repoName: string, + repoOwner: string +) => { + return useQuery({ + queryKey: integrationAuthKeys.getIntegrationAuthGithubEnvs( + integrationAuthId, + repoName, + repoOwner + ), + queryFn: () => fetchIntegrationAuthGithubEnvs(integrationAuthId, repoName, repoOwner), + enabled: true + }); +}; + export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => { return useQuery({ queryKey: integrationAuthKeys.getIntegrationAuthQoveryOrgs(integrationAuthId), diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index 6aea82cf3..ff29583c2 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -41,6 +41,7 @@ import { useCreateIntegration, useGetIntegrationAuthApps, useGetIntegrationAuthById, + useGetIntegrationAuthGithubEnvs, useGetIntegrationAuthGithubOrgs, useGetWorkspaceById } from "@app/hooks/api"; @@ -50,13 +51,8 @@ enum TabSections { Options = "options" } -const targetEnv = [ - "github-repo", - "github-org", - "github-env" -] as const; -type TargetEnv = typeof targetEnv[number]; - +const targetEnv = ["github-repo", "github-org", "github-env"] as const; +type TargetEnv = (typeof targetEnv)[number]; const schema = yup.object({ selectedSourceEnvironment: yup.string().trim().required("Project Environment is required"), @@ -64,22 +60,31 @@ const schema = yup.object({ secretSuffix: yup.string().trim().optional(), scope: yup.mixed().oneOf(targetEnv.slice()).required(), - repoIds: yup - .array(yup.string().required()) - .min(1, "Select atleast one repo") // .min() not working showing error for empty array - .optional(), - repoId: yup - .string() - .optional(), - - envId: yup - .string() - .optional(), - - orgId: yup - .string() - .optional(), - + + repoIds: yup.mixed().when("scope", { + is: "github-repo", + then: yup.array(yup.string().required()).min(1, "Select atleast one repositories") + }), + + repoName: yup.mixed().when("scope", { + is: "github-env", + then: yup.string().required("Repository is required") + }), + + repoOwner: yup.mixed().when("scope", { + is: "github-env", + then: yup.string().required("Repository is required") + }), + + envId: yup.mixed().when("scope", { + is: "github-env", + then: yup.string().required("Environment is required") + }), + + orgId: yup.mixed().when("scope", { + is: "github-org", + then: yup.string().required("Organization is required") + }) }); type FormData = yup.InferType; @@ -89,33 +94,41 @@ export default function GitHubCreateIntegrationPage() { const { mutateAsync } = useCreateIntegration(); const { createNotification } = useNotificationContext(); - const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); + const integrationAuthId = + (queryString.parse(router.asPath.split("?")[1]).integrationAuthId as string) ?? ""; const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); - const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId); + const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({ - integrationAuthId: (integrationAuthId as string) ?? "" + integrationAuthId }); - - const { data: integrationAuthOrgs } = - useGetIntegrationAuthGithubOrgs(integrationAuthId as string); + + const { data: integrationAuthOrgs } = useGetIntegrationAuthGithubOrgs( + integrationAuthId as string + ); const { control, handleSubmit, watch, setValue } = useForm({ resolver: yupResolver(schema), defaultValues: { - selectedSourceEnvironment: "", secretPath: "/", - repoIds: [], - secretSuffix: "", - scope: "github-repo" + scope: "github-repo", + repoIds: [] } }); const scope = watch("scope"); - const repoIds = watch("repoIds"); - + const repoName = watch("repoName"); + const repoOwner = watch("repoOwner"); + + const { data: integrationAuthGithubEnvs } = useGetIntegrationAuthGithubEnvs( + integrationAuthId as string, + repoName, + repoOwner + ); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { @@ -124,61 +137,93 @@ export default function GitHubCreateIntegrationPage() { } }, [workspace]); - useEffect(() => { - if (integrationAuthApps) { - if (integrationAuthApps.length > 0) { - setValue("repoIds", [String(integrationAuthApps[0].appId)]); - } else { - setValue("repoIds", ["none"]); - } - } - }, [integrationAuthApps]); - const onFormSubmit = async (data: FormData) => { try { setIsLoading(true); if (!integrationAuth?.id) return; - const targetApps = integrationAuthApps?.filter((integrationAuthApp) => - data.repoIds?.includes(String(integrationAuthApp.appId)) - ); + switch (data.scope) { + case "github-repo": { + const targetApps = integrationAuthApps?.filter((integrationAuthApp) => + data.repoIds?.includes(String(integrationAuthApp.appId)) + ); - if (!targetApps) return; + if (!targetApps) return; - await Promise.all( - targetApps.map(async (targetApp) => { + await Promise.all( + targetApps.map(async (targetApp) => { + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + scope: data.scope, + secretPath: data.secretPath, + sourceEnvironment: data.selectedSourceEnvironment, + app: targetApp.name, // repo name + owner: targetApp.owner, // repo owner + metadata: { + secretSuffix: data.secretSuffix + } + }); + }) + ); + + break; + } + case "github-org": await mutateAsync({ integrationAuthId: integrationAuth?.id, isActive: true, - app: targetApp.name, - owner: targetApp.owner, secretPath: data.secretPath, sourceEnvironment: data.selectedSourceEnvironment, + scope: data.scope, + targetServiceId: data.orgId, // github org id metadata: { secretSuffix: data.secretSuffix } }); - }) - ); + break; + + case "github-env": + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + secretPath: data.secretPath, + sourceEnvironment: data.selectedSourceEnvironment, + scope: data.scope, + app: repoName, // repo name // TODO: CHANGE THIS STATE INTO YUP + owner: repoOwner, // repo owner + targetEnvironmentId: data.envId, // github environment id + metadata: { + secretSuffix: data.secretSuffix + } + }); + break; + default: + throw new Error("Invalid scope"); + } setIsLoading(false); router.push(`/integrations/${localStorage.getItem("projectData.id")}`); } catch (err) { console.error(err); + + let errorMessage: string = "Something went wrong!"; if (axios.isAxiosError(err)) { const { message } = err?.response?.data as { message: string }; - createNotification({ - text: message, - type: "error" - }); + errorMessage = message; } + + createNotification({ + text: errorMessage, + type: "error" + }); setIsLoading(false); } }; return integrationAuth && workspace && integrationAuthApps ? ( -
+
Set Up GitHub Integration @@ -239,7 +284,7 @@ export default function GitHubCreateIntegrationPage() { > onChange(e)} + onValueChange={onChange} className="w-full border border-mineshaft-500" > Github Repositories @@ -289,17 +329,15 @@ export default function GitHubCreateIntegrationPage() { )} /> - - {scope === "github-repo" && repoIds && ( + {scope === "github-repo" && ( ( + render={({ field: { onChange }, fieldState: { error } }) => ( @@ -335,7 +373,8 @@ export default function GitHubCreateIntegrationPage() { if (repoIds.includes(String(integrationAuthApp.appId))) { onChange( repoIds.filter( - (appId) => appId !== String(integrationAuthApp.appId) + (appId: string) => + appId !== String(integrationAuthApp.appId) ) ); } else { @@ -368,68 +407,73 @@ export default function GitHubCreateIntegrationPage() { )} /> - )} {scope === "github-org" && ( ( - - - - )} - /> + + + )} + /> )} {scope === "github-env" && ( ( )} @@ -442,15 +486,33 @@ export default function GitHubCreateIntegrationPage() { render={({ field: { onChange, ...field }, fieldState: { error } }) => ( )} @@ -493,7 +555,6 @@ export default function GitHubCreateIntegrationPage() { variant="outline_bg" className="mb-6" isLoading={isLoading} - isDisabled={integrationAuthApps.length === 0 || repoIds?.length === 0} > Create Integration From dc696f8932a1298e7d358c4e6a925e073dc35192 Mon Sep 17 00:00:00 2001 From: Salman Date: Thu, 15 Feb 2024 15:59:18 +0530 Subject: [PATCH 004/582] Update integration section for github repo, org and env --- frontend/src/hooks/api/integrations/types.ts | 38 +++++-------- .../src/pages/integrations/github/create.tsx | 5 +- .../IntegrationsSection.tsx | 56 +++++++++---------- 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/frontend/src/hooks/api/integrations/types.ts b/frontend/src/hooks/api/integrations/types.ts index f63257f84..db205a3ec 100644 --- a/frontend/src/hooks/api/integrations/types.ts +++ b/frontend/src/hooks/api/integrations/types.ts @@ -11,31 +11,23 @@ export type TCloudIntegration = { export type TIntegration = { id: string; - projectId: string; - envId: string; - environment: { slug: string; name: string; id: string }; isActive: boolean; - url: any; - app: string; - appId: string; - targetEnvironment: string; - targetEnvironmentId: string; - targetService: string; - targetServiceId: string; - owner: string; - path: string; - region: string; + url?: string; + app?: string; + appId?: string; + targetEnvironment?: string; + targetEnvironmentId?: string; + targetService?: string; + targetServiceId?: string; + owner?: string; + path?: string; + region?: string; + scope?: string; integration: string; - integrationAuth: string; + metadata?: Record; + integrationAuthId: string; + envId: string; secretPath: string; createdAt: string; updatedAt: string; - __v: number; - metadata?: { - secretSuffix?: string; - scope: string; - org: string; - project: string; - environment: string; - }; -}; +}; \ No newline at end of file diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index ff29583c2..398245748 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -63,7 +63,7 @@ const schema = yup.object({ repoIds: yup.mixed().when("scope", { is: "github-repo", - then: yup.array(yup.string().required()).min(1, "Select atleast one repositories") + then: yup.array(yup.string().required()).min(1, "Select at least one repositories") }), repoName: yup.mixed().when("scope", { @@ -177,6 +177,7 @@ export default function GitHubCreateIntegrationPage() { secretPath: data.secretPath, sourceEnvironment: data.selectedSourceEnvironment, scope: data.scope, + owner: integrationAuthOrgs?.find(e=>e.orgId === data.orgId)?.name, // repo owner targetServiceId: data.orgId, // github org id metadata: { secretSuffix: data.secretSuffix @@ -588,7 +589,7 @@ export default function GitHubCreateIntegrationPage() { /> ) : (
- +

Something went wrong. Please contact{" "} ; + environments: Array<{ name: string; slug: string; id: string }>; integrations?: TIntegration[]; isLoading?: boolean; onIntegrationDelete: (integration: TIntegration, cb: () => void) => void; @@ -84,25 +81,11 @@ export const IntegrationsSection = ({ key={`integration-${integration?.id.toString()}`} >

-
- - - +
+ +
+ {environments.find((e) => e.id === integration.envId)?.name || "-"} +
@@ -142,11 +125,21 @@ export const IntegrationsSection = ({
)}
- -
- {integration.integration === "hashicorp-vault" - ? `${integration.app} - path: ${integration.path}` - : integration.app} + +
+ { + (integration.integration === "hashicorp-vault" && `${integration.app} - path: ${integration.path}`) || + (integration.scope === "github-org" && `${integration.owner}` ) || + (integration.scope?.startsWith("github-") && `${integration.owner}/${integration.app}` ) || + integration.app + }
{(integration.integration === "vercel" || @@ -154,11 +147,12 @@ export const IntegrationsSection = ({ integration.integration === "railway" || integration.integration === "gitlab" || integration.integration === "teamcity" || - integration.integration === "bitbucket") && ( + integration.integration === "bitbucket" || + (integration.integration === "github" && integration.scope === "github-env")) && (
-
- {integration.targetEnvironment} +
+ {integration.targetEnvironment || integration.targetEnvironmentId}
)} From b80a5989a856cf91c8ab0955375550f5e3a04c33 Mon Sep 17 00:00:00 2001 From: Salman Date: Fri, 16 Feb 2024 00:58:10 +0530 Subject: [PATCH 005/582] Fix reset env on repo change --- .../src/pages/integrations/github/create.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index 398245748..74cf42fec 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -137,6 +137,16 @@ export default function GitHubCreateIntegrationPage() { } }, [workspace]); + + useEffect(() => { + if (integrationAuthGithubEnvs && integrationAuthGithubEnvs?.length > 0) { + setValue("envId", integrationAuthGithubEnvs[0].envId); + } + else { + setValue("envId", undefined); + } + }, [integrationAuthGithubEnvs]) + const onFormSubmit = async (data: FormData) => { try { setIsLoading(true); @@ -422,7 +432,7 @@ export default function GitHubCreateIntegrationPage() { isError={Boolean(integrationAuthOrgs?.length && error?.message)} > { setValue("repoName", e); setValue( @@ -495,7 +505,7 @@ export default function GitHubCreateIntegrationPage() { isError={Boolean(integrationAuthGithubEnvs?.length || error?.message)} > )} diff --git a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx index bb517950a..10ef21e27 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx @@ -60,7 +60,7 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => link = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/integrations/netlify/oauth2/callback`; break; case "github": - link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/integrations/github/oauth2/callback&state=${state}`; + link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo,admin:org&redirect_uri=${window.location.origin}/integrations/github/oauth2/callback&state=${state}`; break; case "gitlab": link = `${window.location.origin}/integrations/gitlab/authorize`; From 2b7784718da80d5238146edbd2a3850cd168532d Mon Sep 17 00:00:00 2001 From: Salman Date: Sat, 16 Mar 2024 08:01:28 +0530 Subject: [PATCH 035/582] fix: disabled repo env until repo selected --- frontend/src/pages/integrations/github/create.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index f81770441..c1e2073c1 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -17,6 +17,7 @@ import { yupResolver } from "@hookform/resolvers/yup"; import axios from "axios"; import { motion } from "framer-motion"; import queryString from "query-string"; +import { twMerge } from "tailwind-merge"; import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; @@ -124,6 +125,7 @@ export default function GitHubCreateIntegrationPage() { }); const scope = watch("scope"); + const repoId = watch("repoId"); const repoIds = watch("repoIds"); const repoName = watch("repoName"); const repoOwner = watch("repoOwner"); @@ -133,7 +135,7 @@ export default function GitHubCreateIntegrationPage() { repoName, repoOwner ); - + const [isLoading, setIsLoading] = useState(false); useEffect(() => { @@ -237,7 +239,7 @@ export default function GitHubCreateIntegrationPage() { }; return integrationAuth && workspace && integrationAuthApps ? ( -
+
Set Up GitHub Integration @@ -508,7 +510,11 @@ export default function GitHubCreateIntegrationPage() { setSelectedSourceEnvironment(val)} - className="w-full border border-mineshaft-500" - > - {workspace?.environments.map((sourceEnvironment) => ( - + ( + - {sourceEnvironment.name} - - ))} - - - - setSecretPath(evt.target.value)} - placeholder="Provide a path, default is /" - /> - - - + )} - - + /> + ( + + + + )} + /> + { + return ( + + + + ); + }} + /> +
+ ( + onChange(isChecked)} + isChecked={value} + > + Auto-redeploy service upon secret change + + )} + /> +
+
-
+ ) : (
From 9473de22120c36a2e4e45beadcab067ef4a97fce Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 01:34:15 +0100 Subject: [PATCH 042/582] Draft --- .../src/server/routes/v2/project-router.ts | 55 +++++++++++++++++++ backend/src/services/project/project-dal.ts | 39 +++++++++++++ .../src/services/project/project-service.ts | 31 +++++++++++ backend/src/services/project/project-types.ts | 14 +++++ 4 files changed, 139 insertions(+) diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 208edba7b..bf9ecef85 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -17,6 +17,14 @@ const projectWithEnv = ProjectsSchema.merge( }) ); +const slugSchema = z + .string() + .min(5) + .max(36) + .refine((v) => slugify(v) === v, { + message: "Slug must be a valid slug" + }); + export const registerProjectRouter = async (server: FastifyZodProvider) => { /* Get project key */ server.route({ @@ -169,4 +177,51 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { return { project }; } }); + + server.route({ + method: "DELETE", + url: "/:slug", + schema: { + params: z.object({ + slug: slugSchema.describe("The slug of the project to delete.") + }), + response: { + 200: z.void() + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + + handler: async (req) => { + await server.services.project.deleteProjectBySlug({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actor: req.permission.type, + slug: req.params.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.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const project = await server.services.project.getProjectBySlug({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actor: req.permission.type, + slug: req.params.slug + }); + + return project; + } + }); }; diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 7d0826e12..20302b53e 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -160,6 +160,44 @@ export const projectDALFactory = (db: TDbClient) => { } }; + const findProjectBySlug = async (slug: string) => { + try { + const projects = await db(TableName.ProjectMembership) + .where(`${TableName.Project}.slug`, slug) + .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" } + ]); + return 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]; + } catch (error) { + throw new DatabaseError({ error, name: "Find project by slug" }); + } + }; + const checkProjectUpgradeStatus = async (projectId: string) => { const project = await projectOrm.findById(projectId); const upgradeInProgress = @@ -179,6 +217,7 @@ export const projectDALFactory = (db: TDbClient) => { findAllProjectsByIdentity, findProjectGhostUser, findProjectById, + findProjectBySlug, checkProjectUpgradeStatus }; }; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 873a7d36f..4717cca11 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -32,7 +32,9 @@ import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns"; import { TProjectQueueFactory } from "./project-queue"; import { TCreateProjectDTO, + TDeleteProjectBySlugDTO, TDeleteProjectDTO, + TGetProjectBySlugDTO, TGetProjectDTO, TUpdateProjectDTO, TUpgradeProjectDTO @@ -329,6 +331,27 @@ export const projectServiceFactory = ({ return deletedProject; }; + const deleteProjectBySlug = async ({ actor, actorId, actorOrgId, slug }: TDeleteProjectBySlugDTO) => { + const project = await projectDAL.findOne({ slug }); + + const { permission } = await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); + + const deletedProject = await projectDAL.transaction(async (tx) => { + 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 delProject; + }); + + return deletedProject; + }; + const getProjects = async (actorId: string) => { const workspaces = await projectDAL.findAllProjects(actorId); return workspaces; @@ -339,6 +362,12 @@ export const projectServiceFactory = ({ return projectDAL.findProjectById(projectId); }; + const getProjectBySlug = async ({ actorId, actorOrgId, slug, actor }: TGetProjectBySlugDTO) => { + const project = await projectDAL.findProjectBySlug(slug); + await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); + return project; + }; + const updateProject = async ({ projectId, actor, actorId, actorOrgId, update }: TUpdateProjectDTO) => { const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -417,8 +446,10 @@ export const projectServiceFactory = ({ deleteProject, getProjects, updateProject, + deleteProjectBySlug, getProjectUpgradeStatus, getAProject, + getProjectBySlug, toggleAutoCapitalization, updateName, upgradeProject diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 3843450c2..2dabeb07c 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -19,6 +19,13 @@ export type TDeleteProjectDTO = { projectId: string; }; +export type TDeleteProjectBySlugDTO = { + slug: string; + actor: ActorType; + actorId: string; + actorOrgId?: string; +}; + export type TGetProjectDTO = { actor: ActorType; actorId: string; @@ -26,6 +33,13 @@ export type TGetProjectDTO = { projectId: string; }; +export type TGetProjectBySlugDTO = { + slug: string; + actor: ActorType; + actorId: string; + actorOrgId?: string; +}; + export type TUpdateProjectDTO = { update: { name?: string; From 8e3fc044cab2a933da31bd79cf2de5eefb91d80f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 04:17:18 +0100 Subject: [PATCH 043/582] Slug projects and filter type --- .../src/server/routes/v1/project-router.ts | 24 +++++---- .../src/server/routes/v2/project-router.ts | 54 ++++++++++++++++--- backend/src/services/project/project-dal.ts | 33 +++++++++++- .../src/services/project/project-service.ts | 49 +++++------------ backend/src/services/project/project-types.ts | 44 ++++++++------- 5 files changed, 131 insertions(+), 73 deletions(-) diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 3ffedf98d..bd2778141 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"; @@ -137,10 +138,11 @@ 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({ + filterType: ProjectFilterType.ID, + filter: req.params.workspaceId, actorId: req.permission.id, actor: req.permission.type, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + actorOrgId: req.permission.orgId }); return { workspace }; } @@ -189,10 +191,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const workspace = await server.services.project.deleteProject({ + filterType: ProjectFilterType.ID, + filter: req.params.workspaceId, actorId: req.permission.id, actor: req.permission.type, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId + actorOrgId: req.permission.orgId }); return { workspace }; } @@ -253,17 +256,18 @@ 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, + filterType: ProjectFilterType.ID, + filter: req.params.workspaceId, update: { name: req.body.name, autoCapitalization: req.body.autoCapitalization - } + }, + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId }); return { workspace diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index bf9ecef85..8f3d91c62 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( @@ -178,6 +179,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); + /* Delete a project by slug */ server.route({ method: "DELETE", url: "/:slug", @@ -186,21 +188,25 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { slug: slugSchema.describe("The slug of the project to delete.") }), response: { - 200: z.void() + 200: ProjectsSchema } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - await server.services.project.deleteProjectBySlug({ + const project = await server.services.project.deleteProject({ + filterType: ProjectFilterType.SLUG, + filter: req.params.slug, actorId: req.permission.id, actorOrgId: req.permission.orgId, - actor: req.permission.type, - slug: req.params.slug + actor: req.permission.type }); + + return project; } }); + /* Get a project by slug */ server.route({ method: "GET", url: "/:slug", @@ -214,11 +220,47 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const project = await server.services.project.getProjectBySlug({ + const project = await server.services.project.getAProject({ + filter: req.params.slug, + filterType: ProjectFilterType.SLUG, actorId: req.permission.id, actorOrgId: req.permission.orgId, + 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.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const project = await server.services.project.updateProject({ + filterType: ProjectFilterType.SLUG, + filter: req.params.slug, + update: { + name: req.body.name, + autoCapitalization: req.body.autoCapitalization + }, + actorId: req.permission.id, actor: req.permission.type, - slug: req.params.slug + actorOrgId: req.permission.orgId }); return project; diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 20302b53e..d629b1d0f 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 { 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,6 +157,12 @@ 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" }); } @@ -177,7 +185,7 @@ export const projectDALFactory = (db: TDbClient) => { { column: `${TableName.Project}.name`, order: "asc" }, { column: `${TableName.Environment}.position`, order: "asc" } ]); - return sqlNestRelationships({ + const project = sqlNestRelationships({ data: projects, key: "id", parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }), @@ -193,11 +201,31 @@ 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 project by slug" }); } }; + const findProjectByFilter = async (filter: string, type: ProjectFilterType) => { + try { + if (type === ProjectFilterType.ID) { + return await findProjectById(filter); + } + if (type === ProjectFilterType.SLUG) { + return await findProjectBySlug(filter); + } + throw new BadRequestError({ message: "Invalid filter type" }); + } catch (error) { + throw new DatabaseError({ error, name: `Failed to find project by ${type}` }); + } + }; + const checkProjectUpgradeStatus = async (projectId: string) => { const project = await projectOrm.findById(projectId); const upgradeInProgress = @@ -217,6 +245,7 @@ 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 4717cca11..4c647f391 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -32,11 +32,11 @@ import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns"; import { TProjectQueueFactory } from "./project-queue"; import { TCreateProjectDTO, - TDeleteProjectBySlugDTO, TDeleteProjectDTO, - TGetProjectBySlugDTO, TGetProjectDTO, + TToggleProjectAutoCapitalizationDTO, TUpdateProjectDTO, + TUpdateProjectNameDTO, TUpgradeProjectDTO } from "./project-types"; @@ -312,27 +312,8 @@ export const projectServiceFactory = ({ return results; }; - const deleteProject = async ({ actor, actorId, actorOrgId, projectId }: TDeleteProjectDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); - - const deletedProject = await projectDAL.transaction(async (tx) => { - const project = await projectDAL.deleteById(projectId, tx); - const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(projectId).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 deletedProject; - }; - - const deleteProjectBySlug = async ({ actor, actorId, actorOrgId, slug }: TDeleteProjectBySlugDTO) => { - const project = await projectDAL.findOne({ slug }); + const deleteProject = async ({ actor, actorId, actorOrgId, filter, filterType }: TDeleteProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter, filterType); const { permission } = await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); @@ -357,22 +338,20 @@ 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, filter, filterType, actor }: TGetProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter, filterType); - const getProjectBySlug = async ({ actorId, actorOrgId, slug, actor }: TGetProjectBySlugDTO) => { - const project = await projectDAL.findProjectBySlug(slug); await permissionService.getProjectPermission(actor, actorId, project.id, 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, update, filter, filterType }: TUpdateProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter, filterType); + + const { permission } = await permissionService.getProjectPermission(actor, actorId, project.id, 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 }); @@ -385,7 +364,7 @@ export const projectServiceFactory = ({ actorId, actorOrgId, autoCapitalization - }: TGetProjectDTO & { autoCapitalization: boolean }) => { + }: TToggleProjectAutoCapitalizationDTO) => { const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -393,7 +372,7 @@ export const projectServiceFactory = ({ return updatedProject; }; - const updateName = async ({ projectId, actor, actorId, actorOrgId, name }: TGetProjectDTO & { name: string }) => { + const updateName = async ({ projectId, actor, actorId, actorOrgId, name }: TUpdateProjectNameDTO) => { const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -446,10 +425,8 @@ export const projectServiceFactory = ({ deleteProject, getProjects, updateProject, - deleteProjectBySlug, getProjectUpgradeStatus, getAProject, - getProjectBySlug, toggleAutoCapitalization, updateName, upgradeProject diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 2dabeb07c..f43930c47 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -3,6 +3,11 @@ import { TProjectPermission } from "@app/lib/types"; import { ActorType } from "../auth/auth-type"; +export enum ProjectFilterType { + ID = "id", + SLUG = "slug" +} + export type TCreateProjectDTO = { actor: ActorType; actorId: string; @@ -12,13 +17,6 @@ export type TCreateProjectDTO = { slug?: string; }; -export type TDeleteProjectDTO = { - actor: ActorType; - actorId: string; - actorOrgId?: string; - projectId: string; -}; - export type TDeleteProjectBySlugDTO = { slug: string; actor: ActorType; @@ -27,25 +25,33 @@ export type TDeleteProjectBySlugDTO = { }; export type TGetProjectDTO = { - actor: ActorType; - actorId: string; - actorOrgId?: string; - projectId: string; -}; + filter: string; + filterType: ProjectFilterType; +} & Omit; -export type TGetProjectBySlugDTO = { - slug: string; - actor: ActorType; - actorId: string; - actorOrgId?: string; -}; +export type TToggleProjectAutoCapitalizationDTO = { + autoCapitalization: boolean; +} & TProjectPermission; +export type TUpdateProjectNameDTO = { + name: string; +} & TProjectPermission; export type TUpdateProjectDTO = { + filter: string; + filterType: ProjectFilterType; update: { name?: string; autoCapitalization?: boolean; }; -} & TProjectPermission; +} & Omit; + +export type TDeleteProjectDTO = { + filter: string; + filterType: ProjectFilterType; + actor: ActorType; + actorId: string; + actorOrgId?: string; +} & Omit; export type TUpgradeProjectDTO = { userPrivateKey: string; From 7438c114dd387d107e3b405a28eee0d6c53076d4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 05:52:20 +0100 Subject: [PATCH 044/582] Remove API key auth mode --- backend/src/server/routes/v2/project-router.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 8f3d91c62..1b6e0a3e4 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -91,7 +91,7 @@ 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, @@ -116,7 +116,7 @@ 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({ projectId: req.params.projectId, @@ -155,7 +155,7 @@ 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, @@ -191,7 +191,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { 200: ProjectsSchema } }, - 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.deleteProject({ @@ -218,7 +218,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { 200: projectWithEnv } }, - 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.getAProject({ filter: req.params.slug, @@ -249,7 +249,7 @@ 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.updateProject({ filterType: ProjectFilterType.SLUG, From e917b744f400251299f2b3ba54f9efb32f6705c0 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:03:08 +0100 Subject: [PATCH 045/582] feat: standardize org ID's on auth requests --- .../server/plugins/auth/inject-identity.ts | 36 +++++++++++++++- .../server/plugins/auth/inject-permission.ts | 4 +- backend/src/server/routes/index.ts | 8 +++- .../services/auth-token/auth-token-service.ts | 23 +++++++++-- .../identity-access-token-service.ts | 15 ++++++- .../service-token/service-token-service.ts | 9 +++- frontend/src/config/request.ts | 41 +++++++++++-------- 7 files changed, 106 insertions(+), 30 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 3a0a0ab39..d71bca1bd 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -11,12 +11,12 @@ import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-to 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; } | { authMode: AuthMode.API_KEY; @@ -30,12 +30,14 @@ export type TAuthMode = serviceToken: TServiceTokens & { createdByEmail: string }; actor: ActorType.SERVICE; serviceTokenId: string; + orgId: string; } | { authMode: AuthMode.IDENTITY_ACCESS_TOKEN; actor: ActorType.IDENTITY; identityId: string; identityName: string; + orgId: string; } | { authMode: AuthMode.SCIM_TOKEN; @@ -89,6 +91,26 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { } }; +/* +!!! IMPORTANT NOTE ABOUT `orgId` FIELD on `req.auth` !!! + +The `orgId` is an optional field, this is intentional. +There are cases where the `orgId` won't be present on the request auth object. + + +2 Examples: + +1. When a user first creates their account, no organization is present most of the time, because they haven't created one yet. +2. When a user is using an API key. We can't link API keys to organizations, because they are not tied to any organization, but instead they're tied to the user itself. + + +Reasons for orgId to be undefined when JWT is used, is to indicate that a certain token was obtained from successfully logging into an org with org-level auth enforced. +Certain organizations don’t require that enforcement and so the tokens don’t have organizationId on them. +They shouldn’t be used to access organizations that have specific org-level auth enforced +And so to differentiate between tokens that were obtained from regular login vs those at the org-auth level we include that field into those tokens. + +*/ + export const injectIdentity = fp(async (server: FastifyZodProvider) => { server.decorateRequest("auth", null); server.addHook("onRequest", async (req) => { @@ -97,36 +119,46 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { if (!authMode) return; switch (authMode) { + // May or may not have an orgId. If it doesn't have an org ID, it's likely because the token is from an org that doesn't enforce org-level auth. case AuthMode.JWT: { - const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); + const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity( + token, + req.headers?.["x-infisical-organization-id"] + ); req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; break; } + // Will always contain an orgId. case AuthMode.IDENTITY_ACCESS_TOKEN: { const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp); req.auth = { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, actor, + orgId: identity.orgId, identityId: identity.identityId, identityName: identity.name }; break; } + // Will always contain an orgId. case AuthMode.SERVICE_TOKEN: { const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); req.auth = { authMode: AuthMode.SERVICE_TOKEN as const, serviceToken, + orgId: serviceToken.orgId, serviceTokenId: serviceToken.id, actor }; break; } + // Will never contain an orgId. API keys are not tied to an organization. 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 }; break; } + // OK case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId }; diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index 2d61647e8..bd4ac66db 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -11,9 +11,9 @@ export const injectPermission = fp(async (server) => { if (req.auth.actor === ActorType.USER) { req.permission = { type: ActorType.USER, id: req.auth.userId, orgId: req.auth?.orgId }; } else if (req.auth.actor === ActorType.IDENTITY) { - req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId }; + req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId, orgId: req.auth.orgId }; } 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 }; } else if (req.auth.actor === ActorType.SCIM_CLIENT) { req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId }; } diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 0a49806bd..7f254d3be 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -264,7 +264,7 @@ export const registerRoutes = async ( queueService }); - const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); + const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgDAL }); const userService = userServiceFactory({ userDAL }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService }); const passwordService = authPaswordServiceFactory({ @@ -516,6 +516,7 @@ export const registerRoutes = async ( const serviceTokenService = serviceTokenServiceFactory({ projectEnvDAL, serviceTokenDAL, + orgDAL, userDAL, permissionService }); @@ -525,7 +526,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/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 59f336e5a..4c44a101b 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -7,6 +7,7 @@ import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; import { AuthModeJwtTokenPayload } from "../auth/auth-type"; +import { TOrgDALFactory } from "../org/org-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TTokenDALFactory } from "./auth-token-dal"; import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types"; @@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo type TAuthTokenServiceFactoryDep = { tokenDAL: TTokenDALFactory; userDAL: Pick; + orgDAL: Pick; }; export type TAuthTokenServiceFactory = ReturnType; @@ -54,7 +56,7 @@ export const getTokenConfig = (tokenType: TokenType) => { } }; -export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => { +export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenServiceFactoryDep) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const { token, ...tkCfg } = getTokenConfig(type); const appCfg = getConfig(); @@ -130,7 +132,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact const revokeAllMySessions = async (userId: string) => tokenDAL.deleteTokenSession({ userId }); // to parse jwt identity in inject identity plugin - const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => { + const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, organizationIdHeader?: string | string[]) => { const session = await tokenDAL.findOneTokenSession({ id: token.tokenVersionId, userId: token.userId @@ -141,7 +143,22 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); - return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; + let orgId = token.organizationId; + if (!token.organizationId && organizationIdHeader) { + // If the token doesn't have an organization ID, but an organization ID is provided in the header, we need to check if the user is a member of the organization before concluding the organization ID is valid. + const userMembership = ( + await orgDAL.findMembership({ + userId: user.id, + orgId: organizationIdHeader as string + }) + )[0]; + + if (!userMembership) throw new UnauthorizedError({ name: "User not a member of the organization" }); + + orgId = userMembership.orgId; + } + + return { user, tokenVersionId: token.tokenVersionId, orgId }; }; return { 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 32774ccbb..4b53c8174 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/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index cce0d3780..748fbc95d 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 { TOrgDALFactory } from "../org/org-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TServiceTokenDALFactory } from "./service-token-dal"; @@ -23,6 +24,7 @@ type TServiceTokenServiceFactoryDep = { serviceTokenDAL: TServiceTokenDALFactory; userDAL: TUserDALFactory; permissionService: Pick; + orgDAL: Pick; projectEnvDAL: Pick; }; @@ -31,6 +33,7 @@ export type TServiceTokenServiceFactory = ReturnType { @@ -130,6 +133,7 @@ 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(); if (serviceToken.expiresAt && new Date(serviceToken.expiresAt) < new Date()) { @@ -142,7 +146,10 @@ export const serviceTokenServiceFactory = ({ const updatedToken = await serviceTokenDAL.updateById(serviceToken.id, { lastUsed: new Date() }); - return { ...serviceToken, lastUsed: updatedToken.lastUsed }; + + const organization = await orgDAL.findOrgByProjectId(serviceToken.projectId); + + return { ...serviceToken, lastUsed: updatedToken.lastUsed, orgId: organization.id }; }; return { diff --git a/frontend/src/config/request.ts b/frontend/src/config/request.ts index 8f8669010..0d0525799 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,28 @@ 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}`; + const organizationId = localStorage.getItem("orgData.id"); + + if (config.headers) { + if (organizationId) { + // eslint-disable-next-line no-param-reassign + config.headers["x-infisical-organization-id"] = organizationId; + } + + 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; }); From bbceb37d065e7c3ce8d37ecda415b8ff9c60c93b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:03:54 +0100 Subject: [PATCH 046/582] feat: fix project query by slug (now accepts an org ID) --- .../src/server/routes/v1/project-router.ts | 18 ++++++++----- .../src/server/routes/v2/project-router.ts | 21 ++++++++++----- backend/src/services/project/project-dal.ts | 27 +++++++++++++------ .../src/services/project/project-service.ts | 12 ++++----- backend/src/services/project/project-types.ts | 20 +++++++++----- 5 files changed, 66 insertions(+), 32 deletions(-) diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index bd2778141..c76504ee0 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -138,8 +138,10 @@ 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({ - filterType: ProjectFilterType.ID, - filter: req.params.workspaceId, + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId @@ -191,8 +193,10 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const workspace = await server.services.project.deleteProject({ - filterType: ProjectFilterType.ID, - filter: req.params.workspaceId, + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId @@ -259,8 +263,10 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const workspace = await server.services.project.updateProject({ - filterType: ProjectFilterType.ID, - filter: req.params.workspaceId, + filter: { + type: ProjectFilterType.ID, + projectId: req.params.workspaceId + }, update: { name: req.body.name, autoCapitalization: req.body.autoCapitalization diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 1b6e0a3e4..91d7b2684 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -195,8 +195,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const project = await server.services.project.deleteProject({ - filterType: ProjectFilterType.SLUG, - filter: req.params.slug, + filter: { + type: ProjectFilterType.SLUG, + slug: req.params.slug, + orgId: req.permission.orgId + }, actorId: req.permission.id, actorOrgId: req.permission.orgId, actor: req.permission.type @@ -221,8 +224,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const project = await server.services.project.getAProject({ - filter: req.params.slug, - filterType: ProjectFilterType.SLUG, + filter: { + slug: req.params.slug, + orgId: req.permission.orgId, + type: ProjectFilterType.SLUG + }, actorId: req.permission.id, actorOrgId: req.permission.orgId, actor: req.permission.type @@ -252,8 +258,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const project = await server.services.project.updateProject({ - filterType: ProjectFilterType.SLUG, - filter: req.params.slug, + filter: { + type: ProjectFilterType.SLUG, + slug: req.params.slug, + orgId: req.permission.orgId + }, update: { name: req.body.name, autoCapitalization: req.body.autoCapitalization diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index d629b1d0f..369e005ac 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -5,7 +5,7 @@ import { ProjectsSchema, ProjectUpgradeStatus, ProjectVersion, TableName, TProje import { BadRequestError, DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; -import { ProjectFilterType } from "./project-types"; +import { Filter, ProjectFilterType } from "./project-types"; export type TProjectDALFactory = ReturnType; @@ -168,10 +168,11 @@ export const projectDALFactory = (db: TDbClient) => { } }; - const findProjectBySlug = async (slug: string) => { + 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( @@ -185,6 +186,7 @@ export const projectDALFactory = (db: TDbClient) => { { column: `${TableName.Project}.name`, order: "asc" }, { column: `${TableName.Environment}.position`, order: "asc" } ]); + const project = sqlNestRelationships({ data: projects, key: "id", @@ -212,17 +214,26 @@ export const projectDALFactory = (db: TDbClient) => { } }; - const findProjectByFilter = async (filter: string, type: ProjectFilterType) => { + const findProjectByFilter = async (filter: Filter) => { try { - if (type === ProjectFilterType.ID) { - return await findProjectById(filter); + if (filter.type === ProjectFilterType.ID) { + return await findProjectById(filter.projectId); } - if (type === ProjectFilterType.SLUG) { - return await findProjectBySlug(filter); + 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) { - throw new DatabaseError({ error, name: `Failed to find project by ${type}` }); + if (error instanceof BadRequestError) { + throw error; + } + throw new DatabaseError({ error, name: `Failed to find project by ${filter.type}` }); } }; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 4c647f391..6048c142a 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -312,8 +312,8 @@ export const projectServiceFactory = ({ return results; }; - const deleteProject = async ({ actor, actorId, actorOrgId, filter, filterType }: TDeleteProjectDTO) => { - const project = await projectDAL.findProjectByFilter(filter, filterType); + const deleteProject = async ({ actor, actorId, actorOrgId, filter }: TDeleteProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter); const { permission } = await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); @@ -338,15 +338,15 @@ export const projectServiceFactory = ({ return workspaces; }; - const getAProject = async ({ actorId, actorOrgId, filter, filterType, actor }: TGetProjectDTO) => { - const project = await projectDAL.findProjectByFilter(filter, filterType); + const getAProject = async ({ actorId, actorOrgId, filter, actor }: TGetProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter); await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); return project; }; - const updateProject = async ({ actor, actorId, actorOrgId, update, filter, filterType }: TUpdateProjectDTO) => { - const project = await projectDAL.findProjectByFilter(filter, filterType); + const updateProject = async ({ actor, actorId, actorOrgId, update, filter }: TUpdateProjectDTO) => { + const project = await projectDAL.findProjectByFilter(filter); const { permission } = await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index f43930c47..ace09d2b0 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -8,6 +8,17 @@ export enum ProjectFilterType { SLUG = "slug" } +export type Filter = + | { + type: ProjectFilterType.ID; + projectId: string; + } + | { + type: ProjectFilterType.SLUG; + slug: string; + orgId: string | undefined; + }; + export type TCreateProjectDTO = { actor: ActorType; actorId: string; @@ -25,8 +36,7 @@ export type TDeleteProjectBySlugDTO = { }; export type TGetProjectDTO = { - filter: string; - filterType: ProjectFilterType; + filter: Filter; } & Omit; export type TToggleProjectAutoCapitalizationDTO = { @@ -37,8 +47,7 @@ export type TUpdateProjectNameDTO = { } & TProjectPermission; export type TUpdateProjectDTO = { - filter: string; - filterType: ProjectFilterType; + filter: Filter; update: { name?: string; autoCapitalization?: boolean; @@ -46,8 +55,7 @@ export type TUpdateProjectDTO = { } & Omit; export type TDeleteProjectDTO = { - filter: string; - filterType: ProjectFilterType; + filter: Filter; actor: ActorType; actorId: string; actorOrgId?: string; From 2032063c245da6077cdb6c0366a43ad332b89bb1 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:27:30 +0100 Subject: [PATCH 047/582] Fix: Remove org ID from JWT --- .../server/plugins/auth/inject-identity.ts | 5 +--- backend/src/server/routes/index.ts | 2 +- .../services/auth-token/auth-token-service.ts | 23 +++---------------- frontend/src/config/request.ts | 6 ----- 4 files changed, 5 insertions(+), 31 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index d71bca1bd..18d21ebfe 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -121,10 +121,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { switch (authMode) { // May or may not have an orgId. If it doesn't have an org ID, it's likely because the token is from an org that doesn't enforce org-level auth. case AuthMode.JWT: { - const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity( - token, - req.headers?.["x-infisical-organization-id"] - ); + const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; break; } diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 7f254d3be..e17c06c87 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -264,7 +264,7 @@ export const registerRoutes = async ( queueService }); - const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgDAL }); + const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); const userService = userServiceFactory({ userDAL }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService }); const passwordService = authPaswordServiceFactory({ diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 4c44a101b..59f336e5a 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -7,7 +7,6 @@ import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; import { AuthModeJwtTokenPayload } from "../auth/auth-type"; -import { TOrgDALFactory } from "../org/org-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TTokenDALFactory } from "./auth-token-dal"; import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types"; @@ -15,7 +14,6 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo type TAuthTokenServiceFactoryDep = { tokenDAL: TTokenDALFactory; userDAL: Pick; - orgDAL: Pick; }; export type TAuthTokenServiceFactory = ReturnType; @@ -56,7 +54,7 @@ export const getTokenConfig = (tokenType: TokenType) => { } }; -export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenServiceFactoryDep) => { +export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const { token, ...tkCfg } = getTokenConfig(type); const appCfg = getConfig(); @@ -132,7 +130,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenSer const revokeAllMySessions = async (userId: string) => tokenDAL.deleteTokenSession({ userId }); // to parse jwt identity in inject identity plugin - const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, organizationIdHeader?: string | string[]) => { + const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => { const session = await tokenDAL.findOneTokenSession({ id: token.tokenVersionId, userId: token.userId @@ -143,22 +141,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenSer const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); - let orgId = token.organizationId; - if (!token.organizationId && organizationIdHeader) { - // If the token doesn't have an organization ID, but an organization ID is provided in the header, we need to check if the user is a member of the organization before concluding the organization ID is valid. - const userMembership = ( - await orgDAL.findMembership({ - userId: user.id, - orgId: organizationIdHeader as string - }) - )[0]; - - if (!userMembership) throw new UnauthorizedError({ name: "User not a member of the organization" }); - - orgId = userMembership.orgId; - } - - return { user, tokenVersionId: token.tokenVersionId, orgId }; + return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; }; return { diff --git a/frontend/src/config/request.ts b/frontend/src/config/request.ts index 0d0525799..b29f19f5e 100644 --- a/frontend/src/config/request.ts +++ b/frontend/src/config/request.ts @@ -15,14 +15,8 @@ apiRequest.interceptors.request.use((config) => { const mfaTempToken = getMfaTempToken(); const token = getAuthToken(); const providerAuthToken = SecurityClient.getProviderAuthToken(); - const organizationId = localStorage.getItem("orgData.id"); if (config.headers) { - if (organizationId) { - // eslint-disable-next-line no-param-reassign - config.headers["x-infisical-organization-id"] = organizationId; - } - if (signupTempToken) { // eslint-disable-next-line no-param-reassign config.headers.Authorization = `Bearer ${signupTempToken}`; From ce057f44acd0094e93dcd7d41f6a73429218231a Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Wed, 28 Feb 2024 11:08:37 -0500 Subject: [PATCH 048/582] nit: update error message --- backend/src/server/routes/v2/project-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 91d7b2684..3d032168b 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -23,7 +23,7 @@ const slugSchema = z .min(5) .max(36) .refine((v) => slugify(v) === v, { - message: "Slug must be a valid slug" + message: "Slug must be at least 5 character but no more than 36" }); export const registerProjectRouter = async (server: FastifyZodProvider) => { From 12a6fba6459944bd91f86bec4d1d3b52338ec52a Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:37:33 +0100 Subject: [PATCH 049/582] Feat: Create project via org slug instead of org ID --- .../src/server/routes/v2/project-router.ts | 2 +- .../src/services/project/project-service.ts | 37 ++++++++++++++++--- backend/src/services/project/project-types.ts | 3 +- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 3d032168b..c210a1b30 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -160,7 +160,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const project = await server.services.project.createProject({ actorId: req.permission.id, actor: req.permission.type, - orgId: req.body.organizationId, + orgSlug: req.body.organizationSlug, workspaceName: req.body.projectName, slug: req.body.slug }); diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 6048c142a..c19856eb9 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"; @@ -63,6 +64,7 @@ type TProjectServiceFactoryDep = { permissionService: TPermissionServiceFactory; orgService: Pick; licenseService: Pick; + orgDAL: Pick; }; export type TProjectServiceFactory = ReturnType; @@ -72,6 +74,7 @@ export const projectServiceFactory = ({ projectQueue, projectKeyDAL, permissionService, + orgDAL, userDAL, folderDAL, orgService, @@ -88,11 +91,33 @@ export const projectServiceFactory = ({ /* * Create workspace. Make user the admin * */ - const createProject = async ({ orgId, actor, actorId, actorOrgId, workspaceName, slug }: TCreateProjectDTO) => { + const createProject = async ({ + orgId, + orgSlug, + actor, + actorId, + actorOrgId, + workspaceName, + slug + }: TCreateProjectDTO) => { + if (orgSlug && orgId) { + throw new BadRequestError({ + message: "Cannot provide both orgId and orgSlug" + }); + } + + if (!orgSlug && !orgId) { + throw new BadRequestError({ + message: "Must provide either orgId or orgSlug" + }); + } + + const organization = orgSlug ? await orgDAL.findOne({ slug: orgSlug }) : await orgDAL.findOne({ id: orgId }); + const { permission, membership: orgMembership } = await permissionService.getOrgPermission( actor, actorId, - orgId, + organization.id, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); @@ -100,7 +125,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 @@ -110,12 +135,12 @@ 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, + orgId: organization.id, slug: slug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), version: ProjectVersion.V2 }, @@ -272,7 +297,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); diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index ace09d2b0..56237b428 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -23,7 +23,8 @@ export type TCreateProjectDTO = { actor: ActorType; actorId: string; actorOrgId?: string; - orgId: string; + orgId?: string; + orgSlug?: string; workspaceName: string; slug?: string; }; From 0f14fab9158a74c6f2b613bcd6b4e99ca0278c84 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 29 Feb 2024 01:02:36 +0100 Subject: [PATCH 050/582] Update index.ts --- backend/src/server/routes/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index e17c06c87..04fb6d191 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -372,6 +372,7 @@ export const registerRoutes = async ( projectKeyDAL, userDAL, projectEnvDAL, + orgDAL, orgService, projectMembershipDAL, folderDAL, From e7b11eac2b53935ce60664ead7ec4a67d6fbb110 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 29 Feb 2024 02:22:28 +0100 Subject: [PATCH 051/582] Fix: Remove orgId from service token --- backend/src/server/plugins/auth/inject-identity.ts | 2 -- backend/src/server/plugins/auth/inject-permission.ts | 2 +- backend/src/server/routes/index.ts | 1 - .../src/services/service-token/service-token-service.ts | 7 +------ 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 18d21ebfe..c323687d8 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -30,7 +30,6 @@ export type TAuthMode = serviceToken: TServiceTokens & { createdByEmail: string }; actor: ActorType.SERVICE; serviceTokenId: string; - orgId: string; } | { authMode: AuthMode.IDENTITY_ACCESS_TOKEN; @@ -143,7 +142,6 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { req.auth = { authMode: AuthMode.SERVICE_TOKEN as const, serviceToken, - orgId: serviceToken.orgId, serviceTokenId: serviceToken.id, actor }; diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index bd4ac66db..8510d88c2 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -13,7 +13,7 @@ export const injectPermission = fp(async (server) => { } else if (req.auth.actor === ActorType.IDENTITY) { req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId, orgId: req.auth.orgId }; } else if (req.auth.actor === ActorType.SERVICE) { - req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId, orgId: req.auth.orgId }; + req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId }; } else if (req.auth.actor === ActorType.SCIM_CLIENT) { req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId }; } diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 04fb6d191..464ea7d0d 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -517,7 +517,6 @@ export const registerRoutes = async ( const serviceTokenService = serviceTokenServiceFactory({ projectEnvDAL, serviceTokenDAL, - orgDAL, userDAL, permissionService }); diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 748fbc95d..3410aacad 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -9,7 +9,6 @@ import { getConfig } from "@app/lib/config/env"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { ActorType } from "../auth/auth-type"; -import { TOrgDALFactory } from "../org/org-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TServiceTokenDALFactory } from "./service-token-dal"; @@ -24,7 +23,6 @@ type TServiceTokenServiceFactoryDep = { serviceTokenDAL: TServiceTokenDALFactory; userDAL: TUserDALFactory; permissionService: Pick; - orgDAL: Pick; projectEnvDAL: Pick; }; @@ -33,7 +31,6 @@ export type TServiceTokenServiceFactory = ReturnType { @@ -147,9 +144,7 @@ export const serviceTokenServiceFactory = ({ lastUsed: new Date() }); - const organization = await orgDAL.findOrgByProjectId(serviceToken.projectId); - - return { ...serviceToken, lastUsed: updatedToken.lastUsed, orgId: organization.id }; + return { ...serviceToken, lastUsed: updatedToken.lastUsed }; }; return { From f36a056c6217cfa2ee47455f3bd6d936a9ae6f78 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 29 Feb 2024 02:24:16 +0100 Subject: [PATCH 052/582] Update inject-identity.ts --- .../server/plugins/auth/inject-identity.ts | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index c323687d8..11cb24117 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -90,26 +90,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { } }; -/* -!!! IMPORTANT NOTE ABOUT `orgId` FIELD on `req.auth` !!! - -The `orgId` is an optional field, this is intentional. -There are cases where the `orgId` won't be present on the request auth object. - - -2 Examples: - -1. When a user first creates their account, no organization is present most of the time, because they haven't created one yet. -2. When a user is using an API key. We can't link API keys to organizations, because they are not tied to any organization, but instead they're tied to the user itself. - - -Reasons for orgId to be undefined when JWT is used, is to indicate that a certain token was obtained from successfully logging into an org with org-level auth enforced. -Certain organizations don’t require that enforcement and so the tokens don’t have organizationId on them. -They shouldn’t be used to access organizations that have specific org-level auth enforced -And so to differentiate between tokens that were obtained from regular login vs those at the org-auth level we include that field into those tokens. - -*/ - +// ! 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) => { From 34618041ca6bb602f209604fb41d018bbfb9ed37 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 29 Feb 2024 02:26:42 +0100 Subject: [PATCH 053/582] Update inject-identity.ts --- backend/src/server/plugins/auth/inject-identity.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 11cb24117..a95fca451 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -99,7 +99,6 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { if (!authMode) return; switch (authMode) { - // May or may not have an orgId. If it doesn't have an org ID, it's likely because the token is from an org that doesn't enforce org-level auth. 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 }; @@ -117,7 +116,6 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; break; } - // Will always contain an orgId. case AuthMode.SERVICE_TOKEN: { const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); req.auth = { @@ -128,13 +126,11 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; break; } - // Will never contain an orgId. API keys are not tied to an organization. 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 }; break; } - // OK case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId }; From c0b0c0754b992f62468f1eb8ef3054f672d121c9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 03:07:09 +0100 Subject: [PATCH 054/582] Feat: List secrets by project slug --- backend/src/server/routes/v3/secret-router.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 65219d0ab..23d5496d0 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"; @@ -70,6 +71,18 @@ 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, + actor: req.permission.type, + actorOrgId: req.permission.orgId + }); + workspaceId = workspace.id; } if (!workspaceId || !environment) throw new BadRequestError({ message: "Missing workspace id or environment" }); @@ -85,7 +98,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { }); await server.services.auditLog.createAuditLog({ - projectId: req.query.workspaceId, + projectId: workspaceId, ...req.auditLogInfo, event: { type: EventType.GET_SECRETS, From a766329de52ee64f518c0212231d36dd9dc63ed2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 03:09:35 +0100 Subject: [PATCH 055/582] Fix: Non-existant variable being passed to Posthog --- backend/src/server/routes/v2/project-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index c210a1b30..98a4e3c52 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -169,7 +169,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 } From f0383dd55c95e40bc8bbd7b2aabadd7789396464 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 03:19:44 +0100 Subject: [PATCH 056/582] Fix: Change org ID to org slug --- frontend/src/hooks/api/workspace/queries.tsx | 8 ++++---- frontend/src/hooks/api/workspace/types.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 37e512ac8..24a7e5794 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 828a7a890..657b07693 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 }; From aaca3ac229563c779b606cd8afbe1d9a83b2365e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 03:19:58 +0100 Subject: [PATCH 057/582] Fix: Change org ID to org slug --- frontend/src/layouts/AppLayout/AppLayout.tsx | 2 +- frontend/src/pages/org/[id]/overview/index.tsx | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 7b0eeddbf..c09fd000f 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -226,7 +226,7 @@ export const AppLayout = ({ children }: LayoutProps) => { project: { id: newProjectId } } } = await createWs.mutateAsync({ - organizationId: currentOrg.id, + organizationSlug: currentOrg.slug, projectName: name }); diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx index 601e7e55d..b37c8ab9e 100644 --- a/frontend/src/pages/org/[id]/overview/index.tsx +++ b/frontend/src/pages/org/[id]/overview/index.tsx @@ -44,6 +44,7 @@ import { import { OrgPermissionActions, OrgPermissionSubjects, + useOrganization, useSubscription, useUser, useWorkspace @@ -468,8 +469,9 @@ const OrganizationPage = withPermission( const router = useRouter(); const { workspaces, isLoading: isWorkspaceLoading } = useWorkspace(); - const currentOrg = String(router.query.id); - const orgWorkspaces = workspaces?.filter((workspace) => workspace.orgId === currentOrg) || []; + const { currentOrg } = useOrganization(); + const routerOrgId = String(router.query.id); + const orgWorkspaces = workspaces?.filter((workspace) => workspace.orgId === routerOrgId) || []; const { createNotification } = useNotificationContext(); const addUsersToProject = useAddUserToWsNonE2EE(); @@ -505,12 +507,12 @@ const OrganizationPage = withPermission( project: { id: newProjectId } } } = await createWs.mutateAsync({ - organizationId: currentOrg, + organizationSlug: currentOrg.slug, projectName: name }); if (addMembers) { - const orgUsers = await fetchOrgUsers(currentOrg); + const orgUsers = await fetchOrgUsers(currentOrg.id); await addUsersToProject.mutateAsync({ usernames: orgUsers @@ -540,7 +542,7 @@ const OrganizationPage = withPermission( useEffect(() => { onboardingCheck({ - orgId: currentOrg, + orgId: routerOrgId, setHasUserClickedIntro, setHasUserClickedSlack, setHasUserPushedSecrets, From a9bba02f442e66b11b08bdfbd451d2ce3ca0c00f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 01:34:15 +0100 Subject: [PATCH 058/582] Draft --- .../src/services/project/project-service.ts | 25 +++++++++++++++++++ backend/src/services/project/project-types.ts | 14 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index c19856eb9..ca27c883c 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -33,7 +33,9 @@ import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns"; import { TProjectQueueFactory } from "./project-queue"; import { TCreateProjectDTO, + TDeleteProjectBySlugDTO, TDeleteProjectDTO, + TGetProjectBySlugDTO, TGetProjectDTO, TToggleProjectAutoCapitalizationDTO, TUpdateProjectDTO, @@ -358,6 +360,27 @@ export const projectServiceFactory = ({ return deletedProject; }; + const deleteProjectBySlug = async ({ actor, actorId, actorOrgId, slug }: TDeleteProjectBySlugDTO) => { + const project = await projectDAL.findOne({ slug }); + + const { permission } = await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); + + const deletedProject = await projectDAL.transaction(async (tx) => { + 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 delProject; + }); + + return deletedProject; + }; + const getProjects = async (actorId: string) => { const workspaces = await projectDAL.findAllProjects(actorId); return workspaces; @@ -450,8 +473,10 @@ export const projectServiceFactory = ({ deleteProject, getProjects, updateProject, + deleteProjectBySlug, getProjectUpgradeStatus, getAProject, + getProjectBySlug, toggleAutoCapitalization, updateName, upgradeProject diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 56237b428..258018176 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -36,6 +36,13 @@ export type TDeleteProjectBySlugDTO = { actorOrgId?: string; }; +export type TDeleteProjectBySlugDTO = { + slug: string; + actor: ActorType; + actorId: string; + actorOrgId?: string; +}; + export type TGetProjectDTO = { filter: Filter; } & Omit; @@ -47,6 +54,13 @@ export type TUpdateProjectNameDTO = { name: string; } & TProjectPermission; +export type TGetProjectBySlugDTO = { + slug: string; + actor: ActorType; + actorId: string; + actorOrgId?: string; +}; + export type TUpdateProjectDTO = { filter: Filter; update: { From 92441e018f49e3664870202bf4568d7adc9d6f6e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Fri, 23 Feb 2024 04:17:18 +0100 Subject: [PATCH 059/582] Slug projects and filter type --- backend/src/services/project/project-dal.ts | 1 - backend/src/services/project/project-service.ts | 4 ---- backend/src/services/project/project-types.ts | 12 ++++++------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 369e005ac..7041c6cf6 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -186,7 +186,6 @@ export const projectDALFactory = (db: TDbClient) => { { column: `${TableName.Project}.name`, order: "asc" }, { column: `${TableName.Environment}.position`, order: "asc" } ]); - const project = sqlNestRelationships({ data: projects, key: "id", diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index ca27c883c..f9223ca02 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -33,9 +33,7 @@ import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns"; import { TProjectQueueFactory } from "./project-queue"; import { TCreateProjectDTO, - TDeleteProjectBySlugDTO, TDeleteProjectDTO, - TGetProjectBySlugDTO, TGetProjectDTO, TToggleProjectAutoCapitalizationDTO, TUpdateProjectDTO, @@ -473,10 +471,8 @@ export const projectServiceFactory = ({ deleteProject, getProjects, updateProject, - deleteProjectBySlug, getProjectUpgradeStatus, getAProject, - getProjectBySlug, toggleAutoCapitalization, updateName, upgradeProject diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 258018176..364d25a80 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -54,12 +54,12 @@ export type TUpdateProjectNameDTO = { name: string; } & TProjectPermission; -export type TGetProjectBySlugDTO = { - slug: string; - actor: ActorType; - actorId: string; - actorOrgId?: string; -}; +export type TToggleProjectAutoCapitalizationDTO = { + autoCapitalization: boolean; +} & TProjectPermission; +export type TUpdateProjectNameDTO = { + name: string; +} & TProjectPermission; export type TUpdateProjectDTO = { filter: Filter; From 96abbd9f80b6c1de0d46dd143e8e6380b9da61c4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:03:08 +0100 Subject: [PATCH 060/582] feat: standardize org ID's on auth requests --- .../server/plugins/auth/inject-identity.ts | 11 ++++++++- .../server/plugins/auth/inject-permission.ts | 2 +- backend/src/server/routes/index.ts | 3 ++- .../services/auth-token/auth-token-service.ts | 23 ++++++++++++++++--- .../service-token/service-token-service.ts | 3 +++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index a95fca451..afdf1a932 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -30,6 +30,7 @@ export type TAuthMode = serviceToken: TServiceTokens & { createdByEmail: string }; actor: ActorType.SERVICE; serviceTokenId: string; + orgId: string; } | { authMode: AuthMode.IDENTITY_ACCESS_TOKEN; @@ -99,8 +100,12 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { if (!authMode) return; switch (authMode) { + // May or may not have an orgId. If it doesn't have an org ID, it's likely because the token is from an org that doesn't enforce org-level auth. case AuthMode.JWT: { - const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); + const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity( + token, + req.headers?.["x-infisical-organization-id"] + ); req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; break; } @@ -116,21 +121,25 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; break; } + // Will always contain an orgId. case AuthMode.SERVICE_TOKEN: { const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); req.auth = { authMode: AuthMode.SERVICE_TOKEN as const, serviceToken, + orgId: serviceToken.orgId, serviceTokenId: serviceToken.id, actor }; break; } + // Will never contain an orgId. API keys are not tied to an organization. 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 }; break; } + // OK case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId }; diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index 8510d88c2..bd4ac66db 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -13,7 +13,7 @@ export const injectPermission = fp(async (server) => { } else if (req.auth.actor === ActorType.IDENTITY) { req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId, orgId: req.auth.orgId }; } 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 }; } else if (req.auth.actor === ActorType.SCIM_CLIENT) { req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId }; } diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 464ea7d0d..ae126ac34 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -264,7 +264,7 @@ export const registerRoutes = async ( queueService }); - const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); + const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgDAL }); const userService = userServiceFactory({ userDAL }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService }); const passwordService = authPaswordServiceFactory({ @@ -517,6 +517,7 @@ export const registerRoutes = async ( const serviceTokenService = serviceTokenServiceFactory({ projectEnvDAL, serviceTokenDAL, + orgDAL, userDAL, permissionService }); diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 59f336e5a..4c44a101b 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -7,6 +7,7 @@ import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; import { AuthModeJwtTokenPayload } from "../auth/auth-type"; +import { TOrgDALFactory } from "../org/org-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TTokenDALFactory } from "./auth-token-dal"; import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types"; @@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo type TAuthTokenServiceFactoryDep = { tokenDAL: TTokenDALFactory; userDAL: Pick; + orgDAL: Pick; }; export type TAuthTokenServiceFactory = ReturnType; @@ -54,7 +56,7 @@ export const getTokenConfig = (tokenType: TokenType) => { } }; -export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => { +export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenServiceFactoryDep) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const { token, ...tkCfg } = getTokenConfig(type); const appCfg = getConfig(); @@ -130,7 +132,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact const revokeAllMySessions = async (userId: string) => tokenDAL.deleteTokenSession({ userId }); // to parse jwt identity in inject identity plugin - const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => { + const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, organizationIdHeader?: string | string[]) => { const session = await tokenDAL.findOneTokenSession({ id: token.tokenVersionId, userId: token.userId @@ -141,7 +143,22 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); - return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; + let orgId = token.organizationId; + if (!token.organizationId && organizationIdHeader) { + // If the token doesn't have an organization ID, but an organization ID is provided in the header, we need to check if the user is a member of the organization before concluding the organization ID is valid. + const userMembership = ( + await orgDAL.findMembership({ + userId: user.id, + orgId: organizationIdHeader as string + }) + )[0]; + + if (!userMembership) throw new UnauthorizedError({ name: "User not a member of the organization" }); + + orgId = userMembership.orgId; + } + + return { user, tokenVersionId: token.tokenVersionId, orgId }; }; return { diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 3410aacad..40e414acd 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 { TOrgDALFactory } from "../org/org-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TServiceTokenDALFactory } from "./service-token-dal"; @@ -23,6 +24,7 @@ type TServiceTokenServiceFactoryDep = { serviceTokenDAL: TServiceTokenDALFactory; userDAL: TUserDALFactory; permissionService: Pick; + orgDAL: Pick; projectEnvDAL: Pick; }; @@ -31,6 +33,7 @@ export type TServiceTokenServiceFactory = ReturnType { From bd3cbb3c7b87bbab7f6afab1ca1f00ae656a8fa8 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:03:54 +0100 Subject: [PATCH 061/582] feat: fix project query by slug (now accepts an org ID) --- backend/src/services/project/project-dal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 7041c6cf6..369e005ac 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -186,6 +186,7 @@ export const projectDALFactory = (db: TDbClient) => { { column: `${TableName.Project}.name`, order: "asc" }, { column: `${TableName.Environment}.position`, order: "asc" } ]); + const project = sqlNestRelationships({ data: projects, key: "id", From 5eb3258311949ed424493d8d13efc24916da129b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:27:30 +0100 Subject: [PATCH 062/582] Fix: Remove org ID from JWT --- .../server/plugins/auth/inject-identity.ts | 5 +--- backend/src/server/routes/index.ts | 2 +- .../services/auth-token/auth-token-service.ts | 23 +++---------------- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index afdf1a932..faf275b2f 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -102,10 +102,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { switch (authMode) { // May or may not have an orgId. If it doesn't have an org ID, it's likely because the token is from an org that doesn't enforce org-level auth. case AuthMode.JWT: { - const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity( - token, - req.headers?.["x-infisical-organization-id"] - ); + const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; break; } diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index ae126ac34..04fb6d191 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -264,7 +264,7 @@ export const registerRoutes = async ( queueService }); - const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgDAL }); + const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); const userService = userServiceFactory({ userDAL }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService }); const passwordService = authPaswordServiceFactory({ diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 4c44a101b..59f336e5a 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -7,7 +7,6 @@ import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; import { AuthModeJwtTokenPayload } from "../auth/auth-type"; -import { TOrgDALFactory } from "../org/org-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TTokenDALFactory } from "./auth-token-dal"; import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types"; @@ -15,7 +14,6 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo type TAuthTokenServiceFactoryDep = { tokenDAL: TTokenDALFactory; userDAL: Pick; - orgDAL: Pick; }; export type TAuthTokenServiceFactory = ReturnType; @@ -56,7 +54,7 @@ export const getTokenConfig = (tokenType: TokenType) => { } }; -export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenServiceFactoryDep) => { +export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const { token, ...tkCfg } = getTokenConfig(type); const appCfg = getConfig(); @@ -132,7 +130,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenSer const revokeAllMySessions = async (userId: string) => tokenDAL.deleteTokenSession({ userId }); // to parse jwt identity in inject identity plugin - const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, organizationIdHeader?: string | string[]) => { + const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => { const session = await tokenDAL.findOneTokenSession({ id: token.tokenVersionId, userId: token.userId @@ -143,22 +141,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenSer const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); - let orgId = token.organizationId; - if (!token.organizationId && organizationIdHeader) { - // If the token doesn't have an organization ID, but an organization ID is provided in the header, we need to check if the user is a member of the organization before concluding the organization ID is valid. - const userMembership = ( - await orgDAL.findMembership({ - userId: user.id, - orgId: organizationIdHeader as string - }) - )[0]; - - if (!userMembership) throw new UnauthorizedError({ name: "User not a member of the organization" }); - - orgId = userMembership.orgId; - } - - return { user, tokenVersionId: token.tokenVersionId, orgId }; + return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; }; return { From dbf498b44a8dd1e90b911237ab5dee5114a99d2e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 29 Feb 2024 02:22:28 +0100 Subject: [PATCH 063/582] Fix: Remove orgId from service token --- backend/src/server/plugins/auth/inject-identity.ts | 2 -- backend/src/server/plugins/auth/inject-permission.ts | 2 +- backend/src/server/routes/index.ts | 1 - backend/src/services/service-token/service-token-service.ts | 3 --- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index faf275b2f..11cb24117 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -30,7 +30,6 @@ export type TAuthMode = serviceToken: TServiceTokens & { createdByEmail: string }; actor: ActorType.SERVICE; serviceTokenId: string; - orgId: string; } | { authMode: AuthMode.IDENTITY_ACCESS_TOKEN; @@ -124,7 +123,6 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { req.auth = { authMode: AuthMode.SERVICE_TOKEN as const, serviceToken, - orgId: serviceToken.orgId, serviceTokenId: serviceToken.id, actor }; diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index bd4ac66db..8510d88c2 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -13,7 +13,7 @@ export const injectPermission = fp(async (server) => { } else if (req.auth.actor === ActorType.IDENTITY) { req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId, orgId: req.auth.orgId }; } else if (req.auth.actor === ActorType.SERVICE) { - req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId, orgId: req.auth.orgId }; + req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId }; } else if (req.auth.actor === ActorType.SCIM_CLIENT) { req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId }; } diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 04fb6d191..464ea7d0d 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -517,7 +517,6 @@ export const registerRoutes = async ( const serviceTokenService = serviceTokenServiceFactory({ projectEnvDAL, serviceTokenDAL, - orgDAL, userDAL, permissionService }); diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 40e414acd..3410aacad 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -9,7 +9,6 @@ import { getConfig } from "@app/lib/config/env"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { ActorType } from "../auth/auth-type"; -import { TOrgDALFactory } from "../org/org-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TServiceTokenDALFactory } from "./service-token-dal"; @@ -24,7 +23,6 @@ type TServiceTokenServiceFactoryDep = { serviceTokenDAL: TServiceTokenDALFactory; userDAL: TUserDALFactory; permissionService: Pick; - orgDAL: Pick; projectEnvDAL: Pick; }; @@ -33,7 +31,6 @@ export type TServiceTokenServiceFactory = ReturnType { From 307b89e799d083ebf80cf771deba07d6731b51f2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 29 Feb 2024 02:26:42 +0100 Subject: [PATCH 064/582] Update inject-identity.ts --- backend/src/server/plugins/auth/inject-identity.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 11cb24117..a95fca451 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -99,7 +99,6 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { if (!authMode) return; switch (authMode) { - // May or may not have an orgId. If it doesn't have an org ID, it's likely because the token is from an org that doesn't enforce org-level auth. 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 }; @@ -117,7 +116,6 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; break; } - // Will always contain an orgId. case AuthMode.SERVICE_TOKEN: { const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); req.auth = { @@ -128,13 +126,11 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; break; } - // Will never contain an orgId. API keys are not tied to an organization. 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 }; break; } - // OK case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId }; From 30e7fe8a4561d50a8a421d92e8041258e416b62d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:21:27 +0100 Subject: [PATCH 065/582] Fix: Rebase errors --- .../src/services/project/project-service.ts | 21 ------------------- backend/src/services/project/project-types.ts | 14 ------------- 2 files changed, 35 deletions(-) diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index f9223ca02..c19856eb9 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -358,27 +358,6 @@ export const projectServiceFactory = ({ return deletedProject; }; - const deleteProjectBySlug = async ({ actor, actorId, actorOrgId, slug }: TDeleteProjectBySlugDTO) => { - const project = await projectDAL.findOne({ slug }); - - const { permission } = await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); - - const deletedProject = await projectDAL.transaction(async (tx) => { - 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 delProject; - }); - - return deletedProject; - }; - const getProjects = async (actorId: string) => { const workspaces = await projectDAL.findAllProjects(actorId); return workspaces; diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 364d25a80..56237b428 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -36,13 +36,6 @@ export type TDeleteProjectBySlugDTO = { actorOrgId?: string; }; -export type TDeleteProjectBySlugDTO = { - slug: string; - actor: ActorType; - actorId: string; - actorOrgId?: string; -}; - export type TGetProjectDTO = { filter: Filter; } & Omit; @@ -54,13 +47,6 @@ export type TUpdateProjectNameDTO = { name: string; } & TProjectPermission; -export type TToggleProjectAutoCapitalizationDTO = { - autoCapitalization: boolean; -} & TProjectPermission; -export type TUpdateProjectNameDTO = { - name: string; -} & TProjectPermission; - export type TUpdateProjectDTO = { filter: Filter; update: { From f860fd3abeb1792a8d6bb19b596b3fead90af665 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:21:45 +0100 Subject: [PATCH 066/582] Update project-types.ts --- backend/src/services/project/project-types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 56237b428..9ce02a70f 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -43,6 +43,7 @@ export type TGetProjectDTO = { export type TToggleProjectAutoCapitalizationDTO = { autoCapitalization: boolean; } & TProjectPermission; + export type TUpdateProjectNameDTO = { name: string; } & TProjectPermission; From 716e705c2a1cc3b5a6ad8d5e2f5af85b6764e805 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:47:56 +0100 Subject: [PATCH 067/582] Fix: Removed legacy create project code --- .../src/server/routes/v1/project-router.ts | 27 ------------------- .../src/services/project/project-service.ts | 17 ++++-------- backend/src/services/project/project-types.ts | 3 +-- 3 files changed, 6 insertions(+), 41 deletions(-) diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index c76504ee0..55bae1156 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -150,33 +150,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); - 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 - }); - return { workspace }; - } - }); - server.route({ url: "/:workspaceId", method: "DELETE", diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index c19856eb9..384fd2bad 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -92,27 +92,20 @@ export const projectServiceFactory = ({ * Create workspace. Make user the admin * */ const createProject = async ({ - orgId, orgSlug, actor, actorId, actorOrgId, workspaceName, - slug + slug: projectSlug }: TCreateProjectDTO) => { - if (orgSlug && orgId) { + if (!orgSlug) { throw new BadRequestError({ - message: "Cannot provide both orgId and orgSlug" + message: "Must provide organization slug to create project" }); } - if (!orgSlug && !orgId) { - throw new BadRequestError({ - message: "Must provide either orgId or orgSlug" - }); - } - - const organization = orgSlug ? await orgDAL.findOne({ slug: orgSlug }) : await orgDAL.findOne({ id: orgId }); + const organization = await orgDAL.findOne({ slug: orgSlug }); const { permission, membership: orgMembership } = await permissionService.getOrgPermission( actor, @@ -141,7 +134,7 @@ export const projectServiceFactory = ({ { name: workspaceName, orgId: organization.id, - slug: slug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), + slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), version: ProjectVersion.V2 }, tx diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 9ce02a70f..69816882e 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -23,8 +23,7 @@ export type TCreateProjectDTO = { actor: ActorType; actorId: string; actorOrgId?: string; - orgId?: string; - orgSlug?: string; + orgSlug: string; workspaceName: string; slug?: string; }; From 4a0668e92e3ccb0df28509d4528dbc1278b7c7a9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:56:13 +0100 Subject: [PATCH 068/582] Feat: Org Scoped JWT Tokens --- backend/src/ee/services/scim/scim-service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index c542b2340..1ca881ed0 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -429,6 +429,15 @@ export const scimServiceFactory = ({ }); } + const organization = await orgDAL.findById(scimToken.orgId); + + if (!organization.scimEnabled) { + throw new ScimRequestError({ + detail: "SCIM is disabled for the organization", + status: 403 + }); + } + return { scimTokenId: scimToken.id, orgId: scimToken.orgId }; }; From c2bfeb89e8d65fc15613b72c5df36fd1af02271f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:56:25 +0100 Subject: [PATCH 069/582] Feat: Org Scoped JWT Tokens --- .../server/plugins/auth/inject-identity.ts | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index a95fca451..ab70118cf 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -18,18 +18,19 @@ export type TAuthMode = user: TUsers; orgId?: string; } - | { - authMode: AuthMode.API_KEY; - actor: ActorType.USER; - userId: string; - user: TUsers; - orgId?: string; - } + // | { + // authMode: AuthMode.API_KEY; + // actor: ActorType.USER; + // userId: string; + // user: TUsers; + // orgId?: string; + // } | { authMode: AuthMode.SERVICE_TOKEN; serviceToken: TServiceTokens & { createdByEmail: string }; actor: ActorType.SERVICE; serviceTokenId: string; + orgId: string; } | { authMode: AuthMode.IDENTITY_ACCESS_TOKEN; @@ -51,6 +52,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 @@ -72,7 +74,8 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { actor: ActorType.USER } as const; case AuthTokenType.API_KEY: - return { authMode: AuthMode.API_KEY, token: decodedToken, actor: ActorType.USER } as const; + 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 { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, @@ -96,6 +99,10 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { 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) { @@ -119,6 +126,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { 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, @@ -126,11 +134,11 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; 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 }; - 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 }; + // break; + // } case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId }; From 8327f41b8e18aca5c463ea80c2833b30fd527caa Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:56:35 +0100 Subject: [PATCH 070/582] Feat: Org Scoped JWT Tokens --- backend/src/server/plugins/auth/inject-permission.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index 8510d88c2..02cc842d6 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -9,11 +9,11 @@ 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 }; } else if (req.auth.actor === ActorType.IDENTITY) { req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId, orgId: req.auth.orgId }; } 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 }; } else if (req.auth.actor === ActorType.SCIM_CLIENT) { req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId }; } From a1fa0c652de1a6e2e482d0c9b6996120e78a940c Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:56:43 +0100 Subject: [PATCH 071/582] Feat: Org Scoped JWT Tokens --- backend/src/server/plugins/auth/verify-auth.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/server/plugins/auth/verify-auth.ts b/backend/src/server/plugins/auth/verify-auth.ts index a1274f356..db9700867 100644 --- a/backend/src/server/plugins/auth/verify-auth.ts +++ b/backend/src/server/plugins/auth/verify-auth.ts @@ -4,7 +4,7 @@ import { UnauthorizedError } from "@app/lib/errors"; import { AuthMode } from "@app/services/auth/auth-type"; export const verifyAuth = - (authStrats: AuthMode[]) => + (authStrats: AuthMode[], options: { requireOrg: boolean } = { requireOrg: true }) => (req: T, _res: FastifyReply, done: HookHandlerDoneFunction) => { if (!Array.isArray(authStrats)) throw new Error("Auth strategy must be array"); if (!req.auth) throw new UnauthorizedError({ name: "Unauthorized access", message: "Token missing" }); @@ -13,5 +13,12 @@ export const verifyAuth = 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. + // En example of this is the /v1 auth routes. + if (options.requireOrg === true && !req.permission.orgId) { + throw new UnauthorizedError({ name: `${req.url} Unauthorized Access, no organization found` }); + } + done(); }; From 4f80234afa07699730d7ba85a3420283c2c233a4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:56:50 +0100 Subject: [PATCH 072/582] Feat: Org Scoped JWT Tokens --- backend/src/server/routes/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 464ea7d0d..e50e75631 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -264,7 +264,7 @@ export const registerRoutes = async ( queueService }); - const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); + const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgDAL }); const userService = userServiceFactory({ userDAL }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService }); const passwordService = authPaswordServiceFactory({ @@ -518,7 +518,8 @@ export const registerRoutes = async ( projectEnvDAL, serviceTokenDAL, userDAL, - permissionService + permissionService, + projectDAL }); const identityService = identityServiceFactory({ From 29b2b12ec7aa95f190a99d8a5807fe9d1f980c21 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:57:01 +0100 Subject: [PATCH 073/582] Feat: Org Scoped JWT Tokens --- backend/src/server/routes/v1/auth-router.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/server/routes/v1/auth-router.ts b/backend/src/server/routes/v1/auth-router.ts index bd45f59d9..16448b8ca 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, From c42bbbea8bf95d8fb3b0080b3aad203c2a418cca Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:57:10 +0100 Subject: [PATCH 074/582] Feat: Org Scoped JWT Tokens --- backend/src/server/routes/v1/organization-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index d31682d88..5807c44ca 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 }; From 8fc081973dd23484d96d92a3ecec638b5da74a49 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:57:23 +0100 Subject: [PATCH 075/582] Feat: Org Scoped JWT Tokens --- backend/src/server/routes/v1/user-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v1/user-router.ts b/backend/src/server/routes/v1/user-router.ts index ca5148659..031bcc941 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 }; From d287c3e1529e0d9b8e225886f944752a0d33ce42 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:01:13 +0100 Subject: [PATCH 076/582] Feat: Org Scoped JWT Tokens --- backend/src/server/routes/v3/login-router.ts | 64 +++++++++++++++++++ .../services/auth-token/auth-token-service.ts | 19 +++++- backend/src/services/auth/auth-fns.ts | 4 +- .../src/services/auth/auth-login-service.ts | 41 +++++++----- .../src/services/auth/auth-signup-service.ts | 2 + backend/src/services/auth/auth-type.ts | 6 ++ .../service-token/service-token-service.ts | 10 ++- .../permissions/PermissionDeniedBanner.tsx | 9 +-- 8 files changed, 129 insertions(+), 26 deletions(-) diff --git a/backend/src/server/routes/v3/login-router.ts b/backend/src/server/routes/v3/login-router.ts index 240aa21b1..62002b775 100644 --- a/backend/src/server/routes/v3/login-router.ts +++ b/backend/src/server/routes/v3/login-router.ts @@ -1,7 +1,10 @@ +import jwt from "jsonwebtoken"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; +import { UnauthorizedError } from "@app/lib/errors"; import { authRateLimit } from "@app/server/config/rateLimiter"; +import { AuthModeJwtTokenPayload } from "@app/services/auth/auth-type"; export const registerLoginRouter = async (server: FastifyZodProvider) => { server.route({ @@ -34,6 +37,67 @@ 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(); + + if (!req.headers.authorization) throw new UnauthorizedError({ name: "Authorization header is required" }); + if (!req.headers["user-agent"]) throw new UnauthorizedError({ name: "user agent header is required" }); + + const userAgent = req.headers["user-agent"]; + const authToken = req.headers.authorization.slice(7); // slice of after Bearer + + // The decoded JWT token, which contains the auth method. + const decodedToken = jwt.verify(authToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; + + if (decodedToken.organizationId) { + throw new UnauthorizedError({ message: "You have already selected an organization" }); + } + + const user = await server.services.user.getMe(decodedToken.userId); + + // Check if the user actually has access to the specified organization. + const userOrgs = await server.services.org.findAllOrganizationOfUser(user.id); + + if (!userOrgs.some((org) => org.id === req.body.organizationId)) { + throw new UnauthorizedError({ message: "User does not have access to the organization" }); + } + + await server.services.authToken.clearTokenSessionById(decodedToken.userId, decodedToken.tokenVersionId); + const tokens = await server.services.login.generateUserTokens({ + authMethod: decodedToken.authMethod, + user, + userAgent, + ip: req.realIp, + organizationId: req.body.organizationId + }); + + 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/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 59f336e5a..a5c93e3aa 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -6,7 +6,8 @@ import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; -import { AuthModeJwtTokenPayload } from "../auth/auth-type"; +import { AuthMethod, AuthModeJwtTokenPayload } from "../auth/auth-type"; +import { TOrgDALFactory } from "../org/org-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TTokenDALFactory } from "./auth-token-dal"; import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types"; @@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo type TAuthTokenServiceFactoryDep = { tokenDAL: TTokenDALFactory; userDAL: Pick; + orgDAL: TOrgDALFactory; }; export type TAuthTokenServiceFactory = ReturnType; @@ -54,7 +56,7 @@ export const getTokenConfig = (tokenType: TokenType) => { } }; -export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => { +export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenServiceFactoryDep) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const { token, ...tkCfg } = getTokenConfig(type); const appCfg = getConfig(); @@ -135,12 +137,25 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact id: token.tokenVersionId, userId: token.userId }); + if (!session) throw new UnauthorizedError({ name: "Session not found" }); if (token.accessVersion !== session.accessVersion) throw new UnauthorizedError({ name: "Stale session" }); const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); + if (token.organizationId) { + const organization = await orgDAL.findById(token.organizationId); + + if (organization.authEnforced) { + const tokenAuthMode = token.authMethod; + + if (![AuthMethod.AZURE_SAML, AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML].includes(tokenAuthMode)) { + throw new UnauthorizedError({ name: "Organization enforces SAML" }); + } + } + } + return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; }; diff --git a/backend/src/services/auth/auth-fns.ts b/backend/src/services/auth/auth-fns.ts index 0b78ab438..80fb0b325 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 786e69d73..967160f08 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -17,7 +17,7 @@ import { TOauthLoginDTO, TVerifyMfaTokenDTO } from "./auth-login-type"; -import { AuthMethod, AuthTokenType } from "./auth-type"; +import { AuthMethod, AuthModeMfaJwtTokenPayload, AuthTokenType } from "./auth-type"; type TAuthLoginServiceFactoryDep = { userDAL: TUserDALFactory; @@ -83,12 +83,14 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: user, ip, userAgent, - organizationId + organizationId, + authMethod }: { user: TUsers; ip: string; userAgent: string; organizationId?: string; + authMethod: AuthMethod; }) => { const cfg = getConfig(); await updateUserDeviceSession(user, ip, userAgent); @@ -98,8 +100,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 +116,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: const refreshToken = jwt.sign( { + authMethod, authTokenType: AuthTokenType.REFRESH_TOKEN, userId: user.id, tokenVersionId: tokenSession.id, @@ -158,9 +163,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 +173,14 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (!userEnc) throw new Error("Failed to find user"); const cfg = getConfig(); - let organizationId; - if (!userEnc.authMethods?.includes(AuthMethod.EMAIL)) { - const { orgId } = validateProviderAuthToken(providerAuthToken as string, email); - organizationId = orgId; - } else if (providerAuthToken) { - // SAML SSO - const { orgId } = validateProviderAuthToken(providerAuthToken, email); - organizationId = orgId; + // let organizationId; + + // let authMethod = (providerAuthToken as AuthMethod) || AuthMethod.EMAIL; + + let authMethod = AuthMethod.EMAIL; + + if (providerAuthToken) { + authMethod = validateProviderAuthToken(providerAuthToken, email).authMethod; } if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?"); @@ -196,9 +201,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,7 +226,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: }, ip, userAgent, - organizationId + authMethod }); return { token, isMfaEnabled: false, user: userEnc } as const; @@ -250,6 +255,9 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: userId, code: mfaToken }); + + const decodedToken = jwt.verify(mfaToken, getConfig().AUTH_SECRET) as AuthModeMfaJwtTokenPayload; + const userEnc = await userDAL.findUserEncKeyByUserId(userId); if (!userEnc) throw new Error("Failed to authenticate user"); @@ -260,7 +268,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: }, ip, userAgent, - organizationId: orgId + organizationId: orgId, + authMethod: decodedToken.authMethod }); return { token, user: userEnc }; diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 39bfec8b1..4c14cfb81 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -174,6 +174,7 @@ export const authSignupServiceFactory = ({ const accessToken = jwt.sign( { + authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.ACCESS_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, @@ -277,6 +278,7 @@ export const authSignupServiceFactory = ({ const accessToken = jwt.sign( { + authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.ACCESS_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 57c86158f..6ce8de1bf 100644 --- a/backend/src/services/auth/auth-type.ts +++ b/backend/src/services/auth/auth-type.ts @@ -40,6 +40,7 @@ export enum ActorType { // would extend to AWS, Azure, ... export type AuthModeJwtTokenPayload = { authTokenType: AuthTokenType.ACCESS_TOKEN; + authMethod: AuthMethod; userId: string; tokenVersionId: string; accessVersion: number; @@ -48,12 +49,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 +67,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/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 3410aacad..798d2ca0c 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, @@ -132,6 +135,9 @@ export const serviceTokenServiceFactory = ({ 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); @@ -144,7 +150,7 @@ export const serviceTokenServiceFactory = ({ lastUsed: new Date() }); - return { ...serviceToken, lastUsed: updatedToken.lastUsed }; + return { ...serviceToken, lastUsed: updatedToken.lastUsed, orgId: project.orgId }; }; return { diff --git a/frontend/src/components/permissions/PermissionDeniedBanner.tsx b/frontend/src/components/permissions/PermissionDeniedBanner.tsx index 40e17577f..067ee9c4a 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
)}
From dbd7561037aaf424c941f9dd89215ab1febb0d76 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:01:39 +0100 Subject: [PATCH 077/582] Add link button --- frontend/src/components/v2/Button/Button.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/v2/Button/Button.tsx b/frontend/src/components/v2/Button/Button.tsx index 63a735d65..5536d699a 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", From 52cf937826f93781091c08cf18578bc3458b8c44 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:02:33 +0100 Subject: [PATCH 078/582] Fix: Avoid invalidating all queries on logout to prevent UI glitch --- frontend/src/hooks/api/users/queries.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index 3d5e80d74..a443c6750 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(); + } } }); }; From 143de12d67960f782821dcef6746dc5eae555b1e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:03:01 +0100 Subject: [PATCH 079/582] Feat: Select organization on login --- frontend/src/hooks/api/auth/index.tsx | 1 + frontend/src/hooks/api/auth/queries.tsx | 22 +++ .../src/pages/login/select-organization.tsx | 146 ++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 frontend/src/pages/login/select-organization.tsx diff --git a/frontend/src/hooks/api/auth/index.tsx b/frontend/src/hooks/api/auth/index.tsx index 66208a487..8b918c7ab 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 41f1b02f5..9bc7bb034 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -1,5 +1,6 @@ import { useMutation, useQuery } from "@tanstack/react-query"; +import SecurityClient from "@app/components/utilities/SecurityClient"; import { apiRequest } from "@app/config/request"; import { setAuthToken } from "@app/reactQuery"; @@ -56,6 +57,27 @@ 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 = () => { + return useMutation({ + mutationFn: async (details: { organizationId: string }) => { + const data = await selectOrganization(details); + + SecurityClient.setToken(data.token); + SecurityClient.setProviderAuthToken(""); + + return data; + } + }); +}; + export const useLogin2 = () => { return useMutation({ mutationFn: async (details: { diff --git a/frontend/src/pages/login/select-organization.tsx b/frontend/src/pages/login/select-organization.tsx new file mode 100644 index 000000000..d60a06fd3 --- /dev/null +++ b/frontend/src/pages/login/select-organization.tsx @@ -0,0 +1,146 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import { useCallback, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import Head from "next/head"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Button, Spinner } from "@app/components/v2"; +import { useUser } from "@app/context"; +import { useGetOrganizations, useLogoutUser, useSelectOrganization } from "@app/hooks/api"; +import { isLoggedIn } from "@app/reactQuery"; +import { navigateUserToOrg } from "@app/views/Login/Login.utils"; + +const LoadingScreen = () => { + return ( +
+ +

Loading, please wait

+
+ ); +}; + +export default function LoginPage() { + const router = useRouter(); + const { t } = useTranslation(); + + const organizations = useGetOrganizations(); + const selectOrg = useSelectOrganization(); + const { user, isLoading: userLoading } = useUser(); + + const logout = useLogoutUser(true); + const handleLogout = useCallback(async () => { + try { + console.log("Logging out..."); + await logout.mutateAsync(); + router.push("/login"); + } catch (error) { + console.error(error); + } + }, [logout, router]); + + const handleSelectOrganization = useCallback( + async (orgId: string) => { + console.log("Selected organization: ", orgId); + + const { token } = await selectOrg.mutateAsync({ organizationId: orgId }); + + console.log("Organization selected successfully", { token }); + + navigateUserToOrg(router, orgId); + }, + [selectOrg] + ); + + useEffect(() => { + if (!isLoggedIn()) { + router.push("/login"); + } + }, [router]); + + if (userLoading || !user) { + return ; + } + + return ( +
+ + {t("common.head-title", { title: t("login.title") })} + + + + + +
+ +
+ Infisical logo +
+ +
console.log("submit")} + className="mx-auto flex w-full flex-col items-center justify-center" + > +
+

+ Choose your organization +

+ +
+

+ You‘re currently logged in as {user.email} +

+

+ Not you?{" "} + +

+
+
+
+ {organizations.isLoading ? ( + + ) : ( + organizations.data + ?.concat(organizations.data) + .concat(organizations.data) + .map((org) => ( +
handleSelectOrganization(org.id)} + key={org.id} + className="group flex cursor-pointer items-center justify-between rounded-md bg-mineshaft-700 px-4 py-3 capitalize text-gray-200 shadow-md transition-colors hover:bg-mineshaft-600" + > +

{org.name}

+ + +
+ )) + )} +
+ + +
+
+ +
+
+ ); +} From ec4d1dd1b2383addc4dc6ba2c8e79aaade06ebc4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:03:12 +0100 Subject: [PATCH 080/582] Update _app.tsx --- frontend/src/pages/_app.tsx | 49 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index 82829e410..b26968109 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -2,6 +2,7 @@ /* eslint-disable no-var */ /* eslint-disable func-names */ /* eslint-disable react/jsx-props-no-spreading */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck import { useEffect } from "react"; @@ -86,9 +87,11 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element => - - - + + + + + @@ -99,29 +102,29 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element => return ( - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + ); }; From 2a6032a8cf8177602726ab91dde16ea2bd7899bc Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:03:29 +0100 Subject: [PATCH 081/582] Navigate to select org instead of dashboard --- .../src/views/Login/components/InitialStep/InitialStep.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index bf3920c9d..cd33a1465 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -14,7 +14,7 @@ import attemptLogin from "@app/components/utilities/attemptLogin"; import { Button, Input } from "@app/components/v2"; import { useServerConfig } from "@app/context"; -import { navigateUserToOrg } from "../../Login.utils"; +import { navigateUserToSelectOrg } from "../../Login.utils"; type Props = { setStep: (step: number) => void; @@ -85,7 +85,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: return; } - await navigateUserToOrg(router); + await navigateUserToSelectOrg(router); // case: login does not require MFA step createNotification({ From 995777d76fc7b7dfe33c30a1a9607d2c2b801dd3 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:03:45 +0100 Subject: [PATCH 082/582] Formatting and navigating to select org --- frontend/src/views/Login/Login.tsx | 46 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/frontend/src/views/Login/Login.tsx b/frontend/src/views/Login/Login.tsx index 16ea91718..0f3586238 100644 --- a/frontend/src/views/Login/Login.tsx +++ b/frontend/src/views/Login/Login.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/router"; -import axios from "axios" +import axios from "axios"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; import { getAuthToken, isLoggedIn } from "@app/reactQuery"; @@ -13,31 +13,36 @@ import { import { navigateUserToOrg } from "./Login.utils"; export const Login = () => { - const router = useRouter(); - const [step, setStep] = useState(0); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - - const queryParams = new URLSearchParams(window.location.search) - - useEffect(() => { + const router = useRouter(); + const [step, setStep] = useState(0); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const queryParams = new URLSearchParams(window.location.search); + + useEffect(() => { // TODO(akhilmhdh): workspace will be controlled by a workspace context const redirectToDashboard = async () => { + // TODO(daniel): Move this to select-organization page. try { // user details - const userDetails = await fetchUserDetails() + const userDetails = await fetchUserDetails(); // send details back to client if (queryParams && queryParams.get("callback_port")) { - const callbackPort = queryParams.get("callback_port") + const callbackPort = queryParams.get("callback_port"); // send post request to cli with details - const cliUrl = `http://127.0.0.1:${callbackPort}/` - const instance = axios.create() - await instance.post(cliUrl, { email: userDetails.email, privateKey: localStorage.getItem("PRIVATE_KEY"), JTWToken: getAuthToken() }) + const cliUrl = `http://127.0.0.1:${callbackPort}/`; + const instance = axios.create(); + await instance.post(cliUrl, { + email: userDetails.email, + privateKey: localStorage.getItem("PRIVATE_KEY"), + JTWToken: getAuthToken() + }); } - await navigateUserToOrg(router); + navigateUserToSelectOrg(router); } catch (error) { console.log("Error - Not logged in yet"); } @@ -80,10 +85,7 @@ export const Login = () => { return
; } } - - return ( -
- {renderView()} -
- ); -} \ No newline at end of file + }; + + return
{renderView()}
; +}; From 7fd6b63b5d5c8241219320bc948798f7dbcc7817 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 09:04:34 +0100 Subject: [PATCH 083/582] Feat: Navigate to select org --- frontend/src/views/Login/Login.utils.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/Login/Login.utils.tsx b/frontend/src/views/Login/Login.utils.tsx index 0aa063268..4615d8f27 100644 --- a/frontend/src/views/Login/Login.utils.tsx +++ b/frontend/src/views/Login/Login.utils.tsx @@ -1,10 +1,12 @@ import { NextRouter } from "next/router"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; +import { userKeys } from "@app/hooks/api/users/queries"; +import { queryClient } from "@app/reactQuery"; export const navigateUserToOrg = async (router: NextRouter, organizationId?: string) => { const userOrgs = await fetchOrganizations(); - + const nonAuthEnforcedOrgs = userOrgs.filter((org) => !org.authEnforced); if (organizationId) { @@ -24,3 +26,8 @@ export const navigateUserToOrg = async (router: NextRouter, organizationId?: str router.push("/org/none"); } }; + +export const navigateUserToSelectOrg = (router: NextRouter) => { + queryClient.invalidateQueries(userKeys.getUser); + router.push("/login/select-organization", undefined, { shallow: true }); +}; From 6e96f2338c79042123ace3e04c134a9bb1351dd4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:15:03 +0100 Subject: [PATCH 084/582] Feat: Scoped JWT to organization, include authMethod on all service calls --- backend/src/ee/routes/v1/license-router.ts | 19 +++++++++++++- backend/src/ee/routes/v1/org-role-router.ts | 5 ++++ .../src/ee/routes/v1/project-role-router.ts | 5 ++++ backend/src/ee/routes/v1/project-router.ts | 3 +++ backend/src/ee/routes/v1/saml-router.ts | 3 +++ backend/src/ee/routes/v1/scim-router.ts | 3 +++ .../v1/secret-approval-policy-router.ts | 5 ++++ .../v1/secret-approval-request-router.ts | 6 +++++ .../v1/secret-rotation-provider-router.ts | 1 + .../ee/routes/v1/secret-rotation-router.ts | 4 +++ .../ee/routes/v1/secret-scanning-router.ts | 5 ++++ .../src/ee/routes/v1/secret-version-router.ts | 1 + backend/src/ee/routes/v1/snapshot-router.ts | 2 ++ backend/src/ee/routes/v1/trusted-ip-router.ts | 4 +++ backend/src/server/routes/v1/bot-router.ts | 2 ++ .../src/server/routes/v1/identity-router.ts | 3 +++ backend/src/server/routes/v1/identity-ua.ts | 6 +++++ .../routes/v1/integration-auth-router.ts | 21 +++++++++++++++ .../server/routes/v1/integration-router.ts | 3 +++ .../src/server/routes/v1/invite-org-router.ts | 1 + .../server/routes/v1/organization-router.ts | 6 +++++ .../server/routes/v1/project-env-router.ts | 3 +++ .../server/routes/v1/project-key-router.ts | 1 + .../routes/v1/project-membership-router.ts | 4 +++ .../src/server/routes/v1/project-router.ts | 10 +++++++ .../server/routes/v1/secret-folder-router.ts | 4 +++ .../server/routes/v1/secret-import-router.ts | 5 ++++ .../src/server/routes/v1/secret-tag-router.ts | 3 +++ .../src/server/routes/v1/webhook-router.ts | 5 ++++ .../server/routes/v2/identity-org-router.ts | 1 + .../routes/v2/identity-project-router.ts | 4 +++ .../server/routes/v2/organization-router.ts | 7 ++++- .../routes/v2/project-membership-router.ts | 2 ++ .../src/server/routes/v2/project-router.ts | 7 +++++ .../server/routes/v2/service-token-router.ts | 3 +++ .../routes/v3/secret-blind-index-router.ts | 3 +++ backend/src/server/routes/v3/secret-router.ts | 26 +++++++++++++++++++ 37 files changed, 194 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index 41cd11f7d..4b0cf9a60 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,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgPlan({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, orgId: req.params.organizationId }); return data; @@ -89,6 +92,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 +114,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 +136,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 +158,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 +180,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 +206,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 +230,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 +256,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 +282,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 +307,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 +335,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 +362,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 +388,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 +412,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 1e40d2d80..148a84300 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 f6fd53e5e..a08a9851b 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 606448372..3fbe3f026 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 fe387143e..b30afda13 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 2a3772cd6..965aa94e1 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 8fce232a7..1ea16178f 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 97eb89109..16b6d205b 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 e7201b73f..516885936 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 8d2e90ac0..447280cf8 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 7d2c5f1ee..1f8d56c74 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 89ee4e011..630af10cf 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 79161bfd3..902d707af 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 53bc5b117..3d4ac8010 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/server/routes/v1/bot-router.ts b/backend/src/server/routes/v1/bot-router.ts index 507423b0d..7e1d6ec94 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 ac389b478..3ce13ca0a 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 6146fa242..ac5c78a08 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 7dfda4bee..9aac4ec2b 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 @@ -421,6 +430,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 +459,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 +489,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 +519,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 +549,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 +579,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 +612,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 +641,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 +671,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 +708,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 +742,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 +777,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 670f83884..4859ca584 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 5956b53df..212020034 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 5807c44ca..42e64e9d7 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -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 cb5be173e..68a5a9001 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 b34260117..440d45140 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 aece95a5d..9e2f5bd22 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 55bae1156..55f1fdda9 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -46,6 +46,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const publicKeys = await server.services.projectKey.getProjectPublicKeys({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); @@ -98,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 }); @@ -142,6 +144,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { type: ProjectFilterType.ID, projectId: req.params.workspaceId }, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId @@ -171,6 +174,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId }, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId }); @@ -200,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 @@ -244,6 +249,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { name: req.body.name, autoCapitalization: req.body.autoCapitalization }, + actorAuthMethod: req.permission.authMethod, actorId: req.permission.id, actor: req.permission.type, actorOrgId: req.permission.orgId @@ -276,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 @@ -312,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 @@ -337,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 @@ -362,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 dee075943..bd202b70b 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 823e7dbee..bd47a7424 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 7ca3e4893..c60f2b9ba 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/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 9a20a5d22..2a5ab49fb 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 97477b033..440130e13 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 67dccb5e3..d170f87e6 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/organization-router.ts b/backend/src/server/routes/v2/organization-router.ts index 7d5ba3da7..83ab6792e 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 6f81f8392..b19dc53a1 100644 --- a/backend/src/server/routes/v2/project-membership-router.ts +++ b/backend/src/server/routes/v2/project-membership-router.ts @@ -28,6 +28,7 @@ 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, actor: req.permission.type, emails: req.body.emails, @@ -74,6 +75,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 98a4e3c52..c4fecf068 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -56,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 }); @@ -96,6 +97,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { await server.services.project.upgradeProject({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, projectId: req.params.projectId, userPrivateKey: req.body.userPrivateKey }); @@ -119,6 +121,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const status = await server.services.project.getProjectUpgradeStatus({ + actorAuthMethod: req.permission.authMethod, projectId: req.params.projectId, actor: req.permission.type, actorId: req.permission.id @@ -160,6 +163,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const project = await server.services.project.createProject({ actorId: req.permission.id, actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, orgSlug: req.body.organizationSlug, workspaceName: req.body.projectName, slug: req.body.slug @@ -201,6 +205,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { orgId: req.permission.orgId }, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, actor: req.permission.type }); @@ -231,6 +236,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }, actorId: req.permission.id, actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type }); @@ -268,6 +274,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { autoCapitalization: req.body.autoCapitalization }, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId }); diff --git a/backend/src/server/routes/v2/service-token-router.ts b/backend/src/server/routes/v2/service-token-router.ts index 2b6445dea..a3970d4c0 100644 --- a/backend/src/server/routes/v2/service-token-router.ts +++ b/backend/src/server/routes/v2/service-token-router.ts @@ -46,6 +46,7 @@ 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, actor: req.permission.type }); @@ -98,6 +99,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 +138,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/v3/secret-blind-index-router.ts b/backend/src/server/routes/v3/secret-blind-index-router.ts index 94e6cab83..664eaa2c9 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 23d5496d0..9daa884f0 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -79,6 +79,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { slug: req.query.workspaceSlug }, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, actorOrgId: req.permission.orgId }); @@ -92,6 +93,7 @@ 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 @@ -176,6 +178,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, @@ -261,6 +264,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, @@ -344,6 +348,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, @@ -420,6 +425,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, @@ -515,6 +521,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, @@ -601,6 +608,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, @@ -703,6 +711,7 @@ 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, + actorAuthMethod: req.permission.authMethod, actor: req.permission.type, secretPath, environment, @@ -712,6 +721,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, @@ -755,6 +765,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, @@ -879,6 +890,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, @@ -888,6 +900,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, @@ -933,6 +946,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, @@ -1023,6 +1037,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, @@ -1032,6 +1047,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, @@ -1065,6 +1081,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, @@ -1147,6 +1164,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, @@ -1156,6 +1174,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, @@ -1185,6 +1204,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, @@ -1268,6 +1288,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, @@ -1277,6 +1298,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, @@ -1305,6 +1327,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, @@ -1377,6 +1400,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, @@ -1386,6 +1410,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, @@ -1413,6 +1438,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, From 47287be5bf071247150d46d078c7b0bb13779a10 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:18:46 +0100 Subject: [PATCH 085/582] Feat: Scoped JWT to organization, add authMethod to request --- backend/src/@types/fastify.d.ts | 3 +- .../server/plugins/auth/inject-identity.ts | 24 +++++++++++--- .../server/plugins/auth/inject-permission.ts | 32 ++++++++++++++++--- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 649c54c54..8c048481c 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 } 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/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index ab70118cf..95deb78a5 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -6,7 +6,7 @@ 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 = @@ -17,6 +17,7 @@ export type TAuthMode = tokenVersionId: string; // the session id of token used user: TUsers; orgId?: string; + authMethod: AuthMethod; } // | { // authMode: AuthMode.API_KEY; @@ -31,6 +32,7 @@ export type TAuthMode = actor: ActorType.SERVICE; serviceTokenId: string; orgId: string; + authMethod: null; } | { authMode: AuthMode.IDENTITY_ACCESS_TOKEN; @@ -38,12 +40,14 @@ export type TAuthMode = 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) => { @@ -108,7 +112,15 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { 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; } // Will always contain an orgId. @@ -119,7 +131,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { actor, orgId: identity.orgId, identityId: identity.identityId, - identityName: identity.name + identityName: identity.name, + authMethod: null }; break; } @@ -130,7 +143,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { authMode: AuthMode.SERVICE_TOKEN as const, serviceToken, serviceTokenId: serviceToken.id, - actor + actor, + authMethod: null }; break; } @@ -141,7 +155,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { // } 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 02cc842d6..b76c735f0 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -8,14 +8,38 @@ export const injectPermission = fp(async (server) => { server.addHook("onRequest", async (req) => { if (!req.auth) return; + // if (!req.auth.authMethod) { + // throw new Error("THIS SHOULD NOT HAPPEN"); + // } + 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, + authMethod: req.auth.authMethod + }; } else if (req.auth.actor === ActorType.IDENTITY) { - req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId, orgId: req.auth.orgId }; + 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, orgId: req.auth.orgId }; + 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 + }; } }); }); From 7df614a01839e03d8561d36ec47c2620cd5864a2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:19:22 +0100 Subject: [PATCH 086/582] Feat: Scoped JWT to organization, SAML helper functions --- .../ee/services/permission/permission-fns.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 backend/src/ee/services/permission/permission-fns.ts 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 000000000..5127a31f8 --- /dev/null +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -0,0 +1,22 @@ +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?: boolean | null) { + 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 }; From 560274bde80b16d6ffca1203c14bda20fc8b3c6d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:20:04 +0100 Subject: [PATCH 087/582] Feat: Scoped JWT to organization, Add authMethod to services --- .../services/audit-log/audit-log-service.ts | 9 ++- .../ee/services/license/license-service.ts | 81 ++++++++++++------- .../saml-config/saml-config-service.ts | 7 +- backend/src/ee/services/scim/scim-service.ts | 26 ++++-- .../secret-approval-policy-service.ts | 45 +++++++++-- .../secret-approval-request-service.ts | 48 +++++++++-- .../secret-rotation-service.ts | 55 ++++++++++--- .../secret-scanning-service.ts | 45 ++++++++--- .../secret-snapshot-service.ts | 44 ++++++++-- .../services/trusted-ip/trusted-ip-service.ts | 45 +++++++++-- 10 files changed, 326 insertions(+), 79 deletions(-) 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 c4d4aabc0..1564c6dcb 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/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index a55f2edff..e81f6dc12 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/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index e9249d4aa..dc9728957 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/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index 1ca881ed0..0f3a44c12 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 9d65ec7cc..8ddadb9bf 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 b48b6bf95..b8ad89e45 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 75c19c6e9..1e1648a66 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 7066fd485..9b78da3af 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 6ec7a23d5..de2f6efcb 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 14c73db1f..ecd2b3070 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); From edbf459d043481cde3006fd4537c0a17ac1356e1 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:20:37 +0100 Subject: [PATCH 088/582] Feat: Scoped JWT to organization --- .../services/permission/permission-service.ts | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index c7dcf4b8c..e6f0f5a2b 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -11,13 +11,14 @@ 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 { 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, @@ -99,15 +100,24 @@ 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 (membership.orgId !== userOrgId) { + throw new UnauthorizedError({ name: "You are not a member of this organization" }); } + + validateOrgSAML(authMethod, membership.orgAuthEnforced); + return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; }; @@ -120,10 +130,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 + ) => { 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,28 +169,32 @@ 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 (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { + throw new BadRequestError({ name: "Custom permission not found" }); } + if (membership.orgId !== userOrgId) { + throw new UnauthorizedError({ name: "You are not a member of 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 }; }; @@ -238,11 +258,12 @@ export const permissionServiceFactory = ({ type: T, id: string, projectId: string, + actorAuthMethod: ActorAuthMethod, actorOrgId?: string ): 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>; case ActorType.IDENTITY: From 5b7562a76d4d2e4da8ac592c5aab7c28a57fa3f6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:20:58 +0100 Subject: [PATCH 089/582] Feat: Scoped JWT to organization, Add actorAuthMethod to DTO --- .../src/ee/services/saml-config/saml-config-types.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 ec7c066fc..3ccc1e743 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; + } | { type: "orgSlug"; orgSlug: string; From bd92e357291facd1cc565618d2253597c7b8ca9e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:21:16 +0100 Subject: [PATCH 090/582] Feat: Scoped JWT to organization, add actorAuthMethod to Permission types --- backend/src/lib/types/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/lib/types/index.ts b/backend/src/lib/types/index.ts index 918322d62..547e85af9 100644 --- a/backend/src/lib/types/index.ts +++ b/backend/src/lib/types/index.ts @@ -1,9 +1,10 @@ -import { ActorType } from "@app/services/auth/auth-type"; +import { ActorAuthMethod, ActorType, AuthMethod } from "@app/services/auth/auth-type"; export type TOrgPermission = { actor: ActorType; actorId: string; orgId: string; + actorAuthMethod: ActorAuthMethod; actorOrgId?: string; }; @@ -11,6 +12,7 @@ export type TProjectPermission = { actor: ActorType; actorId: string; projectId: string; + actorAuthMethod: AuthMethod | null; actorOrgId?: string; }; From dac5529b6c7a53fcebcd8083229c82c47e225cf7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:22:35 +0100 Subject: [PATCH 091/582] Feat: Scoped JWT to organization, require organization on all requests by default on JWT requests --- backend/src/server/plugins/auth/verify-auth.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/src/server/plugins/auth/verify-auth.ts b/backend/src/server/plugins/auth/verify-auth.ts index db9700867..3b3a239f7 100644 --- a/backend/src/server/plugins/auth/verify-auth.ts +++ b/backend/src/server/plugins/auth/verify-auth.ts @@ -3,21 +3,25 @@ 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[], options: { requireOrg: boolean } = { requireOrg: true }) => + (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. - // En example of this is the /v1 auth routes. - if (options.requireOrg === true && !req.permission.orgId) { - throw new UnauthorizedError({ name: `${req.url} Unauthorized Access, no organization found` }); + // 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(); From bb2413d6590aa53ad3f67341437f0f687f3ddcef Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:22:43 +0100 Subject: [PATCH 092/582] Update index.ts --- backend/src/server/routes/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index e50e75631..e75a24f6b 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -264,7 +264,7 @@ export const registerRoutes = async ( queueService }); - const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgDAL }); + const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); const userService = userServiceFactory({ userDAL }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService }); const passwordService = authPaswordServiceFactory({ From 885d1fbd7fbf494bfa1f12cf60a83cfa09ce27c7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:23:14 +0100 Subject: [PATCH 093/582] Feat: Scoped JWT to organization --- backend/src/server/routes/v2/user-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v2/user-router.ts b/backend/src/server/routes/v2/user-router.ts index 97bc3d864..12c9b703d 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 }; From 08b5975f2668e7ef8efd4216e0f2035c867a83fc Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:24:24 +0100 Subject: [PATCH 094/582] Chore: Move SAML org check to permission service --- backend/src/server/routes/v3/login-router.ts | 6 +++--- .../services/auth-token/auth-token-service.ts | 18 ++---------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/backend/src/server/routes/v3/login-router.ts b/backend/src/server/routes/v3/login-router.ts index 62002b775..4e64d6c09 100644 --- a/backend/src/server/routes/v3/login-router.ts +++ b/backend/src/server/routes/v3/login-router.ts @@ -65,9 +65,9 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { // The decoded JWT token, which contains the auth method. const decodedToken = jwt.verify(authToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; - if (decodedToken.organizationId) { - throw new UnauthorizedError({ message: "You have already selected an organization" }); - } + // if (decodedToken.organizationId) { + // throw new UnauthorizedError({ message: "You have already selected an organization" }); + // } const user = await server.services.user.getMe(decodedToken.userId); diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index a5c93e3aa..5d0011d11 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -6,8 +6,7 @@ import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; -import { AuthMethod, AuthModeJwtTokenPayload } from "../auth/auth-type"; -import { TOrgDALFactory } from "../org/org-dal"; +import { AuthModeJwtTokenPayload } from "../auth/auth-type"; import { TUserDALFactory } from "../user/user-dal"; import { TTokenDALFactory } from "./auth-token-dal"; import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types"; @@ -15,7 +14,6 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo type TAuthTokenServiceFactoryDep = { tokenDAL: TTokenDALFactory; userDAL: Pick; - orgDAL: TOrgDALFactory; }; export type TAuthTokenServiceFactory = ReturnType; @@ -56,7 +54,7 @@ export const getTokenConfig = (tokenType: TokenType) => { } }; -export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenServiceFactoryDep) => { +export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const { token, ...tkCfg } = getTokenConfig(type); const appCfg = getConfig(); @@ -144,18 +142,6 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenSer const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); - if (token.organizationId) { - const organization = await orgDAL.findById(token.organizationId); - - if (organization.authEnforced) { - const tokenAuthMode = token.authMethod; - - if (![AuthMethod.AZURE_SAML, AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML].includes(tokenAuthMode)) { - throw new UnauthorizedError({ name: "Organization enforces SAML" }); - } - } - } - return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; }; From 750a43c9788f2fa55b9cb62954e27d39e5db6f6b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:24:41 +0100 Subject: [PATCH 095/582] Feat: Scoped JWT to organization --- backend/src/services/auth/auth-login-service.ts | 14 +++++++++++--- backend/src/services/auth/auth-type.ts | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 967160f08..8124b46d0 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -1,6 +1,7 @@ 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"; @@ -89,7 +90,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: user: TUsers; ip: string; userAgent: string; - organizationId?: string; + organizationId: string | undefined; authMethod: AuthMethod; }) => { const cfg = getConfig(); @@ -178,9 +179,15 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: // let authMethod = (providerAuthToken as AuthMethod) || AuthMethod.EMAIL; let authMethod = AuthMethod.EMAIL; + let organizationId: string | undefined; if (providerAuthToken) { - authMethod = validateProviderAuthToken(providerAuthToken, email).authMethod; + 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?"); @@ -226,7 +233,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: }, ip, userAgent, - authMethod + authMethod, + organizationId }); return { token, isMfaEnabled: false, user: userEnc } as const; diff --git a/backend/src/services/auth/auth-type.ts b/backend/src/services/auth/auth-type.ts index 6ce8de1bf..6ac1fa008 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,6 +39,8 @@ export enum ActorType { // would extend to AWS, Azure, ... SCIM_CLIENT = "scimClient" } +export type ActorAuthMethod = AuthMethod | null; + export type AuthModeJwtTokenPayload = { authTokenType: AuthTokenType.ACCESS_TOKEN; authMethod: AuthMethod; From fe638ce2c1937baf6b9ec928d52abb740e748831 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:25:14 +0100 Subject: [PATCH 096/582] Feat: Scoped JWT to organization, add actorAuthMethod to services --- .../identity-project-service.ts | 38 +++- .../identity-ua/identity-ua-service.ts | 23 ++- .../src/services/identity/identity-service.ts | 34 +++- .../integration-auth-service.ts | 170 ++++++++++++++++-- .../integration/integration-service.ts | 23 ++- backend/src/services/org/org-role-service.ts | 35 +++- backend/src/services/org/org-service.ts | 107 ++++++++--- backend/src/services/org/org-types.ts | 4 +- 8 files changed, 366 insertions(+), 68 deletions(-) diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index b6f6e4343..d554c81ac 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 d375a8fa5..54a074073 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 e37a3a6dd..be79a0ba2 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,15 @@ 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 ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) @@ -132,8 +152,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 35ff27a9a..c8551df7d 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 4a6bed75f..5bf412c9b 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 fb8a57440..74c4887fb 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, + actorAuthMethod: ActorAuthMethod, actorOrgId?: string ) => { - 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, + actorAuthMethod: ActorAuthMethod, actorOrgId?: string ) => { - 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 + ) => { + 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,8 @@ 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) => { + 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 +124,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 + ) => { + 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 db6d9654d..a545739be 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 + ) => { + 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 + ) => { + 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 + ) => { + 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 + ) => { + 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 + ) => { + 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 + ) => { + 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 bd8fe2e95..a0c0b25c9 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -1,6 +1,6 @@ import { TOrgPermission } from "@app/lib/types"; -import { ActorType } from "../auth/auth-type"; +import { ActorAuthMethod, ActorType } from "../auth/auth-type"; export type TUpdateOrgMembershipDTO = { userId: string; @@ -32,6 +32,8 @@ export type TVerifyUserToOrgDTO = { export type TFindOrgMembersByEmailDTO = { actor: ActorType; + actorAuthMethod: ActorAuthMethod; + actorOrgId: string | undefined; actorId: string; orgId: string; emails: string[]; From 900facdb3652f6007e7229e3a47cedb5f7d2562e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:25:40 +0100 Subject: [PATCH 097/582] Feat: Scoped JWT to organization, add actorAuthMethod to services --- .../project-bot/project-bot-service.ts | 27 +++++- .../project-env/project-env-service.ts | 37 +++++++- .../project-key/project-key-service.ts | 35 ++++++- .../project-membership-service.ts | 55 +++++++++-- .../project-role/project-role-service.ts | 59 ++++++++++-- .../src/services/project/project-service.ts | 65 ++++++++++--- .../secret-blind-index-service.ts | 16 +++- .../secret-folder/secret-folder-service.ts | 30 +++++- .../secret-import/secret-import-service.ts | 54 +++++++++-- .../services/secret-tag/secret-tag-service.ts | 39 ++++++-- backend/src/services/secret/secret-service.ts | 93 +++++++++++++++++-- .../service-token/service-token-service.ts | 28 +++++- .../super-admin/super-admin-service.ts | 1 + .../src/services/webhook/webhook-service.ts | 57 ++++++++++-- 14 files changed, 512 insertions(+), 84 deletions(-) diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index 6e281e69d..23667ef67 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 6ebb3a3d6..2acda33c0 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 fa77760a4..70c8365ee 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 1f751ee89..defef8ad7 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,6 +164,7 @@ export const projectMembershipServiceFactory = ({ const addUsersToProjectNonE2EE = async ({ projectId, actorId, + actorAuthMethod, actor, emails, usernames, @@ -157,7 +177,7 @@ 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); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); const usernamesAndEmails = [...emails, ...usernames]; @@ -273,11 +293,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 +374,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 +408,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 b45a6e8a5..10f64bd12 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, + actorAuthMethod: ActorAuthMethod, actorOrgId?: string ) => { - 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, + actorAuthMethod: ActorAuthMethod, actorOrgId?: string ) => { - 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, + actorAuthMethod: ActorAuthMethod, actorOrgId?: string ) => { - 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 + ) => { + 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 + ) => { + const { permission, membership } = await permissionService.getUserProjectPermission( + userId, + projectId, + actorAuthMethod, + actorOrgId + ); return { permissions: packRules(permission.rules), membership }; }; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 384fd2bad..3ce487014 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -96,6 +96,7 @@ export const projectServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, workspaceName, slug: projectSlug }: TCreateProjectDTO) => { @@ -111,6 +112,7 @@ export const projectServiceFactory = ({ actor, actorId, organization.id, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); @@ -330,10 +332,16 @@ export const projectServiceFactory = ({ return results; }; - const deleteProject = async ({ actor, actorId, actorOrgId, filter }: TDeleteProjectDTO) => { + const deleteProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, filter }: TDeleteProjectDTO) => { const project = await projectDAL.findProjectByFilter(filter); - const { permission } = await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); + 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) => { @@ -356,17 +364,23 @@ export const projectServiceFactory = ({ return workspaces; }; - const getAProject = async ({ actorId, actorOrgId, filter, actor }: TGetProjectDTO) => { + const getAProject = async ({ actorId, actorOrgId, actorAuthMethod, filter, actor }: TGetProjectDTO) => { const project = await projectDAL.findProjectByFilter(filter); - await permissionService.getProjectPermission(actor, actorId, project.id, actorOrgId); + await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId); return project; }; - const updateProject = async ({ actor, actorId, actorOrgId, update, filter }: TUpdateProjectDTO) => { + 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, actorOrgId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + project.id, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(project.id, { @@ -381,25 +395,50 @@ export const projectServiceFactory = ({ actor, actorId, actorOrgId, + actorAuthMethod, autoCapitalization }: TToggleProjectAutoCapitalizationDTO) => { - 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.Settings); const updatedProject = await projectDAL.updateById(projectId, { autoCapitalization }); return updatedProject; }; - const updateName = async ({ projectId, actor, actorId, actorOrgId, name }: TUpdateProjectNameDTO) => { - 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, userPrivateKey }: TUpgradeProjectDTO) => { + const { permission, hasRole } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); @@ -423,8 +462,8 @@ export const projectServiceFactory = ({ }); }; - const getProjectUpgradeStatus = async ({ projectId, actor, actorId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectUpgradeStatus = async ({ projectId, actor, actorAuthMethod, actorId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); const project = await projectDAL.findProjectById(projectId); 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 b681266fd..55fec62fe 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,17 @@ 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, actor }: TGetProjectSecretsDTO) => { + const { hasRole } = await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod); if (!hasRole(ProjectMembershipRole.Admin)) { throw new UnauthorizedError({ message: "User must be admin" }); } @@ -50,10 +51,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 26c1c1f4f..baa1484b1 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 1beae9be6..40f9797e4 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 1007ec4c3..ed8f5fec7 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 51136e40f..f47428fc7 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 798d2ca0c..e434bd91f 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -43,6 +43,7 @@ export const serviceTokenServiceFactory = ({ name, actor, actorOrgId, + actorAuthMethod, scopes, actorId, projectId, @@ -50,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 }) => { @@ -94,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" }); @@ -102,6 +109,7 @@ export const serviceTokenServiceFactory = ({ actor, actorId, serviceToken.projectId, + actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens); @@ -122,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"]] }); diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index d76ad3ec3..07fc2e991 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 c3919cc50..4a05ad219 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); From a9c1f278a1505dc221eb98606b52a6ba0239a243 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:25:59 +0100 Subject: [PATCH 098/582] Feat: Scoped JWT to organization, add actorAuthMethod to DTO's --- backend/src/services/org/org-types.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index a0c0b25c9..55742644b 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -8,6 +8,7 @@ export type TUpdateOrgMembershipDTO = { membershipId: string; role: string; actorOrgId?: string; + actorAuthMethod: ActorAuthMethod; }; export type TDeleteOrgMembershipDTO = { @@ -15,12 +16,14 @@ export type TDeleteOrgMembershipDTO = { orgId: string; membershipId: string; actorOrgId?: string; + actorAuthMethod: ActorAuthMethod; }; export type TInviteUserToOrgDTO = { userId: string; orgId: string; actorOrgId?: string; + actorAuthMethod: ActorAuthMethod; inviteeEmail: string; }; @@ -35,6 +38,7 @@ export type TFindOrgMembersByEmailDTO = { actorAuthMethod: ActorAuthMethod; actorOrgId: string | undefined; actorId: string; + actorAuthMethod: ActorAuthMethod; orgId: string; emails: string[]; }; @@ -43,6 +47,7 @@ export type TFindAllWorkspacesDTO = { actor: ActorType; actorId: string; actorOrgId?: string; + actorAuthMethod: ActorAuthMethod; orgId: string; }; From 65776b7ab98e4e5eeeb005b844264a6ccdf2c9a7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:26:14 +0100 Subject: [PATCH 099/582] Feat: Scoped JWT to organization, actorAuthMethod to create project DTO --- backend/src/services/project/project-types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 69816882e..2b84dc1db 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -1,7 +1,7 @@ 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", @@ -21,6 +21,7 @@ export type Filter = export type TCreateProjectDTO = { actor: ActorType; + actorAuthMethod: ActorAuthMethod; actorId: string; actorOrgId?: string; orgSlug: string; From 9b1a15331a713b83afe8d6156f13fc0a1d63178a Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:26:45 +0100 Subject: [PATCH 100/582] Fix: Creating dummy workspaces --- frontend/src/components/signup/UserInfoStep.tsx | 3 ++- frontend/src/helpers/project.ts | 6 +++--- .../Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/signup/UserInfoStep.tsx b/frontend/src/components/signup/UserInfoStep.tsx index 994ad5a72..43c481763 100644 --- a/frontend/src/components/signup/UserInfoStep.tsx +++ b/frontend/src/components/signup/UserInfoStep.tsx @@ -191,9 +191,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/helpers/project.ts b/frontend/src/helpers/project.ts index 06f13dc65..33c9e5777 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 }); diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index 918a62551..b5e07286f 100644 --- a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -188,8 +188,9 @@ export const UserInfoSSOStep = ({ const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0]?.id; + const orgSlug = userOrgs[0]?.slug; const project = await ProjectService.initProject({ - organizationId: orgId, + organizationSlug: orgSlug, projectName: "Example Project" }); From 27dcb0608389e41d223c615124ff19c3a4ca4e84 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:27:02 +0100 Subject: [PATCH 101/582] Fix: Invalidate after selecting organization --- frontend/src/hooks/api/auth/queries.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index 9bc7bb034..5936408e1 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -1,9 +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, @@ -66,6 +67,7 @@ export const selectOrganization = async (data: { organizationId: string }) => { }; export const useSelectOrganization = () => { + const queryClient = useQueryClient(); return useMutation({ mutationFn: async (details: { organizationId: string }) => { const data = await selectOrganization(details); @@ -74,6 +76,9 @@ export const useSelectOrganization = () => { SecurityClient.setProviderAuthToken(""); return data; + }, + onSuccess: () => { + queryClient.invalidateQueries(organizationKeys.getUserOrganizations); } }); }; From 667fa7a9e3962c2348ed05b979bbb451f55a5b1d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:27:24 +0100 Subject: [PATCH 102/582] Chore: Optional 'invalidate' option for create org hook --- .../src/hooks/api/organization/queries.tsx | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 441aa591c..9e763c771 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 From 5f5d62a285b0138e9fb5b19f845b52afd19f268e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:27:51 +0100 Subject: [PATCH 103/582] Fix: Selecting SAML enforced organization --- .../src/pages/login/select-organization.tsx | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/login/select-organization.tsx b/frontend/src/pages/login/select-organization.tsx index d60a06fd3..464cc24a0 100644 --- a/frontend/src/pages/login/select-organization.tsx +++ b/frontend/src/pages/login/select-organization.tsx @@ -12,6 +12,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, Spinner } from "@app/components/v2"; import { useUser } from "@app/context"; import { useGetOrganizations, useLogoutUser, useSelectOrganization } from "@app/hooks/api"; +import { Organization } from "@app/hooks/api/types"; import { isLoggedIn } from "@app/reactQuery"; import { navigateUserToOrg } from "@app/views/Login/Login.utils"; @@ -44,14 +45,20 @@ export default function LoginPage() { }, [logout, router]); const handleSelectOrganization = useCallback( - async (orgId: string) => { - console.log("Selected organization: ", orgId); + async (organization: Organization) => { + if (organization.authEnforced) { + // org has an org-level auth method enabled (e.g. SAML) + // -> logout + redirect to SAML SSO - const { token } = await selectOrg.mutateAsync({ organizationId: orgId }); + await logout.mutateAsync(); + window.open(`/api/v1/sso/redirect/saml2/organizations/${organization.slug}`); + window.close(); + return; + } - console.log("Organization selected successfully", { token }); + await selectOrg.mutateAsync({ organizationId: organization.id }); - navigateUserToOrg(router, orgId); + navigateUserToOrg(router, organization.id); }, [selectOrg] ); @@ -106,23 +113,20 @@ export default function LoginPage() { {organizations.isLoading ? ( ) : ( - organizations.data - ?.concat(organizations.data) - .concat(organizations.data) - .map((org) => ( -
handleSelectOrganization(org.id)} - key={org.id} - className="group flex cursor-pointer items-center justify-between rounded-md bg-mineshaft-700 px-4 py-3 capitalize text-gray-200 shadow-md transition-colors hover:bg-mineshaft-600" - > -

{org.name}

+ organizations.data?.map((org) => ( +
handleSelectOrganization(org)} + key={org.id} + className="group flex cursor-pointer items-center justify-between rounded-md bg-mineshaft-700 px-4 py-3 capitalize text-gray-200 shadow-md transition-colors hover:bg-mineshaft-600" + > +

{org.name}

- -
- )) + +
+ )) )}
From b9986be3871ce742eaba25f5d39851a717faaf0d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:28:33 +0100 Subject: [PATCH 104/582] Fix: Creating dummy workspaces --- frontend/src/services/ProjectService.ts | 42 ++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/frontend/src/services/ProjectService.ts b/frontend/src/services/ProjectService.ts index b93aa0054..e1d0e3fe8 100644 --- a/frontend/src/services/ProjectService.ts +++ b/frontend/src/services/ProjectService.ts @@ -1,26 +1,26 @@ import { initProjectHelper } from "@app/helpers/project"; class ProjectService { - /** - * Create and initialize a new project in organization with id [organizationId] - * Note: current user should be a member of the organization - * @param {Object} obj - * @param {String} obj.organizationId - id of organization - * @param {String} obj.projectName - name of new project - * @returns {Project} project - new project - */ - static async initProject({ - organizationId, - projectName - }: { - organizationId: string; - projectName: string - }) { - return initProjectHelper({ - organizationId, - projectName - }); - } + /** + * Create and initialize a new project in organization with id [organizationId] + * Note: current user should be a member of the organization + * @param {Object} obj + * @param {String} obj.organizationId - id of organization + * @param {String} obj.projectName - name of new project + * @returns {Project} project - new project + */ + static async initProject({ + organizationSlug, + projectName + }: { + organizationSlug: string; + projectName: string; + }) { + return initProjectHelper({ + organizationSlug, + projectName + }); + } } -export default ProjectService; \ No newline at end of file +export default ProjectService; From e4dba6d5c8ce030e559282260a01a3276b69228c Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:29:17 +0100 Subject: [PATCH 105/582] Fix: Formatting and support for selecting org (line 109-122) --- .../components/PasswordStep/PasswordStep.tsx | 342 +++++++++--------- 1 file changed, 177 insertions(+), 165 deletions(-) diff --git a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx index 6924da3e7..32e5d29c9 100644 --- a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; -import { useRouter } from "next/router" -import axios from "axios" +import { useRouter } from "next/router"; +import axios from "axios"; import jwt_decode from "jwt-decode"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; @@ -10,179 +10,191 @@ import attemptCliLogin from "@app/components/utilities/attemptCliLogin"; import attemptLogin from "@app/components/utilities/attemptLogin"; import { Button, Input } from "@app/components/v2"; import { useUpdateUserAuthMethods } from "@app/hooks/api"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; -import { navigateUserToOrg } from "../../Login.utils"; +import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils"; -type Props = { - providerAuthToken: string; - email: string; - password: string; - setPassword: (password: string) => void; - setStep: (step: number) => void; -} +type Props = { + providerAuthToken: string; + email: string; + password: string; + setPassword: (password: string) => void; + setStep: (step: number) => void; +}; export const PasswordStep = ({ - providerAuthToken, - email, - password, - setPassword, - setStep, + providerAuthToken, + email, + password, + setPassword, + setStep }: Props) => { - const { createNotification } = useNotificationContext(); - const [isLoading, setIsLoading] = useState(false); - const { t } = useTranslation(); - const router = useRouter(); - const { mutateAsync } = useUpdateUserAuthMethods(); - - const { - callbackPort, - isLinkingRequired, - authMethod, - organizationId - } = jwt_decode(providerAuthToken) as any; - - const handleLogin = async (e:React.FormEvent) => { - e.preventDefault() - try { - setIsLoading(true); - - if (callbackPort) { - // attemptCliLogin - const isCliLoginSuccessful = await attemptCliLogin({ - email, - password, - providerAuthToken - }) + const { createNotification } = useNotificationContext(); + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); + const router = useRouter(); + const { mutateAsync } = useUpdateUserAuthMethods(); - if (isCliLoginSuccessful && isCliLoginSuccessful.success) { + const { callbackPort, isLinkingRequired, authMethod, organizationId } = jwt_decode( + providerAuthToken + ) as any; - if (isCliLoginSuccessful.mfaEnabled) { - // case: login requires MFA step - setStep(2); - setIsLoading(false); - return; - } - // case: login was successful - const cliUrl = `http://127.0.0.1:${callbackPort}/` + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + try { + setIsLoading(true); - // send request to server endpoint - const instance = axios.create() - await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse }) + if (callbackPort) { + // attemptCliLogin + const isCliLoginSuccessful = await attemptCliLogin({ + email, + password, + providerAuthToken + }); - // cli page - router.push("/cli-redirect"); - - // on success, router.push to cli Login Successful page - } - } else { - const loginAttempt = await attemptLogin({ - email, - password, - providerAuthToken, - }); - - if (loginAttempt && loginAttempt.success) { - // case: login was successful - - if (loginAttempt.mfaEnabled) { - // TODO: deal with MFA - // case: login requires MFA step - setIsLoading(false); - setStep(2); - return; - } - - // case: login does not require MFA step - setIsLoading(false); - createNotification({ - text: "Successfully logged in", - type: "success" - }); - - if (isLinkingRequired) { - const user = await fetchUserDetails(); - const newAuthMethods = [...user.authMethods, authMethod] - await mutateAsync({ - authMethods: newAuthMethods - }); - } - - await navigateUserToOrg(router, organizationId); - } - } - } catch (err) { + if (isCliLoginSuccessful && isCliLoginSuccessful.success) { + if (isCliLoginSuccessful.mfaEnabled) { + // case: login requires MFA step + setStep(2); setIsLoading(false); - createNotification({ - text: "Login unsuccessful. Double-check your master password and try again.", - type: "error" - }); - console.error(err); + return; + } + // case: login was successful + const cliUrl = `http://127.0.0.1:${callbackPort}/`; + + // send request to server endpoint + const instance = axios.create(); + await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse }); + + // cli page + router.push("/cli-redirect"); + + // on success, router.push to cli Login Successful page } - }; - - return ( -
0) { + navigateUserToSelectOrg(router); + } else { + await navigateUserToOrg(router); + } + } + } + } + } catch (err) { + setIsLoading(false); + createNotification({ + text: "Login unsuccessful. Double-check your master password and try again.", + type: "error" + }); + console.error(err); + } + }; + + return ( + +
+

+ {isLinkingRequired ? "Link your account" : "What's your Infisical password?"} +

+ {isLinkingRequired && ( +
+ + An existing account without this SSO authentication method enabled was found under the + same email. Login with your password to link the account. + +
+ )} +
+
+
+ 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")} - -
-
- -
-
- ); -} \ No newline at end of file + {t("login.login")} + +
+
+ + Infisical Master Password serves as a decryption mechanism so that even Google is not able + to access your secrets. + + + + {t("login.forgot-password")} + + +
+
+ +
+ + ); +}; From 835c36d1613d7ad9f1985c3acd6a49a414787c5c Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:29:29 +0100 Subject: [PATCH 106/582] Fix: Select org after creation --- .../src/views/Org/components/CreateOrgModal.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/Org/components/CreateOrgModal.tsx b/frontend/src/views/Org/components/CreateOrgModal.tsx index b806b2089..e7a17bb0b 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" From f59a75d79093ac854d4b5b93bea91f7eb5f1e9a5 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:49:02 +0100 Subject: [PATCH 107/582] Feat: Org Scoped JWT's, remove inline service --- backend/src/server/routes/v3/login-router.ts | 40 +++----------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/backend/src/server/routes/v3/login-router.ts b/backend/src/server/routes/v3/login-router.ts index 4e64d6c09..e13198440 100644 --- a/backend/src/server/routes/v3/login-router.ts +++ b/backend/src/server/routes/v3/login-router.ts @@ -1,10 +1,7 @@ -import jwt from "jsonwebtoken"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; -import { UnauthorizedError } from "@app/lib/errors"; import { authRateLimit } from "@app/server/config/rateLimiter"; -import { AuthModeJwtTokenPayload } from "@app/services/auth/auth-type"; export const registerLoginRouter = async (server: FastifyZodProvider) => { server.route({ @@ -55,36 +52,11 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { }, handler: async (req, res) => { const cfg = getConfig(); - - if (!req.headers.authorization) throw new UnauthorizedError({ name: "Authorization header is required" }); - if (!req.headers["user-agent"]) throw new UnauthorizedError({ name: "user agent header is required" }); - - const userAgent = req.headers["user-agent"]; - const authToken = req.headers.authorization.slice(7); // slice of after Bearer - - // The decoded JWT token, which contains the auth method. - const decodedToken = jwt.verify(authToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; - - // if (decodedToken.organizationId) { - // throw new UnauthorizedError({ message: "You have already selected an organization" }); - // } - - const user = await server.services.user.getMe(decodedToken.userId); - - // Check if the user actually has access to the specified organization. - const userOrgs = await server.services.org.findAllOrganizationOfUser(user.id); - - if (!userOrgs.some((org) => org.id === req.body.organizationId)) { - throw new UnauthorizedError({ message: "User does not have access to the organization" }); - } - - await server.services.authToken.clearTokenSessionById(decodedToken.userId, decodedToken.tokenVersionId); - const tokens = await server.services.login.generateUserTokens({ - authMethod: decodedToken.authMethod, - user, - userAgent, - ip: req.realIp, - organizationId: req.body.organizationId + const tokens = await server.services.login.selectOrganization({ + userAgentHeader: req.headers["user-agent"], + authorizationHeader: req.headers.authorization, + organizationId: req.body.organizationId, + ipAddress: req.realIp }); void res.setCookie("jid", tokens.refresh, { @@ -93,8 +65,6 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { sameSite: "strict", secure: cfg.HTTPS_ENABLED }); - - return { token: tokens.access }; } }); From 6d60413593d9c0fc21530fb68171a481163b777c Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:49:15 +0100 Subject: [PATCH 108/582] Feat: Org Scoped JWT's, service handler --- .../src/services/auth/auth-login-service.ts | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 8124b46d0..5c774a13c 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -4,11 +4,13 @@ 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"; @@ -18,16 +20,24 @@ import { TOauthLoginDTO, TVerifyMfaTokenDTO } from "./auth-login-type"; -import { AuthMethod, AuthModeMfaJwtTokenPayload, 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 @@ -240,6 +250,53 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: return { token, isMfaEnabled: false, user: userEnc } as const; }; + const selectOrganization = async ({ + userAgentHeader, + authorizationHeader, + ipAddress, + organizationId + }: { + userAgentHeader: string | undefined; + authorizationHeader: string | undefined; + ipAddress: string; + organizationId: string; + }) => { + const cfg = getConfig(); + + if (!authorizationHeader) throw new UnauthorizedError({ name: "Authorization header is required" }); + if (!userAgentHeader) throw new UnauthorizedError({ name: "user agent header is required" }); + + const userAgent = userAgentHeader; + const authToken = authorizationHeader.slice(7); // slice of after Bearer + + // The decoded JWT token, which contains the auth method. + const decodedToken = jwt.verify(authToken, 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: "Get Me" }); + + // Check if the user actually has access to the specified organization. + const userOrgs = await orgDAL.findAllOrgsByUserId(user.id); + + if (!userOrgs.some((org) => org.id === organizationId)) { + 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 @@ -356,6 +413,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: oauth2Login, resendMfaToken, verifyMfaToken, + selectOrganization, generateUserTokens }; }; From 92fd2d080d67872c3a35f9f5bd6b8d6e5994a087 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:41:34 +0100 Subject: [PATCH 109/582] Fix: ActorType unresolved --- backend/src/@types/fastify.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 8c048481c..0b59aa8a9 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 { ActorAuthMethod } 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"; From 7e2685d60448d86dc1a0050f34f911b889cd1fd8 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:41:49 +0100 Subject: [PATCH 110/582] Fix: Better type checking --- backend/src/ee/services/permission/permission-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index e6f0f5a2b..737571fad 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -135,7 +135,7 @@ export const permissionServiceFactory = ({ id: string, orgId: string, authMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { switch (type) { case ActorType.USER: @@ -259,7 +259,7 @@ export const permissionServiceFactory = ({ id: string, projectId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ): Promise> => { switch (type) { case ActorType.USER: From 1e20d780ec9f6e3e99736dc288590b7e1c2eb72a Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:42:10 +0100 Subject: [PATCH 111/582] Feat: Org scoped JWT's --- backend/src/server/routes/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index e75a24f6b..72208cc72 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -266,7 +266,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, From ad0504e9571796cd09e548c292cf6f815cacc2df Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:42:34 +0100 Subject: [PATCH 112/582] Fix: Add missing actor org ID --- backend/src/server/routes/v2/project-membership-router.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/server/routes/v2/project-membership-router.ts b/backend/src/server/routes/v2/project-membership-router.ts index b19dc53a1..51bae6d2b 100644 --- a/backend/src/server/routes/v2/project-membership-router.ts +++ b/backend/src/server/routes/v2/project-membership-router.ts @@ -30,6 +30,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider 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 From 0bd3f32c6ec29ec9ce8ca7774cfe6b6a300ccf67 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:42:41 +0100 Subject: [PATCH 113/582] Fix: Add missing actor org ID --- backend/src/server/routes/v2/project-router.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index c4fecf068..1344c6530 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -84,7 +84,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { params: z.object({ projectId: z.string().trim() }), - body: z.object({ userPrivateKey: z.string().trim() }), @@ -96,6 +95,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { 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, @@ -122,6 +122,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { 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 From eace4f1bdcbed35e1adbf7c713449510ce71cc07 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:42:54 +0100 Subject: [PATCH 114/582] Fix: Return access token --- backend/src/server/routes/v3/login-router.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/server/routes/v3/login-router.ts b/backend/src/server/routes/v3/login-router.ts index e13198440..ea6b3f52e 100644 --- a/backend/src/server/routes/v3/login-router.ts +++ b/backend/src/server/routes/v3/login-router.ts @@ -65,6 +65,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { sameSite: "strict", secure: cfg.HTTPS_ENABLED }); + + return { token: tokens.access }; } }); From f0e3c9a4b2a0f0d3f2e367d3400fd1edbcda9351 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:43:01 +0100 Subject: [PATCH 115/582] Update auth-type.ts --- backend/src/services/auth/auth-type.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/services/auth/auth-type.ts b/backend/src/services/auth/auth-type.ts index 6ac1fa008..a3c53658c 100644 --- a/backend/src/services/auth/auth-type.ts +++ b/backend/src/services/auth/auth-type.ts @@ -39,6 +39,7 @@ 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 = { From c11c5ec85ed30cdd1ca32bf5ae649b8c8da8473e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:43:20 +0100 Subject: [PATCH 116/582] Fix: Add actor org ID --- backend/src/services/identity/identity-service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index be79a0ba2..cdea45631 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -142,7 +142,8 @@ export const identityServiceFactory = ({ ActorType.IDENTITY, id, identityOrgMembership.orgId, - actorAuthMethod + actorAuthMethod, + actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) From 4d4887059a1b4e38db4fefba4620f1232b47f7d0 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:43:41 +0100 Subject: [PATCH 117/582] Chore: Remove unused code --- backend/src/services/org/org-service.ts | 1 - backend/src/services/org/org-types.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index a545739be..916029648 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -33,7 +33,6 @@ import { TOrgRoleDALFactory } from "./org-role-dal"; import { TDeleteOrgMembershipDTO, TFindAllWorkspacesDTO, - TFindOrgMembersByEmailDTO, TInviteUserToOrgDTO, TUpdateOrgDTO, TUpdateOrgMembershipDTO, diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index 55742644b..af8fbd95f 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -35,7 +35,6 @@ export type TVerifyUserToOrgDTO = { export type TFindOrgMembersByEmailDTO = { actor: ActorType; - actorAuthMethod: ActorAuthMethod; actorOrgId: string | undefined; actorId: string; actorAuthMethod: ActorAuthMethod; From f5f20fbdca0aa1fe525b96192eac9e5717b2f60d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:44:03 +0100 Subject: [PATCH 118/582] Fix: Add missing actor org ID to permission check --- .../project-membership/project-membership-service.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index defef8ad7..059c93210 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -166,6 +166,7 @@ export const projectMembershipServiceFactory = ({ actorId, actorAuthMethod, actor, + actorOrgId, emails, usernames, sendEmails = true @@ -177,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, actorAuthMethod); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); const usernamesAndEmails = [...emails, ...usernames]; From e89503f00fd6b9c093d53faadd5a3d4d3eba333b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:44:23 +0100 Subject: [PATCH 119/582] Fix: Add missing actor auth method to permission checks --- .../src/services/project/project-service.ts | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 3ce487014..71703a50c 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -432,12 +432,20 @@ export const projectServiceFactory = ({ return updatedProject; }; - const upgradeProject = async ({ projectId, actor, actorId, actorAuthMethod, userPrivateKey }: TUpgradeProjectDTO) => { + const upgradeProject = async ({ + projectId, + actor, + actorId, + actorAuthMethod, + actorOrgId, + userPrivateKey + }: TUpgradeProjectDTO) => { const { permission, hasRole } = await permissionService.getProjectPermission( actor, actorId, projectId, - actorAuthMethod + actorAuthMethod, + actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); @@ -462,8 +470,20 @@ export const projectServiceFactory = ({ }); }; - const getProjectUpgradeStatus = async ({ projectId, actor, actorAuthMethod, actorId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod); + 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); From 4aef8ab8ee3f878573f69d901d6c4703d7da9e1d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:44:44 +0100 Subject: [PATCH 120/582] Fix: Include actor org id --- .../secret-blind-index-service.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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 55fec62fe..bf2728e95 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 @@ -37,8 +37,20 @@ export const secretBlindIndexServiceFactory = ({ return Number(secretCount); }; - const getProjectSecrets = async ({ projectId, actorId, actorAuthMethod, actor }: TGetProjectSecretsDTO) => { - const { hasRole } = await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod); + 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" }); } From ba22a7fca660c6f74ac1ea0ed43e703f258f3f36 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:45:39 +0100 Subject: [PATCH 121/582] Chore: Remove redundant lint comment --- frontend/src/layouts/AppLayout/AppLayout.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index c09fd000f..2785d82a1 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -111,7 +111,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(); From 354bac486a114d2e84d9396738b0295be8156b20 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:45:59 +0100 Subject: [PATCH 122/582] Fix: Don't allow org select screen when token already has an organization ID --- frontend/src/pages/login/select-organization.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/login/select-organization.tsx b/frontend/src/pages/login/select-organization.tsx index 464cc24a0..03e98af3a 100644 --- a/frontend/src/pages/login/select-organization.tsx +++ b/frontend/src/pages/login/select-organization.tsx @@ -8,12 +8,13 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import jwt_decode from "jwt-decode"; import { Button, Spinner } from "@app/components/v2"; import { useUser } from "@app/context"; import { useGetOrganizations, useLogoutUser, useSelectOrganization } from "@app/hooks/api"; import { Organization } from "@app/hooks/api/types"; -import { isLoggedIn } from "@app/reactQuery"; +import { getAuthToken, isLoggedIn } from "@app/reactQuery"; import { navigateUserToOrg } from "@app/views/Login/Login.utils"; const LoadingScreen = () => { @@ -64,6 +65,16 @@ export default function LoginPage() { ); useEffect(() => { + const authToken = getAuthToken(); + + if (authToken) { + const decodedJwt = jwt_decode(authToken) as any; + + if (decodedJwt?.organizationId) { + navigateUserToOrg(router, decodedJwt.organizationId); + } + } + if (!isLoggedIn()) { router.push("/login"); } From 2bd9914373308c5400edb30302fe349220cf7889 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:55:40 +0100 Subject: [PATCH 123/582] Fix: Add missing actorOrgId to service handlers --- backend/src/ee/routes/v1/license-router.ts | 1 + backend/src/server/routes/v2/project-router.ts | 1 + backend/src/server/routes/v2/service-token-router.ts | 1 + backend/src/server/routes/v3/secret-router.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index 4b0cf9a60..bd3fdca18 100644 --- a/backend/src/ee/routes/v1/license-router.ts +++ b/backend/src/ee/routes/v1/license-router.ts @@ -68,6 +68,7 @@ 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 }); diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 1344c6530..e86762486 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -164,6 +164,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const project = await server.services.project.createProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, orgSlug: req.body.organizationSlug, workspaceName: req.body.projectName, diff --git a/backend/src/server/routes/v2/service-token-router.ts b/backend/src/server/routes/v2/service-token-router.ts index a3970d4c0..c4d08d104 100644 --- a/backend/src/server/routes/v2/service-token-router.ts +++ b/backend/src/server/routes/v2/service-token-router.ts @@ -47,6 +47,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => const { serviceToken, user } = await server.services.serviceToken.getServiceToken({ actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, actor: req.permission.type }); diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 9daa884f0..2ce8f5463 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -711,6 +711,7 @@ 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, From c3a56f469a154a2f6ff6cd976f426791e1c632a9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:56:01 +0100 Subject: [PATCH 124/582] Fix: Better type checking --- .../ee/services/saml-config/saml-config-types.ts | 2 +- backend/src/lib/types/index.ts | 4 ++-- backend/src/services/org/org-role-service.ts | 15 ++++++++++----- backend/src/services/org/org-service.ts | 12 ++++++------ backend/src/services/org/org-types.ts | 8 ++++---- .../services/project-role/project-role-service.ts | 10 +++++----- backend/src/services/project/project-types.ts | 4 ++-- 7 files changed, 30 insertions(+), 25 deletions(-) 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 3ccc1e743..9aedf5d19 100644 --- a/backend/src/ee/services/saml-config/saml-config-types.ts +++ b/backend/src/ee/services/saml-config/saml-config-types.ts @@ -32,7 +32,7 @@ export type TGetSamlCfgDTO = actor: ActorType; actorId: string; actorAuthMethod: ActorAuthMethod; - actorOrgId?: string; + actorOrgId: string | undefined; } | { type: "orgSlug"; diff --git a/backend/src/lib/types/index.ts b/backend/src/lib/types/index.ts index 547e85af9..a81517c94 100644 --- a/backend/src/lib/types/index.ts +++ b/backend/src/lib/types/index.ts @@ -5,7 +5,7 @@ export type TOrgPermission = { actorId: string; orgId: string; actorAuthMethod: ActorAuthMethod; - actorOrgId?: string; + actorOrgId: string | undefined; }; export type TProjectPermission = { @@ -13,7 +13,7 @@ export type TProjectPermission = { actorId: string; projectId: string; actorAuthMethod: AuthMethod | null; - actorOrgId?: string; + actorOrgId: string | undefined; }; export type RequiredKeys = { diff --git a/backend/src/services/org/org-role-service.ts b/backend/src/services/org/org-role-service.ts index 74c4887fb..70c54ff18 100644 --- a/backend/src/services/org/org-role-service.ts +++ b/backend/src/services/org/org-role-service.ts @@ -28,7 +28,7 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol orgId: string, data: Omit, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Role); @@ -48,7 +48,7 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol roleId: string, data: Omit, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Role); @@ -70,7 +70,7 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol orgId: string, roleId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role); @@ -80,7 +80,12 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return deletedRole; }; - const listRoles = async (userId: string, orgId: string, actorAuthMethod: ActorAuthMethod, actorOrgId?: string) => { + 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 }); @@ -128,7 +133,7 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol userId: string, orgId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission, membership } = await permissionService.getUserOrgPermission( userId, diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 916029648..bab3b9869 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -82,7 +82,7 @@ export const orgServiceFactory = ({ userId: string, orgId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); const org = await orgDAL.findOrgById(orgId); @@ -103,7 +103,7 @@ export const orgServiceFactory = ({ userId: string, orgId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); @@ -330,7 +330,7 @@ export const orgServiceFactory = ({ userId: string, orgId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { membership } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); if ((membership.role as OrgMembershipRole) !== OrgMembershipRole.Admin) @@ -613,7 +613,7 @@ export const orgServiceFactory = ({ userId: string, orgId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount); @@ -626,7 +626,7 @@ export const orgServiceFactory = ({ orgId: string, email: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount); @@ -647,7 +647,7 @@ export const orgServiceFactory = ({ orgId: string, id: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount); diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index af8fbd95f..c811f9bc0 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -7,7 +7,7 @@ export type TUpdateOrgMembershipDTO = { orgId: string; membershipId: string; role: string; - actorOrgId?: string; + actorOrgId: string | undefined; actorAuthMethod: ActorAuthMethod; }; @@ -15,14 +15,14 @@ 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; }; @@ -45,7 +45,7 @@ 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-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index 10f64bd12..5c8ecdcff 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -30,7 +30,7 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: projectId: string, data: Omit, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getProjectPermission( actor, @@ -57,7 +57,7 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: roleId: string, data: Omit, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getProjectPermission( actor, @@ -86,7 +86,7 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: projectId: string, roleId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getProjectPermission( actor, @@ -107,7 +107,7 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: actorId: string, projectId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission } = await permissionService.getProjectPermission( actor, @@ -172,7 +172,7 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: userId: string, projectId: string, actorAuthMethod: ActorAuthMethod, - actorOrgId?: string + actorOrgId: string | undefined ) => { const { permission, membership } = await permissionService.getUserProjectPermission( userId, diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 2b84dc1db..5b2b635f2 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -33,7 +33,7 @@ export type TDeleteProjectBySlugDTO = { slug: string; actor: ActorType; actorId: string; - actorOrgId?: string; + actorOrgId: string | undefined; }; export type TGetProjectDTO = { @@ -60,7 +60,7 @@ export type TDeleteProjectDTO = { filter: Filter; actor: ActorType; actorId: string; - actorOrgId?: string; + actorOrgId: string | undefined; } & Omit; export type TUpgradeProjectDTO = { From 926f7199677991f22a23671197218c636d0b029d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:41:48 +0100 Subject: [PATCH 125/582] Fix: Rebase fixes --- backend/src/services/org/org-service.ts | 1 + frontend/src/views/Login/Login.tsx | 67 +++++++++++-------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index bab3b9869..64cd51e09 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -33,6 +33,7 @@ import { TOrgRoleDALFactory } from "./org-role-dal"; import { TDeleteOrgMembershipDTO, TFindAllWorkspacesDTO, + TFindOrgMembersByEmailDTO, TInviteUserToOrgDTO, TUpdateOrgDTO, TUpdateOrgMembershipDTO, diff --git a/frontend/src/views/Login/Login.tsx b/frontend/src/views/Login/Login.tsx index 0f3586238..31695869f 100644 --- a/frontend/src/views/Login/Login.tsx +++ b/frontend/src/views/Login/Login.tsx @@ -5,12 +5,8 @@ import axios from "axios"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; import { getAuthToken, isLoggedIn } from "@app/reactQuery"; -import { - InitialStep, - LDAPStep, - MFAStep, - SAMLSSOStep} from "./components"; -import { navigateUserToOrg } from "./Login.utils"; +import { InitialStep, LDAPStep, MFAStep, SAMLSSOStep } from "./components"; +import { navigateUserToSelectOrg } from "./Login.utils"; export const Login = () => { const router = useRouter(); @@ -52,38 +48,33 @@ export const Login = () => { } }, []); - const renderView = () => { - switch (step) { - case 0: - return ( - - ); - case 1: - return ( - - ); - case 2: - return ( - - ); - case 3: - return ( - - ); - default: - return
; - } + const renderView = () => { + switch (step) { + case 0: + return ( + + ); + case 1: + return ( + + ); + case 2: + return ; + case 3: + return ; + default: + return
; } }; From 8ff37e3ec9e6c9e60078efbbba2f270554476754 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:43:44 +0100 Subject: [PATCH 126/582] Fix: Rebase LDAP fixes --- backend/src/ee/routes/v1/ldap-router.ts | 3 +++ .../services/ldap-config/ldap-config-service.ts | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index de472ff29..d160f97c6 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/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index e9ae0264a..0316052cd 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 From 214894c88b673d4beb07b08f3bfc4e45de1bcc96 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:39:41 +0100 Subject: [PATCH 127/582] Chore: Export Cli login interface --- .../components/utilities/attemptCliLogin.ts | 226 +++++++++--------- 1 file changed, 110 insertions(+), 116 deletions(-) diff --git a/frontend/src/components/utilities/attemptCliLogin.ts b/frontend/src/components/utilities/attemptCliLogin.ts index b8812918a..e95f5bf88 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 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 + }); - 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 - 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 - 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); - // 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 - 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); - // 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 + }); - const privateKey = await KeyService.decryptPrivateKey({ - encryptionVersion, - encryptedPrivateKey, - iv, - tag, - password, - salt, - protectedKey, - protectedKeyIV, - protectedKeyTag - }); + saveTokenToLocalStorage({ + publicKey, + encryptedPrivateKey, + iv, + tag, + privateKey + }); - 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); - } + 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; From b80579fdef3d60592fd297d2f8395a0458b5f722 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:39:47 +0100 Subject: [PATCH 128/582] Update queries.tsx --- frontend/src/hooks/api/auth/queries.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index 5936408e1..4d05fb963 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -44,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({ From 9eb2a74bdf3eefb90e7f995d19fbf8274f8d8af4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:40:01 +0100 Subject: [PATCH 129/582] Feat: Org scoped JWT's, CLI support --- .../src/pages/login/select-organization.tsx | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/login/select-organization.tsx b/frontend/src/pages/login/select-organization.tsx index 03e98af3a..867f5a9dd 100644 --- a/frontend/src/pages/login/select-organization.tsx +++ b/frontend/src/pages/login/select-organization.tsx @@ -8,8 +8,11 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import axios from "axios"; import jwt_decode from "jwt-decode"; +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { IsCliLoginSuccessful } from "@app/components/utilities/attemptCliLogin"; import { Button, Spinner } from "@app/components/v2"; import { useUser } from "@app/context"; import { useGetOrganizations, useLogoutUser, useSelectOrganization } from "@app/hooks/api"; @@ -34,6 +37,10 @@ export default function LoginPage() { const selectOrg = useSelectOrganization(); const { user, isLoading: userLoading } = useUser(); + const { createNotification } = useNotificationContext(); + + const queryParams = new URLSearchParams(window.location.search); + const logout = useLogoutUser(true); const handleLogout = useCallback(async () => { try { @@ -57,17 +64,59 @@ export default function LoginPage() { return; } - await selectOrg.mutateAsync({ organizationId: organization.id }); + const { token } = await selectOrg.mutateAsync({ organizationId: organization.id }); - navigateUserToOrg(router, organization.id); + const callbackPort = queryParams.get("callback_port"); + + if (callbackPort) { + const privateKey = localStorage.getItem("PRIVATE_KEY"); + + if (!privateKey) { + createNotification({ + text: "Private key not found", + type: "error" + }); + } + + if (!user.email) { + createNotification({ + text: "User email not found", + type: "error" + }); + } + + if (!token) { + createNotification({ + text: "No token found", + type: "error" + }); + } + + const payload = { + JTWToken: token, + email: user.email, + privateKey + } as IsCliLoginSuccessful["loginResponse"]; + + console.log("sending to cli", payload); + + // send request to server endpoint + const instance = axios.create(); + await instance.post(`http://127.0.0.1:${callbackPort}/`, payload); + // cli page + router.push("/cli-redirect"); + } else { + navigateUserToOrg(router, organization.id); + } }, [selectOrg] ); useEffect(() => { const authToken = getAuthToken(); + const callbackPort = queryParams.get("callback_port"); - if (authToken) { + if (authToken && !callbackPort) { const decodedJwt = jwt_decode(authToken) as any; if (decodedJwt?.organizationId) { From 46eea972f75fae4489eb20e972aa6d36c73ff408 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:40:14 +0100 Subject: [PATCH 130/582] Feat: Org scoped JWT's CLI support --- .../components/InitialStep/InitialStep.tsx | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index cd33a1465..aebe4e507 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -5,7 +5,6 @@ import { useRouter } from "next/router"; import { faGithub, faGitlab, faGoogle } from "@fortawesome/free-brands-svg-icons"; import { faLock } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import axios from "axios"; import Error from "@app/components/basic/Error"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; @@ -57,17 +56,14 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: setIsLoading(false); return; } - // case: login was successful - const cliUrl = `http://127.0.0.1:${callbackPort}/`; - // send request to server endpoint - const instance = axios.create(); - await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse }); - - // cli page - router.push("/cli-redirect"); - - // on success, router.push to cli Login Successful page + navigateUserToSelectOrg(router, callbackPort!); + } else { + setLoginError(true); + createNotification({ + text: "CLI login unsuccessful. Double-check your credentials and try again.", + type: "error" + }); } } else { const isLoginSuccessful = await attemptLogin({ @@ -85,7 +81,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: return; } - await navigateUserToSelectOrg(router); + navigateUserToSelectOrg(router); // case: login does not require MFA step createNotification({ From 9bbba92768b3c5894e62d363d06bc5e7f2717db6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:40:28 +0100 Subject: [PATCH 131/582] Feat: Org scoped JWT's CLI support --- .../components/PasswordStep/PasswordStep.tsx | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx index 32e5d29c9..7c819a1fd 100644 --- a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx @@ -10,6 +10,7 @@ import attemptCliLogin from "@app/components/utilities/attemptCliLogin"; import attemptLogin from "@app/components/utilities/attemptLogin"; import { Button, Input } from "@app/components/v2"; import { useUpdateUserAuthMethods } from "@app/hooks/api"; +import { selectOrganization } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; @@ -60,17 +61,39 @@ export const PasswordStep = ({ setIsLoading(false); return; } - // case: login was successful const cliUrl = `http://127.0.0.1:${callbackPort}/`; - // send request to server endpoint - const instance = axios.create(); - await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse }); + // case: organization ID is present from the provider auth token -- select the org and use the new jwt token in the CLI, then navigate to the org + if (organizationId) { + const { token: newJwtToken } = await selectOrganization({ organizationId }); - // cli page - router.push("/cli-redirect"); + console.log( + "organization id was present. new JWT token to be used in CLI:", + newJwtToken + ); - // on success, router.push to cli Login Successful page + const instance = axios.create(); + await instance.post(cliUrl, { + ...isCliLoginSuccessful.loginResponse, + JTWToken: newJwtToken + }); + + await navigateUserToOrg(router, organizationId); + } + // case: no organization ID is present -- navigate to the select org page IF the user has any orgs + // if the user has no orgs, navigate to the create org page + else { + const userOrgs = await fetchOrganizations(); + + // case: user has orgs, so we navigate the user to select an org + if (userOrgs.length > 0) { + navigateUserToSelectOrg(router, callbackPort); + } + // case: no orgs found, so we navigate the user to create an org + else { + await navigateUserToOrg(router); + } + } } } else { const loginAttempt = await attemptLogin({ From 22b2fb4c988a79f5d8adfe68b89fa3fd580dbe28 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:40:46 +0100 Subject: [PATCH 132/582] Feat: Org scoped JWT's CLI support --- frontend/src/views/Login/Login.tsx | 32 ++++++++---------------- frontend/src/views/Login/Login.utils.tsx | 11 ++++++-- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/frontend/src/views/Login/Login.tsx b/frontend/src/views/Login/Login.tsx index 31695869f..307ef2e71 100644 --- a/frontend/src/views/Login/Login.tsx +++ b/frontend/src/views/Login/Login.tsx @@ -1,9 +1,7 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/router"; -import axios from "axios"; -import { fetchUserDetails } from "@app/hooks/api/users/queries"; -import { getAuthToken, isLoggedIn } from "@app/reactQuery"; +import { isLoggedIn } from "@app/reactQuery"; import { InitialStep, LDAPStep, MFAStep, SAMLSSOStep } from "./components"; import { navigateUserToSelectOrg } from "./Login.utils"; @@ -18,33 +16,23 @@ export const Login = () => { useEffect(() => { // TODO(akhilmhdh): workspace will be controlled by a workspace context - const redirectToDashboard = async () => { + const handleRedirects = async () => { // TODO(daniel): Move this to select-organization page. try { - // user details - const userDetails = await fetchUserDetails(); - // send details back to client - - if (queryParams && queryParams.get("callback_port")) { - const callbackPort = queryParams.get("callback_port"); - - // send post request to cli with details - const cliUrl = `http://127.0.0.1:${callbackPort}/`; - const instance = axios.create(); - await instance.post(cliUrl, { - email: userDetails.email, - privateKey: localStorage.getItem("PRIVATE_KEY"), - JTWToken: getAuthToken() - }); + const callbackPort = queryParams?.get("callback_port"); + // case: a callback port is set, meaning it's a cli login request: redirect to select org with callback port + if (callbackPort) { + navigateUserToSelectOrg(router, callbackPort); + } else { + // case: no callback port, meaning it's a regular login request: redirect to select org + navigateUserToSelectOrg(router); } - - navigateUserToSelectOrg(router); } catch (error) { console.log("Error - Not logged in yet"); } }; if (isLoggedIn()) { - redirectToDashboard(); + handleRedirects(); } }, []); diff --git a/frontend/src/views/Login/Login.utils.tsx b/frontend/src/views/Login/Login.utils.tsx index 4615d8f27..b6e3c1a10 100644 --- a/frontend/src/views/Login/Login.utils.tsx +++ b/frontend/src/views/Login/Login.utils.tsx @@ -27,7 +27,14 @@ export const navigateUserToOrg = async (router: NextRouter, organizationId?: str } }; -export const navigateUserToSelectOrg = (router: NextRouter) => { +export const navigateUserToSelectOrg = (router: NextRouter, cliCallbackPort?: string) => { queryClient.invalidateQueries(userKeys.getUser); - router.push("/login/select-organization", undefined, { shallow: true }); + + let redirectTo = "/login/select-organization"; + + if (cliCallbackPort) { + redirectTo += `?callback_port=${cliCallbackPort}`; + } + + router.push(redirectTo, undefined, { shallow: true }); }; From 771498b817ec30902a0e1303bf7cc862abc0ca5e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:38:48 +0100 Subject: [PATCH 133/582] Update inject-permission.ts --- backend/src/server/plugins/auth/inject-permission.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index b76c735f0..92fbfff1d 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -8,10 +8,6 @@ export const injectPermission = fp(async (server) => { server.addHook("onRequest", async (req) => { if (!req.auth) return; - // if (!req.auth.authMethod) { - // throw new Error("THIS SHOULD NOT HAPPEN"); - // } - if (req.auth.actor === ActorType.USER) { req.permission = { type: ActorType.USER, From 41323f205dade6d6d669d38d3c525e5aa466e24f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:39:29 +0100 Subject: [PATCH 134/582] Fix: MFA --- backend/src/server/routes/v2/mfa-router.ts | 3 +++ backend/src/services/auth/auth-login-service.ts | 4 ++-- backend/src/services/auth/auth-login-type.ts | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index 2c9465aa8..f45c916f4 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -68,12 +68,15 @@ 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, ip: req.realIp, + mfaJwtToken, userId: req.mfa.userId, orgId: req.mfa.orgId, mfaToken: req.body.mfaToken diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 5c774a13c..2ed1832c5 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -314,14 +314,14 @@ export const authLoginServiceFactory = ({ * 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(mfaToken, getConfig().AUTH_SECRET) as AuthModeMfaJwtTokenPayload; + 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"); diff --git a/backend/src/services/auth/auth-login-type.ts b/backend/src/services/auth/auth-login-type.ts index 86af5a5f9..37b90f548 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; From ddb1d5a1ab7eee448a5f948d5365b895d4c00b26 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:39:49 +0100 Subject: [PATCH 135/582] Remove log --- frontend/src/pages/login/select-organization.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/pages/login/select-organization.tsx b/frontend/src/pages/login/select-organization.tsx index 867f5a9dd..b9226343b 100644 --- a/frontend/src/pages/login/select-organization.tsx +++ b/frontend/src/pages/login/select-organization.tsx @@ -98,8 +98,6 @@ export default function LoginPage() { privateKey } as IsCliLoginSuccessful["loginResponse"]; - console.log("sending to cli", payload); - // send request to server endpoint const instance = axios.create(); await instance.post(`http://127.0.0.1:${callbackPort}/`, payload); From de715c03ad8e7208a2569f900f3e833bf8b62a41 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:40:11 +0100 Subject: [PATCH 136/582] Fix: Org scoped JWT's, MFA support --- .../Login/components/MFAStep/MFAStep.tsx | 126 +++++++++++------- 1 file changed, 79 insertions(+), 47 deletions(-) diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index 89e6c2f70..8fdd831bd 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -2,20 +2,22 @@ import React, { useState } from "react"; import ReactCodeInput from "react-code-input"; import { useTranslation } from "react-i18next"; import { useRouter } from "next/router"; -import axios from "axios" +import axios from "axios"; import jwt_decode from "jwt-decode"; import Error from "@app/components/basic/Error"; // which to notification import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa" +import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"; import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa"; -import { Button } from "@app/components/v2"; +import { Button } from "@app/components/v2"; import { useUpdateUserAuthMethods } from "@app/hooks/api"; import { useSendMfaToken } from "@app/hooks/api/auth"; +import { selectOrganization } from "@app/hooks/api/auth/queries"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; import { AuthMethod } from "@app/hooks/api/users/types"; -import { navigateUserToOrg } from "../../Login.utils"; +import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils"; // The style for the verification code input const props = { @@ -42,7 +44,7 @@ type Props = { password: string; providerAuthToken?: string; callbackPort?: string | null; -} +}; interface VerifyMfaTokenError { response: { @@ -56,11 +58,7 @@ interface VerifyMfaTokenError { }; } -export const MFAStep = ({ - email, - password, - providerAuthToken -}: Props) => { +export const MFAStep = ({ email, password, providerAuthToken }: Props) => { const { createNotification } = useNotificationContext(); const router = useRouter(); const [isLoading, setIsLoading] = useState(false); @@ -71,7 +69,7 @@ export const MFAStep = ({ const { t } = useTranslation(); const sendMfaToken = useSendMfaToken(); - const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods(); + const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods(); const handleLoginMfa = async () => { try { @@ -79,16 +77,20 @@ export const MFAStep = ({ let callbackPort: undefined | string; let authMethod: undefined | AuthMethod; let organizationId: undefined | string; - + + const queryParams = new URLSearchParams(window.location.search); + + callbackPort = queryParams.get("callback_port") || undefined; + if (providerAuthToken) { const decodedToken = jwt_decode(providerAuthToken) as any; - + isLinkingRequired = decodedToken.isLinkingRequired; callbackPort = decodedToken.callbackPort; authMethod = decodedToken.authMethod; organizationId = decodedToken?.organizationId; } - + if (mfaCode.length !== 6) { createNotification({ text: "Please enter a 6-digit MFA code and try again", @@ -99,25 +101,49 @@ export const MFAStep = ({ setIsLoading(true); if (callbackPort) { - // attemptCliLogin const isCliLoginSuccessful = await attemptCliLoginMfa({ email, password, providerAuthToken, mfaToken: mfaCode - }) + }); - if (isCliLoginSuccessful && isCliLoginSuccessful.success){ - // case: login was successful - const cliUrl = `http://127.0.0.1:${callbackPort}/` + if (isCliLoginSuccessful && isCliLoginSuccessful.success) { + const cliUrl = `http://127.0.0.1:${callbackPort}/`; - // send request to server endpoint - const instance = axios.create() - await instance.post(cliUrl,{...isCliLoginSuccessful.loginResponse,email}) + // case: organization ID is present from the provider auth token -- select the org and use the new jwt token in the CLI, then navigate to the org + if (organizationId) { + const { token: newJwtToken } = await selectOrganization({ organizationId }); - // cli page - router.push("/cli-redirect"); + console.log( + "organization id was present. new JWT token to be used in CLI:", + newJwtToken + ); + + const instance = axios.create(); + await instance.post(cliUrl, { + ...isCliLoginSuccessful.loginResponse, + JTWToken: newJwtToken + }); + + await navigateUserToOrg(router, organizationId); + } + // case: no organization ID is present -- navigate to the select org page IF the user has any orgs + // if the user has no orgs, navigate to the create org page + else { + const userOrgs = await fetchOrganizations(); + + // case: user has orgs, so we navigate the user to select an org + if (userOrgs.length > 0) { + navigateUserToSelectOrg(router, callbackPort); + } + // case: no orgs found, so we navigate the user to create an org + // cli login will fail in this case + else { + await navigateUserToOrg(router); + } + } } } else { const isLoginSuccessful = await attemptLoginMfa({ @@ -126,25 +152,29 @@ export const MFAStep = ({ providerAuthToken, mfaToken: mfaCode }); - + if (isLoginSuccessful) { setIsLoading(false); // case: login does not require MFA step createNotification({ - text: "Successfully logged in", - type: "success" + text: "Successfully logged in", + type: "success" }); if (isLinkingRequired && authMethod) { const user = await fetchUserDetails(); - const newAuthMethods = [...user.authMethods, authMethod] + const newAuthMethods = [...user.authMethods, authMethod]; await updateUserAuthMethodsMutateAsync({ - authMethods: newAuthMethods + authMethods: newAuthMethods }); } - - await navigateUserToOrg(router, organizationId); + + if (organizationId) { + await navigateUserToOrg(router, organizationId); + } else { + navigateUserToSelectOrg(router); + } } else { createNotification({ text: "Failed to log in", @@ -152,7 +182,6 @@ export const MFAStep = ({ }); } } - } catch (err) { const error = err as VerifyMfaTokenError; createNotification({ @@ -184,11 +213,11 @@ export const MFAStep = ({ } }; - return ( -
+ return ( +

{t("mfa.step2-message")}

{email}

-
+
-
+
)} -
-
+
+
+ > + {" "} + {String(t("mfa.verify"))}{" "} +
-
+
{t("signup.step2-resend-alert")} -
+
-

{t("signup.step2-spam-alert")}

+

{t("signup.step2-spam-alert")}

- - ); -} \ No newline at end of file + + ); +}; From 14c60bd075e2ac83a8521f0b27df9e7a5c7d187f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:54:21 +0100 Subject: [PATCH 137/582] Fix: Admin signup, select organization --- frontend/src/views/admin/SignUpPage/SignUpPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx index 8a26030ce..29134e50e 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 From 60a37e784b672448820e9bf7daa7092bb75bf64a Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:03:06 +0100 Subject: [PATCH 138/582] Fix: member invites, select org --- frontend/src/pages/signupinvite.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index a3b643e29..01668d314 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -23,7 +23,11 @@ import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKe import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { useServerConfig } from "@app/context"; -import { completeAccountSignupInvite, verifySignupInvite } from "@app/hooks/api/auth/queries"; +import { + completeAccountSignupInvite, + selectOrganization, + verifySignupInvite +} from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; // eslint-disable-next-line new-cap @@ -170,6 +174,10 @@ export default function SignupInvite() { const userOrgs = await fetchOrganizations(); const orgId = userOrgs[0].id; + + const { token: newJwtToken } = await selectOrganization({ organizationId: orgId }); + SecurityClient.setToken(newJwtToken); + localStorage.setItem("orgData.id", orgId); setStep(3); @@ -210,6 +218,8 @@ export default function SignupInvite() { SecurityClient.setSignupToken(response.token); setStep(2); } else { + const { token: newJwtToken } = await selectOrganization({ organizationId }); + SecurityClient.setToken(newJwtToken); // user will be redirected to dashboard // if not logged in gets kicked out to login router.push(`/org/${organizationId}/overview`); From 9a724db6ab9679cfab059162ac1a627e5e59b1d2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:03:21 +0100 Subject: [PATCH 139/582] Improvement: Use select organization hook --- frontend/src/views/Login/components/MFAStep/MFAStep.tsx | 8 ++------ .../views/Login/components/PasswordStep/PasswordStep.tsx | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index 8fdd831bd..acef4c3b1 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -12,7 +12,7 @@ import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa"; import { Button } from "@app/components/v2"; import { useUpdateUserAuthMethods } from "@app/hooks/api"; import { useSendMfaToken } from "@app/hooks/api/auth"; -import { selectOrganization } from "@app/hooks/api/auth/queries"; +import { useSelectOrganization } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; import { AuthMethod } from "@app/hooks/api/users/types"; @@ -70,6 +70,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { const sendMfaToken = useSendMfaToken(); const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods(); + const { mutateAsync: selectOrganization } = useSelectOrganization(); const handleLoginMfa = async () => { try { @@ -116,11 +117,6 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { if (organizationId) { const { token: newJwtToken } = await selectOrganization({ organizationId }); - console.log( - "organization id was present. new JWT token to be used in CLI:", - newJwtToken - ); - const instance = axios.create(); await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse, diff --git a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx index 7c819a1fd..57250af51 100644 --- a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx @@ -10,7 +10,7 @@ import attemptCliLogin from "@app/components/utilities/attemptCliLogin"; import attemptLogin from "@app/components/utilities/attemptLogin"; import { Button, Input } from "@app/components/v2"; import { useUpdateUserAuthMethods } from "@app/hooks/api"; -import { selectOrganization } from "@app/hooks/api/auth/queries"; +import { useSelectOrganization } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; @@ -36,6 +36,7 @@ export const PasswordStep = ({ const { t } = useTranslation(); const router = useRouter(); const { mutateAsync } = useUpdateUserAuthMethods(); + const { mutateAsync: selectOrganization } = useSelectOrganization(); const { callbackPort, isLinkingRequired, authMethod, organizationId } = jwt_decode( providerAuthToken From 8573263379e62be30da1fa35c69c78884dc1c7d2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:38:19 +0100 Subject: [PATCH 140/582] Update permission-service.ts --- backend/src/ee/services/permission/permission-service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 737571fad..8a891dc33 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -180,10 +180,6 @@ export const permissionServiceFactory = ({ throw new BadRequestError({ name: "Custom permission not found" }); } - if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { - throw new BadRequestError({ name: "Custom permission not found" }); - } - if (membership.orgId !== userOrgId) { throw new UnauthorizedError({ name: "You are not a member of this organization" }); } From 258c9e45d43c08d86abdfc190bbd8e573096f4f1 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:43:30 +0100 Subject: [PATCH 141/582] Update permission-service.ts --- backend/src/ee/services/permission/permission-service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 8a891dc33..d05ce34e2 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -170,7 +170,6 @@ export const permissionServiceFactory = ({ userId: string, projectId: string, authMethod: ActorAuthMethod, - userOrgId?: string ): Promise> => { const membership = await permissionDAL.getProjectPermission(userId, projectId); From 6e2f3800d487bd7c513f5877e35875def863b763 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:51:07 +0100 Subject: [PATCH 142/582] Fix: Make API keys compatible with old endpoints --- .../services/permission/permission-service.ts | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index d05ce34e2..d632be3df 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -13,6 +13,7 @@ import { conditionsMatcher } from "@app/lib/casl"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; 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"; @@ -33,6 +34,7 @@ type TPermissionServiceFactoryDep = { orgRoleDAL: Pick; projectRoleDAL: Pick; serviceTokenDAL: Pick; + projectDAL: Pick; permissionDAL: TPermissionDALFactory; }; @@ -42,7 +44,8 @@ export const permissionServiceFactory = ({ permissionDAL, orgRoleDAL, projectRoleDAL, - serviceTokenDAL + serviceTokenDAL, + projectDAL }: TPermissionServiceFactoryDep) => { const buildOrgPermission = (role: string, permission?: unknown) => { switch (role) { @@ -99,7 +102,7 @@ export const permissionServiceFactory = ({ /* * Get user permission in an organization - * */ + */ const getUserOrgPermission = async ( userId: string, orgId: string, @@ -112,8 +115,13 @@ export const permissionServiceFactory = ({ throw new BadRequestError({ name: "Custom permission not found" }); } - if (membership.orgId !== userOrgId) { - throw new UnauthorizedError({ name: "You are not a member of this organization" }); + // 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); @@ -179,8 +187,13 @@ export const permissionServiceFactory = ({ throw new BadRequestError({ name: "Custom permission not found" }); } - if (membership.orgId !== userOrgId) { - throw new UnauthorizedError({ name: "You are not a member of this organization" }); + // 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); @@ -195,7 +208,8 @@ export const permissionServiceFactory = ({ 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" }); @@ -208,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, @@ -218,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), @@ -260,9 +296,9 @@ export const permissionServiceFactory = ({ case ActorType.USER: 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", From 961a73f712e2558276d54b98f2d2553109f31c98 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:51:20 +0100 Subject: [PATCH 143/582] Fix: Re-add API key support --- .../server/plugins/auth/inject-identity.ts | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 95deb78a5..5fcd3635b 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -19,13 +19,14 @@ export type TAuthMode = orgId?: string; authMethod: AuthMethod; } - // | { - // authMode: AuthMode.API_KEY; - // actor: ActorType.USER; - // userId: string; - // user: TUsers; - // orgId?: string; - // } + | { + authMode: AuthMode.API_KEY; + authMethod: null; + actor: ActorType.USER; + userId: string; + user: TUsers; + orgId: string; + } | { authMode: AuthMode.SERVICE_TOKEN; serviceToken: TServiceTokens & { createdByEmail: string }; @@ -78,8 +79,8 @@ 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; + // 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 { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, @@ -148,11 +149,18 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; 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 }; - // 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, + orgId: "API_KEY", + authMethod: null + }; // 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! + break; + } case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId, authMethod: null }; From 36efa6ba638c22895fe865db42af545f43e6d54b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:51:27 +0100 Subject: [PATCH 144/582] Update inject-permission.ts --- backend/src/server/plugins/auth/inject-permission.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index 92fbfff1d..084f18198 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -12,8 +12,8 @@ export const injectPermission = fp(async (server) => { req.permission = { type: ActorType.USER, id: req.auth.userId, - orgId: req.auth.orgId, - authMethod: req.auth.authMethod + 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 = { From 60fbd8ac44440c607f7ae6e9ea4a985bf9a18115 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:51:46 +0100 Subject: [PATCH 145/582] Chore: Better error messages --- backend/src/server/routes/v3/secret-router.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 2ce8f5463..d9613846e 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -83,6 +83,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { 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; } From 0c1d37cc75ca8e4220475c311ef5c3434580ea18 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:52:00 +0100 Subject: [PATCH 146/582] Update index.ts --- backend/src/server/routes/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 72208cc72..c452bfbd3 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({ From 73cc97cf17f55069f8f86c2104875d2b3eecaaa8 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:20:56 +0100 Subject: [PATCH 147/582] Fix: Signup not redirecting to backup PDF page due to error --- frontend/src/helpers/project.ts | 44 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/frontend/src/helpers/project.ts b/frontend/src/helpers/project.ts index 33c9e5777..8379b0fa5 100644 --- a/frontend/src/helpers/project.ts +++ b/frontend/src/helpers/project.ts @@ -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; }; From c9e5f2bb750418f7ee58f98614cfe9f2cd089d3d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:21:06 +0100 Subject: [PATCH 148/582] Select org on signup --- .../Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index b5e07286f..c2ad81efe 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) { @@ -189,6 +190,11 @@ 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({ organizationSlug: orgSlug, projectName: "Example Project" From 0081bbdf9e09745a0ffdbc1bf4c37748deb247e3 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:37:59 +0100 Subject: [PATCH 149/582] Type improvements --- backend/src/ee/services/permission/permission-fns.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts index 5127a31f8..78b8ad8f2 100644 --- a/backend/src/ee/services/permission/permission-fns.ts +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -1,3 +1,4 @@ +import { TOrganizations } from "@app/db/schemas"; import { UnauthorizedError } from "@app/lib/errors"; import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type"; @@ -9,7 +10,7 @@ function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) { ); } -function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced?: boolean | null) { +function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) { if (actorAuthMethod === undefined) { throw new UnauthorizedError({ name: "No auth method defined" }); } From 52fd09b87b544617c5e2412cd51e83f3bdadfe12 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:40:10 +0100 Subject: [PATCH 150/582] Chore: Removed code that spans out of scope --- backend/src/ee/services/scim/scim-service.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/src/ee/services/scim/scim-service.ts b/backend/src/ee/services/scim/scim-service.ts index 0f3a44c12..6ed594777 100644 --- a/backend/src/ee/services/scim/scim-service.ts +++ b/backend/src/ee/services/scim/scim-service.ts @@ -443,15 +443,6 @@ export const scimServiceFactory = ({ }); } - const organization = await orgDAL.findById(scimToken.orgId); - - if (!organization.scimEnabled) { - throw new ScimRequestError({ - detail: "SCIM is disabled for the organization", - status: 403 - }); - } - return { scimTokenId: scimToken.id, orgId: scimToken.orgId }; }; From 6904cd3bdaa1a765230b53872bf7b73f0a21de11 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:42:37 +0100 Subject: [PATCH 151/582] Fix: Better types --- backend/src/lib/types/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/lib/types/index.ts b/backend/src/lib/types/index.ts index a81517c94..7a34222a6 100644 --- a/backend/src/lib/types/index.ts +++ b/backend/src/lib/types/index.ts @@ -1,4 +1,4 @@ -import { ActorAuthMethod, ActorType, AuthMethod } from "@app/services/auth/auth-type"; +import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; export type TOrgPermission = { actor: ActorType; @@ -12,7 +12,7 @@ export type TProjectPermission = { actor: ActorType; actorId: string; projectId: string; - actorAuthMethod: AuthMethod | null; + actorAuthMethod: ActorAuthMethod; actorOrgId: string | undefined; }; From 831da10073af25c65234829257e7ee9650c17925 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:43:48 +0100 Subject: [PATCH 152/582] Chore: Move comment --- backend/src/server/plugins/auth/inject-identity.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 5fcd3635b..dceb31c03 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -124,7 +124,6 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; break; } - // Will always contain an orgId. case AuthMode.IDENTITY_ACCESS_TOKEN: { const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp); req.auth = { @@ -156,9 +155,9 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { userId: user.id, actor, user, - orgId: "API_KEY", + 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 - }; // 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! + }; break; } case AuthMode.SCIM_TOKEN: { From 8f42914df533092a2b735c696d7963083f941256 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:07:35 +0100 Subject: [PATCH 153/582] Chore: Change order --- backend/src/server/routes/v2/mfa-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index f45c916f4..d8e46d0da 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -75,8 +75,8 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { const { user, token } = await server.services.login.verifyMfaToken({ userAgent, - ip: req.realIp, mfaJwtToken, + ip: req.realIp, userId: req.mfa.userId, orgId: req.mfa.orgId, mfaToken: req.body.mfaToken From 700a072ec515d379897999c995a27aa8e949050e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:16:28 +0100 Subject: [PATCH 154/582] Fix: Code readability --- backend/src/server/routes/v3/login-router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/server/routes/v3/login-router.ts b/backend/src/server/routes/v3/login-router.ts index ea6b3f52e..900ad56d2 100644 --- a/backend/src/server/routes/v3/login-router.ts +++ b/backend/src/server/routes/v3/login-router.ts @@ -53,8 +53,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { handler: async (req, res) => { const cfg = getConfig(); const tokens = await server.services.login.selectOrganization({ - userAgentHeader: req.headers["user-agent"], - authorizationHeader: req.headers.authorization, + userAgent: req.headers["user-agent"], + authJwtToken: req.headers.authorization, organizationId: req.body.organizationId, ipAddress: req.realIp }); From 1dbf80d4e63056e551ba08c95ec60f93a146a8fe Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:16:38 +0100 Subject: [PATCH 155/582] Fix: Code readability --- .../src/services/auth/auth-login-service.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 2ed1832c5..9aa289411 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -251,36 +251,36 @@ export const authLoginServiceFactory = ({ }; const selectOrganization = async ({ - userAgentHeader, - authorizationHeader, + userAgent, + authJwtToken, ipAddress, organizationId }: { - userAgentHeader: string | undefined; - authorizationHeader: string | undefined; + userAgent: string | undefined; + authJwtToken: string | undefined; ipAddress: string; organizationId: string; }) => { const cfg = getConfig(); - if (!authorizationHeader) throw new UnauthorizedError({ name: "Authorization header is required" }); - if (!userAgentHeader) throw new UnauthorizedError({ name: "user agent header is required" }); + if (!authJwtToken) throw new UnauthorizedError({ name: "Authorization header is required" }); + if (!userAgent) throw new UnauthorizedError({ name: "user agent header is required" }); - const userAgent = userAgentHeader; - const authToken = authorizationHeader.slice(7); // slice of after Bearer + // 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(authToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload; - + 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: "Get Me" }); + 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 (!userOrgs.some((org) => org.id === organizationId)) { + if (!hasOrganizationMembership) { throw new UnauthorizedError({ message: "User does not have access to the organization" }); } From 80b4bc18ec66c95f4d615c2eb73ce54fd075cd08 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:17:15 +0100 Subject: [PATCH 156/582] Update auth-token-service.ts --- backend/src/services/auth-token/auth-token-service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 5d0011d11..59f336e5a 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -135,7 +135,6 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact id: token.tokenVersionId, userId: token.userId }); - if (!session) throw new UnauthorizedError({ name: "Session not found" }); if (token.accessVersion !== session.accessVersion) throw new UnauthorizedError({ name: "Stale session" }); From c42d407cdae7483fc2757950928dd1ca78cc6ef4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:18:31 +0100 Subject: [PATCH 157/582] Chore: Remove old comments --- backend/src/services/auth/auth-login-service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 9aa289411..e1b10530d 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -184,10 +184,6 @@ export const authLoginServiceFactory = ({ if (!userEnc) throw new Error("Failed to find user"); const cfg = getConfig(); - // let organizationId; - - // let authMethod = (providerAuthToken as AuthMethod) || AuthMethod.EMAIL; - let authMethod = AuthMethod.EMAIL; let organizationId: string | undefined; From f53fa46c510122c9ad13a1524cc7d9bcf06f2603 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:34:28 +0100 Subject: [PATCH 158/582] Fix: Cleanup --- frontend/src/pages/_app.tsx | 40 ++++++++++++++--------------- frontend/src/pages/signupinvite.tsx | 9 ++++--- frontend/src/views/Login/Login.tsx | 1 - 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index b26968109..5c9b63b75 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -102,29 +102,29 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element => return ( - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + ); }; diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 01668d314..95f0770cf 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -25,7 +25,7 @@ import SecurityClient from "@app/components/utilities/SecurityClient"; import { useServerConfig } from "@app/context"; import { completeAccountSignupInvite, - selectOrganization, + useSelectOrganization, verifySignupInvite } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; @@ -63,6 +63,8 @@ export default function SignupInvite() { const email = (parsedUrl.to as string)?.replace(" ", "+").trim(); const { config } = useServerConfig(); + const { mutateAsync: selectOrganization } = useSelectOrganization(); + useEffect(() => { if (!config.allowSignUp) { router.push("/login"); @@ -175,8 +177,9 @@ export default function SignupInvite() { const orgId = userOrgs[0].id; - const { token: newJwtToken } = await selectOrganization({ organizationId: orgId }); - SecurityClient.setToken(newJwtToken); + if (!orgId) throw new Error("You are not part of any organization"); + + await selectOrganization({ organizationId: orgId }); localStorage.setItem("orgData.id", orgId); diff --git a/frontend/src/views/Login/Login.tsx b/frontend/src/views/Login/Login.tsx index 307ef2e71..ac56c28c3 100644 --- a/frontend/src/views/Login/Login.tsx +++ b/frontend/src/views/Login/Login.tsx @@ -17,7 +17,6 @@ export const Login = () => { useEffect(() => { // TODO(akhilmhdh): workspace will be controlled by a workspace context const handleRedirects = async () => { - // TODO(daniel): Move this to select-organization page. try { const callbackPort = queryParams?.get("callback_port"); // case: a callback port is set, meaning it's a cli login request: redirect to select org with callback port From c94caa6fb58de6c5dc490535c7ee48ee359be3b6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:39:35 +0100 Subject: [PATCH 159/582] Chore: Minor code cleanup --- frontend/src/pages/signupinvite.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 95f0770cf..17ac5b4ff 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -29,6 +29,7 @@ import { verifySignupInvite } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; +import { navigateUserToOrg } from "@app/views/Login/Login.utils"; // eslint-disable-next-line new-cap const client = new jsrp.client(); @@ -221,11 +222,11 @@ export default function SignupInvite() { SecurityClient.setSignupToken(response.token); setStep(2); } else { - const { token: newJwtToken } = await selectOrganization({ organizationId }); - SecurityClient.setToken(newJwtToken); + await selectOrganization({ organizationId }); + // user will be redirected to dashboard // if not logged in gets kicked out to login - router.push(`/org/${organizationId}/overview`); + await navigateUserToOrg(router, organizationId); } } } catch (err) { From d1ebdbcc03e9b63c1871368a2f32e57b22e57481 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:01:19 +0100 Subject: [PATCH 160/582] Fix: Add auth method and organization ID to test JWT --- backend/e2e-test/vitest-environment-knex.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index c1c750225..09ab05443 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, From b547309ae4bf511e2bf5e150141bcc37758d643d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:40:31 +0100 Subject: [PATCH 161/582] Fix: Get org ID in getOrgIdentityPermission DAL operation --- .../src/ee/services/permission/permission-dal.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index d94589b43..fc0a85ac6 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: [ { From bebdad81593acf1b0d27f00f95b6b2c3b83e7686 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:56:25 +0100 Subject: [PATCH 162/582] parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345579 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345572 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345563 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345551 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345540 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345533 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345529 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345522 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345503 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345496 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345489 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345357 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345061 +0100 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42 author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100 committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345029 +0100 Feat: Org Scoped JWT Tokens Add link button Fix: Avoid invalidating all queries on logout to prevent UI glitch Update _app.tsx Feat: Scoped JWT to organization, add authMethod to request Feat: Scoped JWT to organization, Add authMethod to services Feat: Scoped JWT to organization, require organization on all requests by default on JWT requests Update index.ts Feat: Scoped JWT to organization Chore: Move SAML org check to permission service Feat: Scoped JWT to organization, actorAuthMethod to create project DTO Fix: Invalidate after selecting organization Chore: Optional 'invalidate' option for create org hook Fix: Creating dummy workspaces Fix: Select org after creation Feat: Org Scoped JWT's, remove inline service Fix: ActorType unresolved Fix: Better type checking Feat: Org scoped JWT's Fix: Add missing actor org ID Fix: Add missing actor org ID Fix: Return access token Update auth-type.ts Fix: Add actor org ID Chore: Remove unused code Fix: Add missing actor org ID to permission check Fix: Add missing actor auth method to permission checks Fix: Include actor org id Chore: Remove redundant lint comment Fix: Add missing actorOrgId to service handlers Fix: Rebase fixes Fix: Rebase LDAP fixes Chore: Export Cli login interface Update queries.tsx Feat: Org scoped JWT's CLI support Update inject-permission.ts Fix: MFA Remove log Fix: Admin signup, select organization Improvement: Use select organization hook Update permission-service.ts Fix: Make API keys compatible with old endpoints Update inject-permission.ts Chore: Better error messages Update index.ts Fix: Signup not redirecting to backup PDF page due to error Select org on signup Type improvements Chore: Removed code that spans out of scope Fix: Better types Chore: Move comment Chore: Change order Fix: Code readability Fix: Code readability Update auth-token-service.ts Chore: Remove old comments Fix: Cleanup Chore: Minor code cleanup Fix: Add auth method and organization ID to test JWT Fix: Get org ID in getOrgIdentityPermission DAL operation --- backend/src/server/routes/v2/mfa-router.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index d8e46d0da..82673f572 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -77,6 +77,8 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { userAgent, mfaJwtToken, ip: req.realIp, + mfaJwtToken, + ip: req.realIp, userId: req.mfa.userId, orgId: req.mfa.orgId, mfaToken: req.body.mfaToken From 605dad29caf2d83a7e4e2173781e319d9d3684a9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:15:39 +0100 Subject: [PATCH 163/582] Fix: Rebase error --- backend/src/server/routes/v2/mfa-router.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index 82673f572..d8e46d0da 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -77,8 +77,6 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { userAgent, mfaJwtToken, ip: req.realIp, - mfaJwtToken, - ip: req.realIp, userId: req.mfa.userId, orgId: req.mfa.orgId, mfaToken: req.body.mfaToken From 4d229ec7457f3547e35afe6ce2b879fc6d84b12a Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:08:16 +0100 Subject: [PATCH 164/582] Fix: Email signup and switching organization --- backend/src/server/routes/v3/signup-router.ts | 18 +++++++++------- .../src/services/auth/auth-signup-service.ts | 10 +++++++-- .../src/components/signup/UserInfoStep.tsx | 7 ++++++- frontend/src/layouts/AppLayout/AppLayout.tsx | 21 ++++++++++++++----- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index 17787be84..ac43df36d 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-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 4c14cfb81..3db935769 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( @@ -187,6 +191,7 @@ export const authSignupServiceFactory = ({ const refreshToken = jwt.sign( { + authMethod: AuthMethod.EMAIL, authTokenType: AuthTokenType.REFRESH_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, @@ -197,7 +202,7 @@ export const authSignupServiceFactory = ({ { expiresIn: appCfg.JWT_REFRESH_LIFETIME } ); - return { user: updateduser.info, accessToken, refreshToken }; + return { user: updateduser.info, accessToken, refreshToken, organizationId }; }; /* @@ -290,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/frontend/src/components/signup/UserInfoStep.tsx b/frontend/src/components/signup/UserInfoStep.tsx index 43c481763..a7d11d10c 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, diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 2785d82a1..e78913f6e 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") }); @@ -147,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"); @@ -164,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 @@ -425,7 +436,7 @@ export const AppLayout = ({ children }: LayoutProps) => { onChangeHandler(e.target.value)} type={type} placeholder={placeholder} value={value} required={isRequired} - className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none" + className="text-md min-w-16 w-full rounded-md border border-gray-600 bg-bunker-800 p-2 text-gray-400 outline-none" name={name} readOnly autoComplete={autoComplete} @@ -58,12 +58,12 @@ const InputField = ({ ); } return ( -
-
-

{label}

+
+
+

{label}

@@ -75,11 +75,11 @@ const InputField = ({ required={isRequired} className={`${ blurred - ? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400" + ? "text-bunker-800 focus:text-gray-400 active:text-gray-400 group-hover:text-gray-400" : "" } ${ error ? "focus:ring-red/50" : "focus:ring-primary/50" - } relative peer bg-mineshaft-900 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`} + } text-md min-w-16 peer relative w-full rounded-md bg-mineshaft-900 p-2 text-gray-400 outline-none duration-200 focus:ring-4`} name={name} spellCheck="false" autoComplete={autoComplete} @@ -91,7 +91,7 @@ const InputField = ({ onClick={() => { setPasswordVisible(!passwordVisible); }} - className="absolute self-end mr-3 text-gray-400 cursor-pointer" + className="absolute mr-3 cursor-pointer self-end text-gray-400" > {passwordVisible ? ( @@ -101,7 +101,7 @@ const InputField = ({ )} {blurred && ( -
+

{value .split("") @@ -109,7 +109,7 @@ const InputField = ({ .map(() => ( ))} @@ -121,7 +121,7 @@ const InputField = ({

)} */}
- {error &&

{errorText}

} + {error &&

{errorText}

}
); }; diff --git a/frontend/src/components/basic/Listbox.tsx b/frontend/src/components/basic/Listbox.tsx index 5cdeb26d9..cad9aaab4 100644 --- a/frontend/src/components/basic/Listbox.tsx +++ b/frontend/src/components/basic/Listbox.tsx @@ -34,19 +34,19 @@ const ListBox = ({
{text} - + {" "} {isSelected}
{data && ( -
+
)} @@ -58,16 +58,16 @@ const ListBox = ({ leaveFrom="opacity-100" leaveTo="opacity-0" > - + {data.map((person, personIdx) => ( - `my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${ - selected ? "bg-white/10 text-gray-400 font-bold" : "" + `relative my-0.5 cursor-default select-none rounded-md py-2 pl-10 pr-4 ${ + selected ? "bg-white/10 font-bold text-gray-400" : "" } ${ active && !selected - ? "bg-white/5 text-mineshaft-200 cursor-pointer" + ? "cursor-pointer bg-white/5 text-mineshaft-200" : "text-gray-400" } ` } @@ -83,7 +83,7 @@ const ListBox = ({ {person} {selected ? ( - + ) : null} @@ -92,9 +92,9 @@ const ListBox = ({ ))} {buttonAction && ( -
diff --git a/frontend/src/components/basic/dialog/AddUserDialog.tsx b/frontend/src/components/basic/dialog/AddUserDialog.tsx index b36c31d6c..dd2aede0a 100644 --- a/frontend/src/components/basic/dialog/AddUserDialog.tsx +++ b/frontend/src/components/basic/dialog/AddUserDialog.tsx @@ -13,76 +13,63 @@ type Props = { orgName: string; }; -const AddUserDialog = ({ - isOpen, - closeModal, - submitModal, - email, - setEmail, - orgName, -}: Props) => { +const AddUserDialog = ({ isOpen, closeModal, submitModal, email, setEmail, orgName }: Props) => { const submit = () => { submitModal(email); }; return ( -
+
- + -
+
-
-
+
+
- + Invite others to {orgName} -
-

- An invite is specific to an email address and expires - after 1 day. For security reasons, you will need to - separately add members to projects. +

+

+ An invite is specific to an email address and expires after 1 day. For + security reasons, you will need to separately add members to projects.

-
+
-
-
{/* diff --git a/frontend/src/components/basic/dialog/AddWorkspaceDialog.tsx b/frontend/src/components/basic/dialog/AddWorkspaceDialog.tsx index 88dc7cb78..b1cc1fd07 100644 --- a/frontend/src/components/basic/dialog/AddWorkspaceDialog.tsx +++ b/frontend/src/components/basic/dialog/AddWorkspaceDialog.tsx @@ -5,7 +5,6 @@ import Button from "../buttons/Button"; import InputField from "../InputField"; import { Checkbox } from "../table/Checkbox"; - type Props = { isOpen: boolean; closeModal: () => void; @@ -26,8 +25,8 @@ const AddWorkspaceDialog = ({ workspaceName, setWorkspaceName, error, - loading, -}:Props) => { + loading +}: Props) => { const [addAllUsers, setAddAllUsers] = useState(true); const submit = () => { submitModal(workspaceName, addAllUsers); @@ -60,11 +59,8 @@ const AddWorkspaceDialog = ({ leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - - + + Create a new project
@@ -72,7 +68,7 @@ const AddWorkspaceDialog = ({ This project will contain your secrets and configs.

-
+
- +
-
{textLine1}
-
{textLine2}
-
+
{textLine1}
+
{textLine2}
+ diff --git a/frontend/src/components/context/Notifications/Notification.tsx b/frontend/src/components/context/Notifications/Notification.tsx index 806b68ad2..14b0ba556 100644 --- a/frontend/src/components/context/Notifications/Notification.tsx +++ b/frontend/src/components/context/Notifications/Notification.tsx @@ -36,25 +36,28 @@ const Notification = ({ notification, clearNotification }: NotificationProps) => return (
{notification.type === "error" && ( -
+
)} {notification.type === "success" && ( -
+
)} {notification.type === "info" && ( -
+
)} -

{notification.text}

+

{notification.text}

); diff --git a/frontend/src/components/context/Notifications/Notifications.tsx b/frontend/src/components/context/Notifications/Notifications.tsx index 81802fff6..29e2826d7 100644 --- a/frontend/src/components/context/Notifications/Notifications.tsx +++ b/frontend/src/components/context/Notifications/Notifications.tsx @@ -11,7 +11,7 @@ const Notifications = ({ notifications, clearNotification }: NoticationsProps) = } return ( -
+
{notifications.map((notif) => ( ))} diff --git a/frontend/src/components/dashboard/ConfirmEnvOverwriteModal.tsx b/frontend/src/components/dashboard/ConfirmEnvOverwriteModal.tsx index 9c8582d6a..1926e7d37 100644 --- a/frontend/src/components/dashboard/ConfirmEnvOverwriteModal.tsx +++ b/frontend/src/components/dashboard/ConfirmEnvOverwriteModal.tsx @@ -30,9 +30,9 @@ const ConfirmEnvOverwriteModal = ({ onClose={onClose} >
-

Your file contains the following duplicate secrets:

+

Your file contains the following duplicate secrets:

{duplicateKeys.join(", ")}

-

Are you sure you want to overwrite these secrets?

+

Are you sure you want to overwrite these secrets?

diff --git a/frontend/src/components/dashboard/DashboardInputField.tsx b/frontend/src/components/dashboard/DashboardInputField.tsx index 3cf87924e..60b92a51e 100644 --- a/frontend/src/components/dashboard/DashboardInputField.tsx +++ b/frontend/src/components/dashboard/DashboardInputField.tsx @@ -1,5 +1,10 @@ import { memo, SyntheticEvent, useRef } from "react"; -import { faCircle, faCodeBranch, faExclamationCircle, faEye } from "@fortawesome/free-solid-svg-icons"; +import { + faCircle, + faCodeBranch, + faExclamationCircle, + faEye +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import guidGenerator from "../utilities/randomId"; @@ -31,8 +36,8 @@ interface DashboardInputFieldProps { * @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard * @param {boolean} obj.isDuplicate - if the key name is duplicated * @param {boolean} obj.override - whether a secret/row should be displalyed as overriden - * - * + * + * * @returns */ @@ -61,29 +66,31 @@ const DashboardInputField = ({ const error = startsWithNumber || isDuplicate; return ( -
+
onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)} + onChange={(e) => + onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id) + } type={type} value={value} - className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${ + className={`ph-no-capture min-w-16 peer z-10 h-full w-full bg-transparent px-2 font-mono text-sm caret-bunker-200 outline-none ${ error ? "text-red-600 focus:text-red-500" : "text-bunker-300 focus:text-bunker-100" } duration-200`} spellCheck="false" />
{startsWithNumber && ( -
- + )} {isDuplicate && value !== "" && !startsWithNumber && ( -
- +
)} - {!error &&
- -
} + {!error && ( +
+ +
+ )}
); } @@ -127,20 +145,29 @@ const DashboardInputField = ({ return ( -
+
- {value?.split("\n")[0] ? - {value?.split("\n")[0]} - : - } - {value?.split("\n")[1] && - {value?.split("\n")[1]} - } + {value?.split("\n")[0] ? ( + + {value?.split("\n")[0]} + + ) : ( + - + )} + {value?.split("\n")[1] && ( + + {value?.split("\n")[1]} + + )}
@@ -148,10 +175,10 @@ const DashboardInputField = ({ } if (type === "value") { return ( -
-
+
+
{overrideEnabled === true && ( -
+
Override enabled
)} @@ -160,20 +187,20 @@ const DashboardInputField = ({ onChange={(e) => onChangeHandler(e.target.value, id)} onScroll={syncScroll} className={`${ - blurred - ? "text-transparent focus:text-transparent active:text-transparent" - : "" - } z-10 peer font-mono ph-no-capture bg-transparent caret-white text-transparent text-sm px-2 py-2 w-full min-w-16 outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`} + blurred ? "text-transparent focus:text-transparent active:text-transparent" : "" + } ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full bg-transparent px-2 py-2 font-mono text-sm text-transparent caret-white outline-none duration-200 no-scrollbar`} spellCheck="false" />
{value?.split(REGEX).map((word) => { if (word.match(REGEX) !== null) { @@ -203,20 +230,24 @@ const DashboardInputField = ({ })}
{blurred && ( -
-
+
+
{value?.split("").map(() => ( ))} - {value?.split("").length === 0 && EMPTY} + {value?.split("").length === 0 && EMPTY} +
+
+
-
)}
diff --git a/frontend/src/components/dashboard/DeleteActionButton.tsx b/frontend/src/components/dashboard/DeleteActionButton.tsx index 530ee3749..da7357fe6 100644 --- a/frontend/src/components/dashboard/DeleteActionButton.tsx +++ b/frontend/src/components/dashboard/DeleteActionButton.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React from "react"; import { useTranslation } from "react-i18next"; import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -8,32 +8,35 @@ import Button from "../basic/buttons/Button"; type Props = { onSubmit: () => void; isPlain?: boolean; -} +}; export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => { const { t } = useTranslation(); return ( -
- {isPlain - ?
null} - role="button" - tabIndex={0} - onClick={onSubmit} - className="invisible group-hover:visible" - > - -
- :
- ) -} + ); +}; diff --git a/frontend/src/components/dashboard/DownloadSecretsMenu.tsx b/frontend/src/components/dashboard/DownloadSecretsMenu.tsx index a921798ca..29942abf0 100644 --- a/frontend/src/components/dashboard/DownloadSecretsMenu.tsx +++ b/frontend/src/components/dashboard/DownloadSecretsMenu.tsx @@ -18,7 +18,7 @@ const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: strin + > + {" "} + {String(t("signup.verify"))}{" "} +
-
+
{t("signup.step2-resend-alert")} -
+
-

{t("signup.step2-spam-alert")}

+

{t("signup.step2-spam-alert")}

); diff --git a/frontend/src/components/signup/DonwloadBackupPDFStep.tsx b/frontend/src/components/signup/DonwloadBackupPDFStep.tsx index 73082af32..22d278cc3 100644 --- a/frontend/src/components/signup/DonwloadBackupPDFStep.tsx +++ b/frontend/src/components/signup/DonwloadBackupPDFStep.tsx @@ -57,19 +57,22 @@ export default function DonwloadBackupPDFStep({ }; return ( -
-

- +

+

+ {t("signup.step4-message")}

-
-
+
+
{t("signup.step4-description1")} {t("signup.step4-description3")}
-
-
+
+
+ > + {" "} + {String(t("signup.step1-submit"))}{" "} +
diff --git a/frontend/src/components/signup/TeamInviteStep.tsx b/frontend/src/components/signup/TeamInviteStep.tsx index d1cc6ef7d..a06fc3568 100644 --- a/frontend/src/components/signup/TeamInviteStep.tsx +++ b/frontend/src/components/signup/TeamInviteStep.tsx @@ -16,7 +16,7 @@ export default function TeamInviteStep(): JSX.Element { const router = useRouter(); const [emails, setEmails] = useState(""); const { data: serverDetails } = useFetchServerStatus(); - + const { mutateAsync } = useAddUserToOrg(); const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const); @@ -40,55 +40,61 @@ export default function TeamInviteStep(): JSX.Element { }; return ( -
-

+

+

{t("signup.step5-invite-team")}

-

+

{t("signup.step5-subtitle")}

-
+
-
+
Emails