Skip to content

Commit 6ff15c2

Browse files
committed
backend: cmd: pkg: Add oidc autologin flag and config
1 parent 5808125 commit 6ff15c2

File tree

7 files changed

+62
-4
lines changed

7 files changed

+62
-4
lines changed

backend/cmd/headlamp.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const (
9797
type clientConfig struct {
9898
Clusters []Cluster `json:"clusters"`
9999
IsDynamicClusterEnabled bool `json:"isDynamicClusterEnabled"`
100+
OidcAutoLogin bool `json:"oidcAutoLogin"`
100101
}
101102

102103
type OauthConfig struct {
@@ -1749,7 +1750,11 @@ func parseClusterFromKubeConfig(kubeConfigs []string) ([]Cluster, []error) {
17491750
func (c *HeadlampConfig) getConfig(w http.ResponseWriter, r *http.Request) {
17501751
w.Header().Set("Content-Type", "application/json")
17511752

1752-
clientConfig := clientConfig{c.getClusters(), c.EnableDynamicClusters}
1753+
clientConfig := clientConfig{
1754+
Clusters: c.getClusters(),
1755+
IsDynamicClusterEnabled: c.EnableDynamicClusters,
1756+
OidcAutoLogin: c.OidcAutoLogin,
1757+
}
17531758

17541759
if err := json.NewEncoder(w).Encode(&clientConfig); err != nil {
17551760
logger.Log(logger.LevelError, nil, err, "encoding config")

backend/cmd/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ func createHeadlampConfig(conf *config.Config) *HeadlampConfig {
125125

126126
cfg := &headlampconfig.HeadlampConfig{
127127
HeadlampCFG: buildHeadlampCFG(conf, kubeConfigStore),
128+
OidcAutoLogin: conf.OidcAutoLogin,
128129
OidcClientID: conf.OidcClientID,
129130
OidcValidatorClientID: conf.OidcValidatorClientID,
130131
OidcClientSecret: conf.OidcClientSecret,

backend/cmd/stateless.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func (c *HeadlampConfig) parseKubeConfig(w http.ResponseWriter, r *http.Request)
178178
return
179179
}
180180

181-
clientConfig := clientConfig{contexts, c.EnableDynamicClusters}
181+
clientConfig := clientConfig{contexts, c.EnableDynamicClusters, c.OidcAutoLogin}
182182

183183
if err := json.NewEncoder(w).Encode(&clientConfig); err != nil {
184184
logger.Log(logger.LevelError, nil, err, "encoding config")

backend/pkg/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type Config struct {
5555
UserPluginsDir string `koanf:"user-plugins-dir"`
5656
BaseURL string `koanf:"base-url"`
5757
ProxyURLs string `koanf:"proxy-urls"`
58+
OidcAutoLogin bool `koanf:"oidc-auto-login"`
5859
OidcClientID string `koanf:"oidc-client-id"`
5960
OidcValidatorClientID string `koanf:"oidc-validator-client-id"`
6061
OidcClientSecret string `koanf:"oidc-client-secret"`
@@ -437,6 +438,7 @@ func addGeneralFlags(f *flag.FlagSet) {
437438
}
438439

439440
func addOIDCFlags(f *flag.FlagSet) {
441+
f.Bool("oidc-auto-login", false, "Automatic Redirect to OIDC provider")
440442
f.String("oidc-client-id", "", "ClientID for OIDC")
441443
f.String("oidc-client-secret", "", "ClientSecret for OIDC")
442444
f.String("oidc-validator-client-id", "", "Override ClientID for OIDC during validation")

backend/pkg/headlampconfig/headlampConfig.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type WebSocketMultiplexer interface {
1717
// HeadlampConfig holds full server config. Lives here so packages (e.g. k8cache) can import without cmd.
1818
type HeadlampConfig struct {
1919
*HeadlampCFG
20+
OidcAutoLogin bool
2021
OidcClientID string
2122
OidcValidatorClientID string
2223
OidcClientSecret string

frontend/src/components/App/Layout.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { useQuery } from '@tanstack/react-query';
2626
import { useEffect } from 'react';
2727
import { Trans, useTranslation } from 'react-i18next';
2828
import { useDispatch } from 'react-redux';
29+
import { useLocation } from 'react-router';
30+
import { getAppUrl } from '../../helpers/getAppUrl';
2931
import { getCluster } from '../../lib/cluster';
3032
import { getSelectedClusters } from '../../lib/cluster';
3133
import { useCluster, useClustersConf } from '../../lib/k8s';
@@ -146,7 +148,11 @@ const fetchConfig = (dispatch: Dispatch<UnknownAction>) => {
146148
clustersToConfig[cluster.name] = cluster;
147149
});
148150

149-
const configToStore = { ...config, clusters: clustersToConfig };
151+
const configToStore = {
152+
...config,
153+
clusters: clustersToConfig,
154+
oidcAutoLogin: config.oidcAutoLogin,
155+
};
150156

151157
if (clusters === null) {
152158
dispatch(setConfig(configToStore));
@@ -191,6 +197,7 @@ export default function Layout({}: LayoutProps) {
191197
const isFullWidth = useTypedSelector(state => state.ui.isFullWidth);
192198
const { t } = useTranslation();
193199
const allClusters = useClustersConf();
200+
const location = useLocation();
194201

195202
/** This fetches the cluster config from the backend and updates the redux store on an interval.
196203
* When stateless clusters are enabled, it also fetches the stateless cluster config from the
@@ -235,6 +242,36 @@ export default function Layout({}: LayoutProps) {
235242

236243
const panels = useUIPanelsGroupedBySide();
237244

245+
const oidcAutoLogin = useTypedSelector(state => state.config.oidcAutoLogin);
246+
247+
useEffect(() => {
248+
if (!oidcAutoLogin || !clusters) {
249+
return;
250+
}
251+
const urlParams = new URLSearchParams(window.location.search);
252+
const isLoggingOut = urlParams.get('logout') === 'true';
253+
if (isLoggingOut || !!error) {
254+
return;
255+
}
256+
const isCallbackPath =
257+
window.location.pathname.includes('oidc-callback') ||
258+
urlParams.has('code') ||
259+
urlParams.has('state');
260+
if (isCallbackPath) {
261+
return;
262+
}
263+
const currentClusterName = getCluster();
264+
const currentCluster = currentClusterName ? clusters[currentClusterName] : null;
265+
const isOIDC = currentCluster?.auth_type === 'oidc';
266+
if (!isOIDC) {
267+
return;
268+
}
269+
if (currentCluster.useToken === undefined) {
270+
const oauthUrl = `${getAppUrl()}oidc?dt=${Date.now()}&cluster=${getCluster()}`;
271+
window.location.href = oauthUrl;
272+
}
273+
}, [oidcAutoLogin, clusters, error, location.pathname]);
274+
238275
if (!disableBackendLoader) {
239276
if (error && !config) {
240277
return <ErrorPage message={<Trans>Failed to connect to the backend</Trans>} error={error} />;

frontend/src/redux/configSlice.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export interface ConfigState {
5757
useEvict: boolean;
5858
[key: string]: any;
5959
};
60+
/**
61+
* Whether OIDC auto-login is enabled. Null indicates the value hasn't been loaded from the backend yet.
62+
*/
63+
oidcAutoLogin?: boolean | null;
6064
}
6165

6266
export const defaultTableRowsPerPageOptions = [15, 25, 50];
@@ -71,6 +75,7 @@ export const initialState: ConfigState = {
7175
clusters: null,
7276
statelessClusters: null,
7377
allClusters: null,
78+
oidcAutoLogin: null,
7479
settings: {
7580
tableRowsPerPageOptions:
7681
storedSettings.tableRowsPerPageOptions || defaultTableRowsPerPageOptions,
@@ -89,8 +94,15 @@ const configSlice = createSlice({
8994
* @param state - The current state.
9095
* @param action - The payload action containing the config.
9196
*/
92-
setConfig(state, action: PayloadAction<{ clusters: ConfigState['clusters'] }>) {
97+
setConfig(
98+
state,
99+
action: PayloadAction<{ clusters: ConfigState['clusters']; oidcAutoLogin?: boolean }>
100+
) {
93101
state.clusters = action.payload.clusters;
102+
103+
if (state.oidcAutoLogin !== undefined) {
104+
state.oidcAutoLogin = action.payload.oidcAutoLogin;
105+
}
94106
},
95107
/**
96108
* Save the config. To both the store, and localStorage.

0 commit comments

Comments
 (0)