diff --git a/changelog/_12142.txt b/changelog/_12142.txt new file mode 100644 index 0000000000..161cb1e7b6 --- /dev/null +++ b/changelog/_12142.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Fixes login form so `?with=` query param correctly displays only the specified mount when multiple mounts of the same auth type are configured with `listing_visibility="unauth"` +``` \ No newline at end of file diff --git a/ui/app/components/auth/page.ts b/ui/app/components/auth/page.ts index a3ce5d58c4..e81f2b791d 100644 --- a/ui/app/components/auth/page.ts +++ b/ui/app/components/auth/page.ts @@ -58,17 +58,6 @@ import type { Task } from 'ember-concurrency'; * 🔀 Multiple visible mounts: * ▸ Path dropdown is shown. * - * @example - * * * @param {object} cluster - the ember data cluster model. contains information such as cluster id, name and boolean for if the cluster is in standby * @param {object} directLinkData - mount data built from the "with" query param. If param is a mount path and maps to a visible mount, the login form defaults to this mount. Otherwise the form preselects the passed auth type. @@ -161,10 +150,14 @@ export default class AuthPage extends Component { get directLinkViews() { const { directLinkData } = this.args; - // If "path" key exists we know the "with" query param references a mount with listing_visibility="unauth" - // Treat it as a preferred method and hide all other tabs. + // If "path" key exists then the "with" query param references a specific mount with listing_visibility="unauth". + // Show only this mount and hide any others for this auth type as well as any tabs for different auth types. if (directLinkData?.path) { - const tabData = this.filterVisibleMountsByType([directLinkData.type]); + const mounts = this.mountsByType(directLinkData.type); + const selectedMount = mounts?.find((m) => m.path === directLinkData?.path); + const tabData: UnauthMountsByType = { + [directLinkData.type]: selectedMount ? [selectedMount] : null, + }; const defaultView = this.constructViews(FormView.TABS, tabData); const alternateView = this.constructViews(FormView.DROPDOWN, null); @@ -263,11 +256,16 @@ export default class AuthPage extends Component { const tabs: UnauthMountsByType = {}; for (const type of authTypes) { // adds visible mounts for each type, if they exist - tabs[type] = this.visibleMountsByType?.[type] || null; + tabs[type] = this.mountsByType(type); } return tabs; } + private mountsByType(type: string) { + // Return null and not an empty array to distinguish between "dropdown mode" and "tabs with no mounts" in downstream components + return this.visibleMountsByType?.[type] || null; + } + private constructViews(view: FormView, tabData: UnauthMountsByType | null) { return { view, tabData }; } diff --git a/ui/app/routes/vault/cluster/auth.js b/ui/app/routes/vault/cluster/auth.js index 8e0e0ddf7d..2d1d3839dc 100644 --- a/ui/app/routes/vault/cluster/auth.js +++ b/ui/app/routes/vault/cluster/auth.js @@ -102,7 +102,6 @@ export default class AuthRoute extends Route { const { default_auth_type, backup_auth_types } = response.data; return { defaultType: default_auth_type, - // TODO WIP backend PR consistently return empty array when no backup_auth_types backupTypes: backup_auth_types?.length ? backup_auth_types : null, }; } diff --git a/ui/tests/integration/components/auth/page/listing-visibility-test.js b/ui/tests/integration/components/auth/page/listing-visibility-test.js index 10a005e027..54eb768ba9 100644 --- a/ui/tests/integration/components/auth/page/listing-visibility-test.js +++ b/ui/tests/integration/components/auth/page/listing-visibility-test.js @@ -132,6 +132,20 @@ module('Integration | Component | auth | page | listing visibility', function (h assert.dom(GENERAL.backButton).doesNotExist(); }); + test('it treats direct link as only mount when multiple mounts are tuned with listing_visibility="unauth"', async function (assert) { + this.directLinkData = { path: 'userpass2/', type: 'userpass' }; + await this.renderComponent(); + assert.dom(AUTH_FORM.authForm('userpass')).exists; + assert.dom(AUTH_FORM.tabBtn('userpass')).hasText('Userpass', 'it renders tab for type'); + assert.dom(AUTH_FORM.tabs).exists({ count: 1 }, 'only one tab renders'); + assert.dom(GENERAL.inputByAttr('path')).hasAttribute('type', 'hidden'); + assert.dom(GENERAL.inputByAttr('path')).hasValue('userpass2/'); + assert.dom(GENERAL.button('Sign in with other methods')).exists('"Sign in with other methods" renders'); + assert.dom(GENERAL.selectByAttr('auth type')).doesNotExist(); + assert.dom(AUTH_FORM.advancedSettings).doesNotExist(); + assert.dom(GENERAL.backButton).doesNotExist(); + }); + test('it prioritizes auth type from canceled mfa instead of direct link for path', async function (assert) { assert.expect(1); this.directLinkData = this.directLinkIsVisibleMount; // type is "oidc"