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 }) {
|
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 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
mfaErrors: null,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
cancelAuthentication() {
|
||||||
|
this.set('cancelAuth', true);
|
||||||
|
this.set('waitingForOktaNumberChallenge', false);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
|
@ -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)}}>
|
<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>
|
||||||
|
|
|
@ -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'
|
'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');
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue