Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion frontend-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@
"backendUrl": "http://localhost:3000",
"gatewayUrl": "https://gateway.ui.dev-core.mcpd.shoot.canary.k8s-hana.ondemand.com/kubernetes/graphql",
"landscape": "LOCAL",
"documentationBaseUrl": "http://localhost:3000"
"documentationBaseUrl": "http://localhost:3000",
"oidcConfig": {
"clientId": "clientId",
"issuerUrl": "issuer-url",
"scopes": []
}
}
40 changes: 25 additions & 15 deletions src/context/AuthProviderOnboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import { ReactNode, use } from 'react';
import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
import { useFrontendConfig } from './FrontendConfigContext.tsx';
import { LoadCrateKubeConfig } from '../lib/oidc/crate.ts';
import { ReactNode } from 'react';
import { AuthProvider } from 'react-oidc-context';
import { OIDCConfig, useFrontendConfig } from './FrontendConfigContext.tsx';
import { WebStorageStateStore } from "oidc-client-ts";
import { AuthProviderProps } from "react-oidc-context";


interface AuthProviderOnboardingProps {
children?: ReactNode;
}

// Promise needs to be cached
// https://react.dev/blog/2024/12/05/react-19#use-does-not-support-promises-created-in-render
const fetchAuthPromiseCache = new Map<string, Promise<AuthProviderProps>>();

export function AuthProviderOnboarding({
children,
}: AuthProviderOnboardingProps) {
const { backendUrl } = useFrontendConfig();

const fetchAuthConfigPromise =
fetchAuthPromiseCache.get(backendUrl) ?? LoadCrateKubeConfig(backendUrl);
fetchAuthPromiseCache.set(backendUrl, fetchAuthConfigPromise);

const authConfig = use(fetchAuthConfigPromise);
const { oidcConfig } = useFrontendConfig();

const authConfig = buildAuthProviderConfig(oidcConfig);
return <AuthProvider {...authConfig}>{children}</AuthProvider>;
}

function buildAuthProviderConfig(oidcConfig: OIDCConfig) {
const userStore = new WebStorageStateStore({ store: window.localStorage });

const props: AuthProviderProps = {
authority: oidcConfig.issuerUrl,
client_id: oidcConfig.clientId,
redirect_uri: window.location.origin,
scope: oidcConfig.scopes.join(' '),
userStore: userStore,
automaticSilentRenew: false, // we show a window instead that asks the user to renew the token
onSigninCallback: () => {
window.history.replaceState({}, document.title, window.location.pathname);
},
};
return props;
}
45 changes: 32 additions & 13 deletions src/context/FrontendConfigContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactNode, createContext, use } from 'react';
import { DocLinkCreator } from '../lib/shared/links';
import { z } from 'zod';

export enum Landscape {
Live = 'LIVE',
Expand All @@ -9,18 +10,16 @@ export enum Landscape {
Local = 'LOCAL',
}

interface FrontendConfigContextProps {
backendUrl: string;
gatewayUrl: string;
landscape?: Landscape;
documentationBaseUrl: string;


interface FrontendConfigContextType extends FrontendConfig {
links: DocLinkCreator;
}

export const FrontendConfigContext =
createContext<FrontendConfigContextProps | null>(null);
createContext<FrontendConfigContextType | null>(null);

const fetchPromise = fetch('/frontend-config.json').then((res) => res.json());
const fetchPromise = fetch('/frontend-config.json').then((res) => res.json()).then((data) => validateAndCastFrontendConfig(data));

interface FrontendConfigProviderProps {
children: ReactNode;
Expand All @@ -31,14 +30,10 @@ export function FrontendConfigProvider({
}: FrontendConfigProviderProps) {
const config = use(fetchPromise);
const docLinks = new DocLinkCreator(config.documentationBaseUrl);
const value: FrontendConfigContextProps = {
const value: FrontendConfigContextType = {
links: docLinks,
backendUrl: config.backendUrl,
gatewayUrl: config.gatewayUrl,
landscape: config.landscape,
documentationBaseUrl: config.documentationBaseUrl,
...config,
};

return (
<FrontendConfigContext value={value}>{children}</FrontendConfigContext>
);
Expand All @@ -54,3 +49,27 @@ export const useFrontendConfig = () => {
}
return context;
};

const OidcConfigSchema = z.object({
clientId: z.string(),
issuerUrl: z.string(),
scopes: z.array(z.string()),
});
export type OIDCConfig = z.infer<typeof OidcConfigSchema>;

const FrontendConfigSchema = z.object({
backendUrl: z.string(),
gatewayUrl: z.string(),
documentationBaseUrl: z.string(),
oidcConfig: OidcConfigSchema,
landscape: z.optional(z.nativeEnum(Landscape)),
});
type FrontendConfig = z.infer<typeof FrontendConfigSchema>;

function validateAndCastFrontendConfig(config: unknown): FrontendConfig {
try {
return FrontendConfigSchema.parse(config);
} catch (error) {
throw new Error(`Invalid frontend config: ${error}`);
}
}
19 changes: 0 additions & 19 deletions src/lib/oidc/crate.ts

This file was deleted.

Loading