diff --git a/docs/deployment/authentication/azuread-auth.mdx b/docs/deployment/authentication/azuread-auth.mdx index 2ee4fd365..8b9349127 100644 --- a/docs/deployment/authentication/azuread-auth.mdx +++ b/docs/deployment/authentication/azuread-auth.mdx @@ -8,7 +8,7 @@ This feature is a part of Keep Enterprise. Talk to us to get access: https://www.keephq.dev/meet-keep -Keep supports enterprise authentication through Azure Active Directory (Azure AD), enabling organizations to use their existing Microsoft identity platform for secure access management. +Keep supports enterprise authentication through Azure Entre ID (formerly known as Azure AD), enabling organizations to use their existing Microsoft identity platform for secure access management. ## When to Use @@ -49,9 +49,9 @@ We use "Web" platform instead of "Single Page Application (SPA)" because Keep's -For localhost, the redirect would be http://localhost:3000/api/auth/callback/azure-ad +For localhost, the redirect would be http://localhost:3000/api/auth/callback/microsoft-entra-id -For production, it should be something like http://your_keep_frontend_domain/api/auth/callback/azure-ad +For production, it should be something like http://your_keep_frontend_domain/api/auth/callback/microsoft-entra-id @@ -177,7 +177,7 @@ How to get: | AUTH_TYPE | Set to 'AZUREAD' for Azure AD authentication | Yes | - | | KEEP_AZUREAD_CLIENT_ID | Your Azure AD application (client) ID | Yes | - | | KEEP_AZUREAD_CLIENT_SECRET | Your client secret | Yes | - | -| KEEP_AZUREAD_TENANT_ID | Your Azure AD tenant ID | Yes | - | +| KEEP_AZUREAD_ISSUER | Your Azure AD tenant ID | Yes | - | | NEXTAUTH_URL | Your Keep application URL | Yes | - | | NEXTAUTH_SECRET | Random string for NextAuth.js | Yes | - | diff --git a/docs/images/azuread_3.png b/docs/images/azuread_3.png index c7466ca36..4e91c7263 100644 Binary files a/docs/images/azuread_3.png and b/docs/images/azuread_3.png differ diff --git a/keep-ui/app/(keep)/alerts/alert-table.tsx b/keep-ui/app/(keep)/alerts/alert-table.tsx index 24b66ec7e..3fcbff454 100644 --- a/keep-ui/app/(keep)/alerts/alert-table.tsx +++ b/keep-ui/app/(keep)/alerts/alert-table.tsx @@ -265,15 +265,20 @@ export function AlertTable({ }; return ( -
- -
- {/* Setting min-h-10 to avoid jumping when actions are shown */} + // Add h-screen to make it full height and remove the default flex-col gap +
+ {/* Add padding to account for any top nav/header */} +
+ +
+ + {/* Make actions/presets section fixed height */} +
{selectedRowIds.length ? ( )}
-
-
- -
-
- -
- {/* For dynamic preset, add alert tabs*/} - {!presetStatic && ( - - )} -
- - - -
-
- + + {/* Main content area - uses flex-grow to fill remaining space */} +
+
+ {/* Facets sidebar */} +
+ +
+ + {/* Table section */} +
+ +
+ {!presetStatic && ( +
+ +
+ )} + +
+ + {/* Make table wrapper scrollable */} +
+ + + +
+
+
+ +
-
+ + {/* Pagination footer - fixed height */} +
+ setIsSidebarOpen(false)} diff --git a/keep-ui/app/(keep)/alerts/alerts-table-body.tsx b/keep-ui/app/(keep)/alerts/alerts-table-body.tsx index 0ee55b8a0..08a4c61bf 100644 --- a/keep-ui/app/(keep)/alerts/alerts-table-body.tsx +++ b/keep-ui/app/(keep)/alerts/alerts-table-body.tsx @@ -35,13 +35,15 @@ export function AlertsTableBody({ if (showEmptyState) { return ( <> -
- +
+
+ +
{modalOpen && ( (); useEffect(() => { - console.log("Fetching providers"); async function fetchProviders() { - console.log("Fetching providers 2"); const response = await getProviders(); setProviders(response as Providers); } - console.log("Fetching providers 3"); fetchProviders(); - console.log("Fetching providers 4"); }, []); useEffect(() => { @@ -69,9 +65,9 @@ export default function SignInForm({ params }: { params?: { amt: string } }) { } else if (providers.keycloak) { console.log("Signing in with keycloak provider"); signIn("keycloak", { callbackUrl: "/" }); - } else if (providers["azure-ad"]) { + } else if (providers["microsoft-entra-id"]) { console.log("Signing in with Azure AD provider"); - signIn("azure-ad", { callbackUrl: "/" }); + signIn("microsoft-entra-id", { callbackUrl: "/" }); } else if ( providers.credentials && providers.credentials.name == "NoAuth" diff --git a/keep-ui/auth.ts b/keep-ui/auth.ts index 2a668d5e7..bd5bd8c92 100644 --- a/keep-ui/auth.ts +++ b/keep-ui/auth.ts @@ -8,9 +8,8 @@ import MicrosoftEntraID from "@auth/core/providers/microsoft-entra-id"; import { AuthError } from "next-auth"; import { AuthenticationError, AuthErrorCodes } from "@/errors"; import type { JWT } from "@auth/core/jwt"; - -// see https://authjs.dev/guides/corporate-proxy -import { ProxyAgent, fetch as undici } from "undici"; +// https://github.com/nextauthjs/next-auth/issues/11028 +import { initProxyFetch } from "./proxyFetch"; export class BackendRefusedError extends AuthError { static type = "BackendRefusedError"; @@ -38,22 +37,61 @@ const authType = ? AuthType.NOAUTH : (authTypeEnv as AuthType); -// Proxy configuration const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy; -const dispatcher = proxyUrl ? new ProxyAgent(proxyUrl) : undefined; -function proxyFetch( - ...args: Parameters -): ReturnType { - if (!dispatcher) return fetch(...args); - // @ts-expect-error `undici` has a `duplex` option - return undici(args[0], { ...args[1], dispatcher }); +// Helper function to dynamically import proxyFetch only when needed +async function getProxyFetch() { + if (typeof window === "undefined" && !process.env.NEXT_RUNTIME) { + // Only import in Node.js environment, not Edge + try { + const { initProxyFetch } = await import("./proxyFetch"); + return initProxyFetch(); + } catch (e) { + console.warn("Failed to load proxy fetch:", e); + return null; + } + } + return null; } +// Create Azure AD provider configuration +const createAzureADProvider = () => { + const baseConfig = { + clientId: process.env.KEEP_AZUREAD_CLIENT_ID!, + clientSecret: process.env.KEEP_AZUREAD_CLIENT_SECRET!, + // The issuer is the URL of the identity provider + issuer: `https://login.microsoftonline.com/${process.env + .KEEP_AZUREAD_TENANT_ID!}/v2.0`, + authorization: { + params: { + scope: `api://${process.env + .KEEP_AZUREAD_CLIENT_ID!}/default openid profile email`, + }, + }, + client: { + token_endpoint_auth_method: "client_secret_post", + }, + }; + + if (!proxyUrl) { + console.log("Using built-in fetch for Azure AD provider"); + return MicrosoftEntraID(baseConfig); + } + + console.log("Using proxy fetch for Azure AD provider"); + return MicrosoftEntraID({ + ...baseConfig, + async [customFetch](...args) { + const proxyFetch = await getProxyFetch(); + return proxyFetch ? proxyFetch(...args) : fetch(...args); + }, + }); +}; + async function refreshAccessToken(token: any) { const issuerUrl = process.env.KEYCLOAK_ISSUER; const refreshTokenUrl = `${issuerUrl}/protocol/openid-connect/token`; @@ -179,21 +217,7 @@ const providerConfigs = { authorization: { params: { scope: "openid email profile roles" } }, }), ], - [AuthType.AZUREAD]: [ - MicrosoftEntraID({ - clientId: process.env.KEEP_AZUREAD_CLIENT_ID!, - clientSecret: process.env.KEEP_AZUREAD_CLIENT_SECRET!, - issuer: process.env.KEEP_AZUREAD_TENANT_ID!, - authorization: { - params: { - scope: `api://${process.env - .KEEP_AZUREAD_CLIENT_ID!}/default openid profile email`, - }, - }, - // see https://authjs.dev/guides/corporate-proxy - ...(proxyFetch && { [customFetch]: proxyFetch }), - }), - ], + [AuthType.AZUREAD]: [createAzureADProvider()], }; // Create the config @@ -225,7 +249,24 @@ const config = { let tenantId: string | undefined = user.tenantId; let role: string | undefined = user.role; // Ensure we always have an accessToken - if (authType == AuthType.AUTH0) { + if (authType === AuthType.AZUREAD) { + // Properly handle Azure AD tokens + accessToken = account.access_token; + // You might want to extract additional claims from the id_token if needed + if (account.id_token) { + try { + // Basic JWT decode (you might want to use a proper JWT library here) + const payload = JSON.parse( + Buffer.from(account.id_token.split(".")[1], "base64").toString() + ); + // Extract any additional claims you need + role = payload.roles?.[0] || "user"; + tenantId = payload.tid || undefined; + } catch (e) { + console.warn("Failed to decode id_token:", e); + } + } + } else if (authType == AuthType.AUTH0) { accessToken = account.id_token; if ((profile as any)?.keep_tenant_id) { tenantId = (profile as any).keep_tenant_id; diff --git a/keep-ui/proxyFetch.ts b/keep-ui/proxyFetch.ts new file mode 100644 index 000000000..38eae2b84 --- /dev/null +++ b/keep-ui/proxyFetch.ts @@ -0,0 +1,20 @@ +// proxyFetch.ts +let proxyFetch: typeof fetch | undefined; + +export async function initProxyFetch() { + const proxyUrl = + process.env.HTTP_PROXY || + process.env.HTTPS_PROXY || + process.env.http_proxy || + process.env.https_proxy; + + if (proxyUrl && typeof window === "undefined") { + const { ProxyAgent, fetch: undici } = await import("undici"); + const dispatcher = new ProxyAgent(proxyUrl); + return (...args: Parameters): ReturnType => { + // @ts-expect-error `undici` has a `duplex` option + return undici(args[0], { ...args[1], dispatcher }); + }; + } + return undefined; +}