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:
linda9379 2022-08-10 15:46:04 -04:00 committed by GitHub
parent 5c4b1cc4ac
commit 5cd1a12178
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 396 additions and 168 deletions

3
changelog/15998.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: UI support for Okta Number Challenge.
```

View File

@ -107,7 +107,7 @@ export default ApplicationAdapter.extend({
}, },
authenticate({ backend, data }) { 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 url = this.urlForAuth(backend, username, path);
const verb = backend === 'token' ? 'GET' : 'POST'; const verb = backend === 'token' ? 'GET' : 'POST';
let options = { let options = {
@ -119,6 +119,8 @@ export default ApplicationAdapter.extend({
}; };
} else if (backend === 'jwt' || backend === 'oidc') { } else if (backend === 'jwt' || backend === 'oidc') {
options.data = { role, jwt }; options.data = { role, jwt };
} else if (backend === 'okta') {
options.data = { password, nonce };
} else { } else {
options.data = token ? { token, password } : { password }; options.data = token ? { token, password } : { password };
} }

View File

@ -18,13 +18,17 @@ const BACKENDS = supportedAuthBackends();
* *
* @example ```js * @example ```js
* // All properties are passed in via query params. * // All properties are passed in via query params.
* <AuthForm @wrappedToken={{wrappedToken}} @cluster={{model}} @namespace={{namespaceQueryParam}} @selectedAuth={{authMethod}} @onSuccess={{action this.onSuccess}} />``` * <AuthForm @wrappedToken={{wrappedToken}} @cluster={{model}} @namespace={{namespaceQueryParam}} @selectedAuth={{authMethod}} @onSuccess={{action this.onSuccess}}/>```
* *
* @param {string} wrappedToken - The auth method that is currently selected in the dropdown. * @param {string} wrappedToken - The auth method that is currently selected in the dropdown.
* @param {object} cluster - The auth method that is currently selected in the dropdown. This corresponds to an Ember Model. * @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} namespace- The currently active namespace.
* @param {string} selectedAuth - The auth method that is currently selected in the dropdown. * @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 = { const DEFAULTS = {
@ -51,6 +55,9 @@ export default Component.extend(DEFAULTS, {
oldNamespace: null, oldNamespace: null,
authMethods: BACKENDS, authMethods: BACKENDS,
// number answer for okta number challenge if applicable
oktaNumberChallengeAnswer: null,
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
let { let {
@ -60,8 +67,14 @@ export default Component.extend(DEFAULTS, {
namespace: ns, namespace: ns,
selectedAuth: newMethod, selectedAuth: newMethod,
oldSelectedAuth: oldMethod, oldSelectedAuth: oldMethod,
cancelAuthForOktaNumberChallenge: cancelAuth,
} = this; } = 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(() => { next(() => {
if (!token && (oldNS === null || oldNS !== ns)) { if (!token && (oldNS === null || oldNS !== ns)) {
this.fetchMethods.perform(); this.fetchMethods.perform();
@ -219,7 +232,11 @@ export default Component.extend(DEFAULTS, {
cluster: { id: clusterId }, cluster: { id: clusterId },
} = this; } = this;
try { try {
this.delayAuthMessageReminder.perform(); if (backendType === 'okta') {
this.pollForOktaNumberChallenge.perform(data.nonce, data.path);
} else {
this.delayAuthMessageReminder.perform();
}
const authResponse = yield this.auth.authenticate({ const authResponse = yield this.auth.authenticate({
clusterId, clusterId,
backend: backendType, 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* () { delayAuthMessageReminder: task(function* () {
if (Ember.testing) { if (Ember.testing) {
this.showLoading = true; this.showLoading = true;
@ -275,6 +314,14 @@ export default Component.extend(DEFAULTS, {
if (this.customPath || backend.id) { if (this.customPath || backend.id) {
data.path = 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); return this.authenticate.unlinked().perform(backend.type, data);
}, },
handleError(e) { handleError(e) {
@ -283,5 +330,9 @@ export default Component.extend(DEFAULTS, {
error: e ? this.auth.handleError(e) : null, error: e ? this.auth.handleError(e) : null,
}); });
}, },
returnToLoginFromOktaNumberChallenge() {
this.setOktaNumberChallenge(false);
this.set('oktaNumberChallengeAnswer', null);
},
}, },
}); });

View File

@ -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;
}
}

View File

@ -85,5 +85,9 @@ export default Controller.extend({
mfaErrors: null, mfaErrors: null,
}); });
}, },
cancelAuthentication() {
this.set('cancelAuth', true);
this.set('waitingForOktaNumberChallenge', false);
},
}, },
}); });

View File

@ -441,4 +441,21 @@ export default Service.extend({
backend: BACKENDS.findBy('type', backend), 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;
}
}
);
},
}); });

View File

@ -1,176 +1,186 @@
<div class="auth-form" data-test-auth-form> <div class="auth-form" data-test-auth-form>
{{#if this.hasMethodsWithPath}} {{#if (or (this.error) (and this.waitingForOktaNumberChallenge (not this.cancelAuthForOktaNumberChallenge)))}}
<nav class="tabs is-marginless"> <OktaNumberChallenge
<ul> @correctAnswer={{this.oktaNumberChallengeAnswer}}
{{#each this.methodsToShow as |method|}} @hasError={{(not-eq this.error null)}}
{{#let (or method.path method.type) as |methodKey|}} @onReturnToLogin={{action "returnToLoginFromOktaNumberChallenge"}}
<li />
class={{if {{else}}
(and this.selectedAuthIsPath (eq (or this.selectedAuthBackend.path this.selectedAuthBackend.type) methodKey)) {{#if this.hasMethodsWithPath}}
"is-active" <nav class="tabs is-marginless">
"" <ul>
}} {{#each this.methodsToShow as |method|}}
data-test-auth-method {{#let (or method.path method.type) as |methodKey|}}
> <li
class={{if
(and
this.selectedAuthIsPath (eq (or this.selectedAuthBackend.path this.selectedAuthBackend.type) methodKey)
)
"is-active"
""
}}
data-test-auth-method
>
<LinkTo
@route="vault.cluster.auth"
@model={{this.cluster.name}}
@query={{hash with=methodKey}}
data-test-auth-method-link={{method.type}}
>
{{or method.id (capitalize method.type)}}
</LinkTo>
</li>
{{/let}}
{{/each}}
{{#if this.hasMethodsWithPath}}
<li class={{unless this.selectedAuthIsPath "is-active" ""}} data-test-auth-method>
<LinkTo <LinkTo
@route="vault.cluster.auth" @route="vault.cluster.auth"
@model={{this.cluster.name}} @model={{this.cluster.name}}
@query={{hash with=methodKey}} @query={{hash with="token"}}
data-test-auth-method-link={{method.type}} data-test-auth-method-link="other"
> >
{{or method.id (capitalize method.type)}} Other
</LinkTo> </LinkTo>
</li> </li>
{{/let}} {{/if}}
{{/each}} </ul>
{{#if this.hasMethodsWithPath}} </nav>
<li class={{unless this.selectedAuthIsPath "is-active" ""}} data-test-auth-method>
<LinkTo
@route="vault.cluster.auth"
@model={{this.cluster.name}}
@query={{hash with="token"}}
data-test-auth-method-link="other"
>
Other
</LinkTo>
</li>
{{/if}}
</ul>
</nav>
{{/if}}
<div class="box is-marginless is-shadowless">
<MessageError
@errorMessage={{if (and this.cluster.standby this.hasCSPError) this.cspErrorText this.error}}
data-test-auth-error
/>
<div class="has-bottom-margin-s">
<p class="is-label">{{this.selectedAuthBackend.path}}</p>
<span class="description has-text-grey" data-test-description={{true}}>
{{this.selectedAuthBackend.mountDescription}}
</span>
</div>
{{#if (or (not this.hasMethodsWithPath) (not this.selectedAuthIsPath))}}
<Select
@label="Method"
@name="auth-method"
@options={{this.authMethods}}
@valueAttribute={{"type"}}
@labelAttribute={{"typeDisplay"}}
@isFullwidth={{true}}
@selectedValue={{this.selectedAuth}}
@onChange={{action (mut this.selectedAuth)}}
/>
{{/if}} {{/if}}
{{#if (or (eq this.selectedAuthBackend.type "jwt") (eq this.selectedAuthBackend.type "oidc"))}} <div class="box is-marginless is-shadowless">
<AuthJwt <MessageError
@onError={{action "handleError"}} @errorMessage={{if (and this.cluster.standby this.hasCSPError) this.cspErrorText this.error}}
@onLoading={{action (mut this.isLoading)}} data-test-auth-error
@onToken={{action (mut this.token)}} />
@namespace={{this.namespace}} <div class="has-bottom-margin-s">
@onNamespace={{action (mut this.namespace)}} <p class="is-label">{{this.selectedAuthBackend.path}}</p>
@onSubmit={{action "doSubmit"}} <span class="description has-text-grey" data-test-description={{true}}>
@onRoleName={{action (mut this.roleName)}} {{this.selectedAuthBackend.mountDescription}}
@roleName={{this.roleName}} </span>
@selectedAuthType={{this.selectedAuthBackend.type}} </div>
@selectedAuthPath={{or this.customPath this.selectedAuthBackend.id}} {{#if (or (not this.hasMethodsWithPath) (not this.selectedAuthIsPath))}}
@disabled={{or this.authenticate.isRunning this.isLoading}} <Select
> @label="Method"
<AuthFormOptions @name="auth-method"
@customPath={{this.customPath}} @options={{this.authMethods}}
@onPathChange={{action (mut this.customPath)}} @valueAttribute={{"type"}}
@selectedAuthIsPath={{this.selectedAuthIsPath}} @labelAttribute={{"typeDisplay"}}
@isFullwidth={{true}}
@selectedValue={{this.selectedAuth}}
@onChange={{action (mut this.selectedAuth)}}
/> />
</AuthJwt> {{/if}}
{{else}} {{#if (or (eq this.selectedAuthBackend.type "jwt") (eq this.selectedAuthBackend.type "oidc"))}}
<form id="auth-form" onsubmit={{action "doSubmit"}}> <AuthJwt
{{#if (eq this.providerName "github")}} @onError={{action "handleError"}}
<div class="field"> @onLoading={{action (mut this.isLoading)}}
<label for="token" class="is-label">GitHub token</label> @onToken={{action (mut this.token)}}
<div class="control"> @namespace={{this.namespace}}
<Input @onNamespace={{action (mut this.namespace)}}
@type="password" @onSubmit={{action "doSubmit"}}
@value={{this.token}} @onRoleName={{action (mut this.roleName)}}
name="token" @roleName={{this.roleName}}
id="token" @selectedAuthType={{this.selectedAuthBackend.type}}
class="input" @selectedAuthPath={{or this.customPath this.selectedAuthBackend.id}}
data-test-token={{true}} @disabled={{or this.authenticate.isRunning this.isLoading}}
autocomplete="off" >
spellcheck="false"
/>
</div>
</div>
{{else if (eq this.providerName "token")}}
<div class="field">
<label for="token" class="is-label">Token</label>
<div class="control">
<Input
@type="password"
@value={{this.token}}
name="token"
class="input"
autocomplete="off"
spellcheck="false"
data-test-token={{true}}
/>
</div>
</div>
{{else}}
<div class="field">
<label for="username" class="is-label">Username</label>
<div class="control">
<Input
@value={{this.username}}
name="username"
id="username"
class="input"
autocomplete="off"
spellcheck="false"
data-test-username={{true}}
/>
</div>
</div>
<div class="field">
<label for="password" class="is-label">Password</label>
<div class="control">
<Input
@value={{this.password}}
name="password"
id="password"
@type="password"
class="input"
autocomplete="off"
spellcheck="false"
data-test-password={{true}}
/>
</div>
</div>
{{/if}}
{{#if (not-eq this.selectedAuthBackend.type "token")}}
<AuthFormOptions <AuthFormOptions
@customPath={{this.customPath}} @customPath={{this.customPath}}
@onPathChange={{action (mut this.customPath)}} @onPathChange={{action (mut this.customPath)}}
@selectedAuthIsPath={{this.selectedAuthIsPath}} @selectedAuthIsPath={{this.selectedAuthIsPath}}
/> />
{{/if}} </AuthJwt>
<button {{else}}
data-test-auth-submit={{true}} <form id="auth-form" onsubmit={{action "doSubmit"}}>
type="submit" {{#if (eq this.providerName "github")}}
disabled={{this.authenticate.isRunning}} <div class="field">
class="button is-primary {{if this.authenticate.isRunning 'is-loading'}}" <label for="token" class="is-label">GitHub token</label>
id="auth-submit" <div class="control">
> <Input
Sign In @type="password"
</button> @value={{this.token}}
{{#if (and this.delayAuthMessageReminder.isIdle this.showLoading)}} name="token"
<AlertInline id="token"
@paddingTop={{true}} class="input"
@sizeSmall={{true}} data-test-token={{true}}
@type="info" autocomplete="off"
@message="If login takes longer than usual, you may need to check your device for an MFA notification, or contact your administrator if login times out." spellcheck="false"
data-test-auth-message="push" />
/> </div>
{{/if}} </div>
</form> {{else if (eq this.providerName "token")}}
{{/if}} <div class="field">
</div> <label for="token" class="is-label">Token</label>
<div class="control">
<Input
@type="password"
@value={{this.token}}
name="token"
class="input"
autocomplete="off"
spellcheck="false"
data-test-token={{true}}
/>
</div>
</div>
{{else}}
<div class="field">
<label for="username" class="is-label">Username</label>
<div class="control">
<Input
@value={{this.username}}
name="username"
id="username"
class="input"
autocomplete="off"
spellcheck="false"
data-test-username={{true}}
/>
</div>
</div>
<div class="field">
<label for="password" class="is-label">Password</label>
<div class="control">
<Input
@value={{this.password}}
name="password"
id="password"
@type="password"
class="input"
autocomplete="off"
spellcheck="false"
data-test-password={{true}}
/>
</div>
</div>
{{/if}}
{{#if (not-eq this.selectedAuthBackend.type "token")}}
<AuthFormOptions
@customPath={{this.customPath}}
@onPathChange={{action (mut this.customPath)}}
@selectedAuthIsPath={{this.selectedAuthIsPath}}
/>
{{/if}}
<button
data-test-auth-submit={{true}}
type="submit"
disabled={{this.authenticate.isRunning}}
class="button is-primary {{if this.authenticate.isRunning 'is-loading'}}"
id="auth-submit"
>
Sign In
</button>
{{#if (and this.delayAuthMessageReminder.isIdle this.showLoading)}}
<AlertInline
@paddingTop={{true}}
@sizeSmall={{true}}
@type="info"
@message="If login takes longer than usual, you may need to check your device for an MFA notification, or contact your administrator if login times out."
data-test-auth-message="push"
/>
{{/if}}
</form>
{{/if}}
</div>
{{/if}}
</div> </div>

View File

@ -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>

View File

@ -27,9 +27,13 @@
<button type="button" class="icon-button" {{on "click" (fn (mut this.mfaAuthData) null)}}> <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" /> <Icon @name="arrow-left" @size="24" aria-label="Back to login" class="icon-blue" />
</button> </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}} {{/if}}
<h1 class="title is-3"> <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> </h1>
</div> </div>
{{/if}} {{/if}}
@ -113,6 +117,10 @@
@redirectTo={{this.redirectTo}} @redirectTo={{this.redirectTo}}
@selectedAuth={{this.authMethod}} @selectedAuth={{this.authMethod}}
@onSuccess={{action "onAuthResponse"}} @onSuccess={{action "onAuthResponse"}}
@setOktaNumberChallenge={{fn (mut this.waitingForOktaNumberChallenge)}}
@waitingForOktaNumberChallenge={{this.waitingForOktaNumberChallenge}}
@setCancellingAuth={{fn (mut this.cancelAuth)}}
@cancelAuthForOktaNumberChallenge={{this.cancelAuth}}
/> />
{{/if}} {{/if}}
</Page.content> </Page.content>

View File

@ -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');
});
});

View File

@ -114,11 +114,12 @@ module('Unit | Adapter | cluster', function (hooks) {
'ldap:userpass options OK' 'ldap:userpass options OK'
); );
data = { password: 'password', username: 'username', nonce: 'uuid' };
adapter.authenticate({ backend: 'okta', data }); adapter.authenticate({ backend: 'okta', data });
assert.equal(url, '/v1/auth/okta/login/username', 'okta:userpass url OK'); assert.equal(url, '/v1/auth/okta/login/username', 'okta:userpass url OK');
assert.equal(method, 'POST', 'ldap:userpass method OK'); assert.equal(method, 'POST', 'ldap:userpass method OK');
assert.deepEqual( assert.deepEqual(
{ data: { password: 'password' }, unauthenticated: true }, { data: { password: 'password', nonce: 'uuid' }, unauthenticated: true },
options, options,
'okta:userpass options OK' 'okta:userpass options OK'
); );
@ -132,6 +133,7 @@ module('Unit | Adapter | cluster', function (hooks) {
adapter.authenticate({ backend: 'LDAP', data }); adapter.authenticate({ backend: 'LDAP', data });
assert.equal(url, '/v1/auth/path/login/username', 'auth:LDAP with path url OK'); 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 }); adapter.authenticate({ backend: 'Okta', data });
assert.equal(url, '/v1/auth/path/login/username', 'auth:Okta with path url OK'); assert.equal(url, '/v1/auth/path/login/username', 'auth:Okta with path url OK');
}); });