Skip to content

Commit

Permalink
fix(idx): expose field level error messages - OKTA-503627
Browse files Browse the repository at this point in the history
OKTA-503627
<<<Jenkins Check-In of Tested SHA: f58f7ea for [email protected]>>>
Artifact: okta-auth-js
Files changed count: 8
PR Link: #1231
  • Loading branch information
shuowu authored and eng-prod-CI-bot-okta committed Jun 8, 2022
1 parent c6e175d commit 4252e9c
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 178 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 6.6.2

### Fixes

- [#1231](https://github.com/okta/okta-auth-js/pull/1231) IDX: exposes field level error messages

## 6.6.1

### Fixes
Expand Down
14 changes: 5 additions & 9 deletions lib/idx/remediate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
IdxActionParams,
} from './types/idx-js';
import {
getMessagesFromResponse,
isTerminalResponse,
filterValuesForRemediation,
getRemediator,
Expand Down Expand Up @@ -102,7 +101,7 @@ export async function remediate(
idxResponse = await idxResponse.actions[action](params);
idxResponse = { ...idxResponse, requestDidSucceed: true };
} catch (e) {
return handleIdxError(authClient, e, remediator);
return handleIdxError(authClient, e, options);
}
if (action === 'cancel') {
return { idxResponse, canceled: true };
Expand All @@ -123,7 +122,7 @@ export async function remediate(
idxResponse = { ...idxResponse, requestDidSucceed: true };
}
catch (e) {
return handleIdxError(authClient, e, remediator);
return handleIdxError(authClient, e, options);
}

return remediate(authClient, idxResponse, values, optionsWithoutExecutedAction); // recursive call
Expand All @@ -133,9 +132,8 @@ export async function remediate(

// Do not attempt to remediate if response is in terminal state
const terminal = isTerminalResponse(idxResponse);
const messages = getMessagesFromResponse(idxResponse);
if (terminal) {
return { idxResponse, terminal, messages };
return { idxResponse, terminal };
}

if (!remediator) {
Expand All @@ -146,7 +144,7 @@ export async function remediate(
idxResponse = { ...idxResponse, requestDidSucceed: true };
return { idxResponse };
} catch(e) {
return handleIdxError(authClient, e);
return handleIdxError(authClient, e, options);
}
}
if (flow === 'default') {
Expand All @@ -164,7 +162,6 @@ export async function remediate(
return {
idxResponse,
nextStep,
messages: messages.length ? messages: undefined
};
}

Expand All @@ -187,12 +184,11 @@ export async function remediate(
return {
idxResponse,
nextStep,
messages: messages.length ? messages: undefined
};
}

return remediate(authClient, idxResponse, values, options); // recursive call
} catch (e) {
return handleIdxError(authClient, e, remediator);
return handleIdxError(authClient, e, options);
}
}
2 changes: 1 addition & 1 deletion lib/idx/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ async function finalizeData(authClient, data: RunData): Promise<RunData> {
shouldSaveResponse = !!(idxResponse.requestDidSucceed || idxResponse.stepUp);
enabledFeatures = getEnabledFeatures(idxResponse);
availableSteps = getAvailableSteps(authClient, idxResponse, options.useGenericRemediator);
messages = getMessagesFromResponse(idxResponse);
messages = getMessagesFromResponse(idxResponse, options);
terminal = isTerminalResponse(idxResponse);
}

Expand Down
36 changes: 18 additions & 18 deletions lib/idx/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { warn } from '../util';
import * as remediators from './remediators';
import { RemediationValues, Remediator, RemediatorConstructor } from './remediators';
import { GenericRemediator } from './remediators/GenericRemediator';
import { IdxFeature, NextStep, RemediateOptions, RemediationResponse } from './types';
import { IdxFeature, NextStep, RemediateOptions, RemediationResponse, RunOptions } from './types';
import { IdxMessage, IdxRemediation, IdxRemediationValue, IdxResponse, isIdxResponse } from './types/idx-js';
import { OktaAuthIdxInterface } from '../types';

Expand Down Expand Up @@ -48,7 +48,7 @@ export function getMessagesFromIdxRemediationValue(
}, []);
}

export function getMessagesFromResponse(idxResponse: IdxResponse): IdxMessage[] {
export function getMessagesFromResponse(idxResponse: IdxResponse, options: RunOptions): IdxMessage[] {
let messages: IdxMessage[] = [];
const { rawIdxState, neededToProceed } = idxResponse;

Expand All @@ -59,10 +59,14 @@ export function getMessagesFromResponse(idxResponse: IdxResponse): IdxMessage[]
}

// Handle field messages for current flow
for (let remediation of neededToProceed) {
const fieldMessages = getMessagesFromIdxRemediationValue(remediation.value);
if (fieldMessages) {
messages = [...messages, ...fieldMessages] as never;
// Preserve existing logic for general cases, remove in the next major version
// Follow ion response format for top level messages when useGenericRemediator is true
if (!options.useGenericRemediator) {
for (let remediation of neededToProceed) {
const fieldMessages = getMessagesFromIdxRemediationValue(remediation.value);
if (fieldMessages) {
messages = [...messages, ...fieldMessages] as never;
}
}
}

Expand Down Expand Up @@ -250,7 +254,7 @@ export function getNextStep(
};
}

export function handleIdxError(authClient: OktaAuthIdxInterface, e, remediator?): RemediationResponse {
export function handleIdxError(authClient: OktaAuthIdxInterface, e, options = {}): RemediationResponse {
// Handle idx messages
let idxResponse = isIdxResponse(e) ? e : null;
if (!idxResponse) {
Expand All @@ -262,15 +266,11 @@ export function handleIdxError(authClient: OktaAuthIdxInterface, e, remediator?)
requestDidSucceed: false
};
const terminal = isTerminalResponse(idxResponse);
const messages = getMessagesFromResponse(idxResponse);
if (terminal) {
return { idxResponse, terminal, messages };
} else {
const nextStep = remediator && getNextStep(authClient, remediator, idxResponse);
return {
idxResponse,
messages,
...(nextStep && { nextStep })
};
}
const remediator = getRemediator(idxResponse.neededToProceed, {}, options);
const nextStep = remediator && getNextStep(authClient, remediator, idxResponse);
return {
idxResponse,
...(terminal && { terminal }),
...(!terminal && nextStep && { nextStep })
};
}
100 changes: 40 additions & 60 deletions test/spec/idx/authenticate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
GoogleAuthenticatorOptionFactory,
SelectAuthenticatorEnrollRemediationFactory,
ChallengeAuthenticatorRemediationFactory,
EnrollAuthenticatorRemediationFactory,
CredentialsValueFactory,
PasscodeValueFactory,
IdxErrorPasscodeInvalidFactory,
Expand Down Expand Up @@ -648,7 +649,7 @@ describe('idx/authenticate', () => {
]
});

const challengeAuthenticatorRemediation = ChallengeAuthenticatorRemediationFactory.build({
const challengeAuthenticatorRemediation = EnrollAuthenticatorRemediationFactory.build({
relatesTo: {
type: 'object',
value: PhoneAuthenticatorFactory.build()
Expand Down Expand Up @@ -857,18 +858,9 @@ describe('idx/authenticate', () => {
EnrollPhoneAuthenticatorRemediationFactory.build()
]
});
const selectAuthenticatorRemediation = SelectAuthenticatorEnrollRemediationFactory.build({
value: [
AuthenticatorValueFactory.build({
options: [
PhoneAuthenticatorOptionFactory.build(),
]
})
]
});
const errorInvalidPhoneResponse = IdxResponseFactory.build({
neededToProceed: [
selectAuthenticatorRemediation,
PhoneAuthenticatorEnrollmentDataRemediationFactory.build()
],
rawIdxState: RawIdxResponseFactory.build({
messages: IdxMessagesFactory.build({
Expand All @@ -879,7 +871,7 @@ describe('idx/authenticate', () => {
remediation: {
type: 'array',
value: [
selectAuthenticatorRemediation
PhoneAuthenticatorEnrollmentDataRemediationFactory.build()
]
}
})
Expand Down Expand Up @@ -1287,29 +1279,6 @@ describe('idx/authenticate', () => {
});

describe('google authenticator', () => {
let errorInvalidCodeResponse;
beforeEach(() => {
errorInvalidCodeResponse = IdxResponseFactory.build({
rawIdxState: RawIdxResponseFactory.build({
messages: IdxMessagesFactory.build({
value: [
IdxErrorGoogleAuthenticatorPasscodeInvalidFactory.build()
]
})
}),
neededToProceed:[
SelectAuthenticatorEnrollRemediationFactory.build({
value: [
AuthenticatorValueFactory.build({
options: [
PhoneAuthenticatorOptionFactory.build(),
]
})
]
})
]
});
});

describe('verification', () => {
beforeEach(() => {
Expand All @@ -1331,6 +1300,18 @@ describe('idx/authenticate', () => {
VerifyGoogleAuthenticatorRemediationFactory.build()
]
});
const errorInvalidCodeResponse = IdxResponseFactory.build({
rawIdxState: RawIdxResponseFactory.build({
messages: IdxMessagesFactory.build({
value: [
IdxErrorGoogleAuthenticatorPasscodeInvalidFactory.build()
]
})
}),
neededToProceed:[
VerifyGoogleAuthenticatorRemediationFactory.build()
]
});
Object.assign(testContext, {
selectAuthenticatorResponse,
verifyAuthenticatorResponse,
Expand Down Expand Up @@ -1494,6 +1475,18 @@ describe('idx/authenticate', () => {
EnrollGoogleAuthenticatorRemediationFactory.build()
]
});
const errorInvalidCodeResponse = IdxResponseFactory.build({
rawIdxState: RawIdxResponseFactory.build({
messages: IdxMessagesFactory.build({
value: [
IdxErrorGoogleAuthenticatorPasscodeInvalidFactory.build()
]
})
}),
neededToProceed:[
EnrollGoogleAuthenticatorRemediationFactory.build()
]
});

Object.assign(testContext, {
selectAuthenticatorResponse,
Expand Down Expand Up @@ -1619,29 +1612,6 @@ describe('idx/authenticate', () => {
});

describe('Okta Verify', () => {
let errorInvalidCodeResponse;
beforeEach(() => {
errorInvalidCodeResponse = IdxResponseFactory.build({
rawIdxState: RawIdxResponseFactory.build({
messages: IdxMessagesFactory.build({
value: [
IdxErrorOktaVerifyPasscodeInvalidFactory.build()
]
})
}),
neededToProceed:[
SelectAuthenticatorEnrollRemediationFactory.build({
value: [
AuthenticatorValueFactory.build({
options: [
OktaVerifyAuthenticatorOptionFactory.build(),
]
})
]
})
]
});
});

describe('verification', () => {
beforeEach(() => {
Expand Down Expand Up @@ -1689,7 +1659,6 @@ describe('idx/authenticate', () => {
Object.assign(testContext, {
selectAuthenticatorResponse,
verifyAuthenticatorResponse,
errorInvalidCodeResponse,
verificationDataResponse,
pollForPushResponse,
});
Expand Down Expand Up @@ -1771,8 +1740,19 @@ describe('idx/authenticate', () => {
const {
authClient,
verifyAuthenticatorResponse,
errorInvalidCodeResponse
} = testContext;
const errorInvalidCodeResponse = IdxResponseFactory.build({
rawIdxState: RawIdxResponseFactory.build({
messages: IdxMessagesFactory.build({
value: [
IdxErrorOktaVerifyPasscodeInvalidFactory.build()
]
})
}),
neededToProceed:[
VerifyOktaVerifyAuthenticatorRemediationFactory.build(),
]
});
jest.spyOn(verifyAuthenticatorResponse, 'proceed').mockRejectedValue(errorInvalidCodeResponse);
jest.spyOn(mocked.introspect, 'introspect').mockResolvedValue(verifyAuthenticatorResponse);
const verificationCode = 'invalid-test-code';
Expand Down
11 changes: 1 addition & 10 deletions test/spec/idx/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import {
IdxErrorInvalidLoginEmailFactory,
IdxErrorDoesNotMatchPattern,
IdxErrorEnrollmentInvalidPhoneFactory,
SelectAuthenticatorAuthenticateRemediationFactory,
EnrollGoogleAuthenticatorRemediationFactory,
GoogleAuthenticatorOptionFactory,
SecurityQuestionAuthenticatorOptionFactory,
Expand Down Expand Up @@ -1290,15 +1289,7 @@ describe('idx/register', () => {
})
}),
neededToProceed: [
SelectAuthenticatorAuthenticateRemediationFactory.build({
value: [
AuthenticatorValueFactory.build({
options: [
PhoneAuthenticatorOptionFactory.build(),
]
})
]
})
PhoneAuthenticatorEnrollmentDataRemediationFactory.build(),
]
});

Expand Down
Loading

0 comments on commit 4252e9c

Please sign in to comment.