UI Support for Okta Number Challenge (#15998)
* Imported uuid library for initial commit to push a clean branch. * Removed import statement in auth-form file since it was causing UI tests to fail as the import was not being used. * Added nonce field to payload for okta sign in. (#16001) * Added nonce field to payload for okta sign in. * Added missing yarn package for uuid * Fixed failing ui tests in cluster-test file to take into account of nonce field in the payload of okta login * Removed uuid library and used crypto.randomUUID() to generate unique uuid values instead * Fixed indent in package.json * Removed uuid library since decided to use crypto.randomUUID() instead to generate unique uuid values * Create polling function for correct answer in okta number challenge (#16070) * Implemented polling function to get correct answer for okta number challenge. * Disabled polling function for testing as it was causing acceptance test to fail in auth-test.js * Changed API call to be the auth mount path instead of being static and created a variable to store the oktaNumberChallengeAnswer to be used later for the display screens * Create component for okta number challenge screen (#16195) * Implemented loading screen and display screen for correct answer for Okta Number Challenge * Fixed linting issues on hbs files * Added periods to parameter descriptions and made parameters optional * Removed optional parameters from calling AuthForm component if authMethod is not Okta * Implement error handling and screens for okta number challenge (#16276) * Implemented loading screen and display screen for correct answer for Okta Number Challenge * Fixed linting issues on hbs files * Temporary changes to include error screen in okta number challenge * Created error screen tests and made minor fixes * Fixed error for wrong parameter name being passed in * Fixed linting issues causing ui tests to fail * Added periods at the end of param descriptions * Imported uuid library for initial commit to push a clean branch. * Removed import statement in auth-form file since it was causing UI tests to fail as the import was not being used. * Removed uuid library since decided to use crypto.randomUUID() instead to generate unique uuid values * Added nonce field to payload for okta sign in. (#16001) * Added nonce field to payload for okta sign in. * Added missing yarn package for uuid * Fixed failing ui tests in cluster-test file to take into account of nonce field in the payload of okta login * Removed uuid library and used crypto.randomUUID() to generate unique uuid values instead * Fixed indent in package.json * Create polling function for correct answer in okta number challenge (#16070) * Implemented polling function to get correct answer for okta number challenge. * Disabled polling function for testing as it was causing acceptance test to fail in auth-test.js * Changed API call to be the auth mount path instead of being static and created a variable to store the oktaNumberChallengeAnswer to be used later for the display screens * Create component for okta number challenge screen (#16195) * Implemented loading screen and display screen for correct answer for Okta Number Challenge * Fixed linting issues on hbs files * Added periods to parameter descriptions and made parameters optional * Removed optional parameters from calling AuthForm component if authMethod is not Okta * Implement error handling and screens for okta number challenge (#16276) * Implemented loading screen and display screen for correct answer for Okta Number Challenge * Fixed linting issues on hbs files * Temporary changes to include error screen in okta number challenge * Created error screen tests and made minor fixes * Fixed error for wrong parameter name being passed in * Fixed linting issues causing ui tests to fail * Added periods at the end of param descriptions * UI/vault 7312/fix vault enterprise error for okta number challenge (#16568) * Fixed bug with okta not working when selecting okta tab after being on other tab * Fixed vault enterprise errors * Fixed error when logging in with Okta in 'Other' tab * Removed namespace parameter in option to use the default * Added changelog
This commit is contained in:
parent
5c4b1cc4ac
commit
5cd1a12178
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
ui: UI support for Okta Number Challenge.
|
||||
```
|
|
@ -107,7 +107,7 @@ export default ApplicationAdapter.extend({
|
|||
},
|
||||
|
||||
authenticate({ backend, data }) {
|
||||
const { role, jwt, token, password, username, path } = data;
|
||||
const { role, jwt, token, password, username, path, nonce } = data;
|
||||
const url = this.urlForAuth(backend, username, path);
|
||||
const verb = backend === 'token' ? 'GET' : 'POST';
|
||||
let options = {
|
||||
|
@ -119,6 +119,8 @@ export default ApplicationAdapter.extend({
|
|||
};
|
||||
} else if (backend === 'jwt' || backend === 'oidc') {
|
||||
options.data = { role, jwt };
|
||||
} else if (backend === 'okta') {
|
||||
options.data = { password, nonce };
|
||||
} else {
|
||||
options.data = token ? { token, password } : { password };
|
||||
}
|
||||
|
|
|
@ -24,7 +24,11 @@ const BACKENDS = supportedAuthBackends();
|
|||
* @param {object} cluster - The auth method that is currently selected in the dropdown. This corresponds to an Ember Model.
|
||||
* @param {string} namespace- The currently active namespace.
|
||||
* @param {string} selectedAuth - The auth method that is currently selected in the dropdown.
|
||||
* @param {function} onSuccess - Fired on auth success
|
||||
* @param {function} onSuccess - Fired on auth success.
|
||||
* @param {function} [setOktaNumberChallenge] - Sets whether we are waiting for okta number challenge to be used to sign in.
|
||||
* @param {boolean} [waitingForOktaNumberChallenge=false] - Determines if we are waiting for the Okta Number Challenge to sign in.
|
||||
* @param {function} [setCancellingAuth] - Sets whether we are cancelling or not the login authentication for Okta Number Challenge.
|
||||
* @param {boolean} [cancelAuthForOktaNumberChallenge=false] - Determines if we are cancelling the login authentication for the Okta Number Challenge.
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
|
@ -51,6 +55,9 @@ export default Component.extend(DEFAULTS, {
|
|||
oldNamespace: null,
|
||||
authMethods: BACKENDS,
|
||||
|
||||
// number answer for okta number challenge if applicable
|
||||
oktaNumberChallengeAnswer: null,
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
let {
|
||||
|
@ -60,8 +67,14 @@ export default Component.extend(DEFAULTS, {
|
|||
namespace: ns,
|
||||
selectedAuth: newMethod,
|
||||
oldSelectedAuth: oldMethod,
|
||||
cancelAuthForOktaNumberChallenge: cancelAuth,
|
||||
} = this;
|
||||
|
||||
// if we are cancelling the login then we reset the number challenge answer and cancel the current authenticate and polling tasks
|
||||
if (cancelAuth) {
|
||||
this.set('oktaNumberChallengeAnswer', null);
|
||||
this.authenticate.cancelAll();
|
||||
this.pollForOktaNumberChallenge.cancelAll();
|
||||
}
|
||||
next(() => {
|
||||
if (!token && (oldNS === null || oldNS !== ns)) {
|
||||
this.fetchMethods.perform();
|
||||
|
@ -219,7 +232,11 @@ export default Component.extend(DEFAULTS, {
|
|||
cluster: { id: clusterId },
|
||||
} = this;
|
||||
try {
|
||||
if (backendType === 'okta') {
|
||||
this.pollForOktaNumberChallenge.perform(data.nonce, data.path);
|
||||
} else {
|
||||
this.delayAuthMessageReminder.perform();
|
||||
}
|
||||
const authResponse = yield this.auth.authenticate({
|
||||
clusterId,
|
||||
backend: backendType,
|
||||
|
@ -236,6 +253,28 @@ export default Component.extend(DEFAULTS, {
|
|||
})
|
||||
),
|
||||
|
||||
pollForOktaNumberChallenge: task(function* (nonce, mount) {
|
||||
// yield for 1s to wait to see if there is a login error before polling
|
||||
yield timeout(1000);
|
||||
if (this.error) {
|
||||
return;
|
||||
}
|
||||
let response = null;
|
||||
this.setOktaNumberChallenge(true);
|
||||
this.setCancellingAuth(false);
|
||||
// keep polling /auth/okta/verify/:nonce API every 1s until a response is given with the correct number for the Okta Number Challenge
|
||||
while (response === null) {
|
||||
// when testing, the polling loop causes promises to be rejected making acceptance tests fail
|
||||
// so disable the poll in tests
|
||||
if (Ember.testing) {
|
||||
return;
|
||||
}
|
||||
yield timeout(1000);
|
||||
response = yield this.auth.getOktaNumberChallengeAnswer(nonce, mount);
|
||||
}
|
||||
this.set('oktaNumberChallengeAnswer', response);
|
||||
}),
|
||||
|
||||
delayAuthMessageReminder: task(function* () {
|
||||
if (Ember.testing) {
|
||||
this.showLoading = true;
|
||||
|
@ -275,6 +314,14 @@ export default Component.extend(DEFAULTS, {
|
|||
if (this.customPath || backend.id) {
|
||||
data.path = this.customPath || backend.id;
|
||||
}
|
||||
// add nonce field for okta backend
|
||||
if (backend.type === 'okta') {
|
||||
data.nonce = crypto.randomUUID();
|
||||
// add a default path of okta if it doesn't exist to be used for Okta Number Challenge
|
||||
if (!data.path) {
|
||||
data.path = 'okta';
|
||||
}
|
||||
}
|
||||
return this.authenticate.unlinked().perform(backend.type, data);
|
||||
},
|
||||
handleError(e) {
|
||||
|
@ -283,5 +330,9 @@ export default Component.extend(DEFAULTS, {
|
|||
error: e ? this.auth.handleError(e) : null,
|
||||
});
|
||||
},
|
||||
returnToLoginFromOktaNumberChallenge() {
|
||||
this.setOktaNumberChallenge(false);
|
||||
this.set('oktaNumberChallengeAnswer', null);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import Component from '@glimmer/component';
|
||||
|
||||
/**
|
||||
* @module OktaNumberChallenge
|
||||
* OktaNumberChallenge components are used to display loading screen and correct answer for Okta Number Challenge when signing in through Okta
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <OktaNumberChallenge @correctAnswer={this.oktaNumberChallengeAnswer} @hasError={this.error} @onReturnToLogin={this.returnToLoginFromOktaNumberChallenge}/>
|
||||
* ```
|
||||
* @param {number} correctAnswer - The correct answer to click for the okta number challenge.
|
||||
* @param {boolean} hasError - Determines if there is an error being thrown.
|
||||
* @param {function} onReturnToLogin - Sets waitingForOktaNumberChallenge to false if want to return to main login.
|
||||
*/
|
||||
|
||||
export default class OktaNumberChallenge extends Component {
|
||||
get oktaNumberChallengeCorrectAnswer() {
|
||||
return this.args.correctAnswer;
|
||||
}
|
||||
|
||||
get errorThrown() {
|
||||
return this.args.hasError;
|
||||
}
|
||||
}
|
|
@ -85,5 +85,9 @@ export default Controller.extend({
|
|||
mfaErrors: null,
|
||||
});
|
||||
},
|
||||
cancelAuthentication() {
|
||||
this.set('cancelAuth', true);
|
||||
this.set('waitingForOktaNumberChallenge', false);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -441,4 +441,21 @@ export default Service.extend({
|
|||
backend: BACKENDS.findBy('type', backend),
|
||||
});
|
||||
}),
|
||||
|
||||
getOktaNumberChallengeAnswer(nonce, mount) {
|
||||
const url = `/v1/auth/${mount}/verify/${nonce}`;
|
||||
return this.ajax(url, 'GET', {}).then(
|
||||
(resp) => {
|
||||
return resp.data.correct_answer;
|
||||
},
|
||||
(e) => {
|
||||
// if error status is 404, return and keep polling for a response
|
||||
if (e.status === 404) {
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
<div class="auth-form" data-test-auth-form>
|
||||
{{#if (or (this.error) (and this.waitingForOktaNumberChallenge (not this.cancelAuthForOktaNumberChallenge)))}}
|
||||
<OktaNumberChallenge
|
||||
@correctAnswer={{this.oktaNumberChallengeAnswer}}
|
||||
@hasError={{(not-eq this.error null)}}
|
||||
@onReturnToLogin={{action "returnToLoginFromOktaNumberChallenge"}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#if this.hasMethodsWithPath}}
|
||||
<nav class="tabs is-marginless">
|
||||
<ul>
|
||||
|
@ -6,7 +13,9 @@
|
|||
{{#let (or method.path method.type) as |methodKey|}}
|
||||
<li
|
||||
class={{if
|
||||
(and this.selectedAuthIsPath (eq (or this.selectedAuthBackend.path this.selectedAuthBackend.type) methodKey))
|
||||
(and
|
||||
this.selectedAuthIsPath (eq (or this.selectedAuthBackend.path this.selectedAuthBackend.type) methodKey)
|
||||
)
|
||||
"is-active"
|
||||
""
|
||||
}}
|
||||
|
@ -173,4 +182,5 @@
|
|||
</form>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -0,0 +1,38 @@
|
|||
<div class="auth-form" data-test-okta-number-challenge>
|
||||
<div class="box is-marginless is-shadowless">
|
||||
<div class="field has-top-margin-xs">
|
||||
<p data-test-okta-number-challenge-description>
|
||||
To finish signing in, you will need to complete an additional MFA step.</p>
|
||||
{{#if this.errorThrown}}
|
||||
<div class="has-top-margin-s">
|
||||
<MessageError @errorMessage="There was a problem" />
|
||||
<button
|
||||
type="button"
|
||||
class="button"
|
||||
{{on "click" @onReturnToLogin}}
|
||||
data-test-return-from-okta-number-challenge
|
||||
>Return to login</button>
|
||||
</div>
|
||||
{{else if this.oktaNumberChallengeCorrectAnswer}}
|
||||
<div class="has-top-margin-s">
|
||||
<p class="has-text-black has-text-weight-semibold" data-test-okta-number-challenge-verification-type>Okta
|
||||
verification</p>
|
||||
<p data-test-okta-number-challenge-verification-description>Select the following number to complete verification:</p>
|
||||
<h1
|
||||
class="title has-font-weight-normal has-top-margin-m has-bottom-margin-s"
|
||||
data-test-okta-number-challenge-answer
|
||||
>{{this.oktaNumberChallengeCorrectAnswer}}</h1>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="has-top-margin-l has-bottom-margin-m">
|
||||
<div class="is-flex-row">
|
||||
<FlightIcon @name="loading" />
|
||||
<div class="has-left-margin-xs">
|
||||
<p data-test-okta-number-challenge-loading>Please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -27,9 +27,13 @@
|
|||
<button type="button" class="icon-button" {{on "click" (fn (mut this.mfaAuthData) null)}}>
|
||||
<Icon @name="arrow-left" @size="24" aria-label="Back to login" class="icon-blue" />
|
||||
</button>
|
||||
{{else if this.waitingForOktaNumberChallenge}}
|
||||
<button type="button" class="icon-button" {{on "click" (action "cancelAuthentication")}}>
|
||||
<Icon @name="arrow-left" @size="24" aria-label="Back to login" class="icon-blue" />
|
||||
</button>
|
||||
{{/if}}
|
||||
<h1 class="title is-3">
|
||||
{{if this.mfaAuthData "Authenticate" "Sign in to Vault"}}
|
||||
{{if (or this.mfaAuthData this.waitingForOktaNumberChallenge) "Authenticate" "Sign in to Vault"}}
|
||||
</h1>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
@ -113,6 +117,10 @@
|
|||
@redirectTo={{this.redirectTo}}
|
||||
@selectedAuth={{this.authMethod}}
|
||||
@onSuccess={{action "onAuthResponse"}}
|
||||
@setOktaNumberChallenge={{fn (mut this.waitingForOktaNumberChallenge)}}
|
||||
@waitingForOktaNumberChallenge={{this.waitingForOktaNumberChallenge}}
|
||||
@setCancellingAuth={{fn (mut this.cancelAuth)}}
|
||||
@cancelAuthForOktaNumberChallenge={{this.cancelAuth}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Page.content>
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, click } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | okta-number-challenge', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.oktaNumberChallengeAnswer = null;
|
||||
this.hasError = false;
|
||||
});
|
||||
|
||||
test('it should render correct descriptions', async function (assert) {
|
||||
await render(hbs`<OktaNumberChallenge @correctAnswer={{this.oktaNumberChallengeAnswer}}/>`);
|
||||
|
||||
assert
|
||||
.dom('[data-test-okta-number-challenge-description]')
|
||||
.includesText(
|
||||
'To finish signing in, you will need to complete an additional MFA step.',
|
||||
'Correct description renders'
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-okta-number-challenge-loading]')
|
||||
.includesText('Please wait...', 'Correct loading description renders');
|
||||
});
|
||||
|
||||
test('it should show correct number for okta number challenge', async function (assert) {
|
||||
this.set('oktaNumberChallengeAnswer', 1);
|
||||
await render(hbs`<OktaNumberChallenge @correctAnswer={{this.oktaNumberChallengeAnswer}}/>`);
|
||||
assert
|
||||
.dom('[data-test-okta-number-challenge-description]')
|
||||
.includesText(
|
||||
'To finish signing in, you will need to complete an additional MFA step.',
|
||||
'Correct description renders'
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-okta-number-challenge-verification-type]')
|
||||
.includesText('Okta verification', 'Correct verification type renders');
|
||||
|
||||
assert
|
||||
.dom('[data-test-okta-number-challenge-verification-description]')
|
||||
.includesText(
|
||||
'Select the following number to complete verification:',
|
||||
'Correct verification description renders'
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-okta-number-challenge-answer]')
|
||||
.includesText('1', 'Correct okta number challenge answer renders');
|
||||
});
|
||||
|
||||
test('it should show error screen', async function (assert) {
|
||||
this.set('hasError', true);
|
||||
await render(
|
||||
hbs`<OktaNumberChallenge @correctAnswer={{this.oktaNumberChallengeAnswer}} @hasError={{this.hasError}} @onReturnToLogin={{fn (mut this.returnToLogin) true}}/>`
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-okta-number-challenge-description]')
|
||||
.includesText(
|
||||
'To finish signing in, you will need to complete an additional MFA step.',
|
||||
'Correct description renders'
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-error]')
|
||||
.includesText('There was a problem', 'Displays error that there was a problem');
|
||||
await click('[data-test-return-from-okta-number-challenge]');
|
||||
assert.true(this.returnToLogin, 'onReturnToLogin was triggered');
|
||||
});
|
||||
});
|
|
@ -114,11 +114,12 @@ module('Unit | Adapter | cluster', function (hooks) {
|
|||
'ldap:userpass options OK'
|
||||
);
|
||||
|
||||
data = { password: 'password', username: 'username', nonce: 'uuid' };
|
||||
adapter.authenticate({ backend: 'okta', data });
|
||||
assert.equal(url, '/v1/auth/okta/login/username', 'okta:userpass url OK');
|
||||
assert.equal(method, 'POST', 'ldap:userpass method OK');
|
||||
assert.deepEqual(
|
||||
{ data: { password: 'password' }, unauthenticated: true },
|
||||
{ data: { password: 'password', nonce: 'uuid' }, unauthenticated: true },
|
||||
options,
|
||||
'okta:userpass options OK'
|
||||
);
|
||||
|
@ -132,6 +133,7 @@ module('Unit | Adapter | cluster', function (hooks) {
|
|||
adapter.authenticate({ backend: 'LDAP', data });
|
||||
assert.equal(url, '/v1/auth/path/login/username', 'auth:LDAP with path url OK');
|
||||
|
||||
data = { password: 'password', username: 'username', path: 'path', nonce: 'uuid' };
|
||||
adapter.authenticate({ backend: 'Okta', data });
|
||||
assert.equal(url, '/v1/auth/path/login/username', 'auth:Okta with path url OK');
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue