From d60ff4c83e25866f560c71b6ffca0033c311add9 Mon Sep 17 00:00:00 2001 From: talboren Date: Sun, 4 Jun 2023 15:00:39 +0200 Subject: [PATCH] fix: token bug and remove console.logs (#159) --- keep-ui/app/github-post-installation/page.tsx | 30 ++++----- keep-ui/app/nav.tsx | 5 +- keep-ui/app/navbar.tsx | 9 +-- keep-ui/app/page.tsx | 6 +- keep-ui/app/providers/page.tsx | 19 ++---- keep-ui/app/providers/provider-form.tsx | 26 ++++---- keep-ui/app/providers/provider-row.tsx | 60 ++++++++++++------ keep-ui/app/providers/providers.css | 4 ++ keep-ui/app/providers/table.tsx | 53 ++++++---------- keep-ui/middleware.tsx | 1 - keep-ui/next.config.js | 5 ++ keep-ui/pages/api/auth/[...nextauth].ts | 63 +++++++++---------- keep-ui/utils/customAuth.ts | 7 ++- keep/api/api.py | 4 ++ keep/api/core/dependencies.py | 35 ++++++----- 15 files changed, 172 insertions(+), 155 deletions(-) diff --git a/keep-ui/app/github-post-installation/page.tsx b/keep-ui/app/github-post-installation/page.tsx index 002121a41..a99095d5b 100644 --- a/keep-ui/app/github-post-installation/page.tsx +++ b/keep-ui/app/github-post-installation/page.tsx @@ -1,34 +1,36 @@ import { getServerSession } from "../../utils/customAuth"; -import { redirect } from 'next/navigation'; +import { redirect } from "next/navigation"; import { getApiURL } from "../../utils/apiUrl"; export default async function GithubPostInstallationPage({ - searchParams, + searchParams, }: { - searchParams: { installation_id: string, setup_action: string }, + searchParams: { installation_id: string; setup_action: string }; }) { // https://github.com/nextauthjs/next-auth/pull/5792 - const accessToken = (await getServerSession({ - callbacks: { session: ({ token }) => token }, - }))?.accessToken; + const accessToken = ( + await getServerSession({ + callbacks: { session: ({ token }) => token }, + }) + )?.accessToken; let installedSuccessfully = false; try { const apiUrl = getApiURL(); installedSuccessfully = await fetch(`${apiUrl}/tenant/github`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}` + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, }, - body: JSON.stringify(searchParams) + body: JSON.stringify(searchParams), }) - .then(res => { + .then((res) => { if (!res.ok) { throw new Error(`HTTP error ${res.status}`); } return res.json(); }) - .then(data => data.success); + .then((data) => data.success); // Handle successful installation if (installedSuccessfully) { @@ -44,5 +46,5 @@ export default async function GithubPostInstallationPage({ } return
502 backend error
; } - redirect('/'); -}; + redirect("/"); +} diff --git a/keep-ui/app/nav.tsx b/keep-ui/app/nav.tsx index 5a7b3d36a..088723853 100644 --- a/keep-ui/app/nav.tsx +++ b/keep-ui/app/nav.tsx @@ -1,7 +1,8 @@ import Navbar from './navbar'; import { getServerSession } from '../utils/customAuth'; +import { authOptions } from '../pages/api/auth/[...nextauth]'; export default async function Nav() { - const session = await getServerSession(); - return ; + const session = await getServerSession(authOptions); + return ; } diff --git a/keep-ui/app/navbar.tsx b/keep-ui/app/navbar.tsx index be08647fa..4a40afcdb 100644 --- a/keep-ui/app/navbar.tsx +++ b/keep-ui/app/navbar.tsx @@ -3,18 +3,19 @@ import PHProvider from "./posthog-client"; import { SessionProvider } from "next-auth/react"; import NavbarInner from "./navbar-inner"; +import { Session } from "next-auth"; const isSingleTenant = process.env.NEXT_PUBLIC_AUTH_ENABLED == "false"; -export default function Navbar({ user }: { user: any }) { +export default function Navbar({ session }: { session: Session | null }) { return isSingleTenant ? ( - + ) : ( - + - + ); diff --git a/keep-ui/app/page.tsx b/keep-ui/app/page.tsx index 499c00955..4536d0403 100644 --- a/keep-ui/app/page.tsx +++ b/keep-ui/app/page.tsx @@ -6,6 +6,7 @@ import ErrorComponent from "./error"; import PostHogClient from "./posthog-server"; import { getApiURL } from "../utils/apiUrl"; import Frill from "./frill"; +import { authOptions } from "../pages/api/auth/[...nextauth]"; export const metadata = { title: "Keep Console", @@ -13,10 +14,7 @@ export const metadata = { }; export default async function IndexPage() { - // https://github.com/nextauthjs/next-auth/pull/5792 - const accessToken = await getServerSession({ - callbacks: { session: ({ token }) => token }, - }); + const accessToken = await getServerSession(authOptions); let isGitHubPluginInstalled = false; try { diff --git a/keep-ui/app/providers/page.tsx b/keep-ui/app/providers/page.tsx index cad161ea9..33ddd51da 100644 --- a/keep-ui/app/providers/page.tsx +++ b/keep-ui/app/providers/page.tsx @@ -2,23 +2,17 @@ import { Card, Title, Text } from "@tremor/react"; import ProvidersTable from "./table"; import { getServerSession } from "../../utils/customAuth"; import { getApiURL } from "../../utils/apiUrl"; +import { authOptions } from "../../pages/api/auth/[...nextauth]"; export default async function ProvidersPage() { - console.log("Rendering dashboard page"); - // get the session so we will be able to pass it to the SessionProvider - const session = await getServerSession(); + const session = await getServerSession(authOptions); // force get session to get a token - const accessToken = ( - await getServerSession({ - callbacks: { session: ({ token }) => token }, - }) - )?.accessToken; - - let installed_providers = []; + const accessToken = session?.accessToken; + let installedProviders = []; // Now let's fetch the providers status from the backend try { const apiUrl = getApiURL(); - installed_providers = await fetch(`${apiUrl}/providers`, { + installedProviders = await fetch(`${apiUrl}/providers`, { headers: { Authorization: `Bearer ${accessToken}`, }, @@ -31,13 +25,12 @@ export default async function ProvidersPage() { return
502 backend error
; } - console.log("Dashboard | session:", session); return (
Providers Connect providers to Keep to make your alerts better. - +
); diff --git a/keep-ui/app/providers/provider-form.tsx b/keep-ui/app/providers/provider-form.tsx index 0a82128ac..1a9cf7b3a 100644 --- a/keep-ui/app/providers/provider-form.tsx +++ b/keep-ui/app/providers/provider-form.tsx @@ -4,7 +4,7 @@ import React, { useState } from "react"; import { useSession } from "../../utils/customAuth"; import { Provider } from "./provider-row"; import { getApiURL } from "../../utils/apiUrl"; -import Alert from './alert'; +import Alert from "./alert"; import "./provider-form.css"; type ProviderFormProps = { @@ -25,16 +25,11 @@ const ProviderForm = ({ }); const [formErrors, setFormErrors] = useState({}); const [testResult, setTestResult] = useState(""); - const [connectResult, setConnectResult] = useState(""); const [alertData, setAlertData] = useState([]); const [isConnected, setIsConnected] = useState(false); const { data: session, status, update } = useSession(); - // @ts-ignore - if (!session?.accessToken) { - console.log("No session access token, refreshing session from the server"); - update(); - } + // update(); // TODO - fix the typing here // @ts-ignore @@ -121,9 +116,7 @@ const ProviderForm = ({ const handleTestClick = async () => { try { - const data = await validateAndSubmit( - `${getApiURL()}/providers/test` - ); + const data = await validateAndSubmit(`${getApiURL()}/providers/test`); if (data && data.alerts) { console.log("Test succeessful"); setTestResult("success"); @@ -132,7 +125,7 @@ const ProviderForm = ({ setTestResult("error"); } } catch (error) { - setFormErrors({"error": error.toString()}) + setFormErrors({ error: error.toString() }); console.error("Test failed:", error); } }; @@ -141,13 +134,13 @@ const ProviderForm = ({ validateAndSubmit(`${getApiURL()}/providers/install`) .then((data) => { console.log("Connect Result:", data); - setConnectResult(data.result); setIsConnected(true); }) .catch((error) => { console.error("Connect failed:", error); }); }; + console.log("ProviderForm component loaded"); return ( @@ -156,7 +149,8 @@ const ProviderForm = ({ {provider.authentication.map((method) => (
{formErrors.error && ( -
Error while testing the provider: "{formErrors.error}"
+
+ Error while testing the provider: "{formErrors.error}" +
)} {testResult === "success" && (
@@ -205,7 +201,7 @@ const ProviderForm = ({ {alertData.map((alert) => ( - + ))} diff --git a/keep-ui/app/providers/provider-row.tsx b/keep-ui/app/providers/provider-row.tsx index c88923f23..453c7e2a2 100644 --- a/keep-ui/app/providers/provider-row.tsx +++ b/keep-ui/app/providers/provider-row.tsx @@ -1,9 +1,8 @@ -// @ts-nocheck -import React, { useState, useEffect } from 'react'; -import { TableRow, TableCell } from '@tremor/react'; -import Image from 'next/image'; -import './providers.css'; -import ProviderForm from './provider-form'; +import React, { useState, useEffect } from "react"; +import { TableRow, TableCell } from "@tremor/react"; +import Image from "next/image"; +import "./providers.css"; +import ProviderForm from "./provider-form"; type AuthenticationMethod = { name: string; @@ -12,6 +11,7 @@ type AuthenticationMethod = { placeholder?: string; validation?: (value: string) => boolean; required?: boolean; + value?: string; }; export type Provider = { @@ -35,18 +35,21 @@ const ProviderRow = ({ provider }: ProviderRowProps) => { setExpanded(!expanded); }; - const onFormChange = (formValues) => { + const onFormChange = (formValues: any) => { setFormData(formValues); }; // Update formData with authentication data useEffect(() => { if (provider.connected) { - const authenticationData = provider.authentication.reduce((data, method) => { - const { name } = method; - const value = method.value || ''; - return { ...data, [name]: value }; - }, {}); + const authenticationData = provider.authentication.reduce( + (data, method) => { + const { name } = method; + const value = method.value || ""; + return { ...data, [name]: value }; + }, + {} + ); setFormData(authenticationData); } }, [provider.connected, provider.authentication]); @@ -55,10 +58,19 @@ const ProviderRow = ({ provider }: ProviderRowProps) => { return ( <> - +
- {provider.name} + {provider.name}
{provider.name}
@@ -66,11 +78,19 @@ const ProviderRow = ({ provider }: ProviderRowProps) => { {isComingSoonProvider ? ( -
🚧 Coming Soon 🚧
+
Coming Soon 🚧
) : (
-
)} @@ -80,7 +100,11 @@ const ProviderRow = ({ provider }: ProviderRowProps) => {
- +
diff --git a/keep-ui/app/providers/providers.css b/keep-ui/app/providers/providers.css index 1af262195..abffffead 100644 --- a/keep-ui/app/providers/providers.css +++ b/keep-ui/app/providers/providers.css @@ -65,6 +65,10 @@ background-color: lightgreen; } + .coming-soon { + opacity: 0.6; + } + .coming-soon-label { display: inline-block; background-color: #f27e12; diff --git a/keep-ui/app/providers/table.tsx b/keep-ui/app/providers/table.tsx index a614e3ed7..755abc35f 100644 --- a/keep-ui/app/providers/table.tsx +++ b/keep-ui/app/providers/table.tsx @@ -1,32 +1,28 @@ -// @ts-nocheck "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { Table } from "@tremor/react"; import ProviderRow from "./provider-row"; -import { Provider } from "./provider-row"; import Providers from "./providers"; import { SessionProvider } from "next-auth/react"; +import { Session } from "next-auth"; const isSingleTenant = process.env.NEXT_PUBLIC_AUTH_ENABLED == "false"; -type ProvidersTableProps = { - providers: Provider[]; -}; +interface InstalledProviders { + name: string; + details: { authentication: { [key: string]: string } }; +} // This runs on the client -const ProvidersTable = ({ installed_providers }) => { - const [expandedProviderId, setExpandedProviderId] = useState( - null - ); - - const handleExpand = (providerId: string) => { - setExpandedProviderId((prevState) => - prevState === providerId ? null : providerId - ); - }; - +export default function ProvidersTable({ + session, + installedProviders, +}: { + session: Session | null; + installedProviders: InstalledProviders[]; +}) { const updatedProviders = Providers.map((provider) => { - const installedProvider = installed_providers.find( + const installedProvider = installedProviders.find( (installedProvider) => installedProvider.name === provider.id ); const connected = !!installedProvider; @@ -56,29 +52,16 @@ const ProvidersTable = ({ installed_providers }) => { {isSingleTenant ? ( updatedProviders.map((provider) => ( - + )) ) : ( - + {updatedProviders.map((provider) => ( - + ))} )} ); - -}; - -export default ProvidersTable; +} diff --git a/keep-ui/middleware.tsx b/keep-ui/middleware.tsx index eac65a7a4..7350b20ae 100644 --- a/keep-ui/middleware.tsx +++ b/keep-ui/middleware.tsx @@ -1,7 +1,6 @@ import { withAuth } from "next-auth/middleware"; const isSingleTenant = process.env.NEXT_PUBLIC_AUTH_ENABLED == "false"; -console.log(isSingleTenant ? "Single tenant mode" : "Multi tenant mode"); export default isSingleTenant ? () => {} diff --git a/keep-ui/next.config.js b/keep-ui/next.config.js index 7f31e6cdc..4d596cdc4 100644 --- a/keep-ui/next.config.js +++ b/keep-ui/next.config.js @@ -21,6 +21,11 @@ const nextConfig = { appDir: true, serverComponentsExternalPackages: ["@tremor/react"], }, + compiler: { + removeConsole: { + exclude: ["error"], + }, + }, output: "standalone", productionBrowserSourceMaps: process.env.ENV === "development", }; diff --git a/keep-ui/pages/api/auth/[...nextauth].ts b/keep-ui/pages/api/auth/[...nextauth].ts index f74451ecc..011928a9c 100644 --- a/keep-ui/pages/api/auth/[...nextauth].ts +++ b/keep-ui/pages/api/auth/[...nextauth].ts @@ -1,37 +1,34 @@ -import NextAuth from "next-auth"; +import NextAuth, { AuthOptions } from "next-auth"; import Auth0Provider from "next-auth/providers/auth0"; const isSingleTenant = process.env.NEXT_PUBLIC_AUTH_ENABLED == "false"; +export const authOptions = { + providers: [ + Auth0Provider({ + clientId: process.env.AUTH0_CLIENT_ID!, + clientSecret: process.env.AUTH0_CLIENT_SECRET!, + issuer: process.env.AUTH0_ISSUER!, + authorization: `https://${process.env.AUTH0_DOMAIN}/authorize?response_type=code&prompt=login`, + }), + ], + session: { + strategy: "jwt", + maxAge: 30 * 24 * 60 * 60, // 30 days + }, + callbacks: { + async jwt({ token, account }) { + // https://next-auth.js.org/configuration/callbacks#jwt-callback + if (account) { + token.accessToken = account.id_token; + } + return token; + }, + async session({ session, token }) { + // https://next-auth.js.org/configuration/callbacks#session-callback + session.accessToken = token.accessToken as string; + return session; + }, + }, +} as AuthOptions; -console.log(isSingleTenant ? "Single tenant mode" : "Multi tenant mode"); - -export default isSingleTenant - ? null - : NextAuth({ - providers: [ - Auth0Provider({ - clientId: process.env.AUTH0_CLIENT_ID!, - clientSecret: process.env.AUTH0_CLIENT_SECRET!, - issuer: process.env.AUTH0_ISSUER!, - authorization: `https://${process.env.AUTH0_DOMAIN}/authorize?response_type=code&prompt=login`, - }), - ], - session: { - strategy: "jwt", - maxAge: 30 * 24 * 60 * 60, // 30 days - }, - callbacks: { - async jwt({ token, account }) { - // https://next-auth.js.org/configuration/callbacks#jwt-callback - if (account) { - token.accessToken = account.id_token; - } - return token; - }, - async session({ session, token }) { - // https://next-auth.js.org/configuration/callbacks#session-callback - session.accessToken = token.accessToken as string; - return session; - }, - }, - }); +export default isSingleTenant ? null : NextAuth(authOptions); diff --git a/keep-ui/utils/customAuth.ts b/keep-ui/utils/customAuth.ts index d817dc4de..1a74781bf 100644 --- a/keep-ui/utils/customAuth.ts +++ b/keep-ui/utils/customAuth.ts @@ -1,6 +1,7 @@ import { AuthOptions, CallbacksOptions, Session } from "next-auth"; import { SessionContextValue, + UseSessionOptions, useSession as useNextAuthSession, } from "next-auth/react"; import { getServerSession as useNextGetServerSession } from "next-auth/next"; @@ -59,7 +60,9 @@ export async function getServerSession< return useNextGetServerSession(...args); } -export function useSession(): SessionContextValue { +export function useSession( + options?: UseSessionOptions +): SessionContextValue { if (isSingleTenant) { // Return a modified session object or perform any additional logic // specific to "single tenant" mode @@ -67,7 +70,7 @@ export function useSession(): SessionContextValue { return useCustomSession(); } // eslint-disable-next-line react-hooks/rules-of-hooks - const session = useNextAuthSession(); + const session = useNextAuthSession(options); // Return the original session object as is return session; diff --git a/keep/api/api.py b/keep/api/api.py index 2fbf24605..627434c26 100644 --- a/keep/api/api.py +++ b/keep/api/api.py @@ -46,6 +46,10 @@ def get_app(multi_tenant: bool = False) -> FastAPI: allow_headers=["*"], ) + multi_tenant = ( + multi_tenant if multi_tenant else os.environ.get("KEEP_MULTI_TENANT", False) + ) + app.include_router(providers.router, prefix="/providers", tags=["providers"]) app.include_router(healthcheck.router, prefix="/healthcheck", tags=["healthcheck"]) app.include_router(tenant.router, prefix="/tenant", tags=["tenant"]) diff --git a/keep/api/core/dependencies.py b/keep/api/core/dependencies.py index 889c0bbde..02c3b215d 100644 --- a/keep/api/core/dependencies.py +++ b/keep/api/core/dependencies.py @@ -1,4 +1,5 @@ import hashlib +import logging import os import jwt @@ -9,6 +10,8 @@ from keep.api.core.db import get_session from keep.api.models.db.tenant import TenantApiKey +logger = logging.getLogger(__name__) + auth_header = APIKeyHeader(name="X-API-KEY", scheme_name="API Key") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -51,17 +54,21 @@ def verify_api_key( def verify_bearer_token(token: str = Depends(oauth2_scheme)) -> str: # Took the implementation from here: # https://github.com/auth0-developer-hub/api_fastapi_python_hello-world/blob/main/application/json_web_token.py - auth_domain = os.environ.get("AUTH0_DOMAIN") - auth_audience = os.environ.get("AUTH0_AUDIENCE") - jwks_uri = f"https://{auth_domain}/.well-known/jwks.json" - issuer = f"https://{auth_domain}/" - jwks_client = jwt.PyJWKClient(jwks_uri) - jwt_signing_key = jwks_client.get_signing_key_from_jwt(token).key - payload = jwt.decode( - token, - jwt_signing_key, - algorithms="RS256", - audience=auth_audience, - issuer=issuer, - ) - return payload["keep_tenant_id"] + try: + auth_domain = os.environ.get("AUTH0_DOMAIN") + auth_audience = os.environ.get("AUTH0_AUDIENCE") + jwks_uri = f"https://{auth_domain}/.well-known/jwks.json" + issuer = f"https://{auth_domain}/" + jwks_client = jwt.PyJWKClient(jwks_uri) + jwt_signing_key = jwks_client.get_signing_key_from_jwt(token).key + payload = jwt.decode( + token, + jwt_signing_key, + algorithms="RS256", + audience=auth_audience, + issuer=issuer, + ) + return payload["keep_tenant_id"] + except Exception as e: + logger.exception("Failed to validate token") + raise HTTPException(status_code=401, detail=str(e))