From 0323d152da7a29ad0f702827254a534c1326a333 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sat, 26 Apr 2025 09:46:23 +0400 Subject: [PATCH] feat(microsoft-teams): better authentication flow and doc references --- ...35_microsoft-teams-workflow-integration.ts | 2 +- .../routes/v1/microsoft-teams-router.ts | 54 +++++-- .../microsoft-teams/microsoft-teams-fns.ts | 54 +++++-- .../microsoft-teams-service.ts | 101 +++++++++---- .../microsoft-teams/microsoft-teams-types.ts | 7 +- .../src/services/project/project-service.ts | 8 +- frontend/src/const/routes.ts | 6 + .../api/workflowIntegrations/queries.tsx | 18 ++- .../hooks/api/workflowIntegrations/types.ts | 3 +- .../MicrosoftTeamsIntegrationForm.tsx | 13 +- .../OauthCallbackPage/OauthCallbackPage.tsx | 99 +++++++++++++ .../SettingsPage/OauthCallbackPage/route.tsx | 27 ++++ .../MicrosoftTeamsIntegrationForm.tsx | 46 ++++-- .../pages/organization/SettingsPage/route.tsx | 2 +- .../EditWorkflowIntegrationModal.tsx | 2 +- frontend/src/routeTree.gen.ts | 133 ++++++++++++++---- frontend/src/routes.ts | 5 +- 17 files changed, 477 insertions(+), 103 deletions(-) create mode 100644 frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx create mode 100644 frontend/src/pages/organization/SettingsPage/OauthCallbackPage/route.tsx diff --git a/backend/src/db/migrations/20250422125635_microsoft-teams-workflow-integration.ts b/backend/src/db/migrations/20250422125635_microsoft-teams-workflow-integration.ts index ed1b9333c..fa0a39446 100644 --- a/backend/src/db/migrations/20250422125635_microsoft-teams-workflow-integration.ts +++ b/backend/src/db/migrations/20250422125635_microsoft-teams-workflow-integration.ts @@ -47,7 +47,7 @@ export async function up(knex: Knex): Promise { table.foreign("id").references("id").inTable(TableName.WorkflowIntegrations).onDelete("CASCADE"); // the ID itself is the workflow integration ID table.string("internalTeamsAppId").nullable(); - table.string("tenantId").notNullable(); + table.string("tenantId").unique().notNullable(); table.binary("encryptedAccessToken").nullable(); table.binary("encryptedBotAccessToken").nullable(); diff --git a/backend/src/server/routes/v1/microsoft-teams-router.ts b/backend/src/server/routes/v1/microsoft-teams-router.ts index 228c7dd90..caed33ba8 100644 --- a/backend/src/server/routes/v1/microsoft-teams-router.ts +++ b/backend/src/server/routes/v1/microsoft-teams-router.ts @@ -22,33 +22,63 @@ const sanitizedMicrosoftTeamsIntegrationSchema = WorkflowIntegrationsSchema.pick export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) => { server.route({ - method: "POST", - url: "/", + method: "GET", + url: "/client-id", config: { - rateLimit: writeLimit + rateLimit: readLimit }, schema: { - body: z.object({ - tenantId: z.string(), - slug: slugSchema({ max: 64 }) - }), response: { - 200: sanitizedMicrosoftTeamsIntegrationSchema + 200: z.object({ + clientId: z.string() + }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const microsoftTeamsIntegration = await server.services.microsoftTeams.createMicrosoftTeamsIntegration({ + const clientId = await server.services.microsoftTeams.getClientId({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - ...req.body + actorOrgId: req.permission.orgId }); - return microsoftTeamsIntegration; + return { + clientId + }; } }); + + server.route({ + method: "POST", + url: "/", + config: { + rateLimit: readLimit + }, + schema: { + body: z.object({ + redirectUri: z.string(), + tenantId: z.string(), + slug: z.string(), + description: z.string().optional() + }) + }, + + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + await server.services.microsoftTeams.completeMicrosoftTeamsIntegration({ + tenantId: req.body.tenantId, + slug: req.body.slug, + description: req.body.description, + redirectUri: req.body.redirectUri, + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId + }); + } + }); + server.route({ method: "GET", url: "/", diff --git a/backend/src/services/microsoft-teams/microsoft-teams-fns.ts b/backend/src/services/microsoft-teams/microsoft-teams-fns.ts index e6173e21b..075151c85 100644 --- a/backend/src/services/microsoft-teams/microsoft-teams-fns.ts +++ b/backend/src/services/microsoft-teams/microsoft-teams-fns.ts @@ -15,6 +15,51 @@ import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow import { WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types"; import { TMicrosoftTeamsIntegrationDALFactory } from "./microsoft-teams-integration-dal"; +export const verifyTenantFromCode = async ( + tenantId: string, + redirectUri: string, + clientId: string, + clientSecret: string +) => { + const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + + const params = new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + scope: "https://graph.microsoft.com/.default", + redirect_uri: redirectUri, + grant_type: "client_credentials" + }); + + const response = await axios + .post<{ access_token: string }>(tokenEndpoint, params, { + headers: { + "Content-Type": "application/x-www-form-urlencoded" + } + }) + .catch((err) => { + if (axios.isAxiosError(err)) { + logger.error(err.response?.data, "Error fetching Microsoft Teams access token"); + } + throw err; + }); + + const accessToken = response.data.access_token; + const tokenParts = accessToken.split("."); + const tokenPayload = JSON.parse(Buffer.from(tokenParts[1], "base64").toString()) as { tid: string }; + + // the 'tid' claim in the token contains the tenant ID + const tenantIdFromToken = tokenPayload.tid; + + if (tenantIdFromToken !== tenantId) { + throw new BadRequestError({ + message: `Invalid tenant state ID. Expected ${tenantId}, got ${tenantIdFromToken}` + }); + } + + return tenantIdFromToken; +}; + export const getMicrosoftTeamsAccessToken = async ( { orgId, @@ -219,9 +264,6 @@ export const isBotInstalledInTenant = async ( tx ).catch(() => null); - console.log("botAccessToken", botAccessToken); - console.log("accessToken", accessToken); - if (!botAccessToken || !accessToken) { return { accessToken: null, @@ -256,12 +298,6 @@ export const isBotInstalledInTenant = async ( const botInstalledInTenant = appsResponse.data.value.find((a) => a.externalId === botId); - for (const app of appsResponse.data.value) { - if (app.displayName.toLowerCase().includes("infisical")) { - console.log(`${app.displayName} - ${app.externalId}`); - } - } - if (!botInstalledInTenant) { return { installed: false, diff --git a/backend/src/services/microsoft-teams/microsoft-teams-service.ts b/backend/src/services/microsoft-teams/microsoft-teams-service.ts index 7c741c396..39ccdd7cc 100644 --- a/backend/src/services/microsoft-teams/microsoft-teams-service.ts +++ b/backend/src/services/microsoft-teams/microsoft-teams-service.ts @@ -10,7 +10,7 @@ import { FastifyReply, FastifyRequest } from "fastify"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { TKmsServiceFactory } from "../kms/kms-service"; @@ -18,12 +18,18 @@ import { KmsDataKey } from "../kms/kms-types"; import { TSuperAdminDALFactory } from "../super-admin/super-admin-dal"; import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow-integration-dal"; import { WorkflowIntegration, WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types"; -import { getMicrosoftTeamsAccessToken, isBotInstalledInTenant, TeamsBot } from "./microsoft-teams-fns"; +import { + getMicrosoftTeamsAccessToken, + isBotInstalledInTenant, + TeamsBot, + verifyTenantFromCode +} from "./microsoft-teams-fns"; import { TMicrosoftTeamsIntegrationDALFactory } from "./microsoft-teams-integration-dal"; import { TCheckInstallationStatusDTO, TCreateMicrosoftTeamsIntegrationDTO, TDeleteMicrosoftTeamsIntegrationDTO, + TGetClientIdDTO, TGetMicrosoftTeamsIntegrationByIdDTO, TGetMicrosoftTeamsIntegrationByOrgDTO, TGetTeamsDTO, @@ -202,14 +208,15 @@ export const microsoftTeamsServiceFactory = ({ } }; - const createMicrosoftTeamsIntegration = async ({ - actorId, + const completeMicrosoftTeamsIntegration = async ({ actor, + actorId, actorOrgId, actorAuthMethod, + tenantId, slug, description, - tenantId + redirectUri }: TCreateMicrosoftTeamsIntegrationDTO) => { const { permission } = await permissionService.getOrgPermission( actor, @@ -237,7 +244,14 @@ export const microsoftTeamsServiceFactory = ({ }); } - const integration = await workflowIntegrationDAL.transaction(async (tx) => { + const decryptWithRoot = kmsService.decryptWithRootKey(); + const botAppId = decryptWithRoot(encryptedMicrosoftTeamsAppId); + const botAppPassword = decryptWithRoot(encryptedMicrosoftTeamsClientSecret); + const botId = decryptWithRoot(encryptedMicrosoftTeamsBotId); + + await verifyTenantFromCode(tenantId, redirectUri, botAppId.toString(), botAppPassword.toString()); + + await workflowIntegrationDAL.transaction(async (tx) => { const workflowIntegration = await workflowIntegrationDAL.create( { description, @@ -249,19 +263,24 @@ export const microsoftTeamsServiceFactory = ({ tx ); - const microsoftTeamsIntegration = await microsoftTeamsIntegrationDAL.create( - { - // @ts-expect-error id is kept as fixed because it is always equal to the workflow integration ID - id: workflowIntegration.id, - tenantId - }, - tx - ); - - const decryptWithRoot = kmsService.decryptWithRootKey(); - const botAppId = decryptWithRoot(encryptedMicrosoftTeamsAppId); - const botAppPassword = decryptWithRoot(encryptedMicrosoftTeamsClientSecret); - const botId = decryptWithRoot(encryptedMicrosoftTeamsBotId); + const microsoftTeamsIntegration = await microsoftTeamsIntegrationDAL + .create( + { + // @ts-expect-error id is kept as fixed because it is always equal to the workflow integration ID + id: workflowIntegration.id, + tenantId + }, + tx + ) + .catch((err) => { + if (err instanceof DatabaseError) { + if ((err.error as Error)?.stack?.includes("duplicate key value violates unique constraint")) + throw new BadRequestError({ + message: "Microsoft Teams integration with the same Tenant ID already exists." + }); + } + throw err; + }); const teamsBotInfo = await isBotInstalledInTenant( { @@ -307,17 +326,37 @@ export const microsoftTeamsServiceFactory = ({ tx ); } - - return { - ...workflowIntegration, - status: workflowIntegration.status as WorkflowIntegrationStatus, - tenantId: microsoftTeamsIntegration.tenantId - }; }); - - return integration; }; + const getClientId = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGetClientIdDTO) => { + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + actorOrgId, + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); + + const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); + if (!serverCfg) { + throw new BadRequestError({ + message: "Failed to get server configuration." + }); + } + + if (!serverCfg.encryptedMicrosoftTeamsAppId) { + throw new BadRequestError({ + message: "Microsoft Teams app ID is not set" + }); + } + + const decryptWithRoot = kmsService.decryptWithRootKey(); + const clientId = decryptWithRoot(serverCfg.encryptedMicrosoftTeamsAppId); + + return clientId.toString(); + }; const getMicrosoftTeamsIntegrationsByOrg = async ({ actorId, actor, @@ -335,7 +374,8 @@ export const microsoftTeamsServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings); const microsoftTeamsIntegrations = await microsoftTeamsIntegrationDAL.findWithWorkflowIntegrationDetails({ - orgId: actorOrgId + orgId: actorOrgId, + status: WorkflowIntegrationStatus.INSTALLED }); return microsoftTeamsIntegrations.map((integration) => ({ @@ -652,12 +692,13 @@ export const microsoftTeamsServiceFactory = ({ getMicrosoftTeamsIntegrationById, updateMicrosoftTeamsIntegration, deleteMicrosoftTeamsIntegration, - createMicrosoftTeamsIntegration, + completeMicrosoftTeamsIntegration, initializeTeamsBot, getTeams, handleMessageEndpoint, start, sendNotification, - checkInstallationStatus + checkInstallationStatus, + getClientId }; }; diff --git a/backend/src/services/microsoft-teams/microsoft-teams-types.ts b/backend/src/services/microsoft-teams/microsoft-teams-types.ts index bf1f5db82..ccae18f8a 100644 --- a/backend/src/services/microsoft-teams/microsoft-teams-types.ts +++ b/backend/src/services/microsoft-teams/microsoft-teams-types.ts @@ -3,10 +3,13 @@ import { TNotification } from "@app/lib/workflow-integrations/types"; export type TGetMicrosoftTeamsIntegrationByOrgDTO = Omit; +export type TGetClientIdDTO = Omit; + export type TCreateMicrosoftTeamsIntegrationDTO = Omit & { - slug: string; - description?: string; tenantId: string; + slug: string; + redirectUri: string; + description?: string; }; export type TCheckInstallationStatusDTO = { workflowIntegrationId: string } & Omit; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index fa74d0a9f..588568428 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -67,7 +67,7 @@ import { validateSlackChannelsField } from "../slack/slack-auth-validators"; import { TSlackIntegrationDALFactory } from "../slack/slack-integration-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { TUserDALFactory } from "../user/user-dal"; -import { WorkflowIntegration } from "../workflow-integration/workflow-integration-types"; +import { WorkflowIntegration, WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types"; import { TProjectDALFactory } from "./project-dal"; import { assignWorkspaceKeysToMembers, bootstrapSshProject, createProjectKey } from "./project-fns"; import { TProjectQueueFactory } from "./project-queue"; @@ -1539,6 +1539,12 @@ export const projectServiceFactory = ({ }); } + if (microsoftTeamsIntegration.status !== WorkflowIntegrationStatus.INSTALLED) { + throw new BadRequestError({ + message: "Microsoft Teams integration is not properly installed in your tenant." + }); + } + if (microsoftTeamsIntegration.orgId !== actorOrgId) { throw new ForbiddenRequestError({ message: "Selected Microsoft Teams integration is not in the same organization" diff --git a/frontend/src/const/routes.ts b/frontend/src/const/routes.ts index cd3c6675f..8b3cc2104 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -17,6 +17,12 @@ export const ROUTE_PATHS = Object.freeze({ PasswordSetupPage: setRoute("/password-setup", "/_authenticate/password-setup") }, Organization: { + Settings: { + OauthCallbackPage: setRoute( + "/organization/settings/oauth/callback", + "/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback" + ) + }, SecretScanning: setRoute( "/organization/secret-scanning", "/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning" diff --git a/frontend/src/hooks/api/workflowIntegrations/queries.tsx b/frontend/src/hooks/api/workflowIntegrations/queries.tsx index 2cba52a8a..999215f1f 100644 --- a/frontend/src/hooks/api/workflowIntegrations/queries.tsx +++ b/frontend/src/hooks/api/workflowIntegrations/queries.tsx @@ -23,7 +23,8 @@ export const workflowIntegrationKeys = { { id }, "microsoft-teams-workflow-integration-teams" ], - getSlackIntegrationChannels: (id?: string) => [{ id }, "slack-workflow-integration-channels"] + getSlackIntegrationChannels: (id?: string) => [{ id }, "slack-workflow-integration-channels"], + getMicrosoftTeamsClientId: () => ["microsoft-teams-client-id"] }; export const fetchSlackInstallUrl = async ({ @@ -104,6 +105,14 @@ export const fetchMicrosoftTeamsIntegrationTeams = async (id?: string) => { return data; }; +export const fetchMicrosoftTeamsClientId = async () => { + const { data } = await apiRequest.get<{ clientId: string }>( + "/api/v1/workflow-integrations/microsoft-teams/client-id" + ); + + return data; +}; + export const useGetMicrosoftTeamsIntegrations = (orgId?: string) => useQuery({ queryKey: workflowIntegrationKeys.getMicrosoftTeamsIntegrations(orgId), @@ -145,3 +154,10 @@ export const useGetWorkflowIntegrations = (id?: string) => queryFn: () => fetchWorkflowIntegrations(), enabled: Boolean(id) }); + +export const useGetMicrosoftTeamsClientId = () => + useQuery({ + queryKey: workflowIntegrationKeys.getMicrosoftTeamsClientId(), + queryFn: () => fetchMicrosoftTeamsClientId(), + enabled: true + }); diff --git a/frontend/src/hooks/api/workflowIntegrations/types.ts b/frontend/src/hooks/api/workflowIntegrations/types.ts index c5d8d7478..9c9eae430 100644 --- a/frontend/src/hooks/api/workflowIntegrations/types.ts +++ b/frontend/src/hooks/api/workflowIntegrations/types.ts @@ -59,10 +59,11 @@ export type TUpdateMicrosoftTeamsIntegrationDTO = { }; export type TCreateMicrosoftTeamsIntegrationDTO = { - orgId: string; tenantId: string; slug: string; description?: string; + redirectUri: string; + orgId: string; }; export type TDeleteSlackIntegrationDTO = { diff --git a/frontend/src/pages/admin/OverviewPage/components/MicrosoftTeamsIntegrationForm.tsx b/frontend/src/pages/admin/OverviewPage/components/MicrosoftTeamsIntegrationForm.tsx index fb7b062d7..f1eba8e91 100644 --- a/frontend/src/pages/admin/OverviewPage/components/MicrosoftTeamsIntegrationForm.tsx +++ b/frontend/src/pages/admin/OverviewPage/components/MicrosoftTeamsIntegrationForm.tsx @@ -80,11 +80,18 @@ export const MicrosoftTeamsIntegrationForm = ({ adminIntegrationsConfig }: Props
-
- Step 1: Create Microsoft Teams bot +
+ Step 1: Create and configure Microsoft Teams bot and Azure Resources. Please refer + to the documentation below for more information.
- + + +
Step 2: Configure your instance-wide settings to enable integration with Microsoft diff --git a/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx b/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx new file mode 100644 index 000000000..a968401c9 --- /dev/null +++ b/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx @@ -0,0 +1,99 @@ +import { useCallback, useEffect, useState } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { ContentLoader } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useOrganization } from "@app/context"; +import { useCreateMicrosoftTeamsIntegration } from "@app/hooks/api"; + +const stateSchema = z.object({ + redirectUri: z.string(), + tenantId: z.string(), + slug: z.string(), + description: z.string().optional(), + csrfToken: z.string() +}); + +export const OAuthCallbackPage = () => { + const navigate = useNavigate(); + const { currentOrg } = useOrganization(); + const [isReady, setIsReady] = useState(false); + + const search = useSearch({ + from: ROUTE_PATHS.Organization.Settings.OauthCallbackPage.id + }); + + const createMicrosoftTeamsWorkflowIntegration = useCreateMicrosoftTeamsIntegration(); + + const { state: rawState, tenant: stateTenant, admin_consent: adminConsent } = search; + + const state = stateSchema.parse(rawState); + + const clearState = () => { + if (state.csrfToken !== localStorage.getItem("latestCSRFToken")) { + throw new Error("Invalid CSRF token"); + } + + localStorage.removeItem("latestCSRFToken"); + }; + + const handleMicrosoftTeams = useCallback(async () => { + clearState(); + + if (Boolean(adminConsent.toLowerCase()) !== true) { + throw new Error("Failed to grant admin consent"); + } + + if (stateTenant !== state.tenantId) { + throw new Error(`Invalid tenant ID. Expected ${stateTenant}, got ${state.tenantId}`); + } + + await createMicrosoftTeamsWorkflowIntegration.mutateAsync({ + orgId: currentOrg.id, + tenantId: state.tenantId, + slug: state.slug, + description: state.description ?? "", + redirectUri: state.redirectUri + }); + }, []); + + // Ensure that the localstorage is ready for use, to avoid the form data being malformed + useEffect(() => { + if (!isReady) { + setIsReady(!!localStorage.length); + } + }, [localStorage.length]); + + useEffect(() => { + if (!isReady) return; + + (async () => { + try { + await handleMicrosoftTeams(); + + createNotification({ + text: "Successfully granted Microsoft Teams admin consent", + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to grant Microsoft Teams admin consent", + type: "error" + }); + } + + navigate({ + to: ROUTE_PATHS.Organization.SettingsPage.path + }); + })(); + }, [isReady]); + + return ( +
+ +
+ ); +}; diff --git a/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/route.tsx b/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/route.tsx new file mode 100644 index 000000000..7cc262518 --- /dev/null +++ b/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/route.tsx @@ -0,0 +1,27 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { OAuthCallbackPage } from "./OauthCallbackPage"; + +const SettingsOAuthCallbackPageQueryParamsSchema = z.object({ + state: z + .object({ + tenantId: z.string(), + slug: z.string(), + description: z.string().optional(), + redirectUri: z.string(), + csrfToken: z.string() + }) + .nullable() + .catch(null), + tenant: z.string().catch(""), + admin_consent: z.string().catch("") +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback" +)({ + component: OAuthCallbackPage, + validateSearch: zodValidator(SettingsOAuthCallbackPageQueryParamsSchema) +}); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/MicrosoftTeamsIntegrationForm.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/MicrosoftTeamsIntegrationForm.tsx index 9dbc434fe..4c05c68c9 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/MicrosoftTeamsIntegrationForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/MicrosoftTeamsIntegrationForm.tsx @@ -1,3 +1,5 @@ +import crypto from "crypto"; + import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -7,7 +9,7 @@ import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input } from "@app/components/v2"; import { useOrganization } from "@app/context"; import { - useCreateMicrosoftTeamsIntegration, + useGetMicrosoftTeamsClientId, useGetMicrosoftTeamsIntegrationById, useUpdateMicrosoftTeamsIntegration } from "@app/hooks/api"; @@ -38,8 +40,8 @@ export const MicrosoftTeamsIntegrationForm = ({ id, onClose }: Props) => { const { currentOrg } = useOrganization(); const { data: microsoftTeamsIntegration } = useGetMicrosoftTeamsIntegrationById(id); - const { mutateAsync: createMicrosoftTeamsIntegration } = useCreateMicrosoftTeamsIntegration(); const { mutateAsync: updateMicrosoftTeamsIntegration } = useUpdateMicrosoftTeamsIntegration(); + const { data: microsoftTeamsClientId } = useGetMicrosoftTeamsClientId(); useEffect(() => { if (microsoftTeamsIntegration) { @@ -54,6 +56,14 @@ export const MicrosoftTeamsIntegrationForm = ({ id, onClose }: Props) => { description, tenantId }: TMicrosoftTeamsFormData) => { + if (!microsoftTeamsClientId) { + createNotification({ + text: "Microsoft Teams client ID is not set. Please contact your instance administrator.", + type: "error" + }); + return; + } + if (id && microsoftTeamsIntegration) { if (!currentOrg) { return; @@ -81,23 +91,37 @@ export const MicrosoftTeamsIntegrationForm = ({ id, onClose }: Props) => { onClose(); } else { - await createMicrosoftTeamsIntegration({ - orgId: currentOrg.id, + const csrfToken = crypto.randomBytes(32).toString("hex"); + localStorage.setItem("latestCSRFToken", csrfToken); + + const state = { + redirectUri: `${window.location.origin}/organization/settings/oauth/callback`, tenantId, slug, - description - }); + description, + csrfToken + }; - onClose(); - createNotification({ - text: "Successfully created Microsoft Teams integration", - type: "success" - }); + const url = `https://login.microsoftonline.com/${tenantId}/adminconsent?client_id=${microsoftTeamsClientId.clientId}&redirect_uri=${state.redirectUri}&state=${encodeURIComponent(JSON.stringify(state))}`; + window.location.href = url; } }; return (
+
+ For seamless installations, ensure that the Infisical bot is already installed in your + Microsoft Teams tenant. For more information, please refer to the{" "} + + Microsoft Teams Workflow Integration Documentation + + , which will guide you through the download and installation process. +
organizationLayoutRoute, } as any) +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute = + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsImport.update({ + id: '/settings', + path: '/settings', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, + } as any) + const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport.update({ id: '/secret-sharing', @@ -521,14 +534,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRoute = getParentRoute: () => organizationLayoutRoute, } as any) -const organizationSettingsPageRouteRoute = - organizationSettingsPageRouteImport.update({ - id: '/settings', - path: '/settings', - getParentRoute: () => - AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, - } as any) - const organizationSecretScanningPageRouteRoute = organizationSecretScanningPageRouteImport.update({ id: '/secret-scanning', @@ -670,6 +675,14 @@ const organizationCertManagerOverviewPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) +const organizationSettingsPageRouteRoute = + organizationSettingsPageRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute, + } as any) + const organizationSecretSharingPageRouteRoute = organizationSecretSharingPageRouteImport.update({ id: '/', @@ -864,6 +877,14 @@ const secretManagerIPAllowlistPageRouteRoute = getParentRoute: () => secretManagerLayoutRoute, } as any) +const organizationSettingsPageOauthCallbackPageRouteRoute = + organizationSettingsPageOauthCallbackPageRouteImport.update({ + id: '/oauth/callback', + path: '/oauth/callback', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute, + } as any) + const kmsSettingsPageRouteRoute = kmsSettingsPageRouteImport.update({ id: '/settings', path: '/settings', @@ -1952,13 +1973,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationSecretScanningPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } - '/_authenticate/_inject-org-details/_org-layout/organization/settings': { - id: '/_authenticate/_inject-org-details/_org-layout/organization/settings' - path: '/settings' - fullPath: '/organization/settings' - preLoaderRoute: typeof organizationSettingsPageRouteImport - parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport - } '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': { id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId' path: '/cert-manager/$projectId' @@ -1994,6 +2008,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } + '/_authenticate/_inject-org-details/_org-layout/organization/settings': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/settings' + path: '/settings' + fullPath: '/organization/settings' + preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport + } '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': { id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId' path: '/secret-manager/$projectId' @@ -2029,6 +2050,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationSecretSharingPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport } + '/_authenticate/_inject-org-details/_org-layout/organization/settings/': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/settings/' + path: '/' + fullPath: '/organization/settings/' + preLoaderRoute: typeof organizationSettingsPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsImport + } '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': { id: '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview' path: '/cert-manager/overview' @@ -2169,6 +2197,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof kmsSettingsPageRouteImport parentRoute: typeof kmsLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback' + path: '/oauth/callback' + fullPath: '/organization/settings/oauth/callback' + preLoaderRoute: typeof organizationSettingsPageOauthCallbackPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsImport + } '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/allowlist': { id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/allowlist' path: '/allowlist' @@ -3090,16 +3125,33 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChi AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren, ) +interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteChildren { + organizationSettingsPageRouteRoute: typeof organizationSettingsPageRouteRoute + organizationSettingsPageOauthCallbackPageRouteRoute: typeof organizationSettingsPageOauthCallbackPageRouteRoute +} + +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteChildren = + { + organizationSettingsPageRouteRoute: organizationSettingsPageRouteRoute, + organizationSettingsPageOauthCallbackPageRouteRoute: + organizationSettingsPageOauthCallbackPageRouteRoute, + } + +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren = + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute._addFileChildren( + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteChildren, + ) + interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren { organizationAccessManagementPageRouteRoute: typeof organizationAccessManagementPageRouteRoute organizationAdminPageRouteRoute: typeof organizationAdminPageRouteRoute organizationAuditLogsPageRouteRoute: typeof organizationAuditLogsPageRouteRoute organizationBillingPageRouteRoute: typeof organizationBillingPageRouteRoute organizationSecretScanningPageRouteRoute: typeof organizationSecretScanningPageRouteRoute - organizationSettingsPageRouteRoute: typeof organizationSettingsPageRouteRoute AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren organizationCertManagerOverviewPageRouteRoute: typeof organizationCertManagerOverviewPageRouteRoute organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute organizationIdentityDetailsByIDPageRouteRoute: typeof organizationIdentityDetailsByIDPageRouteRoute @@ -3119,13 +3171,14 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica organizationBillingPageRouteRoute: organizationBillingPageRouteRoute, organizationSecretScanningPageRouteRoute: organizationSecretScanningPageRouteRoute, - organizationSettingsPageRouteRoute: organizationSettingsPageRouteRoute, AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren, + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: + AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren, organizationCertManagerOverviewPageRouteRoute: organizationCertManagerOverviewPageRouteRoute, organizationGroupDetailsByIDPageRouteRoute: @@ -3809,17 +3862,18 @@ export interface FileRoutesByFullPath { '/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/organization/billing': typeof organizationBillingPageRouteRoute '/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute - '/organization/settings': typeof organizationSettingsPageRouteRoute '/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren '/kms/$projectId': typeof kmsLayoutRouteWithChildren '/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren '/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren '/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren + '/organization/settings': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren '/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren '/ssh/$projectId': typeof sshLayoutRouteWithChildren '/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/organization/gateways/': typeof organizationGatewaysGatewayListPageRouteRoute '/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute + '/organization/settings/': typeof organizationSettingsPageRouteRoute '/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -3836,6 +3890,7 @@ export interface FileRoutesByFullPath { '/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute '/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute '/kms/$projectId/settings': typeof kmsSettingsPageRouteRoute + '/organization/settings/oauth/callback': typeof organizationSettingsPageOauthCallbackPageRouteRoute '/secret-manager/$projectId/allowlist': typeof secretManagerIPAllowlistPageRouteRoute '/secret-manager/$projectId/approval': typeof secretManagerSecretApprovalsPageRouteRoute '/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute @@ -3989,7 +4044,6 @@ export interface FileRoutesByTo { '/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/organization/billing': typeof organizationBillingPageRouteRoute '/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute - '/organization/settings': typeof organizationSettingsPageRouteRoute '/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren '/kms/$projectId': typeof kmsLayoutRouteWithChildren '/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren @@ -3997,6 +4051,7 @@ export interface FileRoutesByTo { '/organization/app-connections': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/organization/gateways': typeof organizationGatewaysGatewayListPageRouteRoute '/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute + '/organization/settings': typeof organizationSettingsPageRouteRoute '/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -4013,6 +4068,7 @@ export interface FileRoutesByTo { '/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute '/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute '/kms/$projectId/settings': typeof kmsSettingsPageRouteRoute + '/organization/settings/oauth/callback': typeof organizationSettingsPageOauthCallbackPageRouteRoute '/secret-manager/$projectId/allowlist': typeof secretManagerIPAllowlistPageRouteRoute '/secret-manager/$projectId/approval': typeof secretManagerSecretApprovalsPageRouteRoute '/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute @@ -4175,17 +4231,18 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/billing': typeof organizationBillingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute - '/_authenticate/_inject-org-details/_org-layout/organization/settings': typeof organizationSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/kms/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren + '/_authenticate/_inject-org-details/_org-layout/organization/settings': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSshProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/gateways/': typeof organizationGatewaysGatewayListPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute + '/_authenticate/_inject-org-details/_org-layout/organization/settings/': typeof organizationSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -4206,6 +4263,7 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': typeof kmsKmipPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview': typeof kmsOverviewPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/settings': typeof kmsSettingsPageRouteRoute + '/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback': typeof organizationSettingsPageOauthCallbackPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/allowlist': typeof secretManagerIPAllowlistPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/approval': typeof secretManagerSecretApprovalsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/overview': typeof secretManagerOverviewPageRouteRoute @@ -4365,17 +4423,18 @@ export interface FileRouteTypes { | '/organization/audit-logs' | '/organization/billing' | '/organization/secret-scanning' - | '/organization/settings' | '/cert-manager/$projectId' | '/kms/$projectId' | '/organization/app-connections' | '/organization/gateways' | '/organization/secret-sharing' + | '/organization/settings' | '/secret-manager/$projectId' | '/ssh/$projectId' | '/organization/app-connections/' | '/organization/gateways/' | '/organization/secret-sharing/' + | '/organization/settings/' | '/organization/cert-manager/overview' | '/organization/groups/$groupId' | '/organization/identities/$identityId' @@ -4392,6 +4451,7 @@ export interface FileRouteTypes { | '/kms/$projectId/kmip' | '/kms/$projectId/overview' | '/kms/$projectId/settings' + | '/organization/settings/oauth/callback' | '/secret-manager/$projectId/allowlist' | '/secret-manager/$projectId/approval' | '/secret-manager/$projectId/overview' @@ -4544,7 +4604,6 @@ export interface FileRouteTypes { | '/organization/audit-logs' | '/organization/billing' | '/organization/secret-scanning' - | '/organization/settings' | '/cert-manager/$projectId' | '/kms/$projectId' | '/secret-manager/$projectId' @@ -4552,6 +4611,7 @@ export interface FileRouteTypes { | '/organization/app-connections' | '/organization/gateways' | '/organization/secret-sharing' + | '/organization/settings' | '/organization/cert-manager/overview' | '/organization/groups/$groupId' | '/organization/identities/$identityId' @@ -4568,6 +4628,7 @@ export interface FileRouteTypes { | '/kms/$projectId/kmip' | '/kms/$projectId/overview' | '/kms/$projectId/settings' + | '/organization/settings/oauth/callback' | '/secret-manager/$projectId/allowlist' | '/secret-manager/$projectId/approval' | '/secret-manager/$projectId/overview' @@ -4728,17 +4789,18 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/organization/billing' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning' - | '/_authenticate/_inject-org-details/_org-layout/organization/settings' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections' | '/_authenticate/_inject-org-details/_org-layout/organization/gateways' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' + | '/_authenticate/_inject-org-details/_org-layout/organization/settings' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/' | '/_authenticate/_inject-org-details/_org-layout/organization/gateways/' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/' + | '/_authenticate/_inject-org-details/_org-layout/organization/settings/' | '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview' | '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId' | '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId' @@ -4759,6 +4821,7 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/settings' + | '/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/allowlist' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/approval' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/overview' @@ -5117,10 +5180,10 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/audit-logs", "/_authenticate/_inject-org-details/_org-layout/organization/billing", "/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning", - "/_authenticate/_inject-org-details/_org-layout/organization/settings", "/_authenticate/_inject-org-details/_org-layout/organization/app-connections", "/_authenticate/_inject-org-details/_org-layout/organization/gateways", "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing", + "/_authenticate/_inject-org-details/_org-layout/organization/settings", "/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview", "/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId", @@ -5162,10 +5225,6 @@ export const routeTree = rootRoute "filePath": "organization/SecretScanningPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" }, - "/_authenticate/_inject-org-details/_org-layout/organization/settings": { - "filePath": "organization/SettingsPage/route.tsx", - "parent": "/_authenticate/_inject-org-details/_org-layout/organization" - }, "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout", @@ -5203,6 +5262,14 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/settings" ] }, + "/_authenticate/_inject-org-details/_org-layout/organization/settings": { + "filePath": "", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization", + "children": [ + "/_authenticate/_inject-org-details/_org-layout/organization/settings/", + "/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback" + ] + }, "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout", @@ -5229,6 +5296,10 @@ export const routeTree = rootRoute "filePath": "organization/SecretSharingPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" }, + "/_authenticate/_inject-org-details/_org-layout/organization/settings/": { + "filePath": "organization/SettingsPage/route.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization/settings" + }, "/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview": { "filePath": "organization/CertManagerOverviewPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" @@ -5354,6 +5425,10 @@ export const routeTree = rootRoute "filePath": "kms/SettingsPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout" }, + "/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback": { + "filePath": "organization/SettingsPage/OauthCallbackPage/route.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization/settings" + }, "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/allowlist": { "filePath": "secret-manager/IPAllowlistPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 6c5c2832e..4bfb82fad 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -20,7 +20,10 @@ const organizationRoutes = route("/organization", [ index("organization/SecretSharingPage/route.tsx"), route("/settings", "organization/SecretSharingSettingsPage/route.tsx") ]), - route("/settings", "organization/SettingsPage/route.tsx"), + route("/settings", [ + index("organization/SettingsPage/route.tsx"), + route("/oauth/callback", "organization/SettingsPage/OauthCallbackPage/route.tsx") + ]), route("/secret-scanning", "organization/SecretScanningPage/route.tsx"), route("/groups/$groupId", "organization/GroupDetailsByIDPage/route.tsx"), route("/members/$membershipId", "organization/UserDetailsByIDPage/route.tsx"),