diff --git a/.changeset/config.json b/.changeset/config.json index 5df6bfca0..dd0165711 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -24,6 +24,8 @@ "@forgerock/oidc-suites", "@forgerock/local-release-tool", "@forgerock/protect-app", - "@forgerock/protect-suites" + "@forgerock/protect-suites", + "@forgerock/journey-app", + "@forgerock/journey-suites" ] } diff --git a/.changeset/eleven-baboons-battle.md b/.changeset/eleven-baboons-battle.md new file mode 100644 index 000000000..c2128b3f6 --- /dev/null +++ b/.changeset/eleven-baboons-battle.md @@ -0,0 +1,5 @@ +--- +'@forgerock/journey-client': patch +--- + +Add support for KBA `allowUserDefinedQuestions` flag diff --git a/e2e/journey-app/components/kba-create.ts b/e2e/journey-app/components/kba-create.ts index 3bc3975dc..b4e184c89 100644 --- a/e2e/journey-app/components/kba-create.ts +++ b/e2e/journey-app/components/kba-create.ts @@ -34,6 +34,14 @@ export default function kbaCreateComponent( questionInput.appendChild(option); }); + // Add option to create a question if allowed + if (callback.isAllowedUserDefinedQuestions()) { + const userDefinedOption = document.createElement('option'); + userDefinedOption.value = 'user-defined'; + userDefinedOption.text = 'Enter your own question'; + questionInput.appendChild(userDefinedOption); + } + // Answer field const answerLabel = document.createElement('label'); answerLabel.htmlFor = `${collectorKey}-answer`; @@ -53,10 +61,32 @@ export default function kbaCreateComponent( // Event listeners questionInput.addEventListener('input', (event) => { - callback.setQuestion((event.target as HTMLInputElement).value); + const selectedQuestion = (event.target as HTMLInputElement).value; + if (selectedQuestion === 'user-defined') { + // If user-defined option is selected, prompt for custom question + const customQuestionLabel = document.createElement('label'); + customQuestionLabel.htmlFor = `${collectorKey}-question-user-defined`; + customQuestionLabel.innerText = 'Type your question ' + idx + ':'; + + const customQuestionInput = document.createElement('input'); + customQuestionInput.type = 'text'; + customQuestionInput.id = `${collectorKey}-question-user-defined`; + customQuestionInput.placeholder = 'Type your question'; + + container.lastElementChild?.before(customQuestionLabel); + container.lastElementChild?.before(customQuestionInput); + customQuestionInput.addEventListener('input', (e) => { + callback.setQuestion((e.target as HTMLInputElement).value); + console.log('Custom question ' + idx + ':', callback.getInputValue(0)); + }); + } else { + callback.setQuestion((event.target as HTMLInputElement).value); + console.log('Selected question ' + idx + ':', callback.getInputValue(0)); + } }); answerInput.addEventListener('input', (event) => { callback.setAnswer((event.target as HTMLInputElement).value); + console.log('Answer ' + idx + ':', callback.getInputValue(1)); }); } diff --git a/e2e/journey-suites/src/registration.test.ts b/e2e/journey-suites/src/registration.test.ts index a536c8f05..6bf6725ad 100644 --- a/e2e/journey-suites/src/registration.test.ts +++ b/e2e/journey-suites/src/registration.test.ts @@ -40,10 +40,13 @@ test('Test happy paths on test page', async ({ page }) => { await page.getByLabel('Send me news and updates').check(); // Fill password await page.getByLabel('Password').fill(password); - // Select "Select a security question 7" dropdown and choose "What's your favorite color?" - await page.getByLabel('Select a security question 7').selectOption("What's your favorite color?"); - // Fill answer with "Red" - await page.getByLabel('Answer 7').fill('Red'); + + // Select "Select a security question 7" dropdown and choose custom question + await page.getByLabel('Select a security question 7').selectOption('user-defined'); + await page.getByLabel('Type your question 7:').fill(`What is your pet's name?`); + // Fill answer with "Rover" + await page.getByLabel('Answer 7').fill('Rover'); + // Select "Select a security question 8" dropdown and choose "Who was your first employer?" await page .getByLabel('Select a security question 8') @@ -61,6 +64,10 @@ test('Test happy paths on test page', async ({ page }) => { await clickButton('Logout', '/authenticate'); // Test assertions + expect(messageArray.includes(`Custom question 7: What is your pet's name?`)).toBe(true); + expect(messageArray.includes('Answer 7: Rover')).toBe(true); + expect(messageArray.includes(`Selected question 8: Who was your first employer?`)).toBe(true); + expect(messageArray.includes('Answer 8: AAA Engineering')).toBe(true); expect(messageArray.includes('Journey completed successfully')).toBe(true); expect(messageArray.includes('Logout successful')).toBe(true); }); diff --git a/packages/journey-client/src/lib/callbacks/kba-create-callback.test.ts b/packages/journey-client/src/lib/callbacks/kba-create-callback.test.ts index 83bfffefc..3ac9b45e1 100644 --- a/packages/journey-client/src/lib/callbacks/kba-create-callback.test.ts +++ b/packages/journey-client/src/lib/callbacks/kba-create-callback.test.ts @@ -24,6 +24,10 @@ describe('KbaCreateCallback', () => { name: 'predefinedQuestions', value: ['Question 1', 'Question 2'], }, + { + name: 'allowUserDefinedQuestions', + value: true, + }, ], input: [ { @@ -50,4 +54,9 @@ describe('KbaCreateCallback', () => { expect(cb.getInputValue('IDToken1question')).toBe('My custom question'); expect(cb.getInputValue('IDToken1answer')).toBe('Blue'); }); + + it('should indicate if user-defined questions are allowed', () => { + const cb = new KbaCreateCallback(payload); + expect(cb.isAllowedUserDefinedQuestions()).toBe(true); + }); }); diff --git a/packages/journey-client/src/lib/callbacks/kba-create-callback.ts b/packages/journey-client/src/lib/callbacks/kba-create-callback.ts index 4528d6820..d98d34302 100644 --- a/packages/journey-client/src/lib/callbacks/kba-create-callback.ts +++ b/packages/journey-client/src/lib/callbacks/kba-create-callback.ts @@ -33,6 +33,13 @@ export class KbaCreateCallback extends BaseCallback { return this.getOutputByName('predefinedQuestions', []); } + /** + * Gets whether the user can define questions. + */ + public isAllowedUserDefinedQuestions(): boolean { + return this.getOutputByName('allowUserDefinedQuestions', false); + } + /** * Sets the callback's security question. */