diff --git a/HACKING.md b/HACKING.md index efbbdb7b7..6edd1e198 100644 --- a/HACKING.md +++ b/HACKING.md @@ -122,7 +122,6 @@ controllerAPIEndpoint: "wss://[controller.ip]:17070/api", To use a local/non-JAAS controller you will need to set: ```shell -identityProviderAvailable: false, isJuju: true, ``` @@ -535,7 +534,6 @@ Change the configuration as follows: ```shell controllerAPIEndpoint: "ws://jimm.localhost:17070/api", -identityProviderAvailable: false, ``` Now you can start the dashboard with: diff --git a/public/config.demo.js b/public/config.demo.js index 329889839..ac43a56aa 100644 --- a/public/config.demo.js +++ b/public/config.demo.js @@ -5,15 +5,11 @@ var jujuDashboardConfig = { controllerAPIEndpoint: "wss://jimm.comsys-internal.v2.demo.canonical.com/api", // Configurable base url to allow hosting the dashboard at different paths. baseAppURL: "/", - // If true, then identity will be provided by a third party provider. This - // boolean is generated by the existance of an identityProviderURL in the - // charm. - identityProviderAvailable: true, // The URL of the third party identity provider. This is typically provided // by the controller when logging in so it's not currently used directly. // But it is available in the charm so putting it here in the event that we // would like to use it in the future. - identityProviderURL: "", + identityProviderURL: "/", // Is this application being rendered in Juju and not JAAS. This flag should // only be used for superficial updates like logos. Use feature detection // for other environment features. diff --git a/public/config.js b/public/config.js index 0ee913065..e20e0205d 100644 --- a/public/config.js +++ b/public/config.js @@ -8,10 +8,6 @@ var jujuDashboardConfig = { controllerAPIEndpoint: "", // Configurable base url to allow hosting the dashboard at different paths. baseAppURL: "/", - // If true, then identity will be provided by a third party provider. This - // boolean is generated by the existance of an identityProviderURL in the - // charm. - identityProviderAvailable: true, // The URL of the third party identity provider. This is typically provided // by the controller when logging in so it's not currently used directly. // But it is available in the charm so putting it here in the event that we diff --git a/public/config.js.go b/public/config.js.go index 497a7366e..c93635c19 100644 --- a/public/config.js.go +++ b/public/config.js.go @@ -4,8 +4,8 @@ var jujuDashboardConfig = { baseControllerURL: null, // Configurable base url to allow deploying to different paths. baseAppURL: "{{.baseAppURL}}", - // If true then identity will be provided by a third party provider. - identityProviderAvailable: {{.identityProviderAvailable}}, + // The URL of the third party identity provider. + identityProviderURL: {{.identityProviderURL}}, // Is this application being rendered in Juju and not JAAS. This flag should // only be used for superficial updates like logos. Use feature detection // for other environment features. diff --git a/src/components/LogIn/LogIn.test.tsx b/src/components/LogIn/LogIn.test.tsx index 36c0eacce..fbdc99cab 100644 --- a/src/components/LogIn/LogIn.test.tsx +++ b/src/components/LogIn/LogIn.test.tsx @@ -2,6 +2,7 @@ import { screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { vi } from "vitest"; +import { AuthMethod } from "store/general/types"; import { configFactory, generalStateFactory } from "testing/factories/general"; import { rootStateFactory } from "testing/factories/root"; import { renderComponent } from "testing/utils"; @@ -41,7 +42,7 @@ describe("LogIn", () => { general: generalStateFactory.withConfig().build({ visitURLs: ["I am a url"], config: configFactory.build({ - identityProviderAvailable: true, + authMethod: AuthMethod.CANDID, }), }), }); @@ -58,7 +59,7 @@ describe("LogIn", () => { const state = rootStateFactory.build({ general: generalStateFactory.withConfig().build({ config: configFactory.build({ - identityProviderAvailable: false, + authMethod: AuthMethod.LOCAL, }), }), }); @@ -78,7 +79,7 @@ describe("LogIn", () => { "wss://controller.example.com": "Controller rejected request", }, config: configFactory.build({ - identityProviderAvailable: false, + authMethod: AuthMethod.LOCAL, }), }), }); @@ -95,7 +96,7 @@ describe("LogIn", () => { "wss://controller.example.com": ErrorResponse.INVALID_TAG, }, config: configFactory.build({ - identityProviderAvailable: false, + authMethod: AuthMethod.LOCAL, }), }), }); @@ -110,7 +111,7 @@ describe("LogIn", () => { "wss://controller.example.com": ErrorResponse.INVALID_FIELD, }, config: configFactory.build({ - identityProviderAvailable: false, + authMethod: AuthMethod.LOCAL, }), }), }); @@ -122,9 +123,9 @@ describe("LogIn", () => { const state = rootStateFactory.build({ general: generalStateFactory.withConfig().build({ config: configFactory.build({ - identityProviderAvailable: true, + authMethod: AuthMethod.CANDID, }), - visitURLs: ["/log-in"], + visitURLs: ["http://example.com/log-in"], }), }); renderComponent(App content, { state }); @@ -145,9 +146,9 @@ describe("LogIn", () => { general: generalStateFactory.withConfig().build({ config: configFactory.build({ isJuju: true, - identityProviderAvailable: false, + authMethod: AuthMethod.LOCAL, }), - visitURLs: ["/log-in"], + visitURLs: ["http://example.com/log-in"], }), }); renderComponent(App content, { state }); diff --git a/src/components/LogIn/LogIn.tsx b/src/components/LogIn/LogIn.tsx index a727cf63f..63fc628bd 100644 --- a/src/components/LogIn/LogIn.tsx +++ b/src/components/LogIn/LogIn.tsx @@ -15,6 +15,7 @@ import { isLoggedIn, getIsJuju, } from "store/general/selectors"; +import { AuthMethod } from "store/general/types"; import { useAppSelector } from "store/store"; import "./_login.scss"; @@ -75,7 +76,7 @@ export default function LogIn({ children }: PropsWithChildren) {
- {config?.identityProviderAvailable ? ( + {config?.authMethod === AuthMethod.CANDID ? ( ) : ( diff --git a/src/index.test.tsx b/src/index.test.tsx index 0d234ec08..01115d5db 100644 --- a/src/index.test.tsx +++ b/src/index.test.tsx @@ -98,6 +98,7 @@ describe("renderApp", () => { .mockImplementation(vi.fn()); const config = configFactory.build({ baseControllerURL: null, + isJuju: true, }); window.jujuDashboardConfig = config; renderApp(); @@ -116,6 +117,7 @@ describe("renderApp", () => { .mockImplementation(vi.fn()); const config = configFactory.build({ baseControllerURL: null, + isJuju: true, }); window.jujuDashboardConfig = config; renderApp(); @@ -146,6 +148,7 @@ describe("renderApp", () => { .mockImplementation(vi.fn()); const config = configFactory.build({ controllerAPIEndpoint: "/api", + isJuju: true, }); window.jujuDashboardConfig = config; renderApp(); @@ -164,6 +167,7 @@ describe("renderApp", () => { .mockImplementation(vi.fn()); const config = configFactory.build({ controllerAPIEndpoint: "/api", + isJuju: true, }); window.jujuDashboardConfig = config; renderApp(); @@ -181,6 +185,7 @@ describe("renderApp", () => { .mockImplementation(vi.fn()); const config = configFactory.build({ controllerAPIEndpoint: "wss://example.com/api", + isJuju: true, }); window.jujuDashboardConfig = config; renderApp(); @@ -190,7 +195,7 @@ describe("renderApp", () => { ); }); - it("connects if there is an identity provider", async () => { + it("connects when using Candid", async () => { // Mock the result of the thunk a normal action so that it can be tested // for. This is necessary because we don't have a full store set up which // can dispatch thunks (and we don't need to handle the thunk, just know it @@ -203,7 +208,8 @@ describe("renderApp", () => { .mockImplementation(vi.fn().mockResolvedValue({ catch: vi.fn() })); const config = configFactory.build({ controllerAPIEndpoint: "wss://example.com/api", - identityProviderAvailable: true, + identityProviderURL: "/candid", + isJuju: true, }); window.jujuDashboardConfig = config; renderApp(); @@ -295,7 +301,8 @@ describe("getControllerAPIEndpointErrors", () => { ); const config = configFactory.build({ baseControllerURL: null, - identityProviderAvailable: true, + identityProviderURL: "/candid", + isJuju: true, }); window.jujuDashboardConfig = config; renderApp(); diff --git a/src/index.tsx b/src/index.tsx index 14a1e1701..7868b516f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,6 +10,8 @@ import App from "components/App"; import reduxStore from "store"; import { thunks as appThunks } from "store/app"; import { actions as generalActions } from "store/general"; +import { AuthMethod } from "store/general/types"; +import type { WindowConfig } from "types"; import packageJSON from "../package.json"; @@ -29,6 +31,16 @@ if (import.meta.env.PROD && window.jujuDashboardConfig?.analyticsEnabled) { Sentry.setTag("dashboardVersion", appVersion); } +const getAuthMethod = (config: WindowConfig) => { + if (!config.isJuju) { + return AuthMethod.OIDC; + } + if (config.identityProviderURL) { + return AuthMethod.CANDID; + } + return AuthMethod.LOCAL; +}; + const addressRegex = new RegExp(/^ws[s]?:\/\/(\S+)\//); export const getControllerAPIEndpointErrors = ( controllerAPIEndpoint?: string, @@ -80,7 +92,13 @@ export const renderApp = () => { renderApp(); function bootstrap() { - const config = window.jujuDashboardConfig; + const windowConfig = window.jujuDashboardConfig; + const config = windowConfig + ? { + ...windowConfig, + authMethod: getAuthMethod(windowConfig), + } + : null; let error: string | null = null; if (!config) { error = Label.NO_CONFIG; @@ -128,8 +146,8 @@ function bootstrap() { reduxStore.dispatch(generalActions.storeConfig(config)); reduxStore.dispatch(generalActions.storeVersion(appVersion)); - if (config.identityProviderAvailable) { - // If an identity provider is available then try and connect automatically + if (config.authMethod === AuthMethod.CANDID) { + // If using Candid authentication then try and connect automatically // If not then wait for the login UI to trigger this reduxStore .dispatch(appThunks.connectAndStartPolling()) diff --git a/src/juju/api-hooks/common.ts b/src/juju/api-hooks/common.ts index 7c13cb8cc..105a897a3 100644 --- a/src/juju/api-hooks/common.ts +++ b/src/juju/api-hooks/common.ts @@ -30,8 +30,7 @@ export const useModelConnectionCallback = (modelUUID?: string) => { const wsControllerURL = useAppSelector((state) => getModelByUUID(state, modelUUID), )?.wsControllerURL; - const identityProviderAvailable = - useAppSelector(getConfig)?.identityProviderAvailable; + const authMethod = useAppSelector(getConfig)?.authMethod; const credentials = useAppSelector((state) => getUserPass(state, wsControllerURL), ); @@ -43,12 +42,7 @@ export const useModelConnectionCallback = (modelUUID?: string) => { // are available. return; } - connectToModel( - modelUUID, - wsControllerURL, - credentials, - identityProviderAvailable, - ) + connectToModel(modelUUID, wsControllerURL, credentials, authMethod) .then((connection) => { if (!connection) { response({ error: Label.NO_CONNECTION_ERROR }); @@ -61,7 +55,7 @@ export const useModelConnectionCallback = (modelUUID?: string) => { response({ error: toErrorString(error) }); }); }, - [credentials, identityProviderAvailable, modelUUID, wsControllerURL], + [credentials, authMethod, modelUUID, wsControllerURL], ); }; diff --git a/src/juju/api.test.ts b/src/juju/api.test.ts index 0fbd025aa..64202578a 100644 --- a/src/juju/api.test.ts +++ b/src/juju/api.test.ts @@ -5,6 +5,7 @@ import { waitFor } from "@testing-library/react"; import { vi } from "vitest"; import { actions as generalActions } from "store/general"; +import { AuthMethod } from "store/general/types"; import { actions as jujuActions } from "store/juju"; import type { RootState } from "store/store"; import { rootStateFactory } from "testing/factories"; @@ -102,7 +103,7 @@ describe("Juju API", () => { user: "eggman", password: "123", }, - false, + AuthMethod.LOCAL, ); expect(response).toStrictEqual({ conn, @@ -132,7 +133,7 @@ describe("Juju API", () => { user: "eggman", password: "123", }, - true, + AuthMethod.CANDID, ); expect(juju.login).toHaveBeenCalledWith({}, CLIENT_VERSION); }); @@ -150,7 +151,7 @@ describe("Juju API", () => { user: "eggman", password: "123", }, - false, + AuthMethod.LOCAL, ); expect(response).toStrictEqual({ error: "Could not log into controller", @@ -177,7 +178,7 @@ describe("Juju API", () => { user: "eggman", password: "123", }, - false, + AuthMethod.LOCAL, ); expect(ping).not.toHaveBeenCalled(); vi.advanceTimersByTime(PING_TIME); @@ -209,7 +210,7 @@ describe("Juju API", () => { user: "eggman", password: "123", }, - false, + AuthMethod.LOCAL, ); vi.advanceTimersByTime(PING_TIME); await waitFor(() => { @@ -243,7 +244,7 @@ describe("Juju API", () => { password: "123", }, generateConnectionOptions(false), - false, + AuthMethod.LOCAL, ); expect(response).toStrictEqual(juju); }); @@ -262,7 +263,7 @@ describe("Juju API", () => { password: "123", }, generateConnectionOptions(false), - false, + AuthMethod.LOCAL, ); vi.advanceTimersByTime(LOGIN_TIMEOUT); await expect(response).rejects.toMatchObject( @@ -283,7 +284,7 @@ describe("Juju API", () => { password: "123", }, generateConnectionOptions(false), - false, + AuthMethod.LOCAL, ); await expect(response).rejects.toMatchObject(new Error("Uh oh!")); }); @@ -317,7 +318,7 @@ describe("Juju API", () => { it("can log in with an external provider", async () => { if (state.general.config) { - state.general.config.identityProviderAvailable = true; + state.general.config.authMethod = AuthMethod.CANDID; } const loginResponse = { conn: { @@ -1321,7 +1322,7 @@ describe("Juju API", () => { "abc123", "wss://example.com/api", credentials, - false, + AuthMethod.LOCAL, ); expect(connectAndLogin).toHaveBeenCalledWith( "wss://example.com/model/abc123/api", diff --git a/src/juju/api.ts b/src/juju/api.ts index 453d5b48d..51165ad22 100644 --- a/src/juju/api.ts +++ b/src/juju/api.ts @@ -34,6 +34,7 @@ import { isLoggedIn, } from "store/general/selectors"; import type { Credential } from "store/general/types"; +import { AuthMethod } from "store/general/types"; import { actions as jujuActions } from "store/juju"; import { addControllerCloudRegion } from "store/juju/thunks"; import type { @@ -110,10 +111,10 @@ export function generateConnectionOptions( function determineLoginParams( credentials: Credential | null | undefined, - identityProviderAvailable: boolean, + authMethod?: AuthMethod, ) { let loginParams: Credentials = {}; - if (credentials && !identityProviderAvailable) { + if (credentials && authMethod === AuthMethod.LOCAL) { loginParams = { username: credentials.user, password: credentials.password, @@ -147,7 +148,7 @@ function stopPingerLoop(intervalId: number) { @param wsControllerURL The fully qualified URL of the controller api. @param credentials The users credentials in the format {user: ..., password: ...} - @param identityProviderAvailable Whether an identity provider is available. + @param authMethod The method to use for authentication. @returns conn The controller connection instance. juju The juju api instance. @@ -155,16 +156,13 @@ function stopPingerLoop(intervalId: number) { export async function loginWithBakery( wsControllerURL: string, credentials?: Credential, - identityProviderAvailable: boolean = false, + authMethod?: AuthMethod, ) { const juju: JujuClient = await connect( wsControllerURL, generateConnectionOptions(true, (e) => console.log("controller closed", e)), ); - const loginParams = determineLoginParams( - credentials, - identityProviderAvailable, - ); + const loginParams = determineLoginParams(credentials, authMethod); let conn: ConnectionWithFacades | null | undefined = null; try { conn = await juju.login(loginParams, CLIENT_VERSION); @@ -187,22 +185,19 @@ export type LoginResponse = Awaited> & { @param credentials The users credentials in the format {user: ..., password: ...} @param options The options for the connection. - @param identityProviderAvailable If an identity provider is available. + @param authMethod The method to use for authentication. @returns The full model status. */ export async function connectAndLoginWithTimeout( modelURL: string, credentials: Credential | null | undefined, options: ConnectOptions, - identityProviderAvailable: boolean, + authMethod?: AuthMethod, ): Promise { const timeout: Promise = new Promise((_resolve, reject) => { setTimeout(reject, LOGIN_TIMEOUT, new Error(Label.LOGIN_TIMEOUT_ERROR)); }); - const loginParams = determineLoginParams( - credentials, - identityProviderAvailable, - ); + const loginParams = determineLoginParams(credentials, authMethod); const juju: Promise = connectAndLogin( modelURL, loginParams, @@ -226,13 +221,7 @@ export async function fetchModelStatus( getState: () => RootState, ) { const appState = getState(); - const baseWSControllerURL = getWSControllerURL(appState); const config = getConfig(appState); - let useIdentityProvider = false; - - if (baseWSControllerURL === wsControllerURL) { - useIdentityProvider = config?.identityProviderAvailable ?? false; - } const modelURL = wsControllerURL.replace("/api", `/model/${modelUUID}/api`); let status: FullStatusWithAnnotations | null = null; let features: ModelFeatures | null = null; @@ -245,7 +234,7 @@ export async function fetchModelStatus( modelURL, controllerCredentials, generateConnectionOptions(false), - useIdentityProvider, + config?.authMethod, ); if (isLoggedIn(getState(), wsControllerURL)) { try { @@ -532,14 +521,14 @@ export async function connectToModel( modelUUID: string, wsControllerURL: string, credentials?: Credential, - identityProviderAvailable = false, + authMethod?: AuthMethod, ) { const modelURL = wsControllerURL.replace("/api", `/model/${modelUUID}/api`); const response = await connectAndLoginWithTimeout( modelURL, credentials, generateConnectionOptions(true), - identityProviderAvailable, + authMethod, ); return response.conn; } @@ -564,7 +553,7 @@ export async function connectAndLoginToModel( modelUUID, wsControllerURL, credentials, - config?.identityProviderAvailable, + config?.authMethod, ); } diff --git a/src/store/app/actions.test.ts b/src/store/app/actions.test.ts index 6c5c23344..befe54f8d 100644 --- a/src/store/app/actions.test.ts +++ b/src/store/app/actions.test.ts @@ -1,3 +1,5 @@ +import { AuthMethod } from "store/general/types"; + import type { ControllerArgs } from "./actions"; import { updatePermissions, connectAndPollControllers } from "./actions"; @@ -21,7 +23,7 @@ describe("actions", () => { const controller: ControllerArgs = [ "wss://example.com", { user: "eggman@external", password: "verysecure123" }, - false, + AuthMethod.LOCAL, ]; const args = { controllers: [controller], diff --git a/src/store/app/actions.ts b/src/store/app/actions.ts index 1400b0455..67e0440dc 100644 --- a/src/store/app/actions.ts +++ b/src/store/app/actions.ts @@ -1,6 +1,6 @@ import { createAction } from "@reduxjs/toolkit"; -import type { Credential } from "store/general/types"; +import type { AuthMethod, Credential } from "store/general/types"; export const updatePermissions = createAction<{ action: string; @@ -16,8 +16,8 @@ export type ControllerArgs = [ string, // credentials Credential | undefined, - // identityProviderAvailable - boolean | undefined, + // authMethod + AuthMethod, ]; export const connectAndPollControllers = createAction<{ diff --git a/src/store/app/thunks.test.ts b/src/store/app/thunks.test.ts index fb65ef2c7..020a10339 100644 --- a/src/store/app/thunks.test.ts +++ b/src/store/app/thunks.test.ts @@ -2,6 +2,7 @@ import { vi } from "vitest"; import { actions as appActions } from "store/app"; import { actions as generalActions } from "store/general"; +import { AuthMethod } from "store/general/types"; import { actions as jujuActions } from "store/juju"; import type { RootState } from "store/store"; import { rootStateFactory } from "testing/factories"; @@ -67,7 +68,7 @@ describe("thunks", () => { rootStateFactory.build({ general: generalStateFactory.build({ config: configFactory.build({ - identityProviderAvailable: true, + authMethod: AuthMethod.CANDID, }), }), }), @@ -91,7 +92,9 @@ describe("thunks", () => { await action(dispatch, getState, null); expect(dispatch).toHaveBeenCalledWith( appActions.connectAndPollControllers({ - controllers: [["wss://controller.example.com", undefined, false]], + controllers: [ + ["wss://controller.example.com", undefined, AuthMethod.LOCAL], + ], isJuju: true, }), ); @@ -112,7 +115,9 @@ describe("thunks", () => { await action(dispatch, getState, null); expect(dispatch).toHaveBeenCalledWith( appActions.connectAndPollControllers({ - controllers: [["wss://controller.example.com", undefined, false]], + controllers: [ + ["wss://controller.example.com", undefined, AuthMethod.LOCAL], + ], isJuju: true, }), ); @@ -141,7 +146,9 @@ describe("thunks", () => { await action(dispatch, getState, null); expect(dispatch).toHaveBeenCalledWith( appActions.connectAndPollControllers({ - controllers: [["wss://controller.example.com", undefined, false]], + controllers: [ + ["wss://controller.example.com", undefined, AuthMethod.LOCAL], + ], isJuju: true, }), ); @@ -171,7 +178,9 @@ describe("thunks", () => { await action(dispatch, getState, null); expect(dispatch).toHaveBeenCalledWith( appActions.connectAndPollControllers({ - controllers: [["wss://controller.example.com", undefined, false]], + controllers: [ + ["wss://controller.example.com", undefined, AuthMethod.LOCAL], + ], isJuju: true, }), ); diff --git a/src/store/app/thunks.ts b/src/store/app/thunks.ts index dce0b2664..93a788e4e 100644 --- a/src/store/app/thunks.ts +++ b/src/store/app/thunks.ts @@ -10,6 +10,7 @@ import { getUserPass, getWSControllerURL, } from "store/general/selectors"; +import { AuthMethod } from "store/general/types"; import { actions as jujuActions } from "store/juju"; import type { RootState } from "store/store"; @@ -23,8 +24,7 @@ export const logOut = createAsyncThunk< } >("app/logout", async (_, thunkAPI) => { const state = thunkAPI.getState(); - const identityProviderAvailable = - state?.general?.config?.identityProviderAvailable; + const authMethod = state?.general?.config?.authMethod ?? AuthMethod.LOCAL; const pingerIntervalIds = getPingerIntervalIds(state); bakery.storage.clear(); Object.entries(pingerIntervalIds ?? {}).forEach((pingerIntervalId) => @@ -33,7 +33,7 @@ export const logOut = createAsyncThunk< thunkAPI.dispatch(jujuActions.clearModelData()); thunkAPI.dispatch(jujuActions.clearControllerData()); thunkAPI.dispatch(generalActions.logOut()); - if (identityProviderAvailable) { + if (authMethod === AuthMethod.CANDID) { // To enable users to log back in after logging out we have to re-connect // to the controller to get another wait url and start polling on it // again. @@ -62,7 +62,7 @@ export const connectAndStartPolling = createAsyncThunk< controllerList.push([ wsControllerURL, credentials, - config?.identityProviderAvailable, + config?.authMethod ?? AuthMethod.LOCAL, ]); } const connectedControllers = Object.keys(controllerConnections); diff --git a/src/store/general/selectors.ts b/src/store/general/selectors.ts index 0ef7442d1..b4487483f 100644 --- a/src/store/general/selectors.ts +++ b/src/store/general/selectors.ts @@ -13,7 +13,7 @@ const slice = (state: RootState) => state.general; */ export const getConfig = createSelector( [slice], - (sliceState) => sliceState?.config, + (sliceState) => sliceState.config, ); /** diff --git a/src/store/general/types.ts b/src/store/general/types.ts index 1af90c2b3..b36e0ea7f 100644 --- a/src/store/general/types.ts +++ b/src/store/general/types.ts @@ -1,14 +1,20 @@ import type { ConnectionInfo } from "@canonical/jujulib"; +export enum AuthMethod { + CANDID = "candid", + LOCAL = "local", + OIDC = "oidc", +} + export type Config = { - controllerAPIEndpoint: string; + analyticsEnabled: boolean; + authMethod: AuthMethod; baseAppURL: string; // Support for 2.9 configuration. baseControllerURL?: string | null; - identityProviderAvailable: boolean; + controllerAPIEndpoint: string; identityProviderURL: string; isJuju: boolean; - analyticsEnabled: boolean; }; export type ControllerConnections = Record; diff --git a/src/store/middleware/model-poller.test.ts b/src/store/middleware/model-poller.test.ts index 50c870ce8..dc53df830 100644 --- a/src/store/middleware/model-poller.test.ts +++ b/src/store/middleware/model-poller.test.ts @@ -8,6 +8,7 @@ import type { RelationshipTuple } from "juju/jimm/JIMMV4"; import { actions as appActions, thunks as appThunks } from "store/app"; import type { ControllerArgs } from "store/app/actions"; import { actions as generalActions } from "store/general"; +import { AuthMethod } from "store/general/types"; import { actions as jujuActions } from "store/juju"; import { rootStateFactory } from "testing/factories"; import { generalStateFactory } from "testing/factories/general"; @@ -44,7 +45,11 @@ describe("model poller", () => { const originalLog = console.log; const wsControllerURL = "wss://example.com"; const controllers: ControllerArgs[] = [ - [wsControllerURL, { user: "eggman@external", password: "test" }, false], + [ + wsControllerURL, + { user: "eggman@external", password: "test" }, + AuthMethod.LOCAL, + ], ]; const models = [ { @@ -182,7 +187,7 @@ describe("model poller", () => { password: "test", user: "eggman@external", }, - false, + AuthMethod.LOCAL, ], ); }); @@ -384,7 +389,11 @@ describe("model poller", () => { it("disables masking when using JIMM", async () => { const controllers = [ - [wsControllerURL, { user: "eggman@external", password: "test" }, true], + [ + wsControllerURL, + { user: "eggman@external", password: "test" }, + AuthMethod.CANDID, + ], ]; const disableControllerUUIDMasking = vi.spyOn( jujuModule, diff --git a/src/store/middleware/model-poller.ts b/src/store/middleware/model-poller.ts index a03bffa4e..6d9dd66a8 100644 --- a/src/store/middleware/model-poller.ts +++ b/src/store/middleware/model-poller.ts @@ -16,6 +16,7 @@ import type { ConnectionWithFacades } from "juju/types"; import { actions as appActions, thunks as appThunks } from "store/app"; import { actions as generalActions } from "store/general"; import { isLoggedIn } from "store/general/selectors"; +import { AuthMethod } from "store/general/types"; import { actions as jujuActions } from "store/juju"; import type { RootState, Store } from "store/store"; import { isSpecificAction } from "types"; @@ -71,8 +72,7 @@ export const modelPollerMiddleware: Middleware< // first clean up any old auth requests: reduxStore.dispatch(generalActions.clearVisitURLs()); for (const controllerData of action.payload.controllers) { - const [wsControllerURL, credentials, identityProviderAvailable] = - controllerData; + const [wsControllerURL, credentials, authMethod] = controllerData; let conn: ConnectionWithFacades | undefined; let juju: Client | undefined; let error: unknown; @@ -81,7 +81,7 @@ export const modelPollerMiddleware: Middleware< ({ conn, error, juju, intervalId } = await loginWithBakery( wsControllerURL, credentials, - identityProviderAvailable, + authMethod, )); if (conn) { controllers.set(wsControllerURL, conn); @@ -190,7 +190,7 @@ export const modelPollerMiddleware: Middleware< reduxStore.dispatch, reduxStore.getState, ); - if (identityProviderAvailable) { + if (authMethod === AuthMethod.CANDID) { // This call will be a noop if the user isn't an administrator // on the JIMM controller we're connected to. try { diff --git a/src/testing/factories/general.ts b/src/testing/factories/general.ts index 1ee3959b6..309cddfaa 100644 --- a/src/testing/factories/general.ts +++ b/src/testing/factories/general.ts @@ -1,17 +1,18 @@ import { Factory } from "fishery"; -import type { - Config, - ControllerFeatures, - Credential, - GeneralState, - ControllerFeaturesState, +import { + type Config, + type ControllerFeatures, + type Credential, + type GeneralState, + type ControllerFeaturesState, + AuthMethod, } from "store/general/types"; export const configFactory = Factory.define(() => ({ + authMethod: AuthMethod.LOCAL, controllerAPIEndpoint: "wss://controller.example.com", baseAppURL: "/", - identityProviderAvailable: false, identityProviderURL: "", isJuju: false, analyticsEnabled: true, diff --git a/src/types.ts b/src/types.ts index 2db4886c7..ece9e126a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,9 +4,11 @@ import { isAction } from "redux"; import type { Config } from "store/general/types"; +export type WindowConfig = Omit; + declare global { interface Window { - jujuDashboardConfig?: Config; + jujuDashboardConfig?: WindowConfig; } }