diff --git a/ui/src/features/auth/context/auth-context-provider.tsx b/ui/src/features/auth/context/auth-context-provider.tsx index 643a1d27b..72c30048c 100644 --- a/ui/src/features/auth/context/auth-context-provider.tsx +++ b/ui/src/features/auth/context/auth-context-provider.tsx @@ -2,7 +2,7 @@ import React, { PropsWithChildren, useMemo } from 'react'; import { authTokenKey, refreshTokenKey } from '@ui/config/auth'; -import { extractInfoFromJWT, JWTInfo } from '../utils'; +import { extractInfoFromJWT, JWTInfo } from '../jwt-utils'; import { AuthContext, AuthContextType } from './auth-context'; diff --git a/ui/src/features/auth/context/auth-context.tsx b/ui/src/features/auth/context/auth-context.tsx index d08787c68..8b02d8171 100644 --- a/ui/src/features/auth/context/auth-context.tsx +++ b/ui/src/features/auth/context/auth-context.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { JWTInfo } from '../utils'; +import { JWTInfo } from '../jwt-utils'; export interface AuthContextType { isLoggedIn: boolean; diff --git a/ui/src/features/auth/utils.ts b/ui/src/features/auth/jwt-utils.ts similarity index 82% rename from ui/src/features/auth/utils.ts rename to ui/src/features/auth/jwt-utils.ts index f22d74416..f04093923 100644 --- a/ui/src/features/auth/utils.ts +++ b/ui/src/features/auth/jwt-utils.ts @@ -1,5 +1,3 @@ -import { ClientAuth } from 'oauth4webapi'; - // https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims export type JWTInfo = { sub: string; @@ -40,9 +38,3 @@ export const getUserEmail = (user?: JWTInfo | null) => { return meta; }; - -export const oidcClientAuth: ClientAuth = () => { - // equivalent function for token_endpoint_auth_method: 'none' -}; - -export const shouldAllowIdpHttpRequest = () => __UI_VERSION__ === 'development'; diff --git a/ui/src/features/auth/oidc-login.tsx b/ui/src/features/auth/oidc-login.tsx index e8815cd67..f924e52bf 100644 --- a/ui/src/features/auth/oidc-login.tsx +++ b/ui/src/features/auth/oidc-login.tsx @@ -18,7 +18,11 @@ import { useLocation } from 'react-router-dom'; import { OIDCConfig } from '@ui/gen/service/v1alpha1/service_pb'; import { useAuthContext } from './context/use-auth-context'; -import { oidcClientAuth, shouldAllowIdpHttpRequest as shouldAllowHttpRequest } from './utils'; +import { + getOIDCScopes, + oidcClientAuth, + shouldAllowIdpHttpRequest as shouldAllowHttpRequest +} from './oidc-utils'; const codeVerifierKey = 'PKCE_code_verifier'; @@ -92,14 +96,7 @@ export const OIDCLogin = ({ oidcConfig }: Props) => { url.searchParams.set('code_challenge_method', 'S256'); url.searchParams.set('redirect_uri', redirectURI); url.searchParams.set('response_type', 'code'); - url.searchParams.set( - 'scope', - [ - ...oidcConfig.scopes, - // Add offline_access scope if it does not exist - ...(oidcConfig.scopes.includes('offline_access') ? [] : ['offline_access']) - ].join(' ') - ); + url.searchParams.set('scope', getOIDCScopes(oidcConfig, as).join(' ')); window.location.replace(url.toString()); }; diff --git a/ui/src/features/auth/oidc-utils.ts b/ui/src/features/auth/oidc-utils.ts new file mode 100644 index 000000000..c1ea81c32 --- /dev/null +++ b/ui/src/features/auth/oidc-utils.ts @@ -0,0 +1,20 @@ +import { AuthorizationServer, ClientAuth } from 'oauth4webapi'; + +import { OIDCConfig } from '@ui/gen/service/v1alpha1/service_pb'; + +export const oidcClientAuth: ClientAuth = () => { + // equivalent function for token_endpoint_auth_method: 'none' +}; + +export const shouldAllowIdpHttpRequest = () => __UI_VERSION__ === 'development'; + +export const getOIDCScopes = (userOIDCConfig: OIDCConfig, idp: AuthorizationServer) => { + const scopes = [...userOIDCConfig.scopes]; + + // add offline_access scope automatically only if it is supported by IDP + if (!scopes.includes('offline_access') && idp.scopes_supported?.includes('offline_access')) { + scopes.push('offline_access'); + } + + return scopes; +}; diff --git a/ui/src/features/auth/token-renew.tsx b/ui/src/features/auth/token-renew.tsx index 96ac2ae5e..31fe7ae8f 100644 --- a/ui/src/features/auth/token-renew.tsx +++ b/ui/src/features/auth/token-renew.tsx @@ -18,7 +18,7 @@ import { getPublicConfig } from '@ui/gen/service/v1alpha1/service-KargoService_c import { LoadingState } from '../common'; import { useAuthContext } from './context/use-auth-context'; -import { oidcClientAuth, shouldAllowIdpHttpRequest as shouldAllowHttpRequest } from './utils'; +import { oidcClientAuth, shouldAllowIdpHttpRequest as shouldAllowHttpRequest } from './oidc-utils'; export const TokenRenew = () => { const navigate = useNavigate(); diff --git a/ui/src/features/common/layout/main-layout.tsx b/ui/src/features/common/layout/main-layout.tsx index 3f201f7bb..e307673c3 100644 --- a/ui/src/features/common/layout/main-layout.tsx +++ b/ui/src/features/common/layout/main-layout.tsx @@ -13,7 +13,7 @@ import { Outlet } from 'react-router-dom'; import { paths } from '@ui/config/paths'; import { useAuthContext } from '@ui/features/auth/context/use-auth-context'; -import { isJWTDirty } from '@ui/features/auth/utils'; +import { isJWTDirty } from '@ui/features/auth/jwt-utils'; import { KargoLogo } from '@ui/features/common/logo/logo'; import * as styles from './main-layout.module.less'; diff --git a/ui/src/pages/user.tsx b/ui/src/pages/user.tsx index 6201f874a..378dbcccc 100644 --- a/ui/src/pages/user.tsx +++ b/ui/src/pages/user.tsx @@ -7,7 +7,7 @@ import { Navigate } from 'react-router-dom'; import { redirectToQueryParam } from '@ui/config/auth'; import { paths } from '@ui/config/paths'; import { useAuthContext } from '@ui/features/auth/context/use-auth-context'; -import { isAdmin, isJWTDirty } from '@ui/features/auth/utils'; +import { isAdmin, isJWTDirty } from '@ui/features/auth/jwt-utils'; import { PageTitle } from '@ui/features/common'; export const User = () => {