UI: Assume default auth path if custom path is not provided (#30300)

* add default path and update base tests

* finish okta, oidc-jwt and saml tests

* fix test name
This commit is contained in:
claire bontempo 2025-04-21 11:28:22 -07:00 committed by GitHub
parent 4c36d90281
commit b00d986be7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 210 additions and 21 deletions

View file

@ -3,6 +3,8 @@
SPDX-License-Identifier: BUSL-1.1
~}}
{{! Auth methods with standard login flows use this template. To implement any custom logic, this template should be copied into a new hbs file for that method. }}
<form {{on "submit" this.onSubmit}} data-test-auth-form={{@authType}}>
{{yield to="namespace"}}

View file

@ -42,6 +42,13 @@ export default class AuthBase extends Component<Args> {
data[key] = formData.get(key);
}
// If path is not included in the submitted form data,
// set it as the auth type which is the default path Vault expects.
// The "token" auth method does not support custom login paths.
if (this.args.authType !== 'token' && !Object.keys(data).includes('path')) {
data['path'] = this.args.authType;
}
this.login.unlinked().perform(data);
}

View file

@ -28,7 +28,25 @@ module('Integration | Component | auth | form | base', function (hooks) {
hooks.beforeEach(function () {
this.authType = 'github';
this.expectedFields = ['token'];
this.renderComponent = () => {
this.expectedSubmit = {
default: { path: 'github', token: 'mysupersecuretoken' },
custom: { path: 'custom-github', token: 'mysupersecuretoken' },
};
this.renderComponent = ({ yieldBlock = false } = {}) => {
if (yieldBlock) {
return render(hbs`
<Auth::Form::Github
@authType={{this.authType}}
@cluster={{this.cluster}}
@onError={{this.onError}}
@onSuccess={{this.onSuccess}}
>
<:advancedSettings>
<label for="path">Mount path</label>
<input data-test-input="path" id="path" name="path" type="text" />
</:advancedSettings>
</Auth::Form::Github>`);
}
return render(hbs`
<Auth::Form::Github
@authType={{this.authType}}
@ -52,7 +70,25 @@ module('Integration | Component | auth | form | base', function (hooks) {
hooks.beforeEach(function () {
this.authType = 'ldap';
this.expectedFields = ['username', 'password'];
this.renderComponent = () => {
this.expectedSubmit = {
default: { password: 'password', path: 'ldap', username: 'matilda' },
custom: { password: 'password', path: 'custom-ldap', username: 'matilda' },
};
this.renderComponent = ({ yieldBlock = false } = {}) => {
if (yieldBlock) {
return render(hbs`
<Auth::Form::Ldap
@authType={{this.authType}}
@cluster={{this.cluster}}
@onError={{this.onError}}
@onSuccess={{this.onSuccess}}
>
<:advancedSettings>
<label for="path">Mount path</label>
<input data-test-input="path" id="path" name="path" type="text" />
</:advancedSettings>
</Auth::Form::Ldap>`);
}
return render(hbs`
<Auth::Form::Ldap
@authType={{this.authType}}
@ -70,7 +106,25 @@ module('Integration | Component | auth | form | base', function (hooks) {
hooks.beforeEach(function () {
this.authType = 'radius';
this.expectedFields = ['username', 'password'];
this.renderComponent = () => {
this.expectedSubmit = {
default: { password: 'password', path: 'radius', username: 'matilda' },
custom: { password: 'password', path: 'custom-radius', username: 'matilda' },
};
this.renderComponent = ({ yieldBlock = false } = {}) => {
if (yieldBlock) {
return render(hbs`
<Auth::Form::Radius
@authType={{this.authType}}
@cluster={{this.cluster}}
@onError={{this.onError}}
@onSuccess={{this.onSuccess}}
>
<:advancedSettings>
<label for="path">Mount path</label>
<input data-test-input="path" id="path" name="path" type="text" />
</:advancedSettings>
</Auth::Form::Radius>`);
}
return render(hbs`
<Auth::Form::Radius
@authType={{this.authType}}
@ -88,7 +142,26 @@ module('Integration | Component | auth | form | base', function (hooks) {
hooks.beforeEach(function () {
this.authType = 'token';
this.expectedFields = ['token'];
this.renderComponent = () => {
this.expectedSubmit = {
default: { token: 'mytoken' },
// token doesn't support custom paths, so just test yielding functionality
custom: { token: 'mytoken', yield: 'yield-token' },
};
this.renderComponent = ({ yieldBlock = false } = {}) => {
if (yieldBlock) {
return render(hbs`
<Auth::Form::Token
@authType={{this.authType}}
@cluster={{this.cluster}}
@onError={{this.onError}}
@onSuccess={{this.onSuccess}}
>
<:advancedSettings>
<label for="yield">Yielded input</label>
<input data-test-input="yield" id="yield" name="yield" type="text" />
</:advancedSettings>
</Auth::Form::Token>`);
}
return render(hbs`
<Auth::Form::Token
@authType={{this.authType}}
@ -106,7 +179,25 @@ module('Integration | Component | auth | form | base', function (hooks) {
hooks.beforeEach(function () {
this.authType = 'userpass';
this.expectedFields = ['username', 'password'];
this.renderComponent = () => {
this.expectedSubmit = {
default: { password: 'password', path: 'userpass', username: 'matilda' },
custom: { password: 'password', path: 'custom-userpass', username: 'matilda' },
};
this.renderComponent = ({ yieldBlock = false } = {}) => {
if (yieldBlock) {
return render(hbs`
<Auth::Form::Userpass
@authType={{this.authType}}
@cluster={{this.cluster}}
@onError={{this.onError}}
@onSuccess={{this.onSuccess}}
>
<:advancedSettings>
<label for="path">Mount path</label>
<input data-test-input="path" id="path" name="path" type="text" />
</:advancedSettings>
</Auth::Form::Userpass>`);
}
return render(hbs`
<Auth::Form::Userpass
@authType={{this.authType}}

View file

@ -23,7 +23,21 @@ module('Integration | Component | auth | form | oidc-jwt', function (hooks) {
this.cluster = { id: 1 };
this.onError = sinon.spy();
this.onSuccess = sinon.spy();
this.renderComponent = () => {
this.renderComponent = ({ yieldBlock = false } = {}) => {
if (yieldBlock) {
return render(hbs`
<Auth::Form::OidcJwt
@authType={{this.authType}}
@cluster={{this.cluster}}
@onError={{this.onError}}
@onSuccess={{this.onSuccess}}
>
<:advancedSettings>
<label for="path">Mount path</label>
<input data-test-input="path" id="path" name="path" type="text" />
</:advancedSettings>
</Auth::Form::OidcJwt>`);
}
return render(hbs`
<Auth::Form::OidcJwt
@authType={{this.authType}}
@ -46,6 +60,10 @@ module('Integration | Component | auth | form | oidc-jwt', function (hooks) {
module('oidc', function (hooks) {
hooks.beforeEach(function () {
this.authType = 'oidc';
this.expectedSubmit = {
default: { path: 'oidc', role: 'some-dev' },
custom: { path: 'custom-oidc', role: 'some-dev' },
};
});
testHelper(test);
@ -54,6 +72,10 @@ module('Integration | Component | auth | form | oidc-jwt', function (hooks) {
module('jwt', function (hooks) {
hooks.beforeEach(function () {
this.authType = 'jwt';
this.expectedSubmit = {
default: { path: 'jwt', role: 'some-dev' },
custom: { path: 'custom-jwt', role: 'some-dev' },
};
});
testHelper(test);

View file

@ -23,7 +23,25 @@ module('Integration | Component | auth | form | okta', function (hooks) {
this.cluster = { id: 1 };
this.onError = sinon.spy();
this.onSuccess = sinon.spy();
this.renderComponent = () => {
this.expectedSubmit = {
default: { path: 'okta', username: 'matilda', password: 'password' },
custom: { path: 'custom-okta', username: 'matilda', password: 'password' },
};
this.renderComponent = ({ yieldBlock = false } = {}) => {
if (yieldBlock) {
return render(hbs`
<Auth::Form::Okta
@authType={{this.authType}}
@cluster={{this.cluster}}
@onError={{this.onError}}
@onSuccess={{this.onSuccess}}
>
<:advancedSettings>
<label for="path">Mount path</label>
<input data-test-input="path" id="path" name="path" type="text" />
</:advancedSettings>
</Auth::Form::Okta>`);
}
return render(hbs`
<Auth::Form::Okta
@authType={{this.authType}}

View file

@ -24,7 +24,25 @@ module('Integration | Component | auth | form | saml', function (hooks) {
this.cluster = { id: 1 };
this.onError = sinon.spy();
this.onSuccess = sinon.spy();
this.renderComponent = () => {
this.expectedSubmit = {
default: { path: 'saml', role: 'some-dev' },
custom: { path: 'custom-saml', role: 'some-dev' },
};
this.renderComponent = ({ yieldBlock = false } = {}) => {
if (yieldBlock) {
return render(hbs`
<Auth::Form::Saml
@authType={{this.authType}}
@cluster={{this.cluster}}
@onError={{this.onError}}
@onSuccess={{this.onSuccess}}
>
<:advancedSettings>
<label for="path">Mount path</label>
<input data-test-input="path" id="path" name="path" type="text" />
</:advancedSettings>
</Auth::Form::Saml>`);
}
return render(hbs`
<Auth::Form::Saml
@authType={{this.authType}}

View file

@ -24,19 +24,6 @@ export default (test) => {
});
});
test('it submits expected form data', async function (assert) {
await this.renderComponent();
const { options } = AUTH_METHOD_MAP.find((m) => m.authType === this.authType);
const { loginData } = options;
for (const [field, value] of Object.entries(loginData)) {
await fillIn(GENERAL.inputByAttr(field), value);
}
await click(AUTH_FORM.login);
const [actual] = this.authenticateStub.lastCall.args;
assert.propEqual(actual.data, loginData, 'auth service "authenticate" method is called with form data');
});
test('it fires onError callback', async function (assert) {
this.authenticateStub.throws('permission denied');
await this.renderComponent();
@ -58,4 +45,48 @@ export default (test) => {
const [actual] = this.onSuccess.lastCall.args;
assert.strictEqual(actual, 'success!', 'it calls onSuccess');
});
test('it submits form data with defaults', async function (assert) {
await this.renderComponent();
const { options } = AUTH_METHOD_MAP.find((m) => m.authType === this.authType);
const { loginData } = options;
for (const [field, value] of Object.entries(loginData)) {
await fillIn(GENERAL.inputByAttr(field), value);
}
await click(AUTH_FORM.login);
const [actual] = this.authenticateStub.lastCall.args;
assert.propEqual(
actual.data,
this.expectedSubmit.default,
'auth service "authenticate" method is called with form data'
);
});
// not for testing real-world submit, that happens in acceptance tests
// component here just yields <:advancedSettings> to test form submits data from yielded inputs
test('it submits form data from yielded inputs', async function (assert) {
await this.renderComponent({ yieldBlock: true });
const { options } = AUTH_METHOD_MAP.find((m) => m.authType === this.authType);
const { loginData } = options;
for (const [field, value] of Object.entries(loginData)) {
await fillIn(GENERAL.inputByAttr(field), value);
}
if (this.authType === 'token') {
// token doesn't support custom paths, so just test yielding functionality
await fillIn(GENERAL.inputByAttr('yield'), `yield-${this.authType}`);
} else {
await fillIn(GENERAL.inputByAttr('path'), `custom-${this.authType}`);
}
await click(AUTH_FORM.login);
const [actual] = this.authenticateStub.lastCall.args;
assert.propEqual(
actual.data,
this.expectedSubmit.custom,
'auth service "authenticate" method is called with yielded form data'
);
});
};