@@ -76,8 +92,8 @@ export default async function Home() {
- }>
-
+ }>
+
diff --git a/ui/app/[locale]/layout.tsx b/ui/app/[locale]/layout.tsx
index 58a945b8e..7953b5d78 100644
--- a/ui/app/[locale]/layout.tsx
+++ b/ui/app/[locale]/layout.tsx
@@ -1,5 +1,4 @@
import { getMessages, getTranslations } from "next-intl/server";
-import { useNow, useTimeZone } from "next-intl";
import { ReactNode } from "react";
import NextIntlProvider from "./NextIntlProvider";
import "../globals.css";
@@ -27,7 +26,3 @@ export async function generateMetadata({
title: t("title"),
};
}
-
-// export function generateStaticParams() {
-// return [{ locale: "en" }];
-// }
diff --git a/ui/app/api/auth/[...nextauth]/anonymous.ts b/ui/app/api/auth/[...nextauth]/anonymous.ts
index a8e95b652..55a29c5dd 100644
--- a/ui/app/api/auth/[...nextauth]/anonymous.ts
+++ b/ui/app/api/auth/[...nextauth]/anonymous.ts
@@ -1,4 +1,3 @@
-import { AuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { Provider } from "next-auth/providers/index";
diff --git a/ui/app/api/auth/[...nextauth]/keycloak.ts b/ui/app/api/auth/[...nextauth]/keycloak.ts
deleted file mode 100644
index 957868765..000000000
--- a/ui/app/api/auth/[...nextauth]/keycloak.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import { logger } from "@/utils/logger";
-import { AuthOptions, Session, TokenSet } from "next-auth";
-import { JWT } from "next-auth/jwt";
-import { Provider } from "next-auth/providers/index";
-import KeycloakProvider from "next-auth/providers/keycloak";
-
-const log = logger.child({ module: "keycloak" });
-
-export function makeOauthProvider(
- clientId: string,
- clientSecret: string,
- issuer: string,
-): Provider {
- const provider = KeycloakProvider({
- clientId,
- clientSecret,
- issuer,
- });
-
- let _tokenEndpoint: string | undefined = undefined;
-
- async function getTokenEndpoint() {
- if (provider && provider.wellKnown) {
- const kc = await fetch(provider.wellKnown);
- const res = await kc.json();
- _tokenEndpoint = res.token_endpoint;
- }
- return _tokenEndpoint;
- }
-
- async function refreshToken(token: JWT): Promise
{
- try {
- const tokenEndpoint = await getTokenEndpoint();
- if (!provider) {
- log.error("Invalid Keycloak configuratio");
- throw token;
- }
- if (!tokenEndpoint) {
- log.error("Invalid Keycloak wellKnow");
- throw token;
- }
- let tokenExpiration = new Date(
- (typeof token?.expires_at === "number" ? token.expires_at : 0) * 1000,
- );
- log.trace({ tokenExpiration }, "Token expiration");
-
- if (Date.now() < tokenExpiration.getTime()) {
- log.trace(token, "Token not yet expired");
- return token;
- } else {
- log.trace(token, "Token has expired");
- let refresh_token =
- typeof token.refresh_token === "string" ? token.refresh_token : "";
-
- const params = {
- client_id: provider.options!.clientId,
- client_secret: provider.options!.clientSecret,
- grant_type: "refresh_token",
- refresh_token: refresh_token,
- };
-
- log.trace(
- {
- url: tokenEndpoint,
- },
- "Refreshing token",
- );
-
- const response = await fetch(tokenEndpoint, {
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
- body: new URLSearchParams(params),
- method: "POST",
- });
-
- const refreshToken: TokenSet = await response.json();
- if (!response.ok) {
- throw new Error(response.statusText);
- }
- log.trace(refreshToken, "Got refresh token");
-
- let expires_in =
- typeof refreshToken.expires_in === "number"
- ? refreshToken.expires_in
- : -1;
-
- const newToken: JWT = {
- ...token, // Keep the previous token properties
- access_token: refreshToken.access_token,
- expires_at: Math.floor(Date.now() / 1000 + expires_in),
- // Fall back to old refresh token, but note that
- // many providers may only allow using a refresh token once.
- refresh_token: refreshToken.refresh_token ?? token.refresh_token,
- };
- log.trace(newToken, "New token");
- return newToken;
- }
- } catch (error: unknown) {
- if (typeof error === "string") {
- log.error({ message: error }, "Error refreshing access token");
- } else if (error instanceof Error) {
- log.error(error, "Error refreshing access token");
- } else {
- log.error("Unknown error refreshing access token");
- }
- // The error property will be used client-side to handle the refresh token error
- return { ...token, error: "RefreshAccessTokenError" as const };
- }
- }
-
- return provider;
-
- // return {
- // providers: [provider],
- // callbacks: {
- // async jwt({ token, account }: { token: JWT; account: any }) {
- // // Persist the OAuth access_token and or the user id to the token right after signin
- // if (account) {
- // log.trace("account present, saving new token");
- // // Save the access token and refresh token in the JWT on the initial login
- // return {
- // access_token: account.access_token,
- // expires_at: account.expires_at,
- // refresh_token: account.refresh_token,
- // email: token.email,
- // name: token.name,
- // picture: token.picture,
- // sub: token.sub,
- // };
- // }
- //
- // return refreshToken(token);
- // },
- // async session({ session, token }: { session: Session; token: JWT }) {
- // // Send properties to the client, like an access_token from a provider.
- // log.trace(token, "Creating session from token");
- // return {
- // ...session,
- // error: token.error,
- // accessToken: token.access_token,
- // };
- // },
- // },
- // };
-}
diff --git a/ui/app/api/auth/[...nextauth]/oidc.ts b/ui/app/api/auth/[...nextauth]/oidc.ts
new file mode 100644
index 000000000..eef30f153
--- /dev/null
+++ b/ui/app/api/auth/[...nextauth]/oidc.ts
@@ -0,0 +1,182 @@
+import { logger } from "@/utils/logger";
+import { Session, TokenSet } from "next-auth";
+import { JWT } from "next-auth/jwt";
+import { OAuthConfig } from "next-auth/providers/index";
+import config from '@/app/api/config';
+
+const log = logger.child({ module: "oidc" });
+
+class OpenIdConnect {
+
+ provider: OAuthConfig | null;
+
+ constructor(
+ authServerUrl: string | null,
+ clientId: string | null,
+ clientSecret: string | null
+ ) {
+ if (clientId && clientSecret && authServerUrl) {
+ this.provider = {
+ id: "oidc",
+ name: "OpenID Connect Provider",
+ type: "oauth",
+ clientId: clientId,
+ clientSecret: clientSecret,
+ wellKnown: `${authServerUrl}/.well-known/openid-configuration`,
+ authorization: { params: { scope: "openid email profile groups" } },
+ idToken: true,
+ profile(profile) {
+ return {
+ id: profile.sub,
+ name: profile.name ?? profile.preferred_username,
+ email: profile.email,
+ image: profile.image,
+ }
+ },
+ }
+ } else {
+ this.provider = null;
+ }
+ }
+
+ isEnabled() {
+ return this.provider != null;
+ }
+
+ async getTokenEndpoint() {
+ let _tokenEndpoint: string | undefined = undefined;
+
+ if (this.provider?.wellKnown) {
+ log.debug(`wellKnown endpoint: ${this.provider.wellKnown}`);
+ const kc = await fetch(this.provider.wellKnown);
+ const res = await kc.json();
+ _tokenEndpoint = res.token_endpoint;
+ }
+
+ log.debug(`token endpoint: ${_tokenEndpoint}`);
+
+ return _tokenEndpoint;
+ }
+
+ async refreshToken(token: JWT): Promise {
+ if (this.provider == null) {
+ throw new Error("OIDC is not properly configured");
+ }
+
+ try {
+ let tokenExpiration = new Date(
+ (typeof token?.expires_at === "number" ? token.expires_at : 0) * 1000,
+ );
+ log.trace({ tokenExpiration }, "Token expiration");
+
+ if (Date.now() < tokenExpiration.getTime()) {
+ log.trace(token, "Token not yet expired");
+ return token;
+ }
+ log.trace(token, "Token has expired");
+ let refresh_token =
+ typeof token.refresh_token === "string" ? token.refresh_token : "";
+
+ const params = {
+ client_id: this.provider.clientId!,
+ client_secret: this.provider.clientSecret!,
+ grant_type: "refresh_token",
+ refresh_token: refresh_token,
+ };
+
+ const tokenEndpoint = await this.getTokenEndpoint();
+
+ if (!tokenEndpoint) {
+ log.error("Invalid OIDC wellKnown");
+ throw token;
+ }
+
+ log.trace(
+ {
+ url: tokenEndpoint,
+ },
+ "Refreshing token",
+ );
+
+ const response = await fetch(tokenEndpoint, {
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: new URLSearchParams(params),
+ method: "POST",
+ });
+
+ const refreshToken: TokenSet = await response.json();
+ if (!response.ok) {
+ throw new Error(response.statusText);
+ }
+ log.trace(refreshToken, "Got refresh token");
+
+ let expires_in =
+ typeof refreshToken.expires_in === "number"
+ ? refreshToken.expires_in
+ : -1;
+
+ const newToken: JWT = {
+ ...token, // Keep the previous token properties
+ access_token: refreshToken.access_token,
+ expires_at: Math.floor(Date.now() / 1000 + expires_in),
+ // Fall back to old refresh token, but note that
+ // many providers may only allow using a refresh token once.
+ refresh_token: refreshToken.refresh_token ?? token.refresh_token,
+ };
+ log.trace(newToken, "New token");
+ return newToken;
+ } catch (error: unknown) {
+ if (typeof error === "string") {
+ log.error({ message: error }, "Error refreshing access token");
+ } else if (error instanceof Error) {
+ log.error(error, "Error refreshing access token");
+ } else {
+ log.error("Unknown error refreshing access token");
+ }
+ // The error property will be used client-side to handle the refresh token error
+ return { ...token, error: "RefreshAccessTokenError" as const };
+ }
+ }
+
+ async jwt({ token, account }: { token: JWT, account: any }) {
+ // Persist the OAuth access_token and or the user id to the token right after signin
+ log.info("jwt callback invoked")
+ if (account) {
+ log.trace(`account ${JSON.stringify(account)} present, saving new token: ${JSON.stringify(token)}`);
+ // Save the access token and refresh token in the JWT on the initial login
+ return {
+ access_token: account.access_token,
+ expires_at: account.expires_at,
+ refresh_token: account.refresh_token,
+ email: token.email,
+ name: token.name,
+ picture: token.picture,
+ sub: token.sub,
+ };
+ }
+
+ return this.refreshToken(token);
+ };
+
+ async session({ session, token }: { session: Session, token: JWT }) {
+ // Send properties to the client, like an access_token from a provider.
+ log.trace(token, "Creating session from token");
+ return {
+ ...session,
+ error: token.error,
+ accessToken: token.access_token,
+ authorization: `Bearer ${token.access_token}`,
+ };
+ };
+}
+
+
+export default async function oidcSource() {
+ let cfg = await config();
+ let oidcCfg = cfg?.security?.oidc;
+ const authServerUrl: string | null = oidcCfg?.authServerUrl ?? null;
+ const clientId: string | null = oidcCfg?.clientId ?? null;
+ const clientSecret: string | null = oidcCfg?.clientSecret ?? null;
+ const oidcProvider = new OpenIdConnect(authServerUrl, clientId, clientSecret);
+ return oidcProvider;
+};
diff --git a/ui/app/api/auth/[...nextauth]/route.ts b/ui/app/api/auth/[...nextauth]/route.ts
index 812db7da4..4d3d88a93 100644
--- a/ui/app/api/auth/[...nextauth]/route.ts
+++ b/ui/app/api/auth/[...nextauth]/route.ts
@@ -1,37 +1,60 @@
import { getKafkaClusters } from "@/api/kafka/actions";
import { ClusterList } from "@/api/kafka/schema";
import { logger } from "@/utils/logger";
-import NextAuth, { AuthOptions } from "next-auth";
+import NextAuth, { AuthOptions, Session } from "next-auth";
+import { JWT } from "next-auth/jwt";
import { Provider } from "next-auth/providers/index";
import { NextRequest, NextResponse } from "next/server";
import { makeAnonymous } from "./anonymous";
import { makeOauthTokenProvider } from "./oauth-token";
import { makeScramShaProvider } from "./scram";
+import oidcSource from "./oidc";
const log = logger.child({ module: "auth" });
export async function getAuthOptions(): Promise {
- // retrieve the authentication method required by the default Kafka cluster
- const clusters = await getKafkaClusters();
- const providers = clusters.map(makeAuthOption);
- log.trace({ providers }, "getAuthOptions");
- return {
- providers,
- callbacks: {
- async jwt({ token, user }) {
- if (user) {
- token.authorization = user.authorization;
+ let providers: Provider[];
+ log.info("fetching the oidcSource");
+ let oidc = await oidcSource();
+
+ if (oidc.isEnabled()) {
+ log.info("OIDC is enabled");
+ providers = [ oidc.provider! ];
+ return {
+ providers,
+ callbacks: {
+ async jwt({ token, account }: { token: JWT, account: any }) {
+ return oidc.jwt({ token, account });
+ },
+ async session({ session, token }: { session: Session, token: JWT }) {
+ return oidc.session({ session, token });
}
- return token;
- },
- async session({ session, token, user }) {
- // Send properties to the client, like an access_token and user id from a provider.
- session.authorization = token.authorization;
+ }
+ }
+ } else {
+ log.info("OIDC is disabled");
+ // retrieve the authentication method required by the default Kafka cluster
+ const clusters = await getKafkaClusters();
+ providers = clusters.map(makeAuthOption);
+ log.trace({ providers }, "getAuthOptions");
+ return {
+ providers,
+ callbacks: {
+ async jwt({ token, user }) {
+ if (user) {
+ token.authorization = user.authorization;
+ }
+ return token;
+ },
+ async session({ session, token, user }) {
+ // Send properties to the client, like an access_token and user id from a provider.
+ session.authorization = token.authorization;
- return session;
+ return session;
+ },
},
- },
- };
+ };
+ }
}
function makeAuthOption(cluster: ClusterList): Provider {
diff --git a/ui/app/api/auth/[...nextauth]/scram.ts b/ui/app/api/auth/[...nextauth]/scram.ts
index 3bcbd5eb0..b24e6f8c9 100644
--- a/ui/app/api/auth/[...nextauth]/scram.ts
+++ b/ui/app/api/auth/[...nextauth]/scram.ts
@@ -1,5 +1,3 @@
-import { getKafkaClusters } from "@/api/kafka/actions";
-import { AuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { Provider } from "next-auth/providers/index";
diff --git a/ui/app/api/config.ts b/ui/app/api/config.ts
new file mode 100644
index 000000000..fc07a95ec
--- /dev/null
+++ b/ui/app/api/config.ts
@@ -0,0 +1,21 @@
+"use server";
+
+import * as yaml from 'js-yaml';
+
+export interface OidcConfig {
+ authServerUrl: string | null;
+ clientId: string | null;
+ clientSecret: string | null;
+}
+
+export interface GlobalSecurityConfig {
+ oidc: OidcConfig | null;
+}
+
+export interface ConsoleConfig {
+ security: GlobalSecurityConfig | null;
+}
+
+export default async function config(): Promise {
+ return yaml.load(process.env.CONSOLE_CONFIG!) as ConsoleConfig;
+}
diff --git a/ui/components/ClustersTable.tsx b/ui/components/ClustersTable.tsx
index 9fc35d60c..103d737a3 100644
--- a/ui/components/ClustersTable.tsx
+++ b/ui/components/ClustersTable.tsx
@@ -6,6 +6,7 @@ import { ResponsiveTable } from "@/components/Table";
import { Truncate } from "@/libs/patternfly/react-core";
import { TableVariant } from "@/libs/patternfly/react-table";
import { useTranslations } from "next-intl";
+import { Link } from "@/i18n/routing";
const columns = [
"name",
@@ -13,14 +14,28 @@ const columns = [
"namespace",
"authentication",
"login",
-] as const;
+];
export function ClustersTable({
clusters,
+ authenticated,
}: {
clusters: ClusterList[] | undefined;
+ authenticated: boolean
}) {
const t = useTranslations();
+ const columns = authenticated ? [
+ "name",
+ "version",
+ "namespace",
+ ] as const : [
+ "name",
+ "version",
+ "namespace",
+ "authentication",
+ "login",
+ ] as const;
+
return (
-
+ {authenticated
+ ?
+
+
+ :
+ }
);
case "version":
@@ -87,8 +107,8 @@ export function ClustersTable({
case "login":
return (
-
- Login to cluster
+
+ { authenticated ? "View" : "Login to cluster" }
|
);
diff --git a/ui/environment.d.ts b/ui/environment.d.ts
index 06950d380..e6d03d648 100644
--- a/ui/environment.d.ts
+++ b/ui/environment.d.ts
@@ -3,9 +3,6 @@ namespace NodeJS {
NEXTAUTH_URL: string;
NEXTAUTH_SECRET: string;
BACKEND_URL: string;
- KEYCLOAK_CLIENTID?: string;
- KEYCLOAK_CLIENTSECRET?: string;
- NEXT_PUBLIC_KEYCLOAK_URL?: string;
NEXT_PUBLIC_PRODUCTIZED_BUILD?: "true" | "false";
CONSOLE_METRICS_PROMETHEUS_URL?: string;
LOG_LEVEL?: "fatal" | "error" | "warn" | "info" | "debug" | "trace";
diff --git a/ui/middleware.ts b/ui/middleware.ts
index 8b5909822..a0b46a936 100644
--- a/ui/middleware.ts
+++ b/ui/middleware.ts
@@ -3,6 +3,7 @@ import withAuth from "next-auth/middleware";
import createIntlMiddleware from "next-intl/middleware";
import { NextRequest, NextResponse } from "next/server";
+import consoleConfig from '@/app/api/config';
import { logger } from "@/utils/logger";
const log = logger.child({ module: "middleware" });
@@ -24,7 +25,7 @@ const authMiddleware = withAuth(
authorized: ({ token }) => token != null,
},
pages: {
- signIn: `/kafka/1/login`,
+ //signIn: `/kafka/1/login`,
},
},
) as any;
@@ -44,16 +45,20 @@ const protectedPathnameRegex = RegExp(
);
export default async function middleware(req: NextRequest) {
+ let cfg = await consoleConfig();
+ let oidcCfg = cfg?.['security']?.['oidc'];
+ let oidcEnabled = oidcCfg ? true : false;
+
const requestPath = req.nextUrl.pathname;
- const isPublicPage = publicPathnameRegex.test(requestPath);
- const isProtectedPage = protectedPathnameRegex.test(requestPath);
+ const isPublicPage = !oidcEnabled && publicPathnameRegex.test(requestPath);
+ const isProtectedPage = oidcEnabled || protectedPathnameRegex.test(requestPath);
if (isPublicPage) {
log.trace({ requestPath: requestPath }, "public page");
return intlMiddleware(req);
} else if (isProtectedPage) {
log.trace({ requestPath: requestPath }, "protected page");
- return (authMiddleware as any)(req);
+ return (authMiddleware)(req);
} else {
log.debug(
{
diff --git a/ui/package-lock.json b/ui/package-lock.json
index d1223f0c4..9208ed3c7 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -36,6 +36,7 @@
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-storybook": "^0.10.1",
"iron-session": "^8.0.3",
+ "js-yaml": "^4.1.0",
"lodash.groupby": "^4.6.0",
"next": "^14.2.15",
"next-auth": "^4.24.8",
@@ -2502,11 +2503,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/@eslint/eslintrc/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
- },
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
@@ -2521,17 +2517,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/@eslint/eslintrc/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@@ -3150,6 +3135,15 @@
"node": ">=8"
}
},
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
"node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -3163,6 +3157,19 @@
"node": ">=8"
}
},
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -8808,13 +8815,9 @@
"dev": true
},
"node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/aria-query": {
"version": "5.3.0",
@@ -11879,11 +11882,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
- },
"node_modules/eslint/node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
@@ -11898,17 +11896,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/eslint/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/eslint/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@@ -15616,13 +15603,12 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
- "dev": true,
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "license": "MIT",
"dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
+ "argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
@@ -17507,12 +17493,6 @@
}
}
},
- "node_modules/postcss-loader/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
"node_modules/postcss-loader/node_modules/cosmiconfig": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
@@ -17539,18 +17519,6 @@
}
}
},
- "node_modules/postcss-loader/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/postcss-loader/node_modules/semver": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
diff --git a/ui/package.json b/ui/package.json
index 8b564ff4e..cc76cf824 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -41,6 +41,7 @@
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-storybook": "^0.10.1",
"iron-session": "^8.0.3",
+ "js-yaml": "^4.1.0",
"lodash.groupby": "^4.6.0",
"next": "^14.2.15",
"next-auth": "^4.24.8",
diff --git a/ui/utils/env.ts b/ui/utils/env.ts
index c413be6c4..8c716b2bd 100644
--- a/ui/utils/env.ts
+++ b/ui/utils/env.ts
@@ -3,14 +3,6 @@ export const isReadonly = (() => {
return true;
}
- if (
- process.env.NEXT_PUBLIC_KEYCLOAK_URL &&
- process.env.KEYCLOAK_CLIENTID &&
- process.env.KEYCLOAK_CLIENTSECRET
- ) {
- return false;
- }
-
return true;
})();