From bc3da80184b6ba8e46845fc98708463584fc3212 Mon Sep 17 00:00:00 2001 From: Ryan Bas Date: Tue, 21 Oct 2025 14:14:55 -0400 Subject: [PATCH] chore: migrate-old-mock-api migrate old mock api to repo chore: fix-tests --- .node-version | 2 +- e2e/am-mock-api/.babelrc | 3 + e2e/am-mock-api/.eslintrc.json | 30 + e2e/am-mock-api/package.json | 19 + e2e/am-mock-api/project.json | 50 + e2e/am-mock-api/src/app/.gitkeep | 0 e2e/am-mock-api/src/app/app.auth.js | 36 + e2e/am-mock-api/src/app/constants.js | 66 + e2e/am-mock-api/src/app/env.config.js | 71 + .../src/app/response.registration.js | 224 +++ e2e/am-mock-api/src/app/responses.js | 1349 +++++++++++++++++ e2e/am-mock-api/src/app/routes.auth.js | 644 ++++++++ e2e/am-mock-api/src/app/routes.resource.js | 191 +++ e2e/am-mock-api/src/app/wait.js | 15 + e2e/am-mock-api/src/assets/.gitkeep | 0 .../src/environments/environment.prod.ts | 3 + .../src/environments/environment.ts | 9 + e2e/am-mock-api/src/index.js | 57 + e2e/am-mock-api/tsconfig.app.json | 13 + e2e/am-mock-api/tsconfig.json | 13 + e2e/am-mock-api/tsconfig.spec.json | 9 + .../src/phone-number-field.test.ts | 4 +- e2e/journey-app/main.ts | 10 +- e2e/journey-app/server-configs.ts | 10 +- e2e/journey-suites/playwright.config.ts | 10 +- .../src/choice-confirm-poll.test.ts | 6 +- e2e/journey-suites/src/custom-paths.test.ts | 2 +- e2e/journey-suites/src/device-profile.test.ts | 2 +- e2e/journey-suites/src/email-suspend.test.ts | 2 +- e2e/journey-suites/src/login.test.ts | 4 +- e2e/journey-suites/src/no-session.test.ts | 4 +- e2e/journey-suites/src/otp-register.test.ts | 2 +- e2e/journey-suites/src/protect.test.ts | 2 +- e2e/journey-suites/src/registration.test.ts | 4 +- .../src/request-middleware.test.ts | 4 +- e2e/journey-suites/src/utils/demo-user.ts | 4 +- package.json | 3 +- .../src/lib/client.store.test.ts | 59 + .../journey-client/src/lib/client.store.ts | 27 +- pnpm-lock.yaml | 313 ++++ tsconfig.json | 3 + 41 files changed, 3246 insertions(+), 33 deletions(-) create mode 100644 e2e/am-mock-api/.babelrc create mode 100644 e2e/am-mock-api/.eslintrc.json create mode 100644 e2e/am-mock-api/package.json create mode 100644 e2e/am-mock-api/project.json create mode 100644 e2e/am-mock-api/src/app/.gitkeep create mode 100644 e2e/am-mock-api/src/app/app.auth.js create mode 100644 e2e/am-mock-api/src/app/constants.js create mode 100644 e2e/am-mock-api/src/app/env.config.js create mode 100644 e2e/am-mock-api/src/app/response.registration.js create mode 100644 e2e/am-mock-api/src/app/responses.js create mode 100644 e2e/am-mock-api/src/app/routes.auth.js create mode 100644 e2e/am-mock-api/src/app/routes.resource.js create mode 100644 e2e/am-mock-api/src/app/wait.js create mode 100644 e2e/am-mock-api/src/assets/.gitkeep create mode 100644 e2e/am-mock-api/src/environments/environment.prod.ts create mode 100644 e2e/am-mock-api/src/environments/environment.ts create mode 100644 e2e/am-mock-api/src/index.js create mode 100644 e2e/am-mock-api/tsconfig.app.json create mode 100644 e2e/am-mock-api/tsconfig.json create mode 100644 e2e/am-mock-api/tsconfig.spec.json diff --git a/.node-version b/.node-version index a45fd52cc5..2bd5a0a98a 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -24 +22 diff --git a/e2e/am-mock-api/.babelrc b/e2e/am-mock-api/.babelrc new file mode 100644 index 0000000000..aed1b9711b --- /dev/null +++ b/e2e/am-mock-api/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nx/js/babel", { "useBuiltIns": "entry" }]] +} diff --git a/e2e/am-mock-api/.eslintrc.json b/e2e/am-mock-api/.eslintrc.json new file mode 100644 index 0000000000..80cfb89e9b --- /dev/null +++ b/e2e/am-mock-api/.eslintrc.json @@ -0,0 +1,30 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "ignorePatterns": [ + "node_modules", + "*.md", + "LICENSE", + ".babelrc", + ".env*", + ".bin", + "dist", + ".eslintignore" + ] + }, + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/e2e/am-mock-api/package.json b/e2e/am-mock-api/package.json new file mode 100644 index 0000000000..bb3816e1be --- /dev/null +++ b/e2e/am-mock-api/package.json @@ -0,0 +1,19 @@ +{ + "name": "am-mock-api", + "version": "0.0.0", + "private": true, + "description": "", + "keywords": [], + "license": "ISC", + "author": "", + "main": "./index.js", + "dependencies": { + "@types/express": "^4.17.17", + "body-parser": "^2.2.0", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "express": "^4.21.2", + "superagent": "^10.2.3", + "uuid": "^13.0.0" + } +} diff --git a/e2e/am-mock-api/project.json b/e2e/am-mock-api/project.json new file mode 100644 index 0000000000..8dfbab1073 --- /dev/null +++ b/e2e/am-mock-api/project.json @@ -0,0 +1,50 @@ +{ + "name": "am-mock-api", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/am-mock-api/src", + "projectType": "application", + "tags": ["scope:e2e"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{projectRoot}/dist"], + "options": { + "outputPath": "e2e/am-mock-api/dist", + "main": "e2e/am-mock-api/src/index.js", + "clean": true, + "tsConfig": "e2e/am-mock-api/tsconfig.app.json", + "assets": ["e2e/am-mock-api/src/assets"] + }, + "configurations": { + "development": { + "watch": true + }, + "production": { + "optimization": true, + "extractLicenses": true, + "inspect": false, + "fileReplacements": [ + { + "replace": "e2e/am-mock-api/src/environments/environment.ts", + "with": "e2e/am-mock-api/src/environments/environment.prod.ts" + } + ] + } + } + }, + "serve": { + "executor": "@nx/js:node", + "outputs": ["{projectRoot}/dist"], + "options": { + "buildTarget": "am-mock-api:build" + } + }, + "lint": { + "options": { + "fix": true, + "ignore-path": ".eslintignore", + "args": ["**/*.ts"] + } + } + } +} diff --git a/e2e/am-mock-api/src/app/.gitkeep b/e2e/am-mock-api/src/app/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/e2e/am-mock-api/src/app/app.auth.js b/e2e/am-mock-api/src/app/app.auth.js new file mode 100644 index 0000000000..71d160dd03 --- /dev/null +++ b/e2e/am-mock-api/src/app/app.auth.js @@ -0,0 +1,36 @@ +/* + * @forgerock/javascript-sdk + * + * app.auth.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +import request from 'superagent'; +import { AM_URL, REALM_PATH } from './env.config.js'; + +export let session; + +export async function authorizeApp({ un, pw }) { + try { + const response = await request + .post(`${AM_URL}/json/realms/${REALM_PATH}/authenticate`) + .set('Content-Type', 'application/json') + .set('Accept-API-Version', 'resource=2.0, protocol=1.0') + .set('X-OpenAM-Username', un) + .set('X-OpenAM-Password', pw) + .send({}); + + session = response.body; + + console.log(`REST app identity token: ${session.tokenId}`); + + return session; + } catch (error) { + console.warn('\n###################################################'); + console.warn('WARNING: REST app user for Step Up/Txn Auth missing'); + console.warn('###################################################\n'); + } +} diff --git a/e2e/am-mock-api/src/app/constants.js b/e2e/am-mock-api/src/app/constants.js new file mode 100644 index 0000000000..6e7b8240a8 --- /dev/null +++ b/e2e/am-mock-api/src/app/constants.js @@ -0,0 +1,66 @@ +/* + * @forgerock/javascript-sdk + * + * constants.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +export const authPaths = { + tokenExchange: [ + '/am/auth/tokenExchange', + '/am/oauth2/realms/root/access_token', + '/am/oauth2/realms/root/realms/middleware/access_token', + '/am/oauth2/realms/root/realms/middleware-modern/access_token', + '/am/oauth2/realms/root/realms/tokens-expiring-soon/access_token', + '/am/oauth2/realms/root/realms/tokens-expired/access_token', + ], + authenticate: [ + '/am/auth/authenticate', + '/am/json/realms/root/authenticate', + '/am/json/realms/root/realms/middleware/authenticate', + '/am/json/realms/root/realms/tokens-expiring-soon/authenticate', + '/am/json/realms/root/realms/tokens-expired/authenticate', + ], + htmlAuthenticate: ['/am/'], + authorize: [ + '/am/auth/authorize', + '/am/oauth2/realms/root/authorize', + '/am/oauth2/realms/root/realms/middleware/authorize', + '/am/oauth2/realms/root/realms/middleware-modern/authorize', + '/am/oauth2/realms/root/realms/tokens-expiring-soon/authorize', + '/am/oauth2/realms/root/realms/tokens-expired/authorize', + ], + endSession: [ + '/am/auth/endSession', + '/am/oauth2/realms/root/connect/endSession', + '/am/oauth2/realms/root/connect/idpEndSession', + '/am/oauth2/realms/root/realms/middleware/connect/endSession', + '/am/oauth2/realms/root/realms/tokens-expiring-soon/connect/endSession', + '/am/oauth2/realms/root/realms/tokens-expired/connect/endSession', + ], + userInfo: [ + '/am/auth/userInfo', + '/am/oauth2/realms/root/userinfo', + '/am/oauth2/realms/root/realms/middleware/userinfo', + '/am/oauth2/realms/root/realms/tokens-expiring-soon/userinfo', + '/am/oauth2/realms/root/realms/tokens-expired/userinfo', + ], + revoke: [ + '/am/auth/revoke', + '/am/oauth2/realms/root/token/revoke', + '/am/oauth2/realms/root/realms/middleware/token/revoke', + '/am/oauth2/realms/root/realms/tokens-expiring-soon/token/revoke', + '/am/oauth2/realms/root/realms/tokens-expired/token/revoke', + ], + sessions: [ + '/am/auth/sessions', + '/am/json/realms/root/sessions', + '/am/json/realms/root/realms/middleware/sessions', + '/am/json/realms/root/realms/tokens-expiring-soon/sessions', + '/am/json/realms/root/realms/tokens-expired/sessions', + ], + accounts: ['/o/oauth2/v2/auth', '/SAMLFailure', '/SAMLTest'], +}; diff --git a/e2e/am-mock-api/src/app/env.config.js b/e2e/am-mock-api/src/app/env.config.js new file mode 100644 index 0000000000..e6bc01a1cc --- /dev/null +++ b/e2e/am-mock-api/src/app/env.config.js @@ -0,0 +1,71 @@ +/* + * @forgerock/javascript-sdk + * + * env.config.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +import { env } from 'process'; + +/** + * Configure your environment defaults below. + */ +const oauth = { + client: 'WebOAuthClient', + scope: 'openid profile me.read', +}; +const origins = { + // Ensure all domains are added to the security cert creation + app: process.env.NODE_ENV === 'LIVE' ? 'https://sdkapp.petrov.ca' : 'http://localhost', + forgeops: 'https://default.forgeops.petrov.ca', + mock: 'http://localhost', + resource: 'http://localhost', +}; +const paths = { + am: '/am', +}; +const ports = { + app: '8443', + forgeops: '443', + mock: '9443', + resource: '9443', +}; +const realm = 'root'; +const testUsers = [ + { + // Already exists in forgeops... + pw: 'password', + un: 'sdkuser', + }, +]; + +/** + * The below will be composed of the above values. + * Do not edit unless you know what you're doing. + */ +let amUrl; +let amPort; + +if (env.LIVE) { + amUrl = origins.forgeops; + amPort = ports.forgeops; +} else { + amUrl = origins.mock; + amPort = ports.mock; +} + +export const APP_PORT = ports.app; +export const AM_PORT = amPort; +export const MOCK_PORT = process.env.PORT || ports.mock; + +export const AM_URL = `${amUrl}:${amPort}${paths.am}`; +export const BASE_URL = `${origins.app}:${ports.app}`; +export const CLIENT_ID = oauth.client; +export const FORGEOPS = origins.forgeops; +export const REALM_PATH = realm; +export const RESOURCE_URL = `${origins.resource}:${ports.resource}`; +export const SCOPE = oauth.scope; +export const USERS = testUsers; diff --git a/e2e/am-mock-api/src/app/response.registration.js b/e2e/am-mock-api/src/app/response.registration.js new file mode 100644 index 0000000000..5271473711 --- /dev/null +++ b/e2e/am-mock-api/src/app/response.registration.js @@ -0,0 +1,224 @@ +/* + * @forgerock/javascript-sdk + * + * response.registration.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +export default { + authId: 'foo', + callbacks: [ + { + type: 'ValidatedCreateUsernameCallback', + output: [ + { + name: 'policies', + value: [ + { policyId: 'unique' }, + { policyId: 'no-internal-user-conflict' }, + { policyId: 'cannot-contain-characters', params: { forbiddenChars: ['/'] } }, + ], + }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'prompt', value: 'Username' }, + ], + input: [ + { name: 'IDToken1', value: '' }, + { name: 'IDToken1validateOnly', value: false }, + ], + _id: 0, + }, + { + type: 'StringAttributeInputCallback', + output: [ + { name: 'name', value: 'givenName' }, + { name: 'prompt', value: 'First Name' }, + { name: 'required', value: true }, + { name: 'policies', value: [] }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'value', value: '' }, + ], + input: [ + { name: 'IDToken2', value: '' }, + { name: 'IDToken2validateOnly', value: false }, + ], + _id: 1, + }, + { + type: 'StringAttributeInputCallback', + output: [ + { name: 'name', value: 'sn' }, + { name: 'prompt', value: 'Last Name' }, + { name: 'required', value: true }, + { name: 'policies', value: [] }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'value', value: '' }, + ], + input: [ + { name: 'IDToken3', value: '' }, + { name: 'IDToken3validateOnly', value: false }, + ], + _id: 2, + }, + // { + // type: 'NumberAttributeInputCallback', + // input: [ + // { name: 'IDToken4', value: null }, + // { name: 'IDToken4validateOnly', value: false }, + // ], + // output: [ + // { name: 'name', value: 'age' }, + // { name: 'prompt', value: 'Age' }, + // { name: 'required', value: true }, + // { + // name: 'policies', + // value: { + // policyRequirements: ['VALID_TYPE'], + // fallbackPolicies: null, + // name: 'age', + // policies: [ + // { + // policyRequirements: ['VALID_TYPE'], + // policyId: 'valid-type', + // params: { types: ['number'] }, + // }, + // ], + // conditionalPolicies: null, + // }, + // }, + // { name: 'failedPolicies', value: [] }, + // { name: 'validateOnly', value: false }, + // { name: 'value', value: null }, + // ], + // }, + { + type: 'StringAttributeInputCallback', + output: [ + { name: 'name', value: 'mail' }, + { name: 'prompt', value: 'Email Address' }, + { name: 'required', value: true }, + { name: 'policies', value: [{ policyId: 'valid-email-address-format' }] }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'value', value: '' }, + ], + input: [ + { name: 'IDToken4', value: '' }, + { name: 'IDToken4validateOnly', value: false }, + ], + _id: 3, + }, + { + type: 'BooleanAttributeInputCallback', + output: [ + { name: 'name', value: 'preferences/marketing' }, + { name: 'prompt', value: 'Send me special offers and services' }, + { name: 'required', value: true }, + { name: 'policies', value: [] }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'value', value: false }, + ], + input: [ + { name: 'IDToken5', value: false }, + { name: 'IDToken5validateOnly', value: false }, + ], + _id: 4, + }, + { + type: 'BooleanAttributeInputCallback', + output: [ + { name: 'name', value: 'preferences/updates' }, + { name: 'prompt', value: 'Send me news and updates' }, + { name: 'required', value: true }, + { name: 'policies', value: [] }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'value', value: false }, + ], + input: [ + { name: 'IDToken6', value: false }, + { name: 'IDToken6validateOnly', value: false }, + ], + _id: 5, + }, + { + type: 'ValidatedCreatePasswordCallback', + output: [ + { name: 'echoOn', value: false }, + { + name: 'policies', + value: [ + { policyId: 'at-least-X-capitals', params: { numCaps: 1 } }, + { policyId: 'at-least-X-numbers', params: { numNums: 1 } }, + { + policyId: 'cannot-contain-others', + params: { disallowedFields: ['userName', 'givenName', 'sn'] }, + }, + ], + }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'prompt', value: 'Password' }, + ], + input: [ + { name: 'IDToken7', value: '' }, + { name: 'IDToken7validateOnly', value: false }, + ], + _id: 6, + }, + { + type: 'KbaCreateCallback', + output: [ + { name: 'prompt', value: 'Select a security question' }, + { + name: 'predefinedQuestions', + value: [`What's your favorite color?`, 'Who was your first employer?'], + }, + ], + input: [ + { name: 'IDToken8question', value: '' }, + { name: 'IDToken8answer', value: '' }, + ], + _id: 7, + }, + { + type: 'KbaCreateCallback', + output: [ + { name: 'prompt', value: 'Select a security question' }, + { + name: 'predefinedQuestions', + value: [`What's your favorite color?`, 'Who was your first employer?'], + }, + ], + input: [ + { name: 'IDToken9question', value: '' }, + { name: 'IDToken9answer', value: '' }, + ], + _id: 8, + }, + { + type: 'TermsAndConditionsCallback', + output: [ + { name: 'version', value: '0.0' }, + { + name: 'terms', + // eslint-disable-next-line + value: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + { name: 'createDate', value: '2019-10-28T04:20:11.320Z' }, + ], + input: [{ name: 'IDToken10', value: false }], + _id: 9, + }, + ], + header: 'Sign Up', + description: 'Already have an account? Sign In', +}; diff --git a/e2e/am-mock-api/src/app/responses.js b/e2e/am-mock-api/src/app/responses.js new file mode 100644 index 0000000000..ccd1052feb --- /dev/null +++ b/e2e/am-mock-api/src/app/responses.js @@ -0,0 +1,1349 @@ +/* + * @forgerock/javascript-sdk + * + * responses.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +import { AM_URL, RESOURCE_URL } from './env.config.js'; + +export const oauthTokens = { + access_token: 'baz', + refresh_token: 'qux', + scope: 'openid profile me.read', + id_token: 'mox', + token_type: 'Bearer', + expires_in: 3598, +}; + +export const oauthTokensExpiringSoon = { + access_token: 'baz', + refresh_token: 'qux', + scope: 'openid profile me.read', + id_token: 'mox', + token_type: 'Bearer', + expires_in: 20, +}; + +export const oauthTokensExpired = { + access_token: 'baz', + refresh_token: 'qux', + scope: 'openid profile me.read', + id_token: 'mox', + token_type: 'Bearer', + expires_in: 1, +}; + +export const authFail = { + code: 401, + message: 'Authentication Failed For Given Credentials', +}; + +export const authSuccess = { + tokenId: 'bar', + successUrl: '/console', + realm: '/', +}; + +export const createTxnStepUpUrl = (url) => { + console.log(url); + // Grab the client's desired AM URL + const referer = new URL(url); + const amUrl = referer.searchParams.get('amUrl'); + // Create the redirect URL + const redirectUrl = new URL(amUrl || AM_URL); + redirectUrl.searchParams.set('goto', `${RESOURCE_URL}/ig`); + redirectUrl.searchParams.set('realm', '/'); + redirectUrl.searchParams.set('authIndexType', 'composite_advice'); + redirectUrl.searchParams.set( + 'authIndexValue', + // eslint-disable-next-line max-len + '%3CAdvices%3E%3CAttributeValuePair%3E%3CAttribute%20name%3D%22TransactionConditionAdvice%22/%3E%3CValue%3E39dfdd15-59a3-473c-a7fc-ecda3bbc3bc8%3C/Value%3E%3C/AttributeValuePair%3E%3C/Advices%3E', + ); + + return redirectUrl.toString(); +}; + +export const createTreeStepUpUrl = (url) => { + // Grab the client's desired AM URL + const referer = new URL(url); + const amUrl = referer.searchParams.get('amUrl'); + // Create the redirect URL + const redirectUrl = new URL(amUrl || AM_URL); + redirectUrl.searchParams.set('goto', `${RESOURCE_URL}/ig`); + redirectUrl.searchParams.set('realm', '/'); + redirectUrl.searchParams.set('authIndexType', 'composite_advice'); + redirectUrl.searchParams.set( + 'authIndexValue', + // eslint-disable-next-line max-len + '%3CAdvices%3E%3CAttributeValuePair%3E%3CAttribute%20name=%22AuthenticateToServiceConditionAdvice%22/%3E%3CValue%3E/sdk:ConfirmPassword%3C/Value%3E%3C/AttributeValuePair%3E%3C/Advices%3E', + ); + + return redirectUrl.toString(); +}; + +export const createTxnStepUpHeader = (url) => { + // Grab the client's desired AM URL + const referer = new URL(url); + const amUrl = referer.searchParams.get('amUrl') || AM_URL; + + // Base 64 of {"TransactionConditionAdvice":["39dfdd15-59a3-473c-a7fc-ecda3bbc3bc8"]} + const advices = + 'eyJUcmFuc2FjdGlvbkNvbmRpdGlvbkFkdmljZSI6WyIzOWRmZGQxNS01OWEzLTQ3M2MtYTdmYy1lY2RhM2JiYzNiYzgiXX0='; + const realm = '/'; + const headerValue = `SSOADVICE realm="${realm}",advices="${advices}",am_uri="${amUrl}"`; + return headerValue; +}; + +export const createTreeStepUpHeader = (url) => { + // Grab the client's desired AM URL + const referer = new URL(url); + const amUrl = referer.searchParams.get('amUrl') || AM_URL; + + // Base 64 of {"AuthenticateToServiceConditionAdvice":["/sdk:ConfirmPassword"]} + const advices = + 'eyJBdXRoZW50aWNhdGVUb1NlcnZpY2VDb25kaXRpb25BZHZpY2UiOlsiL3NkazpDb25maXJtUGFzc3dvcmQiXX0='; + const realm = '/'; + const headerValue = `SSOADVICE realm="${realm}",advices="${advices}",am_uri="${amUrl}"`; + return headerValue; +}; + +export const authByTreeResponse = { + resource: '', + actions: {}, + attributes: {}, + advices: { + AuthenticateToServiceConditionAdvice: ['/sdk:ConfirmPassword'], + }, + ttl: 0, +}; + +export const authByTxnResponse = { + resource: '', + actions: {}, + attributes: {}, + advices: { + TransactionConditionAdvice: ['39dfdd15-59a3-473c-a7fc-ecda3bbc3bc8'], + }, + ttl: 0, +}; + +export const emailSuspend = { + authId: 'foo', + callbacks: [ + { + type: 'SuspendedTextOutputCallback', + output: [ + { + name: 'message', + value: + // eslint-disable-next-line max-len + 'An email has been sent to the address you entered. Click the link in that email to proceed.', + }, + { name: 'messageType', value: '0' }, + ], + }, + ], +}; + +export const idpChoiceCallback = { + authId: 'foo', + callbacks: [ + { + type: 'ChoiceCallback', + output: [ + { name: 'prompt', value: 'Select Provider' }, + { name: 'choices', value: ['google', 'facebook'] }, + { name: 'defaultChoice', value: 0 }, + ], + input: [{ name: 'IDToken1', value: 0 }], + }, + ], +}; +export const nameCallback = { + authId: 'foo', + callbacks: [ + { + type: 'NameCallback', + output: [{ name: 'prompt', value: 'User Name' }], + input: [{ name: 'IDToken1', value: '' }], + _id: 0, + }, + ], +}; +export const textInputCallback = { + authId: 'foo', + callbacks: [ + { + type: 'TextInputCallback', + output: [ + { + name: 'prompt', + value: 'Provide a nickname for this account', + }, + ], + input: [ + { + name: 'IDToken1', + value: '', + }, + ], + }, + ], +}; + +export const initialBasicLogin = { + authId: 'foo', + callbacks: [ + { + type: 'NameCallback', + output: [{ name: 'prompt', value: 'User Name' }], + input: [{ name: 'IDToken1', value: '' }], + _id: 0, + }, + { + type: 'PasswordCallback', + output: [{ name: 'prompt', value: 'Password' }], + input: [{ name: 'IDToken2', value: '' }], + _id: 1, + }, + ], + stage: 'UsernamePassword', +}; + +export const initialLoginWithEmailResponse = { + authId: 'foo', + callbacks: [ + { + type: 'ValidatedCreateUsernameCallback', + output: [ + { name: 'policies', value: {} }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'prompt', value: 'Username' }, + ], + input: [ + { name: 'IDToken1', value: '' }, + { name: 'IDToken1validateOnly', value: false }, + ], + }, + ], +}; + +export const initialPlatformLogin = { + authId: 'foo', + callbacks: [ + { + type: 'ValidatedCreateUsernameCallback', + input: [ + { name: 'IDToken1', value: '' }, + { name: 'IDToken1validateOnly', value: false }, + ], + output: [ + { name: 'policies', value: [] }, + { name: 'failedPolicies', value: [] }, + { name: 'validateOnly', value: false }, + { name: 'prompt', value: 'Username' }, + ], + _id: 0, + }, + { + type: 'ValidatedCreatePasswordCallback', + output: [{ name: 'prompt', value: 'Password' }], + input: [ + { name: 'IDToken2', value: '' }, + { name: 'IDToken2validateOnly', value: false }, + ], + _id: 1, + }, + ], + stage: 'UsernamePassword', +}; + +export const initialMiscCallbacks = { + authId: 'foo', + callbacks: [ + { + type: 'NameCallback', + output: [{ name: 'prompt', value: 'User Name' }], + input: [{ name: 'IDToken1', value: '' }], + }, + ], +}; + +export const passwordCallback = { + authId: 'foo', + callbacks: [ + { + type: 'PasswordCallback', + output: [{ name: 'prompt', value: 'Password' }], + input: [{ name: 'IDToken1', value: '' }], + }, + ], +}; + +export const pingProtectEvaluate = { + authId: 'foo', + callbacks: [ + { + type: 'PingOneProtectEvaluationCallback', + output: [ + { + name: 'pauseBehavioralData', + value: true, + }, + ], + input: [ + { + name: 'IDToken1signals', + value: '', + }, + { + name: 'IDToken1clientError', + value: '', + }, + ], + }, + ], +}; + +export const pingProtectInitialize = { + authId: 'foo', + callbacks: [ + { + type: 'PingOneProtectInitializeCallback', + output: [ + { + name: 'envId', + value: '02fb1243-189a-4bc7-9d6c-a919edf6447', + }, + { + name: 'consoleLogEnabled', + value: true, + }, + { + name: 'deviceAttributesToIgnore', + value: ['userAgent'], + }, + { + name: 'customHost', + value: 'http://localhost', + }, + { + name: 'lazyMetadata', + value: false, + }, + { + name: 'behavioralDataCollection', + value: true, + }, + { + name: 'deviceKeyRsyncIntervals', + value: 14, + }, + { + name: 'enableTrust', + value: false, + }, + { + name: 'disableTags', + value: false, + }, + { + name: 'disableHub', + value: false, + }, + ], + input: [ + { + name: 'IDToken1clientError', + value: '', + }, + ], + }, + ], +}; + +export const choiceCallback = { + authId: 'foo', + callbacks: [ + { + type: 'ChoiceCallback', + output: [ + { name: 'prompt', value: 'Choose your color' }, + { name: 'choices', value: ['red', 'green', 'blue'] }, + { name: 'defaultChoice', value: 0 }, + ], + input: [{ name: 'IDToken1', value: 0 }], + }, + ], +}; + +export const secondFactorChoiceCallback = { + authId: 'foo', + callbacks: [ + { + type: 'ChoiceCallback', + output: [ + { name: 'prompt', value: 'Choose Second Factor' }, + { name: 'choices', value: ['Email', 'SMS'] }, + { name: 'defaultChoice', value: 0 }, + ], + input: [{ name: 'IDToken1', value: 0 }], + }, + ], +}; + +export const messageCallback = { + authId: 'foo', + callbacks: [ + { + type: 'TextOutputCallback', + output: [ + { name: 'message', value: 'Is it true?' }, + { name: 'messageType', value: '0' }, + ], + }, + { + type: 'ConfirmationCallback', + output: [ + { name: 'prompt', value: '' }, + { name: 'messageType', value: 0 }, + { name: 'options', value: ['Yes', 'No'] }, + { name: 'optionType', value: -1 }, + { name: 'defaultOption', value: 1 }, + ], + input: [{ name: 'IDToken2', value: 0 }], + }, + ], +}; + +export const noSessionSuccess = { successUrl: '/am/console', realm: '/' }; + +export const pollingCallback = { + authId: 'foo', + callbacks: [ + { + type: 'PollingWaitCallback', + output: [ + { name: 'waitTime', value: '1000' }, + { name: 'message', value: 'Waiting for response...' }, + ], + }, + ], +}; + +export const selectIdPCallback = { + authId: 'foo', + callbacks: [ + { + type: 'SelectIdPCallback', + output: [ + { + name: 'providers', + value: [ + { + provider: 'google', + uiConfig: { + buttonImage: 'images/g-logo.png', + buttonCustomStyle: 'background-color: #fff; color: #757575; border-color: #ddd;', + buttonClass: '', + buttonCustomStyleHover: + 'color: #6d6d6d; background-color: #eee; border-color: #ccc;', + buttonDisplayName: 'Google', + iconFontColor: 'white', + iconClass: 'fa-google', + iconBackground: '#4184f3', + }, + }, + { + provider: 'facebook', + uiConfig: { + buttonImage: '', + buttonCustomStyle: 'background-color: #3b5998;border-color: #3b5998; color: white;', + buttonClass: 'fa-facebook-official', + buttonCustomStyleHover: + 'background-color: #334b7d;border-color: #334b7d; color: white;', + buttonDisplayName: 'Facebook', + iconFontColor: 'white', + iconClass: 'fa-facebook', + iconBackground: '#3b5998', + }, + }, + { provider: 'localAuthentication' }, + ], + }, + { name: 'value', value: '' }, + ], + input: [{ name: 'IDToken1', value: '' }], + _id: 0, + }, + ], +}; + +export const secondFactorCallback = { + authId: 'foo', + callbacks: [ + { + type: 'PasswordCallback', + output: [{ name: 'prompt', value: 'One Time Password' }], + input: [{ name: 'IDToken1', value: '' }], + _id: 0, + }, + ], + stage: 'OneTimePasswordEmail', +}; + +export const otpQRCodeCallbacks = { + authId: 'foo', + callbacks: [ + { + type: 'TextOutputCallback', + output: [ + { + name: 'message', + value: + 'Scan the QR code image below with the ForgeRock Authenticator app to register your device with your login.', + }, + { + name: 'messageType', + value: '0', + }, + ], + }, + { + type: 'TextOutputCallback', + output: [ + { + name: 'message', + value: + // eslint-disable-next-line quotes + "window.QRCodeReader.createCode({\n id: 'callback_0',\n text: 'otpauth\\x3A\\x2F\\x2Ftotp\\x2FForgeRock\\x3Ajlowery\\x3Fperiod\\x3D30\\x26b\\x3D032b75\\x26digits\\x3D6\\x26secret\\QITSTC234FRIU8DD987DW3VPICFY\\x3D\\x3D\\x3D\\x3D\\x3D\\x3D\\x26issuer\\x3DForgeRock',\n version: '20',\n code: 'L'\n});", + }, + { + name: 'messageType', + value: '4', + }, + ], + }, + { + type: 'HiddenValueCallback', + output: [ + { + name: 'value', + value: + 'otpauth://totp/ForgeRock:jlowery?secret=QITSTC234FRIU8DD987DW3VPICFY======&issuer=ForgeRock&period=30&digits=6&b=032b75', + }, + { + name: 'id', + value: 'mfaDeviceRegistration', + }, + ], + input: [ + { + name: 'IDToken3', + value: 'mfaDeviceRegistration', + }, + ], + }, + { + type: 'ConfirmationCallback', + output: [ + { + name: 'prompt', + value: '', + }, + { + name: 'messageType', + value: 0, + }, + { + name: 'options', + value: ['Next'], + }, + { + name: 'optionType', + value: -1, + }, + { + name: 'defaultOption', + value: 0, + }, + ], + input: [ + { + name: 'IDToken4', + value: 0, + }, + ], + }, + ], +}; + +export const redirectCallback = { + authId: 'foo', + callbacks: [ + { + type: 'RedirectCallback', + output: [ + { + name: 'redirectUrl', + value: + // eslint-disable-next-line max-len + 'http://localhost:9443/o/oauth2/v2/auth?nonce=ko7fdf2v3b6yctgq35bdpndel0p9qiq&response_type=code&client_id=546064052569-ke17g9ufsmvda3kgg7s5kp2hpf3gnqi8.apps.googleusercontent.com&scope=openid%20profile%20email&code_challenge=Bh_6aMiI04KGI1wVILtEamByklmXnQY9JKhKhlwsIxk&code_challenge_method=S256&state=rtu8pz65dbg6baw985d532myfbbnf5v', + }, + { name: 'redirectMethod', value: 'GET' }, + { name: 'trackingCookie', value: true }, + ], + }, + ], +}; +export const redirectCallbackSaml = { + authId: 'foo', + callbacks: [ + { + type: 'RedirectCallback', + output: [ + { + name: 'redirectUrl', + value: + // eslint-disable-next-line max-len + 'http://localhost:9443/SAMLTest/', + }, + { name: 'redirectMethod', value: 'GET' }, + { name: 'trackingCookie', value: true }, + ], + }, + ], +}; +export const redirectCallbackFailureSaml = { + authId: 'foo', + callbacks: [ + { + type: 'RedirectCallback', + output: [ + { + name: 'redirectUrl', + value: + // eslint-disable-next-line max-len + 'http://localhost:9443/SAMLFailure', + }, + { name: 'redirectMethod', value: 'GET' }, + { name: 'trackingCookie', value: true }, + ], + }, + ], +}; +export const txnAuthz = { + authId: 'bar', + callbacks: [ + { + type: 'PasswordCallback', + output: [{ name: 'prompt', value: 'Password' }], + input: [{ name: 'IDToken2', value: '' }], + _id: 1, + }, + ], + stage: 'TransactionAuthorization', +}; +export const treeAuthz = { + authId: 'bar', + callbacks: [ + { + type: 'PasswordCallback', + output: [{ name: 'prompt', value: 'Password' }], + input: [{ name: 'IDToken2', value: '' }], + _id: 1, + }, + ], + stage: 'TreeBasedAuthorization', +}; + +export const userInfo = { + family_name: 'Tester', + given_name: 'Bob', + name: 'Bob Tester', + updated_at: 1575671644, + sub: 'thetester', +}; + +export const requestDeviceProfile = { + authId: 'foo', + callbacks: [ + { + type: 'DeviceProfileCallback', + output: [ + { + name: 'metadata', + value: true, + }, + { + name: 'location', + value: true, + }, + { + name: 'message', + value: 'Collecting profile ...', + }, + ], + input: [ + { + name: 'IDToken1', + value: '', + }, + ], + }, + ], +}; + +export const wellKnownForgeRock = { + request_parameter_supported: true, + pushed_authorization_request_endpoint: 'http://localhost:9443/am/oauth2/realms/root/par', + introspection_encryption_alg_values_supported: [ + 'ECDH-ES+A256KW', + 'ECDH-ES+A192KW', + 'RSA-OAEP', + 'ECDH-ES+A128KW', + 'RSA-OAEP-256', + 'A128KW', + 'A256KW', + 'ECDH-ES', + 'dir', + 'A192KW', + ], + claims_parameter_supported: false, + introspection_endpoint: 'http://localhost:9443/am/oauth2/realms/root/introspect', + issuer: 'http://localhost:9443/am/oauth2/realms/root', + id_token_encryption_enc_values_supported: [ + 'A256GCM', + 'A192GCM', + 'A128GCM', + 'A128CBC-HS256', + 'A192CBC-HS384', + 'A256CBC-HS512', + ], + userinfo_encryption_enc_values_supported: [ + 'A256GCM', + 'A192GCM', + 'A128GCM', + 'A128CBC-HS256', + 'A192CBC-HS384', + 'A256CBC-HS512', + ], + authorization_endpoint: 'http://localhost:9443/am/oauth2/realms/root/authorize', + authorization_encryption_alg_values_supported: [ + 'ECDH-ES+A256KW', + 'ECDH-ES+A192KW', + 'RSA-OAEP', + 'ECDH-ES+A128KW', + 'RSA-OAEP-256', + 'A128KW', + 'A256KW', + 'ECDH-ES', + 'dir', + 'A192KW', + ], + introspection_encryption_enc_values_supported: [ + 'A256GCM', + 'A192GCM', + 'A128GCM', + 'A128CBC-HS256', + 'A192CBC-HS384', + 'A256CBC-HS512', + ], + claims_supported: [], + rcs_request_signing_alg_values_supported: [ + 'PS384', + 'ES384', + 'RS384', + 'HS256', + 'HS512', + 'ES256', + 'RS256', + 'HS384', + 'ES512', + 'PS256', + 'PS512', + 'RS512', + ], + token_endpoint_auth_methods_supported: [ + 'client_secret_post', + 'private_key_jwt', + 'self_signed_tls_client_auth', + 'tls_client_auth', + 'none', + 'client_secret_basic', + ], + tls_client_certificate_bound_access_tokens: true, + response_modes_supported: [ + 'fragment.jwt', + 'form_post', + 'form_post.jwt', + 'jwt', + 'fragment', + 'query.jwt', + 'query', + ], + backchannel_logout_session_supported: true, + token_endpoint: 'http://localhost:9443/am/oauth2/realms/root/access_token', + response_types_supported: [ + 'code token id_token', + 'code', + 'code id_token', + 'device_code', + 'id_token', + 'code token', + 'token', + 'token id_token', + ], + authorization_encryption_enc_values_supported: [ + 'A256GCM', + 'A192GCM', + 'A128GCM', + 'A128CBC-HS256', + 'A192CBC-HS384', + 'A256CBC-HS512', + ], + revocation_endpoint_auth_methods_supported: [ + 'client_secret_post', + 'private_key_jwt', + 'self_signed_tls_client_auth', + 'tls_client_auth', + 'none', + 'client_secret_basic', + ], + request_uri_parameter_supported: true, + grant_types_supported: [ + 'implicit', + 'urn:ietf:params:oauth:grant-type:saml2-bearer', + 'refresh_token', + 'password', + 'client_credentials', + 'urn:ietf:params:oauth:grant-type:device_code', + 'authorization_code', + 'urn:openid:params:grant-type:ciba', + 'urn:ietf:params:oauth:grant-type:uma-ticket', + 'urn:ietf:params:oauth:grant-type:jwt-bearer', + ], + version: '3.0', + prompt_values_supported: ['none', 'login', 'consent'], + userinfo_endpoint: 'http://localhost:9443/am/oauth2/realms/root/userinfo', + require_request_uri_registration: true, + code_challenge_methods_supported: ['plain', 'S256'], + id_token_encryption_alg_values_supported: [ + 'ECDH-ES+A256KW', + 'ECDH-ES+A192KW', + 'RSA-OAEP', + 'ECDH-ES+A128KW', + 'RSA-OAEP-256', + 'A128KW', + 'A256KW', + 'ECDH-ES', + 'dir', + 'A192KW', + ], + authorization_signing_alg_values_supported: [ + 'PS384', + 'RS384', + 'EdDSA', + 'ES384', + 'HS256', + 'HS512', + 'ES256', + 'RS256', + 'HS384', + 'ES512', + 'PS256', + 'PS512', + 'RS512', + ], + request_object_signing_alg_values_supported: [ + 'PS384', + 'ES384', + 'RS384', + 'HS256', + 'HS512', + 'ES256', + 'RS256', + 'HS384', + 'ES512', + 'PS256', + 'PS512', + 'RS512', + ], + request_object_encryption_alg_values_supported: [ + 'ECDH-ES+A256KW', + 'ECDH-ES+A192KW', + 'ECDH-ES+A128KW', + 'RSA-OAEP', + 'RSA-OAEP-256', + 'A128KW', + 'A256KW', + 'ECDH-ES', + 'dir', + 'A192KW', + ], + rcs_response_signing_alg_values_supported: [ + 'PS384', + 'ES384', + 'RS384', + 'HS256', + 'HS512', + 'ES256', + 'RS256', + 'HS384', + 'ES512', + 'PS256', + 'PS512', + 'RS512', + ], + introspection_signing_alg_values_supported: [ + 'PS384', + 'RS384', + 'EdDSA', + 'ES384', + 'HS256', + 'HS512', + 'ES256', + 'RS256', + 'HS384', + 'ES512', + 'PS256', + 'PS512', + 'RS512', + ], + check_session_iframe: 'http://localhost:9443/am/oauth2/realms/root/connect/checkSession', + scopes_supported: [ + 'address', + 'phone', + 'openid', + 'profile', + 'fr:idm:*', + 'am-introspect-all-tokens', + 'email', + ], + backchannel_logout_supported: true, + acr_values_supported: [], + request_object_encryption_enc_values_supported: [ + 'A256GCM', + 'A192GCM', + 'A128GCM', + 'A128CBC-HS256', + 'A192CBC-HS384', + 'A256CBC-HS512', + ], + rcs_request_encryption_alg_values_supported: [ + 'ECDH-ES+A256KW', + 'ECDH-ES+A192KW', + 'RSA-OAEP', + 'ECDH-ES+A128KW', + 'RSA-OAEP-256', + 'A128KW', + 'A256KW', + 'ECDH-ES', + 'dir', + 'A192KW', + ], + userinfo_signing_alg_values_supported: [ + 'ES384', + 'HS256', + 'HS512', + 'ES256', + 'RS256', + 'HS384', + 'ES512', + ], + require_pushed_authorization_requests: false, + rcs_response_encryption_enc_values_supported: [ + 'A256GCM', + 'A192GCM', + 'A128GCM', + 'A128CBC-HS256', + 'A192CBC-HS384', + 'A256CBC-HS512', + ], + userinfo_encryption_alg_values_supported: [ + 'ECDH-ES+A256KW', + 'ECDH-ES+A192KW', + 'RSA-OAEP', + 'ECDH-ES+A128KW', + 'RSA-OAEP-256', + 'A128KW', + 'A256KW', + 'ECDH-ES', + 'dir', + 'A192KW', + ], + end_session_endpoint: 'http://localhost:9443/am/oauth2/realms/root/connect/endSession', + rcs_request_encryption_enc_values_supported: [ + 'A256GCM', + 'A192GCM', + 'A128GCM', + 'A128CBC-HS256', + 'A192CBC-HS384', + 'A256CBC-HS512', + ], + revocation_endpoint: 'http://localhost:9443/am/oauth2/realms/root/token/revoke', + rcs_response_encryption_alg_values_supported: [ + 'ECDH-ES+A256KW', + 'ECDH-ES+A192KW', + 'ECDH-ES+A128KW', + 'RSA-OAEP', + 'RSA-OAEP-256', + 'A128KW', + 'A256KW', + 'ECDH-ES', + 'dir', + 'A192KW', + ], + token_endpoint_auth_signing_alg_values_supported: [ + 'PS384', + 'ES384', + 'RS384', + 'HS256', + 'HS512', + 'ES256', + 'RS256', + 'HS384', + 'ES512', + 'PS256', + 'PS512', + 'RS512', + ], + jwks_uri: 'http://localhost:9443/am/oauth2/realms/root/connect/jwk_uri', + subject_types_supported: ['public', 'pairwise'], + id_token_signing_alg_values_supported: [ + 'PS384', + 'ES384', + 'RS384', + 'HS256', + 'HS512', + 'ES256', + 'RS256', + 'HS384', + 'ES512', + 'PS256', + 'PS512', + 'RS512', + ], + registration_endpoint: 'http://localhost:9443/am/oauth2/realms/root/register', +}; + +// NOT USED +export const wellKnownPing = { + issuer: 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as', + authorization_endpoint: + 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/authorize', + pushed_authorization_request_endpoint: + 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/par', + token_endpoint: 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/token', + userinfo_endpoint: + 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/userinfo', + jwks_uri: 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/jwks', + end_session_endpoint: + 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/signoff', + introspection_endpoint: + 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/introspect', + revocation_endpoint: + 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/revoke', + device_authorization_endpoint: + 'https://ping.example.com:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/device_authorization', + claims_parameter_supported: false, + request_parameter_supported: true, + request_uri_parameter_supported: false, + require_pushed_authorization_requests: false, + scopes_supported: ['openid', 'profile', 'email', 'address', 'phone'], + response_types_supported: [ + 'code', + 'id_token', + 'token id_token', + 'code id_token', + 'code token', + 'code token id_token', + ], + response_modes_supported: ['pi.flow', 'query', 'fragment', 'form_post'], + grant_types_supported: [ + 'authorization_code', + 'implicit', + 'client_credentials', + 'refresh_token', + 'urn:ietf:params:oauth:grant-type:device_code', + ], + subject_types_supported: ['public'], + id_token_signing_alg_values_supported: ['RS256'], + userinfo_signing_alg_values_supported: ['none'], + request_object_signing_alg_values_supported: [ + 'none', + 'HS256', + 'HS384', + 'HS512', + 'RS256', + 'RS384', + 'RS512', + ], + token_endpoint_auth_methods_supported: [ + 'client_secret_basic', + 'client_secret_post', + 'client_secret_jwt', + 'private_key_jwt', + ], + token_endpoint_auth_signing_alg_values_supported: [ + 'HS256', + 'HS384', + 'HS512', + 'RS256', + 'RS384', + 'RS512', + ], + claim_types_supported: ['normal'], + claims_supported: [ + 'sub', + 'iss', + 'auth_time', + 'acr', + 'name', + 'given_name', + 'family_name', + 'middle_name', + 'preferred_username', + 'profile', + 'picture', + 'zoneinfo', + 'phone_number', + 'updated_at', + 'address', + 'email', + 'locale', + ], + code_challenge_methods_supported: ['plain', 'S256'], +}; + +export const newPiWellKnown = { + issuer: 'http://localhost:9443/am', + authorization_endpoint: 'http://localhost:9443/am/oauth2/realms/root/authorize', + token_endpoint: 'http://localhost:9443/am/oauth2/realms/root/access_token', + userinfo_endpoint: 'http://localhost:9443/am/oauth2/realms/root/userinfo', + jwks_uri: 'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/jwks', + end_session_endpoint: 'http://localhost:9443/am/oauth2/realms/root/connect/endSession', + ping_end_idp_session_endpoint: + 'http://localhost:9443/am/oauth2/realms/root/connect/idpEndSession', + introspection_endpoint: 'http://localhost:9443/am/oauth2/realms/root/introspect', + revocation_endpoint: 'http://localhost:9443/am/oauth2/realms/root/token/revoke', + claims_parameter_supported: false, + request_parameter_supported: true, + request_uri_parameter_supported: false, + require_pushed_authorization_requests: false, + scopes_supported: ['openid', 'profile', 'email', 'address', 'phone', 'offline_access'], + response_types_supported: [ + 'code', + 'id_token', + 'token id_token', + 'code id_token', + 'code token', + 'code token id_token', + ], + response_modes_supported: ['pi.flow', 'query', 'fragment', 'form_post'], + grant_types_supported: [ + 'authorization_code', + 'implicit', + 'client_credentials', + 'refresh_token', + 'urn:ietf:params:oauth:grant-type:device_code', + ], + subject_types_supported: ['public'], + id_token_signing_alg_values_supported: ['RS256'], + userinfo_signing_alg_values_supported: ['none'], + request_object_signing_alg_values_supported: [ + 'none', + 'HS256', + 'HS384', + 'HS512', + 'RS256', + 'RS384', + 'RS512', + ], + token_endpoint_auth_methods_supported: [ + 'client_secret_basic', + 'client_secret_post', + 'client_secret_jwt', + 'private_key_jwt', + ], + token_endpoint_auth_signing_alg_values_supported: [ + 'HS256', + 'HS384', + 'HS512', + 'RS256', + 'RS384', + 'RS512', + ], + claim_types_supported: ['normal'], + claims_supported: [ + 'sub', + 'iss', + 'auth_time', + 'acr', + 'name', + 'given_name', + 'family_name', + 'middle_name', + 'preferred_username', + 'profile', + 'picture', + 'zoneinfo', + 'phone_number', + 'updated_at', + 'address', + 'email', + 'locale', + ], + code_challenge_methods_supported: ['plain', 'S256'], +}; + +export const MetadataMarketPlaceInitialize = { + authId: 'foo', + callbacks: [ + { + type: 'MetadataCallback', + output: [ + { + name: 'data', + value: { + _type: 'PingOneProtect', + _action: 'protect_initialize', + envId: 'some_id', + consoleLogEnabled: true, + deviceAttributesToIgnore: [], + customHost: '', + lazyMetadata: true, + behavioralDataCollection: true, + disableHub: true, + deviceKeyRsyncIntervals: 10, + enableTrust: true, + disableTags: true, + }, + }, + ], + }, + { + type: 'HiddenValueCallback', + output: [ + { + name: 'value', + value: '', + }, + { + name: 'id', + value: 'clientError', + }, + ], + input: [ + { + name: 'IDToken1', + value: '', + }, + ], + }, + ], +}; + +export const MetadataMarketPlacePingOneEvaluation = { + authId: 'foo', + callbacks: [ + { + type: 'MetadataCallback', + output: [ + { + name: 'data', + value: { + _type: 'PingOneProtect', + _action: 'protect_risk_evaluation', + envId: 'some_id', + pauseBehavioralData: true, + }, + }, + ], + }, + { + type: 'HiddenValueCallback', + output: [ + { + name: 'value', + value: '', + }, + { + name: 'id', + value: 'pingone_risk_evaluation_signals', + }, + ], + input: [ + { + name: 'IDToken1', + value: 'pingone_risk_evaluation_signals', + }, + ], + }, + { + type: 'HiddenValueCallback', + output: [ + { + name: 'value', + value: '', + }, + { + name: 'id', + value: 'clientError', + }, + ], + input: [ + { + name: 'IDToken1', + value: '', + }, + ], + }, + ], +}; + +export const recaptchaEnterpriseCallback = { + authId: 'foo', + callbacks: [ + { + type: 'ReCaptchaEnterpriseCallback', + output: [ + { + name: 'recaptchaSiteKey', + value: '6LdSu_spAAAAAKz3UhIy4JYQld2lm_WRt7dEhf9T', + }, + { + name: 'captchaApiUri', + value: 'https://www.google.com/recaptcha/enterprise.js', + }, + { + name: 'captchaDivClass', + value: 'g-recaptcha', + }, + ], + input: [ + { + name: 'IDToken1token', + value: '', + }, + { + name: 'IDToken1action', + value: '', + }, + { + name: 'IDToken1clientError', + value: '', + }, + { + name: 'IDToken1payload', + value: '', + }, + ], + }, + ], +}; diff --git a/e2e/am-mock-api/src/app/routes.auth.js b/e2e/am-mock-api/src/app/routes.auth.js new file mode 100644 index 0000000000..3d13208ea8 --- /dev/null +++ b/e2e/am-mock-api/src/app/routes.auth.js @@ -0,0 +1,644 @@ +/* + * @forgerock/javascript-sdk + * + * routes.auth.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +import { v4 } from 'uuid'; +import { authPaths } from './constants.js'; +import { AM_URL, USERS } from './env.config.js'; +import { + oauthTokens, + authFail, + authSuccess, + emailSuspend, + idpChoiceCallback, + initialBasicLogin, + initialLoginWithEmailResponse, + initialMiscCallbacks, + initialPlatformLogin, + passwordCallback, + choiceCallback, + messageCallback, + noSessionSuccess, + pollingCallback, + pingProtectEvaluate, + pingProtectInitialize, + redirectCallback, + redirectCallbackSaml, + requestDeviceProfile, + secondFactorCallback, + secondFactorChoiceCallback, + selectIdPCallback, + userInfo, + oauthTokensExpiringSoon, + oauthTokensExpired, + nameCallback, + redirectCallbackFailureSaml, + textInputCallback, + treeAuthz, + txnAuthz, + otpQRCodeCallbacks, + wellKnownForgeRock, + recaptchaEnterpriseCallback, + MetadataMarketPlaceInitialize, + MetadataMarketPlacePingOneEvaluation, + newPiWellKnown, +} from './responses.js'; +import initialRegResponse from './response.registration.js'; +import wait from './wait.js'; + +console.log(`Your user password from 'env.config' file: ${USERS[0].pw}`); + +export const baz = { + canWithdraw: false, +}; + +export default function (app) { + app.post(authPaths.authenticate, wait, async (req, res) => { + if (!req.body.callbacks) { + if (req.query.authIndexType === 'composite_advice') { + if (req.query.authIndexValue.includes('TransactionConditionAdvice')) { + res.json(txnAuthz); + } else if (req.query.authIndexValue.includes('AuthenticateToServiceConditionAdvice')) { + res.json(treeAuthz); + } + } else if (req.query.authIndexValue === 'MiscCallbacks') { + res.json(initialMiscCallbacks); + } else if (req.query.authIndexValue === 'PlatformUsernamePasswordTest') { + res.json(initialPlatformLogin); + } else if (req.query.authIndexValue === 'Registration') { + res.json(initialRegResponse); + } else if (req.query.authIndexValue === 'LoginWithEmail') { + if (typeof req.query.suspendedId === 'string' && req.query.suspendedId.length) { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } else { + res.json(initialLoginWithEmailResponse); + } + } else if ( + req.query.authIndexValue === 'SAMLTest' || + req.query.authIndexValue === 'SAMLFailure' + ) { + res.json(nameCallback); + } else if (req.query.authIndexValue === 'TEST_LoginPingProtect') { + res.json(pingProtectInitialize); + } else if (req.query.authIndexValue === 'IDMSocialLogin') { + res.json(selectIdPCallback); + } else if (req.query.authIndexValue === 'TEST_MetadataMarketPlace') { + res.json(MetadataMarketPlaceInitialize); + } else if (req.query.authIndexValue === 'AMSocialLogin') { + res.json(idpChoiceCallback); + } else if (req.query.authIndexValue === 'RecaptchaEnterprise') { + res.json(initialBasicLogin); + } else { + if (req.path.includes('middleware')) { + if ( + req.query['start-authenticate-middleware'] === 'start-authentication' && + req.headers['x-start-authenticate-middleware'] === 'start-authentication' && + !req.headers['x-logout-middleware'] && + !req.query['logout-middleware'] + ) { + res.json(initialBasicLogin); + } else { + res.status(406).send('Middleware additions are missing.'); + } + } else { + res.json(initialBasicLogin); + } + } + } else if (req.query.authIndexValue === 'LoginWithEmail') { + res.json(emailSuspend); + } else if (req.query.authIndexValue === 'RecaptchaEnterprise') { + console.log(req.body.callbacks); + if (req.body.callbacks[0].type === 'NameCallback') { + const [username, password] = req.body.callbacks; + if (username && username.type === 'NameCallback' && username.input[0].value === 'demo') { + if ( + password && + password.type === 'PasswordCallback' && + password.input[0].value === 'Password' + ) { + res.json(recaptchaEnterpriseCallback); + } + } + } else { + const [captcha] = req.body.callbacks; + if (captcha && captcha.input[0].value === '123') { + res.json(authSuccess); + } + } + } else if (req.query.authIndexValue === 'TEST_MetadataMarketPlace') { + if (req.body.callbacks.find((cb) => cb.type === 'MetadataCallback')) { + const metadataCb = req.body.callbacks.find((cb) => cb.type === 'MetadataCallback'); + const action = metadataCb.output[0].value._action; + console.log('the action', action); + if (action === 'protect_initialize') { + if (req.body.callbacks.find((cb) => cb.type === 'HiddenValueCallback')) { + const hiddenCb = req.body.callbacks.find((cb) => cb.type === 'HiddenValueCallback'); + if (hiddenCb.input[0].value === 'we had an error') { + return res.json(authFail); + } + return res.json(MetadataMarketPlacePingOneEvaluation); + } + } + if (action === 'protect_risk_evaluation') { + if (req.body.callbacks.find((cb) => cb.type === 'HiddenValueCallback')) { + const hiddenCb = req.body.callbacks.find((cb) => cb.type === 'HiddenValueCallback'); + if (hiddenCb.input[0].value === 'we had an error') { + return res.json(authFail); + } + return res.json(authSuccess); + } + } + } else { + if (req.body.callbacks.find((cb) => cb.type === 'PingOneEvaluationCallback')) { + const cb = req.body.callbacks.find((cb) => cb.type === 'PingOneEvaluationCallback'); + if (cb.input[0].value === 'the value to set') { + return res.json(authSuccess); + } else { + return res.json(authFail); + } + } + } + return res.json(MetadataMarketPlacePingOneEvaluation); + } else if (req.query.authIndexValue === 'QRCodeTest') { + // If QR Code callbacks are being returned, return success + if (req.body.callbacks.find((cb) => cb.type === 'HiddenValueCallback')) { + return res.json(authSuccess); + } + // Client is returning callbacks from username password, so return QR Code callbacks + res.json(otpQRCodeCallbacks); + } else if (req.query.authIndexValue === 'SAMLTestFailure') { + if (req.body.callbacks.find((cb) => cb.type === 'RedirectCallback')) { + if ( + req.query.error === 'true' && + req.query.errorCode === '401' && + req.body.errorMessage === 'errorSaml' + ) { + res.json(authFail); + } else { + res.json(authSuccess); + } + } else { + res.json(redirectCallbackFailureSaml); + } + } else if (req.query.authIndexValue === 'SAMLTest') { + if (req.body.callbacks.find((cb) => cb.type === 'RedirectCallback')) { + if ( + req.query.RelayState === 'https://forgerock.com' && + req.query.responsekey === '885cae87-f88b-4d75-a0fd-0ae1400b766f' && + req.body.authId === 'foo' + ) { + res.json(authSuccess); + } else { + res.json(authFail); + } + } else { + res.json(redirectCallbackSaml); + } + } else if (req.query.authIndexValue === 'MiscCallbacks') { + if (req.body.callbacks.find((cb) => cb.type === 'NameCallback')) { + const cb = req.body.callbacks.find((cb) => cb.type === 'NameCallback'); + if (cb.input[0].value !== USERS[0].un) { + res.json(authFail); + } else { + res.json(textInputCallback); + } + } else if (req.body.callbacks.find((cb) => cb.type === 'TextInputCallback')) { + const cb = req.body.callbacks.find((cb) => cb.type === 'TextInputCallback'); + if (cb.input[0].value !== 'Text Input String') { + res.json(authFail); + } else { + res.json(passwordCallback); + } + } else if (req.body.callbacks.find((cb) => cb.type === 'PasswordCallback')) { + const cb = req.body.callbacks.find((cb) => cb.type === 'PasswordCallback'); + if (cb.input[0].value !== USERS[0].pw) { + res.json(authFail); + } else { + res.json(choiceCallback); + } + } else if (req.body.callbacks.find((cb) => cb.type === 'ChoiceCallback')) { + const cb = req.body.callbacks.find((cb) => cb.type === 'ChoiceCallback'); + if (cb.input[0].value !== 1) { + res.json(authFail); + } else { + res.json(messageCallback); + } + } else if (req.body.callbacks.find((cb) => cb.type === 'ConfirmationCallback')) { + const cb = req.body.callbacks.find((cb) => cb.type === 'ConfirmationCallback'); + if (cb.input[0].value !== 0) { + res.json(authFail); + } else { + res.json(pollingCallback); + } + } else if (req.body.callbacks.find((cb) => cb.type === 'PollingCallback')) { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } else { + res.json(authFail); + } + } else if (req.query.authIndexValue === 'PlatformUsernamePasswordTest') { + const pwCb = req.body.callbacks.find((cb) => cb.type === 'ValidatedCreatePasswordCallback'); + // If validate only, return callbacks + if (pwCb.input[1].value) { + res.json(initialPlatformLogin); + } else if (pwCb.input[0].value !== USERS[0].pw) { + res.status(401).json(authFail); + } else { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } + } else if (req.query.authIndexValue === 'Registration') { + const un = req.body.callbacks.find((cb) => cb.type === 'ValidatedCreateUsernameCallback'); + const [fn, ln, em] = req.body.callbacks.filter( + (cb) => cb.type === 'StringAttributeInputCallback', + ); + // const age = req.body.callbacks.find((cb) => cb.type === 'NumberAttributeInputCallback'); + const [mktg, update] = req.body.callbacks.filter( + (cb) => cb.type === 'BooleanAttributeInputCallback', + ); + const pw = req.body.callbacks.find((cb) => cb.type === 'ValidatedCreatePasswordCallback'); + const [kba1, kba2] = req.body.callbacks.filter((cb) => cb.type === 'KbaCreateCallback'); + const terms = req.body.callbacks.find((cb) => cb.type === 'TermsAndConditionsCallback'); + if ( + un.input[0].value.length && + fn.input[0].value === 'Sally' && + ln.input[0].value === 'Tester' && + // age.input[0].value === 40 && + em.input[0].value.length && + mktg.input[0].value === false && + update.input[0].value === false && + pw.input[0].value === USERS[0].pw && + kba1.input[0].value === 'What is your favorite color?' && + kba1.input[1].value === 'Red' && + kba2.input[0].value === 'Who was your first employer?' && + kba2.input[1].value === 'AAA Engineering' && + terms.input[0].value === true + ) { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } else { + res.status(401).json(authFail); + } + } else if (req.query.authIndexValue === 'SecondFactor') { + if (req.body.callbacks.find((cb) => cb.type === 'NameCallback')) { + res.json(secondFactorChoiceCallback); + } else if (req.body.callbacks.find((cb) => cb.type === 'ChoiceCallback')) { + res.json(secondFactorCallback); + } else if (req.body.callbacks.find((cb) => cb.type === 'PasswordCallback')) { + const pwCb = req.body.callbacks.find((cb) => cb.type === 'PasswordCallback'); + if (pwCb.input[0].value !== 'abc123') { + res.status(401).json(authFail); + } else { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } + } + } else if (req.query.authIndexValue === 'IDMSocialLogin') { + if (req.body.callbacks.find((cb) => cb.type === 'SelectIdPCallback')) { + const idPCb = req.body.callbacks.find((cb) => cb.type === 'SelectIdPCallback'); + if (idPCb.input[0].value !== 'google') { + res.status(401).json(authFail); + } else { + res.json(redirectCallback); + } + } else if (req.body.callbacks.find((cb) => cb.type === 'RedirectCallback')) { + if (req.body.authId && req.query.code && req.query.state) { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } else { + res.status(401).json(authFail); + } + } + } else if (req.query.authIndexValue === 'AMSocialLogin') { + if (req.body.callbacks.find((cb) => cb.type === 'ChoiceCallback')) { + const idPCb = req.body.callbacks.find((cb) => cb.type === 'ChoiceCallback'); + if (idPCb.input[0].value !== 0) { + res.status(401).json(authFail); + } else { + res.json(redirectCallback); + } + } else if (req.body.callbacks.find((cb) => cb.type === 'RedirectCallback')) { + if (req.body.authId && req.query.code && req.query.state) { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } else { + res.status(401).json(authFail); + } + } + } else if (req.query.authIndexValue === 'TEST_LoginPingProtect') { + const protectInitCb = req.body.callbacks.find( + (cb) => cb.type === 'PingOneProtectInitializeCallback', + ); + const usernameCb = req.body.callbacks.find((cb) => cb.type === 'NameCallback'); + const protectEvalCb = req.body.callbacks.find( + (cb) => cb.type === 'PingOneProtectEvaluationCallback', + ); + if (protectInitCb) { + res.json(initialBasicLogin); + } else if (usernameCb && usernameCb.input[0].value) { + res.json(pingProtectEvaluate); + } else if (protectEvalCb && protectEvalCb.input[0].value) { + res.json(authSuccess); + } else { + res.status(401).json(authFail); + } + } else if (req.body.callbacks.find((cb) => cb.type === 'PasswordCallback')) { + const pwCb = req.body.callbacks.find((cb) => cb.type === 'PasswordCallback'); + if (pwCb.input[0].value !== USERS[0].pw) { + res.status(401).json(authFail); + } else { + if (req.query.authIndexValue === 'DeviceProfileCallbackTest') { + res.json(requestDeviceProfile); + } else { + if ( + req.body.stage === 'TransactionAuthorization' || + req.body.stage === 'TreeBasedAuthorization' + ) { + baz.canWithdraw = true; + } + if (req.path.includes('middleware')) { + if ( + req.query['authenticate-middleware'] === 'authentication' && + req.headers['x-authenticate-middleware'] === 'authentication' && + !req.headers['x-logout-middleware'] && + !req.query['logout-middleware'] + ) { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } else { + res.status(406).send('Middleware additions are missing.'); + } + } else { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(req.query.noSession === 'true' ? noSessionSuccess : authSuccess); + } + } + } + } else if (req.body.callbacks.find((cb) => cb.type === 'DeviceProfileCallback')) { + const deviceCb = req.body.callbacks.find((cb) => cb.type === 'DeviceProfileCallback') || {}; + const inputArr = deviceCb.input || []; + const input = inputArr[0] || {}; + const value = JSON.parse(input.value); + const location = value.location || {}; + const metadata = value.metadata || {}; + // location is not allowed in some browser automation + // const location = value.location || {}; + + // We just need property existence to ensure profile is generated + // We don't care about values since they are unique per browser + if ( + location && + location.latitude && + location.longitude && + metadata.browser && + metadata.browser.userAgent && + metadata.platform && + metadata.platform.deviceName && + metadata.platform.fonts && + metadata.platform.fonts.length > 0 && + metadata.platform.timezone && + value.identifier && + value.identifier.length > 0 + ) { + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain: 'localhost' }); + res.json(authSuccess); + } else { + // Just failing the auth for testing, but in reality, + // an additional auth callback would be sent, like OTP + res.json(authFail); + } + } + }); + + app.post(authPaths.tokenExchange, wait, async (req, res) => { + // eslint-disable-next-line + const access_token = v4(); + const refresh_token = v4(); + // eslint-disable-next-line + const tokens = { ...oauthTokens, access_token, refresh_token }; + + if (req.path.includes('middleware')) { + if ( + req.query['exchange-token-middleware'] === 'exchange-token' && + req.headers['x-exchange-token-middleware'] === 'exchange-token' && + !req.headers['x-logout-middleware'] && + !req.query['logout-middleware'] + ) { + res.json(tokens); + } else { + res.status(406).send('Middleware header is missing.'); + } + } else if (req.path.includes('tokens-expiring-soon')) { + const tokensExpiringSoon = { ...oauthTokensExpiringSoon, access_token, refresh_token }; + res.json(tokensExpiringSoon); + } else if (req.path.includes('tokens-expired')) { + const tokensExpired = { ...oauthTokensExpired, access_token, refresh_token }; + res.json(tokensExpired); + } else { + res.json(tokens); + } + }); + + app.get(authPaths.accounts, wait, async (req, res) => { + if (req.url.includes('SAMLFailure')) { + const referrer = new URL(req.get('Referer')); + const additionalQueryParams = 'error=true&errorCode=401&errorMessage=errorSaml'; + const redirectUrl = `${referrer.href}${ + referrer.href.includes('?') ? '&' : '?' + }${additionalQueryParams}`; + return res.redirect(redirectUrl); + } else if (req.url.includes('SAMLTest')) { + const referrer = new URL(req.get('Referer')); + const additionalQueryParams = + 'responsekey=885cae87-f88b-4d75-a0fd-0ae1400b766f&RelayState=https://forgerock.com'; + const redirectUrl = `${referrer.href}${ + referrer.href.includes('?') ? '&' : '?' + }${additionalQueryParams}`; + return res.redirect(redirectUrl); + } else { + const referrer = new URL(req.get('Referer')); + const additionalQueryParams = + // eslint-disable-next-line max-len + 'state=rtu8pz65dbg6baw985d532myfbbnf5v&code=4%2F0AY0e-g5vHGhzfggdAuIofxnblW-iR1Y30G5lN5RvbrU8Zv5ZmtUVheTzSX7YMJF_usbzUA&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&hd=forgerock.com&prompt=none'; + const redirectUrl = `${referrer.href}${ + referrer.href.includes('?') ? '&' : '?' + }${additionalQueryParams}`; + res.redirect(redirectUrl); + } + }); + + app.get(authPaths.authorize, wait, async (req, res) => { + const loginUrl = new URL(`${req.protocol}://${req.headers.host}/login`); + loginUrl.searchParams.set('client_id', req.query.client_id); + loginUrl.searchParams.set('acr_values', req.query.acr_values); + loginUrl.searchParams.set('redirect_uri', req.query.redirect_uri); + loginUrl.searchParams.set('state', req.query.state); + + // Detect if Central Login to enforce ACR value presence + if ( + req.query.client_id === 'CentralLoginOAuthClient' && + req.query.acr_values !== 'SpecificTree' + ) { + return res.status(400).json({ message: 'acr_values did not match "SpecificTree"' }); + } + if (req.path.includes('middleware-modern')) { + if ( + req.query['authorize-middleware'] === 'authorization' && + req.headers['x-authorize-middleware'] === 'authorization' && + !req.query['logout-middleware'] && + !req.headers['x-logout-middleware'] + ) { + res.redirect(loginUrl); + } else { + res.status(406).send('Middleware additions are missing.'); + } + } else if (req.path.includes('middleware')) { + if ( + req.query['authorize-middleware'] === 'authorization' && + !req.query['logout-middleware'] + ) { + res.redirect(loginUrl); + } else { + res.status(406).send('Middleware additions are missing.'); + } + } else { + if (req.cookies.iPlanetDirectoryPro) { + const redirectUrl = new URL(`${req.query.redirect_uri}`); + + console.log(`Request URL: ${req.query.client_id}`); + + redirectUrl.searchParams.set('client_id', req.query.client_id); + redirectUrl.searchParams.set('code', 'foo'); + redirectUrl.searchParams.set('iss', `${AM_URL}/oauth2`); + redirectUrl.searchParams.set('state', req.query.state); + + res.redirect(redirectUrl); + } else if ( + req.cookies.redirected === 'true' || + req.query['acr_values'] === 'skipBackgroundRequest' + ) { + res.redirect(loginUrl); + } else { + res.cookie('redirected', 'true'); + + const interactionNeeded = 'The request requires some interaction that is not allowed.'; + const redirectErrorUrl = new URL( + `${req.query.redirect_uri}?error_description=${interactionNeeded}`, + ); + + res.redirect(redirectErrorUrl); + } + } + }); + + app.get('/login', async (req, res) => { + const domain = req.url.includes('localhost') ? 'localhost' : 'example.com'; + + res.clearCookie('redirected'); + res.cookie('iPlanetDirectoryPro', 'abcd1234', { domain, sameSite: 'none', secure: true }); + + const url = new URL(`${req.protocol}://${req.headers.host}${authPaths.authorize[1]}`); + url.searchParams.set('client_id', req.query.client_id); + url.searchParams.set('acr_values', req.query.acr_values); + url.searchParams.set('redirect_uri', req.query.redirect_uri); + url.searchParams.set('state', req.query.state); + + res.redirect(url); + }); + + app.get(authPaths.userInfo, wait, async (req, res) => { + if (req.headers['authorization'] && req.path.includes('middleware')) { + if ( + req.query['userinfo-middleware'] === 'userinfo' && + req.headers['x-userinfo-middleware'] === 'userinfo' && + !req.headers['x-logout-middleware'] && + !req.query['logout-middleware'] + ) { + res.json(userInfo); + } else { + res.status(406).send('Middleware additions are missing.'); + } + } else if (req.headers['authorization']) { + res.json(userInfo); + } else { + res.status(401).send('Unauthorized'); + } + }); + + app.get(authPaths.endSession, wait, async (req, res) => { + if (req.path.includes('middleware')) { + if ( + req.query['end-session-middleware'] === 'end-session' && + req.headers['x-end-session-middleware'] === 'end-session' && + !req.headers['x-logout-middleware'] && + !req.query['logout-middleware'] + ) { + res.status(204).send(); + } else { + res.status(406).send('Middleware additions are missing missing.'); + } + } else { + res.status(204).send(); + } + }); + + app.post(authPaths.revoke, wait, async (req, res) => { + if (req.path.includes('middleware')) { + if ( + req.query['revoke-token-middleware'] === 'revoke-token' && + req.headers['x-revoke-token-middleware'] === 'revoke-token' && + !req.headers['x-logout-middleware'] && + !req.query['logout-middleware'] + ) { + res.status(200).send(); + } else { + res.status(406).send('Middleware header is missing.'); + } + } else { + res.status(200).send(); + } + }); + + app.all(authPaths.htmlAuthenticate, wait, async (req, res) => { + res.type('html'); + res.status(200).send(''); + }); + + app.post(authPaths.sessions, wait, async (req, res) => { + if (req.query._action === 'logout') { + if (req.path.includes('middleware')) { + if ( + req.query['logout-middleware'] === 'logout' && + req.headers['x-logout-middleware'] === 'logout' && + !req.headers['x-auth-middleware'] && + !req.query['auth-middleware'] + ) { + res.clearCookie('iPlanetDirectoryPro', { domain: 'localhost', path: '/' }); + res.status(204).send(); + } else { + res.status(406).send('Middleware header is missing.'); + } + } else { + res.clearCookie('iPlanetDirectoryPro', { domain: 'localhost', path: '/' }); + res.status(204).send(); + } + } + }); + + app.get('/callback', (req, res) => res.status(200).send('ok')); + + app.get('/am/.well-known/oidc-configuration', (req, res) => { + res.send(wellKnownForgeRock); + }); + + app.get('/as/.well-known/new-oidc-configuration', (req, res) => { + res.send(newPiWellKnown); + }); +} diff --git a/e2e/am-mock-api/src/app/routes.resource.js b/e2e/am-mock-api/src/app/routes.resource.js new file mode 100644 index 0000000000..d10dc278a3 --- /dev/null +++ b/e2e/am-mock-api/src/app/routes.resource.js @@ -0,0 +1,191 @@ +/* + * @forgerock/javascript-sdk + * + * routes.resource.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +import { env } from 'process'; +import request from 'superagent'; +import { session } from './app.auth.js'; +import { AM_URL, AM_PORT, FORGEOPS, REALM_PATH } from './env.config.js'; +import { + authByTreeResponse, + authByTxnResponse, + createTxnStepUpHeader, + createTreeStepUpHeader, + createTxnStepUpUrl, + createTreeStepUpUrl, +} from './responses.js'; +import { baz } from './routes.auth.js'; +import wait from './wait.js'; + +async function authorization(req, res, next) { + if (env.NODE_ENV === 'LIVE' && req.hostname !== FORGEOPS) { + const fullURL = `${req.protocol}://${req.host}:${AM_PORT}${req.url}`; + let realms; + const body = { + application: req.path.includes('authz-by-txn') ? 'TxnBasedPolicy' : 'TreeBasedPolicy', + resources: [fullURL], + subject: { + ssoToken: req.headers['x-idtoken'] || req.cookies.iPlanetDirectoryPro, + }, + }; + if (req.headers['x-txid']) { + body.environment = { + TxId: [req.headers['x-txid']], + }; + } + if (REALM_PATH !== 'root') { + realms = `realms/root/realms/${REALM_PATH}`; + } else { + realms = 'realms/root'; + } + const response = await request + // eslint-disable-next-line + .post(`${AM_URL}/json/${realms}/policies/?_action=evaluate`) + // .key(key) + // .cert(cert) + .set('Content-Type', 'application/json') + .set('Accept-API-Version', 'resource=2.1') + .set('iPlanetDirectoryPro', session.tokenId) + .send(body); + + req.access = response.body[0] || {}; + next(); + } else { + next(); + } +} + +export default function (app) { + // Passthrough route that enforces authentication + app.all('/resource/*', async (req, res, next) => { + if (env.NODE_ENV === 'LIVE' && req.hostname === FORGEOPS) { + // Only enforce authentication if IG is not used + // In other words, the call comes directly from app + let response; + if (req.headers.authorization) { + // Using OAuth + const authHeaderArr = req.headers.authorization.split(' '); + response = await request + .post(`${AM_URL}/oauth2/introspect`) + .set('Content-Type', 'application/json') + .set('iPlanetDirectoryPro', session.tokenId) + .set('Accept-API-Version', 'resource=1.2') + .send({ token: authHeaderArr[1] }); + } else { + // Using SSO + response = await request + .post(`${AM_URL}/json/sessions/?_action=validate`) + .set('Content-Type', 'application/json') + .set('iPlanetDirectoryPro', session.tokenId) + .set('Accept-API-Version', 'resource=2.1, protocol=1.0') + .send({ tokenId: req.cookies.iPlanetDirectoryPro }); + } + + if (response.body.active || response.body.valid) { + next(); + } else { + res.status(401).send(); + } + } else { + // Call came from a proxy, so proxy (e.g. IG) will enforce auth + next(); + } + }); + + app.get('/resource/reflect-authz-header', wait, authorization, async (req, res) => { + // Respond with the value of the authorization header to assist in testing http client + res.json({ message: req.headers['authorization'] }); + }); + + app.get('/resource/ig/authz-by-txn', wait, authorization, async (req, res) => { + if (req.hostname === FORGEOPS) { + // Calls are coming from IG, so Auth is already enforced + res.json({ message: 'Successfully retrieved resource!' }); + } else { + // Calls are coming directly from app, so let's mocks IG's behavior + if ( + req.cookies.iPlanetDirectoryPro === 'abcd1234' && + baz.canWithdraw && + req.query._txid === authByTxnResponse.advices.TransactionConditionAdvice[0] + ) { + baz.canWithdraw = false; + res.json({ message: 'Successfully retrieved resource!' }); + } else { + console.log(req.headers['x-authenticate-response']); + if ( + req.headers['x-authenticate-response'] && + req.headers['x-authenticate-response'] === 'header' && + req.headers.referer.includes('json') + ) { + res.setHeader('WWW-Authenticate', createTxnStepUpHeader(req.headers.referer)); + res.send(401, null); + } else { + res.redirect(307, createTxnStepUpUrl(req.headers.referer)); + } + } + } + }); + + app.get('/resource/ig/authz-by-tree', wait, authorization, async (req, res) => { + if (req.hostname === FORGEOPS) { + // Calls are coming from IG, so Auth is already enforced + res.json({ message: 'Successfully retrieved resource!' }); + } else { + // Calls are coming directly from app, so let's mocks IG's behavior + if (req.cookies.iPlanetDirectoryPro === 'abcd1234' && baz.canWithdraw) { + baz.canWithdraw = false; + res.json({ message: 'Successfully retrieved resource!' }); + } else { + if ( + req.headers['x-authenticate-response'] && + req.headers['x-authenticate-response'] === 'header' && + req.headers.referer.includes('json') + ) { + res.setHeader('WWW-Authenticate', createTreeStepUpHeader(req.headers.referer)); + res.send(401, null); + } else { + res.redirect(307, createTreeStepUpUrl(req.headers.referer)); + } + } + } + }); + + app.get('/resource/rest/*', wait, authorization, async (req, res) => { + if (env.NODE_ENV === 'live') { + if (req.access.actions && req.access.actions.GET) { + res.json({ message: 'Successfully retrieved resource!' }); + } else if ( + req.access.advices && + (req.access.advices.TransactionConditionAdvice || + req.access.advices.AuthenticateToServiceConditionAdvice) + ) { + res.json(req.access); + } else { + res.status(401).send(); + } + } else { + if ( + req.cookies.iPlanetDirectoryPro === 'abcd1234' && + baz.canWithdraw && + (req.headers['x-txid'] === authByTxnResponse.advices.TransactionConditionAdvice[0] || + req.headers['x-tree'] === + authByTreeResponse.advices.AuthenticateToServiceConditionAdvice[0]) + ) { + baz.canWithdraw = false; + res.json({ message: 'Successfully retrieved resource!' }); + } else { + if (req.path.includes('authz-by-txn')) { + res.json(authByTxnResponse); + } else if (req.path.includes('authz-by-tree')) { + res.json(authByTreeResponse); + } + } + } + }); +} diff --git a/e2e/am-mock-api/src/app/wait.js b/e2e/am-mock-api/src/app/wait.js new file mode 100644 index 0000000000..3738de1fab --- /dev/null +++ b/e2e/am-mock-api/src/app/wait.js @@ -0,0 +1,15 @@ +/* + * @forgerock/javascript-sdk + * + * wait.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +const delay = 0; + +export default function wait(req, res, next) { + setTimeout(() => next(), delay); +} diff --git a/e2e/am-mock-api/src/assets/.gitkeep b/e2e/am-mock-api/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/e2e/am-mock-api/src/environments/environment.prod.ts b/e2e/am-mock-api/src/environments/environment.prod.ts new file mode 100644 index 0000000000..c9669790be --- /dev/null +++ b/e2e/am-mock-api/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; diff --git a/e2e/am-mock-api/src/environments/environment.ts b/e2e/am-mock-api/src/environments/environment.ts new file mode 100644 index 0000000000..a24f6ba2ca --- /dev/null +++ b/e2e/am-mock-api/src/environments/environment.ts @@ -0,0 +1,9 @@ +export const environment = { + AM_URL: 'https://openam-crbrl-01.forgeblocks.com/am/', + REALM_PATH: 'alpha', + WEB_OAUTH_CLIENT: 'WebOAuthClient', + JOURNEY_LOGIN: 'UsernamelessWebAuthn', + JOURNEY_REGISTER: 'Registration', + API_URL: 'http://localhost:9443', + production: 'development', +}; diff --git a/e2e/am-mock-api/src/index.js b/e2e/am-mock-api/src/index.js new file mode 100644 index 0000000000..3162598341 --- /dev/null +++ b/e2e/am-mock-api/src/index.js @@ -0,0 +1,57 @@ +/* + * @forgerock/javascript-sdk + * + * index.js + * + * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +import * as dns from 'dns'; +import cors from 'cors'; +import express from 'express'; +import cookieParser from 'cookie-parser'; +import { createServer } from 'http'; +import { env } from 'process'; +import path from 'path'; +import { authorizeApp } from './app/app.auth.js'; +import { MOCK_PORT } from './app/env.config.js'; +import authRoutes from './app/routes.auth.js'; +import resourceRoutes from './app/routes.resource.js'; + +dns.setDefaultResultOrder('ipv4first'); + +const app = express(); + +app.use(express.json()); +app.use('/am/XUI/images', express.static(path.join(__dirname, 'public'))); +app.use(cookieParser()); +app.use( + cors({ + exposedHeaders: ['www-authenticate'], + credentials: true, + origin: function (origin, callback) { + return callback(null, true); + }, + }), +); +app.use((req, res, next) => { + console.log(`${req.method} ${req.path}`); + next(); +}); + +if (env.NODE_ENV === 'LIVE') { + authorizeApp({ + un: '9190fcce-d6d7-4473-9449-412f281f9bc6', + pw: '7fh9sj7*NP$%F6978', + }); +} + +authRoutes(app); +resourceRoutes(app); + +app.get('/healthcheck', (req, res) => res.status(200).send('ok')); + +env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0; +createServer(app).listen(MOCK_PORT); +console.log(`Listening to HTTP on secure port: ${MOCK_PORT}`); diff --git a/e2e/am-mock-api/tsconfig.app.json b/e2e/am-mock-api/tsconfig.app.json new file mode 100644 index 0000000000..a3b8102e3f --- /dev/null +++ b/e2e/am-mock-api/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "Node16", + "moduleResolution": "Node16", + "target": "ES6", + "allowJs": true, + "types": ["node", "express"] + }, + "exclude": ["**/*.spec.ts", "**/*.test.ts", "dist"], + "include": ["**/*.ts", "**/*.js"] +} diff --git a/e2e/am-mock-api/tsconfig.json b/e2e/am-mock-api/tsconfig.json new file mode 100644 index 0000000000..63dbe35fb2 --- /dev/null +++ b/e2e/am-mock-api/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/e2e/am-mock-api/tsconfig.spec.json b/e2e/am-mock-api/tsconfig.spec.json new file mode 100644 index 0000000000..c5b810b201 --- /dev/null +++ b/e2e/am-mock-api/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["node"] + }, + "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] +} diff --git a/e2e/davinci-suites/src/phone-number-field.test.ts b/e2e/davinci-suites/src/phone-number-field.test.ts index 77617e2b72..b4d9070caf 100644 --- a/e2e/davinci-suites/src/phone-number-field.test.ts +++ b/e2e/davinci-suites/src/phone-number-field.test.ts @@ -106,7 +106,9 @@ test.describe('Device registration tests', () => { await expect(page.getByText('SDK Automation - Enter Phone Number')).toBeVisible(); await page.getByRole('textbox', { name: 'Enter Phone Number' }).fill('3035550100'); await page.getByRole('button', { name: 'Submit' }).click(); - await expect(page.getByText('SMS/Voice MFA Registered')).toBeVisible(); + await expect( + async () => await expect(page.getByText('SMS/Voice MFA Registered')).toBeVisible(), + ).toPass(); await page.getByRole('button', { name: 'Continue' }).click(); }); }); diff --git a/e2e/journey-app/main.ts b/e2e/journey-app/main.ts index 30bb36b8d1..45ad9979ba 100644 --- a/e2e/journey-app/main.ts +++ b/e2e/journey-app/main.ts @@ -18,6 +18,7 @@ const searchParams = new URLSearchParams(qs); const config = serverConfigs[searchParams.get('clientId') || 'basic']; +const tree = searchParams.get('tree') ?? 'UsernamePassword'; let requestMiddleware: RequestMiddleware[] = []; if (searchParams.get('middleware') === 'true') { @@ -48,16 +49,13 @@ if (searchParams.get('middleware') === 'true') { } (async () => { - const journeyClient = await journey({ config, requestMiddleware }); + const journeyClient = await journey({ config: config, requestMiddleware }); const errorEl = document.getElementById('error') as HTMLDivElement; const formEl = document.getElementById('form') as HTMLFormElement; const journeyEl = document.getElementById('journey') as HTMLDivElement; - let step = await journeyClient.start({ - journey: searchParams.get('journey') || '', - query: { noSession: searchParams.get('no-session') || 'false' }, - }); + let step = await journeyClient.start({ journey: tree }); function renderComplete() { if (step?.type !== 'LoginSuccess') { @@ -81,7 +79,7 @@ if (searchParams.get('middleware') === 'true') { console.log('Logout successful'); - step = await journeyClient.start(); + step = await journeyClient.start({ journey: tree }); renderForm(); }); diff --git a/e2e/journey-app/server-configs.ts b/e2e/journey-app/server-configs.ts index c85ac80332..4bc08a1b1c 100644 --- a/e2e/journey-app/server-configs.ts +++ b/e2e/journey-app/server-configs.ts @@ -9,8 +9,14 @@ import type { JourneyClientConfig } from '@forgerock/journey-client/types'; export const serverConfigs: Record = { basic: { serverConfig: { - baseUrl: 'https://openam-sdks.forgeblocks.com/am/', + baseUrl: 'http://localhost:9443/am', }, - realmPath: '/alpha', + realmPath: 'root', + }, + tenant: { + serverConfig: { + baseUrl: 'https://openam-sdks.forgeblocks.com/am', + }, + realmPath: 'alpha', }, }; diff --git a/e2e/journey-suites/playwright.config.ts b/e2e/journey-suites/playwright.config.ts index bbdad2cf75..85229ca845 100644 --- a/e2e/journey-suites/playwright.config.ts +++ b/e2e/journey-suites/playwright.config.ts @@ -27,9 +27,6 @@ const config: PlaywrightTestConfig = { process.env.CI == 'false' ? { command: 'pnpm watch @forgerock/journey-app', - port: 5829, - ignoreHTTPSErrors: true, - reuseExistingServer: !process.env.CI, cwd: workspaceRoot, } : undefined, @@ -40,6 +37,13 @@ const config: PlaywrightTestConfig = { reuseExistingServer: !process.env.CI, cwd: workspaceRoot, }, + { + command: 'pnpm nx serve am-mock-api', + port: 9443, + ignoreHTTPSErrors: true, + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, ].filter(Boolean), }; diff --git a/e2e/journey-suites/src/choice-confirm-poll.test.ts b/e2e/journey-suites/src/choice-confirm-poll.test.ts index 24db060471..61034a3ca1 100644 --- a/e2e/journey-suites/src/choice-confirm-poll.test.ts +++ b/e2e/journey-suites/src/choice-confirm-poll.test.ts @@ -11,7 +11,7 @@ import { username, password } from './utils/demo-user.js'; test('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?journey=TEST_LoginWithMiscCallbacks'); + await navigate('/?tree=TEST_LoginWithMiscCallbacks&clientId=tenant'); const messageArray: string[] = []; @@ -21,7 +21,9 @@ test('Test happy paths on test page', async ({ page }) => { return Promise.resolve(true); }); - expect(page.url()).toBe('http://localhost:5829/?journey=TEST_LoginWithMiscCallbacks'); + expect(page.url()).toBe( + 'http://localhost:5829/?tree=TEST_LoginWithMiscCallbacks&clientId=tenant', + ); // Perform basic login await page.getByLabel('User Name').fill(username); diff --git a/e2e/journey-suites/src/custom-paths.test.ts b/e2e/journey-suites/src/custom-paths.test.ts index f848f8f1d2..5bdee9bf7b 100644 --- a/e2e/journey-suites/src/custom-paths.test.ts +++ b/e2e/journey-suites/src/custom-paths.test.ts @@ -12,7 +12,7 @@ import { username, password } from './utils/demo-user.js'; // Skipping test until AM Mock API is available that supports custom paths test.skip('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?paths=true&journey=Login'); + await navigate('/?paths=true&tree=Login&clientId=tenant'); const messageArray: string[] = []; diff --git a/e2e/journey-suites/src/device-profile.test.ts b/e2e/journey-suites/src/device-profile.test.ts index 65c8c348ee..2d44d7c309 100644 --- a/e2e/journey-suites/src/device-profile.test.ts +++ b/e2e/journey-suites/src/device-profile.test.ts @@ -11,7 +11,7 @@ import { username, password } from './utils/demo-user.js'; test.skip('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?journey=TEST_DeviceProfile'); + await navigate('/?tree=TEST_DeviceProfile'); const messageArray: string[] = []; diff --git a/e2e/journey-suites/src/email-suspend.test.ts b/e2e/journey-suites/src/email-suspend.test.ts index 900d61b0a7..e2519a29f9 100644 --- a/e2e/journey-suites/src/email-suspend.test.ts +++ b/e2e/journey-suites/src/email-suspend.test.ts @@ -11,7 +11,7 @@ import { username, password } from './utils/demo-user.js'; test('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?journey=TEST_LoginSuspendEmail'); + await navigate('/?tree=TEST_LoginSuspendEmail&clientId=tenant'); const messageArray: string[] = []; diff --git a/e2e/journey-suites/src/login.test.ts b/e2e/journey-suites/src/login.test.ts index f048e6de15..168c69eaf2 100644 --- a/e2e/journey-suites/src/login.test.ts +++ b/e2e/journey-suites/src/login.test.ts @@ -7,11 +7,11 @@ import { expect, test } from '@playwright/test'; import { asyncEvents } from './utils/async-events.js'; -import { username, password } from './utils/demo-user.js'; +import { password, username } from './utils/demo-user.js'; test('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?journey=Login'); + await navigate('/?tree=Login'); const messageArray: string[] = []; diff --git a/e2e/journey-suites/src/no-session.test.ts b/e2e/journey-suites/src/no-session.test.ts index 679e6e9cba..729f67cc70 100644 --- a/e2e/journey-suites/src/no-session.test.ts +++ b/e2e/journey-suites/src/no-session.test.ts @@ -11,7 +11,7 @@ import { username, password } from './utils/demo-user.js'; test('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?journey=Login&no-session=true'); + await navigate('/?tree=Login&no-session=true'); const messageArray: string[] = []; @@ -21,7 +21,7 @@ test('Test happy paths on test page', async ({ page }) => { return Promise.resolve(true); }); - expect(page.url()).toBe('http://localhost:5829/?journey=Login&no-session=true'); + expect(page.url()).toBe('http://localhost:5829/?tree=Login&no-session=true'); // Perform basic login await page.getByLabel('User Name').fill(username); diff --git a/e2e/journey-suites/src/otp-register.test.ts b/e2e/journey-suites/src/otp-register.test.ts index fc21076e02..ef934b3b4f 100644 --- a/e2e/journey-suites/src/otp-register.test.ts +++ b/e2e/journey-suites/src/otp-register.test.ts @@ -11,7 +11,7 @@ import { username, password } from './utils/demo-user.js'; test('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?journey=TEST_OTPRegistration'); + await navigate('/?tree=TEST_OTPRegistration&clientId=tenant'); const messageArray: string[] = []; diff --git a/e2e/journey-suites/src/protect.test.ts b/e2e/journey-suites/src/protect.test.ts index bb7eb3ee11..abb8b25d08 100644 --- a/e2e/journey-suites/src/protect.test.ts +++ b/e2e/journey-suites/src/protect.test.ts @@ -11,7 +11,7 @@ import { username, password } from './utils/demo-user.js'; test.skip('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?journey=TEST_Protect'); + await navigate('/?tree=TEST_Protect'); const messageArray: string[] = []; diff --git a/e2e/journey-suites/src/registration.test.ts b/e2e/journey-suites/src/registration.test.ts index 242ae04270..3d455e3bff 100644 --- a/e2e/journey-suites/src/registration.test.ts +++ b/e2e/journey-suites/src/registration.test.ts @@ -11,7 +11,7 @@ import { password } from './utils/demo-user.js'; test('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?journey=Registration'); + await navigate('/?tree=Registration&clientId=tenant'); // generate ID, 3 sections of random numbers: "714524572-2799534390-3707617532" const id = globalThis.crypto.getRandomValues(new Uint32Array(3)).join('-'); @@ -23,7 +23,7 @@ test('Test happy paths on test page', async ({ page }) => { return Promise.resolve(true); }); - expect(page.url()).toBe('http://localhost:5829/?journey=Registration'); + expect(page.url()).toBe('http://localhost:5829/?tree=Registration&clientId=tenant'); // Perform registration await page.getByLabel('Username').fill('testuser+' + id); diff --git a/e2e/journey-suites/src/request-middleware.test.ts b/e2e/journey-suites/src/request-middleware.test.ts index be0e269113..84d58d9465 100644 --- a/e2e/journey-suites/src/request-middleware.test.ts +++ b/e2e/journey-suites/src/request-middleware.test.ts @@ -11,7 +11,7 @@ import { username, password } from './utils/demo-user.js'; test.skip('Test happy paths on test page', async ({ page }) => { const { clickButton, navigate } = asyncEvents(page); - await navigate('/?middleware=true&journey=Login'); + await navigate('/?middleware=true&tree=Login'); const headerArray: Headers[] = []; const messageArray: string[] = []; @@ -33,7 +33,7 @@ test.skip('Test happy paths on test page', async ({ page }) => { headerArray.push(new Headers(headers)); }); - expect(page.url()).toBe('http://localhost:5829/?middleware=true&journey=Login'); + expect(page.url()).toBe('http://localhost:5829/?middleware=true&tree=Login'); // Perform basic login await page.getByLabel('User Name').fill(username); diff --git a/e2e/journey-suites/src/utils/demo-user.ts b/e2e/journey-suites/src/utils/demo-user.ts index b9e9fd45c0..406caf673b 100644 --- a/e2e/journey-suites/src/utils/demo-user.ts +++ b/e2e/journey-suites/src/utils/demo-user.ts @@ -4,7 +4,7 @@ * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ -export const username = 'demouser'; -export const password = 'U.QPDWEN47ZMyJhCDmhGLK*nr'; +export const username = 'sdkuser'; +export const password = 'password'; export const phoneNumber1 = '888123456'; export const phoneNumber2 = '888123457'; diff --git a/package.json b/package.json index 437aba8674..43559a1bc4 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "test": "CI=true nx affected:test", "test:e2e": "CI=true nx affected:e2e", "verdaccio": "nx local-registry", - "watch": "nx watch-deps" + "watch": "nx vite:watch-deps" }, "lint-staged": { "*": [ @@ -61,6 +61,7 @@ "@nx/devkit": "21.2.3", "@nx/eslint": "21.2.3", "@nx/eslint-plugin": "21.2.3", + "@nx/express": "21.2.3", "@nx/jest": "21.2.3", "@nx/js": "21.2.3", "@nx/playwright": "21.2.3", diff --git a/packages/journey-client/src/lib/client.store.test.ts b/packages/journey-client/src/lib/client.store.test.ts index ea7038c307..dfd8b3151d 100644 --- a/packages/journey-client/src/lib/client.store.test.ts +++ b/packages/journey-client/src/lib/client.store.test.ts @@ -238,6 +238,65 @@ describe('journey-client', () => { }); }); + describe('baseUrl normalization', () => { + test('should add trailing slash to baseUrl without one', async () => { + const configWithoutSlash: JourneyClientConfig = { + serverConfig: { + baseUrl: 'http://localhost:9443/am', + }, + realmPath: 'root', + }; + + const mockStepResponse: Step = { authId: 'test-auth-id', callbacks: [] }; + mockFetch.mockResolvedValue(new Response(JSON.stringify(mockStepResponse))); + + const client = await journey({ config: configWithoutSlash }); + await client.start(); + + expect(mockFetch).toHaveBeenCalledTimes(1); + const request = mockFetch.mock.calls[0][0] as Request; + expect(request.url).toBe('http://localhost:9443/am/json/realms/root/authenticate'); + }); + + test('should preserve trailing slash if already present', async () => { + const configWithSlash: JourneyClientConfig = { + serverConfig: { + baseUrl: 'http://localhost:9443/am/', + }, + realmPath: 'root', + }; + + const mockStepResponse: Step = { authId: 'test-auth-id', callbacks: [] }; + mockFetch.mockResolvedValue(new Response(JSON.stringify(mockStepResponse))); + + const client = await journey({ config: configWithSlash }); + await client.start(); + + expect(mockFetch).toHaveBeenCalledTimes(1); + const request = mockFetch.mock.calls[0][0] as Request; + expect(request.url).toBe('http://localhost:9443/am/json/realms/root/authenticate'); + }); + + test('should work with baseUrl without context path', async () => { + const configNoContext: JourneyClientConfig = { + serverConfig: { + baseUrl: 'http://localhost:9443', + }, + realmPath: 'root', + }; + + const mockStepResponse: Step = { authId: 'test-auth-id', callbacks: [] }; + mockFetch.mockResolvedValue(new Response(JSON.stringify(mockStepResponse))); + + const client = await journey({ config: configNoContext }); + await client.start(); + + expect(mockFetch).toHaveBeenCalledTimes(1); + const request = mockFetch.mock.calls[0][0] as Request; + expect(request.url).toBe('http://localhost:9443/json/realms/root/authenticate'); + }); + }); + // TODO: Add tests for endSession when the test environment AbortSignal issue is resolved // test('endSession() should call the sessions endpoint with DELETE method', async () => { // mockFetch.mockResolvedValue(new Response('', { status: 200 })); diff --git a/packages/journey-client/src/lib/client.store.ts b/packages/journey-client/src/lib/client.store.ts index 531675417a..9288cd0dc2 100644 --- a/packages/journey-client/src/lib/client.store.ts +++ b/packages/journey-client/src/lib/client.store.ts @@ -22,6 +22,26 @@ import type { JourneyClientConfig } from './config.types.js'; import type { RedirectCallback } from './callbacks/redirect-callback.js'; import { NextOptions, StartParam, ResumeOptions } from './interfaces.js'; +/** + * Normalizes the serverConfig to ensure baseUrl has a trailing slash. + * This is required for the resolve() function to work correctly with context paths like /am. + */ +function normalizeConfig(config: JourneyClientConfig): JourneyClientConfig { + if (config.serverConfig?.baseUrl) { + const url = config.serverConfig.baseUrl; + if (url.charAt(url.length - 1) !== '/') { + return { + ...config, + serverConfig: { + ...config.serverConfig, + baseUrl: url + '/', + }, + }; + } + } + return config; +} + export async function journey({ config, requestMiddleware, @@ -36,8 +56,11 @@ export async function journey({ }) { const log = loggerFn({ level: logger?.level || 'error', custom: logger?.custom }); - const store = createJourneyStore({ requestMiddleware, logger: log, config }); - store.dispatch(setConfig(config)); + // Normalize config to ensure baseUrl has trailing slash + const normalizedConfig = normalizeConfig(config); + + const store = createJourneyStore({ requestMiddleware, logger: log, config: normalizedConfig }); + store.dispatch(setConfig(normalizedConfig)); const stepStorage = createStorage<{ step: Step }>({ type: 'sessionStorage', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 010611e4cf..9ebd94f451 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,6 +79,9 @@ importers: '@nx/eslint-plugin': specifier: 21.2.3 version: 21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(typescript@5.8.3)(verdaccio@6.2.1(typanion@3.14.0)) + '@nx/express': + specifier: 21.2.3 + version: 21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.39.1(jiti@2.6.1))(express@4.21.2)(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(typescript@5.8.3))(typescript@5.8.3)(verdaccio@6.2.1(typanion@3.14.0)) '@nx/jest': specifier: 21.2.3 version: 21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(babel-plugin-macros@3.1.0)(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(typescript@5.8.3))(typescript@5.8.3)(verdaccio@6.2.1(typanion@3.14.0)) @@ -248,6 +251,30 @@ importers: specifier: ^0.3.3 version: 0.3.3(vitest@3.2.4) + e2e/am-mock-api: + dependencies: + '@types/express': + specifier: ^4.17.17 + version: 4.17.23 + body-parser: + specifier: ^2.2.0 + version: 2.2.0 + cookie-parser: + specifier: ^1.4.7 + version: 1.4.7 + cors: + specifier: ^2.8.5 + version: 2.8.5 + express: + specifier: ^4.21.2 + version: 4.21.2 + superagent: + specifier: ^10.2.3 + version: 10.2.3 + uuid: + specifier: ^13.0.0 + version: 13.0.0 + e2e/davinci-app: dependencies: '@forgerock/davinci-client': @@ -2177,6 +2204,10 @@ packages: '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2212,6 +2243,14 @@ packages: '@zkochan/js-yaml': optional: true + '@nx/express@21.2.3': + resolution: {integrity: sha512-XUHDpH8ilLUYkuHobm3UZbtkY+09AfjrlR5xfb/fDCimvjyPb/E8MvcYPya+jvUTkNQ5Z8PL51qG/2F5SnUDBw==} + peerDependencies: + express: ^4.21.2 + peerDependenciesMeta: + express: + optional: true + '@nx/jest@21.2.3': resolution: {integrity: sha512-lkH+tX8c1XSRjDa1g/lnYiC4zgs+8tZsj9yUVR2/1x+OO2SYDL8KVld6ZkWzXhRW8ZKXPHkDMWMUNBqsYlAWHA==} @@ -2223,6 +2262,9 @@ packages: verdaccio: optional: true + '@nx/node@21.2.3': + resolution: {integrity: sha512-5ivOTIYyXHwZSwpCR3AnKFCzjjzKHMfmVnMLQbiDhYB7nd9RJXsKsPAMdEVFCP/JBTPmQkufXElw/Kxfww7dnA==} + '@nx/nx-darwin-arm64@21.2.3': resolution: {integrity: sha512-5WgOjoX4vqG286A8abYoLCScA2ZF5af/2ZBjaM5EHypgbJLGQuMcP2ahzX66FYohT4wdAej1D0ILkEax71fAKw==} cpu: [arm64] @@ -2486,6 +2528,9 @@ packages: cpu: [x64] os: [win32] + '@paralleldrive/cuid2@2.2.2': + resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -2935,9 +2980,15 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@4.19.7': + resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==} + '@types/express-serve-static-core@5.1.0': resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/express@5.0.5': resolution: {integrity: sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==} @@ -3510,6 +3561,9 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -3681,6 +3735,10 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -3939,6 +3997,9 @@ packages: compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -3987,6 +4048,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-parser@1.4.7: + resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==} + engines: {node: '>= 0.8.0'} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -3994,10 +4059,17 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + core-js-compat@3.46.0: resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==} @@ -4112,6 +4184,15 @@ packages: supports-color: optional: true + debug@4.3.1: + resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -4291,6 +4372,9 @@ packages: peerDependencies: typescript: ^5.4.4 + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4729,6 +4813,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -4861,6 +4948,10 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -4968,6 +5059,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-them-args@1.3.2: + resolution: {integrity: sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==} + get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} @@ -5267,6 +5361,10 @@ packages: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} + ip-regex@4.3.0: + resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} + engines: {node: '>=8'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -5480,6 +5578,10 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} + is2@2.0.9: + resolution: {integrity: sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==} + engines: {node: '>=v0.10.0'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -5743,6 +5845,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kill-port@1.6.1: + resolution: {integrity: sha512-un0Y55cOM7JKGaLnGja28T38tDDop0AQ8N0KlAdyh+B1nmMoX8AnNmqPNZbS3mUMgiST51DCVqmbFT1gNJpVNw==} + hasBin: true + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -5946,6 +6052,10 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + meow@12.1.1: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} @@ -5983,6 +6093,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -6083,6 +6197,9 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -6678,6 +6795,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + raw-body@3.0.1: + resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==} + engines: {node: '>= 0.10'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -6953,6 +7074,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-exec@1.0.2: + resolution: {integrity: sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==} + shelljs@0.9.2: resolution: {integrity: sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==} engines: {node: '>=18'} @@ -7203,6 +7327,10 @@ packages: engines: {node: '>=18'} hasBin: true + superagent@10.2.3: + resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==} + engines: {node: '>=14.18.0'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -7243,6 +7371,9 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tcp-port-used@1.0.2: + resolution: {integrity: sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -7469,6 +7600,10 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -7624,6 +7759,10 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -9871,6 +10010,8 @@ snapshots: '@emnapi/runtime': 1.7.0 '@tybys/wasm-util': 0.9.0 + '@noble/hashes@1.8.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -9941,6 +10082,30 @@ snapshots: - supports-color - verdaccio + '@nx/express@21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.39.1(jiti@2.6.1))(express@4.21.2)(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(typescript@5.8.3))(typescript@5.8.3)(verdaccio@6.2.1(typanion@3.14.0))': + dependencies: + '@nx/devkit': 21.2.3(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))) + '@nx/js': 21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(verdaccio@6.2.1(typanion@3.14.0)) + '@nx/node': 21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.39.1(jiti@2.6.1))(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(typescript@5.8.3))(typescript@5.8.3)(verdaccio@6.2.1(typanion@3.14.0)) + tslib: 2.8.1 + optionalDependencies: + express: 4.21.2 + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - '@types/node' + - '@zkochan/js-yaml' + - babel-plugin-macros + - debug + - eslint + - node-notifier + - nx + - supports-color + - ts-node + - typescript + - verdaccio + '@nx/jest@21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(babel-plugin-macros@3.1.0)(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(typescript@5.8.3))(typescript@5.8.3)(verdaccio@6.2.1(typanion@3.14.0))': dependencies: '@jest/reporters': 29.7.0 @@ -10013,6 +10178,31 @@ snapshots: - nx - supports-color + '@nx/node@21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.39.1(jiti@2.6.1))(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(typescript@5.8.3))(typescript@5.8.3)(verdaccio@6.2.1(typanion@3.14.0))': + dependencies: + '@nx/devkit': 21.2.3(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))) + '@nx/eslint': 21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.39.1(jiti@2.6.1))(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(verdaccio@6.2.1(typanion@3.14.0)) + '@nx/jest': 21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(babel-plugin-macros@3.1.0)(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.21(@swc/helpers@0.5.17))(@types/node@24.9.2)(typescript@5.8.3))(typescript@5.8.3)(verdaccio@6.2.1(typanion@3.14.0)) + '@nx/js': 21.2.3(@babel/traverse@7.28.5)(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17))(nx@21.2.3(@swc-node/register@1.10.10(@swc/core@1.11.21(@swc/helpers@0.5.17))(@swc/types@0.1.25)(typescript@5.8.3))(@swc/core@1.11.21(@swc/helpers@0.5.17)))(verdaccio@6.2.1(typanion@3.14.0)) + kill-port: 1.6.1 + tcp-port-used: 1.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - '@types/node' + - '@zkochan/js-yaml' + - babel-plugin-macros + - debug + - eslint + - node-notifier + - nx + - supports-color + - ts-node + - typescript + - verdaccio + '@nx/nx-darwin-arm64@21.2.3': optional: true @@ -10321,6 +10511,10 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@5.3.0': optional: true + '@paralleldrive/cuid2@2.2.2': + dependencies: + '@noble/hashes': 1.8.0 + '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -10706,6 +10900,13 @@ snapshots: '@types/estree@1.0.8': {} + '@types/express-serve-static-core@4.19.7': + dependencies: + '@types/node': 24.9.2 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + '@types/express-serve-static-core@5.1.0': dependencies: '@types/node': 24.9.2 @@ -10713,6 +10914,13 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 1.2.1 + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.7 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.10 + '@types/express@5.0.5': dependencies: '@types/body-parser': 1.19.6 @@ -11577,6 +11785,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + asap@2.0.6: {} + asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -11786,6 +11996,20 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.1 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.1 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -12044,6 +12268,8 @@ snapshots: array-ify: 1.0.0 dot-prop: 5.3.0 + component-emitter@1.3.1: {} + compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -12095,12 +12321,21 @@ snapshots: convert-source-map@2.0.0: {} + cookie-parser@1.4.7: + dependencies: + cookie: 0.7.2 + cookie-signature: 1.0.6 + cookie-signature@1.0.6: {} cookie@0.7.1: {} + cookie@0.7.2: {} + cookie@1.0.2: {} + cookiejar@2.1.4: {} + core-js-compat@3.46.0: dependencies: browserslist: 4.27.0 @@ -12226,6 +12461,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.1: + dependencies: + ms: 2.1.2 + debug@4.4.1: dependencies: ms: 2.1.3 @@ -12377,6 +12616,11 @@ snapshots: transitivePeerDependencies: - supports-color + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + diff-sequences@29.6.3: {} diff@4.0.2: {} @@ -12969,6 +13213,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} fastq@1.19.1: @@ -13114,6 +13360,12 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.2.2 + dezalgo: 1.0.4 + once: 1.4.0 + forwarded@0.2.0: {} fresh@0.5.2: {} @@ -13219,6 +13471,8 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-them-args@1.3.2: {} + get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -13586,6 +13840,8 @@ snapshots: interpret@1.4.0: {} + ip-regex@4.3.0: {} + ipaddr.js@1.9.1: {} is-array-buffer@3.0.5: @@ -13762,6 +14018,12 @@ snapshots: dependencies: is-docker: 2.2.1 + is2@2.0.9: + dependencies: + deep-is: 0.1.4 + ip-regex: 4.3.0 + is-url: 1.2.4 + isarray@1.0.0: {} isarray@2.0.5: {} @@ -14233,6 +14495,11 @@ snapshots: dependencies: json-buffer: 3.0.1 + kill-port@1.6.1: + dependencies: + get-them-args: 1.3.2 + shell-exec: 1.0.2 + kind-of@6.0.3: {} leven@3.1.0: {} @@ -14436,6 +14703,8 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + meow@12.1.1: {} merge-descriptors@1.0.3: {} @@ -14461,6 +14730,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mime@2.6.0: {} @@ -14536,6 +14809,8 @@ snapshots: ms@2.0.0: {} + ms@2.1.2: {} + ms@2.1.3: {} msgpackr-extract@3.0.3: @@ -15172,6 +15447,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.1: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -15497,6 +15779,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-exec@1.0.2: {} + shelljs@0.9.2: dependencies: execa: 1.0.0 @@ -15778,6 +16062,20 @@ snapshots: dependencies: commander: 12.1.0 + superagent@10.2.3: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.1 + fast-safe-stringify: 2.1.1 + form-data: 4.0.4 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.14.0 + transitivePeerDependencies: + - supports-color + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -15823,6 +16121,13 @@ snapshots: - bare-abort-controller - react-native-b4a + tcp-port-used@1.0.2: + dependencies: + debug: 4.3.1 + is2: 2.0.9 + transitivePeerDependencies: + - supports-color + term-size@2.2.1: {} terser-webpack-plugin@5.3.14(@swc/core@1.11.21(@swc/helpers@0.5.17))(webpack@5.102.1(@swc/core@1.11.21(@swc/helpers@0.5.17))): @@ -16036,6 +16341,12 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -16180,6 +16491,8 @@ snapshots: uuid@11.1.0: {} + uuid@13.0.0: {} + uuid@8.3.2: {} v8-compile-cache-lib@3.0.1: {} diff --git a/tsconfig.json b/tsconfig.json index 0bd1c48eb3..92f092123e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -75,6 +75,9 @@ }, { "path": "./e2e/journey-suites" + }, + { + "path": "./e2e/am-mock-api" } ] }