Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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,20 +10,18 @@ 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>(
export const FrontendConfigContext = 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 @@ interface FrontendConfigProviderProps {
export function FrontendConfigProvider({ children }: 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