From c70d73cfb16315756b72462de020b5615b87b240 Mon Sep 17 00:00:00 2001 From: Mayursinh Sarvaiya Date: Wed, 4 Dec 2024 15:07:48 -0400 Subject: [PATCH] chore(ui): bump `oauth4webapi` major version (#3039) Signed-off-by: Mayursinh Sarvaiya --- ui/package.json | 2 +- ui/pnpm-lock.yaml | 10 +-- ui/src/features/auth/oidc-login.tsx | 98 ++++++++++++++++++---------- ui/src/features/auth/token-renew.tsx | 26 ++++++-- ui/src/features/auth/utils.ts | 8 +++ 5 files changed, 96 insertions(+), 48 deletions(-) diff --git a/ui/package.json b/ui/package.json index 277a6bf9f..fbc80c950 100644 --- a/ui/package.json +++ b/ui/package.json @@ -75,7 +75,7 @@ "moment": "^2.30.1", "monaco-editor": "^0.52.0", "monaco-yaml": "^5.2.2", - "oauth4webapi": "^2.17.0", + "oauth4webapi": "^3.1.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.0", diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index d36391207..982a903d6 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -78,8 +78,8 @@ importers: specifier: ^5.2.2 version: 5.2.2(monaco-editor@0.52.0) oauth4webapi: - specifier: ^2.17.0 - version: 2.17.0 + specifier: ^3.1.3 + version: 3.1.4 react: specifier: ^18.3.1 version: 18.3.1 @@ -2380,8 +2380,8 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - oauth4webapi@2.17.0: - resolution: {integrity: sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==} + oauth4webapi@3.1.4: + resolution: {integrity: sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -5961,7 +5961,7 @@ snapshots: dependencies: boolbase: 1.0.0 - oauth4webapi@2.17.0: {} + oauth4webapi@3.1.4: {} object-assign@4.1.1: {} diff --git a/ui/src/features/auth/oidc-login.tsx b/ui/src/features/auth/oidc-login.tsx index 523ef0682..e8815cd67 100644 --- a/ui/src/features/auth/oidc-login.tsx +++ b/ui/src/features/auth/oidc-login.tsx @@ -1,12 +1,24 @@ import { useQuery } from '@tanstack/react-query'; import { Button, notification } from 'antd'; -import * as oauth from 'oauth4webapi'; +import { + discoveryRequest, + processDiscoveryResponse, + generateRandomCodeVerifier, + calculatePKCECodeChallenge, + validateAuthResponse, + authorizationCodeGrantRequest, + processAuthorizationCodeResponse, + AuthorizationResponseError, + WWWAuthenticateChallengeError, + allowInsecureRequests +} from 'oauth4webapi'; import React from 'react'; 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'; const codeVerifierKey = 'PKCE_code_verifier'; @@ -44,9 +56,10 @@ export const OIDCLogin = ({ oidcConfig }: Props) => { queryKey: [issuerUrl], queryFn: () => issuerUrl && - oauth - .discoveryRequest(issuerUrl) - .then((response) => oauth.processDiscoveryResponse(issuerUrl, response)) + discoveryRequest(issuerUrl, { + [allowInsecureRequests]: shouldAllowHttpRequest() + }) + .then((response) => processDiscoveryResponse(issuerUrl, response)) .then((response) => { if (response.code_challenge_methods_supported?.includes('S256') !== true) { throw new Error('OIDC config fetch error'); @@ -60,7 +73,7 @@ export const OIDCLogin = ({ oidcConfig }: Props) => { React.useEffect(() => { if (error) { const errorMessage = error instanceof Error ? error.message : 'OIDC config fetch error'; - notification.error({ message: errorMessage, placement: 'bottomRight' }); + notification.error({ message: `OIDC: ${errorMessage}`, placement: 'bottomRight' }); } }, [error]); @@ -69,10 +82,10 @@ export const OIDCLogin = ({ oidcConfig }: Props) => { return; } - const code_verifier = oauth.generateRandomCodeVerifier(); + const code_verifier = generateRandomCodeVerifier(); sessionStorage.setItem(codeVerifierKey, code_verifier); - const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier); + const code_challenge = await calculatePKCECodeChallenge(code_verifier); const url = new URL(as.authorization_endpoint); url.searchParams.set('client_id', client.client_id); url.searchParams.set('code_challenge', code_challenge); @@ -106,42 +119,57 @@ export const OIDCLogin = ({ oidcConfig }: Props) => { searchParams.delete('state'); } - const params = oauth.validateAuthResponse(as, client, searchParams, oauth.expectNoState); + try { + const params = validateAuthResponse(as, client, searchParams); + + const response = await authorizationCodeGrantRequest( + as, + client, + oidcClientAuth, + params, + redirectURI, + code_verifier, + { + [allowInsecureRequests]: shouldAllowHttpRequest(), + additionalParameters: [['client_id', client.client_id]] + } + ); - if (oauth.isOAuth2Error(params)) { - notification.error({ - message: 'OIDC: Validation Auth Response error', - placement: 'bottomRight' + const result = await processAuthorizationCodeResponse(as, client, response, { + requireIdToken: true }); - return; - } - const response = await oauth.authorizationCodeGrantRequest( - as, - client, - params, - redirectURI, - code_verifier - ); + if (!result.id_token) { + notification.error({ + message: 'OIDC: Proccess Authorization Code Grant Response error', + placement: 'bottomRight' + }); + return; + } + + onLogin(result.id_token, result.refresh_token); + } catch (err) { + if (err instanceof AuthorizationResponseError) { + notification.error({ + message: 'OIDC: Validation Auth Response error', + placement: 'bottomRight' + }); + return; + } + + if (err instanceof WWWAuthenticateChallengeError) { + notification.error({ + message: 'OIDC: Parsing Authenticate Challenges error', + placement: 'bottomRight' + }); + return; + } - if (oauth.parseWwwAuthenticateChallenges(response)) { notification.error({ - message: 'OIDC: Parsing Authenticate Challenges error', + message: `OIDC: ${JSON.stringify(err)}`, placement: 'bottomRight' }); - return; } - - const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response); - if (oauth.isOAuth2Error(result) || !result.id_token) { - notification.error({ - message: 'OIDC: Proccess Authorization Code Grant Response error', - placement: 'bottomRight' - }); - return; - } - - onLogin(result.id_token, result.refresh_token); })(); }, [as, client, location]); diff --git a/ui/src/features/auth/token-renew.tsx b/ui/src/features/auth/token-renew.tsx index f626136a0..96ac2ae5e 100644 --- a/ui/src/features/auth/token-renew.tsx +++ b/ui/src/features/auth/token-renew.tsx @@ -1,7 +1,13 @@ import { useQuery as useConnectQuery } from '@connectrpc/connect-query'; import { useQuery } from '@tanstack/react-query'; import { notification } from 'antd'; -import * as oauth from 'oauth4webapi'; +import { + allowInsecureRequests, + discoveryRequest, + processDiscoveryResponse, + refreshTokenGrantRequest, + processRefreshTokenResponse +} from 'oauth4webapi'; import React from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; @@ -12,6 +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'; export const TokenRenew = () => { const navigate = useNavigate(); @@ -44,9 +51,10 @@ export const TokenRenew = () => { queryKey: [issuerUrl], queryFn: () => issuerUrl && - oauth - .discoveryRequest(issuerUrl) - .then((response) => oauth.processDiscoveryResponse(issuerUrl, response)) + discoveryRequest(issuerUrl, { + [allowInsecureRequests]: shouldAllowHttpRequest() + }) + .then((response) => processDiscoveryResponse(issuerUrl, response)) .then((response) => { if (response.code_challenge_methods_supported?.includes('S256') !== true) { throw new Error('OIDC config fetch error'); @@ -71,10 +79,14 @@ export const TokenRenew = () => { } (async () => { - const response = await oauth.refreshTokenGrantRequest(as, client, refreshToken); + const response = await refreshTokenGrantRequest(as, client, oidcClientAuth, refreshToken, { + [allowInsecureRequests]: shouldAllowHttpRequest(), + additionalParameters: [['client_id', client.client_id]] + }); + + const result = await processRefreshTokenResponse(as, client, response); - const result = await oauth.processRefreshTokenResponse(as, client, response); - if (oauth.isOAuth2Error(result) || !result.id_token) { + if (!result.id_token) { notification.error({ message: 'OIDC: Proccess Authorization Code Grant Response error', placement: 'bottomRight' diff --git a/ui/src/features/auth/utils.ts b/ui/src/features/auth/utils.ts index f04093923..f22d74416 100644 --- a/ui/src/features/auth/utils.ts +++ b/ui/src/features/auth/utils.ts @@ -1,3 +1,5 @@ +import { ClientAuth } from 'oauth4webapi'; + // https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims export type JWTInfo = { sub: string; @@ -38,3 +40,9 @@ 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';