From 6058670db002f59ce1962d86a2e47028d22e8d15 Mon Sep 17 00:00:00 2001 From: Phil Owen Date: Fri, 13 Mar 2026 09:53:53 -0400 Subject: [PATCH 1/2] implmenting keycloak. --- src/contexts/workspaces-context/api.ts | 8 ++++++++ src/contexts/workspaces-context/api.types.ts | 1 + src/views/workspaces/login/login.js | 14 ++++++++++---- src/views/workspaces/login/sso/index.js | 3 ++- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/contexts/workspaces-context/api.ts b/src/contexts/workspaces-context/api.ts index a72cc138..ccfc90a5 100644 --- a/src/contexts/workspaces-context/api.ts +++ b/src/contexts/workspaces-context/api.ts @@ -292,6 +292,14 @@ export class WorkspacesAPI implements IWorkspacesAPI { window.location.href = url } + @APIRequest() + loginKeycloak() { + // const url = `http://localhost:8080/Keycloak/auth?client_id=django-app&response_type=code&scope=openid&redirect_uri=http://localhost:8000/accounts/keycloak/login/callback/` + + const url = `${this.apiUrl}../../accounts/oidc/keycloak/login/`; + + window.location.href = url + } @APIRequest() loginSAMLGoogle() { /** I've been informed that we aren't actually using Google SAML, we're using OAuth... but this gets the job done regardless. */ diff --git a/src/contexts/workspaces-context/api.types.ts b/src/contexts/workspaces-context/api.types.ts index 19977e0c..876610d0 100644 --- a/src/contexts/workspaces-context/api.types.ts +++ b/src/contexts/workspaces-context/api.types.ts @@ -225,6 +225,7 @@ export interface IWorkspacesAPI { loginSAMLGoogle(): Promise loginSAMLCILogon(): void loginDex(): void + loginKeycloak(): void logout(fetchOptions?: AxiosRequestConfig): Promise /** May throw a WhitelistRequiredError */ diff --git a/src/views/workspaces/login/login.js b/src/views/workspaces/login/login.js index ea18f77b..0989d765 100644 --- a/src/views/workspaces/login/login.js +++ b/src/views/workspaces/login/login.js @@ -6,7 +6,7 @@ import { LoginForm } from '@ant-design/pro-form' import classNames from 'classnames' import { FormWrapper } from './form-wrapper' import { UsernameInput, PasswordInput } from './form-fields' -import { CILogonSSO, GithubSSO, GoogleSSO, UNCSSO, DexOAuth } from './sso' +import { CILogonSSO, GithubSSO, GoogleSSO, UNCSSO, DexOAuth, KeycloakOAuth } from './sso' import { withAPIReady } from '../' import { useDest, useEnvironment, useWorkspacesAPI } from '../../../contexts' import '@ant-design/pro-form/dist/form.css' @@ -40,7 +40,7 @@ const WhitelistRequired = (props) => ( // ) -const SSOLoginOptions = ({ main, unc, google, github, cilogon, dex, onWhitelistRequired, onSignupRequired }) => ( +const SSOLoginOptions = ({ main, unc, google, github, cilogon, dex, keycloak, onWhitelistRequired, onSignupRequired }) => (
@@ -63,6 +63,9 @@ const SSOLoginOptions = ({ main, unc, google, github, cilogon, dex, onWhitelistR { dex && ( ) } + { keycloak && ( + + ) }
) @@ -97,14 +100,16 @@ export const WorkspaceLoginView = withAPIReady(({ const allowGithubLogin = useMemo(() => loginProviders.includes("GitHub"), [loginProviders]) const allowCILogon = useMemo(() => loginProviders.includes("CILogon"), [loginProviders]) const allowDexLogon = useMemo(() => loginProviders.includes("dex"), [loginProviders]) + const allowKeycloakLogon = useMemo(() => loginProviders.includes("keycloak"), [loginProviders]) const hasAdditionalProviders = useMemo(() => ( allowUncLogin || allowGoogleLogin || allowGithubLogin || allowCILogon || - allowDexLogon - ), [allowUncLogin, allowGoogleLogin, allowGithubLogin, allowCILogon, allowDexLogon]) + allowDexLogon || + allowKeycloakLogon + ), [allowUncLogin, allowGoogleLogin, allowGithubLogin, allowCILogon, allowDexLogon, allowKeycloakLogon]) const showWhitelistRequired = useMemo(() => { const required = new URLSearchParams(location.search).get("whitelist_required") @@ -219,6 +224,7 @@ export const WorkspaceLoginView = withAPIReady(({ github={ allowGithubLogin } cilogon={ allowCILogon } dex={ allowDexLogon } + keycloak={ allowKeycloakLogon } onWhitelistRequired={ () => { setShowWhitelistRequired(true) } } diff --git a/src/views/workspaces/login/sso/index.js b/src/views/workspaces/login/sso/index.js index d273a82c..1ba0fa5a 100644 --- a/src/views/workspaces/login/sso/index.js +++ b/src/views/workspaces/login/sso/index.js @@ -2,4 +2,5 @@ export * from './unc-sso' export * from './cilogon-sso' export * from './dex-oauth' export * from './google-sso' -export * from './github-sso' \ No newline at end of file +export * from './github-sso' +export * from './keycloak-oauth' \ No newline at end of file From 07007c9279359ed6a65a87c6bdcb3d0005bcef97 Mon Sep 17 00:00:00 2001 From: Phil Owen Date: Fri, 13 Mar 2026 10:08:05 -0400 Subject: [PATCH 2/2] implmenting keycloak. --- .../login/sso/keycloak-glyph-color.png | Bin 0 -> 4887 bytes .../workspaces/login/sso/keycloak-oauth.js | 38 ++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/views/workspaces/login/sso/keycloak-glyph-color.png create mode 100644 src/views/workspaces/login/sso/keycloak-oauth.js diff --git a/src/views/workspaces/login/sso/keycloak-glyph-color.png b/src/views/workspaces/login/sso/keycloak-glyph-color.png new file mode 100644 index 0000000000000000000000000000000000000000..90ab618d99f1fe22703a3d9656c1e4916f42ff92 GIT binary patch literal 4887 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 qH7aih1pyO?zwr literal 0 HcmV?d00001 diff --git a/src/views/workspaces/login/sso/keycloak-oauth.js b/src/views/workspaces/login/sso/keycloak-oauth.js new file mode 100644 index 00000000..6a50a17d --- /dev/null +++ b/src/views/workspaces/login/sso/keycloak-oauth.js @@ -0,0 +1,38 @@ +import { useMemo } from 'react' +import { useWorkspacesAPI } from '../../../../contexts' +import { SAMLButton } from './sso-button' +import KeycloakPng from './keycloak-glyph-color.png' +import Icon from '@ant-design/icons' + + +/* + Component to render the button and handling of the Dex OAuth + */ +export const KeycloakOAuth = (props) => { + // get the context for the workspaces + const { api } = useWorkspacesAPI() + + // create some state for the icon + const icon = useMemo(() => ( + ( + + ) } /> + ), []) + + // return the button control for rendering + return ( + api.loginKeycloak() } + { ...props } + > + + ) +} \ No newline at end of file