Skip to content

Commit

Permalink
feat: new readme
Browse files Browse the repository at this point in the history
  • Loading branch information
shahargl committed Nov 23, 2024
1 parent 344e49a commit 60c4fa5
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 101 deletions.
8 changes: 4 additions & 4 deletions docs/deployment/authentication/azuread-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This feature is a part of Keep Enterprise.
Talk to us to get access: https://www.keephq.dev/meet-keep
</Tip>

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

Expand Down Expand Up @@ -49,9 +49,9 @@ We use "Web" platform instead of "Single Page Application (SPA)" because Keep's
</Info>

<Tip>
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

</Tip>

Expand Down Expand Up @@ -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 | - |

Expand Down
Binary file modified docs/images/azuread_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
133 changes: 77 additions & 56 deletions keep-ui/app/(keep)/alerts/alert-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,20 @@ export function AlertTable({
};

return (
<div className="flex flex-col gap-4 h-full">
<TitleAndFilters
table={table}
alerts={alerts}
presetName={presetName}
onThemeChange={handleThemeChange}
/>
<div className="min-h-10">
{/* 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
<div className="h-screen flex flex-col">
{/* Add padding to account for any top nav/header */}
<div className="pt-4 px-4 flex-none">
<TitleAndFilters
table={table}
alerts={alerts}
presetName={presetName}
onThemeChange={handleThemeChange}
/>
</div>

{/* Make actions/presets section fixed height */}
<div className="h-12 px-4 flex-none">
{selectedRowIds.length ? (
<AlertActions
selectedRowIds={selectedRowIds}
Expand All @@ -292,61 +297,77 @@ export function AlertTable({
/>
)}
</div>
<div className="flex gap-6">
<div className="w-32 min-w-[12rem]">
<AlertFacets
className="sticky top-0"
alerts={alerts}
facetFilters={facetFilters}
setFacetFilters={setFacetFilters}
dynamicFacets={dynamicFacets}
setDynamicFacets={setDynamicFacets}
onDelete={handleFacetDelete}
table={table}
showSkeleton={showSkeleton}
/>
</div>
<div className="flex flex-col gap-4 min-w-0">
<Card className="flex-grow h-full flex flex-col p-0">
<div className="flex-grow">
{/* For dynamic preset, add alert tabs*/}
{!presetStatic && (
<AlertTabs
presetId={presetId}
tabs={tabs}
setTabs={setTabs}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
/>
)}
<div ref={a11yContainerRef} className="sr-only" />
<Table className="[&>table]:table-fixed [&>table]:w-full">
<AlertsTableHeaders
columns={columns}
table={table}
presetName={presetName}
a11yContainerRef={a11yContainerRef}
/>
<AlertsTableBody
table={table}
showSkeleton={showSkeleton}
showEmptyState={showEmptyState}
theme={theme}
onRowClick={handleRowClick}
presetName={presetName}
/>
</Table>
</div>
</Card>

{/* Main content area - uses flex-grow to fill remaining space */}
<div className="flex-grow overflow-hidden px-4 pb-4">
<div className="h-full flex gap-6">
{/* Facets sidebar */}
<div className="w-32 min-w-[12rem] overflow-y-auto">
<AlertFacets
className="sticky top-0"
alerts={alerts}
facetFilters={facetFilters}
setFacetFilters={setFacetFilters}
dynamicFacets={dynamicFacets}
setDynamicFacets={setDynamicFacets}
onDelete={handleFacetDelete}
table={table}
showSkeleton={showSkeleton}
/>
</div>

{/* Table section */}
<div className="flex-1 flex flex-col min-w-0">
<Card className="h-full flex flex-col p-0">
<div className="flex-grow flex flex-col overflow-hidden">
{!presetStatic && (
<div className="flex-none">
<AlertTabs
presetId={presetId}
tabs={tabs}
setTabs={setTabs}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
/>
</div>
)}

<div ref={a11yContainerRef} className="sr-only" />

{/* Make table wrapper scrollable */}
<div className="flex-grow overflow-auto">
<Table className="[&>table]:table-fixed [&>table]:w-full">
<AlertsTableHeaders
columns={columns}
table={table}
presetName={presetName}
a11yContainerRef={a11yContainerRef}
/>
<AlertsTableBody
table={table}
showSkeleton={showSkeleton}
showEmptyState={showEmptyState}
theme={theme}
onRowClick={handleRowClick}
presetName={presetName}
/>
</Table>
</div>
</div>
</Card>
</div>
</div>
</div>
<div className="mt-2 mb-8 pl-[14rem]">

{/* Pagination footer - fixed height */}
<div className="h-16 px-4 flex-none pl-[14rem]">
<AlertPagination
table={table}
presetName={presetName}
isRefreshAllowed={isRefreshAllowed}
/>
</div>

<AlertSidebar
isOpen={isSidebarOpen}
toggle={() => setIsSidebarOpen(false)}
Expand Down
16 changes: 9 additions & 7 deletions keep-ui/app/(keep)/alerts/alerts-table-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ export function AlertsTableBody({
if (showEmptyState) {
return (
<>
<div className="flex flex-col justify-center items-center h-96 w-full absolute p-4">
<EmptyStateCard
title="No alerts to display"
description="It is because you have not connected any data source yet or there are no alerts matching the filter."
buttonText="Add Alert"
onClick={handleModalOpen}
/>
<div className="flex items-center h-full w-full absolute -mt-20">
<div className="flex flex-col justify-center items-center w-full p-4">
<EmptyStateCard
title="No alerts to display"
description="It is because you have not connected any data source yet or there are no alerts matching the filter."
buttonText="Add Alert"
onClick={handleModalOpen}
/>
</div>
</div>
{modalOpen && (
<PushAlertToServerModal
Expand Down
10 changes: 3 additions & 7 deletions keep-ui/app/(signin)/signin/SignInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface Providers {
auth0?: Provider;
credentials?: Provider;
keycloak?: Provider;
"azure-ad"?: Provider;
"microsoft-entra-id"?: Provider;
}

interface SignInFormInputs {
Expand All @@ -42,15 +42,11 @@ export default function SignInForm({ params }: { params?: { amt: string } }) {
} = useForm<SignInFormInputs>();

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(() => {
Expand All @@ -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"
Expand Down
95 changes: 68 additions & 27 deletions keep-ui/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<typeof fetch>
): ReturnType<typeof fetch> {
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`;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
20 changes: 20 additions & 0 deletions keep-ui/proxyFetch.ts
Original file line number Diff line number Diff line change
@@ -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<typeof fetch>): ReturnType<typeof fetch> => {
// @ts-expect-error `undici` has a `duplex` option
return undici(args[0], { ...args[1], dispatcher });
};
}
return undefined;
}

0 comments on commit 60c4fa5

Please sign in to comment.