From b618bf4d3cfec89d9bd52455620924ca35d20bc8 Mon Sep 17 00:00:00 2001 From: garronej Date: Sun, 2 Mar 2025 00:59:40 +0100 Subject: [PATCH] Update oidc-spa --- web/package.json | 2 +- web/src/core/adapters/oidc/oidc.ts | 49 +++++++++++++----- web/src/core/adapters/onyxiaApi/ApiTypes.ts | 3 +- web/src/core/adapters/onyxiaApi/onyxiaApi.ts | 37 +++++++++++--- web/src/core/bootstrap.ts | 9 ++-- web/src/core/ports/Oidc.ts | 31 ++++++++--- web/src/core/ports/OnyxiaApi/OidcParams.ts | 3 +- web/src/core/usecases/k8sCodeSnippets.ts | 8 +-- web/src/core/usecases/launcher/thunks.ts | 2 +- .../usecases/s3ConfigManagement/thunks.ts | 2 +- web/src/ui/App/App.tsx | 39 +++++++------- web/src/ui/App/Main.tsx | 26 +--------- web/src/ui/pages/account/Account.tsx | 7 ++- web/src/ui/pages/account/route.ts | 2 - web/src/ui/pages/catalog/route.ts | 2 - web/src/ui/pages/dataExplorer/route.ts | 2 - web/src/ui/pages/home/route.ts | 2 - web/src/ui/pages/launcher/Launcher.tsx | 7 ++- web/src/ui/pages/launcher/route.ts | 2 - web/src/ui/pages/myFiles/MyFiles.tsx | 7 ++- web/src/ui/pages/myFiles/route.ts | 2 - web/src/ui/pages/mySecrets/MySecrets.tsx | 7 ++- web/src/ui/pages/mySecrets/route.ts | 2 - web/src/ui/pages/myService/MyService.tsx | 7 ++- web/src/ui/pages/myService/route.ts | 2 - web/src/ui/pages/myServices/MyServices.tsx | 7 ++- web/src/ui/pages/myServices/route.ts | 2 - web/src/ui/pages/page404/route.ts | 2 - .../pages/projectSettings/ProjectSettings.tsx | 7 ++- web/src/ui/pages/projectSettings/route.ts | 2 - .../ui/pages/sqlOlapShell/SqlOlapShell.tsx | 7 ++- web/src/ui/pages/sqlOlapShell/route.ts | 2 - web/src/ui/pages/terms/route.ts | 2 - web/src/ui/shared/withLoginEnforced.tsx | 51 +++++++++++++++++++ web/yarn.lock | 8 +-- 35 files changed, 225 insertions(+), 127 deletions(-) create mode 100644 web/src/ui/shared/withLoginEnforced.tsx diff --git a/web/package.json b/web/package.json index 546ff2e06..adac37c2b 100644 --- a/web/package.json +++ b/web/package.json @@ -63,7 +63,7 @@ "memoizee": "^0.4.17", "minimal-polyfills": "^2.2.3", "mui-icons-material-lazy": "^1.0.4", - "oidc-spa": "^6.5.2", + "oidc-spa": "^6.9.4", "onyxia-ui": "^6.2.3", "pathe": "^1.1.2", "powerhooks": "^1.0.19", diff --git a/web/src/core/adapters/oidc/oidc.ts b/web/src/core/adapters/oidc/oidc.ts index 15561abb0..26700eaa9 100644 --- a/web/src/core/adapters/oidc/oidc.ts +++ b/web/src/core/adapters/oidc/oidc.ts @@ -1,11 +1,15 @@ import type { Oidc } from "core/ports/Oidc"; import { createOidc as createOidcSpa } from "oidc-spa"; +import { parseKeycloakIssuerUri } from "oidc-spa/tools/parseKeycloakIssuerUri"; import type { OidcParams, OidcParams_Partial } from "core/ports/OnyxiaApi"; import { objectKeys } from "tsafe/objectKeys"; export async function createOidc( params: OidcParams & { - transformUrlBeforeRedirect: (url: string) => string; + transformUrlBeforeRedirect_ui: (params: { + isKeycloak: boolean; + authorizationUrl: string; + }) => string; autoLogin: AutoLogin; } ): Promise { @@ -13,8 +17,8 @@ export async function createOidc( issuerUri, clientId, scope_spaceSeparated, - clientSecret, - transformUrlBeforeRedirect, + audience, + transformUrlBeforeRedirect_ui, extraQueryParams_raw, autoLogin } = params; @@ -22,19 +26,38 @@ export async function createOidc( const oidc = await createOidcSpa({ issuerUri, clientId, - __unsafe_clientSecret: clientSecret, scopes: scope_spaceSeparated?.split(" "), - transformUrlBeforeRedirect: url => { - url = transformUrlBeforeRedirect(url); + transformUrlBeforeRedirect_next: ({ authorizationUrl, isSilent }) => { + if (!isSilent) { + authorizationUrl = transformUrlBeforeRedirect_ui({ + isKeycloak: + parseKeycloakIssuerUri(oidc.params.issuerUri) !== undefined, + authorizationUrl + }); + } + + if (audience !== undefined) { + const url_obj = new URL(authorizationUrl); + + url_obj.searchParams.set("audience", audience); + + authorizationUrl = url_obj.href; + } if (extraQueryParams_raw !== undefined) { - url += `&${extraQueryParams_raw}`; + const url_obj = new URL(authorizationUrl); + const extraUrlSearchParams = new URLSearchParams(extraQueryParams_raw); + + for (const [key, value] of extraUrlSearchParams) { + url_obj.searchParams.set(key, value); + } + + authorizationUrl = url_obj.href; } - return url; + return authorizationUrl; }, - homeUrl: import.meta.env.BASE_URL, - debugLogs: false + homeUrl: import.meta.env.BASE_URL }); if (!oidc.isUserLoggedIn) { @@ -63,9 +86,11 @@ export function mergeOidcParams(params: { for (const key of objectKeys(oidcParams_partial)) { const value = oidcParams_partial[key]; - if (value !== undefined) { - oidcParams_merged[key] = value; + if (value === undefined) { + continue; } + // @ts-expect-error + oidcParams_merged[key] = value; } return oidcParams_merged; diff --git a/web/src/core/adapters/onyxiaApi/ApiTypes.ts b/web/src/core/adapters/onyxiaApi/ApiTypes.ts index 8721324dd..da0e7f486 100644 --- a/web/src/core/adapters/onyxiaApi/ApiTypes.ts +++ b/web/src/core/adapters/onyxiaApi/ApiTypes.ts @@ -264,6 +264,7 @@ export namespace ApiTypes { clientID: string; extraQueryParams?: string; scope?: string; - workaroundForGoogleClientSecret?: string; + audience?: string; + idleSessionLifetimeInSeconds?: number | string; }; } diff --git a/web/src/core/adapters/onyxiaApi/onyxiaApi.ts b/web/src/core/adapters/onyxiaApi/onyxiaApi.ts index 4f3f50a3d..11c412a21 100644 --- a/web/src/core/adapters/onyxiaApi/onyxiaApi.ts +++ b/web/src/core/adapters/onyxiaApi/onyxiaApi.ts @@ -128,13 +128,25 @@ export function createOnyxiaApi(params: { : id({ issuerUri: data.oidcConfiguration.issuerURI, clientId: data.oidcConfiguration.clientID, - clientSecret: - data.oidcConfiguration - .workaroundForGoogleClientSecret || undefined, extraQueryParams_raw: data.oidcConfiguration.extraQueryParams || undefined, scope_spaceSeparated: - data.oidcConfiguration.scope || undefined + data.oidcConfiguration.scope || undefined, + audience: data.oidcConfiguration.audience || undefined, + idleSessionLifetimeInSeconds: (() => { + const value = + data.oidcConfiguration.idleSessionLifetimeInSeconds; + + if (value === "" || value === undefined) { + return undefined; + } + + if (typeof value === "number") { + return value; + } + + return parseInt(value); + })() }); const regions = data.regions.map( @@ -974,8 +986,21 @@ function apiTypesOidcConfigurationToOidcParams_Partial( return { issuerUri: oidcConfiguration?.issuerURI || undefined, clientId: oidcConfiguration?.clientID || undefined, - clientSecret: oidcConfiguration?.workaroundForGoogleClientSecret || undefined, extraQueryParams_raw: oidcConfiguration?.extraQueryParams || undefined, - scope_spaceSeparated: oidcConfiguration?.scope || undefined + scope_spaceSeparated: oidcConfiguration?.scope || undefined, + audience: oidcConfiguration?.audience || undefined, + idleSessionLifetimeInSeconds: (() => { + const value = oidcConfiguration?.idleSessionLifetimeInSeconds; + + if (value === "" || value === undefined) { + return undefined; + } + + if (typeof value === "number") { + return value; + } + + return parseInt(value); + })() }; } diff --git a/web/src/core/bootstrap.ts b/web/src/core/bootstrap.ts index c61fb0098..63b8b7bdf 100644 --- a/web/src/core/bootstrap.ts +++ b/web/src/core/bootstrap.ts @@ -17,7 +17,10 @@ import { assert } from "tsafe/assert"; type ParamsOfBootstrapCore = { apiUrl: string; - transformUrlBeforeRedirectToLogin: (url: string) => string; + transformUrlBeforeRedirectToLogin: (params: { + isKeycloak: boolean; + authorizationUrl: string; + }) => string; getCurrentLang: () => Language; disablePersonalInfosInjectionInGroup: boolean; isCommandBarEnabledByDefault: boolean; @@ -116,7 +119,7 @@ export async function bootstrapCore( return createOidc({ ...oidcParams, - transformUrlBeforeRedirect: transformUrlBeforeRedirectToLogin, + transformUrlBeforeRedirect_ui: transformUrlBeforeRedirectToLogin, autoLogin: false }); })(); @@ -219,7 +222,7 @@ export async function bootstrapCore( oidcParams, oidcParams_partial: deploymentRegion.vault.oidcParams }), - transformUrlBeforeRedirect: transformUrlBeforeRedirectToLogin, + transformUrlBeforeRedirect_ui: transformUrlBeforeRedirectToLogin, autoLogin: true }) }); diff --git a/web/src/core/ports/Oidc.ts b/web/src/core/ports/Oidc.ts index 0cb72ad50..7398db522 100644 --- a/web/src/core/ports/Oidc.ts +++ b/web/src/core/ports/Oidc.ts @@ -15,7 +15,7 @@ export declare namespace Oidc { export type LoggedIn = Common & { isUserLoggedIn: true; - renewTokens(): Promise; + renewTokens: () => Promise; getTokens: () => Promise; logout: (params: { redirectTo: "home" | "current page" }) => Promise; isNewBrowserSession: boolean; @@ -24,11 +24,26 @@ export declare namespace Oidc { ) => { unsubscribeFromAutoLogoutCountdown: () => void }; }; - export type Tokens = { - accessToken: string; - idToken: string; - refreshToken: string; - refreshTokenExpirationTime: number; - decodedIdToken: Record; - }; + export type Tokens = Tokens.WithRefreshToken | Tokens.WithoutRefreshToken; + + export namespace Tokens { + export type Common = { + accessToken: string; + accessTokenExpirationTime: number; + idToken: string; + decodedIdToken: Record; + }; + + export type WithRefreshToken = Common & { + hasRefreshToken: true; + refreshToken: string; + refreshTokenExpirationTime: number | undefined; + }; + + export type WithoutRefreshToken = Common & { + hasRefreshToken: false; + refreshToken?: never; + refreshTokenExpirationTime?: never; + }; + } } diff --git a/web/src/core/ports/OnyxiaApi/OidcParams.ts b/web/src/core/ports/OnyxiaApi/OidcParams.ts index 1e3120a8c..1a3ce672e 100644 --- a/web/src/core/ports/OnyxiaApi/OidcParams.ts +++ b/web/src/core/ports/OnyxiaApi/OidcParams.ts @@ -1,9 +1,10 @@ export type OidcParams = { issuerUri: string; clientId: string; + audience: string | undefined; extraQueryParams_raw: string | undefined; scope_spaceSeparated: string | undefined; - clientSecret: string | undefined; + idleSessionLifetimeInSeconds: number | undefined; }; export type OidcParams_Partial = { [P in keyof OidcParams]: OidcParams[P] | undefined }; diff --git a/web/src/core/usecases/k8sCodeSnippets.ts b/web/src/core/usecases/k8sCodeSnippets.ts index a6557ba09..435663fff 100644 --- a/web/src/core/usecases/k8sCodeSnippets.ts +++ b/web/src/core/usecases/k8sCodeSnippets.ts @@ -138,7 +138,7 @@ export const thunks = { oidcParams_partial: region.kubernetes.oidcParams }), autoLogin: true, - transformUrlBeforeRedirect: + transformUrlBeforeRedirect_ui: paramsOfBootstrapCore.transformUrlBeforeRedirectToLogin }); @@ -153,12 +153,14 @@ export const thunks = { actions.refreshed({ idpIssuerUrl: kubernetesOidcClient.params.issuerUri, clientId: kubernetesOidcClient.params.clientId, - refreshToken: oidcTokens.refreshToken, + refreshToken: oidcTokens.refreshToken ?? "", idToken: oidcTokens.idToken, user: `${region.kubernetes.usernamePrefix ?? ""}${ userAuthentication.selectors.user(getState()).username }`, - expirationTime: oidcTokens.refreshTokenExpirationTime + expirationTime: + oidcTokens.refreshTokenExpirationTime ?? + oidcTokens.accessTokenExpirationTime }) ); } diff --git a/web/src/core/usecases/launcher/thunks.ts b/web/src/core/usecases/launcher/thunks.ts index db2be5a04..1df1d46df 100644 --- a/web/src/core/usecases/launcher/thunks.ts +++ b/web/src/core/usecases/launcher/thunks.ts @@ -479,7 +479,7 @@ const privateThunks = { lang: paramsOfBootstrapCore.getCurrentLang(), decodedIdToken, accessToken, - refreshToken + refreshToken: refreshToken ?? "" }, service: { oneTimePassword: generateRandomPassword() diff --git a/web/src/core/usecases/s3ConfigManagement/thunks.ts b/web/src/core/usecases/s3ConfigManagement/thunks.ts index e4069c4e8..72247225e 100644 --- a/web/src/core/usecases/s3ConfigManagement/thunks.ts +++ b/web/src/core/usecases/s3ConfigManagement/thunks.ts @@ -184,7 +184,7 @@ export const protectedThunks = { oidcParams_partial }), autoLogin: true, - transformUrlBeforeRedirect: + transformUrlBeforeRedirect_ui: paramsOfBootstrapCore.transformUrlBeforeRedirectToLogin }) ); diff --git a/web/src/ui/App/App.tsx b/web/src/ui/App/App.tsx index 780bbf31e..82692e4fa 100644 --- a/web/src/ui/App/App.tsx +++ b/web/src/ui/App/App.tsx @@ -30,26 +30,25 @@ injectCustomFontFaceIfNotAlreadyDone(); const { CoreProvider } = createCoreProvider({ apiUrl: env.ONYXIA_API_URL, getCurrentLang: () => evtLang.state, - transformUrlBeforeRedirectToLogin: url => - [url] - .map(injectTransferableEnvsInQueryParams) - .map(injectGlobalStatesInSearchParams) - .map( - url => - addParamToUrl({ - url, - name: onyxiaInstancePublicUrlKey, - value: `${window.location.origin}${env.PUBLIC_URL}` - }).newUrl - ) - .map( - url => - addParamToUrl({ - url, - name: "ui_locales", - value: evtLang.state - }).newUrl - )[0], + transformUrlBeforeRedirectToLogin: ({ authorizationUrl, isKeycloak }) => { + if (isKeycloak) { + authorizationUrl = injectTransferableEnvsInQueryParams(authorizationUrl); + authorizationUrl = injectGlobalStatesInSearchParams(authorizationUrl); + authorizationUrl = addParamToUrl({ + url: authorizationUrl, + name: onyxiaInstancePublicUrlKey, + value: `${window.location.origin}${env.PUBLIC_URL}` + }).newUrl; + } + + authorizationUrl = addParamToUrl({ + url: authorizationUrl, + name: "ui_locales", + value: evtLang.state + }).newUrl; + + return authorizationUrl; + }, disablePersonalInfosInjectionInGroup: env.DISABLE_PERSONAL_INFOS_INJECTION_IN_GROUP, isCommandBarEnabledByDefault: !env.DISABLE_COMMAND_BAR, quotaWarningThresholdPercent: env.QUOTA_WARNING_THRESHOLD * 100, diff --git a/web/src/ui/App/Main.tsx b/web/src/ui/App/Main.tsx index af4e5aca7..32354acb0 100644 --- a/web/src/ui/App/Main.tsx +++ b/web/src/ui/App/Main.tsx @@ -5,8 +5,6 @@ import { useSplashScreen } from "onyxia-ui"; import { keyframes } from "tss-react"; import { objectKeys } from "tsafe/objectKeys"; import { pages } from "ui/pages"; -import { useCore, useCoreState } from "core"; -import { CircularProgress } from "onyxia-ui/CircularProgress"; type Props = { className?: string; @@ -17,9 +15,6 @@ export const Main = memo((props: Props) => { const route = useRoute(); - const { userAuthentication } = useCore().functions; - const { isUserLoggedIn } = useCoreState("userAuthentication", "authenticationState"); - const { classes } = useStyles(); return ( @@ -27,21 +22,10 @@ export const Main = memo((props: Props) => { }> {(() => { for (const pageName of objectKeys(pages)) { - //You must be able to replace "home" by any other page and get no type error. + //We must be able to replace "home" by any other page and get no type error. const page = pages[pageName as "home"]; if (page.routeGroup.has(route)) { - if (page.getDoRequireUserLoggedIn(route) && !isUserLoggedIn) { - userAuthentication.login({ - doesCurrentHrefRequiresAuth: true - }); - return ( -
- -
- ); - } - return ( { const { className, route } = props; const { t } = useTranslation({ Account }); @@ -91,7 +92,9 @@ export default function Account(props: Props) { ); -} +}); + +export default Account; const { i18n } = declareComponentKeys< AccountTabId | "text1" | "text2" | "text3" | "personal tokens tooltip" diff --git a/web/src/ui/pages/account/route.ts b/web/src/ui/pages/account/route.ts index 686a23e4a..b25b5c94c 100644 --- a/web/src/ui/pages/account/route.ts +++ b/web/src/ui/pages/account/route.ts @@ -32,5 +32,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => true; diff --git a/web/src/ui/pages/catalog/route.ts b/web/src/ui/pages/catalog/route.ts index f86669c47..b8900561b 100644 --- a/web/src/ui/pages/catalog/route.ts +++ b/web/src/ui/pages/catalog/route.ts @@ -13,5 +13,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => false; diff --git a/web/src/ui/pages/dataExplorer/route.ts b/web/src/ui/pages/dataExplorer/route.ts index cb16e5c76..c5e654f57 100644 --- a/web/src/ui/pages/dataExplorer/route.ts +++ b/web/src/ui/pages/dataExplorer/route.ts @@ -23,5 +23,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => false; diff --git a/web/src/ui/pages/home/route.ts b/web/src/ui/pages/home/route.ts index 3aafabd93..42a26570d 100644 --- a/web/src/ui/pages/home/route.ts +++ b/web/src/ui/pages/home/route.ts @@ -7,5 +7,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => false; diff --git a/web/src/ui/pages/launcher/Launcher.tsx b/web/src/ui/pages/launcher/Launcher.tsx index d00382d56..a84e9c8a8 100644 --- a/web/src/ui/pages/launcher/Launcher.tsx +++ b/web/src/ui/pages/launcher/Launcher.tsx @@ -33,13 +33,14 @@ import { z } from "zod"; import FormControlLabel from "@mui/material/FormControlLabel"; import Radio from "@mui/material/Radio"; import RadioGroup from "@mui/material/RadioGroup"; +import { withLoginEnforced } from "ui/shared/withLoginEnforced"; export type Props = { route: PageRoute; className?: string; }; -export default function Launcher(props: Props) { +const Launcher = withLoginEnforced((props: Props) => { const { className, route } = props; const { t } = useTranslation({ Launcher }); @@ -513,7 +514,9 @@ export default function Launcher(props: Props) { /> ); -} +}); + +export default Launcher; const { i18n } = declareComponentKeys< | { diff --git a/web/src/ui/pages/launcher/route.ts b/web/src/ui/pages/launcher/route.ts index c4cc9dfe6..fcc564404 100644 --- a/web/src/ui/pages/launcher/route.ts +++ b/web/src/ui/pages/launcher/route.ts @@ -192,5 +192,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => true; diff --git a/web/src/ui/pages/myFiles/MyFiles.tsx b/web/src/ui/pages/myFiles/MyFiles.tsx index 5a510d14f..ee12a853a 100644 --- a/web/src/ui/pages/myFiles/MyFiles.tsx +++ b/web/src/ui/pages/myFiles/MyFiles.tsx @@ -21,19 +21,22 @@ import { assert } from "tsafe/assert"; import { env } from "env"; import { getIconUrlByName, customIcons } from "lazy-icons"; import { MyFilesDisabledDialog } from "./MyFilesDisabledDialog"; +import { withLoginEnforced } from "ui/shared/withLoginEnforced"; export type Props = { route: PageRoute; className?: string; }; -export default function MyFilesMaybeDisabled(props: Props) { +const MyFilesMaybeDisabled = withLoginEnforced((props: Props) => { const isFileExplorerEnabled = useCoreState("fileExplorer", "isFileExplorerEnabled"); if (!isFileExplorerEnabled) { return ; } return ; -} +}); + +export default MyFilesMaybeDisabled; function MyFiles(props: Props) { const { className, route } = props; diff --git a/web/src/ui/pages/myFiles/route.ts b/web/src/ui/pages/myFiles/route.ts index 016760529..63950bc04 100644 --- a/web/src/ui/pages/myFiles/route.ts +++ b/web/src/ui/pages/myFiles/route.ts @@ -37,5 +37,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => true; diff --git a/web/src/ui/pages/mySecrets/MySecrets.tsx b/web/src/ui/pages/mySecrets/MySecrets.tsx index a5a69fe9f..624d12a64 100644 --- a/web/src/ui/pages/mySecrets/MySecrets.tsx +++ b/web/src/ui/pages/mySecrets/MySecrets.tsx @@ -22,13 +22,14 @@ import type { PageRoute } from "./route"; import { useEvt } from "evt/hooks"; import { getIconUrlByName, customIcons } from "lazy-icons"; import { env } from "env"; +import { withLoginEnforced } from "ui/shared/withLoginEnforced"; export type Props = { route: PageRoute; className?: string; }; -export default function MySecrets(props: Props) { +const MySecrets = withLoginEnforced((props: Props) => { const { className, route } = props; const { t } = useTranslation({ MySecrets }); @@ -310,7 +311,9 @@ export default function MySecrets(props: Props) { /> ); -} +}); + +export default MySecrets; const { i18n } = declareComponentKeys< | "page title - my secrets" diff --git a/web/src/ui/pages/mySecrets/route.ts b/web/src/ui/pages/mySecrets/route.ts index 7ee09ceed..3e986d65b 100644 --- a/web/src/ui/pages/mySecrets/route.ts +++ b/web/src/ui/pages/mySecrets/route.ts @@ -13,5 +13,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => true; diff --git a/web/src/ui/pages/myService/MyService.tsx b/web/src/ui/pages/myService/MyService.tsx index 09870c1d8..0583e0dca 100644 --- a/web/src/ui/pages/myService/MyService.tsx +++ b/web/src/ui/pages/myService/MyService.tsx @@ -13,13 +13,14 @@ import { CommandBar } from "ui/shared/CommandBar"; import { useEvt } from "evt/hooks"; import { useDomRect } from "powerhooks/useDomRect"; import { declareComponentKeys, useTranslation } from "ui/i18n"; +import { withLoginEnforced } from "ui/shared/withLoginEnforced"; export type Props = { route: PageRoute; className?: string; }; -export default function MyService(props: Props) { +const MyService = withLoginEnforced((props: Props) => { const { className, route } = props; const { t } = useTranslation({ MyService }); @@ -149,7 +150,9 @@ export default function MyService(props: Props) { })()} ); -} +}); + +export default MyService; const { i18n } = declareComponentKeys<{ K: "page title"; diff --git a/web/src/ui/pages/myService/route.ts b/web/src/ui/pages/myService/route.ts index 9b7766532..42a2b0b73 100644 --- a/web/src/ui/pages/myService/route.ts +++ b/web/src/ui/pages/myService/route.ts @@ -12,5 +12,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => true; diff --git a/web/src/ui/pages/myServices/MyServices.tsx b/web/src/ui/pages/myServices/MyServices.tsx index e469a3380..13aa1d7ae 100644 --- a/web/src/ui/pages/myServices/MyServices.tsx +++ b/web/src/ui/pages/myServices/MyServices.tsx @@ -34,13 +34,14 @@ import { } from "./ClusterEventsSnackbar"; import { useEvt } from "evt/hooks"; import { getIconUrlByName, customIcons } from "lazy-icons"; +import { withLoginEnforced } from "ui/shared/withLoginEnforced"; export type Props = { route: PageRoute; className?: string; }; -export default function MyServices(props: Props) { +const MyServices = withLoginEnforced((props: Props) => { const { className, route } = props; const { t } = useTranslation({ MyServices }); @@ -397,7 +398,9 @@ export default function MyServices(props: Props) { /> ); -} +}); + +export default MyServices; function useCommandBarPositioning() { const { diff --git a/web/src/ui/pages/myServices/route.ts b/web/src/ui/pages/myServices/route.ts index 6c92c5cd0..d92f9a2a1 100644 --- a/web/src/ui/pages/myServices/route.ts +++ b/web/src/ui/pages/myServices/route.ts @@ -13,5 +13,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => true; diff --git a/web/src/ui/pages/page404/route.ts b/web/src/ui/pages/page404/route.ts index 129f7b333..ab86d820a 100644 --- a/web/src/ui/pages/page404/route.ts +++ b/web/src/ui/pages/page404/route.ts @@ -7,5 +7,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => false; diff --git a/web/src/ui/pages/projectSettings/ProjectSettings.tsx b/web/src/ui/pages/projectSettings/ProjectSettings.tsx index a072f5ac4..d7211915d 100644 --- a/web/src/ui/pages/projectSettings/ProjectSettings.tsx +++ b/web/src/ui/pages/projectSettings/ProjectSettings.tsx @@ -11,13 +11,14 @@ import { assert, type Equals } from "tsafe/assert"; import type { PageRoute } from "./route"; import { useCoreState } from "core"; import { getIconUrlByName, customIcons } from "lazy-icons"; +import { withLoginEnforced } from "ui/shared/withLoginEnforced"; export type Props = { route: PageRoute; className?: string; }; -export default function ProjectSettings(props: Props) { +const ProjectSettings = withLoginEnforced((props: Props) => { const { className, route } = props; const { t } = useTranslation({ ProjectSettings }); @@ -67,7 +68,9 @@ export default function ProjectSettings(props: Props) { ); -} +}); + +export default ProjectSettings; const { i18n } = declareComponentKeys< | TabId diff --git a/web/src/ui/pages/projectSettings/route.ts b/web/src/ui/pages/projectSettings/route.ts index 2ff7c54c6..3b3afabd1 100644 --- a/web/src/ui/pages/projectSettings/route.ts +++ b/web/src/ui/pages/projectSettings/route.ts @@ -32,5 +32,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => true; diff --git a/web/src/ui/pages/sqlOlapShell/SqlOlapShell.tsx b/web/src/ui/pages/sqlOlapShell/SqlOlapShell.tsx index d1cf3d581..1326d3717 100644 --- a/web/src/ui/pages/sqlOlapShell/SqlOlapShell.tsx +++ b/web/src/ui/pages/sqlOlapShell/SqlOlapShell.tsx @@ -7,13 +7,14 @@ import type { PageRoute } from "./route"; import { CircularProgress } from "onyxia-ui/CircularProgress"; import * as duckdbWasmShell from "@duckdb/duckdb-wasm-shell"; import shellBgWasmUrl from "@duckdb/duckdb-wasm-shell/dist/shell_bg.wasm?url"; +import { withLoginEnforced } from "ui/shared/withLoginEnforced"; type Props = { route: PageRoute; className?: string; }; -export default function SqlOlapShell(props: Props) { +const SqlOlapShell = withLoginEnforced((props: Props) => { const { className } = props; const isReady = useCoreState("sqlOlapShell", "isReady"); @@ -34,7 +35,9 @@ export default function SqlOlapShell(props: Props) { } return ; -} +}); + +export default SqlOlapShell; function ReadySqlOlapShell(params: Props) { const { className } = params; diff --git a/web/src/ui/pages/sqlOlapShell/route.ts b/web/src/ui/pages/sqlOlapShell/route.ts index a35ecba30..094e66ba5 100644 --- a/web/src/ui/pages/sqlOlapShell/route.ts +++ b/web/src/ui/pages/sqlOlapShell/route.ts @@ -7,5 +7,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => true; diff --git a/web/src/ui/pages/terms/route.ts b/web/src/ui/pages/terms/route.ts index 44fff2f23..af53878ab 100644 --- a/web/src/ui/pages/terms/route.ts +++ b/web/src/ui/pages/terms/route.ts @@ -7,5 +7,3 @@ export const routeDefs = { export const routeGroup = createGroup(Object.values(createRouter(routeDefs).routes)); export type PageRoute = Route; - -export const getDoRequireUserLoggedIn: (route: PageRoute) => boolean = () => false; diff --git a/web/src/ui/shared/withLoginEnforced.tsx b/web/src/ui/shared/withLoginEnforced.tsx new file mode 100644 index 000000000..cacf58ad8 --- /dev/null +++ b/web/src/ui/shared/withLoginEnforced.tsx @@ -0,0 +1,51 @@ +import { useEffect, type ComponentType, type FC } from "react"; +import { useCore, useCoreState } from "core"; +import { CircularProgress } from "onyxia-ui/CircularProgress"; +import { tss } from "tss"; + +export function withLoginEnforced>( + Component: ComponentType +): FC { + function ComponentWithLoginEnforced(props: Props) { + const { userAuthentication } = useCore().functions; + const { isUserLoggedIn } = useCoreState( + "userAuthentication", + "authenticationState" + ); + + useEffect(() => { + if (isUserLoggedIn) { + return; + } + + console.log( + "Redirecting to login page!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + ); + + userAuthentication.login({ doesCurrentHrefRequiresAuth: true }); + }, []); + + const { classes } = useStyles(); + + if (!isUserLoggedIn) { + return ( +
+ +
+ ); + } + + return ; + } + + return ComponentWithLoginEnforced; +} + +const useStyles = tss.withName({ withLoginEnforced }).create({ + loginRedirect: { + display: "flex", + justifyContent: "center", + alignItems: "center", + height: "100%" + } +}); diff --git a/web/yarn.lock b/web/yarn.lock index a18fe1d5b..8dba072b0 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -5613,10 +5613,10 @@ object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -oidc-spa@^6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/oidc-spa/-/oidc-spa-6.5.2.tgz#c4bfeab0b440e821d79c4815f244c177dd72037f" - integrity sha512-YZ/Gvo6o/M78C3fQsJK+Zq+AUXOR//+njgeb1bP4dWdfjBFo7qH0x19ccDBaooVLwaa2+mW1o7o3bWKKOIHNhA== +oidc-spa@^6.9.4: + version "6.9.4" + resolved "https://registry.yarnpkg.com/oidc-spa/-/oidc-spa-6.9.4.tgz#cd76a0460a46ced05cc0e8333babbe0f1e4fed8a" + integrity sha512-24AswImnpbeP9ae9v3yak8SrmxBSql6K4sDGfND0aeSmjbiWv4cDYVULaEDYTD6PQa3JqaoYwKU9Zriv4IJiew== once@^1.3.0: version "1.4.0"