From 8cb202eb290bd258049e3ddbe0c5ec50169c3192 Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Fri, 3 Feb 2023 11:45:11 +0100 Subject: [PATCH] Add JavaScript admin client to repository (#16697) * Add JavaScript admin client to repository * Apply review feedback Co-authored-by: Stian Thorgersen --------- Co-authored-by: Stian Thorgersen --- .github/actions/conditional/action.yml | 9 +- .github/actions/conditional/conditions | 5 +- .github/dependabot.yml | 24 + .github/workflows/js-ci.yml | 96 + js/.eslintrc.js | 96 + js/.gitignore | 38 + js/.husky/.gitignore | 1 + js/.husky/pre-commit | 4 + js/README.md | 14 + js/libs/keycloak-admin-client/.eslintignore | 1 + js/libs/keycloak-admin-client/.gitignore | 1 + js/libs/keycloak-admin-client/.mocharc.json | 5 + js/libs/keycloak-admin-client/LICENSE | 201 + js/libs/keycloak-admin-client/README.md | 455 ++ js/libs/keycloak-admin-client/package.json | 65 + js/libs/keycloak-admin-client/src/client.ts | 143 + .../src/defs/AccessTokenAccess.ts | 4 + .../src/defs/PermissonRepresentation.ts | 6 + .../src/defs/accessTokenCertConf.ts | 3 + .../src/defs/accessTokenRepresentation.ts | 50 + .../src/defs/addressClaimSet.ts | 8 + .../src/defs/adminEventRepresentation.ts | 12 + .../src/defs/authDetailsRepresentation.ts | 6 + ...enticationExecutionExportRepresentation.ts | 12 + ...thenticationExecutionInfoRepresentation.ts | 18 + .../defs/authenticationFlowRepresentation.ts | 14 + .../authenticatorConfigInfoRepresentation.ts | 19 + .../defs/authenticatorConfigRepresentation.ts | 16 + .../src/defs/certificateRepresentation.ts | 9 + .../defs/clientInitialAccessPresentation.ts | 11 + .../src/defs/clientPoliciesRepresentation.ts | 8 + .../clientPolicyConditionRepresentation.ts | 7 + .../clientPolicyExecutorRepresentation.ts | 7 + .../src/defs/clientPolicyRepresentation.ts | 12 + .../src/defs/clientProfileRepresentation.ts | 10 + .../src/defs/clientProfilesRepresentation.ts | 9 + .../src/defs/clientRepresentation.ts | 46 + .../src/defs/clientScopeRepresentation.ts | 13 + .../src/defs/componentExportRepresentation.ts | 12 + .../src/defs/componentRepresentation.ts | 13 + .../src/defs/componentTypeRepresentation.ts | 11 + .../src/defs/configPropertyRepresentation.ts | 12 + .../src/defs/credentialRepresentation.ts | 15 + .../defs/evaluationResultRepresentation.ts | 12 + .../src/defs/eventRepresentation.ts | 16 + .../src/defs/eventTypes.ts | 89 + .../defs/federatedIdentityRepresentation.ts | 9 + .../src/defs/globalRequestResult.ts | 7 + .../src/defs/groupRepresentation.ts | 16 + .../identityProviderMapperRepresentation.ts | 11 + ...dentityProviderMapperTypeRepresentation.ts | 9 + .../defs/identityProviderRepresentation.ts | 18 + .../src/defs/keyMetadataRepresentation.ts | 18 + .../src/defs/keystoreConfig.ts | 11 + .../src/defs/managementPermissionReference.ts | 5 + .../src/defs/mappingsRepresentation.ts | 9 + .../defs/passwordPolicyTypeRepresentation.ts | 10 + .../src/defs/policyEvaluationResponse.ts | 10 + .../src/defs/policyProviderRepresentation.ts | 5 + .../src/defs/policyRepresentation.ts | 40 + .../src/defs/policyResultRepresentation.ts | 9 + .../src/defs/profileInfoRepresentation.ts | 9 + .../src/defs/protocolMapperRepresentation.ts | 11 + .../defs/realmEventsConfigRepresentation.ts | 12 + .../src/defs/realmRepresentation.ts | 144 + .../requiredActionProviderRepresentation.ts | 21 + ...uiredActionProviderSimpleRepresentation.ts | 5 + .../src/defs/resourceEvaluation.ts | 14 + .../src/defs/resourceRepresentation.ts | 18 + .../src/defs/resourceServerRepresentation.ts | 44 + .../src/defs/roleRepresentation.ts | 27 + .../src/defs/rolesRepresentation.ts | 11 + .../src/defs/scopeRepresentation.ts | 14 + .../src/defs/serverInfoRepesentation.ts | 76 + .../synchronizationResultRepresentation.ts | 12 + .../src/defs/systemInfoRepersantation.ts | 24 + .../src/defs/testLdapConnection.ts | 15 + .../src/defs/userConsentRepresentation.ts | 10 + .../src/defs/userProfileConfig.ts | 42 + .../src/defs/userRepresentation.ts | 33 + .../src/defs/userSessionRepresentation.ts | 9 + .../src/defs/whoAmIRepresentation.ts | 29 + js/libs/keycloak-admin-client/src/index.ts | 7 + .../src/resources/agent.ts | 300 ++ .../src/resources/attackDetection.ts | 35 + .../src/resources/authenticationManagement.ts | 286 + .../src/resources/cache.ts | 19 + .../src/resources/clientPolicies.ts | 50 + .../src/resources/clientScopes.ts | 336 ++ .../src/resources/clients.ts | 1040 ++++ .../src/resources/components.ts | 72 + .../src/resources/groups.ts | 242 + .../src/resources/identityProviders.ts | 157 + .../src/resources/realms.ts | 402 ++ .../src/resources/resource.ts | 42 + .../src/resources/roles.ts | 178 + .../src/resources/serverInfo.ts | 17 + .../src/resources/sessions.ts | 18 + .../src/resources/userStorageProvider.ts | 60 + .../src/resources/users.ts | 497 ++ .../src/resources/whoAmI.ts | 20 + .../keycloak-admin-client/src/utils/auth.ts | 97 + .../src/utils/constants.ts | 3 + .../src/utils/fetchWithError.ts | 44 + .../src/utils/stringifyQueryParams.ts | 21 + .../test/attackDetection.spec.ts | 47 + .../keycloak-admin-client/test/auth.spec.ts | 47 + .../test/authenticationManagement.spec.ts | 415 ++ .../test/clientPolicies.spec.ts | 58 + .../test/clientRegistrationPolicies.ts | 20 + .../test/clientScopes.spec.ts | 663 +++ .../test/clients.spec.ts | 1308 +++++ .../test/components.spec.ts | 96 + .../keycloak-admin-client/test/constants.ts | 8 + .../test/crossRealm.spec.ts | 46 + .../test/groupUser.spec.ts | 206 + .../keycloak-admin-client/test/groups.spec.ts | 306 ++ .../keycloak-admin-client/test/idp.spec.ts | 184 + .../keycloak-admin-client/test/realms.spec.ts | 525 ++ .../keycloak-admin-client/test/roles.spec.ts | 227 + .../test/serverInfo.spec.ts | 20 + .../test/sessions.spec.ts | 22 + .../test/stringifyQueryParams.spec.ts | 31 + .../test/userStorageProvider.spec.ts | 54 + .../keycloak-admin-client/test/users.spec.ts | 682 +++ .../keycloak-admin-client/test/whoAmI.spec.ts | 21 + js/libs/keycloak-admin-client/tsconfig.json | 11 + .../keycloak-admin-client/tsconfig.test.json | 4 + js/package-lock.json | 4709 +++++++++++++++++ js/package.json | 31 + js/tsconfig.eslint.json | 8 + js/tsconfig.json | 20 + 132 files changed, 16113 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/js-ci.yml create mode 100644 js/.eslintrc.js create mode 100644 js/.gitignore create mode 100644 js/.husky/.gitignore create mode 100755 js/.husky/pre-commit create mode 100644 js/README.md create mode 100644 js/libs/keycloak-admin-client/.eslintignore create mode 100644 js/libs/keycloak-admin-client/.gitignore create mode 100644 js/libs/keycloak-admin-client/.mocharc.json create mode 100644 js/libs/keycloak-admin-client/LICENSE create mode 100644 js/libs/keycloak-admin-client/README.md create mode 100644 js/libs/keycloak-admin-client/package.json create mode 100644 js/libs/keycloak-admin-client/src/client.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/AccessTokenAccess.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/PermissonRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/accessTokenCertConf.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/accessTokenRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/addressClaimSet.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/adminEventRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/authDetailsRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/authenticationExecutionExportRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/authenticationExecutionInfoRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/authenticationFlowRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/authenticatorConfigInfoRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/authenticatorConfigRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/certificateRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientInitialAccessPresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientPoliciesRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientPolicyConditionRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientPolicyExecutorRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientPolicyRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientProfileRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientProfilesRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/clientScopeRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/componentExportRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/componentRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/componentTypeRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/configPropertyRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/credentialRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/evaluationResultRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/eventRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/eventTypes.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/federatedIdentityRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/globalRequestResult.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/groupRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/identityProviderMapperRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/identityProviderMapperTypeRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/identityProviderRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/keyMetadataRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/keystoreConfig.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/managementPermissionReference.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/mappingsRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/passwordPolicyTypeRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/policyEvaluationResponse.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/policyProviderRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/policyRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/policyResultRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/profileInfoRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/protocolMapperRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/realmEventsConfigRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/realmRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/requiredActionProviderRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/requiredActionProviderSimpleRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/resourceEvaluation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/resourceRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/resourceServerRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/roleRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/rolesRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/scopeRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/serverInfoRepesentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/synchronizationResultRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/systemInfoRepersantation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/testLdapConnection.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/userConsentRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/userProfileConfig.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/userRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/userSessionRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/defs/whoAmIRepresentation.ts create mode 100644 js/libs/keycloak-admin-client/src/index.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/agent.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/attackDetection.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/authenticationManagement.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/cache.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/clientPolicies.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/clientScopes.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/clients.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/components.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/groups.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/identityProviders.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/realms.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/resource.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/roles.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/serverInfo.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/sessions.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/userStorageProvider.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/users.ts create mode 100644 js/libs/keycloak-admin-client/src/resources/whoAmI.ts create mode 100644 js/libs/keycloak-admin-client/src/utils/auth.ts create mode 100644 js/libs/keycloak-admin-client/src/utils/constants.ts create mode 100644 js/libs/keycloak-admin-client/src/utils/fetchWithError.ts create mode 100644 js/libs/keycloak-admin-client/src/utils/stringifyQueryParams.ts create mode 100644 js/libs/keycloak-admin-client/test/attackDetection.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/auth.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/clientPolicies.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/clientRegistrationPolicies.ts create mode 100644 js/libs/keycloak-admin-client/test/clientScopes.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/clients.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/components.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/constants.ts create mode 100644 js/libs/keycloak-admin-client/test/crossRealm.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/groupUser.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/groups.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/idp.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/realms.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/roles.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/serverInfo.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/sessions.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/stringifyQueryParams.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/userStorageProvider.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/users.spec.ts create mode 100644 js/libs/keycloak-admin-client/test/whoAmI.spec.ts create mode 100644 js/libs/keycloak-admin-client/tsconfig.json create mode 100644 js/libs/keycloak-admin-client/tsconfig.test.json create mode 100644 js/package-lock.json create mode 100644 js/package.json create mode 100644 js/tsconfig.eslint.json create mode 100644 js/tsconfig.json diff --git a/.github/actions/conditional/action.yml b/.github/actions/conditional/action.yml index 810969ad84..9c7340edf1 100644 --- a/.github/actions/conditional/action.yml +++ b/.github/actions/conditional/action.yml @@ -5,6 +5,12 @@ outputs: ci: description: Should "ci.yml" execute value: ${{ steps.changes.outputs.ci }} + operator: + description: Should "operator-ci.yml" execute + value: ${{ steps.changes.outputs.operator }} + js: + description: Should "js-ci.yml" execute + value: ${{ steps.changes.outputs.js }} codeql-java: description: Should "codeql-analysis.yml / java" execute value: ${{ steps.changes.outputs.codeql-java }} @@ -14,9 +20,6 @@ outputs: codeql-js_adapter: description: Should "codeql-analysis.yml / js-adapter" execute value: ${{ steps.changes.outputs.codeql-js_adapter }} - operator: - description: Should "operator-ci.yml" execute - value: ${{ steps.changes.outputs.operator }} runs: using: composite diff --git a/.github/actions/conditional/conditions b/.github/actions/conditional/conditions index dfe664ecb8..d63a9c7aaa 100644 --- a/.github/actions/conditional/conditions +++ b/.github/actions/conditional/conditions @@ -2,16 +2,19 @@ # # To test a pattern run '.github/actions/conditional/conditional.sh ' -.github/actions/ ci operator codeql-java codeql-themes codeql-js_adapter +.github/actions/ ci operator js codeql-java codeql-themes codeql-js_adapter .github/workflows/ci.yml ci .github/workflows/operator-ci.yml operator +.github/workflows/js-ci.yml js .github/workflows/codeql-analysis.yml codeql-java codeql-themes codeql-js_adapter */src/main/ ci operator */src/test/ ci operator pom.xml ci operator +js/ js + *.java codeql-java themes/ codeql-themes adapters/oidc/js/ codeql-js_adapter diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b0107de317..2652bf93d2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,6 +20,30 @@ updates: labels: - area/dependencies - area/admin/ui + - team/ui ignore: - dependency-name: bootstrap update-types: ["version-update:semver-major"] + - package-ecosystem: npm + directory: js + open-pull-requests-limit: 999 + rebase-strategy: disabled + versioning-strategy: increase + schedule: + interval: weekly + labels: + - area/dependencies + - team/ui + ignore: + - dependency-name: react + update-types: ["version-update:semver-major"] + - dependency-name: react-dom + update-types: ["version-update:semver-major"] + - dependency-name: "@types/react" + update-types: ["version-update:semver-major"] + - dependency-name: "@types/react-dom" + update-types: ["version-update:semver-major"] + - dependency-name: vite + update-types: ["version-update:semver-major"] + - dependency-name: "@vitejs/plugin-react" + update-types: ["version-update:semver-major"] diff --git a/.github/workflows/js-ci.yml b/.github/workflows/js-ci.yml new file mode 100644 index 0000000000..a09bb9495a --- /dev/null +++ b/.github/workflows/js-ci.yml @@ -0,0 +1,96 @@ +name: Keycloak JavaScript CI + +on: + push: + branches-ignore: + - main + - dependabot/** + pull_request: + schedule: + - cron: 0 20,23,2,5 * * * + workflow_dispatch: + +env: + NODE_VERSION: 18 + +concurrency: + # Only cancel jobs for PR updates + group: js-ci-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + conditional: + name: Check conditional workflows and jobs + if: github.event_name != 'schedule' || github.repository == 'keycloak/keycloak' + runs-on: ubuntu-latest + outputs: + js-ci: ${{ steps.conditional.outputs.js }} + steps: + - uses: actions/checkout@v3 + + - id: conditional + uses: ./.github/actions/conditional + + npm-run: + needs: conditional + if: needs.conditional.outputs.js-ci == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + include: + # Keycloak Admin Client + - workspace: "@keycloak/keycloak-admin-client" + command: lint + - workspace: "@keycloak/keycloak-admin-client" + command: build + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + check-latest: true + cache: npm + cache-dependency-path: js/package-lock.json + + - name: Install dependencies + working-directory: js + run: npm ci + + - name: Run ${{ matrix.command }} task + working-directory: js + run: npm run ${{ matrix.command }} --workspace=${{ matrix.workspace }} + + check-set-status: + name: Set check conclusion + needs: + - npm-run + runs-on: ubuntu-latest + outputs: + conclusion: ${{ steps.check.outputs.conclusion }} + steps: + - uses: actions/checkout@v3 + + - id: check + uses: ./.github/actions/checks-success + + check: + name: Status Check - Keycloak JavaScript CI + if: always() && ( github.event_name != 'schedule' || github.repository == 'keycloak/keycloak' ) + needs: + - conditional + - check-set-status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Check status + uses: ./.github/actions/checks-job-pass + with: + required: ${{ needs.conditional.outputs.ci }} + conclusion: ${{ needs.check-set-status.outputs.conclusion }} diff --git a/js/.eslintrc.js b/js/.eslintrc.js new file mode 100644 index 0000000000..0a04243f2c --- /dev/null +++ b/js/.eslintrc.js @@ -0,0 +1,96 @@ +/** @type {import("eslint").Linter.Config } */ +module.exports = { + root: true, + ignorePatterns: [ + "node_modules", + "dist", + // Keycloak JS follows a completely different and outdated style, so we'll exclude it for now. + // TODO: Eventually align the code-style for Keycloak JS. + "libs/keycloak-js", + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: "./tsconfig.eslint.json", + extraFileExtensions: [".mjs"], + }, + env: { + node: true, + }, + plugins: ["lodash"], + extends: [ + "eslint:recommended", + "plugin:import/recommended", + "plugin:import/typescript", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:@typescript-eslint/base", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:prettier/recommended", + ], + settings: { + react: { + version: "detect", + }, + "import/resolver": { + typescript: true, + node: true, + }, + }, + rules: { + // Prefer using `includes()` to check if values exist over `indexOf() === -1`, as it's a more appropriate API for this. + "@typescript-eslint/prefer-includes": "error", + // Prefer using an optional chain expression, as it's more concise and easier to read. + "@typescript-eslint/prefer-optional-chain": "error", + "no-unused-vars": "off", + "@typescript-eslint/no-empty-function": "error", + "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/no-unused-vars": "error", + "lodash/import-scope": ["error", "member"], + // react/prop-types cannot handle generic props, so we need to disable it. + // https://github.com/yannickcr/eslint-plugin-react/issues/2777#issuecomment-814968432 + "react/prop-types": "off", + // Prevent fragments from being added that have only a single child. + "react/jsx-no-useless-fragment": "error", + "prefer-arrow-callback": "error", + "prettier/prettier": [ + "error", + { + endOfLine: "auto", + }, + ], + // Prevent default imports from React, named imports should be used instead. + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "react", + importNames: ["default"], + }, + ], + }, + ], + }, + overrides: [ + { + files: ["*.test.*"], + rules: { + // For tests it can make sense to pass empty functions as mocks. + "@typescript-eslint/no-empty-function": "off", + }, + }, + { + files: ["**/cypress/**/*"], + extends: ["plugin:cypress/recommended", "plugin:mocha/recommended"], + // TODO: Set these rules to "error" when issues have been resolved. + rules: { + "cypress/no-unnecessary-waiting": "warn", + "mocha/max-top-level-suites": "off", + "mocha/no-exclusive-tests": "error", + "mocha/no-identical-title": "off", + "mocha/no-mocha-arrows": "off", + "mocha/no-setup-in-describe": "off", + }, + }, + ], +}; diff --git a/js/.gitignore b/js/.gitignore new file mode 100644 index 0000000000..f6d2019bea --- /dev/null +++ b/js/.gitignore @@ -0,0 +1,38 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# NPM +node_modules + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Cypress +assets +**/cypress.env.json +**/cypress/downloads + +# Optional eslint cache +.eslintcache + +# Wireit +.wireit + +# Vite +dist +dist-ssr +*.local diff --git a/js/.husky/.gitignore b/js/.husky/.gitignore new file mode 100644 index 0000000000..31354ec138 --- /dev/null +++ b/js/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/js/.husky/pre-commit b/js/.husky/pre-commit new file mode 100755 index 0000000000..23aef7712b --- /dev/null +++ b/js/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" +cd js +npx lint-staged diff --git a/js/README.md b/js/README.md new file mode 100644 index 0000000000..441528dc18 --- /dev/null +++ b/js/README.md @@ -0,0 +1,14 @@ +# Keycloak JavaScript + +This directory contains an [NPM workspace](https://docs.npmjs.com/cli/v9/using-npm/workspaces) with JavaScript (and TypeScript) code related to the UIs and libraries of the Keycloak project. + +## Repository structure + + ├── apps + │ ├── account-ui # Account UI for account management i.e controlling password and account access, tracking and managing permissions + │ └── admin-ui # Admin UI for handling login, registration, administration, and account management + ├── libs + │ ├── keycloak-admin-client # Keycloak Admin Client library for Keycloak REST API + │ ├── keycloak-js # Keycloak JS library for securing HTML5/JavaScript applications + │ └── keycloak-masthead # Keycloak Masthead library for an easy way to bring applications into the Keycloak ecosystem, allow users to access + │ # and manage security for those applications and manage authorization of resources diff --git a/js/libs/keycloak-admin-client/.eslintignore b/js/libs/keycloak-admin-client/.eslintignore new file mode 100644 index 0000000000..7951405f85 --- /dev/null +++ b/js/libs/keycloak-admin-client/.eslintignore @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/js/libs/keycloak-admin-client/.gitignore b/js/libs/keycloak-admin-client/.gitignore new file mode 100644 index 0000000000..7951405f85 --- /dev/null +++ b/js/libs/keycloak-admin-client/.gitignore @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/js/libs/keycloak-admin-client/.mocharc.json b/js/libs/keycloak-admin-client/.mocharc.json new file mode 100644 index 0000000000..4e7c141a16 --- /dev/null +++ b/js/libs/keycloak-admin-client/.mocharc.json @@ -0,0 +1,5 @@ +{ + "node-option": [ + "loader=ts-node/esm" + ] +} diff --git a/js/libs/keycloak-admin-client/LICENSE b/js/libs/keycloak-admin-client/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/js/libs/keycloak-admin-client/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/js/libs/keycloak-admin-client/README.md b/js/libs/keycloak-admin-client/README.md new file mode 100644 index 0000000000..64daaf1f1d --- /dev/null +++ b/js/libs/keycloak-admin-client/README.md @@ -0,0 +1,455 @@ +## Keycloak Admin Client + +## Features + +- TypeScript supported +- Latest Keycloak version supported +- [Complete resource definitions](./src/defs) +- [Well-tested for supported APIs](./test) + +## Install + +```sh +npm install @keycloak/keycloak-admin-client +``` + +## Usage + +```js +import KcAdminClient from '@keycloak/keycloak-admin-client'; + +// To configure the client, pass an object to override any of these options: +// { +// baseUrl: 'http://127.0.0.1:8080', +// realmName: 'master', +// requestOptions: { +// /* Fetch request options https://developer.mozilla.org/en-US/docs/Web/API/fetch#options */ +// }, +// } +const kcAdminClient = new KcAdminClient(); + +// Authorize with username / password +await kcAdminClient.auth({ + username: 'admin', + password: 'admin', + grantType: 'password', + clientId: 'admin-cli', + totp: '123456', // optional Time-based One-time Password if OTP is required in authentication flow +}); + +// List all users +const users = await kcAdminClient.users.find(); + +// Override client configuration for all further requests: +kcAdminClient.setConfig({ + realmName: 'another-realm', +}); + +// This operation will now be performed in 'another-realm' if the user has access. +const groups = await kcAdminClient.groups.find(); + +// Set a `realm` property to override the realm for only a single operation. +// For example, creating a user in another realm: +await this.kcAdminClient.users.create({ + realm: 'a-third-realm', + username: 'username', + email: 'user@example.com', +}); +``` + +To refresh the access token provided by Keycloak, an OpenID client like [panva/node-openid-client](https://github.com/panva/node-openid-client) can be used like this: + +```js +import {Issuer} from 'openid-client'; + +const keycloakIssuer = await Issuer.discover( + 'http://localhost:8080/realms/master', +); + +const client = new keycloakIssuer.Client({ + client_id: 'admin-cli', // Same as `clientId` passed to client.auth() + token_endpoint_auth_method: 'none', // to send only client_id in the header +}); + +// Use the grant type 'password' +let tokenSet = await client.grant({ + grant_type: 'password', + username: 'admin', + password: 'admin', +}); + +// Periodically using refresh_token grant flow to get new access token here +setInterval(async () => { + const refreshToken = tokenSet.refresh_token; + tokenSet = await client.refresh(refreshToken); + kcAdminClient.setAccessToken(tokenSet.access_token); +}, 58 * 1000); // 58 seconds +``` + +In cases where you don't have a refresh token, eg. in a client credentials flow, you can simply call `kcAdminClient.auth` to get a new access token, like this: + +```js +const credentials = { + grantType: 'client_credentials', + clientId: 'clientId', + clientSecret: 'some-client-secret-uuid', +}; +await kcAdminClient.auth(credentials); + +setInterval(() => kcAdminClient.auth(credentials), 58 * 1000); // 58 seconds +``` + +## Building and running the tests + +To build the source do a build: + +```bash +npm run build +``` + +Start the Keycloak server: + +```bash +npm run server:start +``` + +If you started your container manually make sure there is an admin user named 'admin' with password 'admin'. +Then start the tests with: + +```bash +npm test +``` + +## Supported APIs + +### [Realm admin](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_realms_admin_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/realms.spec.ts + +- Import a realm from a full representation of that realm (`POST /`) +- Get the top-level representation of the realm (`GET /{realm}`) +- Update the top-level information of the realm (`PUT /{realm}`) +- Delete the realm (`DELETE /{realm}`) +- Partial export of existing realm into a JSON file (`POST /{realm}/partial-export`) +- Get users management permissions (`GET /{realm}/users-management-permissions`) +- Enable users management permissions (`PUT /{realm}/users-management-permissions`) +- Get events (`GET /{realm}/events`) +- Get admin events (`GET /{realm}/admin-events`) +- Remove all user sessions (`POST /{realm}/logout-all`) +- Remove a specific user session (`DELETE /{realm}/sessions/{session}`) +- Get client policies policies (`GET /{realm}/client-policies/policies`) +- Update client policies policies (`PUT /{realm}/client-policies/policies`) +- Get client policies profiles (`GET /{realm}/client-policies/profiles`) +- Update client policies profiles (`PUT /{realm}/client-policies/profiles`) +- Get a group by path (`GET /{realm}/group-by-path/{path}`) +### [Role](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_roles_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/roles.spec.ts + +- Create a new role for the realm (`POST /{realm}/roles`) +- Get all roles for the realm (`GET /{realm}/roles`) +- Get a role by name (`GET /{realm}/roles/{role-name}`) +- Update a role by name (`PUT /{realm}/roles/{role-name}`) +- Delete a role by name (`DELETE /{realm}/roles/{role-name}`) +- Get all users in a role by name for the realm (`GET /{realm}/roles/{role-name}/users`) + +### [Roles (by ID)](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_roles_by_id_resource) + +- Get a specific role (`GET /{realm}/roles-by-id/{role-id}`) +- Update the role (`PUT /{realm}/roles-by-id/{role-id}`) +- Delete the role (`DELETE /{realm}/roles-by-id/{role-id}`) +- Make the role a composite role by associating some child roles(`POST /{realm}/roles-by-id/{role-id}/composites`) +- Get role’s children Returns a set of role’s children provided the role is a composite. (`GET /{realm}/roles-by-id/{role-id}/composites`) +- Remove a set of roles from the role’s composite (`DELETE /{realm}/roles-by-id/{role-id}/composites`) +- Get client-level roles for the client that are in the role’s composite (`GET /{realm}/roles-by-id/{role-id}/composites/clients/{client}`) +- Get realm-level roles that are in the role’s composite (`GET /{realm}/roles-by-id/{role-id}/composites/realm`) + +### [User](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_users_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/users.spec.ts + +- Create a new user (`POST /{realm}/users`) +- Get users Returns a list of users, filtered according to query parameters (`GET /{realm}/users`) +- Get representation of the user (`GET /{realm}/users/{id}`) +- Update the user (`PUT /{realm}/users/{id}`) +- Delete the user (`DELETE /{realm}/users/{id}`) +- Count users (`GET /{realm}/users/count`) +- Send a update account email to the user An email contains a link the user can click to perform a set of required actions. (`PUT /{realm}/users/{id}/execute-actions-email`) +- Get user groups (`GET /{realm}/users/{id}/groups`) +- Add user to group (`PUT /{realm}/users/{id}/groups/{groupId}`) +- Delete user from group (`DELETE /{realm}/users/{id}/groups/{groupId}`) +- Remove TOTP from the user (`PUT /{realm}/users/{id}/remove-totp`) +- Set up a temporary password for the user User will have to reset the temporary password next time they log in. (`PUT /{realm}/users/{id}/reset-password`) +- Send an email-verification email to the user An email contains a link the user can click to verify their email address. (`PUT /{realm}/users/{id}/send-verify-email`) +- Update a credential label for a user (`PUT /{realm}/users/{id}/credentials/{credentialId}/userLabel`) +- Move a credential to a position behind another credential (`POST /{realm}/users/{id}/credentials/{credentialId}/moveAfter/{newPreviousCredentialId}`) +- Move a credential to a first position in the credentials list of the user (`PUT /{realm}/users/{id}/credentials/{credentialId}/moveToFirst`) + +### User group-mapping + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/users.spec.ts#L178 + +- Add user to group (`PUT /{id}/groups/{groupId}`) +- List all user groups (`GET /{id}/groups`) +- Count user groups (`GET /{id}/groups/count`) +- Remove user from group (`DELETE /{id}/groups/{groupId}`) + +### User role-mapping + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/users.spec.ts#L352 + +- Get user role-mappings (`GET /{realm}/users/{id}/role-mappings`) +- Add realm-level role mappings to the user (`POST /{realm}/users/{id}/role-mappings/realm`) +- Get realm-level role mappings (`GET /{realm}/users/{id}/role-mappings/realm`) +- Delete realm-level role mappings (`DELETE /{realm}/users/{id}/role-mappings/realm`) +- Get realm-level roles that can be mapped (`GET /{realm}/users/{id}/role-mappings/realm/available`) +- Get effective realm-level role mappings This will recurse all composite roles to get the result. (`GET /{realm}/users/{id}/role-mappings/realm/composite`) + +### [Group](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_groups_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/groups.spec.ts + +- Create (`POST /{realm}/groups`) +- List (`GET /{realm}/groups`) +- Get one (`GET /{realm}/groups/{id}`) +- Update (`PUT /{realm}/groups/{id}`) +- Delete (`DELETE /{realm}/groups/{id}`) +- Count (`GET /{realm}/groups/count`) +- List members (`GET /{realm}/groups/{id}/members`) +- Set or create child (`POST /{realm}/groups/{id}/children`) + +### Group role-mapping + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/groups.spec.ts#L103 + +- Get group role-mappings (`GET /{realm}/groups/{id}/role-mappings`) +- Add realm-level role mappings to the group (`POST /{realm}/groups/{id}/role-mappings/realm`) +- Get realm-level role mappings (`GET /{realm}/groups/{id}/role-mappings/realm`) +- Delete realm-level role mappings (`DELETE /{realm}/groups/{id}/role-mappings/realm`) +- Get realm-level roles that can be mapped (`GET /{realm}/groups/{id}/role-mappings/realm/available`) +- Get effective realm-level role mappings This will recurse all composite roles to get the result. (`GET /{realm}/groups/{id}/role-mappings/realm/composite`) + +### [Client](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_clients_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clients.spec.ts + +- Create a new client (`POST /{realm}/clients`) +- Get clients belonging to the realm (`GET /{realm}/clients`) +- Get representation of the client (`GET /{realm}/clients/{id}`) +- Update the client (`PUT /{realm}/clients/{id}`) +- Delete the client (`DELETE /{realm}/clients/{id}`) + +### [Client roles](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_roles_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clients.spec.ts + +- Create a new role for the client (`POST /{realm}/clients/{id}/roles`) +- Get all roles for the client (`GET /{realm}/clients/{id}/roles`) +- Get a role by name (`GET /{realm}/clients/{id}/roles/{role-name}`) +- Update a role by name (`PUT /{realm}/clients/{id}/roles/{role-name}`) +- Delete a role by name (`DELETE /{realm}/clients/{id}/roles/{role-name}`) + +### [Client role-mapping for group](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_client_role_mappings_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/groups.spec.ts#L192 + +- Add client-level roles to the group role mapping (`POST /{realm}/groups/{id}/role-mappings/clients/{client}`) +- Get client-level role mappings for the group (`GET /{realm}/groups/{id}/role-mappings/clients/{client}`) +- Delete client-level roles from group role mapping (`DELETE /{realm}/groups/{id}/role-mappings/clients/{client}`) +- Get available client-level roles that can be mapped to the group (`GET /{realm}/groups/{id}/role-mappings/clients/{client}/available`) +- Get effective client-level role mappings This will recurse all composite roles to get the result. (`GET /{realm}/groups/{id}/role-mappings/clients/{client}/composite`) + +### [Client role-mapping for user](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_client_role_mappings_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/users.spec.ts#L352 + +- Add client-level roles to the user role mapping (`POST /{realm}/users/{id}/role-mappings/clients/{client}`) +- Get client-level role mappings for the user (`GET /{realm}/users/{id}/role-mappings/clients/{client}`) +- Delete client-level roles from user role mapping (`DELETE /{realm}/users/{id}/role-mappings/clients/{client}`) +- Get available client-level roles that can be mapped to the user (`GET /{realm}/users/{id}/role-mappings/clients/{client}/available`) +- Get effective client-level role mappings This will recurse all composite roles to get the result. (`GET /{realm}/users/{id}/role-mappings/clients/{client}/composite`) + +### [Client Attribute Certificate](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_client_attribute_certificate_resource) + +- Get key info (`GET /{realm}/clients/{id}/certificates/{attr}`) +- Get a keystore file for the client, containing private key and public certificate (`POST /{realm}/clients/{id}/certificates/{attr}/download`) +- Generate a new certificate with new key pair (`POST /{realm}/clients/{id}/certificates/{attr}/generate`) +- Generate a new keypair and certificate, and get the private key file Generates a keypair and certificate and serves the private key in a specified keystore format. (`POST /{realm}/clients/{id}/certificates/{attr}/generate-and-download`) +- Upload certificate and eventually private key (`POST /{realm}/clients/{id}/certificates/{attr}/upload`) +- Upload only certificate, not private key (`POST /{realm}/clients/{id}/certificates/{attr}/upload-certificate`) + +### [Identity Providers](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_identity_providers_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/idp.spec.ts + +- Create a new identity provider (`POST /{realm}/identity-provider/instances`) +- Get identity providers (`GET /{realm}/identity-provider/instances`) +- Get the identity provider (`GET /{realm}/identity-provider/instances/{alias}`) +- Update the identity provider (`PUT /{realm}/identity-provider/instances/{alias}`) +- Delete the identity provider (`DELETE /{realm}/identity-provider/instances/{alias}`) +- Find identity provider factory (`GET /{realm}/identity-provider/providers/{providerId}`) +- Create a new identity provider mapper (`POST /{realm}/identity-provider/instances/{alias}/mappers`) +- Get identity provider mappers (`GET /{realm}/identity-provider/instances/{alias}/mappers`) +- Get the identity provider mapper (`GET /{realm}/identity-provider/instances/{alias}/mappers/{id}`) +- Update the identity provider mapper (`PUT /{realm}/identity-provider/instances/{alias}/mappers/{id}`) +- Delete the identity provider mapper (`DELETE /{realm}/identity-provider/instances/{alias}/mappers/{id}`) +- Find the identity provider mapper types (`GET /{realm}/identity-provider/instances/{alias}/mapper-types`) + +### [Client Scopes](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_client_scopes_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clientScopes.spec.ts + +- Create a new client scope (`POST /{realm}/client-scopes`) +- Get client scopes belonging to the realm (`GET /{realm}/client-scopes`) +- Get representation of the client scope (`GET /{realm}/client-scopes/{id}`) +- Update the client scope (`PUT /{realm}/client-scopes/{id}`) +- Delete the client scope (`DELETE /{realm}/client-scopes/{id}`) + +### [Client Scopes for realm](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_client_scopes_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clientScopes.spec.ts + +- Get realm default client scopes (`GET /{realm}/default-default-client-scopes`) +- Add realm default client scope (`PUT /{realm}/default-default-client-scopes/{id}`) +- Delete realm default client scope (`DELETE /{realm}/default-default-client-scopes/{id}`) +- Get realm optional client scopes (`GET /{realm}/default-optional-client-scopes`) +- Add realm optional client scope (`PUT /{realm}/default-optional-client-scopes/{id}`) +- Delete realm optional client scope (`DELETE /{realm}/default-optional-client-scopes/{id}`) + +### [Client Scopes for client](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_client_scopes_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clientScopes.spec.ts + +- Get default client scopes (`GET /{realm}/clients/{id}/default-client-scopes`) +- Add default client scope (`PUT /{realm}/clients/{id}/default-client-scopes/{clientScopeId}`) +- Delete default client scope (`DELETE /{realm}/clients/{id}/default-client-scopes/{clientScopeId}`) +- Get optional client scopes (`GET /{realm}/clients/{id}/optional-client-scopes`) +- Add optional client scope (`PUT /{realm}/clients/{id}/optional-client-scopes/{clientScopeId}`) +- Delete optional client scope (`DELETE /{realm}/clients/{id}/optional-client-scopes/{clientScopeId}`) + +### [Scope Mappings for client scopes](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_scope_mappings_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clientScopes.spec.ts + +- Get all scope mappings for the client (`GET /{realm}/client-scopes/{id}/scope-mappings`) +- Add client-level roles to the client’s scope (`POST /{realm}/client-scopes/{id}/scope-mappings/clients/{client}`) +- Get the roles associated with a client’s scope (`GET /{realm}/client-scopes/{id}/scope-mappings/clients/{client}`) +- The available client-level roles (`GET /{realm}/client-scopes/{id}/scope-mappings/clients/{client}/available`) +- Get effective client roles (`GET /{realm}/client-scopes/{id}/scope-mappings/clients/{client}/composite`) +- Remove client-level roles from the client’s scope. (`DELETE /{realm}/client-scopes/{id}/scope-mappings/clients/{client}`) +- Add a set of realm-level roles to the client’s scope (`POST /{realm}/client-scopes/{id}/scope-mappings/realm`) +- Get realm-level roles associated with the client’s scope (`GET /{realm}/client-scopes/{id}/scope-mappings/realm`) +- Remove a set of realm-level roles from the client’s scope (`DELETE /{realm}/client-scopes/{id}/scope-mappings/realm`) +- Get realm-level roles that are available to attach to this client’s scope (`GET /{realm}/client-scopes/{id}/scope-mappings/realm/available`) +- Get effective realm-level roles associated with the client’s scope (`GET /{realm}/client-scopes/{id}/scope-mappings/realm/composite`) + +### [Scope Mappings for clients](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_scope_mappings_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clientScopes.spec.ts + +- Get all scope mappings for the client (`GET /{realm}/clients/{id}/scope-mappings`) +- Add client-level roles to the client’s scope (`POST /{realm}/clients/{id}/scope-mappings/clients/{client}`) +- Get the roles associated with a client’s scope (`GET /{realm}/clients/{id}/scope-mappings/clients/{client}`) +- Remove client-level roles from the client’s scope. (`DELETE /{realm}/clients/{id}/scope-mappings/clients/{client}`) +- The available client-level roles (`GET /{realm}/clients/{id}/scope-mappings/clients/{client}/available`) +- Get effective client roles (`GET /{realm}/clients/{id}/scope-mappings/clients/{client}/composite`) +- Add a set of realm-level roles to the client’s scope (`POST /{realm}/clients/{id}/scope-mappings/realm`) +- Get realm-level roles associated with the client’s scope (`GET /{realm}/clients/{id}/scope-mappings/realm`) +- Remove a set of realm-level roles from the client’s scope (`DELETE /{realm}/clients/{id}/scope-mappings/realm`) +- Get realm-level roles that are available to attach to this client’s scope (`GET /{realm}/clients/{id}/scope-mappings/realm/available`) +- Get effective realm-level roles associated with the client’s scope (`GET /{realm}/clients/{id}/scope-mappings/realm/composite`) + +### [Protocol Mappers for client scopes](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_protocol_mappers_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clientScopes.spec.ts + +- Create multiple mappers (`POST /{realm}/client-scopes/{id}/protocol-mappers/add-models`) +- Create a mapper (`POST /{realm}/client-scopes/{id}/protocol-mappers/models`) +- Get mappers (`GET /{realm}/client-scopes/{id}/protocol-mappers/models`) +- Get mapper by id (`GET /{realm}/client-scopes/{id}/protocol-mappers/models/{mapperId}`) +- Update the mapper (`PUT /{realm}/client-scopes/{id}/protocol-mappers/models/{mapperId}`) +- Delete the mapper (`DELETE /{realm}/client-scopes/{id}/protocol-mappers/models/{mapperId}`) +- Get mappers by name for a specific protocol (`GET /{realm}/client-scopes/{id}/protocol-mappers/protocol/{protocol}`) + +### [Protocol Mappers for clients](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_protocol_mappers_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clients.spec.ts + +- Create multiple mappers (`POST /{realm}/clients/{id}/protocol-mappers/add-models`) +- Create a mapper (`POST /{realm}/clients/{id}/protocol-mappers/models`) +- Get mappers (`GET /{realm}/clients/{id}/protocol-mappers/models`) +- Get mapper by id (`GET /{realm}/clients/{id}/protocol-mappers/models/{mapperId}`) +- Update the mapper (`PUT /{realm}/clients/{id}/protocol-mappers/models/{mapperId}`) +- Delete the mapper (`DELETE /{realm}/clients/{id}/protocol-mappers/models/{mapperId}`) +- Get mappers by name for a specific protocol (`GET /{realm}/clients/{id}/protocol-mappers/protocol/{protocol}`) + +### [Component]() + +Supported for [user federation](https://www.keycloak.org/docs/latest/server_admin/index.html#_user-storage-federation). Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/components.spec.ts + +- Create (`POST /{realm}/components`) +- List (`GET /{realm}/components`) +- Get (`GET /{realm}/components/{id}`) +- Update (`PUT /{realm}/components/{id}`) +- Delete (`DELETE /{realm}/components/{id}`) + +### [Sessions for clients]() + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clients.spec.ts + +- List user sessions for a specific client (`GET /{realm}/clients/{id}/user-sessions`) +- List offline sessions for a specific client (`GET /{realm}/clients/{id}/offline-sessions`) +- Get user session count for a specific client (`GET /{realm}/clients/{id}/session-count`) +- List offline session count for a specific client (`GET /{realm}/clients/{id}/offline-session-count`) + +### [Authentication Management: Required actions](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_authentication_management_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts + +- Register a new required action (`POST /{realm}/authentication/register-required-action`) +- Get required actions. Returns a list of required actions. (`GET /{realm}/authentication/required-actions`) +- Get required action for alias (`GET /{realm}/authentication/required-actions/{alias}`) +- Update required action (`PUT /{realm}/authentication/required-actions/{alias}`) +- Delete required action (`DELETE /{realm}/authentication/required-actions/{alias}`) +- Lower required action’s priority (`POST /{realm}/authentication/required-actions/{alias}/lower-priority`) +- Raise required action’s priority (`POST /{realm}/authentication/required-actions/{alias}/raise-priority`) +- Get unregistered required actions Returns a list of unregistered required actions. (`GET /{realm}/authentication/unregistered-required-actions`) + +### [Authorization: Permission](https://www.keycloak.org/docs/8.0/authorization_services/#_overview) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clients.spec.ts + +- Create permission (`POST /{realm}/clients/{id}/authz/resource-server/permission/{type}`) +- Get permission (`GET /{realm}/clients/{id}/authz/resource-server/permission/{type}/{permissionId}`) +- Update permission (`PUT /{realm}/clients/{id}/authz/resource-server/permission/{type}/{permissionId}`) +- Delete permission (`DELETE /{realm}/clients/{id}/authz/resource-server/permission/{type}/{permissionId}`) + +### [Authorization: Policy](https://www.keycloak.org/docs/8.0/authorization_services/#_overview) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/clients.spec.ts + +- Create policy (`POST /{realm}/clients/{id}/authz/resource-server/policy/{type}`) +- Get policy (`GET /{realm}/clients/{id}/authz/resource-server/policy/{type}/{policyId}`) +- Get policy by name (`GET /{realm}/clients/{id}/authz/resource-server/policy/search`) +- Update policy (`PUT /{realm}/clients/{id}/authz/resource-server/policy/{type}/{policyId}`) +- Delete policy (`DELETE /{realm}/clients/{id}/authz/resource-server/policy/{policyId}`) + +### [Attack Detection](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_attack_detection_resource) + +Demo code: https://github.com/keycloak/keycloak/blob/main/js/libs/keycloak-admin-client/test/attackDetection.spec.ts + +- Clear any user login failures for all users This can release temporary disabled users (`DELETE /{realm}/attack-detection/brute-force/users`) +- Get status of a username in brute force detection (`GET /{realm}/attack-detection/brute-force/users/{userId}`) +- Clear any user login failures for the user This can release temporary disabled user (`DELETE /{realm}/attack-detection/brute-force/users/{userId}`) + +## Not yet supported + +- [Authentication Management](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_authentication_management_resource) +- [Client Initial Access](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_client_initial_access_resource) +- [Client Registration Policy](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_client_registration_policy_resource) +- [Key](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_key_resource) +- [User Storage Provider](https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html#_user_storage_provider_resource) + +## Maintainers + +This repo is originally developed by [Canner](https://www.cannercms.com) and [InfuseAI](https://infuseai.io) before being transferred under keycloak organization. diff --git a/js/libs/keycloak-admin-client/package.json b/js/libs/keycloak-admin-client/package.json new file mode 100644 index 0000000000..d9dc972840 --- /dev/null +++ b/js/libs/keycloak-admin-client/package.json @@ -0,0 +1,65 @@ +{ + "name": "@keycloak/keycloak-admin-client", + "version": "999.0.0-dev", + "description": "A client to interact with Keycloak's Administration API", + "type": "module", + "main": "lib/index.js", + "files": [ + "lib" + ], + "types": "lib/index.d.ts", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "scripts": { + "build": "wireit", + "lint": "wireit", + "test": "wireit", + "prepublishOnly": "npm run build" + }, + "wireit": { + "build": { + "command": "tsc --pretty", + "files": [ + "src/**", + "package.json", + "tsconfig.json" + ], + "output": [ + "lib/**" + ] + }, + "lint": { + "command": "eslint . --ext js,jsx,mjs,ts,tsx" + }, + "test": { + "command": "TS_NODE_PROJECT=tsconfig.test.json mocha --recursive \"test/**/*.spec.ts\" --timeout 10000" + } + }, + "dependencies": { + "camelize-ts": "^2.2.0", + "lodash-es": "^4.17.21", + "url-join": "^5.0.0", + "url-template": "^3.1.0" + }, + "devDependencies": { + "@faker-js/faker": "^7.6.0", + "@types/chai": "^4.3.4", + "@types/lodash-es": "^4.17.6", + "@types/mocha": "^10.0.1", + "@types/node": "^18.11.18", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "ts-node": "^10.9.1" + }, + "author": { + "name": "Red Hat, Inc.", + "url": "https://www.keycloak.org/" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/keycloak/keycloak.git" + }, + "homepage": "https://www.keycloak.org/" +} diff --git a/js/libs/keycloak-admin-client/src/client.ts b/js/libs/keycloak-admin-client/src/client.ts new file mode 100644 index 0000000000..655bee8e74 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/client.ts @@ -0,0 +1,143 @@ +import type { RequestArgs } from "./resources/agent.js"; +import { AttackDetection } from "./resources/attackDetection.js"; +import { AuthenticationManagement } from "./resources/authenticationManagement.js"; +import { Cache } from "./resources/cache.js"; +import { ClientPolicies } from "./resources/clientPolicies.js"; +import { Clients } from "./resources/clients.js"; +import { ClientScopes } from "./resources/clientScopes.js"; +import { Components } from "./resources/components.js"; +import { Groups } from "./resources/groups.js"; +import { IdentityProviders } from "./resources/identityProviders.js"; +import { Realms } from "./resources/realms.js"; +import { Roles } from "./resources/roles.js"; +import { ServerInfo } from "./resources/serverInfo.js"; +import { Sessions } from "./resources/sessions.js"; +import { Users } from "./resources/users.js"; +import { UserStorageProvider } from "./resources/userStorageProvider.js"; +import { WhoAmI } from "./resources/whoAmI.js"; +import { Credentials, getToken } from "./utils/auth.js"; +import { defaultBaseUrl, defaultRealm } from "./utils/constants.js"; + +export interface TokenProvider { + getAccessToken: () => Promise; +} + +export interface ConnectionConfig { + baseUrl?: string; + realmName?: string; + requestOptions?: RequestInit; + requestArgOptions?: Pick; +} + +export class KeycloakAdminClient { + // Resources + public users: Users; + public userStorageProvider: UserStorageProvider; + public groups: Groups; + public roles: Roles; + public clients: Clients; + public realms: Realms; + public clientScopes: ClientScopes; + public clientPolicies: ClientPolicies; + public identityProviders: IdentityProviders; + public components: Components; + public serverInfo: ServerInfo; + public whoAmI: WhoAmI; + public attackDetection: AttackDetection; + public sessions: Sessions; + public authenticationManagement: AuthenticationManagement; + public cache: Cache; + + // Members + public baseUrl: string; + public realmName: string; + public accessToken?: string; + public refreshToken?: string; + + private requestOptions?: RequestInit; + private globalRequestArgOptions?: Pick; + private tokenProvider?: TokenProvider; + + constructor(connectionConfig?: ConnectionConfig) { + this.baseUrl = connectionConfig?.baseUrl || defaultBaseUrl; + this.realmName = connectionConfig?.realmName || defaultRealm; + this.requestOptions = connectionConfig?.requestOptions; + this.globalRequestArgOptions = connectionConfig?.requestArgOptions; + + // Initialize resources + this.users = new Users(this); + this.userStorageProvider = new UserStorageProvider(this); + this.groups = new Groups(this); + this.roles = new Roles(this); + this.clients = new Clients(this); + this.realms = new Realms(this); + this.clientScopes = new ClientScopes(this); + this.clientPolicies = new ClientPolicies(this); + this.identityProviders = new IdentityProviders(this); + this.components = new Components(this); + this.authenticationManagement = new AuthenticationManagement(this); + this.serverInfo = new ServerInfo(this); + this.whoAmI = new WhoAmI(this); + this.sessions = new Sessions(this); + this.attackDetection = new AttackDetection(this); + this.cache = new Cache(this); + } + + public async auth(credentials: Credentials) { + const { accessToken, refreshToken } = await getToken({ + baseUrl: this.baseUrl, + realmName: this.realmName, + credentials, + requestOptions: this.requestOptions, + }); + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public registerTokenProvider(provider: TokenProvider) { + if (this.tokenProvider) { + throw new Error("An existing token provider was already registered."); + } + + this.tokenProvider = provider; + } + + public setAccessToken(token: string) { + this.accessToken = token; + } + + public async getAccessToken() { + if (this.tokenProvider) { + return this.tokenProvider.getAccessToken(); + } + + return this.accessToken; + } + + public getRequestOptions() { + return this.requestOptions; + } + + public getGlobalRequestArgOptions(): + | Pick + | undefined { + return this.globalRequestArgOptions; + } + + public setConfig(connectionConfig: ConnectionConfig) { + if ( + typeof connectionConfig.baseUrl === "string" && + connectionConfig.baseUrl + ) { + this.baseUrl = connectionConfig.baseUrl; + } + + if ( + typeof connectionConfig.realmName === "string" && + connectionConfig.realmName + ) { + this.realmName = connectionConfig.realmName; + } + this.requestOptions = connectionConfig.requestOptions; + } +} diff --git a/js/libs/keycloak-admin-client/src/defs/AccessTokenAccess.ts b/js/libs/keycloak-admin-client/src/defs/AccessTokenAccess.ts new file mode 100644 index 0000000000..02af3a2e20 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/AccessTokenAccess.ts @@ -0,0 +1,4 @@ +export default interface AccessTokenAccess { + roles?: string[]; + verify_caller?: boolean; +} diff --git a/js/libs/keycloak-admin-client/src/defs/PermissonRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/PermissonRepresentation.ts new file mode 100644 index 0000000000..272da529c1 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/PermissonRepresentation.ts @@ -0,0 +1,6 @@ +export default interface PermissionRepresentation { + claims?: { [index: string]: string }; + rsid?: string; + rsname?: string; + scopes?: string[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/accessTokenCertConf.ts b/js/libs/keycloak-admin-client/src/defs/accessTokenCertConf.ts new file mode 100644 index 0000000000..3d271b67e1 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/accessTokenCertConf.ts @@ -0,0 +1,3 @@ +export default interface AccessTokenCertConf { + "x5t#S256"?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/accessTokenRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/accessTokenRepresentation.ts new file mode 100644 index 0000000000..fbc8db24ae --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/accessTokenRepresentation.ts @@ -0,0 +1,50 @@ +import type AccessTokenAccess from "./AccessTokenAccess.js"; +import type AccessTokenCertConf from "./accessTokenCertConf.js"; +import type AddressClaimSet from "./addressClaimSet.js"; +import type { Category } from "./resourceServerRepresentation.js"; + +export default interface AccessTokenRepresentation { + acr?: string; + address?: AddressClaimSet; + "allowed-origins"?: string[]; + at_hash?: string; + auth_time?: number; + authorization?: AccessTokenRepresentation; + azp?: string; + birthdate?: string; + c_hash?: string; + category?: Category; + claims_locales?: string; + cnf?: AccessTokenCertConf; + email?: string; + email_verified?: boolean; + exp?: number; + family_name?: string; + gender: string; + given_name?: string; + iat?: number; + iss?: string; + jti?: string; + locale?: string; + middle_name?: string; + name?: string; + nbf?: number; + nickname?: string; + nonce?: string; + otherClaims?: { [index: string]: string }; + phone_number?: string; + phone_number_verified?: boolean; + picture?: string; + preferred_username?: string; + profile?: string; + realm_access?: AccessTokenAccess; + s_hash?: string; + scope?: string; + session_state?: string; + sub?: string; + "trusted-certs"?: string[]; + typ?: string; + updated_at?: number; + website?: string; + zoneinfo?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/addressClaimSet.ts b/js/libs/keycloak-admin-client/src/defs/addressClaimSet.ts new file mode 100644 index 0000000000..0b8ef628da --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/addressClaimSet.ts @@ -0,0 +1,8 @@ +export default interface AddressClaimSet { + country?: string; + formatted?: string; + locality?: string; + postal_code?: string; + region?: string; + street_address?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/adminEventRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/adminEventRepresentation.ts new file mode 100644 index 0000000000..d546abe01d --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/adminEventRepresentation.ts @@ -0,0 +1,12 @@ +import type AuthDetailsRepresentation from "./authDetailsRepresentation.js"; + +export default interface AdminEventRepresentation { + authDetails?: AuthDetailsRepresentation; + error?: string; + operationType?: string; + realmId?: string; + representation?: string; + resourcePath?: string; + resourceType?: string; + time?: number; +} diff --git a/js/libs/keycloak-admin-client/src/defs/authDetailsRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/authDetailsRepresentation.ts new file mode 100644 index 0000000000..2d225e43dd --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/authDetailsRepresentation.ts @@ -0,0 +1,6 @@ +export default interface AuthDetailsRepresentation { + clientId?: string; + ipAddress?: string; + realmId?: string; + userId?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/authenticationExecutionExportRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/authenticationExecutionExportRepresentation.ts new file mode 100644 index 0000000000..16a8426d8e --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/authenticationExecutionExportRepresentation.ts @@ -0,0 +1,12 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_authenticationexecutionexportrepresentation + */ +export default interface AuthenticationExecutionExportRepresentation { + flowAlias?: string; + userSetupAllowed?: boolean; + authenticatorConfig?: string; + authenticator?: string; + requirement?: string; + priority?: number; + autheticatorFlow?: boolean; +} diff --git a/js/libs/keycloak-admin-client/src/defs/authenticationExecutionInfoRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/authenticationExecutionInfoRepresentation.ts new file mode 100644 index 0000000000..3fa5db2ba9 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/authenticationExecutionInfoRepresentation.ts @@ -0,0 +1,18 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_authenticationexecutioninforepresentation + */ +export default interface AuthenticationExecutionInfoRepresentation { + id?: string; + requirement?: string; + displayName?: string; + alias?: string; + description?: string; + requirementChoices?: string[]; + configurable?: boolean; + authenticationFlow?: boolean; + providerId?: string; + authenticationConfig?: string; + flowId?: string; + level?: number; + index?: number; +} diff --git a/js/libs/keycloak-admin-client/src/defs/authenticationFlowRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/authenticationFlowRepresentation.ts new file mode 100644 index 0000000000..c24d4907b0 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/authenticationFlowRepresentation.ts @@ -0,0 +1,14 @@ +import type AuthenticationExecutionExportRepresentation from "./authenticationExecutionExportRepresentation.js"; + +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_authenticationflowrepresentation + */ +export default interface AuthenticationFlowRepresentation { + id?: string; + alias?: string; + description?: string; + providerId?: string; + topLevel?: boolean; + builtIn?: boolean; + authenticationExecutions?: AuthenticationExecutionExportRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/authenticatorConfigInfoRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/authenticatorConfigInfoRepresentation.ts new file mode 100644 index 0000000000..96cd28477e --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/authenticatorConfigInfoRepresentation.ts @@ -0,0 +1,19 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_authenticatorconfiginforepresentation + */ +export default interface AuthenticatorConfigInfoRepresentation { + name?: string; + providerId?: string; + helpText?: string; + properties?: ConfigPropertyRepresentation[]; +} + +export interface ConfigPropertyRepresentation { + name?: string; + label?: string; + helpText?: string; + type?: string; + defaultValue?: any; + options?: string[]; + secret?: boolean; +} diff --git a/js/libs/keycloak-admin-client/src/defs/authenticatorConfigRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/authenticatorConfigRepresentation.ts new file mode 100644 index 0000000000..00830a86a4 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/authenticatorConfigRepresentation.ts @@ -0,0 +1,16 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_authenticatorconfigrepresentation + */ +export default interface AuthenticatorConfigRepresentation { + id?: string; + alias?: string; + config?: { [index: string]: string }; +} + +// we defined this type ourself as the original is just `{[index: string]: any}[]` +// but the admin console does assume these properties are there. +export interface AuthenticationProviderRepresentation { + id?: string; + displayName?: string; + description?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/certificateRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/certificateRepresentation.ts new file mode 100644 index 0000000000..9abf28ea32 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/certificateRepresentation.ts @@ -0,0 +1,9 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/#_certificaterepresentation + */ +export default interface CertificateRepresentation { + privateKey?: string; + publicKey?: string; + certificate?: string; + kid?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientInitialAccessPresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientInitialAccessPresentation.ts new file mode 100644 index 0000000000..dc3cf94dbf --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientInitialAccessPresentation.ts @@ -0,0 +1,11 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_clientinitialaccesspresentation + */ +export default interface ClientInitialAccessPresentation { + id?: string; + token?: string; + timestamp?: number; + expiration?: number; + count?: number; + remainingCount?: number; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientPoliciesRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientPoliciesRepresentation.ts new file mode 100644 index 0000000000..06b7b597ac --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientPoliciesRepresentation.ts @@ -0,0 +1,8 @@ +import type ClientPolicyRepresentation from "./clientPolicyRepresentation.js"; + +/** + * https://www.keycloak.org/docs-api/15.0/rest-api/#_clientpoliciesrepresentation + */ +export default interface ClientPoliciesRepresentation { + policies?: ClientPolicyRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientPolicyConditionRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientPolicyConditionRepresentation.ts new file mode 100644 index 0000000000..03ff4ebb6e --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientPolicyConditionRepresentation.ts @@ -0,0 +1,7 @@ +/** + * https://www.keycloak.org/docs-api/15.0/rest-api/#_clientpolicyconditionrepresentation + */ +export default interface ClientPolicyConditionRepresentation { + condition?: string; + configuration?: object; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientPolicyExecutorRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientPolicyExecutorRepresentation.ts new file mode 100644 index 0000000000..80517c2a35 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientPolicyExecutorRepresentation.ts @@ -0,0 +1,7 @@ +/** + * https://www.keycloak.org/docs-api/15.0/rest-api/#_clientpolicyexecutorrepresentation + */ +export default interface ClientPolicyExecutorRepresentation { + configuration?: object; + executor?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientPolicyRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientPolicyRepresentation.ts new file mode 100644 index 0000000000..f979bfe45e --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientPolicyRepresentation.ts @@ -0,0 +1,12 @@ +import type ClientPolicyConditionRepresentation from "./clientPolicyConditionRepresentation.js"; + +/** + * https://www.keycloak.org/docs-api/15.0/rest-api/#_clientpolicyrepresentation + */ +export default interface ClientPolicyRepresentation { + conditions?: ClientPolicyConditionRepresentation[]; + description?: string; + enabled?: boolean; + name?: string; + profiles?: string[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientProfileRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientProfileRepresentation.ts new file mode 100644 index 0000000000..7e16675d81 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientProfileRepresentation.ts @@ -0,0 +1,10 @@ +import type ClientPolicyExecutorRepresentation from "./clientPolicyExecutorRepresentation.js"; + +/** + * https://www.keycloak.org/docs-api/15.0/rest-api/#_clientprofilerepresentation + */ +export default interface ClientProfileRepresentation { + description?: string; + executors?: ClientPolicyExecutorRepresentation[]; + name?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientProfilesRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientProfilesRepresentation.ts new file mode 100644 index 0000000000..13063b0d4b --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientProfilesRepresentation.ts @@ -0,0 +1,9 @@ +import type ClientProfileRepresentation from "./clientProfileRepresentation.js"; + +/** + * https://www.keycloak.org/docs-api/15.0/rest-api/#_clientprofilesrepresentation + */ +export default interface ClientProfilesRepresentation { + globalProfiles?: ClientProfileRepresentation[]; + profiles?: ClientProfileRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientRepresentation.ts new file mode 100644 index 0000000000..627a914b8c --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientRepresentation.ts @@ -0,0 +1,46 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_clientrepresentation + */ +import type ResourceServerRepresentation from "./resourceServerRepresentation.js"; +import type ProtocolMapperRepresentation from "./protocolMapperRepresentation.js"; + +export default interface ClientRepresentation { + access?: Record; + adminUrl?: string; + attributes?: Record; + authenticationFlowBindingOverrides?: Record; + authorizationServicesEnabled?: boolean; + authorizationSettings?: ResourceServerRepresentation; + baseUrl?: string; + bearerOnly?: boolean; + clientAuthenticatorType?: string; + clientId?: string; + consentRequired?: boolean; + defaultClientScopes?: string[]; + defaultRoles?: string[]; + description?: string; + directAccessGrantsEnabled?: boolean; + enabled?: boolean; + alwaysDisplayInConsole?: boolean; + frontchannelLogout?: boolean; + fullScopeAllowed?: boolean; + id?: string; + implicitFlowEnabled?: boolean; + name?: string; + nodeReRegistrationTimeout?: number; + notBefore?: number; + optionalClientScopes?: string[]; + origin?: string; + protocol?: string; + protocolMappers?: ProtocolMapperRepresentation[]; + publicClient?: boolean; + redirectUris?: string[]; + registeredNodes?: Record; + registrationAccessToken?: string; + rootUrl?: string; + secret?: string; + serviceAccountsEnabled?: boolean; + standardFlowEnabled?: boolean; + surrogateAuthRequired?: boolean; + webOrigins?: string[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/clientScopeRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/clientScopeRepresentation.ts new file mode 100644 index 0000000000..008229f18d --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/clientScopeRepresentation.ts @@ -0,0 +1,13 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_clientscoperepresentation + */ +import type ProtocolMapperRepresentation from "./protocolMapperRepresentation.js"; + +export default interface ClientScopeRepresentation { + attributes?: Record; + description?: string; + id?: string; + name?: string; + protocol?: string; + protocolMappers?: ProtocolMapperRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/componentExportRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/componentExportRepresentation.ts new file mode 100644 index 0000000000..4cf865ddc1 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/componentExportRepresentation.ts @@ -0,0 +1,12 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_componentexportrepresentation + */ + +export default interface ComponentExportRepresentation { + id?: string; + name?: string; + providerId?: string; + subType?: string; + subComponents?: { [index: string]: ComponentExportRepresentation }; + config?: { [index: string]: string }; +} diff --git a/js/libs/keycloak-admin-client/src/defs/componentRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/componentRepresentation.ts new file mode 100644 index 0000000000..dd9cb4c055 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/componentRepresentation.ts @@ -0,0 +1,13 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_componentrepresentation + */ + +export default interface ComponentRepresentation { + id?: string; + name?: string; + providerId?: string; + providerType?: string; + parentId?: string; + subType?: string; + config?: { [index: string]: string | string[] }; +} diff --git a/js/libs/keycloak-admin-client/src/defs/componentTypeRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/componentTypeRepresentation.ts new file mode 100644 index 0000000000..36ebfb8b7e --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/componentTypeRepresentation.ts @@ -0,0 +1,11 @@ +import type { ConfigPropertyRepresentation } from "./configPropertyRepresentation.js"; + +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_componenttyperepresentation + */ +export default interface ComponentTypeRepresentation { + id: string; + helpText: string; + properties: ConfigPropertyRepresentation[]; + metadata: { [index: string]: any }; +} diff --git a/js/libs/keycloak-admin-client/src/defs/configPropertyRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/configPropertyRepresentation.ts new file mode 100644 index 0000000000..1eb322a1c1 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/configPropertyRepresentation.ts @@ -0,0 +1,12 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_configpropertyrepresentation + */ +export interface ConfigPropertyRepresentation { + name?: string; + label?: string; + helpText?: string; + type?: string; + defaultValue?: object; + options?: string[]; + secret?: boolean; +} diff --git a/js/libs/keycloak-admin-client/src/defs/credentialRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/credentialRepresentation.ts new file mode 100644 index 0000000000..c5438f1165 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/credentialRepresentation.ts @@ -0,0 +1,15 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_credentialrepresentation + */ + +export default interface CredentialRepresentation { + createdDate?: number; + credentialData?: string; + id?: string; + priority?: number; + secretData?: string; + temporary?: boolean; + type?: string; + userLabel?: string; + value?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/evaluationResultRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/evaluationResultRepresentation.ts new file mode 100644 index 0000000000..689af48f75 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/evaluationResultRepresentation.ts @@ -0,0 +1,12 @@ +import type { DecisionEffect } from "./policyRepresentation.js"; +import type PolicyResultRepresentation from "./policyResultRepresentation.js"; +import type ResourceRepresentation from "./resourceRepresentation.js"; +import type ScopeRepresentation from "./scopeRepresentation.js"; + +export default interface EvaluationResultRepresentation { + resource?: ResourceRepresentation; + scopes?: ScopeRepresentation[]; + policies?: PolicyResultRepresentation[]; + status?: DecisionEffect; + allowedScopes?: ScopeRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/eventRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/eventRepresentation.ts new file mode 100644 index 0000000000..6f0feaae18 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/eventRepresentation.ts @@ -0,0 +1,16 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_eventrepresentation + */ +import type EventType from "./eventTypes.js"; + +export default interface EventRepresentation { + clientId?: string; + details?: Record; + error?: string; + ipAddress?: string; + realmId?: string; + sessionId?: string; + time?: number; + type?: EventType; + userId?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/eventTypes.ts b/js/libs/keycloak-admin-client/src/defs/eventTypes.ts new file mode 100644 index 0000000000..288692bb42 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/eventTypes.ts @@ -0,0 +1,89 @@ +type EventType = + | "LOGIN" + | "LOGIN_ERROR" + | "REGISTER" + | "REGISTER_ERROR" + | "LOGOUT" + | "LOGOUT_ERROR" + | "CODE_TO_TOKEN" + | "CODE_TO_TOKEN_ERROR" + | "CLIENT_LOGIN" + | "CLIENT_LOGIN_ERROR" + | "REFRESH_TOKEN" + | "REFRESH_TOKEN_ERROR" + | "VALIDATE_ACCESS_TOKEN" + | "VALIDATE_ACCESS_TOKEN_ERROR" + | "INTROSPECT_TOKEN" + | "INTROSPECT_TOKEN_ERROR" + | "FEDERATED_IDENTITY_LINK" + | "FEDERATED_IDENTITY_LINK_ERROR" + | "REMOVE_FEDERATED_IDENTITY" + | "REMOVE_FEDERATED_IDENTITY_ERROR" + | "UPDATE_EMAIL" + | "UPDATE_EMAIL_ERROR" + | "UPDATE_PROFILE" + | "UPDATE_PROFILE_ERROR" + | "UPDATE_PASSWORD" + | "UPDATE_PASSWORD_ERROR" + | "UPDATE_TOTP" + | "UPDATE_TOTP_ERROR" + | "VERIFY_EMAIL" + | "VERIFY_EMAIL_ERROR" + | "REMOVE_TOTP" + | "REMOVE_TOTP_ERROR" + | "REVOKE_GRANT" + | "REVOKE_GRANT_ERROR" + | "SEND_VERIFY_EMAIL" + | "SEND_VERIFY_EMAIL_ERROR" + | "SEND_RESET_PASSWORD" + | "SEND_RESET_PASSWORD_ERROR" + | "SEND_IDENTITY_PROVIDER_LINK" + | "SEND_IDENTITY_PROVIDER_LINK_ERROR" + | "RESET_PASSWORD" + | "RESET_PASSWORD_ERROR" + | "RESTART_AUTHENTICATION" + | "RESTART_AUTHENTICATION_ERROR" + | "INVALID_SIGNATURE" + | "INVALID_SIGNATURE_ERROR" + | "REGISTER_NODE" + | "REGISTER_NODE_ERROR" + | "UNREGISTER_NODE" + | "UNREGISTER_NODE_ERROR" + | "USER_INFO_REQUEST" + | "USER_INFO_REQUEST_ERROR" + | "IDENTITY_PROVIDER_LINK_ACCOUNT" + | "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR" + | "IDENTITY_PROVIDER_LOGIN" + | "IDENTITY_PROVIDER_LOGIN_ERROR" + | "IDENTITY_PROVIDER_FIRST_LOGIN" + | "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" + | "IDENTITY_PROVIDER_POST_LOGIN" + | "IDENTITY_PROVIDER_POST_LOGIN_ERROR" + | "IDENTITY_PROVIDER_RESPONSE" + | "IDENTITY_PROVIDER_RESPONSE_ERROR" + | "IDENTITY_PROVIDER_RETRIEVE_TOKEN" + | "IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR" + | "IMPERSONATE" + | "IMPERSONATE_ERROR" + | "CUSTOM_REQUIRED_ACTION" + | "CUSTOM_REQUIRED_ACTION_ERROR" + | "EXECUTE_ACTIONS" + | "EXECUTE_ACTIONS_ERROR" + | "EXECUTE_ACTION_TOKEN" + | "EXECUTE_ACTION_TOKEN_ERROR" + | "CLIENT_INFO" + | "CLIENT_INFO_ERROR" + | "CLIENT_REGISTER" + | "CLIENT_REGISTER_ERROR" + | "CLIENT_UPDATE" + | "CLIENT_UPDATE_ERROR" + | "CLIENT_DELETE" + | "CLIENT_DELETE_ERROR" + | "CLIENT_INITIATED_ACCOUNT_LINKING" + | "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR" + | "TOKEN_EXCHANGE" + | "TOKEN_EXCHANGE_ERROR" + | "PERMISSION_TOKEN" + | "PERMISSION_TOKEN_ERROR"; + +export default EventType; diff --git a/js/libs/keycloak-admin-client/src/defs/federatedIdentityRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/federatedIdentityRepresentation.ts new file mode 100644 index 0000000000..f9ace144fc --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/federatedIdentityRepresentation.ts @@ -0,0 +1,9 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_federatedidentityrepresentation + */ + +export default interface FederatedIdentityRepresentation { + identityProvider?: string; + userId?: string; + userName?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/globalRequestResult.ts b/js/libs/keycloak-admin-client/src/defs/globalRequestResult.ts new file mode 100644 index 0000000000..bb650bc1fd --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/globalRequestResult.ts @@ -0,0 +1,7 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_globalrequestresult + */ +export default interface GlobalRequestResult { + successRequests?: string[]; + failedRequests?: string[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/groupRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/groupRepresentation.ts new file mode 100644 index 0000000000..7ebee981c3 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/groupRepresentation.ts @@ -0,0 +1,16 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_grouprepresentation + */ + +export default interface GroupRepresentation { + id?: string; + name?: string; + path?: string; + subGroups?: GroupRepresentation[]; + + // optional in response + access?: Record; + attributes?: Record; + clientRoles?: Record; + realmRoles?: string[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/identityProviderMapperRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/identityProviderMapperRepresentation.ts new file mode 100644 index 0000000000..9912b0aaf1 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/identityProviderMapperRepresentation.ts @@ -0,0 +1,11 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_identityprovidermapperrepresentation + */ + +export default interface IdentityProviderMapperRepresentation { + config?: any; + id?: string; + identityProviderAlias?: string; + identityProviderMapper?: string; + name?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/identityProviderMapperTypeRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/identityProviderMapperTypeRepresentation.ts new file mode 100644 index 0000000000..e75d65d0c1 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/identityProviderMapperTypeRepresentation.ts @@ -0,0 +1,9 @@ +import type { ConfigPropertyRepresentation } from "./configPropertyRepresentation.js"; + +export interface IdentityProviderMapperTypeRepresentation { + id?: string; + name?: string; + category?: string; + helpText?: string; + properties?: ConfigPropertyRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/identityProviderRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/identityProviderRepresentation.ts new file mode 100644 index 0000000000..7e8ba1dca6 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/identityProviderRepresentation.ts @@ -0,0 +1,18 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_identityproviderrepresentation + */ + +export default interface IdentityProviderRepresentation { + addReadTokenRoleOnCreate?: boolean; + alias?: string; + config?: Record; + displayName?: string; + enabled?: boolean; + firstBrokerLoginFlowAlias?: string; + internalId?: string; + linkOnly?: boolean; + postBrokerLoginFlowAlias?: string; + providerId?: string; + storeToken?: boolean; + trustEmail?: boolean; +} diff --git a/js/libs/keycloak-admin-client/src/defs/keyMetadataRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/keyMetadataRepresentation.ts new file mode 100644 index 0000000000..cff2624899 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/keyMetadataRepresentation.ts @@ -0,0 +1,18 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_keysmetadatarepresentation-keymetadatarepresentation + */ +export default interface KeysMetadataRepresentation { + active?: { [index: string]: string }; + keys?: KeyMetadataRepresentation[]; +} + +export interface KeyMetadataRepresentation { + providerId?: string; + providerPriority?: number; + kid?: string; + status?: string; + type?: string; + algorithm?: string; + publicKey?: string; + certificate?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/keystoreConfig.ts b/js/libs/keycloak-admin-client/src/defs/keystoreConfig.ts new file mode 100644 index 0000000000..5c11410ed8 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/keystoreConfig.ts @@ -0,0 +1,11 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/#_keystoreconfig + */ +export default interface KeyStoreConfig { + realmCertificate?: boolean; + storePassword?: string; + keyPassword?: string; + keyAlias?: string; + realmAlias?: string; + format?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/managementPermissionReference.ts b/js/libs/keycloak-admin-client/src/defs/managementPermissionReference.ts new file mode 100644 index 0000000000..e4424ac6c4 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/managementPermissionReference.ts @@ -0,0 +1,5 @@ +export interface ManagementPermissionReference { + enabled?: boolean; + resource?: string; + scopePermissions?: Record; +} diff --git a/js/libs/keycloak-admin-client/src/defs/mappingsRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/mappingsRepresentation.ts new file mode 100644 index 0000000000..23d144b019 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/mappingsRepresentation.ts @@ -0,0 +1,9 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_mappingsrepresentation + */ +import type RoleRepresentation from "./roleRepresentation.js"; + +export default interface MappingsRepresentation { + clientMappings?: Record; + realmMappings?: RoleRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/passwordPolicyTypeRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/passwordPolicyTypeRepresentation.ts new file mode 100644 index 0000000000..1b7464644f --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/passwordPolicyTypeRepresentation.ts @@ -0,0 +1,10 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_passwordpolicytyperepresentation + */ +export default interface PasswordPolicyTypeRepresentation { + id?: string; + displayName?: string; + configType?: string; + defaultValue?: string; + multipleSupported?: boolean; +} diff --git a/js/libs/keycloak-admin-client/src/defs/policyEvaluationResponse.ts b/js/libs/keycloak-admin-client/src/defs/policyEvaluationResponse.ts new file mode 100644 index 0000000000..e123872237 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/policyEvaluationResponse.ts @@ -0,0 +1,10 @@ +import type AccessTokenRepresentation from "./accessTokenRepresentation.js"; +import type EvaluationResultRepresentation from "./evaluationResultRepresentation.js"; +import type { DecisionEffect } from "./policyRepresentation.js"; + +export default interface PolicyEvaluationResponse { + results?: EvaluationResultRepresentation[]; + entitlements?: boolean; + status?: DecisionEffect; + rpt?: AccessTokenRepresentation; +} diff --git a/js/libs/keycloak-admin-client/src/defs/policyProviderRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/policyProviderRepresentation.ts new file mode 100644 index 0000000000..468ec183d7 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/policyProviderRepresentation.ts @@ -0,0 +1,5 @@ +export default interface PolicyProviderRepresentation { + type?: string; + name?: string; + group?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/policyRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/policyRepresentation.ts new file mode 100644 index 0000000000..7b4158989e --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/policyRepresentation.ts @@ -0,0 +1,40 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_policyrepresentation + */ + +export enum DecisionStrategy { + AFFIRMATIVE = "AFFIRMATIVE", + UNANIMOUS = "UNANIMOUS", + CONSENSUS = "CONSENSUS", +} + +export enum DecisionEffect { + Permit = "PERMIT", + Deny = "DENY", +} + +export enum Logic { + POSITIVE = "POSITIVE", + NEGATIVE = "NEGATIVE", +} + +export interface PolicyRoleRepresentation { + id: string; + required?: boolean; +} + +export default interface PolicyRepresentation { + config?: Record; + decisionStrategy?: DecisionStrategy; + description?: string; + id?: string; + logic?: Logic; + name?: string; + owner?: string; + policies?: string[]; + resources?: string[]; + scopes?: string[]; + type?: string; + users?: string[]; + roles?: PolicyRoleRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/policyResultRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/policyResultRepresentation.ts new file mode 100644 index 0000000000..02d4f24422 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/policyResultRepresentation.ts @@ -0,0 +1,9 @@ +import type PolicyRepresentation from "./policyRepresentation.js"; +import type { DecisionEffect } from "./policyRepresentation.js"; + +export default interface PolicyResultRepresentation { + policy?: PolicyRepresentation; + status?: DecisionEffect; + associatedPolicies?: PolicyResultRepresentation[]; + scopes?: string[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/profileInfoRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/profileInfoRepresentation.ts new file mode 100644 index 0000000000..24caa8b979 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/profileInfoRepresentation.ts @@ -0,0 +1,9 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_profileinforepresentation + */ +export default interface ProfileInfoRepresentation { + name?: string; + disabledFeatures?: string[]; + previewFeatures?: string[]; + experimentalFeatures?: string[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/protocolMapperRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/protocolMapperRepresentation.ts new file mode 100644 index 0000000000..835db76ca2 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/protocolMapperRepresentation.ts @@ -0,0 +1,11 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_protocolmapperrepresentation + */ + +export default interface ProtocolMapperRepresentation { + config?: Record; + id?: string; + name?: string; + protocol?: string; + protocolMapper?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/realmEventsConfigRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/realmEventsConfigRepresentation.ts new file mode 100644 index 0000000000..01b46fe4bc --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/realmEventsConfigRepresentation.ts @@ -0,0 +1,12 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/#_realmeventsconfigrepresentation + */ + +export interface RealmEventsConfigRepresentation { + eventsEnabled?: boolean; + eventsExpiration?: number; + eventsListeners?: string[]; + enabledEventTypes?: string[]; + adminEventsEnabled?: boolean; + adminEventsDetailsEnabled?: boolean; +} diff --git a/js/libs/keycloak-admin-client/src/defs/realmRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/realmRepresentation.ts new file mode 100644 index 0000000000..6681704d54 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/realmRepresentation.ts @@ -0,0 +1,144 @@ +import type ClientRepresentation from "./clientRepresentation.js"; +import type ComponentExportRepresentation from "./componentExportRepresentation.js"; +import type UserRepresentation from "./userRepresentation.js"; +import type GroupRepresentation from "./groupRepresentation.js"; +import type IdentityProviderRepresentation from "./identityProviderRepresentation.js"; +import type RequiredActionProviderRepresentation from "./requiredActionProviderRepresentation.js"; +import type RolesRepresentation from "./rolesRepresentation.js"; +import type ClientProfilesRepresentation from "./clientProfilesRepresentation.js"; +import type ClientPoliciesRepresentation from "./clientPoliciesRepresentation.js"; +import type RoleRepresentation from "./roleRepresentation.js"; + +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_realmrepresentation + */ + +export default interface RealmRepresentation { + accessCodeLifespan?: number; + accessCodeLifespanLogin?: number; + accessCodeLifespanUserAction?: number; + accessTokenLifespan?: number; + accessTokenLifespanForImplicitFlow?: number; + accountTheme?: string; + actionTokenGeneratedByAdminLifespan?: number; + actionTokenGeneratedByUserLifespan?: number; + adminEventsDetailsEnabled?: boolean; + adminEventsEnabled?: boolean; + adminTheme?: string; + attributes?: Record; + // AuthenticationFlowRepresentation + authenticationFlows?: any[]; + // AuthenticatorConfigRepresentation + authenticatorConfig?: any[]; + browserFlow?: string; + browserSecurityHeaders?: Record; + bruteForceProtected?: boolean; + clientAuthenticationFlow?: string; + clientScopeMappings?: Record; + // ClientScopeRepresentation + clientScopes?: any[]; + clients?: ClientRepresentation[]; + clientPolicies?: ClientPoliciesRepresentation; + clientProfiles?: ClientProfilesRepresentation; + components?: { [index: string]: ComponentExportRepresentation }; + defaultDefaultClientScopes?: string[]; + defaultGroups?: string[]; + defaultLocale?: string; + defaultOptionalClientScopes?: string[]; + defaultRoles?: string[]; + defaultRole?: RoleRepresentation; + defaultSignatureAlgorithm?: string; + directGrantFlow?: string; + displayName?: string; + displayNameHtml?: string; + dockerAuthenticationFlow?: string; + duplicateEmailsAllowed?: boolean; + editUsernameAllowed?: boolean; + emailTheme?: string; + enabled?: boolean; + enabledEventTypes?: string[]; + eventsEnabled?: boolean; + eventsExpiration?: number; + eventsListeners?: string[]; + failureFactor?: number; + federatedUsers?: UserRepresentation[]; + groups?: GroupRepresentation[]; + id?: string; + // IdentityProviderMapperRepresentation + identityProviderMappers?: any[]; + identityProviders?: IdentityProviderRepresentation[]; + internationalizationEnabled?: boolean; + keycloakVersion?: string; + loginTheme?: string; + loginWithEmailAllowed?: boolean; + maxDeltaTimeSeconds?: number; + maxFailureWaitSeconds?: number; + minimumQuickLoginWaitSeconds?: number; + notBefore?: number; + oauth2DeviceCodeLifespan?: number; + oauth2DevicePollingInterval?: number; + offlineSessionIdleTimeout?: number; + offlineSessionMaxLifespan?: number; + offlineSessionMaxLifespanEnabled?: boolean; + otpPolicyAlgorithm?: string; + otpPolicyDigits?: number; + otpPolicyInitialCounter?: number; + otpPolicyLookAheadWindow?: number; + otpPolicyPeriod?: number; + otpPolicyType?: string; + otpSupportedApplications?: string[]; + otpPolicyCodeReusable?: boolean; + passwordPolicy?: string; + permanentLockout?: boolean; + // ProtocolMapperRepresentation + protocolMappers?: any[]; + quickLoginCheckMilliSeconds?: number; + realm?: string; + refreshTokenMaxReuse?: number; + registrationAllowed?: boolean; + registrationEmailAsUsername?: boolean; + registrationFlow?: string; + rememberMe?: boolean; + requiredActions?: RequiredActionProviderRepresentation[]; + resetCredentialsFlow?: string; + resetPasswordAllowed?: boolean; + revokeRefreshToken?: boolean; + roles?: RolesRepresentation; + // ScopeMappingRepresentation + scopeMappings?: any[]; + smtpServer?: Record; + sslRequired?: string; + ssoSessionIdleTimeout?: number; + ssoSessionIdleTimeoutRememberMe?: number; + ssoSessionMaxLifespan?: number; + ssoSessionMaxLifespanRememberMe?: number; + clientSessionIdleTimeout?: number; + clientSessionMaxLifespan?: number; + supportedLocales?: string[]; + // UserFederationMapperRepresentation + userFederationMappers?: any[]; + // UserFederationProviderRepresentation + userFederationProviders?: any[]; + userManagedAccessAllowed?: boolean; + users?: UserRepresentation[]; + verifyEmail?: boolean; + waitIncrementSeconds?: number; +} + +export type PartialImportRealmRepresentation = RealmRepresentation & { + ifResourceExists: "FAIL" | "SKIP" | "OVERWRITE"; +}; + +export type PartialImportResponse = { + overwritten: number; + added: number; + skipped: number; + results: PartialImportResult[]; +}; + +export type PartialImportResult = { + action: string; + resourceType: string; + resourceName: string; + id: string; +}; diff --git a/js/libs/keycloak-admin-client/src/defs/requiredActionProviderRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/requiredActionProviderRepresentation.ts new file mode 100644 index 0000000000..90fec730e5 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/requiredActionProviderRepresentation.ts @@ -0,0 +1,21 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_requiredactionproviderrepresentation + */ + +export enum RequiredActionAlias { + VERIFY_EMAIL = "VERIFY_EMAIL", + UPDATE_PROFILE = "UPDATE_PROFILE", + CONFIGURE_TOTP = "CONFIGURE_TOTP", + UPDATE_PASSWORD = "UPDATE_PASSWORD", + terms_and_conditions = "terms_and_conditions", +} + +export default interface RequiredActionProviderRepresentation { + alias?: string; + config?: Record; + defaultAction?: boolean; + enabled?: boolean; + name?: string; + providerId?: string; + priority?: number; +} diff --git a/js/libs/keycloak-admin-client/src/defs/requiredActionProviderSimpleRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/requiredActionProviderSimpleRepresentation.ts new file mode 100644 index 0000000000..8968145824 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/requiredActionProviderSimpleRepresentation.ts @@ -0,0 +1,5 @@ +export default interface RequiredActionProviderSimpleRepresentation { + id?: string; + name?: string; + providerId?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/resourceEvaluation.ts b/js/libs/keycloak-admin-client/src/defs/resourceEvaluation.ts new file mode 100644 index 0000000000..e3f02fad89 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/resourceEvaluation.ts @@ -0,0 +1,14 @@ +import type ResourceRepresentation from "./resourceRepresentation.js"; + +export default interface ResourceEvaluation { + roleIds?: string[]; + clientId: string; + userId: string; + resources?: ResourceRepresentation[]; + entitlements: boolean; + context: { + attributes: { + [key: string]: string; + }; + }; +} diff --git a/js/libs/keycloak-admin-client/src/defs/resourceRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/resourceRepresentation.ts new file mode 100644 index 0000000000..0abaa365f8 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/resourceRepresentation.ts @@ -0,0 +1,18 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_resourcerepresentation + */ +import type { ResourceOwnerRepresentation } from "./resourceServerRepresentation.js"; +import type ScopeRepresentation from "./scopeRepresentation.js"; + +export default interface ResourceRepresentation { + name?: string; + type?: string; + owner?: ResourceOwnerRepresentation; + ownerManagedAccess?: boolean; + displayName?: string; + attributes?: { [index: string]: string[] }; + _id?: string; + uris?: string[]; + scopes?: ScopeRepresentation[]; + icon_uri?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/resourceServerRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/resourceServerRepresentation.ts new file mode 100644 index 0000000000..eb0cc90543 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/resourceServerRepresentation.ts @@ -0,0 +1,44 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_policyrepresentation + */ +import type PolicyRepresentation from "./policyRepresentation.js"; +import type ResourceRepresentation from "./resourceRepresentation.js"; +import type ScopeRepresentation from "./scopeRepresentation.js"; + +export default interface ResourceServerRepresentation { + id?: string; + clientId?: string; + name?: string; + allowRemoteResourceManagement?: boolean; + policyEnforcementMode?: PolicyEnforcementMode; + resources?: ResourceRepresentation[]; + policies?: PolicyRepresentation[]; + scopes?: ScopeRepresentation[]; + decisionStrategy?: DecisionStrategy; +} +export interface ResourceOwnerRepresentation { + id?: string; + name?: string; +} +export interface AbstractPolicyRepresentation { + id?: string; + name?: string; + description?: string; + type?: string; + policies?: string[]; + resources?: string[]; + scopes?: string[]; + logic?: Logic; + decisionStrategy?: DecisionStrategy; + owner?: string; + resourcesData?: ResourceRepresentation[]; + scopesData?: ScopeRepresentation[]; +} + +export type PolicyEnforcementMode = "ENFORCING" | "PERMISSIVE" | "DISABLED"; + +export type DecisionStrategy = "AFFIRMATIVE" | "UNANIMOUS" | "CONSENSUS"; + +export type Logic = "POSITIVE" | "NEGATIVE"; + +export type Category = "INTERNAL" | "ACCESS" | "ID" | "ADMIN" | "USERINFO"; diff --git a/js/libs/keycloak-admin-client/src/defs/roleRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/roleRepresentation.ts new file mode 100644 index 0000000000..39aeb95b11 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/roleRepresentation.ts @@ -0,0 +1,27 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_rolerepresentation + */ + +export default interface RoleRepresentation { + id?: string; + name?: string; + description?: string; + scopeParamRequired?: boolean; + composite?: boolean; + composites?: Composites; + clientRole?: boolean; + containerId?: string; + attributes?: { [index: string]: string[] }; +} + +export interface Composites { + realm?: string[]; + client?: { [index: string]: string[] }; + application?: { [index: string]: string[] }; +} + +// when requesting to role-mapping api (create, delete), id and name are required +export interface RoleMappingPayload extends RoleRepresentation { + id: string; + name: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/rolesRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/rolesRepresentation.ts new file mode 100644 index 0000000000..8959ad0ea0 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/rolesRepresentation.ts @@ -0,0 +1,11 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_rolesrepresentation + */ + +import type RoleRepresentation from "./roleRepresentation.js"; + +export default interface RolesRepresentation { + realm?: RoleRepresentation[]; + client?: { [index: string]: RoleRepresentation[] }; + application?: { [index: string]: RoleRepresentation[] }; +} diff --git a/js/libs/keycloak-admin-client/src/defs/scopeRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/scopeRepresentation.ts new file mode 100644 index 0000000000..c32e9cb145 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/scopeRepresentation.ts @@ -0,0 +1,14 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_scoperepresentation + */ +import type PolicyRepresentation from "./policyRepresentation.js"; +import type ResourceRepresentation from "./resourceRepresentation.js"; + +export default interface ScopeRepresentation { + displayName?: string; + iconUri?: string; + id?: string; + name?: string; + policies?: PolicyRepresentation[]; + resources?: ResourceRepresentation[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/serverInfoRepesentation.ts b/js/libs/keycloak-admin-client/src/defs/serverInfoRepesentation.ts new file mode 100644 index 0000000000..6dcf20e1d5 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/serverInfoRepesentation.ts @@ -0,0 +1,76 @@ +import type ComponentTypeRepresentation from "./componentTypeRepresentation.js"; +import type { ConfigPropertyRepresentation } from "./configPropertyRepresentation.js"; +import type PasswordPolicyTypeRepresentation from "./passwordPolicyTypeRepresentation.js"; +import type ProfileInfoRepresentation from "./profileInfoRepresentation.js"; +import type ProtocolMapperRepresentation from "./protocolMapperRepresentation.js"; +import type SystemInfoRepresentation from "./systemInfoRepersantation.js"; + +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_serverinforepresentation + */ +export interface ServerInfoRepresentation { + systemInfo?: SystemInfoRepresentation; + memoryInfo?: MemoryInfoRepresentation; + profileInfo?: ProfileInfoRepresentation; + cryptoInfo?: CryptoInfoRepresentation; + themes?: { [index: string]: ThemeInfoRepresentation[] }; + socialProviders?: { [index: string]: string }[]; + identityProviders?: { [index: string]: string }[]; + clientImporters?: { [index: string]: string }[]; + providers?: { [index: string]: SpiInfoRepresentation }; + protocolMapperTypes?: { [index: string]: ProtocolMapperTypeRepresentation[] }; + builtinProtocolMappers?: { [index: string]: ProtocolMapperRepresentation[] }; + clientInstallations?: { [index: string]: ClientInstallationRepresentation[] }; + componentTypes?: { [index: string]: ComponentTypeRepresentation[] }; + passwordPolicies?: PasswordPolicyTypeRepresentation[]; + enums?: { [index: string]: string[] }; +} + +export interface ThemeInfoRepresentation { + name: string; + locales?: string[]; +} + +export interface SpiInfoRepresentation { + internal: boolean; + providers: { [index: string]: ProviderRepresentation }; +} + +export interface ProviderRepresentation { + order: number; + operationalInfo?: Record; +} + +export interface ClientInstallationRepresentation { + id: string; + protocol: string; + downloadOnly: boolean; + displayType: string; + helpText: string; + filename: string; + mediaType: string; +} + +export interface MemoryInfoRepresentation { + total: number; + totalFormated: string; + used: number; + usedFormated: string; + free: number; + freePercentage: number; + freeFormated: string; +} + +export interface ProtocolMapperTypeRepresentation { + id: string; + name: string; + category: string; + helpText: string; + priority: number; + properties: ConfigPropertyRepresentation[]; +} + +export interface CryptoInfoRepresentation { + cryptoProvider: string; + supportedKeystoreTypes: string[]; +} diff --git a/js/libs/keycloak-admin-client/src/defs/synchronizationResultRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/synchronizationResultRepresentation.ts new file mode 100644 index 0000000000..6efb54fa2e --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/synchronizationResultRepresentation.ts @@ -0,0 +1,12 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_synchronizationresult + */ + +export default interface SynchronizationResultRepresentation { + ignored?: boolean; + added?: number; + updated?: number; + removed?: number; + failed?: number; + status?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/systemInfoRepersantation.ts b/js/libs/keycloak-admin-client/src/defs/systemInfoRepersantation.ts new file mode 100644 index 0000000000..38c694ba2a --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/systemInfoRepersantation.ts @@ -0,0 +1,24 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_systeminforepresentation + */ + +export default interface SystemInfoRepresentation { + version?: string; + serverTime?: string; + uptime?: string; + uptimeMillis?: number; + javaVersion?: string; + javaVendor?: string; + javaVm?: string; + javaVmVersion?: string; + javaRuntime?: string; + javaHome?: string; + osName?: string; + osArchitecture?: string; + osVersion?: string; + fileEncoding?: string; + userName?: string; + userDir?: string; + userTimezone?: string; + userLocale?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/testLdapConnection.ts b/js/libs/keycloak-admin-client/src/defs/testLdapConnection.ts new file mode 100644 index 0000000000..2cd79167c5 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/testLdapConnection.ts @@ -0,0 +1,15 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/#_testldapconnectionrepresentation + */ + +export default interface TestLdapConnectionRepresentation { + action?: string; + connectionUrl?: string; + bindDn?: string; + bindCredential?: string; + useTruststoreSpi?: string; + connectionTimeout?: string; + componentId?: string; + startTls?: string; + authType?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/userConsentRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/userConsentRepresentation.ts new file mode 100644 index 0000000000..50d96fd987 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/userConsentRepresentation.ts @@ -0,0 +1,10 @@ +/** + * https://www.keycloak.org/docs-api/11.0/rest-api/#_userconsentrepresentation + */ + +export default interface UserConsentRepresentation { + clientId?: string; + createDate?: string; + grantedClientScopes?: string[]; + lastUpdatedDate?: number; +} diff --git a/js/libs/keycloak-admin-client/src/defs/userProfileConfig.ts b/js/libs/keycloak-admin-client/src/defs/userProfileConfig.ts new file mode 100644 index 0000000000..47ad4c22af --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/userProfileConfig.ts @@ -0,0 +1,42 @@ +// See: https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/userprofile/config/UPConfig.java +export default interface UserProfileConfig { + attributes?: UserProfileAttribute[]; + groups?: UserProfileGroup[]; +} + +// See: https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/userprofile/config/UPAttribute.java +export interface UserProfileAttribute { + name?: string; + validations?: Record>; + annotations?: Record[]; + required?: UserProfileAttributeRequired; + permissions?: UserProfileAttributePermissions; + selector?: UserProfileAttributeSelector; + displayName?: string; + group?: string; +} + +// See: https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/userprofile/config/UPAttributeRequired.java +export interface UserProfileAttributeRequired { + roles?: string[]; + scopes?: string[]; +} + +// See: https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/userprofile/config/UPAttributePermissions.java +export interface UserProfileAttributePermissions { + view?: string[]; + edit?: string[]; +} + +// See: https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/userprofile/config/UPAttributeSelector.java +export interface UserProfileAttributeSelector { + scopes?: string[]; +} + +// See: https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/userprofile/config/UPGroup.java +export interface UserProfileGroup { + name?: string; + displayHeader?: string; + displayDescription?: string; + annotations?: Record; +} diff --git a/js/libs/keycloak-admin-client/src/defs/userRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/userRepresentation.ts new file mode 100644 index 0000000000..4215dc9c4d --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/userRepresentation.ts @@ -0,0 +1,33 @@ +import type UserConsentRepresentation from "./userConsentRepresentation.js"; +import type CredentialRepresentation from "./credentialRepresentation.js"; +import type FederatedIdentityRepresentation from "./federatedIdentityRepresentation.js"; +import type { RequiredActionAlias } from "./requiredActionProviderRepresentation.js"; + +export default interface UserRepresentation { + id?: string; + createdTimestamp?: number; + username?: string; + enabled?: boolean; + totp?: boolean; + emailVerified?: boolean; + disableableCredentialTypes?: string[]; + requiredActions?: (RequiredActionAlias | string)[]; + notBefore?: number; + access?: Record; + + // optional from response + attributes?: Record; + clientConsents?: UserConsentRepresentation[]; + clientRoles?: Record; + credentials?: CredentialRepresentation[]; + email?: string; + federatedIdentities?: FederatedIdentityRepresentation[]; + federationLink?: string; + firstName?: string; + groups?: string[]; + lastName?: string; + origin?: string; + realmRoles?: string[]; + self?: string; + serviceAccountClientId?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/userSessionRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/userSessionRepresentation.ts new file mode 100644 index 0000000000..7611fa29ae --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/userSessionRepresentation.ts @@ -0,0 +1,9 @@ +export default interface UserSessionRepresentation { + id?: string; + clients?: Record; + ipAddress?: string; + lastAccess?: number; + start?: number; + userId?: string; + username?: string; +} diff --git a/js/libs/keycloak-admin-client/src/defs/whoAmIRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/whoAmIRepresentation.ts new file mode 100644 index 0000000000..1086f93a82 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/defs/whoAmIRepresentation.ts @@ -0,0 +1,29 @@ +export type AccessType = + | "view-realm" + | "view-identity-providers" + | "manage-identity-providers" + | "impersonation" + | "create-client" + | "manage-users" + | "query-realms" + | "view-authorization" + | "query-clients" + | "query-users" + | "manage-events" + | "manage-realm" + | "view-events" + | "view-users" + | "view-clients" + | "manage-authorization" + | "manage-clients" + | "query-groups" + | "anyone"; + +export default interface WhoAmIRepresentation { + userId: string; + realm: string; + displayName: string; + locale: string; + createRealm: boolean; + realm_access: { [key: string]: AccessType[] }; +} diff --git a/js/libs/keycloak-admin-client/src/index.ts b/js/libs/keycloak-admin-client/src/index.ts new file mode 100644 index 0000000000..4829e2ded7 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/index.ts @@ -0,0 +1,7 @@ +import { KeycloakAdminClient } from "./client.js"; +import { RequiredActionAlias } from "./defs/requiredActionProviderRepresentation.js"; + +export const requiredAction = RequiredActionAlias; +export default KeycloakAdminClient; +export { NetworkError } from "./utils/fetchWithError.js"; +export type { NetworkErrorOptions } from "./utils/fetchWithError.js"; diff --git a/js/libs/keycloak-admin-client/src/resources/agent.ts b/js/libs/keycloak-admin-client/src/resources/agent.ts new file mode 100644 index 0000000000..91da741a54 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/agent.ts @@ -0,0 +1,300 @@ +import { isUndefined, last, omit, pick } from "lodash-es"; +import urlJoin from "url-join"; +import { parseTemplate } from "url-template"; +import type { KeycloakAdminClient } from "../client.js"; +import { + fetchWithError, + NetworkError, + parseResponse, +} from "../utils/fetchWithError.js"; +import { stringifyQueryParams } from "../utils/stringifyQueryParams.js"; + +// constants +const SLASH = "/"; + +type Method = "GET" | "POST" | "PUT" | "DELETE"; + +// interface +export interface RequestArgs { + method: Method; + path?: string; + // Keys of url params to be applied + urlParamKeys?: string[]; + // Keys of query parameters to be applied + queryParamKeys?: string[]; + // Mapping of key transformations to be performed on the payload + keyTransform?: Record; + // If responding with 404, catch it and return null instead + catchNotFound?: boolean; + // The key of the value to use from the payload of request. Only works for POST & PUT. + payloadKey?: string; + // Whether the response header have a location field with newly created resource id + // if this value is set, we return the field with format: {[field]: resourceId} + // to represent the newly created resource + // detail: keycloak/keycloak-nodejs-admin-client issue #11 + returnResourceIdInLocationHeader?: { field: string }; + /** + * Keys to be ignored, meaning that they will not be filtered out of the request payload even if they are a part of `urlParamKeys` or `queryParamKeys`, + */ + ignoredKeys?: string[]; + headers?: HeadersInit; +} + +export class Agent { + private client: KeycloakAdminClient; + private basePath: string; + private getBaseParams?: () => Record; + private getBaseUrl?: () => string; + + constructor({ + client, + path = "/", + getUrlParams = () => ({}), + getBaseUrl = () => client.baseUrl, + }: { + client: KeycloakAdminClient; + path?: string; + getUrlParams?: () => Record; + getBaseUrl?: () => string; + }) { + this.client = client; + this.getBaseParams = getUrlParams; + this.getBaseUrl = getBaseUrl; + this.basePath = path; + } + + public request({ + method, + path = "", + urlParamKeys = [], + queryParamKeys = [], + catchNotFound = false, + keyTransform, + payloadKey, + returnResourceIdInLocationHeader, + ignoredKeys, + headers, + }: RequestArgs) { + return async ( + payload: any = {}, + options?: Pick + ) => { + const baseParams = this.getBaseParams?.() ?? {}; + + // Filter query parameters by queryParamKeys + const queryParams = queryParamKeys + ? pick(payload, queryParamKeys) + : undefined; + + // Add filtered payload parameters to base parameters + const allUrlParamKeys = [...Object.keys(baseParams), ...urlParamKeys]; + const urlParams = { ...baseParams, ...pick(payload, allUrlParamKeys) }; + + // Omit url parameters and query parameters from payload + const omittedKeys = ignoredKeys + ? [...allUrlParamKeys, ...queryParamKeys].filter( + (key) => !ignoredKeys.includes(key) + ) + : [...allUrlParamKeys, ...queryParamKeys]; + + payload = omit(payload, omittedKeys); + + // Transform keys of both payload and queryParams + if (keyTransform) { + this.transformKey(payload, keyTransform); + this.transformKey(queryParams, keyTransform); + } + + return this.requestWithParams({ + method, + path, + payload, + urlParams, + queryParams, + // catchNotFound precedence: global > local > default + catchNotFound, + ...(this.client.getGlobalRequestArgOptions() ?? options ?? {}), + payloadKey, + returnResourceIdInLocationHeader, + headers, + }); + }; + } + + public updateRequest({ + method, + path = "", + urlParamKeys = [], + queryParamKeys = [], + catchNotFound = false, + keyTransform, + payloadKey, + returnResourceIdInLocationHeader, + headers, + }: RequestArgs) { + return async (query: any = {}, payload: any = {}) => { + const baseParams = this.getBaseParams?.() ?? {}; + + // Filter query parameters by queryParamKeys + const queryParams = queryParamKeys + ? pick(query, queryParamKeys) + : undefined; + + // Add filtered query parameters to base parameters + const allUrlParamKeys = [...Object.keys(baseParams), ...urlParamKeys]; + const urlParams = { + ...baseParams, + ...pick(query, allUrlParamKeys), + }; + + // Transform keys of queryParams + if (keyTransform) { + this.transformKey(queryParams, keyTransform); + } + + return this.requestWithParams({ + method, + path, + payload, + urlParams, + queryParams, + catchNotFound, + payloadKey, + returnResourceIdInLocationHeader, + headers, + }); + }; + } + + private async requestWithParams({ + method, + path, + payload, + urlParams, + queryParams, + catchNotFound, + payloadKey, + returnResourceIdInLocationHeader, + headers, + }: { + method: Method; + path: string; + payload: any; + urlParams: any; + queryParams?: Record; + catchNotFound: boolean; + payloadKey?: string; + returnResourceIdInLocationHeader?: { field: string }; + headers?: HeadersInit; + }) { + const newPath = urlJoin(this.basePath, path); + + // Parse template and replace with values from urlParams + const pathTemplate = parseTemplate(newPath); + const parsedPath = pathTemplate.expand(urlParams); + const url = new URL(`${this.getBaseUrl?.() ?? ""}${parsedPath}`); + const requestOptions = { ...this.client.getRequestOptions() }; + const requestHeaders = new Headers([ + ...new Headers(requestOptions.headers).entries(), + ["authorization", `Bearer ${await this.client.getAccessToken()}`], + ["accept", "application/json, text/plain, */*"], + ...new Headers(headers).entries(), + ]); + + const searchParams: Record = {}; + + // Add payload parameters to search params if method is 'GET'. + if (method === "GET") { + Object.assign(searchParams, payload); + } else if (requestHeaders.get("content-type") === "text/plain") { + // Pass the payload as a plain string if the content type is 'text/plain'. + requestOptions.body = payload as unknown as string; + } else { + // Otherwise assume it's JSON and stringify it. + requestOptions.body = JSON.stringify( + payloadKey ? payload[payloadKey] : payload + ); + } + + if (!requestHeaders.has("content-type")) { + requestHeaders.set("content-type", "application/json"); + } + + if (queryParams) { + Object.assign(searchParams, queryParams); + } + + url.search = stringifyQueryParams(searchParams); + + try { + const res = await fetchWithError(url, { + ...requestOptions, + headers: requestHeaders, + method, + }); + + // now we get the response of the http request + // if `resourceIdInLocationHeader` is true, we'll get the resourceId from the location header field + // todo: find a better way to find the id in path, maybe some kind of pattern matching + // for now, we simply split the last sub-path of the path returned in location header field + if (returnResourceIdInLocationHeader) { + const locationHeader = res.headers.get("location"); + + if (typeof locationHeader !== "string") { + throw new Error( + `location header is not found in request: ${res.url}` + ); + } + + const resourceId = last(locationHeader.split(SLASH)); + if (!resourceId) { + // throw an error to let users know the response is not expected + throw new Error( + `resourceId is not found in Location header from request: ${res.url}` + ); + } + + // return with format {[field]: string} + const { field } = returnResourceIdInLocationHeader; + return { [field]: resourceId }; + } + + if ( + Object.entries(headers || []).find( + ([key, value]) => + key.toLowerCase() === "accept" && + value === "application/octet-stream" + ) + ) { + return res.arrayBuffer(); + } + + return parseResponse(res); + } catch (err) { + if ( + err instanceof NetworkError && + err.response.status === 404 && + catchNotFound + ) { + return null; + } + throw err; + } + } + + private transformKey(payload: any, keyMapping: Record) { + if (!payload) { + return; + } + + Object.keys(keyMapping).some((key) => { + if (isUndefined(payload[key])) { + // Skip if undefined + return false; + } + const newKey = keyMapping[key]; + payload[newKey] = payload[key]; + delete payload[key]; + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/attackDetection.ts b/js/libs/keycloak-admin-client/src/resources/attackDetection.ts new file mode 100644 index 0000000000..384608b564 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/attackDetection.ts @@ -0,0 +1,35 @@ +import Resource from "./resource.js"; +import type KeycloakAdminClient from "../index.js"; + +export class AttackDetection extends Resource<{ realm?: string }> { + public findOne = this.makeRequest< + { id: string }, + Record | undefined + >({ + method: "GET", + path: "/users/{id}", + urlParamKeys: ["id"], + catchNotFound: true, + }); + + public del = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/users/{id}", + urlParamKeys: ["id"], + }); + + public delAll = this.makeRequest<{}, void>({ + method: "DELETE", + path: "/users", + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/attack-detection/brute-force", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/authenticationManagement.ts b/js/libs/keycloak-admin-client/src/resources/authenticationManagement.ts new file mode 100644 index 0000000000..f7235b9d74 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/authenticationManagement.ts @@ -0,0 +1,286 @@ +import Resource from "./resource.js"; +import type RequiredActionProviderRepresentation from "../defs/requiredActionProviderRepresentation.js"; +import type { KeycloakAdminClient } from "../client.js"; +import type AuthenticationExecutionInfoRepresentation from "../defs/authenticationExecutionInfoRepresentation.js"; +import type AuthenticationFlowRepresentation from "../defs/authenticationFlowRepresentation.js"; +import type AuthenticatorConfigRepresentation from "../defs/authenticatorConfigRepresentation.js"; +import type { AuthenticationProviderRepresentation } from "../defs/authenticatorConfigRepresentation.js"; +import type AuthenticatorConfigInfoRepresentation from "../defs/authenticatorConfigInfoRepresentation.js"; +import type RequiredActionProviderSimpleRepresentation from "../defs/requiredActionProviderSimpleRepresentation.js"; + +export class AuthenticationManagement extends Resource { + /** + * Authentication Management + * https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_authentication_management_resource + */ + + // Register a new required action + public registerRequiredAction = this.makeRequest>({ + method: "POST", + path: "/register-required-action", + }); + + // Get required actions. Returns a list of required actions. + public getRequiredActions = this.makeRequest< + void, + RequiredActionProviderRepresentation[] + >({ + method: "GET", + path: "/required-actions", + }); + + // Get required action for alias + public getRequiredActionForAlias = this.makeRequest<{ + alias: string; + }>({ + method: "GET", + path: "/required-actions/{alias}", + urlParamKeys: ["alias"], + catchNotFound: true, + }); + + public getClientAuthenticatorProviders = this.makeRequest< + void, + AuthenticationProviderRepresentation[] + >({ + method: "GET", + path: "/client-authenticator-providers", + }); + + public getAuthenticatorProviders = this.makeRequest< + void, + AuthenticationProviderRepresentation[] + >({ + method: "GET", + path: "/authenticator-providers", + }); + + public getFormActionProviders = this.makeRequest< + void, + AuthenticationProviderRepresentation[] + >({ + method: "GET", + path: "/form-action-providers", + }); + + // Update required action + public updateRequiredAction = this.makeUpdateRequest< + { alias: string }, + RequiredActionProviderRepresentation, + void + >({ + method: "PUT", + path: "/required-actions/{alias}", + urlParamKeys: ["alias"], + }); + + // Delete required action + public deleteRequiredAction = this.makeRequest<{ alias: string }, void>({ + method: "DELETE", + path: "/required-actions/{alias}", + urlParamKeys: ["alias"], + }); + + // Lower required action’s priority + public lowerRequiredActionPriority = this.makeRequest<{ + alias: string; + }>({ + method: "POST", + path: "/required-actions/{alias}/lower-priority", + urlParamKeys: ["alias"], + }); + + // Raise required action’s priority + public raiseRequiredActionPriority = this.makeRequest<{ + alias: string; + }>({ + method: "POST", + path: "/required-actions/{alias}/raise-priority", + urlParamKeys: ["alias"], + }); + + // Get unregistered required actions Returns a list of unregistered required actions. + public getUnregisteredRequiredActions = this.makeRequest< + void, + RequiredActionProviderSimpleRepresentation[] + >({ + method: "GET", + path: "/unregistered-required-actions", + }); + + public getFlows = this.makeRequest<{}, AuthenticationFlowRepresentation[]>({ + method: "GET", + path: "/flows", + }); + + public getFlow = this.makeRequest< + { flowId: string }, + AuthenticationFlowRepresentation + >({ + method: "GET", + path: "/flows/{flowId}", + urlParamKeys: ["flowId"], + }); + + public getFormProviders = this.makeRequest< + void, + AuthenticationProviderRepresentation[] + >({ + method: "GET", + path: "/form-providers", + }); + + public createFlow = this.makeRequest< + AuthenticationFlowRepresentation, + AuthenticationFlowRepresentation + >({ + method: "POST", + path: "/flows", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + public copyFlow = this.makeRequest<{ flow: string; newName: string }>({ + method: "POST", + path: "/flows/{flow}/copy", + urlParamKeys: ["flow"], + }); + + public deleteFlow = this.makeRequest<{ flowId: string }>({ + method: "DELETE", + path: "/flows/{flowId}", + urlParamKeys: ["flowId"], + }); + + public updateFlow = this.makeUpdateRequest< + { flowId: string }, + AuthenticationFlowRepresentation + >({ + method: "PUT", + path: "/flows/{flowId}", + urlParamKeys: ["flowId"], + }); + + public getExecutions = this.makeRequest< + { flow: string }, + AuthenticationExecutionInfoRepresentation[] + >({ + method: "GET", + path: "/flows/{flow}/executions", + urlParamKeys: ["flow"], + }); + + public addExecution = this.makeUpdateRequest< + { flow: string }, + AuthenticationExecutionInfoRepresentation + >({ + method: "POST", + path: "/flows/{flow}/executions", + urlParamKeys: ["flow"], + }); + + public addExecutionToFlow = this.makeRequest< + { flow: string; provider: string }, + AuthenticationExecutionInfoRepresentation + >({ + method: "POST", + path: "/flows/{flow}/executions/execution", + urlParamKeys: ["flow"], + returnResourceIdInLocationHeader: { field: "id" }, + }); + + public addFlowToFlow = this.makeRequest< + { + flow: string; + alias: string; + type: string; + provider: string; + description: string; + }, + AuthenticationFlowRepresentation + >({ + method: "POST", + path: "/flows/{flow}/executions/flow", + urlParamKeys: ["flow"], + returnResourceIdInLocationHeader: { field: "id" }, + }); + + public updateExecution = this.makeUpdateRequest< + { flow: string }, + AuthenticationExecutionInfoRepresentation + >({ + method: "PUT", + path: "/flows/{flow}/executions", + urlParamKeys: ["flow"], + }); + + public delExecution = this.makeRequest<{ id: string }>({ + method: "DELETE", + path: "/executions/{id}", + urlParamKeys: ["id"], + }); + + public lowerPriorityExecution = this.makeRequest<{ id: string }>({ + method: "POST", + path: "/executions/{id}/lower-priority", + urlParamKeys: ["id"], + }); + + public raisePriorityExecution = this.makeRequest<{ id: string }>({ + method: "POST", + path: "/executions/{id}/raise-priority", + urlParamKeys: ["id"], + }); + + public getConfigDescription = this.makeRequest< + { providerId: string }, + AuthenticatorConfigInfoRepresentation + >({ + method: "GET", + path: "config-description/{providerId}", + urlParamKeys: ["providerId"], + }); + + public createConfig = this.makeRequest< + AuthenticatorConfigRepresentation, + AuthenticatorConfigRepresentation + >({ + method: "POST", + path: "/executions/{id}/config", + urlParamKeys: ["id"], + returnResourceIdInLocationHeader: { field: "id" }, + }); + + public updateConfig = this.makeRequest< + AuthenticatorConfigRepresentation, + void + >({ + method: "PUT", + path: "/config/{id}", + urlParamKeys: ["id"], + }); + + public getConfig = this.makeRequest< + { id: string }, + AuthenticatorConfigRepresentation + >({ + method: "GET", + path: "/config/{id}", + urlParamKeys: ["id"], + }); + + public delConfig = this.makeRequest<{ id: string }>({ + method: "DELETE", + path: "/config/{id}", + urlParamKeys: ["id"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/authentication", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/cache.ts b/js/libs/keycloak-admin-client/src/resources/cache.ts new file mode 100644 index 0000000000..ce6baf81d5 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/cache.ts @@ -0,0 +1,19 @@ +import Resource from "./resource.js"; +import type { KeycloakAdminClient } from "../client.js"; + +export class Cache extends Resource<{ realm?: string }> { + public clearUserCache = this.makeRequest<{}, void>({ + method: "POST", + path: "/clear-user-cache", + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/clientPolicies.ts b/js/libs/keycloak-admin-client/src/resources/clientPolicies.ts new file mode 100644 index 0000000000..23bb5b4393 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/clientPolicies.ts @@ -0,0 +1,50 @@ +import Resource from "./resource.js"; +import type { KeycloakAdminClient } from "../client.js"; +import type ClientProfilesRepresentation from "../defs/clientProfilesRepresentation.js"; +import type ClientPoliciesRepresentation from "../defs/clientPoliciesRepresentation.js"; + +/** + * https://www.keycloak.org/docs-api/15.0/rest-api/#_client_registration_policy_resource + */ +export class ClientPolicies extends Resource<{ realm?: string }> { + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/client-policies", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } + + /* Client Profiles */ + + public listProfiles = this.makeRequest< + { includeGlobalProfiles?: boolean }, + ClientProfilesRepresentation + >({ + method: "GET", + path: "/profiles", + queryParamKeys: ["include-global-profiles"], + keyTransform: { + includeGlobalProfiles: "include-global-profiles", + }, + }); + + public createProfiles = this.makeRequest({ + method: "PUT", + path: "/profiles", + }); + + /* Client Policies */ + + public listPolicies = this.makeRequest<{}, ClientPoliciesRepresentation>({ + method: "GET", + path: "/policies", + }); + + public updatePolicy = this.makeRequest({ + method: "PUT", + path: "/policies", + }); +} diff --git a/js/libs/keycloak-admin-client/src/resources/clientScopes.ts b/js/libs/keycloak-admin-client/src/resources/clientScopes.ts new file mode 100644 index 0000000000..e08f66415b --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/clientScopes.ts @@ -0,0 +1,336 @@ +import type ClientScopeRepresentation from "../defs/clientScopeRepresentation.js"; +import Resource from "./resource.js"; +import type { KeycloakAdminClient } from "../client.js"; +import type ProtocolMapperRepresentation from "../defs/protocolMapperRepresentation.js"; +import type MappingsRepresentation from "../defs/mappingsRepresentation.js"; +import type RoleRepresentation from "../defs/roleRepresentation.js"; + +export class ClientScopes extends Resource<{ realm?: string }> { + public find = this.makeRequest<{}, ClientScopeRepresentation[]>({ + method: "GET", + path: "/client-scopes", + }); + + public create = this.makeRequest({ + method: "POST", + path: "/client-scopes", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + /** + * Client-Scopes by id + */ + + public findOne = this.makeRequest< + { id: string }, + ClientScopeRepresentation | undefined + >({ + method: "GET", + path: "/client-scopes/{id}", + urlParamKeys: ["id"], + catchNotFound: true, + }); + + public update = this.makeUpdateRequest< + { id: string }, + ClientScopeRepresentation, + void + >({ + method: "PUT", + path: "/client-scopes/{id}", + urlParamKeys: ["id"], + }); + + public del = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/client-scopes/{id}", + urlParamKeys: ["id"], + }); + + /** + * Default Client-Scopes + */ + + public listDefaultClientScopes = this.makeRequest< + void, + ClientScopeRepresentation[] + >({ + method: "GET", + path: "/default-default-client-scopes", + }); + + public addDefaultClientScope = this.makeRequest<{ id: string }, void>({ + method: "PUT", + path: "/default-default-client-scopes/{id}", + urlParamKeys: ["id"], + }); + + public delDefaultClientScope = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/default-default-client-scopes/{id}", + urlParamKeys: ["id"], + }); + + /** + * Default Optional Client-Scopes + */ + + public listDefaultOptionalClientScopes = this.makeRequest< + void, + ClientScopeRepresentation[] + >({ + method: "GET", + path: "/default-optional-client-scopes", + }); + + public addDefaultOptionalClientScope = this.makeRequest<{ id: string }, void>( + { + method: "PUT", + path: "/default-optional-client-scopes/{id}", + urlParamKeys: ["id"], + } + ); + + public delDefaultOptionalClientScope = this.makeRequest<{ id: string }, void>( + { + method: "DELETE", + path: "/default-optional-client-scopes/{id}", + urlParamKeys: ["id"], + } + ); + + /** + * Protocol Mappers + */ + + public addMultipleProtocolMappers = this.makeUpdateRequest< + { id: string }, + ProtocolMapperRepresentation[], + void + >({ + method: "POST", + path: "/client-scopes/{id}/protocol-mappers/add-models", + urlParamKeys: ["id"], + }); + + public addProtocolMapper = this.makeUpdateRequest< + { id: string }, + ProtocolMapperRepresentation, + void + >({ + method: "POST", + path: "/client-scopes/{id}/protocol-mappers/models", + urlParamKeys: ["id"], + }); + + public listProtocolMappers = this.makeRequest< + { id: string }, + ProtocolMapperRepresentation[] + >({ + method: "GET", + path: "/client-scopes/{id}/protocol-mappers/models", + urlParamKeys: ["id"], + }); + + public findProtocolMapper = this.makeRequest< + { id: string; mapperId: string }, + ProtocolMapperRepresentation | undefined + >({ + method: "GET", + path: "/client-scopes/{id}/protocol-mappers/models/{mapperId}", + urlParamKeys: ["id", "mapperId"], + catchNotFound: true, + }); + + public findProtocolMappersByProtocol = this.makeRequest< + { id: string; protocol: string }, + ProtocolMapperRepresentation[] + >({ + method: "GET", + path: "/client-scopes/{id}/protocol-mappers/protocol/{protocol}", + urlParamKeys: ["id", "protocol"], + catchNotFound: true, + }); + + public updateProtocolMapper = this.makeUpdateRequest< + { id: string; mapperId: string }, + ProtocolMapperRepresentation, + void + >({ + method: "PUT", + path: "/client-scopes/{id}/protocol-mappers/models/{mapperId}", + urlParamKeys: ["id", "mapperId"], + }); + + public delProtocolMapper = this.makeRequest< + { id: string; mapperId: string }, + void + >({ + method: "DELETE", + path: "/client-scopes/{id}/protocol-mappers/models/{mapperId}", + urlParamKeys: ["id", "mapperId"], + }); + + /** + * Scope Mappings + */ + public listScopeMappings = this.makeRequest< + { id: string }, + MappingsRepresentation + >({ + method: "GET", + path: "/client-scopes/{id}/scope-mappings", + urlParamKeys: ["id"], + }); + + public addClientScopeMappings = this.makeUpdateRequest< + { id: string; client: string }, + RoleRepresentation[], + void + >({ + method: "POST", + path: "/client-scopes/{id}/scope-mappings/clients/{client}", + urlParamKeys: ["id", "client"], + }); + + public listClientScopeMappings = this.makeRequest< + { id: string; client: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/client-scopes/{id}/scope-mappings/clients/{client}", + urlParamKeys: ["id", "client"], + }); + + public listAvailableClientScopeMappings = this.makeRequest< + { id: string; client: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/client-scopes/{id}/scope-mappings/clients/{client}/available", + urlParamKeys: ["id", "client"], + }); + + public listCompositeClientScopeMappings = this.makeRequest< + { id: string; client: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/client-scopes/{id}/scope-mappings/clients/{client}/composite", + urlParamKeys: ["id", "client"], + }); + + public delClientScopeMappings = this.makeUpdateRequest< + { id: string; client: string }, + RoleRepresentation[], + void + >({ + method: "DELETE", + path: "/client-scopes/{id}/scope-mappings/clients/{client}", + urlParamKeys: ["id", "client"], + }); + + public addRealmScopeMappings = this.makeUpdateRequest< + { id: string }, + RoleRepresentation[], + void + >({ + method: "POST", + path: "/client-scopes/{id}/scope-mappings/realm", + urlParamKeys: ["id"], + }); + + public listRealmScopeMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/client-scopes/{id}/scope-mappings/realm", + urlParamKeys: ["id"], + }); + + public listAvailableRealmScopeMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/client-scopes/{id}/scope-mappings/realm/available", + urlParamKeys: ["id"], + }); + + public listCompositeRealmScopeMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/client-scopes/{id}/scope-mappings/realm/composite", + urlParamKeys: ["id"], + }); + + public delRealmScopeMappings = this.makeUpdateRequest< + { id: string }, + RoleRepresentation[], + void + >({ + method: "DELETE", + path: "/client-scopes/{id}/scope-mappings/realm", + urlParamKeys: ["id"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } + + /** + * Find client scope by name. + */ + public async findOneByName(payload: { + realm?: string; + name: string; + }): Promise { + const allScopes = await this.find({ + ...(payload.realm ? { realm: payload.realm } : {}), + }); + return allScopes.find((item) => item.name === payload.name); + } + + /** + * Delete client scope by name. + */ + public async delByName(payload: { + realm?: string; + name: string; + }): Promise { + const scope = await this.findOneByName(payload); + + if (!scope) { + throw new Error("Scope not found."); + } + + await this.del({ + ...(payload.realm ? { realm: payload.realm } : {}), + id: scope.id!, + }); + } + + /** + * Find single protocol mapper by name. + */ + public async findProtocolMapperByName(payload: { + realm?: string; + id: string; + name: string; + }): Promise { + const allProtocolMappers = await this.listProtocolMappers({ + id: payload.id, + ...(payload.realm ? { realm: payload.realm } : {}), + }); + return allProtocolMappers.find((mapper) => mapper.name === payload.name); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/clients.ts b/js/libs/keycloak-admin-client/src/resources/clients.ts new file mode 100644 index 0000000000..d3b1dc32ae --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/clients.ts @@ -0,0 +1,1040 @@ +import type { KeycloakAdminClient } from "../client.js"; +import type CertificateRepresentation from "../defs/certificateRepresentation.js"; +import type ClientRepresentation from "../defs/clientRepresentation.js"; +import type ClientScopeRepresentation from "../defs/clientScopeRepresentation.js"; +import type CredentialRepresentation from "../defs/credentialRepresentation.js"; +import type GlobalRequestResult from "../defs/globalRequestResult.js"; +import type KeyStoreConfig from "../defs/keystoreConfig.js"; +import type { ManagementPermissionReference } from "../defs/managementPermissionReference.js"; +import type MappingsRepresentation from "../defs/mappingsRepresentation.js"; +import type PolicyEvaluationResponse from "../defs/policyEvaluationResponse.js"; +import type PolicyProviderRepresentation from "../defs/policyProviderRepresentation.js"; +import type PolicyRepresentation from "../defs/policyRepresentation.js"; +import type ProtocolMapperRepresentation from "../defs/protocolMapperRepresentation.js"; +import type ResourceEvaluation from "../defs/resourceEvaluation.js"; +import type ResourceRepresentation from "../defs/resourceRepresentation.js"; +import type ResourceServerRepresentation from "../defs/resourceServerRepresentation.js"; +import type RoleRepresentation from "../defs/roleRepresentation.js"; +import type ScopeRepresentation from "../defs/scopeRepresentation.js"; +import type UserRepresentation from "../defs/userRepresentation.js"; +import type UserSessionRepresentation from "../defs/userSessionRepresentation.js"; +import Resource from "./resource.js"; + +export interface PaginatedQuery { + first?: number; + max?: number; +} + +export interface ClientQuery extends PaginatedQuery { + clientId?: string; + viewableOnly?: boolean; + search?: boolean; +} + +export interface ResourceQuery extends PaginatedQuery { + id?: string; + name?: string; + type?: string; + owner?: string; + uri?: string; + deep?: boolean; +} + +export interface PolicyQuery extends PaginatedQuery { + id?: string; + name?: string; + type?: string; + resource?: string; + scope?: string; + permission?: string; + owner?: string; + fields?: string; +} + +export class Clients extends Resource<{ realm?: string }> { + public find = this.makeRequest({ + method: "GET", + }); + + public create = this.makeRequest({ + method: "POST", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + /** + * Single client + */ + + public findOne = this.makeRequest< + { id: string }, + ClientRepresentation | undefined + >({ + method: "GET", + path: "/{id}", + urlParamKeys: ["id"], + catchNotFound: true, + }); + + public update = this.makeUpdateRequest< + { id: string }, + ClientRepresentation, + void + >({ + method: "PUT", + path: "/{id}", + urlParamKeys: ["id"], + }); + + public del = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/{id}", + urlParamKeys: ["id"], + }); + + /** + * Client roles + */ + + public createRole = this.makeRequest< + RoleRepresentation, + { roleName: string } + >({ + method: "POST", + path: "/{id}/roles", + urlParamKeys: ["id"], + returnResourceIdInLocationHeader: { field: "roleName" }, + }); + + public listRoles = this.makeRequest<{ id: string }, RoleRepresentation[]>({ + method: "GET", + path: "/{id}/roles", + urlParamKeys: ["id"], + }); + + public findRole = this.makeRequest< + { id: string; roleName: string }, + RoleRepresentation + >({ + method: "GET", + path: "/{id}/roles/{roleName}", + urlParamKeys: ["id", "roleName"], + catchNotFound: true, + }); + + public updateRole = this.makeUpdateRequest< + { id: string; roleName: string }, + RoleRepresentation, + void + >({ + method: "PUT", + path: "/{id}/roles/{roleName}", + urlParamKeys: ["id", "roleName"], + }); + + public delRole = this.makeRequest<{ id: string; roleName: string }, void>({ + method: "DELETE", + path: "/{id}/roles/{roleName}", + urlParamKeys: ["id", "roleName"], + }); + + public findUsersWithRole = this.makeRequest< + { id: string; roleName: string; first?: number; max?: number }, + UserRepresentation[] + >({ + method: "GET", + path: "/{id}/roles/{roleName}/users", + urlParamKeys: ["id", "roleName"], + }); + + /** + * Service account user + */ + + public getServiceAccountUser = this.makeRequest< + { id: string }, + UserRepresentation + >({ + method: "GET", + path: "/{id}/service-account-user", + urlParamKeys: ["id"], + }); + + /** + * Client secret + */ + + public generateNewClientSecret = this.makeRequest< + { id: string }, + CredentialRepresentation + >({ + method: "POST", + path: "/{id}/client-secret", + urlParamKeys: ["id"], + }); + + public invalidateSecret = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/{id}/client-secret/rotated", + urlParamKeys: ["id"], + }); + + public generateRegistrationAccessToken = this.makeRequest< + { id: string }, + { registrationAccessToken: string } + >({ + method: "POST", + path: "/{id}/registration-access-token", + urlParamKeys: ["id"], + }); + + public getClientSecret = this.makeRequest< + { id: string }, + CredentialRepresentation + >({ + method: "GET", + path: "/{id}/client-secret", + urlParamKeys: ["id"], + }); + + /** + * Client Scopes + */ + public listDefaultClientScopes = this.makeRequest< + { id: string }, + ClientScopeRepresentation[] + >({ + method: "GET", + path: "/{id}/default-client-scopes", + urlParamKeys: ["id"], + }); + + public addDefaultClientScope = this.makeRequest< + { id: string; clientScopeId: string }, + void + >({ + method: "PUT", + path: "/{id}/default-client-scopes/{clientScopeId}", + urlParamKeys: ["id", "clientScopeId"], + }); + + public delDefaultClientScope = this.makeRequest< + { id: string; clientScopeId: string }, + void + >({ + method: "DELETE", + path: "/{id}/default-client-scopes/{clientScopeId}", + urlParamKeys: ["id", "clientScopeId"], + }); + + public listOptionalClientScopes = this.makeRequest< + { id: string }, + ClientScopeRepresentation[] + >({ + method: "GET", + path: "/{id}/optional-client-scopes", + urlParamKeys: ["id"], + }); + + public addOptionalClientScope = this.makeRequest< + { id: string; clientScopeId: string }, + void + >({ + method: "PUT", + path: "/{id}/optional-client-scopes/{clientScopeId}", + urlParamKeys: ["id", "clientScopeId"], + }); + + public delOptionalClientScope = this.makeRequest< + { id: string; clientScopeId: string }, + void + >({ + method: "DELETE", + path: "/{id}/optional-client-scopes/{clientScopeId}", + urlParamKeys: ["id", "clientScopeId"], + }); + + /** + * Protocol Mappers + */ + + public addMultipleProtocolMappers = this.makeUpdateRequest< + { id: string }, + ProtocolMapperRepresentation[], + void + >({ + method: "POST", + path: "/{id}/protocol-mappers/add-models", + urlParamKeys: ["id"], + }); + + public addProtocolMapper = this.makeUpdateRequest< + { id: string }, + ProtocolMapperRepresentation, + void + >({ + method: "POST", + path: "/{id}/protocol-mappers/models", + urlParamKeys: ["id"], + }); + + public listProtocolMappers = this.makeRequest< + { id: string }, + ProtocolMapperRepresentation[] + >({ + method: "GET", + path: "/{id}/protocol-mappers/models", + urlParamKeys: ["id"], + }); + + public findProtocolMapperById = this.makeRequest< + { id: string; mapperId: string }, + ProtocolMapperRepresentation + >({ + method: "GET", + path: "/{id}/protocol-mappers/models/{mapperId}", + urlParamKeys: ["id", "mapperId"], + catchNotFound: true, + }); + + public findProtocolMappersByProtocol = this.makeRequest< + { id: string; protocol: string }, + ProtocolMapperRepresentation[] + >({ + method: "GET", + path: "/{id}/protocol-mappers/protocol/{protocol}", + urlParamKeys: ["id", "protocol"], + catchNotFound: true, + }); + + public updateProtocolMapper = this.makeUpdateRequest< + { id: string; mapperId: string }, + ProtocolMapperRepresentation, + void + >({ + method: "PUT", + path: "/{id}/protocol-mappers/models/{mapperId}", + urlParamKeys: ["id", "mapperId"], + }); + + public delProtocolMapper = this.makeRequest< + { id: string; mapperId: string }, + void + >({ + method: "DELETE", + path: "/{id}/protocol-mappers/models/{mapperId}", + urlParamKeys: ["id", "mapperId"], + }); + + /** + * Scope Mappings + */ + public listScopeMappings = this.makeRequest< + { id: string }, + MappingsRepresentation + >({ + method: "GET", + path: "/{id}/scope-mappings", + urlParamKeys: ["id"], + }); + + public addClientScopeMappings = this.makeUpdateRequest< + { id: string; client: string }, + RoleRepresentation[], + void + >({ + method: "POST", + path: "/{id}/scope-mappings/clients/{client}", + urlParamKeys: ["id", "client"], + }); + + public listClientScopeMappings = this.makeRequest< + { id: string; client: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/scope-mappings/clients/{client}", + urlParamKeys: ["id", "client"], + }); + + public listAvailableClientScopeMappings = this.makeRequest< + { id: string; client: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/scope-mappings/clients/{client}/available", + urlParamKeys: ["id", "client"], + }); + + public listCompositeClientScopeMappings = this.makeRequest< + { id: string; client: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/scope-mappings/clients/{client}/composite", + urlParamKeys: ["id", "client"], + }); + + public delClientScopeMappings = this.makeUpdateRequest< + { id: string; client: string }, + RoleRepresentation[], + void + >({ + method: "DELETE", + path: "/{id}/scope-mappings/clients/{client}", + urlParamKeys: ["id", "client"], + }); + + public evaluatePermission = this.makeRequest< + { + id: string; + roleContainer: string; + type: "granted" | "not-granted"; + scope: string; + }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/evaluate-scopes/scope-mappings/{roleContainer}/{type}", + urlParamKeys: ["id", "roleContainer", "type"], + queryParamKeys: ["scope"], + }); + + public evaluateListProtocolMapper = this.makeRequest< + { + id: string; + scope: string; + }, + ProtocolMapperRepresentation[] + >({ + method: "GET", + path: "/{id}/evaluate-scopes/protocol-mappers", + urlParamKeys: ["id"], + queryParamKeys: ["scope"], + }); + + public evaluateGenerateAccessToken = this.makeRequest< + { id: string; scope: string; userId: string }, + Record + >({ + method: "GET", + path: "/{id}/evaluate-scopes/generate-example-access-token", + urlParamKeys: ["id"], + queryParamKeys: ["scope", "userId"], + }); + + public evaluateGenerateUserInfo = this.makeRequest< + { id: string; scope: string; userId: string }, + Record + >({ + method: "GET", + path: "/{id}/evaluate-scopes/generate-example-userinfo", + urlParamKeys: ["id"], + queryParamKeys: ["scope", "userId"], + }); + + public evaluateGenerateIdToken = this.makeRequest< + { id: string; scope: string; userId: string }, + Record + >({ + method: "GET", + path: "/{id}/evaluate-scopes/generate-example-id-token", + urlParamKeys: ["id"], + queryParamKeys: ["scope", "userId"], + }); + + public addRealmScopeMappings = this.makeUpdateRequest< + { id: string }, + RoleRepresentation[], + void + >({ + method: "POST", + path: "/{id}/scope-mappings/realm", + urlParamKeys: ["id", "client"], + }); + + public listRealmScopeMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/scope-mappings/realm", + urlParamKeys: ["id"], + }); + + public listAvailableRealmScopeMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/scope-mappings/realm/available", + urlParamKeys: ["id"], + }); + + public listCompositeRealmScopeMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/scope-mappings/realm/composite", + urlParamKeys: ["id"], + }); + + public delRealmScopeMappings = this.makeUpdateRequest< + { id: string }, + RoleRepresentation[], + void + >({ + method: "DELETE", + path: "/{id}/scope-mappings/realm", + urlParamKeys: ["id"], + }); + + /** + * Sessions + */ + public listSessions = this.makeRequest< + { id: string; first?: number; max?: number }, + UserSessionRepresentation[] + >({ + method: "GET", + path: "/{id}/user-sessions", + urlParamKeys: ["id"], + }); + + public listOfflineSessions = this.makeRequest< + { id: string; first?: number; max?: number }, + UserSessionRepresentation[] + >({ + method: "GET", + path: "/{id}/offline-sessions", + urlParamKeys: ["id"], + }); + + public getSessionCount = this.makeRequest<{ id: string }, { count: number }>({ + method: "GET", + path: "/{id}/session-count", + urlParamKeys: ["id"], + }); + + /** + * Resource + */ + + public getResourceServer = this.makeRequest< + { id: string }, + ResourceServerRepresentation + >({ + method: "GET", + path: "{id}/authz/resource-server", + urlParamKeys: ["id"], + }); + + public updateResourceServer = this.makeUpdateRequest< + { id: string }, + ResourceServerRepresentation, + void + >({ + method: "PUT", + path: "{id}/authz/resource-server", + urlParamKeys: ["id"], + }); + + public listResources = this.makeRequest< + ResourceQuery, + ResourceRepresentation[] + >({ + method: "GET", + path: "{id}/authz/resource-server/resource", + urlParamKeys: ["id"], + }); + + public createResource = this.makeUpdateRequest< + { id: string }, + ResourceRepresentation, + ResourceRepresentation + >({ + method: "POST", + path: "{id}/authz/resource-server/resource", + urlParamKeys: ["id"], + }); + + public getResource = this.makeRequest< + { id: string; resourceId: string }, + ResourceRepresentation + >({ + method: "GET", + path: "{id}/authz/resource-server/resource/{resourceId}", + urlParamKeys: ["id", "resourceId"], + }); + + public updateResource = this.makeUpdateRequest< + { id: string; resourceId: string }, + ResourceRepresentation, + void + >({ + method: "PUT", + path: "/{id}/authz/resource-server/resource/{resourceId}", + urlParamKeys: ["id", "resourceId"], + }); + + public delResource = this.makeRequest< + { id: string; resourceId: string }, + void + >({ + method: "DELETE", + path: "/{id}/authz/resource-server/resource/{resourceId}", + urlParamKeys: ["id", "resourceId"], + }); + + public importResource = this.makeUpdateRequest< + { id: string }, + ResourceServerRepresentation + >({ + method: "POST", + path: "/{id}/authz/resource-server/import", + urlParamKeys: ["id"], + }); + + public exportResource = this.makeRequest< + { id: string }, + ResourceServerRepresentation + >({ + method: "GET", + path: "/{id}/authz/resource-server/settings", + urlParamKeys: ["id"], + }); + + public evaluateResource = this.makeUpdateRequest< + { id: string }, + ResourceEvaluation, + PolicyEvaluationResponse + >({ + method: "POST", + path: "{id}/authz/resource-server/policy/evaluate", + urlParamKeys: ["id"], + }); + + /** + * Policy + */ + public listPolicies = this.makeRequest< + PolicyQuery, + PolicyRepresentation[] | "" + >({ + method: "GET", + path: "{id}/authz/resource-server/policy", + urlParamKeys: ["id"], + }); + + public findPolicyByName = this.makeRequest< + { id: string; name: string }, + PolicyRepresentation + >({ + method: "GET", + path: "{id}/authz/resource-server/policy/search", + urlParamKeys: ["id"], + }); + + public updatePolicy = this.makeUpdateRequest< + { id: string; type: string; policyId: string }, + PolicyRepresentation, + void + >({ + method: "PUT", + path: "/{id}/authz/resource-server/policy/{type}/{policyId}", + urlParamKeys: ["id", "type", "policyId"], + }); + + public createPolicy = this.makeUpdateRequest< + { id: string; type: string }, + PolicyRepresentation, + PolicyRepresentation + >({ + method: "POST", + path: "/{id}/authz/resource-server/policy/{type}", + urlParamKeys: ["id", "type"], + }); + + public findOnePolicy = this.makeRequest< + { id: string; type: string; policyId: string }, + void + >({ + method: "GET", + path: "/{id}/authz/resource-server/policy/{type}/{policyId}", + urlParamKeys: ["id", "type", "policyId"], + catchNotFound: true, + }); + + public listDependentPolicies = this.makeRequest< + { id: string; policyId: string }, + PolicyRepresentation[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/policy/{policyId}/dependentPolicies", + urlParamKeys: ["id", "policyId"], + }); + + public delPolicy = this.makeRequest<{ id: string; policyId: string }, void>({ + method: "DELETE", + path: "{id}/authz/resource-server/policy/{policyId}", + urlParamKeys: ["id", "policyId"], + }); + + public listPolicyProviders = this.makeRequest< + { id: string }, + PolicyProviderRepresentation[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/policy/providers", + urlParamKeys: ["id"], + }); + + public async createOrUpdatePolicy(payload: { + id: string; + policyName: string; + policy: PolicyRepresentation; + }): Promise { + const policyFound = await this.findPolicyByName({ + id: payload.id, + name: payload.policyName, + }); + if (policyFound) { + await this.updatePolicy( + { + id: payload.id, + policyId: policyFound.id!, + type: payload.policy.type!, + }, + payload.policy + ); + return this.findPolicyByName({ + id: payload.id, + name: payload.policyName, + }); + } else { + return this.createPolicy( + { id: payload.id, type: payload.policy.type! }, + payload.policy + ); + } + } + + /** + * Scopes + */ + public listAllScopes = this.makeRequest< + { id: string; name?: string; deep?: boolean } & PaginatedQuery, + ScopeRepresentation[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/scope", + urlParamKeys: ["id"], + }); + + public listAllResourcesByScope = this.makeRequest< + { id: string; scopeId: string }, + ResourceRepresentation[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/scope/{scopeId}/resources", + urlParamKeys: ["id", "scopeId"], + }); + + public listAllPermissionsByScope = this.makeRequest< + { id: string; scopeId: string }, + PolicyRepresentation[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/scope/{scopeId}/permissions", + urlParamKeys: ["id", "scopeId"], + }); + + public listPermissionsByResource = this.makeRequest< + { id: string; resourceId: string }, + ResourceServerRepresentation[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/resource/{resourceId}/permissions", + urlParamKeys: ["id", "resourceId"], + }); + + public listScopesByResource = this.makeRequest< + { id: string; resourceName: string }, + { id: string; name: string }[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/resource/{resourceName}/scopes", + urlParamKeys: ["id", "resourceName"], + }); + + public createAuthorizationScope = this.makeUpdateRequest< + { id: string }, + ScopeRepresentation + >({ + method: "POST", + path: "{id}/authz/resource-server/scope", + urlParamKeys: ["id"], + }); + + public updateAuthorizationScope = this.makeUpdateRequest< + { id: string; scopeId: string }, + ScopeRepresentation + >({ + method: "PUT", + path: "/{id}/authz/resource-server/scope/{scopeId}", + urlParamKeys: ["id", "scopeId"], + }); + + public getAuthorizationScope = this.makeRequest< + { id: string; scopeId: string }, + ScopeRepresentation + >({ + method: "GET", + path: "/{id}/authz/resource-server/scope/{scopeId}", + urlParamKeys: ["id", "scopeId"], + }); + + public delAuthorizationScope = this.makeRequest< + { id: string; scopeId: string }, + void + >({ + method: "DELETE", + path: "/{id}/authz/resource-server/scope/{scopeId}", + urlParamKeys: ["id", "scopeId"], + }); + + /** + * Permissions + */ + public findPermissions = this.makeRequest< + { + id: string; + name?: string; + resource?: string; + scope?: string; + } & PaginatedQuery, + PolicyRepresentation[] + >({ + method: "GET", + path: "{id}/authz/resource-server/permission", + urlParamKeys: ["id"], + }); + + public createPermission = this.makeUpdateRequest< + { id: string; type: string }, + PolicyRepresentation, + PolicyRepresentation + >({ + method: "POST", + path: "/{id}/authz/resource-server/permission/{type}", + urlParamKeys: ["id", "type"], + }); + + public updatePermission = this.makeUpdateRequest< + { id: string; type: string; permissionId: string }, + PolicyRepresentation, + void + >({ + method: "PUT", + path: "/{id}/authz/resource-server/permission/{type}/{permissionId}", + urlParamKeys: ["id", "type", "permissionId"], + }); + + public delPermission = this.makeRequest< + { id: string; type: string; permissionId: string }, + void + >({ + method: "DELETE", + path: "/{id}/authz/resource-server/permission/{type}/{permissionId}", + urlParamKeys: ["id", "type", "permissionId"], + }); + + public findOnePermission = this.makeRequest< + { id: string; type: string; permissionId: string }, + PolicyRepresentation | undefined + >({ + method: "GET", + path: "/{id}/authz/resource-server/permission/{type}/{permissionId}", + urlParamKeys: ["id", "type", "permissionId"], + }); + + public getAssociatedScopes = this.makeRequest< + { id: string; permissionId: string }, + { id: string; name: string }[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/policy/{permissionId}/scopes", + urlParamKeys: ["id", "permissionId"], + }); + + public getAssociatedResources = this.makeRequest< + { id: string; permissionId: string }, + { _id: string; name: string }[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/policy/{permissionId}/resources", + urlParamKeys: ["id", "permissionId"], + }); + + public getAssociatedPolicies = this.makeRequest< + { id: string; permissionId: string }, + PolicyRepresentation[] + >({ + method: "GET", + path: "/{id}/authz/resource-server/policy/{permissionId}/associatedPolicies", + urlParamKeys: ["id", "permissionId"], + }); + + public getOfflineSessionCount = this.makeRequest< + { id: string }, + { count: number } + >({ + method: "GET", + path: "/{id}/offline-session-count", + urlParamKeys: ["id"], + }); + + public getInstallationProviders = this.makeRequest< + { id: string; providerId: string }, + string + >({ + method: "GET", + path: "/{id}/installation/providers/{providerId}", + urlParamKeys: ["id", "providerId"], + }); + + public pushRevocation = this.makeRequest<{ id: string }, GlobalRequestResult>( + { + method: "POST", + path: "/{id}/push-revocation", + urlParamKeys: ["id"], + } + ); + + public addClusterNode = this.makeRequest<{ id: string; node: string }, void>({ + method: "POST", + path: "/{id}/nodes", + urlParamKeys: ["id"], + }); + + public deleteClusterNode = this.makeRequest< + { id: string; node: string }, + void + >({ + method: "DELETE", + path: "/{id}/nodes/{node}", + urlParamKeys: ["id", "node"], + }); + + public testNodesAvailable = this.makeRequest< + { id: string }, + GlobalRequestResult + >({ + method: "GET", + path: "/{id}/test-nodes-available", + urlParamKeys: ["id"], + }); + + public getKeyInfo = this.makeRequest< + { id: string; attr: string }, + CertificateRepresentation + >({ + method: "GET", + path: "/{id}/certificates/{attr}", + urlParamKeys: ["id", "attr"], + }); + + public generateKey = this.makeRequest< + { id: string; attr: string }, + CertificateRepresentation + >({ + method: "POST", + path: "/{id}/certificates/{attr}/generate", + urlParamKeys: ["id", "attr"], + }); + + public downloadKey = this.makeUpdateRequest< + { id: string; attr: string }, + KeyStoreConfig, + ArrayBuffer + >({ + method: "POST", + path: "/{id}/certificates/{attr}/download", + urlParamKeys: ["id", "attr"], + headers: { + accept: "application/octet-stream", + }, + }); + + public generateAndDownloadKey = this.makeUpdateRequest< + { id: string; attr: string }, + KeyStoreConfig, + ArrayBuffer + >({ + method: "POST", + path: "/{id}/certificates/{attr}/generate-and-download", + urlParamKeys: ["id", "attr"], + headers: { + accept: "application/octet-stream", + }, + }); + + public uploadKey = this.makeUpdateRequest<{ id: string; attr: string }, any>({ + method: "POST", + path: "/{id}/certificates/{attr}/upload", + urlParamKeys: ["id", "attr"], + }); + + public uploadCertificate = this.makeUpdateRequest< + { id: string; attr: string }, + any + >({ + method: "POST", + path: "/{id}/certificates/{attr}/upload-certificate", + urlParamKeys: ["id", "attr"], + }); + + public updateFineGrainPermission = this.makeUpdateRequest< + { id: string }, + ManagementPermissionReference, + ManagementPermissionReference + >({ + method: "PUT", + path: "/{id}/management/permissions", + urlParamKeys: ["id"], + }); + + public listFineGrainPermissions = this.makeRequest< + { id: string }, + ManagementPermissionReference + >({ + method: "GET", + path: "/{id}/management/permissions", + urlParamKeys: ["id"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/clients", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } + + /** + * Find single protocol mapper by name. + */ + public async findProtocolMapperByName(payload: { + realm?: string; + id: string; + name: string; + }): Promise { + const allProtocolMappers = await this.listProtocolMappers({ + id: payload.id, + ...(payload.realm ? { realm: payload.realm } : {}), + }); + return allProtocolMappers.find((mapper) => mapper.name === payload.name); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/components.ts b/js/libs/keycloak-admin-client/src/resources/components.ts new file mode 100644 index 0000000000..524fe12ab1 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/components.ts @@ -0,0 +1,72 @@ +import Resource from "./resource.js"; +import type ComponentRepresentation from "../defs/componentRepresentation.js"; +import type ComponentTypeRepresentation from "../defs/componentTypeRepresentation.js"; +import type { KeycloakAdminClient } from "../client.js"; + +export interface ComponentQuery { + name?: string; + parent?: string; + type?: string; +} + +export class Components extends Resource<{ realm?: string }> { + /** + * components + * https://www.keycloak.org/docs-api/11.0/rest-api/#_component_resource + */ + + public find = this.makeRequest({ + method: "GET", + }); + + public create = this.makeRequest({ + method: "POST", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + public findOne = this.makeRequest< + { id: string }, + ComponentRepresentation | undefined + >({ + method: "GET", + path: "/{id}", + urlParamKeys: ["id"], + catchNotFound: true, + }); + + public update = this.makeUpdateRequest< + { id: string }, + ComponentRepresentation, + void + >({ + method: "PUT", + path: "/{id}", + urlParamKeys: ["id"], + }); + + public del = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/{id}", + urlParamKeys: ["id"], + }); + + public listSubComponents = this.makeRequest< + { id: string; type: string }, + ComponentTypeRepresentation[] + >({ + method: "GET", + path: "/{id}/sub-component-types", + urlParamKeys: ["id"], + queryParamKeys: ["type"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/components", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/groups.ts b/js/libs/keycloak-admin-client/src/resources/groups.ts new file mode 100644 index 0000000000..f2de81dece --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/groups.ts @@ -0,0 +1,242 @@ +import type { KeycloakAdminClient } from "../client.js"; +import type GroupRepresentation from "../defs/groupRepresentation.js"; +import type { ManagementPermissionReference } from "../defs/managementPermissionReference.js"; +import type MappingsRepresentation from "../defs/mappingsRepresentation.js"; +import type RoleRepresentation from "../defs/roleRepresentation.js"; +import type { RoleMappingPayload } from "../defs/roleRepresentation.js"; +import type UserRepresentation from "../defs/userRepresentation.js"; +import Resource from "./resource.js"; + +export interface GroupQuery { + first?: number; + max?: number; + search?: string; + briefRepresentation?: boolean; +} + +export interface GroupCountQuery { + search?: string; + top?: boolean; +} + +export class Groups extends Resource<{ realm?: string }> { + public find = this.makeRequest({ + method: "GET", + }); + + public create = this.makeRequest({ + method: "POST", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + /** + * Single user + */ + + public findOne = this.makeRequest< + { id: string }, + GroupRepresentation | undefined + >({ + method: "GET", + path: "/{id}", + urlParamKeys: ["id"], + catchNotFound: true, + }); + + public update = this.makeUpdateRequest< + { id: string }, + GroupRepresentation, + void + >({ + method: "PUT", + path: "/{id}", + urlParamKeys: ["id"], + }); + + public del = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/{id}", + urlParamKeys: ["id"], + }); + + public count = this.makeRequest({ + method: "GET", + path: "/count", + }); + + /** + * Set or create child. + * This will just set the parent if it exists. Create it and set the parent if the group doesn’t exist. + */ + + public setOrCreateChild = this.makeUpdateRequest< + { id: string }, + GroupRepresentation, + { id: string } + >({ + method: "POST", + path: "/{id}/children", + urlParamKeys: ["id"], + returnResourceIdInLocationHeader: { field: "id" }, + }); + + /** + * Members + */ + + public listMembers = this.makeRequest< + { id: string; first?: number; max?: number }, + UserRepresentation[] + >({ + method: "GET", + path: "/{id}/members", + urlParamKeys: ["id"], + catchNotFound: true, + }); + + /** + * Role mappings + * https://www.keycloak.org/docs-api/11.0/rest-api/#_role_mapper_resource + */ + + public listRoleMappings = this.makeRequest< + { id: string }, + MappingsRepresentation + >({ + method: "GET", + path: "/{id}/role-mappings", + urlParamKeys: ["id"], + }); + + public addRealmRoleMappings = this.makeRequest< + { id: string; roles: RoleMappingPayload[] }, + void + >({ + method: "POST", + path: "/{id}/role-mappings/realm", + urlParamKeys: ["id"], + payloadKey: "roles", + }); + + public listRealmRoleMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/realm", + urlParamKeys: ["id"], + }); + + public delRealmRoleMappings = this.makeRequest< + { id: string; roles: RoleMappingPayload[] }, + void + >({ + method: "DELETE", + path: "/{id}/role-mappings/realm", + urlParamKeys: ["id"], + payloadKey: "roles", + }); + + public listAvailableRealmRoleMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/realm/available", + urlParamKeys: ["id"], + }); + + // Get effective realm-level role mappings This will recurse all composite roles to get the result. + public listCompositeRealmRoleMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/realm/composite", + urlParamKeys: ["id"], + }); + + /** + * Client role mappings + * https://www.keycloak.org/docs-api/11.0/rest-api/#_client_role_mappings_resource + */ + + public listClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/clients/{clientUniqueId}", + urlParamKeys: ["id", "clientUniqueId"], + }); + + public addClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string; roles: RoleMappingPayload[] }, + void + >({ + method: "POST", + path: "/{id}/role-mappings/clients/{clientUniqueId}", + urlParamKeys: ["id", "clientUniqueId"], + payloadKey: "roles", + }); + + public delClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string; roles: RoleMappingPayload[] }, + void + >({ + method: "DELETE", + path: "/{id}/role-mappings/clients/{clientUniqueId}", + urlParamKeys: ["id", "clientUniqueId"], + payloadKey: "roles", + }); + + public listAvailableClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/clients/{clientUniqueId}/available", + urlParamKeys: ["id", "clientUniqueId"], + }); + + public listCompositeClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/clients/{clientUniqueId}/composite", + urlParamKeys: ["id", "clientUniqueId"], + }); + + /** + * Authorization permissions + */ + public updatePermission = this.makeUpdateRequest< + { id: string }, + ManagementPermissionReference, + ManagementPermissionReference + >({ + method: "PUT", + path: "/{id}/management/permissions", + urlParamKeys: ["id"], + }); + + public listPermissions = this.makeRequest< + { id: string }, + ManagementPermissionReference + >({ + method: "GET", + path: "/{id}/management/permissions", + urlParamKeys: ["id"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/groups", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/identityProviders.ts b/js/libs/keycloak-admin-client/src/resources/identityProviders.ts new file mode 100644 index 0000000000..49f56000d9 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/identityProviders.ts @@ -0,0 +1,157 @@ +import type { KeycloakAdminClient } from "../client.js"; +import type IdentityProviderMapperRepresentation from "../defs/identityProviderMapperRepresentation.js"; +import type { IdentityProviderMapperTypeRepresentation } from "../defs/identityProviderMapperTypeRepresentation.js"; +import type IdentityProviderRepresentation from "../defs/identityProviderRepresentation.js"; +import type { ManagementPermissionReference } from "../defs/managementPermissionReference.js"; +import Resource from "./resource.js"; + +export class IdentityProviders extends Resource<{ realm?: string }> { + /** + * Identity provider + * https://www.keycloak.org/docs-api/11.0/rest-api/#_identity_providers_resource + */ + + public find = this.makeRequest<{}, IdentityProviderRepresentation[]>({ + method: "GET", + path: "/instances", + }); + + public create = this.makeRequest< + IdentityProviderRepresentation, + { id: string } + >({ + method: "POST", + path: "/instances", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + public findOne = this.makeRequest< + { alias: string }, + IdentityProviderRepresentation | undefined + >({ + method: "GET", + path: "/instances/{alias}", + urlParamKeys: ["alias"], + catchNotFound: true, + }); + + public update = this.makeUpdateRequest< + { alias: string }, + IdentityProviderRepresentation, + void + >({ + method: "PUT", + path: "/instances/{alias}", + urlParamKeys: ["alias"], + }); + + public del = this.makeRequest<{ alias: string }, void>({ + method: "DELETE", + path: "/instances/{alias}", + urlParamKeys: ["alias"], + }); + + public findFactory = this.makeRequest<{ providerId: string }, any>({ + method: "GET", + path: "/providers/{providerId}", + urlParamKeys: ["providerId"], + }); + + public findMappers = this.makeRequest< + { alias: string }, + IdentityProviderMapperRepresentation[] + >({ + method: "GET", + path: "/instances/{alias}/mappers", + urlParamKeys: ["alias"], + }); + + public findOneMapper = this.makeRequest< + { alias: string; id: string }, + IdentityProviderMapperRepresentation | undefined + >({ + method: "GET", + path: "/instances/{alias}/mappers/{id}", + urlParamKeys: ["alias", "id"], + catchNotFound: true, + }); + + public createMapper = this.makeRequest< + { + alias: string; + identityProviderMapper: IdentityProviderMapperRepresentation; + }, + { id: string } + >({ + method: "POST", + path: "/instances/{alias}/mappers", + urlParamKeys: ["alias"], + payloadKey: "identityProviderMapper", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + public updateMapper = this.makeUpdateRequest< + { alias: string; id: string }, + IdentityProviderMapperRepresentation, + void + >({ + method: "PUT", + path: "/instances/{alias}/mappers/{id}", + urlParamKeys: ["alias", "id"], + }); + + public delMapper = this.makeRequest<{ alias: string; id: string }, void>({ + method: "DELETE", + path: "/instances/{alias}/mappers/{id}", + urlParamKeys: ["alias", "id"], + }); + + public findMapperTypes = this.makeRequest< + { alias: string }, + Record + >({ + method: "GET", + path: "/instances/{alias}/mapper-types", + urlParamKeys: ["alias"], + }); + + public importFromUrl = this.makeRequest< + { + fromUrl: string; + providerId: string; + }, + Record + >({ + method: "POST", + path: "/import-config", + }); + + public updatePermission = this.makeUpdateRequest< + { alias: string }, + ManagementPermissionReference, + ManagementPermissionReference + >({ + method: "PUT", + path: "/instances/{alias}/management/permissions", + urlParamKeys: ["alias"], + }); + + public listPermissions = this.makeRequest< + { alias: string }, + ManagementPermissionReference + >({ + method: "GET", + path: "/instances/{alias}/management/permissions", + urlParamKeys: ["alias"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/identity-provider", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/realms.ts b/js/libs/keycloak-admin-client/src/resources/realms.ts new file mode 100644 index 0000000000..20e8f4fea4 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/realms.ts @@ -0,0 +1,402 @@ +import Resource from "./resource.js"; +import type AdminEventRepresentation from "../defs/adminEventRepresentation.js"; +import type RealmRepresentation from "../defs/realmRepresentation.js"; +import type { + PartialImportRealmRepresentation, + PartialImportResponse, +} from "../defs/realmRepresentation.js"; +import type EventRepresentation from "../defs/eventRepresentation.js"; +import type EventType from "../defs/eventTypes.js"; +import type KeysMetadataRepresentation from "../defs/keyMetadataRepresentation.js"; +import type ClientInitialAccessPresentation from "../defs/clientInitialAccessPresentation.js"; +import type TestLdapConnectionRepresentation from "../defs/testLdapConnection.js"; + +import type { KeycloakAdminClient } from "../client.js"; +import type { RealmEventsConfigRepresentation } from "../defs/realmEventsConfigRepresentation.js"; +import type GlobalRequestResult from "../defs/globalRequestResult.js"; +import type GroupRepresentation from "../defs/groupRepresentation.js"; +import type { ManagementPermissionReference } from "../defs/managementPermissionReference.js"; +import type ComponentTypeRepresentation from "../defs/componentTypeRepresentation.js"; + +export class Realms extends Resource { + /** + * Realm + * https://www.keycloak.org/docs-api/11.0/rest-api/#_realms_admin_resource + */ + + public find = this.makeRequest< + { briefRepresentation?: boolean }, + RealmRepresentation[] + >({ + method: "GET", + }); + + public create = this.makeRequest({ + method: "POST", + returnResourceIdInLocationHeader: { field: "realmName" }, + }); + + public findOne = this.makeRequest< + { realm: string }, + RealmRepresentation | undefined + >({ + method: "GET", + path: "/{realm}", + urlParamKeys: ["realm"], + catchNotFound: true, + }); + + public update = this.makeUpdateRequest< + { realm: string }, + RealmRepresentation, + void + >({ + method: "PUT", + path: "/{realm}", + urlParamKeys: ["realm"], + }); + + public del = this.makeRequest<{ realm: string }, void>({ + method: "DELETE", + path: "/{realm}", + urlParamKeys: ["realm"], + }); + + public partialImport = this.makeRequest< + { + realm: string; + rep: PartialImportRealmRepresentation; + }, + PartialImportResponse + >({ + method: "POST", + path: "/{realm}/partialImport", + urlParamKeys: ["realm"], + payloadKey: "rep", + }); + + public export = this.makeRequest< + { + realm: string; + exportClients?: boolean; + exportGroupsAndRoles?: boolean; + }, + RealmRepresentation + >({ + method: "POST", + path: "/{realm}/partial-export", + urlParamKeys: ["realm"], + queryParamKeys: ["exportClients", "exportGroupsAndRoles"], + }); + + public getDefaultGroups = this.makeRequest< + { realm: string }, + GroupRepresentation[] + >({ + method: "GET", + path: "/{realm}/default-groups", + urlParamKeys: ["realm"], + }); + + public addDefaultGroup = this.makeRequest<{ realm: string; id: string }>({ + method: "PUT", + path: "/{realm}/default-groups/{id}", + urlParamKeys: ["realm", "id"], + }); + + public removeDefaultGroup = this.makeRequest<{ realm: string; id: string }>({ + method: "DELETE", + path: "/{realm}/default-groups/{id}", + urlParamKeys: ["realm", "id"], + }); + + public getGroupByPath = this.makeRequest< + { path: string; realm: string }, + GroupRepresentation + >({ + method: "GET", + path: "/{realm}/group-by-path/{path}", + urlParamKeys: ["realm", "path"], + }); + + /** + * Get events Returns all events, or filters them based on URL query parameters listed here + */ + public findEvents = this.makeRequest< + { + realm: string; + client?: string; + dateFrom?: string; + dateTo?: string; + first?: number; + ipAddress?: string; + max?: number; + type?: EventType | EventType[]; + user?: string; + }, + EventRepresentation[] + >({ + method: "GET", + path: "/{realm}/events", + urlParamKeys: ["realm"], + queryParamKeys: [ + "client", + "dateFrom", + "dateTo", + "first", + "ipAddress", + "max", + "type", + "user", + ], + }); + + public getConfigEvents = this.makeRequest< + { realm: string }, + RealmEventsConfigRepresentation + >({ + method: "GET", + path: "/{realm}/events/config", + urlParamKeys: ["realm"], + }); + + public updateConfigEvents = this.makeUpdateRequest< + { realm: string }, + RealmEventsConfigRepresentation, + void + >({ + method: "PUT", + path: "/{realm}/events/config", + urlParamKeys: ["realm"], + }); + + public clearEvents = this.makeRequest<{ realm: string }, void>({ + method: "DELETE", + path: "/{realm}/events", + urlParamKeys: ["realm"], + }); + + public clearAdminEvents = this.makeRequest<{ realm: string }, void>({ + method: "DELETE", + path: "/{realm}/admin-events", + urlParamKeys: ["realm"], + }); + + public getClientRegistrationPolicyProviders = this.makeRequest< + { realm: string }, + ComponentTypeRepresentation[] + >({ + method: "GET", + path: "/{realm}/client-registration-policy/providers", + urlParamKeys: ["realm"], + }); + + public getClientsInitialAccess = this.makeRequest< + { realm: string }, + ClientInitialAccessPresentation[] + >({ + method: "GET", + path: "/{realm}/clients-initial-access", + urlParamKeys: ["realm"], + }); + + public createClientsInitialAccess = this.makeUpdateRequest< + { realm: string }, + { count?: number; expiration?: number }, + ClientInitialAccessPresentation + >({ + method: "POST", + path: "/{realm}/clients-initial-access", + urlParamKeys: ["realm"], + }); + + public delClientsInitialAccess = this.makeRequest< + { realm: string; id: string }, + void + >({ + method: "DELETE", + path: "/{realm}/clients-initial-access/{id}", + urlParamKeys: ["realm", "id"], + }); + + /** + * Remove a specific user session. + */ + public removeSession = this.makeRequest< + { realm: string; sessionId: string }, + void + >({ + method: "DELETE", + path: "/{realm}/sessions/{session}", + urlParamKeys: ["realm", "session"], + catchNotFound: true, + }); + + /** + * Get admin events Returns all admin events, or filters events based on URL query parameters listed here + */ + public findAdminEvents = this.makeRequest< + { + realm: string; + authClient?: string; + authIpAddress?: string; + authRealm?: string; + authUser?: string; + dateFrom?: Date; + dateTo?: Date; + first?: number; + max?: number; + operationTypes?: string; + resourcePath?: string; + resourceTypes?: string; + }, + AdminEventRepresentation[] + >({ + method: "GET", + path: "/{realm}/admin-events", + urlParamKeys: ["realm"], + queryParamKeys: [ + "authClient", + "authIpAddress", + "authRealm", + "authUser", + "dateFrom", + "dateTo", + "max", + "first", + "operationTypes", + "resourcePath", + "resourceTypes", + ], + }); + + /** + * Users management permissions + */ + public getUsersManagementPermissions = this.makeRequest< + { realm: string }, + ManagementPermissionReference + >({ + method: "GET", + path: "/{realm}/users-management-permissions", + urlParamKeys: ["realm"], + }); + + public updateUsersManagementPermissions = this.makeRequest< + { realm: string; enabled: boolean }, + ManagementPermissionReference + >({ + method: "PUT", + path: "/{realm}/users-management-permissions", + urlParamKeys: ["realm"], + }); + + /** + * Sessions + */ + public logoutAll = this.makeRequest<{ realm: string }, void>({ + method: "POST", + path: "/{realm}/logout-all", + urlParamKeys: ["realm"], + }); + + public deleteSession = this.makeRequest< + { realm: string; session: string }, + void + >({ + method: "DELETE", + path: "/{realm}/sessions/{session}", + urlParamKeys: ["realm", "session"], + }); + + public pushRevocation = this.makeRequest< + { realm: string }, + GlobalRequestResult + >({ + method: "POST", + path: "/{realm}/push-revocation", + urlParamKeys: ["realm"], + ignoredKeys: ["realm"], + }); + + public getKeys = this.makeRequest< + { realm: string }, + KeysMetadataRepresentation + >({ + method: "GET", + path: "/{realm}/keys", + urlParamKeys: ["realm"], + }); + + public testLDAPConnection = this.makeUpdateRequest< + { realm: string }, + TestLdapConnectionRepresentation + >({ + method: "POST", + path: "/{realm}/testLDAPConnection", + urlParamKeys: ["realm"], + }); + + public testSMTPConnection = this.makeUpdateRequest< + { realm: string }, + Record + >({ + method: "POST", + path: "/{realm}/testSMTPConnection", + urlParamKeys: ["realm"], + }); + + public ldapServerCapabilities = this.makeUpdateRequest< + { realm: string }, + TestLdapConnectionRepresentation + >({ + method: "POST", + path: "/{realm}/ldap-server-capabilities", + urlParamKeys: ["realm"], + }); + + public getRealmSpecificLocales = this.makeRequest< + { realm: string }, + string[] + >({ + method: "GET", + path: "/{realm}/localization", + urlParamKeys: ["realm"], + }); + + public getRealmLocalizationTexts = this.makeRequest< + { realm: string; selectedLocale: string; first?: number; max?: number }, + Record + >({ + method: "GET", + path: "/{realm}/localization/{selectedLocale}", + urlParamKeys: ["realm", "selectedLocale"], + }); + + public addLocalization = this.makeUpdateRequest< + { realm: string; selectedLocale: string; key: string }, + string, + void + >({ + method: "PUT", + path: "/{realm}/localization/{selectedLocale}/{key}", + urlParamKeys: ["realm", "selectedLocale", "key"], + headers: { "content-type": "text/plain" }, + }); + + public deleteRealmLocalizationTexts = this.makeRequest< + { realm: string; selectedLocale: string; key?: string }, + void + >({ + method: "DELETE", + path: "/{realm}/localization/{selectedLocale}/{key}", + urlParamKeys: ["realm", "selectedLocale", "key"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms", + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/resource.ts b/js/libs/keycloak-admin-client/src/resources/resource.ts new file mode 100644 index 0000000000..d72fa4f3f1 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/resource.ts @@ -0,0 +1,42 @@ +import type { KeycloakAdminClient } from "../client.js"; +import { Agent, RequestArgs } from "./agent.js"; + +export default class Resource { + private agent: Agent; + constructor( + client: KeycloakAdminClient, + settings: { + path?: string; + getUrlParams?: () => Record; + getBaseUrl?: () => string; + } = {} + ) { + this.agent = new Agent({ + client, + ...settings, + }); + } + + public makeRequest = ( + args: RequestArgs + ): (( + payload?: PayloadType & ParamType, + options?: Pick + ) => Promise) => { + return this.agent.request(args); + }; + + // update request will take three types: query, payload and response + public makeUpdateRequest = < + QueryType = any, + PayloadType = any, + ResponseType = any + >( + args: RequestArgs + ): (( + query: QueryType & ParamType, + payload: PayloadType + ) => Promise) => { + return this.agent.updateRequest(args); + }; +} diff --git a/js/libs/keycloak-admin-client/src/resources/roles.ts b/js/libs/keycloak-admin-client/src/resources/roles.ts new file mode 100644 index 0000000000..9a97c2b303 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/roles.ts @@ -0,0 +1,178 @@ +import Resource from "./resource.js"; +import type RoleRepresentation from "../defs/roleRepresentation.js"; +import type UserRepresentation from "../defs/userRepresentation.js"; +import type { KeycloakAdminClient } from "../client.js"; +import type { ManagementPermissionReference } from "../defs/managementPermissionReference.js"; + +export interface RoleQuery { + first?: number; + max?: number; + search?: string; + briefRepresentation?: boolean; +} + +export class Roles extends Resource<{ realm?: string }> { + /** + * Realm roles + */ + + public find = this.makeRequest({ + method: "GET", + path: "/roles", + }); + + public create = this.makeRequest({ + method: "POST", + path: "/roles", + returnResourceIdInLocationHeader: { field: "roleName" }, + }); + + /** + * Roles by name + */ + + public findOneByName = this.makeRequest< + { name: string }, + RoleRepresentation | undefined + >({ + method: "GET", + path: "/roles/{name}", + urlParamKeys: ["name"], + catchNotFound: true, + }); + + public updateByName = this.makeUpdateRequest< + { name: string }, + RoleRepresentation, + void + >({ + method: "PUT", + path: "/roles/{name}", + urlParamKeys: ["name"], + }); + + public delByName = this.makeRequest<{ name: string }, void>({ + method: "DELETE", + path: "/roles/{name}", + urlParamKeys: ["name"], + }); + + public findUsersWithRole = this.makeRequest< + { name: string; first?: number; max?: number }, + UserRepresentation[] + >({ + method: "GET", + path: "/roles/{name}/users", + urlParamKeys: ["name"], + catchNotFound: true, + }); + + /** + * Roles by id + */ + + public findOneById = this.makeRequest< + { id: string }, + RoleRepresentation | undefined + >({ + method: "GET", + path: "/roles-by-id/{id}", + urlParamKeys: ["id"], + catchNotFound: true, + }); + + public createComposite = this.makeUpdateRequest< + { roleId: string }, + RoleRepresentation[], + void + >({ + method: "POST", + path: "/roles-by-id/{roleId}/composites", + urlParamKeys: ["roleId"], + }); + + public getCompositeRoles = this.makeRequest< + { id: string; search?: string; first?: number; max?: number }, + RoleRepresentation[] + >({ + method: "GET", + path: "/roles-by-id/{id}/composites", + urlParamKeys: ["id"], + }); + + public getCompositeRolesForRealm = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/roles-by-id/{id}/composites/realm", + urlParamKeys: ["id"], + }); + + public getCompositeRolesForClient = this.makeRequest< + { id: string; clientId: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/roles-by-id/{id}/composites/clients/{clientId}", + urlParamKeys: ["id", "clientId"], + }); + + public delCompositeRoles = this.makeUpdateRequest< + { id: string }, + RoleRepresentation[], + void + >({ + method: "DELETE", + path: "/roles-by-id/{id}/composites", + urlParamKeys: ["id"], + }); + + public updateById = this.makeUpdateRequest< + { id: string }, + RoleRepresentation, + void + >({ + method: "PUT", + path: "/roles-by-id/{id}", + urlParamKeys: ["id"], + }); + + public delById = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/roles-by-id/{id}", + urlParamKeys: ["id"], + }); + + /** + * Authorization permissions + */ + public updatePermission = this.makeUpdateRequest< + { id: string }, + ManagementPermissionReference, + ManagementPermissionReference + >({ + method: "PUT", + path: "/roles-by-id/{id}/management/permissions", + urlParamKeys: ["id"], + }); + + public listPermissions = this.makeRequest< + { id: string }, + ManagementPermissionReference + >({ + method: "GET", + path: "/roles-by-id/{id}/management/permissions", + urlParamKeys: ["id"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/serverInfo.ts b/js/libs/keycloak-admin-client/src/resources/serverInfo.ts new file mode 100644 index 0000000000..b4b027d0ff --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/serverInfo.ts @@ -0,0 +1,17 @@ +import Resource from "./resource.js"; +import type { ServerInfoRepresentation } from "../defs/serverInfoRepesentation.js"; +import type KeycloakAdminClient from "../index.js"; + +export class ServerInfo extends Resource { + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/serverinfo", + getBaseUrl: () => client.baseUrl, + }); + } + + public find = this.makeRequest<{}, ServerInfoRepresentation>({ + method: "GET", + path: "/", + }); +} diff --git a/js/libs/keycloak-admin-client/src/resources/sessions.ts b/js/libs/keycloak-admin-client/src/resources/sessions.ts new file mode 100644 index 0000000000..b9105411b6 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/sessions.ts @@ -0,0 +1,18 @@ +import Resource from "./resource.js"; +import type KeycloakAdminClient from "../index.js"; + +export class Sessions extends Resource<{ realm?: string }> { + public find = this.makeRequest<{}, Record[]>({ + method: "GET", + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/client-session-stats", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/userStorageProvider.ts b/js/libs/keycloak-admin-client/src/resources/userStorageProvider.ts new file mode 100644 index 0000000000..93156592ac --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/userStorageProvider.ts @@ -0,0 +1,60 @@ +import type { KeycloakAdminClient } from "../client.js"; +import type SynchronizationResultRepresentation from "../defs/synchronizationResultRepresentation.js"; +import Resource from "./resource.js"; + +type ActionType = "triggerFullSync" | "triggerChangedUsersSync"; +export type DirectionType = "fedToKeycloak" | "keycloakToFed"; +type NameResponse = { + id: string; + name: string; +}; + +export class UserStorageProvider extends Resource<{ realm?: string }> { + public name = this.makeRequest<{ id: string }, NameResponse>({ + method: "GET", + path: "/{id}/name", + urlParamKeys: ["id"], + }); + + public removeImportedUsers = this.makeRequest<{ id: string }, void>({ + method: "POST", + path: "/{id}/remove-imported-users", + urlParamKeys: ["id"], + }); + + public sync = this.makeRequest< + { id: string; action?: ActionType }, + SynchronizationResultRepresentation + >({ + method: "POST", + path: "/{id}/sync", + urlParamKeys: ["id"], + queryParamKeys: ["action"], + }); + + public unlinkUsers = this.makeRequest<{ id: string }, void>({ + method: "POST", + path: "/{id}/unlink-users", + urlParamKeys: ["id"], + }); + + public mappersSync = this.makeRequest< + { id: string; parentId: string; direction?: DirectionType }, + SynchronizationResultRepresentation + >({ + method: "POST", + path: "/{parentId}/mappers/{id}/sync", + urlParamKeys: ["id", "parentId"], + queryParamKeys: ["direction"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/user-storage", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/users.ts b/js/libs/keycloak-admin-client/src/resources/users.ts new file mode 100644 index 0000000000..3c42f59f01 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/users.ts @@ -0,0 +1,497 @@ +import Resource from "./resource.js"; +import type UserRepresentation from "../defs/userRepresentation.js"; +import type UserConsentRepresentation from "../defs/userConsentRepresentation.js"; +import type UserSessionRepresentation from "../defs/userSessionRepresentation.js"; +import type { KeycloakAdminClient } from "../client.js"; +import type MappingsRepresentation from "../defs/mappingsRepresentation.js"; +import type RoleRepresentation from "../defs/roleRepresentation.js"; +import type { RoleMappingPayload } from "../defs/roleRepresentation.js"; +import type { RequiredActionAlias } from "../defs/requiredActionProviderRepresentation.js"; +import type FederatedIdentityRepresentation from "../defs/federatedIdentityRepresentation.js"; +import type GroupRepresentation from "../defs/groupRepresentation.js"; +import type CredentialRepresentation from "../defs/credentialRepresentation.js"; +import type UserProfileConfig from "../defs/userProfileConfig.js"; + +interface SearchQuery { + search?: string; +} + +interface PaginationQuery { + first?: number; + max?: number; +} + +interface UserBaseQuery { + email?: string; + firstName?: string; + lastName?: string; + username?: string; +} + +export interface UserQuery extends PaginationQuery, SearchQuery, UserBaseQuery { + exact?: boolean; + [key: string]: string | number | undefined | boolean; +} + +export class Users extends Resource<{ realm?: string }> { + public find = this.makeRequest({ + method: "GET", + }); + + public create = this.makeRequest({ + method: "POST", + returnResourceIdInLocationHeader: { field: "id" }, + }); + + /** + * Single user + */ + + public findOne = this.makeRequest< + { id: string }, + UserRepresentation | undefined + >({ + method: "GET", + path: "/{id}", + urlParamKeys: ["id"], + catchNotFound: true, + }); + + public update = this.makeUpdateRequest< + { id: string }, + UserRepresentation, + void + >({ + method: "PUT", + path: "/{id}", + urlParamKeys: ["id"], + }); + + public del = this.makeRequest<{ id: string }, void>({ + method: "DELETE", + path: "/{id}", + urlParamKeys: ["id"], + }); + + public count = this.makeRequest({ + method: "GET", + path: "/count", + }); + + public getProfile = this.makeRequest<{}, UserProfileConfig>({ + method: "GET", + path: "/profile", + }); + + public updateProfile = this.makeRequest( + { + method: "PUT", + path: "/profile", + } + ); + + /** + * role mappings + */ + + public listRoleMappings = this.makeRequest< + { id: string }, + MappingsRepresentation + >({ + method: "GET", + path: "/{id}/role-mappings", + urlParamKeys: ["id"], + }); + + public addRealmRoleMappings = this.makeRequest< + { id: string; roles: RoleMappingPayload[] }, + void + >({ + method: "POST", + path: "/{id}/role-mappings/realm", + urlParamKeys: ["id"], + payloadKey: "roles", + }); + + public listRealmRoleMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/realm", + urlParamKeys: ["id"], + }); + + public delRealmRoleMappings = this.makeRequest< + { id: string; roles: RoleMappingPayload[] }, + void + >({ + method: "DELETE", + path: "/{id}/role-mappings/realm", + urlParamKeys: ["id"], + payloadKey: "roles", + }); + + public listAvailableRealmRoleMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/realm/available", + urlParamKeys: ["id"], + }); + + // Get effective realm-level role mappings This will recurse all composite roles to get the result. + public listCompositeRealmRoleMappings = this.makeRequest< + { id: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/realm/composite", + urlParamKeys: ["id"], + }); + + /** + * Client role mappings + * https://www.keycloak.org/docs-api/11.0/rest-api/#_client_role_mappings_resource + */ + + public listClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/clients/{clientUniqueId}", + urlParamKeys: ["id", "clientUniqueId"], + }); + + public addClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string; roles: RoleMappingPayload[] }, + void + >({ + method: "POST", + path: "/{id}/role-mappings/clients/{clientUniqueId}", + urlParamKeys: ["id", "clientUniqueId"], + payloadKey: "roles", + }); + + public delClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string; roles: RoleMappingPayload[] }, + void + >({ + method: "DELETE", + path: "/{id}/role-mappings/clients/{clientUniqueId}", + urlParamKeys: ["id", "clientUniqueId"], + payloadKey: "roles", + }); + + public listAvailableClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/clients/{clientUniqueId}/available", + urlParamKeys: ["id", "clientUniqueId"], + }); + + public listCompositeClientRoleMappings = this.makeRequest< + { id: string; clientUniqueId: string }, + RoleRepresentation[] + >({ + method: "GET", + path: "/{id}/role-mappings/clients/{clientUniqueId}/composite", + urlParamKeys: ["id", "clientUniqueId"], + }); + + /** + * Send a update account email to the user + * an email contains a link the user can click to perform a set of required actions. + */ + + public executeActionsEmail = this.makeRequest< + { + id: string; + clientId?: string; + lifespan?: number; + redirectUri?: string; + actions?: (RequiredActionAlias | string)[]; + }, + void + >({ + method: "PUT", + path: "/{id}/execute-actions-email", + urlParamKeys: ["id"], + payloadKey: "actions", + queryParamKeys: ["lifespan", "redirectUri", "clientId"], + keyTransform: { + clientId: "client_id", + redirectUri: "redirect_uri", + }, + }); + + /** + * Group + */ + + public listGroups = this.makeRequest< + { id: string; briefRepresentation?: boolean } & PaginationQuery & + SearchQuery, + GroupRepresentation[] + >({ + method: "GET", + path: "/{id}/groups", + urlParamKeys: ["id"], + }); + + public addToGroup = this.makeRequest<{ id: string; groupId: string }, string>( + { + method: "PUT", + path: "/{id}/groups/{groupId}", + urlParamKeys: ["id", "groupId"], + } + ); + + public delFromGroup = this.makeRequest< + { id: string; groupId: string }, + string + >({ + method: "DELETE", + path: "/{id}/groups/{groupId}", + urlParamKeys: ["id", "groupId"], + }); + + public countGroups = this.makeRequest< + { id: string; search?: string }, + { count: number } + >({ + method: "GET", + path: "/{id}/groups/count", + urlParamKeys: ["id"], + }); + + /** + * Federated Identity + */ + + public listFederatedIdentities = this.makeRequest< + { id: string }, + FederatedIdentityRepresentation[] + >({ + method: "GET", + path: "/{id}/federated-identity", + urlParamKeys: ["id"], + }); + + public addToFederatedIdentity = this.makeRequest< + { + id: string; + federatedIdentityId: string; + federatedIdentity: FederatedIdentityRepresentation; + }, + void + >({ + method: "POST", + path: "/{id}/federated-identity/{federatedIdentityId}", + urlParamKeys: ["id", "federatedIdentityId"], + payloadKey: "federatedIdentity", + }); + + public delFromFederatedIdentity = this.makeRequest< + { id: string; federatedIdentityId: string }, + void + >({ + method: "DELETE", + path: "/{id}/federated-identity/{federatedIdentityId}", + urlParamKeys: ["id", "federatedIdentityId"], + }); + + /** + * remove totp + */ + public removeTotp = this.makeRequest<{ id: string }, void>({ + method: "PUT", + path: "/{id}/remove-totp", + urlParamKeys: ["id"], + }); + + /** + * reset password + */ + public resetPassword = this.makeRequest< + { id: string; credential: CredentialRepresentation }, + void + >({ + method: "PUT", + path: "/{id}/reset-password", + urlParamKeys: ["id"], + payloadKey: "credential", + }); + + public getUserStorageCredentialTypes = this.makeRequest< + { id: string }, + string[] + >({ + method: "GET", + path: "/{id}/configured-user-storage-credential-types", + urlParamKeys: ["id"], + }); + + /** + * get user credentials + */ + public getCredentials = this.makeRequest< + { id: string }, + CredentialRepresentation[] + >({ + method: "GET", + path: "/{id}/credentials", + urlParamKeys: ["id"], + }); + + /** + * delete user credentials + */ + public deleteCredential = this.makeRequest< + { id: string; credentialId: string }, + void + >({ + method: "DELETE", + path: "/{id}/credentials/{credentialId}", + urlParamKeys: ["id", "credentialId"], + }); + + /** + * update a credential label for a user + */ + public updateCredentialLabel = this.makeUpdateRequest< + { id: string; credentialId: string }, + string, + void + >({ + method: "PUT", + path: "/{id}/credentials/{credentialId}/userLabel", + urlParamKeys: ["id", "credentialId"], + headers: { "content-type": "text/plain" }, + }); + + // Move a credential to a position behind another credential + public moveCredentialPositionDown = this.makeRequest< + { + id: string; + credentialId: string; + newPreviousCredentialId: string; + }, + void + >({ + method: "POST", + path: "/{id}/credentials/{credentialId}/moveAfter/{newPreviousCredentialId}", + urlParamKeys: ["id", "credentialId", "newPreviousCredentialId"], + }); + + // Move a credential to a first position in the credentials list of the user + public moveCredentialPositionUp = this.makeRequest< + { + id: string; + credentialId: string; + }, + void + >({ + method: "POST", + path: "/{id}/credentials/{credentialId}/moveToFirst", + urlParamKeys: ["id", "credentialId"], + }); + + /** + * send verify email + */ + public sendVerifyEmail = this.makeRequest< + { id: string; clientId?: string; redirectUri?: string }, + void + >({ + method: "PUT", + path: "/{id}/send-verify-email", + urlParamKeys: ["id"], + queryParamKeys: ["clientId", "redirectUri"], + keyTransform: { + clientId: "client_id", + redirectUri: "redirect_uri", + }, + }); + + /** + * list user sessions + */ + public listSessions = this.makeRequest< + { id: string }, + UserSessionRepresentation[] + >({ + method: "GET", + path: "/{id}/sessions", + urlParamKeys: ["id"], + }); + + /** + * list offline sessions associated with the user and client + */ + public listOfflineSessions = this.makeRequest< + { id: string; clientId: string }, + UserSessionRepresentation[] + >({ + method: "GET", + path: "/{id}/offline-sessions/{clientId}", + urlParamKeys: ["id", "clientId"], + }); + + /** + * logout user from all sessions + */ + public logout = this.makeRequest<{ id: string }, void>({ + method: "POST", + path: "/{id}/logout", + urlParamKeys: ["id"], + }); + + /** + * list consents granted by the user + */ + public listConsents = this.makeRequest< + { id: string }, + UserConsentRepresentation[] + >({ + method: "GET", + path: "/{id}/consents", + urlParamKeys: ["id"], + }); + + public impersonation = this.makeUpdateRequest< + { id: string }, + { user: string; realm: string }, + Record + >({ + method: "POST", + path: "/{id}/impersonation", + urlParamKeys: ["id"], + }); + + /** + * revoke consent and offline tokens for particular client from user + */ + public revokeConsent = this.makeRequest< + { id: string; clientId: string }, + void + >({ + method: "DELETE", + path: "/{id}/consents/{clientId}", + urlParamKeys: ["id", "clientId"], + }); + + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/realms/{realm}/users", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } +} diff --git a/js/libs/keycloak-admin-client/src/resources/whoAmI.ts b/js/libs/keycloak-admin-client/src/resources/whoAmI.ts new file mode 100644 index 0000000000..f6013d33d5 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/whoAmI.ts @@ -0,0 +1,20 @@ +import type WhoAmIRepresentation from "../defs/whoAmIRepresentation.js"; +import type KeycloakAdminClient from "../index.js"; +import Resource from "./resource.js"; + +export class WhoAmI extends Resource<{ realm?: string }> { + constructor(client: KeycloakAdminClient) { + super(client, { + path: "/admin/{realm}/console", + getUrlParams: () => ({ + realm: client.realmName, + }), + getBaseUrl: () => client.baseUrl, + }); + } + + public find = this.makeRequest<{}, WhoAmIRepresentation>({ + method: "GET", + path: "/whoami", + }); +} diff --git a/js/libs/keycloak-admin-client/src/utils/auth.ts b/js/libs/keycloak-admin-client/src/utils/auth.ts new file mode 100644 index 0000000000..b552179b7b --- /dev/null +++ b/js/libs/keycloak-admin-client/src/utils/auth.ts @@ -0,0 +1,97 @@ +import camelize from "camelize-ts"; +import { defaultBaseUrl, defaultRealm } from "./constants.js"; +import { fetchWithError } from "./fetchWithError.js"; +import { stringifyQueryParams } from "./stringifyQueryParams.js"; + +export type GrantTypes = "client_credentials" | "password" | "refresh_token"; + +export interface Credentials { + username?: string; + password?: string; + grantType: GrantTypes; + clientId: string; + clientSecret?: string; + totp?: string; + offlineToken?: boolean; + refreshToken?: string; + scopes?: string[]; +} + +export interface Settings { + realmName?: string; + baseUrl?: string; + credentials: Credentials; + requestOptions?: RequestInit; +} + +export interface TokenResponseRaw { + access_token: string; + expires_in: string; + refresh_expires_in: number; + refresh_token: string; + token_type: string; + not_before_policy: number; + session_state: string; + scope: string; + id_token?: string; +} + +export interface TokenResponse { + accessToken: string; + expiresIn: string; + refreshExpiresIn: number; + refreshToken: string; + tokenType: string; + notBeforePolicy: number; + sessionState: string; + scope: string; + idToken?: string; +} + +export const getToken = async (settings: Settings): Promise => { + // Construct URL + const baseUrl = settings.baseUrl || defaultBaseUrl; + const realmName = settings.realmName || defaultRealm; + const url = `${baseUrl}/realms/${realmName}/protocol/openid-connect/token`; + + // Prepare credentials for openid-connect token request + // ref: http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint + const credentials = settings.credentials || ({} as any); + const payload = stringifyQueryParams({ + username: credentials.username, + password: credentials.password, + grant_type: credentials.grantType, + client_id: credentials.clientId, + totp: credentials.totp, + ...(credentials.offlineToken ? { scope: "offline_access" } : {}), + ...(credentials.scopes ? { scope: credentials.scopes.join(" ") } : {}), + ...(credentials.refreshToken + ? { + refresh_token: credentials.refreshToken, + client_secret: credentials.clientSecret, + } + : {}), + }); + + const options = settings.requestOptions ?? {}; + const headers = new Headers(options.headers); + + if (credentials.clientSecret) { + headers.set( + "Authorization", + atob(credentials.clientId + ":" + credentials.clientSecret) + ); + } + + headers.set("content-type", "application/x-www-form-urlencoded"); + + const response = await fetchWithError(url, { + ...options, + method: "POST", + headers, + body: payload, + }); + + const data: TokenResponseRaw = await response.json(); + return camelize(data); +}; diff --git a/js/libs/keycloak-admin-client/src/utils/constants.ts b/js/libs/keycloak-admin-client/src/utils/constants.ts new file mode 100644 index 0000000000..951654fc6f --- /dev/null +++ b/js/libs/keycloak-admin-client/src/utils/constants.ts @@ -0,0 +1,3 @@ +export const defaultBaseUrl = "http://127.0.0.1:8180"; + +export const defaultRealm = "master"; diff --git a/js/libs/keycloak-admin-client/src/utils/fetchWithError.ts b/js/libs/keycloak-admin-client/src/utils/fetchWithError.ts new file mode 100644 index 0000000000..aa84df2495 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/utils/fetchWithError.ts @@ -0,0 +1,44 @@ +export type NetworkErrorOptions = { response: Response; responseData: unknown }; + +export class NetworkError extends Error { + response: Response; + responseData: unknown; + + constructor(message: string, options: NetworkErrorOptions) { + super(message); + this.response = options.response; + this.responseData = options.responseData; + } +} + +export async function fetchWithError( + input: RequestInfo | URL, + init?: RequestInit +) { + const response = await fetch(input, init); + + if (!response.ok) { + const responseData = await parseResponse(response); + throw new NetworkError("Network response was not OK.", { + response, + responseData, + }); + } + + return response; +} + +export async function parseResponse(response: Response): Promise { + if (!response.body) { + return ""; + } + + const data = await response.text(); + + try { + return JSON.parse(data); + // eslint-disable-next-line no-empty + } catch (error) {} + + return data; +} diff --git a/js/libs/keycloak-admin-client/src/utils/stringifyQueryParams.ts b/js/libs/keycloak-admin-client/src/utils/stringifyQueryParams.ts new file mode 100644 index 0000000000..cb085d0810 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/utils/stringifyQueryParams.ts @@ -0,0 +1,21 @@ +export function stringifyQueryParams(params: Record) { + return new URLSearchParams( + Object.entries(params).filter((param): param is [string, string] => { + const [, value] = param; + + if (typeof value === "undefined" || value === null) { + return false; + } + + if (typeof value === "string" && value.length === 0) { + return false; + } + + if (Array.isArray(value) && value.length === 0) { + return false; + } + + return true; + }) + ).toString(); +} diff --git a/js/libs/keycloak-admin-client/test/attackDetection.spec.ts b/js/libs/keycloak-admin-client/test/attackDetection.spec.ts new file mode 100644 index 0000000000..91c9ed463d --- /dev/null +++ b/js/libs/keycloak-admin-client/test/attackDetection.spec.ts @@ -0,0 +1,47 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type UserRepresentation from "../src/defs/userRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Attack Detection", () => { + let kcAdminClient: KeycloakAdminClient; + let currentUser: UserRepresentation; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + const username = faker.internet.userName(); + currentUser = await kcAdminClient.users.create({ + username, + }); + }); + + after(async () => { + await kcAdminClient.users.del({ id: currentUser.id! }); + }); + + it("list attack detection for user", async () => { + const attackDetection = await kcAdminClient.attackDetection.findOne({ + id: currentUser.id!, + }); + expect(attackDetection).to.deep.equal({ + numFailures: 0, + disabled: false, + lastIPFailure: "n/a", + lastFailure: 0, + }); + }); + + it("clear any user login failures for all users", async () => { + await kcAdminClient.attackDetection.delAll(); + }); + + it("clear any user login failures for a user", async () => { + await kcAdminClient.attackDetection.del({ id: currentUser.id! }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/auth.spec.ts b/js/libs/keycloak-admin-client/test/auth.spec.ts new file mode 100644 index 0000000000..9a0faf34bd --- /dev/null +++ b/js/libs/keycloak-admin-client/test/auth.spec.ts @@ -0,0 +1,47 @@ +import * as chai from "chai"; +import { getToken } from "../src/utils/auth.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Authorization", () => { + it("should get token from local keycloak", async () => { + const data = await getToken({ + credentials, + }); + + expect(data).to.have.all.keys( + "accessToken", + "expiresIn", + "refreshExpiresIn", + "refreshToken", + "tokenType", + "notBeforePolicy", + "sessionState", + "scope" + ); + }); + + it("should get token from local keycloak with custom scope", async () => { + const data = await getToken({ + credentials: { + ...credentials, + scopes: ["openid", "profile"], + }, + }); + + expect(data).to.have.all.keys( + "accessToken", + "expiresIn", + "refreshExpiresIn", + "refreshToken", + "tokenType", + "notBeforePolicy", + "sessionState", + "scope", + "idToken" + ); + + expect(data.scope).to.equal("openid profile email"); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts b/js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts new file mode 100644 index 0000000000..29104c6583 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts @@ -0,0 +1,415 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import { fail } from "assert"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { RequiredActionAlias } from "../src/defs/requiredActionProviderRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Authentication management", () => { + let kcAdminClient: KeycloakAdminClient; + let currentRealm: string; + let requiredActionProvider: Record; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + const realmName = faker.internet.userName().toLowerCase(); + await kcAdminClient.realms.create({ + id: realmName, + realm: realmName, + enabled: true, + }); + currentRealm = realmName; + kcAdminClient.setConfig({ + realmName, + }); + }); + + after(async () => { + // delete test realm + await kcAdminClient.realms.del({ realm: currentRealm }); + const realm = await kcAdminClient.realms.findOne({ + realm: currentRealm, + }); + expect(realm).to.be.null; + }); + + /** + * Required Actions + */ + describe("Required Actions", () => { + it("should delete required action by alias", async () => { + await kcAdminClient.authenticationManagement.deleteRequiredAction({ + alias: RequiredActionAlias.UPDATE_PROFILE, + }); + }); + + it("should get unregistered required actions", async () => { + const unregisteredReqActions = + await kcAdminClient.authenticationManagement.getUnregisteredRequiredActions(); + expect(unregisteredReqActions).to.be.an("array"); + expect(unregisteredReqActions.length).to.be.least(1); + requiredActionProvider = unregisteredReqActions[0]; + }); + + it("should register new required action", async () => { + const requiredAction = + await kcAdminClient.authenticationManagement.registerRequiredAction({ + providerId: requiredActionProvider.providerId, + name: requiredActionProvider.name, + }); + expect(requiredAction).to.be.empty; + }); + + it("should get required actions", async () => { + const requiredActions = + await kcAdminClient.authenticationManagement.getRequiredActions(); + expect(requiredActions).to.be.an("array"); + }); + + it("should get required action by alias", async () => { + const requiredAction = + await kcAdminClient.authenticationManagement.getRequiredActionForAlias({ + alias: requiredActionProvider.providerId, + }); + expect(requiredAction).to.be.ok; + }); + + it("should update required action by alias", async () => { + const requiredAction = + await kcAdminClient.authenticationManagement.getRequiredActionForAlias({ + alias: requiredActionProvider.providerId, + }); + const response = + await kcAdminClient.authenticationManagement.updateRequiredAction( + { alias: requiredActionProvider.providerId }, + { + ...requiredAction, + enabled: true, + priority: 10, + } + ); + expect(response).to.be.empty; + }); + + it("should lower required action priority", async () => { + const requiredAction = + await kcAdminClient.authenticationManagement.getRequiredActionForAlias({ + alias: requiredActionProvider.providerId, + }); + const response = + await kcAdminClient.authenticationManagement.lowerRequiredActionPriority( + { alias: requiredActionProvider.providerId } + ); + expect(response).to.be.empty; + const requiredActionUpdated = + await kcAdminClient.authenticationManagement.getRequiredActionForAlias({ + alias: requiredActionProvider.providerId, + }); + expect(requiredActionUpdated.priority).to.be.greaterThan( + requiredAction.priority + ); + }); + + it("should raise required action priority", async () => { + const requiredAction = + await kcAdminClient.authenticationManagement.getRequiredActionForAlias({ + alias: requiredActionProvider.providerId, + }); + const response = + await kcAdminClient.authenticationManagement.raiseRequiredActionPriority( + { alias: requiredActionProvider.providerId } + ); + expect(response).to.be.empty; + const requiredActionUpdated = + await kcAdminClient.authenticationManagement.getRequiredActionForAlias({ + alias: requiredActionProvider.providerId, + }); + expect(requiredActionUpdated.priority).to.be.lessThan( + requiredAction.priority + ); + }); + + it("should get client authenticator providers", async () => { + const authenticationProviders = + await kcAdminClient.authenticationManagement.getClientAuthenticatorProviders(); + + expect(authenticationProviders).is.ok; + expect(authenticationProviders.length).to.be.equal(4); + }); + + it("should fetch form providers", async () => { + const formProviders = + await kcAdminClient.authenticationManagement.getFormActionProviders(); + expect(formProviders).is.ok; + expect(formProviders.length).to.be.eq(4); + }); + + it("should fetch authenticator providers", async () => { + const providers = + await kcAdminClient.authenticationManagement.getAuthenticatorProviders(); + expect(providers).is.ok; + expect(providers.length).to.be.greaterThan(1); + }); + }); + describe("Flows", () => { + it("should get the registered form providers", async () => { + const formProviders = + await kcAdminClient.authenticationManagement.getFormProviders(); + + expect(formProviders).to.be.ok; + expect(formProviders.length).to.be.eq(1); + expect(formProviders[0].displayName).to.be.eq("Registration Page"); + }); + + it("should get authentication flows", async () => { + const flows = await kcAdminClient.authenticationManagement.getFlows(); + + expect(flows.map((flow) => flow.alias)).to.be.deep.eq([ + "browser", + "direct grant", + "registration", + "reset credentials", + "clients", + "first broker login", + "docker auth", + "http challenge", + ]); + }); + + it("should get authentication flow", async () => { + const flows = await kcAdminClient.authenticationManagement.getFlows(); + const flow = await kcAdminClient.authenticationManagement.getFlow({ + flowId: flows[0].id!, + }); + + expect(flow.alias).to.be.eq("browser"); + }); + + it("should create new authentication flow", async () => { + const flow = "test"; + await kcAdminClient.authenticationManagement.createFlow({ + alias: flow, + providerId: "basic-flow", + description: "", + topLevel: true, + builtIn: false, + }); + + const flows = await kcAdminClient.authenticationManagement.getFlows(); + expect(flows.find((f) => f.alias === flow)).to.be.ok; + }); + + const flowName = "copy of browser"; + it("should copy existing authentication flow", async () => { + await kcAdminClient.authenticationManagement.copyFlow({ + flow: "browser", + newName: flowName, + }); + + const flows = await kcAdminClient.authenticationManagement.getFlows(); + const flow = flows.find((f) => f.alias === flowName); + expect(flow).to.be.ok; + }); + + it("should update authentication flow", async () => { + const flows = await kcAdminClient.authenticationManagement.getFlows(); + const flow = flows.find((f) => f.alias === flowName)!; + const description = "Updated description"; + flow.description = description; + const updatedFlow = + await kcAdminClient.authenticationManagement.updateFlow( + { flowId: flow.id! }, + flow + ); + + expect(updatedFlow.description).to.be.eq(description); + }); + + it("should delete authentication flow", async () => { + let flows = await kcAdminClient.authenticationManagement.getFlows(); + const flow = flows.find((f) => f.alias === flowName)!; + await kcAdminClient.authenticationManagement.deleteFlow({ + flowId: flow.id!, + }); + + flows = await kcAdminClient.authenticationManagement.getFlows(); + expect(flows.find((f) => f.alias === flowName)).to.be.undefined; + }); + }); + describe("Flow executions", () => { + it("should fetch all executions for a flow", async () => { + const executions = + await kcAdminClient.authenticationManagement.getExecutions({ + flow: "browser", + }); + expect(executions.length).to.be.gt(5); + }); + + const flowName = "executionTest"; + it("should add execution to a flow", async () => { + await kcAdminClient.authenticationManagement.copyFlow({ + flow: "browser", + newName: flowName, + }); + const execution = + await kcAdminClient.authenticationManagement.addExecutionToFlow({ + flow: flowName, + provider: "auth-otp-form", + }); + + expect(execution.id).to.be.ok; + }); + + it("should add flow to a flow", async () => { + const flow = await kcAdminClient.authenticationManagement.addFlowToFlow({ + flow: flowName, + alias: "subFlow", + description: "", + provider: "registration-page-form", + type: "basic-flow", + }); + const executions = + await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + expect(flow.id).to.be.ok; + + expect(executions.map((execution) => execution.displayName)).includes( + "subFlow" + ); + }); + + it("should update execution to a flow", async () => { + let executions = + await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + let execution = executions[executions.length - 1]; + const choice = execution.requirementChoices![1]; + execution.requirement = choice; + await kcAdminClient.authenticationManagement.updateExecution( + { flow: flowName }, + execution + ); + + executions = await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + execution = executions[executions.length - 1]; + + expect(execution.requirement).to.be.eq(choice); + }); + + it("should delete execution", async () => { + let executions = + await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + const id = executions[0].id!; + await kcAdminClient.authenticationManagement.delExecution({ id }); + executions = await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + expect(executions.find((ex) => ex.id === id)).to.be.undefined; + }); + + it("should raise priority of execution", async () => { + let executions = + await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + let execution = executions[executions.length - 1]; + const priority = execution.index!; + await kcAdminClient.authenticationManagement.raisePriorityExecution({ + id: execution.id!, + }); + + executions = await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + execution = executions.find((ex) => ex.id === execution.id)!; + + expect(execution.index).to.be.eq(priority - 1); + }); + + it("should lower priority of execution", async () => { + let executions = + await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + let execution = executions[0]; + const priority = execution.index!; + await kcAdminClient.authenticationManagement.lowerPriorityExecution({ + id: execution.id!, + }); + + executions = await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }); + execution = executions.find((ex) => ex.id === execution.id)!; + + expect(execution.index).to.be.eq(priority + 1); + }); + + it("should create, update and delete config for execution", async () => { + const execution = ( + await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }) + )[0]; + const alias = "test"; + let config = await kcAdminClient.authenticationManagement.createConfig({ + id: execution.id, + alias, + }); + config = await kcAdminClient.authenticationManagement.getConfig({ + id: config.id!, + }); + expect(config.alias).to.be.eq(alias); + + const extraConfig = { defaultProvider: "sdf" }; + await kcAdminClient.authenticationManagement.updateConfig({ + ...config, + config: extraConfig, + }); + config = await kcAdminClient.authenticationManagement.getConfig({ + id: config.id!, + }); + + expect(config.config!.defaultProvider).to.be.eq( + extraConfig.defaultProvider + ); + + await kcAdminClient.authenticationManagement.delConfig({ + id: config.id!, + }); + try { + await kcAdminClient.authenticationManagement.getConfig({ + id: config.id!, + }); + fail("should not find deleted config"); + } catch (error) { + // ignore + } + }); + + it("should fetch config description for execution", async () => { + const execution = ( + await kcAdminClient.authenticationManagement.getExecutions({ + flow: flowName, + }) + )[0]; + + const configDescription = + await kcAdminClient.authenticationManagement.getConfigDescription({ + providerId: execution.providerId!, + }); + expect(configDescription).is.ok; + expect(configDescription.providerId).to.be.eq(execution.providerId); + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/clientPolicies.spec.ts b/js/libs/keycloak-admin-client/test/clientPolicies.spec.ts new file mode 100644 index 0000000000..149868299f --- /dev/null +++ b/js/libs/keycloak-admin-client/test/clientPolicies.spec.ts @@ -0,0 +1,58 @@ +// tslint:disable:no-unused-expression +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Client Policies", () => { + let kcAdminClient: KeycloakAdminClient; + const newPolicy = { + name: "new_test_policy", + }; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + }); + + it("creates/updates client policy", async () => { + const createdPolicy = await kcAdminClient.clientPolicies.updatePolicy({ + policies: [newPolicy], + }); + expect(createdPolicy).to.be.deep.eq(""); + }); + + it("lists client policy profiles", async () => { + const profiles = await kcAdminClient.clientPolicies.listProfiles({ + includeGlobalProfiles: true, + }); + expect(profiles).to.be.ok; + }); + + it("create client policy profiles", async () => { + const profiles = await kcAdminClient.clientPolicies.listProfiles({ + includeGlobalProfiles: true, + }); + const globalProfiles = profiles.globalProfiles; + const newClientProfiles = { + profiles: [ + { + name: "test", + executors: [], + }, + ], + globalProfiles, + }; + + const createdClientProfile = + await kcAdminClient.clientPolicies.createProfiles(newClientProfiles); + + expect(createdClientProfile).to.be.deep.eq(""); + }); + + it("lists client policy policies", async () => { + const policies = await kcAdminClient.clientPolicies.listPolicies(); + expect(policies).to.be.ok; + }); +}); diff --git a/js/libs/keycloak-admin-client/test/clientRegistrationPolicies.ts b/js/libs/keycloak-admin-client/test/clientRegistrationPolicies.ts new file mode 100644 index 0000000000..4663568ada --- /dev/null +++ b/js/libs/keycloak-admin-client/test/clientRegistrationPolicies.ts @@ -0,0 +1,20 @@ +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Client Registration Policies", () => { + let client: KeycloakAdminClient; + + before(async () => { + client = new KeycloakAdminClient(); + await client.auth(credentials); + }); + + it("list client registration policies", async () => { + const clientRegistrationPolicies = + await client.clientRegistrationPolicies.find(); + expect(clientRegistrationPolicies).to.be.ok; + }); +}); diff --git a/js/libs/keycloak-admin-client/test/clientScopes.spec.ts b/js/libs/keycloak-admin-client/test/clientScopes.spec.ts new file mode 100644 index 0000000000..3f3175a313 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/clientScopes.spec.ts @@ -0,0 +1,663 @@ +// tslint:disable:no-unused-expression +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type ClientRepresentation from "../src/defs/clientRepresentation.js"; +import type ClientScopeRepresentation from "../src/defs/clientScopeRepresentation.js"; +import type ProtocolMapperRepresentation from "../src/defs/protocolMapperRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Client Scopes", () => { + let kcAdminClient: KeycloakAdminClient; + let currentClientScope: ClientScopeRepresentation; + let currentClientScopeName: string; + let currentClient: ClientRepresentation; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + }); + + beforeEach(async () => { + currentClientScopeName = "best-of-the-bests-scope"; + await kcAdminClient.clientScopes.create({ + name: currentClientScopeName, + }); + currentClientScope = (await kcAdminClient.clientScopes.findOneByName({ + name: currentClientScopeName, + }))!; + }); + + afterEach(async () => { + // cleanup default client scopes + try { + await kcAdminClient.clientScopes.delDefaultClientScope({ + id: currentClientScope.id!, + }); + } catch (e) { + // ignore + } + + // cleanup optional client scopes + try { + await kcAdminClient.clientScopes.delDefaultOptionalClientScope({ + id: currentClientScope.id!, + }); + } catch (e) { + // ignore + } + + // cleanup client scopes + try { + await kcAdminClient.clientScopes.delByName({ + name: currentClientScopeName, + }); + } catch (e) { + // ignore + } + }); + + it("list client scopes", async () => { + const scopes = await kcAdminClient.clientScopes.find(); + expect(scopes).to.be.ok; + }); + + it("create client scope and get by name", async () => { + // ensure that the scope does not exist + try { + await kcAdminClient.clientScopes.delByName({ + name: currentClientScopeName, + }); + } catch (e) { + // ignore + } + + await kcAdminClient.clientScopes.create({ + name: currentClientScopeName, + }); + + const scope = (await kcAdminClient.clientScopes.findOneByName({ + name: currentClientScopeName, + }))!; + expect(scope).to.be.ok; + expect(scope.name).to.equal(currentClientScopeName); + }); + + it("create client scope and return id", async () => { + // ensure that the scope does not exist + try { + await kcAdminClient.clientScopes.delByName({ + name: currentClientScopeName, + }); + } catch (e) { + // ignore + } + + const { id } = await kcAdminClient.clientScopes.create({ + name: currentClientScopeName, + }); + + const scope = (await kcAdminClient.clientScopes.findOne({ + id, + }))!; + expect(scope).to.be.ok; + expect(scope.name).to.equal(currentClientScopeName); + }); + + it("find scope by id", async () => { + const scope = await kcAdminClient.clientScopes.findOne({ + id: currentClientScope.id!, + }); + expect(scope).to.be.ok; + expect(scope).to.eql(currentClientScope); + }); + + it("find scope by name", async () => { + const scope = (await kcAdminClient.clientScopes.findOneByName({ + name: currentClientScopeName, + }))!; + expect(scope).to.be.ok; + expect(scope.name).to.eql(currentClientScopeName); + }); + + it("return null if scope not found by id", async () => { + const scope = await kcAdminClient.clientScopes.findOne({ + id: "I do not exist", + }); + expect(scope).to.be.null; + }); + + it("return null if scope not found by name", async () => { + const scope = await kcAdminClient.clientScopes.findOneByName({ + name: "I do not exist", + }); + expect(scope).to.be.undefined; + }); + + it.skip("update client scope", async () => { + const { id, description: oldDescription } = currentClientScope; + const description = "This scope is totally awesome."; + + await kcAdminClient.clientScopes.update({ id: id! }, { description }); + const updatedScope = (await kcAdminClient.clientScopes.findOne({ + id: id!, + }))!; + expect(updatedScope).to.be.ok; + expect(updatedScope).not.to.eql(currentClientScope); + expect(updatedScope.description).to.eq(description); + expect(updatedScope.description).not.to.eq(oldDescription); + }); + + it("delete single client scope by id", async () => { + await kcAdminClient.clientScopes.del({ + id: currentClientScope.id!, + }); + const scope = await kcAdminClient.clientScopes.findOne({ + id: currentClientScope.id!, + }); + expect(scope).not.to.be.ok; + }); + + it("delete single client scope by name", async () => { + await kcAdminClient.clientScopes.delByName({ + name: currentClientScopeName, + }); + const scope = await kcAdminClient.clientScopes.findOneByName({ + name: currentClientScopeName, + }); + expect(scope).not.to.be.ok; + }); + + describe("default client scope", () => { + it("list default client scopes", async () => { + const defaultClientScopes = + await kcAdminClient.clientScopes.listDefaultClientScopes(); + expect(defaultClientScopes).to.be.ok; + }); + + it("add default client scope", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addDefaultClientScope({ id: id! }); + + const defaultClientScopeList = + await kcAdminClient.clientScopes.listDefaultClientScopes(); + const defaultClientScope = defaultClientScopeList.find( + (scope) => scope.id === id + )!; + + expect(defaultClientScope).to.be.ok; + expect(defaultClientScope.id).to.equal(currentClientScope.id); + expect(defaultClientScope.name).to.equal(currentClientScope.name); + }); + + it("delete default client scope", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addDefaultClientScope({ id: id! }); + + await kcAdminClient.clientScopes.delDefaultClientScope({ id: id! }); + + const defaultClientScopeList = + await kcAdminClient.clientScopes.listDefaultClientScopes(); + const defaultClientScope = defaultClientScopeList.find( + (scope) => scope.id === id + ); + + expect(defaultClientScope).not.to.be.ok; + }); + }); + + describe("default optional client scopes", () => { + it("list default optional client scopes", async () => { + const defaultOptionalClientScopes = + await kcAdminClient.clientScopes.listDefaultOptionalClientScopes(); + expect(defaultOptionalClientScopes).to.be.ok; + }); + + it("add default optional client scope", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addDefaultOptionalClientScope({ + id: id!, + }); + + const defaultOptionalClientScopeList = + await kcAdminClient.clientScopes.listDefaultOptionalClientScopes(); + const defaultOptionalClientScope = defaultOptionalClientScopeList.find( + (scope) => scope.id === id + )!; + + expect(defaultOptionalClientScope).to.be.ok; + expect(defaultOptionalClientScope.id).to.eq(currentClientScope.id); + expect(defaultOptionalClientScope.name).to.eq(currentClientScope.name); + }); + + it("delete default optional client scope", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addDefaultOptionalClientScope({ + id: id!, + }); + await kcAdminClient.clientScopes.delDefaultOptionalClientScope({ + id: id!, + }); + + const defaultOptionalClientScopeList = + await kcAdminClient.clientScopes.listDefaultOptionalClientScopes(); + const defaultOptionalClientScope = defaultOptionalClientScopeList.find( + (scope) => scope.id === id + ); + + expect(defaultOptionalClientScope).not.to.be.ok; + }); + }); + + describe("protocol mappers", () => { + let dummyMapper: ProtocolMapperRepresentation; + + beforeEach(() => { + dummyMapper = { + name: "mapping-maps-mapper", + protocol: "openid-connect", + protocolMapper: "oidc-audience-mapper", + }; + }); + + afterEach(async () => { + try { + const { id } = currentClientScope; + const { id: mapperId } = + (await kcAdminClient.clientScopes.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + await kcAdminClient.clientScopes.delProtocolMapper({ + id: id!, + mapperId: mapperId!, + }); + } catch (e) { + // ignore + } + }); + + it("list protocol mappers", async () => { + const { id } = currentClientScope; + const mapperList = await kcAdminClient.clientScopes.listProtocolMappers({ + id: id!, + }); + expect(mapperList).to.be.ok; + }); + + it("add multiple protocol mappers", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addMultipleProtocolMappers({ id: id! }, [ + dummyMapper, + ]); + + const mapper = (await kcAdminClient.clientScopes.findProtocolMapperByName( + { + id: id!, + name: dummyMapper.name!, + } + ))!; + expect(mapper).to.be.ok; + expect(mapper.protocol).to.eq(dummyMapper.protocol); + expect(mapper.protocolMapper).to.eq(dummyMapper.protocolMapper); + }); + + it("add single protocol mapper", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addProtocolMapper( + { id: id! }, + dummyMapper + ); + + const mapper = (await kcAdminClient.clientScopes.findProtocolMapperByName( + { + id: id!, + name: dummyMapper.name!, + } + ))!; + expect(mapper).to.be.ok; + expect(mapper.protocol).to.eq(dummyMapper.protocol); + expect(mapper.protocolMapper).to.eq(dummyMapper.protocolMapper); + }); + + it("find protocol mapper by id", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addProtocolMapper( + { id: id! }, + dummyMapper + ); + + const { id: mapperId } = + (await kcAdminClient.clientScopes.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + + const mapper = await kcAdminClient.clientScopes.findProtocolMapper({ + id: id!, + mapperId: mapperId!, + }); + + expect(mapper).to.be.ok; + expect(mapper?.id).to.eql(mapperId); + }); + + it("find protocol mapper by name", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addProtocolMapper( + { id: id! }, + dummyMapper + ); + + const mapper = (await kcAdminClient.clientScopes.findProtocolMapperByName( + { + id: id!, + name: dummyMapper.name!, + } + ))!; + + expect(mapper).to.be.ok; + expect(mapper.name).to.eql(dummyMapper.name); + }); + + it("find protocol mappers by protocol", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addProtocolMapper( + { id: id! }, + dummyMapper + ); + + const mapperList = + await kcAdminClient.clientScopes.findProtocolMappersByProtocol({ + id: id!, + protocol: dummyMapper.protocol!, + }); + + expect(mapperList).to.be.ok; + expect(mapperList.length).to.be.gte(1); + + const mapper = mapperList.find((item) => item.name === dummyMapper.name); + expect(mapper).to.be.ok; + }); + + it("update protocol mapper", async () => { + const { id } = currentClientScope; + + dummyMapper.config = { "access.token.claim": "true" }; + await kcAdminClient.clientScopes.addProtocolMapper( + { id: id! }, + dummyMapper + ); + const mapper = (await kcAdminClient.clientScopes.findProtocolMapperByName( + { + id: id!, + name: dummyMapper.name!, + } + ))!; + + expect(mapper.config!["access.token.claim"]).to.eq("true"); + + mapper.config = { "access.token.claim": "false" }; + + await kcAdminClient.clientScopes.updateProtocolMapper( + { id: id!, mapperId: mapper.id! }, + mapper + ); + + const updatedMapper = + (await kcAdminClient.clientScopes.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + + expect(updatedMapper.config!["access.token.claim"]).to.eq("false"); + }); + + it("delete protocol mapper", async () => { + const { id } = currentClientScope; + await kcAdminClient.clientScopes.addProtocolMapper( + { id: id! }, + dummyMapper + ); + + const { id: mapperId } = + (await kcAdminClient.clientScopes.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + + await kcAdminClient.clientScopes.delProtocolMapper({ + id: id!, + mapperId: mapperId!, + }); + + const mapper = await kcAdminClient.clientScopes.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }); + + expect(mapper).not.to.be.ok; + }); + }); + + describe("scope mappings", () => { + it("list client and realm scope mappings", async () => { + const { id } = currentClientScope; + const scopes = await kcAdminClient.clientScopes.listScopeMappings({ + id: id!, + }); + expect(scopes).to.be.ok; + }); + + describe("client", () => { + const dummyClientId = "scopeMappings-dummy"; + const dummyRoleName = "scopeMappingsRole-dummy"; + + beforeEach(async () => { + const { id } = await kcAdminClient.clients.create({ + clientId: dummyClientId, + }); + currentClient = (await kcAdminClient.clients.findOne({ + id, + }))!; + + await kcAdminClient.clients.createRole({ + id, + name: dummyRoleName, + }); + }); + + afterEach(async () => { + const { id } = currentClient; + await kcAdminClient.clients.delRole({ + id: id!, + roleName: dummyRoleName, + }); + await kcAdminClient.clients.del({ id: id! }); + }); + + it("add scope mappings", async () => { + const { id } = currentClientScope; + const { id: clientUniqueId } = currentClient; + + const availableRoles = + await kcAdminClient.clientScopes.listAvailableClientScopeMappings({ + id: id!, + client: clientUniqueId!, + }); + + const filteredRoles = availableRoles.filter((role) => !role.composite); + + await kcAdminClient.clientScopes.addClientScopeMappings( + { + id: id!, + client: clientUniqueId!, + }, + filteredRoles + ); + + const roles = await kcAdminClient.clientScopes.listClientScopeMappings({ + id: id!, + client: clientUniqueId!, + }); + + expect(roles).to.be.ok; + expect(roles).to.be.eql(filteredRoles); + }); + + it("list scope mappings", async () => { + const { id } = currentClientScope; + const { id: clientUniqueId } = currentClient; + const roles = await kcAdminClient.clientScopes.listClientScopeMappings({ + id: id!, + client: clientUniqueId!, + }); + expect(roles).to.be.ok; + }); + + it("list available scope mappings", async () => { + const { id } = currentClientScope; + const { id: clientUniqueId } = currentClient; + const roles = + await kcAdminClient.clientScopes.listAvailableClientScopeMappings({ + id: id!, + client: clientUniqueId!, + }); + expect(roles).to.be.ok; + }); + + it("list composite scope mappings", async () => { + const { id } = currentClientScope; + const { id: clientUniqueId } = currentClient; + const roles = + await kcAdminClient.clientScopes.listCompositeClientScopeMappings({ + id: id!, + client: clientUniqueId!, + }); + expect(roles).to.be.ok; + }); + + it("delete scope mappings", async () => { + const { id } = currentClientScope; + const { id: clientUniqueId } = currentClient; + + const rolesBefore = + await kcAdminClient.clientScopes.listClientScopeMappings({ + id: id!, + client: clientUniqueId!, + }); + + await kcAdminClient.clientScopes.delClientScopeMappings( + { + id: id!, + client: clientUniqueId!, + }, + rolesBefore + ); + + const rolesAfter = + await kcAdminClient.clientScopes.listClientScopeMappings({ + id: id!, + client: clientUniqueId!, + }); + + expect(rolesAfter).to.be.ok; + expect(rolesAfter).to.eql([]); + }); + }); + + describe("realm", () => { + const dummyRoleName = "realmScopeMappingsRole-dummy"; + + beforeEach(async () => { + await kcAdminClient.roles.create({ + name: dummyRoleName, + }); + }); + + afterEach(async () => { + try { + await kcAdminClient.roles.delByName({ + name: dummyRoleName, + }); + } catch (e) { + // ignore + } + }); + + it("add scope mappings", async () => { + const { id } = currentClientScope; + + const availableRoles = + await kcAdminClient.clientScopes.listAvailableRealmScopeMappings({ + id: id!, + }); + + const filteredRoles = availableRoles.filter((role) => !role.composite); + + await kcAdminClient.clientScopes.addRealmScopeMappings( + { id: id! }, + filteredRoles + ); + + const roles = await kcAdminClient.clientScopes.listRealmScopeMappings({ + id: id!, + }); + + expect(roles).to.be.ok; + expect(roles).to.include.deep.members(filteredRoles); + }); + + it("list scope mappings", async () => { + const { id } = currentClientScope; + const roles = await kcAdminClient.clientScopes.listRealmScopeMappings({ + id: id!, + }); + expect(roles).to.be.ok; + }); + + it("list available scope mappings", async () => { + const { id } = currentClientScope; + const roles = + await kcAdminClient.clientScopes.listAvailableRealmScopeMappings({ + id: id!, + }); + expect(roles).to.be.ok; + }); + + it("list composite scope mappings", async () => { + const { id } = currentClientScope; + const roles = + await kcAdminClient.clientScopes.listCompositeRealmScopeMappings({ + id: id!, + }); + expect(roles).to.be.ok; + }); + + it("delete scope mappings", async () => { + const { id } = currentClientScope; + + const rolesBefore = + await kcAdminClient.clientScopes.listRealmScopeMappings({ + id: id!, + }); + + await kcAdminClient.clientScopes.delRealmScopeMappings( + { + id: id!, + }, + rolesBefore + ); + + const rolesAfter = + await kcAdminClient.clientScopes.listRealmScopeMappings({ + id: id!, + }); + + expect(rolesAfter).to.be.ok; + expect(rolesAfter).to.eql([]); + }); + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/clients.spec.ts b/js/libs/keycloak-admin-client/test/clients.spec.ts new file mode 100644 index 0000000000..8d8490e944 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/clients.spec.ts @@ -0,0 +1,1308 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type ClientRepresentation from "../src/defs/clientRepresentation.js"; +import type ClientScopeRepresentation from "../src/defs/clientScopeRepresentation.js"; +import type PolicyRepresentation from "../src/defs/policyRepresentation.js"; +import { Logic } from "../src/defs/policyRepresentation.js"; +import type ProtocolMapperRepresentation from "../src/defs/protocolMapperRepresentation.js"; +import type ResourceRepresentation from "../src/defs/resourceRepresentation.js"; +import type ScopeRepresentation from "../src/defs/scopeRepresentation.js"; +import type UserRepresentation from "../src/defs/userRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Clients", () => { + let kcAdminClient: KeycloakAdminClient; + let currentClient: ClientRepresentation; + let currentClientScope: ClientScopeRepresentation; + let currentRoleName: string; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + // create client and also test it + // NOTICE: to be clear, clientId stands for the property `clientId` of client + // clientUniqueId stands for property `id` of client + const clientId = faker.internet.userName(); + const createdClient = await kcAdminClient.clients.create({ + clientId, + }); + expect(createdClient.id).to.be.ok; + + const client = await kcAdminClient.clients.findOne({ + id: createdClient.id, + }); + expect(client).to.be.ok; + currentClient = client!; + }); + + after(async () => { + // delete the current one + await kcAdminClient.clients.del({ + id: currentClient.id!, + }); + }); + + it("list clients", async () => { + const clients = await kcAdminClient.clients.find(); + expect(clients).to.be.ok; + }); + + it("get single client", async () => { + const clientUniqueId = currentClient.id!; + const client = await kcAdminClient.clients.findOne({ + id: clientUniqueId, + }); + // not sure why entity from list api will not have property: authorizationServicesEnabled + expect(client).to.deep.include(currentClient); + }); + + it("update single client", async () => { + const { clientId, id: clientUniqueId } = currentClient; + await kcAdminClient.clients.update( + { id: clientUniqueId! }, + { + // clientId is required in client update. no idea why... + clientId, + description: "test", + } + ); + + const client = await kcAdminClient.clients.findOne({ + id: clientUniqueId!, + }); + expect(client).to.include({ + description: "test", + }); + }); + + it("delete single client", async () => { + // create another one for delete test + const clientId = faker.internet.userName(); + const { id } = await kcAdminClient.clients.create({ + clientId, + }); + + // delete it + await kcAdminClient.clients.del({ + id, + }); + + const delClient = await kcAdminClient.clients.findOne({ + id, + }); + expect(delClient).to.be.null; + }); + + /** + * client roles + */ + describe("client roles", () => { + before(async () => { + const roleName = faker.internet.userName(); + // create a client role + const { roleName: createdRoleName } = + await kcAdminClient.clients.createRole({ + id: currentClient.id, + name: roleName, + }); + + expect(createdRoleName).to.be.equal(roleName); + + // assign currentClientRole + currentRoleName = roleName; + }); + + after(async () => { + // delete client role + await kcAdminClient.clients.delRole({ + id: currentClient.id!, + roleName: currentRoleName, + }); + }); + + it("list the client roles", async () => { + const roles = await kcAdminClient.clients.listRoles({ + id: currentClient.id!, + }); + + expect(roles[0]).to.include({ + name: currentRoleName, + }); + }); + + it("find the client role", async () => { + const role = await kcAdminClient.clients.findRole({ + id: currentClient.id!, + roleName: currentRoleName, + }); + + expect(role).to.include({ + name: currentRoleName, + clientRole: true, + containerId: currentClient.id, + }); + }); + + it("update the client role", async () => { + // NOTICE: roleName MUST be in the payload, no idea why... + const delta = { + name: currentRoleName, + description: "test", + }; + await kcAdminClient.clients.updateRole( + { + id: currentClient.id!, + roleName: currentRoleName, + }, + delta + ); + + // check the change + const role = await kcAdminClient.clients.findRole({ + id: currentClient.id!, + roleName: currentRoleName, + }); + + expect(role).to.include(delta); + }); + + it("delete a client role", async () => { + const roleName = faker.internet.userName(); + // create a client role + await kcAdminClient.clients.createRole({ + id: currentClient.id, + name: roleName, + }); + + // delete + await kcAdminClient.clients.delRole({ + id: currentClient.id!, + roleName, + }); + + // check it's null + const role = await kcAdminClient.clients.findRole({ + id: currentClient.id!, + roleName, + }); + + expect(role).to.be.null; + }); + }); + + describe("client secret", () => { + before(async () => { + const { clientId, id: clientUniqueId } = currentClient; + // update with serviceAccountsEnabled: true + await kcAdminClient.clients.update( + { + id: clientUniqueId!, + }, + { + clientId, + serviceAccountsEnabled: true, + } + ); + }); + + it("get client secret", async () => { + const credential = await kcAdminClient.clients.getClientSecret({ + id: currentClient.id!, + }); + + expect(credential).to.have.all.keys("type", "value"); + }); + + it("generate new client secret", async () => { + const newCredential = await kcAdminClient.clients.generateNewClientSecret( + { + id: currentClient.id!, + } + ); + + const credential = await kcAdminClient.clients.getClientSecret({ + id: currentClient.id!, + }); + + expect(newCredential).to.be.eql(credential); + }); + + it("generate new registration access token", async () => { + const newRegistrationAccessToken = + await kcAdminClient.clients.generateRegistrationAccessToken({ + id: currentClient.id!, + }); + + expect(newRegistrationAccessToken).to.be.ok; + }); + + it("invalidate rotation token", async () => { + await kcAdminClient.clients.invalidateSecret({ + id: currentClient.id!, + }); + }); + + it("get installation providers", async () => { + const installationProvider = + await kcAdminClient.clients.getInstallationProviders({ + id: currentClient.id!, + providerId: "keycloak-oidc-jboss-subsystem", + }); + expect(installationProvider).to.be.ok; + expect(typeof installationProvider).to.be.equal("string"); + }); + + it("get service account user", async () => { + const serviceAccountUser = + await kcAdminClient.clients.getServiceAccountUser({ + id: currentClient.id!, + }); + + expect(serviceAccountUser).to.be.ok; + }); + }); + + describe("default client scopes", () => { + let dummyClientScope: ClientScopeRepresentation; + + beforeEach(async () => { + dummyClientScope = { + name: "does-anyone-read-this", + description: "Oh - seems like you are reading Hey there!", + protocol: "openid-connect", + }; + + // setup dummy client scope + await kcAdminClient.clientScopes.create(dummyClientScope); + currentClientScope = (await kcAdminClient.clientScopes.findOneByName({ + name: dummyClientScope.name!, + }))!; + }); + + afterEach(async () => { + // cleanup default scopes + try { + const { id } = currentClient; + const { id: clientScopeId } = currentClientScope; + await kcAdminClient.clients.delDefaultClientScope({ + clientScopeId: clientScopeId!, + id: id!, + }); + } catch (e) { + // ignore + } + + // cleanup client scopes + try { + await kcAdminClient.clientScopes.delByName({ + name: dummyClientScope.name!, + }); + } catch (e) { + // ignore + } + }); + + it("list default client scopes", async () => { + const defaultClientScopes = + await kcAdminClient.clients.listDefaultClientScopes({ + id: currentClient.id!, + }); + + expect(defaultClientScopes).to.be.ok; + }); + + it("add default client scope", async () => { + const { id } = currentClient; + const { id: clientScopeId } = currentClientScope; + + await kcAdminClient.clients.addDefaultClientScope({ + id: id!, + clientScopeId: clientScopeId!, + }); + + const defaultScopes = await kcAdminClient.clients.listDefaultClientScopes( + { id: id! } + ); + + expect(defaultScopes).to.be.ok; + + const clientScope = defaultScopes.find( + (scope) => scope.id === clientScopeId + ); + expect(clientScope).to.be.ok; + }); + + it("delete default client scope", async () => { + const { id } = currentClient; + const { id: clientScopeId } = currentClientScope; + + await kcAdminClient.clients.addDefaultClientScope({ + id: id!, + clientScopeId: clientScopeId!, + }); + + await kcAdminClient.clients.delDefaultClientScope({ + id: id!, + clientScopeId: clientScopeId!, + }); + const defaultScopes = await kcAdminClient.clients.listDefaultClientScopes( + { id: id! } + ); + + const clientScope = defaultScopes.find( + (scope) => scope.id === clientScopeId + ); + expect(clientScope).not.to.be.ok; + }); + }); + + describe("optional client scopes", () => { + let dummyClientScope: ClientScopeRepresentation; + + beforeEach(async () => { + dummyClientScope = { + name: "i-hope-your-well", + description: "Everyone has that one friend.", + protocol: "openid-connect", + }; + + // setup dummy client scope + await kcAdminClient.clientScopes.create(dummyClientScope); + currentClientScope = (await kcAdminClient.clientScopes.findOneByName({ + name: dummyClientScope.name!, + }))!; + }); + + afterEach(async () => { + // cleanup optional scopes + try { + const { id } = currentClient; + const { id: clientScopeId } = currentClientScope; + await kcAdminClient.clients.delOptionalClientScope({ + clientScopeId: clientScopeId!, + id: id!, + }); + } catch (e) { + // ignore + } + + // cleanup client scopes + try { + await kcAdminClient.clientScopes.delByName({ + name: dummyClientScope.name!, + }); + } catch (e) { + // ignore + } + }); + + it("list optional client scopes", async () => { + const optionalClientScopes = + await kcAdminClient.clients.listOptionalClientScopes({ + id: currentClient.id!, + }); + + expect(optionalClientScopes).to.be.ok; + }); + + it("add optional client scope", async () => { + const { id } = currentClient; + const { id: clientScopeId } = currentClientScope; + + await kcAdminClient.clients.addOptionalClientScope({ + id: id!, + clientScopeId: clientScopeId!, + }); + + const optionalScopes = + await kcAdminClient.clients.listOptionalClientScopes({ id: id! }); + + expect(optionalScopes).to.be.ok; + + const clientScope = optionalScopes.find( + (scope) => scope.id === clientScopeId + ); + expect(clientScope).to.be.ok; + }); + + it("delete optional client scope", async () => { + const { id } = currentClient; + const { id: clientScopeId } = currentClientScope; + + await kcAdminClient.clients.addOptionalClientScope({ + id: id!, + clientScopeId: clientScopeId!, + }); + + await kcAdminClient.clients.delOptionalClientScope({ + id: id!, + clientScopeId: clientScopeId!, + }); + const optionalScopes = + await kcAdminClient.clients.listOptionalClientScopes({ id: id! }); + + const clientScope = optionalScopes.find( + (scope) => scope.id === clientScopeId + ); + expect(clientScope).not.to.be.ok; + }); + }); + + describe("protocol mappers", () => { + let dummyMapper: ProtocolMapperRepresentation; + + beforeEach(() => { + dummyMapper = { + name: "become-a-farmer", + protocol: "openid-connect", + protocolMapper: "oidc-role-name-mapper", + config: { + role: "admin", + "new.role.name": "farmer", + }, + }; + }); + + afterEach(async () => { + try { + const { id: clientUniqueId } = currentClient; + const { id: mapperId } = + (await kcAdminClient.clients.findProtocolMapperByName({ + id: clientUniqueId!, + name: dummyMapper.name!, + }))!; + await kcAdminClient.clients.delProtocolMapper({ + id: clientUniqueId!, + mapperId: mapperId!, + }); + } catch (e) { + // ignore + } + }); + + it("list protocol mappers", async () => { + const { id } = currentClient; + const mapperList = await kcAdminClient.clients.listProtocolMappers({ + id: id!, + }); + expect(mapperList).to.be.ok; + }); + + it("add multiple protocol mappers", async () => { + const { id } = currentClient; + await kcAdminClient.clients.addMultipleProtocolMappers({ id: id! }, [ + dummyMapper, + ]); + + const mapper = (await kcAdminClient.clients.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + expect(mapper).to.be.ok; + expect(mapper.protocol).to.eq(dummyMapper.protocol); + expect(mapper.protocolMapper).to.eq(dummyMapper.protocolMapper); + }); + + it("add single protocol mapper", async () => { + const { id } = currentClient; + await kcAdminClient.clients.addProtocolMapper({ id: id! }, dummyMapper); + + const mapper = (await kcAdminClient.clients.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + expect(mapper).to.be.ok; + expect(mapper.protocol).to.eq(dummyMapper.protocol); + expect(mapper.protocolMapper).to.eq(dummyMapper.protocolMapper); + }); + + it("find protocol mapper by id", async () => { + const { id } = currentClient; + await kcAdminClient.clients.addProtocolMapper({ id: id! }, dummyMapper); + + const { id: mapperId } = + (await kcAdminClient.clients.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + + const mapper = await kcAdminClient.clients.findProtocolMapperById({ + mapperId: mapperId!, + id: id!, + }); + + expect(mapper).to.be.ok; + expect(mapper.id).to.eql(mapperId); + }); + + it("find protocol mapper by name", async () => { + const { id } = currentClient; + await kcAdminClient.clients.addProtocolMapper({ id: id! }, dummyMapper); + + const mapper = (await kcAdminClient.clients.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + + expect(mapper).to.be.ok; + expect(mapper.name).to.eql(dummyMapper.name); + }); + + it("find protocol mappers by protocol", async () => { + const { id } = currentClient; + await kcAdminClient.clients.addProtocolMapper({ id: id! }, dummyMapper); + + const mapperList = + await kcAdminClient.clients.findProtocolMappersByProtocol({ + id: id!, + protocol: dummyMapper.protocol!, + }); + + expect(mapperList).to.be.ok; + expect(mapperList.length).to.be.gte(1); + + const mapper = mapperList.find((item) => item.name === dummyMapper.name); + expect(mapper).to.be.ok; + }); + + it("update protocol mapper", async () => { + const { id } = currentClient; + + dummyMapper.config = { "access.token.claim": "true" }; + await kcAdminClient.clients.addProtocolMapper({ id: id! }, dummyMapper); + const mapper = (await kcAdminClient.clients.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + + expect(mapper.config!["access.token.claim"]).to.eq("true"); + + mapper.config = { "access.token.claim": "false" }; + + await kcAdminClient.clients.updateProtocolMapper( + { id: id!, mapperId: mapper.id! }, + mapper + ); + + const updatedMapper = + (await kcAdminClient.clients.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + + expect(updatedMapper.config!["access.token.claim"]).to.eq("false"); + }); + + it("delete protocol mapper", async () => { + const { id } = currentClient; + await kcAdminClient.clients.addProtocolMapper({ id: id! }, dummyMapper); + + const { id: mapperId } = + (await kcAdminClient.clients.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }))!; + + await kcAdminClient.clients.delProtocolMapper({ + id: id!, + mapperId: mapperId!, + }); + + const mapper = await kcAdminClient.clients.findProtocolMapperByName({ + id: id!, + name: dummyMapper.name!, + }); + + expect(mapper).not.to.be.ok; + }); + }); + + describe("scope mappings", () => { + it("list client and realm scope mappings", async () => { + const { id } = currentClient; + const scopes = await kcAdminClient.clients.listScopeMappings({ + id: id!, + }); + expect(scopes).to.be.ok; + }); + + describe("client", () => { + const dummyRoleName = "clientScopeMappingsRole-dummy"; + + beforeEach(async () => { + const { id } = currentClient; + await kcAdminClient.clients.createRole({ + id, + name: dummyRoleName, + }); + }); + + afterEach(async () => { + try { + const { id } = currentClient; + await kcAdminClient.clients.delRole({ + id: id!, + roleName: dummyRoleName, + }); + } catch (e) { + // ignore + } + }); + + it("add scope mappings", async () => { + const { id: clientUniqueId } = currentClient; + + const availableRoles = + await kcAdminClient.clients.listAvailableClientScopeMappings({ + id: clientUniqueId!, + client: clientUniqueId!, + }); + + await kcAdminClient.clients.addClientScopeMappings( + { + id: clientUniqueId!, + client: clientUniqueId!, + }, + availableRoles + ); + + const roles = await kcAdminClient.clients.listClientScopeMappings({ + id: clientUniqueId!, + client: clientUniqueId!, + }); + + expect(roles).to.be.ok; + expect(roles).to.be.eql(availableRoles); + }); + + it("list scope mappings", async () => { + const { id: clientUniqueId } = currentClient; + const roles = await kcAdminClient.clients.listClientScopeMappings({ + id: clientUniqueId!, + client: clientUniqueId!, + }); + expect(roles).to.be.ok; + }); + + it("list available scope mappings", async () => { + const { id: clientUniqueId } = currentClient; + const roles = + await kcAdminClient.clients.listAvailableClientScopeMappings({ + id: clientUniqueId!, + client: clientUniqueId!, + }); + expect(roles).to.be.ok; + }); + + it("list composite scope mappings", async () => { + const { id: clientUniqueId } = currentClient; + const roles = + await kcAdminClient.clients.listCompositeClientScopeMappings({ + id: clientUniqueId!, + client: clientUniqueId!, + }); + expect(roles).to.be.ok; + }); + + it("delete scope mappings", async () => { + const { id: clientUniqueId } = currentClient; + + const rolesBefore = await kcAdminClient.clients.listClientScopeMappings( + { + id: clientUniqueId!, + client: clientUniqueId!, + } + ); + + await kcAdminClient.clients.delClientScopeMappings( + { + id: clientUniqueId!, + client: clientUniqueId!, + }, + rolesBefore + ); + + const rolesAfter = await kcAdminClient.clients.listClientScopeMappings({ + id: clientUniqueId!, + client: clientUniqueId!, + }); + + expect(rolesAfter).to.be.ok; + expect(rolesAfter).to.eql([]); + }); + + it("get effective scope mapping of all roles for a specific container", async () => { + const { id: clientUniqueId } = currentClient; + const roles = await kcAdminClient.clients.evaluatePermission({ + id: clientUniqueId!, + roleContainer: "master", + type: "granted", + scope: "openid", + }); + + expect(roles).to.be.ok; + expect(roles.length).to.be.eq(5); + }); + + it("get list of all protocol mappers", async () => { + const { id: clientUniqueId } = currentClient; + const protocolMappers = + await kcAdminClient.clients.evaluateListProtocolMapper({ + id: clientUniqueId!, + scope: "openid", + }); + expect(protocolMappers).to.be.ok; + expect(protocolMappers.length).to.be.gt(10); + }); + + it("get JSON with payload of examples", async () => { + const { id: clientUniqueId } = currentClient; + const username = faker.internet.userName(); + const user = await kcAdminClient.users.create({ + username, + }); + const accessToken = + await kcAdminClient.clients.evaluateGenerateAccessToken({ + id: clientUniqueId!, + userId: user.id, + scope: "openid", + }); + const idToken = await kcAdminClient.clients.evaluateGenerateIdToken({ + id: clientUniqueId!, + userId: user.id, + scope: "openid", + }); + const userInfo = await kcAdminClient.clients.evaluateGenerateUserInfo({ + id: clientUniqueId!, + userId: user.id, + scope: "openid", + }); + + expect(accessToken).to.be.ok; + expect(idToken).to.be.ok; + expect(userInfo).to.be.ok; + await kcAdminClient.users.del({ id: user.id }); + }); + }); + + describe("realm", () => { + const dummyRoleName = "realmScopeMappingsRole-dummy"; + + beforeEach(async () => { + await kcAdminClient.roles.create({ + name: dummyRoleName, + }); + }); + + afterEach(async () => { + try { + await kcAdminClient.roles.delByName({ + name: dummyRoleName, + }); + } catch (e) { + // ignore + } + }); + + it("add scope mappings", async () => { + const { id } = currentClient; + + const availableRoles = + await kcAdminClient.clients.listAvailableRealmScopeMappings({ + id: id!, + }); + + await kcAdminClient.clients.addRealmScopeMappings( + { id: id! }, + availableRoles + ); + + const roles = await kcAdminClient.clients.listRealmScopeMappings({ + id: id!, + }); + + expect(roles).to.be.ok; + expect(roles).to.deep.members(availableRoles); + }); + + it("list scope mappings", async () => { + const { id } = currentClient; + const roles = await kcAdminClient.clients.listRealmScopeMappings({ + id: id!, + }); + expect(roles).to.be.ok; + }); + + it("list available scope mappings", async () => { + const { id } = currentClient; + const roles = + await kcAdminClient.clients.listAvailableRealmScopeMappings({ + id: id!, + }); + expect(roles).to.be.ok; + }); + + it("list composite scope mappings", async () => { + const { id } = currentClient; + const roles = + await kcAdminClient.clients.listCompositeRealmScopeMappings({ + id: id!, + }); + expect(roles).to.be.ok; + }); + + it("delete scope mappings", async () => { + const { id } = currentClient; + + const rolesBefore = await kcAdminClient.clients.listRealmScopeMappings({ + id: id!, + }); + + await kcAdminClient.clients.delRealmScopeMappings( + { id: id! }, + rolesBefore + ); + + const rolesAfter = await kcAdminClient.clients.listRealmScopeMappings({ + id: id!, + }); + + expect(rolesAfter).to.be.ok; + expect(rolesAfter).to.eql([]); + }); + }); + }); + + describe("sessions", () => { + it("list clients user sessions", async () => { + const clientUniqueId = currentClient.id; + const userSessions = await kcAdminClient.clients.listSessions({ + id: clientUniqueId!, + }); + expect(userSessions).to.be.ok; + }); + + it("list clients offline user sessions", async () => { + const clientUniqueId = currentClient.id; + const userSessions = await kcAdminClient.clients.listOfflineSessions({ + id: clientUniqueId!, + }); + expect(userSessions).to.be.ok; + }); + + it("list clients user session count", async () => { + const clientUniqueId = currentClient.id; + const userSessions = await kcAdminClient.clients.getSessionCount({ + id: clientUniqueId!, + }); + expect(userSessions).to.be.ok; + }); + + it("list clients offline user session count", async () => { + const clientUniqueId = currentClient.id; + const userSessions = await kcAdminClient.clients.getOfflineSessionCount({ + id: clientUniqueId!, + }); + expect(userSessions).to.be.ok; + }); + }); + + describe("nodes", () => { + const host = "127.0.0.1"; + it("register a node manually", async () => { + await kcAdminClient.clients.addClusterNode({ + id: currentClient.id!, + node: host, + }); + const client = (await kcAdminClient.clients.findOne({ + id: currentClient.id!, + }))!; + + expect(Object.keys(client.registeredNodes!)).to.be.eql([host]); + }); + + it("remove registered host", async () => { + await kcAdminClient.clients.deleteClusterNode({ + id: currentClient.id!, + node: host, + }); + const client = (await kcAdminClient.clients.findOne({ + id: currentClient.id!, + }))!; + + expect(client.registeredNodes).to.be.undefined; + }); + }); + + describe("client attribute certificate", () => { + const keystoreConfig = { + format: "JKS", + keyAlias: "new", + keyPassword: "password", + realmAlias: "master", + realmCertificate: false, + storePassword: "password", + }; + const attr = "jwt.credential"; + + it("generate and download keys", async () => { + const result = await kcAdminClient.clients.generateAndDownloadKey( + { id: currentClient.id!, attr }, + keystoreConfig + ); + + expect(result).to.be.ok; + }); + + it("generate key and updated info", async () => { + const certificate = await kcAdminClient.clients.generateKey({ + id: currentClient.id!, + attr, + }); + + expect(certificate).to.be.ok; + expect(certificate.certificate).to.be.ok; + + const info = await kcAdminClient.clients.getKeyInfo({ + id: currentClient.id!, + attr, + }); + expect(info).to.be.eql(certificate); + }); + + it("download key", async () => { + const result = await kcAdminClient.clients.downloadKey( + { id: currentClient.id!, attr }, + keystoreConfig + ); + + expect(result).to.be.ok; + }); + }); + + describe("authorization", async () => { + const resourceConfig = { + name: "testResourceName", + type: "testResourceType", + scopeNames: ["testScopeA", "testScopeB", "testScopeC"], + }; + const policyConfig = { + name: "testPolicyName", + type: "user", + logic: Logic.POSITIVE, + }; + const permissionConfig = { + name: "testPermissionName", + type: "scope", + logic: Logic.POSITIVE, + }; + let scopes: ScopeRepresentation[]; + let resource: ResourceRepresentation; + let policy: PolicyRepresentation; + let permission: PolicyRepresentation; + let user: UserRepresentation; + + before("enable authorization services", async () => { + await kcAdminClient.clients.update( + { id: currentClient.id! }, + { + clientId: currentClient.clientId, + authorizationServicesEnabled: true, + serviceAccountsEnabled: true, + } + ); + }); + + before("create test user", async () => { + const username = faker.internet.userName(); + user = await kcAdminClient.users.create({ + username, + }); + }); + + after("delete test user", async () => { + await kcAdminClient.users.del({ + id: user.id!, + }); + }); + + after("disable authorization services", async () => { + await kcAdminClient.clients.update( + { id: currentClient.id! }, + { + clientId: currentClient.clientId, + authorizationServicesEnabled: false, + serviceAccountsEnabled: false, + } + ); + }); + + it("create authorization scopes", async () => { + scopes = ( + await Promise.all( + resourceConfig.scopeNames.map(async (name) => { + const result = await kcAdminClient.clients.createAuthorizationScope( + { id: currentClient.id! }, + { + name, + } + ); + expect(result).to.be.ok; + return result; + }) + ) + ).sort((a, b) => (a.name < b.name ? -1 : 1)); + }); + + it("list all authorization scopes", async () => { + const result = await kcAdminClient.clients.listAllScopes({ + id: currentClient.id!, + }); + expect(result.sort((a, b) => (a.name! < b.name! ? -1 : 1))).to.deep.equal( + scopes + ); + }); + + it("update authorization scope", async () => { + const updatedScope = { ...scopes[0], displayName: "Hello" }; + await kcAdminClient.clients.updateAuthorizationScope( + { id: currentClient.id!, scopeId: scopes[0].id! }, + updatedScope + ); + + const fetchedScope = await kcAdminClient.clients.getAuthorizationScope({ + id: currentClient.id!, + scopeId: scopes[0].id!, + }); + + expect(fetchedScope).to.deep.equal(updatedScope); + }); + + it("list all resources by scope", async () => { + const result = await kcAdminClient.clients.listAllResourcesByScope({ + id: currentClient.id!, + scopeId: scopes[0].id!, + }); + expect(result).to.deep.equal([]); + }); + + it("list all permissions by scope", async () => { + const result = await kcAdminClient.clients.listAllPermissionsByScope({ + id: currentClient.id!, + scopeId: scopes[0].id!, + }); + expect(result).to.deep.equal([]); + }); + + it("import resource", async () => { + await kcAdminClient.clients.importResource( + { id: currentClient.id! }, + { + allowRemoteResourceManagement: true, + policyEnforcementMode: "ENFORCING", + resources: [], + policies: [], + scopes: [], + decisionStrategy: "UNANIMOUS", + } + ); + }); + + it("export resource", async () => { + const result = await kcAdminClient.clients.exportResource({ + id: currentClient.id!, + }); + + expect(result.allowRemoteResourceManagement).to.be.equal(true); + expect(result.resources?.length).to.be.equal(1); + }); + + it("create resource", async () => { + resource = await kcAdminClient.clients.createResource( + { id: currentClient.id! }, + { + name: resourceConfig.name, + type: resourceConfig.type, + scopes, + } + ); + expect(resource).to.be.ok; + }); + + it("get resource", async () => { + const r = await kcAdminClient.clients.getResource({ + id: currentClient.id!, + resourceId: resource._id!, + }); + expect(r).to.deep.equal(resource); + }); + + it("get resource server", async () => { + const resourceServer = await kcAdminClient.clients.getResourceServer({ + id: currentClient.id!, + }); + expect(resourceServer).to.be.ok; + expect(resourceServer.clientId).to.be.equal(currentClient.id); + + resourceServer.decisionStrategy = "UNANIMOUS"; + await kcAdminClient.clients.updateResourceServer( + { id: currentClient.id! }, + resourceServer + ); + }); + + it("list permission by resource", async () => { + const result = await kcAdminClient.clients.listPermissionsByResource({ + id: currentClient.id!, + resourceId: resource._id!, + }); + + expect(result).to.be.ok; + }); + + it("list scopes by resource", async () => { + const result = await kcAdminClient.clients.listScopesByResource({ + id: currentClient.id!, + resourceName: resource._id!, + }); + expect(result.sort((a, b) => (a.name < b.name ? -1 : 1))).to.deep.equal( + scopes + ); + }); + + it("list resources", async () => { + const result = await kcAdminClient.clients.listResources({ + id: currentClient.id!, + }); + expect(result).to.deep.include(resource); + }); + + it("update resource", async () => { + resource.name = "foo"; + await kcAdminClient.clients.updateResource( + { + id: currentClient.id!, + resourceId: resource._id!, + }, + resource + ); + const result = await kcAdminClient.clients.getResource({ + id: currentClient.id!, + resourceId: resource._id!, + }); + + expect(result.name).to.equal("foo"); + }); + + it("create policy", async () => { + policy = await kcAdminClient.clients.createPolicy( + { + id: currentClient.id!, + type: policyConfig.type, + }, + { + name: policyConfig.name, + logic: policyConfig.logic, + users: [user.id!], + } + ); + expect(policy).to.be.ok; + }); + + it("policy list dependencies", async () => { + const dependencies = await kcAdminClient.clients.listDependentPolicies({ + id: currentClient.id!, + policyId: policy.id!, + }); + expect(dependencies).to.be.ok; + }); + + it("create permission", async () => { + permission = await kcAdminClient.clients.createPermission( + { + id: currentClient.id!, + type: "scope", + }, + { + name: permissionConfig.name, + logic: permissionConfig.logic, + // @ts-ignore + resources: [resource._id], + policies: [policy.id!], + scopes: scopes.map((scope) => scope.id!), + } + ); + + const p = await kcAdminClient.clients.findPermissions({ + id: currentClient.id!, + name: permissionConfig.name, + }); + + expect(p.length).to.be.eq(1); + expect(p[0].logic).to.be.eq(permissionConfig.logic); + }); + + it("get associated scopes for permission", async () => { + const result = await kcAdminClient.clients.getAssociatedScopes({ + id: currentClient.id!, + permissionId: permission.id!, + }); + expect(result.sort((a, b) => (a.name < b.name ? -1 : 1))).to.deep.equal( + scopes + ); + }); + + it("get associated policies for permission", async () => { + const result = await kcAdminClient.clients.getAssociatedPolicies({ + id: currentClient.id!, + permissionId: permission.id!, + }); + + expect(result.length).to.be.eq(1); + expect(result[0].id).to.be.eq(policy.id); + }); + + it("get associated resources for permission", async () => { + const result = await kcAdminClient.clients.getAssociatedResources({ + id: currentClient.id!, + permissionId: permission.id!, + }); + expect(result).to.deep.equal([ + { + _id: resource._id, + name: resource.name, + }, + ]); + }); + + it("list policy providers", async () => { + const result = await kcAdminClient.clients.listPolicyProviders({ + id: currentClient.id!, + }); + expect(result).to.be.ok; + }); + + it.skip("Enable fine grained permissions", async () => { + const permission = await kcAdminClient.clients.updateFineGrainPermission( + { id: currentClient.id! }, + { enabled: true } + ); + expect(permission).to.include({ + enabled: true, + }); + }); + + it.skip("List fine grained permissions for this client", async () => { + const permissions = (await kcAdminClient.clients.listFineGrainPermissions( + { id: currentClient.id! } + ))!; + + expect(permissions.scopePermissions).to.be.an("object"); + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/components.spec.ts b/js/libs/keycloak-admin-client/test/components.spec.ts new file mode 100644 index 0000000000..fdbc64d1c4 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/components.spec.ts @@ -0,0 +1,96 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type ComponentRepresentation from "../src/defs/componentRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("User federation using component api", () => { + let kcAdminClient: KeycloakAdminClient; + let currentUserFed: ComponentRepresentation; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + // create user fed + const name = faker.internet.userName(); + const component = await kcAdminClient.components.create({ + name, + parentId: "master", + providerId: "ldap", + providerType: "org.keycloak.storage.UserStorageProvider", + config: { + editMode: ["READ_ONLY"], + }, + }); + expect(component.id).to.be.ok; + + // assign current user fed + const fed = (await kcAdminClient.components.findOne({ + id: component.id, + }))!; + currentUserFed = fed; + }); + + after(async () => { + await kcAdminClient.components.del({ + id: currentUserFed.id!, + }); + + // check deleted + const idp = await kcAdminClient.components.findOne({ + id: currentUserFed.id!, + }); + expect(idp).to.be.null; + }); + + it("list user federations", async () => { + const feds = await kcAdminClient.components.find({ + parent: "master", + type: "org.keycloak.storage.UserStorageProvider", + }); + expect(feds.length).to.be.least(1); + }); + + it("get a user federation", async () => { + const fed = await kcAdminClient.components.findOne({ + id: currentUserFed.id!, + }); + expect(fed).to.include({ + id: currentUserFed.id, + }); + }); + + it("get a sub components", async () => { + const list = await kcAdminClient.components.listSubComponents({ + id: currentUserFed.id!, + type: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + }); + + expect(list).to.be.ok; + }); + + it("update a user federation", async () => { + await kcAdminClient.components.update( + { id: currentUserFed.id! }, + { + // parentId, providerId, providerType required for update + parentId: "master", + providerId: "ldap", + providerType: "org.keycloak.storage.UserStorageProvider", + name: "cool-name", + } + ); + const updated = await kcAdminClient.components.findOne({ + id: currentUserFed.id!, + }); + + expect(updated).to.include({ + id: currentUserFed.id, + name: "cool-name", + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/constants.ts b/js/libs/keycloak-admin-client/test/constants.ts new file mode 100644 index 0000000000..6e58d47470 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/constants.ts @@ -0,0 +1,8 @@ +import type { Credentials } from "../src/utils/auth.js"; + +export const credentials: Credentials = { + username: "admin", + password: "admin", + grantType: "password", + clientId: "admin-cli", +}; diff --git a/js/libs/keycloak-admin-client/test/crossRealm.spec.ts b/js/libs/keycloak-admin-client/test/crossRealm.spec.ts new file mode 100644 index 0000000000..b36a4eb784 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/crossRealm.spec.ts @@ -0,0 +1,46 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Realms", () => { + let kcAdminClient: KeycloakAdminClient; + let currentRealmId: string; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + const realmId = faker.internet.userName(); + const realm = await kcAdminClient.realms.create({ + id: realmId, + realm: realmId, + }); + expect(realm.realmName).to.be.ok; + currentRealmId = realmId; + }); + + after(async () => { + await kcAdminClient.realms.del({ realm: currentRealmId }); + }); + + it("add a user to another realm", async () => { + const username = faker.internet.userName().toLowerCase(); + const user = await kcAdminClient.users.create({ + realm: currentRealmId, + username, + email: "test@keycloak.org", + // enabled required to be true in order to send actions email + emailVerified: true, + enabled: true, + }); + const foundUser = (await kcAdminClient.users.findOne({ + realm: currentRealmId, + id: user.id, + }))!; + expect(foundUser.username).to.be.eql(username); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/groupUser.spec.ts b/js/libs/keycloak-admin-client/test/groupUser.spec.ts new file mode 100644 index 0000000000..8610f1bc42 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/groupUser.spec.ts @@ -0,0 +1,206 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { omit, pick } from "lodash-es"; +import { KeycloakAdminClient } from "../src/client.js"; +import type ClientRepresentation from "../src/defs/clientRepresentation.js"; +import type GroupRepresentation from "../src/defs/groupRepresentation.js"; +import type PolicyRepresentation from "../src/defs/policyRepresentation.js"; +import { DecisionStrategy, Logic } from "../src/defs/policyRepresentation.js"; +import type UserRepresentation from "../src/defs/userRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Group user integration", () => { + let kcAdminClient: KeycloakAdminClient; + let currentGroup: GroupRepresentation; + let currentUser: UserRepresentation; + let managementClient: ClientRepresentation; + let currentUserPolicy: PolicyRepresentation; + let currentPolicy: PolicyRepresentation; + + before(async () => { + const groupName = faker.internet.userName(); + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + // create group + const group = await kcAdminClient.groups.create({ + name: groupName, + }); + currentGroup = (await kcAdminClient.groups.findOne({ id: group.id }))!; + + // create user + const username = faker.internet.userName(); + const user = await kcAdminClient.users.create({ + username, + email: "test@keycloak.org", + enabled: true, + }); + currentUser = (await kcAdminClient.users.findOne({ id: user.id }))!; + }); + + after(async () => { + await kcAdminClient.groups.del({ + id: currentGroup.id!, + }); + await kcAdminClient.users.del({ + id: currentUser.id!, + }); + }); + + it("should list user's group and expect empty", async () => { + const groups = await kcAdminClient.users.listGroups({ + id: currentUser.id!, + }); + expect(groups).to.be.eql([]); + }); + + it("should add user to group", async () => { + await kcAdminClient.users.addToGroup({ + id: currentUser.id!, + groupId: currentGroup.id!, + }); + + const groups = await kcAdminClient.users.listGroups({ + id: currentUser.id!, + }); + // expect id,name,path to be the same + expect(groups[0]).to.be.eql(pick(currentGroup, ["id", "name", "path"])); + }); + + it("should list members using group api", async () => { + const members = await kcAdminClient.groups.listMembers({ + id: currentGroup.id!, + }); + // access will not returned from member api + expect(members[0]).to.be.eql(omit(currentUser, ["access"])); + }); + + it("should remove user from group", async () => { + await kcAdminClient.users.delFromGroup({ + id: currentUser.id!, + groupId: currentGroup.id!, + }); + + const groups = await kcAdminClient.users.listGroups({ + id: currentUser.id!, + }); + expect(groups).to.be.eql([]); + }); + + /** + * Authorization permissions + */ + describe.skip("authorization permissions", () => { + before(async () => { + const clients = await kcAdminClient.clients.find(); + managementClient = clients.find( + (client) => client.clientId === "master-realm" + )!; + }); + after(async () => { + await kcAdminClient.clients.delPolicy({ + id: managementClient.id!, + policyId: currentUserPolicy.id!, + }); + }); + + it("Enable permissions", async () => { + const permission = await kcAdminClient.groups.updatePermission( + { id: currentGroup.id! }, + { enabled: true } + ); + expect(permission).to.include({ + enabled: true, + }); + }); + + it("list of users in policy management", async () => { + const userPolicyData: PolicyRepresentation = { + type: "user", + logic: Logic.POSITIVE, + decisionStrategy: DecisionStrategy.UNANIMOUS, + name: `policy.manager.${currentGroup.id}`, + users: [currentUser.id!], + }; + currentUserPolicy = await kcAdminClient.clients.createPolicy( + { id: managementClient.id!, type: userPolicyData.type! }, + userPolicyData + ); + + expect(currentUserPolicy).to.include({ + type: "user", + logic: Logic.POSITIVE, + decisionStrategy: DecisionStrategy.UNANIMOUS, + name: `policy.manager.${currentGroup.id}`, + }); + }); + + it("list the roles available for this group", async () => { + const permissions = (await kcAdminClient.groups.listPermissions({ + id: currentGroup.id!, + }))!; + + expect(permissions.scopePermissions).to.be.an("object"); + + const scopes = (await kcAdminClient.clients.listScopesByResource({ + id: managementClient.id!, + resourceName: permissions.resource!, + }))!; + + const policies = await kcAdminClient.clients.listPolicies({ + id: managementClient.id, + resource: permissions.resource, + max: 2, + }); + expect(policies).to.have.length(2); + + expect(scopes).to.have.length(5); + + // Search for the id of the management role + const roleId = scopes.find((scope) => scope.name === "manage")!.id; + + const userPolicy = await kcAdminClient.clients.findPolicyByName({ + id: managementClient.id!, + name: `policy.manager.${currentGroup.id}`, + }); + + expect(userPolicy).to.deep.include({ + name: `policy.manager.${currentGroup.id}`, + }); + + // Update of the role with the above modifications + const policyData: PolicyRepresentation = { + id: permissions.scopePermissions!.manage!, + name: `manage.permission.group.${currentGroup.id}`, + type: "scope", + logic: Logic.POSITIVE, + decisionStrategy: DecisionStrategy.UNANIMOUS, + resources: [permissions.resource!], + scopes: [roleId], + policies: [userPolicy.id!], + }; + await kcAdminClient.clients.updatePermission( + { + id: managementClient.id!, + permissionId: permissions.scopePermissions!.manage, + type: "scope", + }, + policyData + ); + currentPolicy = (await kcAdminClient.clients.findOnePermission({ + id: managementClient.id!, + permissionId: permissions.scopePermissions!.manage, + type: "scope", + }))!; + expect(currentPolicy).to.deep.include({ + id: permissions.scopePermissions!.manage, + name: `manage.permission.group.${currentGroup.id}`, + type: "scope", + logic: Logic.POSITIVE, + decisionStrategy: DecisionStrategy.UNANIMOUS, + }); + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/groups.spec.ts b/js/libs/keycloak-admin-client/test/groups.spec.ts new file mode 100644 index 0000000000..87bf93042e --- /dev/null +++ b/js/libs/keycloak-admin-client/test/groups.spec.ts @@ -0,0 +1,306 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type ClientRepresentation from "../src/defs/clientRepresentation.js"; +import type GroupRepresentation from "../src/defs/groupRepresentation.js"; +import type RoleRepresentation from "../src/defs/roleRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Groups", () => { + let kcAdminClient: KeycloakAdminClient; + let currentClient: ClientRepresentation; + let currentGroup: GroupRepresentation; + let currentRole: RoleRepresentation; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + // initialize group + const group = await kcAdminClient.groups.create({ + name: "cool-group", + }); + expect(group.id).to.be.ok; + currentGroup = (await kcAdminClient.groups.findOne({ id: group.id }))!; + }); + + after(async () => { + const groupId = currentGroup.id; + await kcAdminClient.groups.del({ + id: groupId!, + }); + + const group = await kcAdminClient.groups.findOne({ + id: groupId!, + }); + expect(group).to.be.null; + }); + + it("list groups", async () => { + const groups = await kcAdminClient.groups.find(); + expect(groups).to.be.ok; + }); + + it("count groups", async () => { + const result = await kcAdminClient.groups.count(); + expect(result.count).to.eq(1); + }); + + it("count groups with filter", async () => { + let result = await kcAdminClient.groups.count({ search: "fake-group" }); + expect(result.count).to.eq(0); + + result = await kcAdminClient.groups.count({ search: "cool-group" }); + expect(result.count).to.eq(1); + }); + + it("get single groups", async () => { + const groupId = currentGroup.id; + const group = await kcAdminClient.groups.findOne({ + id: groupId!, + }); + // get group from id will contains more fields than listing api + expect(group).to.deep.include(currentGroup); + }); + + it("update single groups", async () => { + const groupId = currentGroup.id; + await kcAdminClient.groups.update( + { id: groupId! }, + { name: "another-group-name" } + ); + + const group = await kcAdminClient.groups.findOne({ + id: groupId!, + }); + expect(group).to.include({ + name: "another-group-name", + }); + }); + + it("set or create child", async () => { + const groupName = "child-group"; + const groupId = currentGroup.id; + const childGroup = await kcAdminClient.groups.setOrCreateChild( + { id: groupId! }, + { name: groupName } + ); + + expect(childGroup.id).to.be.ok; + + const group = (await kcAdminClient.groups.findOne({ + id: groupId!, + }))!; + expect(group.subGroups![0]).to.deep.include({ + id: childGroup.id, + name: groupName, + path: `/${group.name}/${groupName}`, + }); + }); + + /** + * Role mappings + */ + describe("role-mappings", () => { + before(async () => { + // create new role + const roleName = faker.internet.userName(); + const { roleName: createdRoleName } = await kcAdminClient.roles.create({ + name: roleName, + }); + expect(createdRoleName).to.be.equal(roleName); + const role = await kcAdminClient.roles.findOneByName({ + name: roleName, + }); + currentRole = role!; + }); + + after(async () => { + await kcAdminClient.roles.delByName({ name: currentRole.name! }); + }); + + it("add a role to group", async () => { + // add role-mappings with role id + await kcAdminClient.groups.addRealmRoleMappings({ + id: currentGroup.id!, + + // at least id and name should appear + roles: [ + { + id: currentRole.id!, + name: currentRole.name!, + }, + ], + }); + }); + + it("list available role-mappings", async () => { + const roles = await kcAdminClient.groups.listAvailableRealmRoleMappings({ + id: currentGroup.id!, + }); + + // admin, create-realm, offline_access, uma_authorization + expect(roles.length).to.be.least(4); + }); + + it("list role-mappings", async () => { + const { realmMappings } = await kcAdminClient.groups.listRoleMappings({ + id: currentGroup.id!, + }); + + expect(realmMappings).to.be.ok; + // currentRole will have an empty `attributes`, but role-mappings do not + expect(currentRole).to.deep.include(realmMappings![0]); + }); + + it("list realm role-mappings of group", async () => { + const roles = await kcAdminClient.groups.listRealmRoleMappings({ + id: currentGroup.id!, + }); + // currentRole will have an empty `attributes`, but role-mappings do not + expect(currentRole).to.deep.include(roles[0]); + }); + + it("list realm composite role-mappings of group", async () => { + const roles = await kcAdminClient.groups.listCompositeRealmRoleMappings({ + id: currentGroup.id!, + }); + // todo: add data integrity check later + expect(roles).to.be.ok; + }); + + it("del realm role-mappings from group", async () => { + await kcAdminClient.groups.delRealmRoleMappings({ + id: currentGroup.id!, + roles: [ + { + id: currentRole.id!, + name: currentRole.name!, + }, + ], + }); + + const roles = await kcAdminClient.groups.listRealmRoleMappings({ + id: currentGroup.id!, + }); + expect(roles).to.be.empty; + }); + }); + + /** + * client Role mappings + */ + describe("client role-mappings", () => { + before(async () => { + // create new client + const clientId = faker.internet.userName(); + await kcAdminClient.clients.create({ + clientId, + }); + + const clients = await kcAdminClient.clients.find({ clientId }); + expect(clients[0]).to.be.ok; + currentClient = clients[0]; + + // create new client role + const roleName = faker.internet.userName(); + await kcAdminClient.clients.createRole({ + id: currentClient.id, + name: roleName, + }); + + // assign to currentRole + currentRole = await kcAdminClient.clients.findRole({ + id: currentClient.id!, + roleName, + }); + }); + + after(async () => { + await kcAdminClient.clients.delRole({ + id: currentClient.id!, + roleName: currentRole.name!, + }); + await kcAdminClient.clients.del({ id: currentClient.id! }); + }); + + it("add a client role to group", async () => { + // add role-mappings with role id + await kcAdminClient.groups.addClientRoleMappings({ + id: currentGroup.id!, + clientUniqueId: currentClient.id!, + + // at least id and name should appear + roles: [ + { + id: currentRole.id!, + name: currentRole.name!, + }, + ], + }); + }); + + it("list available client role-mappings for group", async () => { + const roles = await kcAdminClient.groups.listAvailableClientRoleMappings({ + id: currentGroup.id!, + clientUniqueId: currentClient.id!, + }); + + expect(roles).to.be.empty; + }); + + it("list client role-mappings of group", async () => { + const roles = await kcAdminClient.groups.listClientRoleMappings({ + id: currentGroup.id!, + clientUniqueId: currentClient.id!, + }); + + // currentRole will have an empty `attributes`, but role-mappings do not + expect(currentRole).to.deep.include(roles[0]); + }); + + it("list composite client role-mappings for group", async () => { + const roles = await kcAdminClient.groups.listCompositeClientRoleMappings({ + id: currentGroup.id!, + clientUniqueId: currentClient.id!, + }); + + expect(roles).to.be.ok; + }); + + it("del client role-mappings from group", async () => { + const roleName = faker.internet.userName(); + await kcAdminClient.clients.createRole({ + id: currentClient.id, + name: roleName, + }); + const role = await kcAdminClient.clients.findRole({ + id: currentClient.id!, + roleName, + }); + + // delete the created role + await kcAdminClient.groups.delClientRoleMappings({ + id: currentGroup.id!, + clientUniqueId: currentClient.id!, + roles: [ + { + id: role.id!, + name: role.name!, + }, + ], + }); + + // check if mapping is successfully deleted + const roles = await kcAdminClient.groups.listClientRoleMappings({ + id: currentGroup.id!, + clientUniqueId: currentClient.id!, + }); + + // should only left the one we added in the previous test + expect(roles.length).to.be.eql(1); + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/idp.spec.ts b/js/libs/keycloak-admin-client/test/idp.spec.ts new file mode 100644 index 0000000000..a104677987 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/idp.spec.ts @@ -0,0 +1,184 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Identity providers", () => { + let kcAdminClient: KeycloakAdminClient; + let currentIdpAlias: string; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + // create idp + const alias = faker.internet.userName(); + const idp = await kcAdminClient.identityProviders.create({ + alias, + providerId: "saml", + }); + expect(idp.id).to.be.ok; + currentIdpAlias = alias; + + // create idp mapper + const mapper = { + name: "First Name", + identityProviderAlias: currentIdpAlias, + identityProviderMapper: "saml-user-attribute-idp-mapper", + config: {}, + }; + const idpMapper = await kcAdminClient.identityProviders.createMapper({ + alias: currentIdpAlias, + identityProviderMapper: mapper, + }); + expect(idpMapper.id).to.be.ok; + }); + + after(async () => { + const idpMapper = await kcAdminClient.identityProviders.findMappers({ + alias: currentIdpAlias, + }); + + const idpMapperId = idpMapper[0].id; + await kcAdminClient.identityProviders.delMapper({ + alias: currentIdpAlias, + id: idpMapperId!, + }); + + const idpMapperUpdated = + await kcAdminClient.identityProviders.findOneMapper({ + alias: currentIdpAlias, + id: idpMapperId!, + }); + + // check idp mapper deleted + expect(idpMapperUpdated).to.be.null; + + await kcAdminClient.identityProviders.del({ + alias: currentIdpAlias, + }); + + const idp = await kcAdminClient.identityProviders.findOne({ + alias: currentIdpAlias, + }); + + // check idp deleted + expect(idp).to.be.null; + }); + + it("list idp", async () => { + const idps = await kcAdminClient.identityProviders.find(); + expect(idps.length).to.be.least(1); + }); + + it("get an idp", async () => { + const idp = await kcAdminClient.identityProviders.findOne({ + alias: currentIdpAlias, + }); + expect(idp).to.include({ + alias: currentIdpAlias, + }); + }); + + it("update an idp", async () => { + const idp = (await kcAdminClient.identityProviders.findOne({ + alias: currentIdpAlias, + }))!; + await kcAdminClient.identityProviders.update( + { alias: currentIdpAlias }, + { + // alias and providerId are required to update + alias: idp.alias!, + providerId: idp.providerId!, + displayName: "test", + } + ); + const updatedIdp = await kcAdminClient.identityProviders.findOne({ + alias: currentIdpAlias, + }); + + expect(updatedIdp).to.include({ + alias: currentIdpAlias, + displayName: "test", + }); + }); + + it("list idp factory", async () => { + const idpFactory = await kcAdminClient.identityProviders.findFactory({ + providerId: "saml", + }); + + expect(idpFactory).to.include({ + id: "saml", + }); + }); + + it("get an idp mapper", async () => { + const mappers = await kcAdminClient.identityProviders.findMappers({ + alias: currentIdpAlias, + }); + expect(mappers.length).to.be.least(1); + }); + + it("update an idp mapper", async () => { + const idpMapper = await kcAdminClient.identityProviders.findMappers({ + alias: currentIdpAlias, + }); + const idpMapperId = idpMapper[0].id; + + await kcAdminClient.identityProviders.updateMapper( + { alias: currentIdpAlias, id: idpMapperId! }, + { + id: idpMapperId, + identityProviderAlias: currentIdpAlias, + identityProviderMapper: "saml-user-attribute-idp-mapper", + config: { + "user.attribute": "firstName", + }, + } + ); + + const updatedIdpMappers = + (await kcAdminClient.identityProviders.findOneMapper({ + alias: currentIdpAlias, + id: idpMapperId!, + }))!; + + const userAttribute = updatedIdpMappers.config["user.attribute"]; + expect(userAttribute).to.equal("firstName"); + }); + + it("Import from url", async () => { + const result = await kcAdminClient.identityProviders.importFromUrl({ + providerId: "oidc", + fromUrl: + "http://localhost:8180/realms/master/.well-known/openid-configuration", + }); + + expect(result).to.be.ok; + expect(result.authorizationUrl).to.equal( + "http://localhost:8180/realms/master/protocol/openid-connect/auth" + ); + }); + + it.skip("Enable fine grained permissions", async () => { + const permission = await kcAdminClient.identityProviders.updatePermission( + { alias: currentIdpAlias }, + { enabled: true } + ); + expect(permission).to.include({ + enabled: true, + }); + }); + + it.skip("list permissions", async () => { + const permissions = await kcAdminClient.identityProviders.listPermissions({ + alias: currentIdpAlias, + }); + + expect(permissions.scopePermissions).to.be.an("object"); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/realms.spec.ts b/js/libs/keycloak-admin-client/test/realms.spec.ts new file mode 100644 index 0000000000..3a772d9b82 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/realms.spec.ts @@ -0,0 +1,525 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import { fail } from "assert"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type GroupRepresentation from "../src/defs/groupRepresentation.js"; +import type { PartialImportRealmRepresentation } from "../src/defs/realmRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +const createRealm = async (kcAdminClient: KeycloakAdminClient) => { + const realmId = faker.internet.userName().toLowerCase(); + const realmName = faker.internet.userName().toLowerCase(); + const realm = await kcAdminClient.realms.create({ + id: realmId, + realm: realmName, + }); + expect(realm.realmName).to.be.equal(realmName); + + return { realmId, realmName }; +}; + +const deleteRealm = async ( + kcAdminClient: KeycloakAdminClient, + currentRealmName: string +) => { + await kcAdminClient.realms.del({ realm: currentRealmName }); + const realm = await kcAdminClient.realms.findOne({ + realm: currentRealmName, + }); + expect(realm).to.be.null; +}; + +describe("Realms", () => { + let kcAdminClient: KeycloakAdminClient; + let currentRealmId: string; + let currentRealmName: string; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + }); + + it("list realms", async () => { + const realms = await kcAdminClient.realms.find(); + expect(realms.length).to.be.least(1); + }); + + it("create realm", async () => { + const realmId = faker.internet.userName().toLowerCase(); + const realmName = faker.internet.userName().toLowerCase(); + const realm = await kcAdminClient.realms.create({ + id: realmId, + realm: realmName, + }); + expect(realm.realmName).to.be.equal(realmName); + currentRealmId = realmId; + currentRealmName = realmName; + }); + + it("get a realm", async () => { + const realm = await kcAdminClient.realms.findOne({ + realm: currentRealmName, + }); + expect(realm).to.include({ + id: currentRealmId, + realm: currentRealmName, + }); + }); + + const roleToImport: PartialImportRealmRepresentation = { + ifResourceExists: "FAIL", + roles: { + realm: [ + { + id: "9d2638c8-4c62-4c42-90ea-5f3c836d0cc8", + name: "myRole", + scopeParamRequired: false, + composite: false, + }, + ], + }, + }; + + it("does partial import", async () => { + const result = await kcAdminClient.realms.partialImport({ + realm: currentRealmName, + rep: roleToImport, + }); + expect(result.added).to.be.eq(1); + expect(result.overwritten).to.be.eq(0); + expect(result.skipped).to.be.eq(0); + expect(result.results.length).to.be.eq(1); + expect(result.results[0].action).to.be.eq("ADDED"); + expect(result.results[0].resourceName).to.be.eq("myRole"); + expect(result.results[0].id).to.exist; + }); + + it("export a realm", async () => { + const realm = await kcAdminClient.realms.export({ + realm: currentRealmName, + exportClients: true, + exportGroupsAndRoles: true, + }); + expect(realm).to.include({ + id: currentRealmId, + realm: currentRealmName, + }); + }); + + it("update a realm", async () => { + await kcAdminClient.realms.update( + { realm: currentRealmName }, + { + displayName: "test", + } + ); + const realm = await kcAdminClient.realms.findOne({ + realm: currentRealmName, + }); + expect(realm).to.include({ + id: currentRealmId, + realm: currentRealmName, + displayName: "test", + }); + }); + + it("client registration policy providers", async () => { + const list = + await kcAdminClient.realms.getClientRegistrationPolicyProviders({ + realm: currentRealmName, + }); + + expect(list).to.be.ok; + }); + + it("delete a realm", async () => { + await kcAdminClient.realms.del({ realm: currentRealmName }); + const realm = await kcAdminClient.realms.findOne({ + realm: currentRealmName, + }); + expect(realm).to.be.null; + }); + + describe("Realm Client Initial Access", () => { + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + const created = await createRealm(kcAdminClient); + currentRealmName = created.realmName; + + await kcAdminClient.realms.createClientsInitialAccess( + { realm: currentRealmName }, + { count: 1, expiration: 0 } + ); + }); + + after(async () => { + deleteRealm(kcAdminClient, currentRealmName); + }); + + it("list client initial access", async () => { + const initialAccess = await kcAdminClient.realms.getClientsInitialAccess({ + realm: currentRealmName, + }); + expect(initialAccess).to.be.ok; + expect(initialAccess[0].count).to.be.eq(1); + }); + + it("del client initial access", async () => { + const access = await kcAdminClient.realms.createClientsInitialAccess( + { realm: currentRealmName }, + { count: 1, expiration: 0 } + ); + expect( + ( + await kcAdminClient.realms.getClientsInitialAccess({ + realm: currentRealmName, + }) + ).length + ).to.be.eq(2); + + await kcAdminClient.realms.delClientsInitialAccess({ + realm: currentRealmName, + id: access.id!, + }); + + const initialAccess = await kcAdminClient.realms.getClientsInitialAccess({ + realm: currentRealmName, + }); + expect(initialAccess).to.be.ok; + expect(initialAccess[0].count).to.be.eq(1); + }); + }); + + describe("Realm default groups", () => { + const groupName = "my-group"; + let currentGroup: GroupRepresentation; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + currentRealmName = (await createRealm(kcAdminClient)).realmName; + currentGroup = await kcAdminClient.groups.create({ + name: groupName, + realm: currentRealmName, + }); + }); + + after(async () => { + deleteRealm(kcAdminClient, currentRealmName); + }); + + it("add group to default groups", async () => { + await kcAdminClient.realms.addDefaultGroup({ + id: currentGroup.id!, + realm: currentRealmName, + }); + + const defaultGroups = await kcAdminClient.realms.getDefaultGroups({ + realm: currentRealmName, + }); + + expect(defaultGroups).to.be.ok; + expect(defaultGroups.length).to.be.eq(1); + expect(defaultGroups[0].id).to.be.eq(currentGroup.id); + }); + + it("get a group by its path name", async () => { + const queriedGroup = await kcAdminClient.realms.getGroupByPath({ + realm: currentRealmName, + path: groupName, + }); + + expect(queriedGroup).to.be.ok; + expect(queriedGroup.id).to.be.eq(currentGroup.id); + }); + + it("remove group from default groups", async () => { + await kcAdminClient.realms.removeDefaultGroup({ + id: currentGroup.id!, + realm: currentRealmName, + }); + + const defaultGroups = await kcAdminClient.realms.getDefaultGroups({ + realm: currentRealmName, + }); + + expect(defaultGroups).to.be.ok; + expect(defaultGroups.length).to.be.eq(0); + }); + }); + + describe("Realm Events", () => { + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + const created = await createRealm(kcAdminClient); + currentRealmId = created.realmId; + currentRealmName = created.realmName; + }); + + it("get events config for a realm", async () => { + const config = await kcAdminClient.realms.getConfigEvents({ + realm: currentRealmName, + }); + + expect(config).to.be.ok; + expect(config.adminEventsEnabled).to.be.eq(false); + }); + + it("enable events", async () => { + const config = await kcAdminClient.realms.getConfigEvents({ + realm: currentRealmName, + }); + config.eventsEnabled = true; + await kcAdminClient.realms.updateConfigEvents( + { realm: currentRealmName }, + config + ); + + const newConfig = await kcAdminClient.realms.getConfigEvents({ + realm: currentRealmName, + }); + + expect(newConfig).to.be.ok; + expect(newConfig.eventsEnabled).to.be.eq(true); + }); + + it("list events of a realm", async () => { + // @TODO: In order to test it, there have to be events + const events = await kcAdminClient.realms.findEvents({ + realm: currentRealmName, + }); + + expect(events).to.be.ok; + }); + + it("list admin events of a realm", async () => { + // @TODO: In order to test it, there have to be events + const events = await kcAdminClient.realms.findAdminEvents({ + realm: currentRealmName, + }); + + expect(events).to.be.ok; + }); + + it("clear events", async () => { + await kcAdminClient.realms.clearEvents({ realm: currentRealmName }); + await kcAdminClient.realms.clearAdminEvents({ realm: currentRealmName }); + + const events = await kcAdminClient.realms.findAdminEvents({ + realm: currentRealmName, + }); + + expect(events).to.deep.eq([]); + }); + + after(async () => { + deleteRealm(kcAdminClient, currentRealmName); + }); + }); + + describe("Realm Users Management Permissions", () => { + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + const created = await createRealm(kcAdminClient); + currentRealmId = created.realmId; + currentRealmName = created.realmName; + }); + + it("get users management permissions", async () => { + const managementPermissions = + await kcAdminClient.realms.getUsersManagementPermissions({ + realm: currentRealmName, + }); + expect(managementPermissions).to.be.ok; + }); + + it.skip("enable users management permissions", async () => { + const managementPermissions = + await kcAdminClient.realms.updateUsersManagementPermissions({ + realm: currentRealmName, + enabled: true, + }); + expect(managementPermissions).to.include({ enabled: true }); + }); + + it("get realm keys", async () => { + const keys = await kcAdminClient.realms.getKeys({ + realm: currentRealmName, + }); + expect(keys.active).to.be.ok; + }); + + after(async () => { + deleteRealm(kcAdminClient, currentRealmName); + }); + }); + + describe("Realm Session Management", () => { + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + const created = await createRealm(kcAdminClient); + currentRealmId = created.realmId; + currentRealmName = created.realmName; + }); + + it("push revocation", async () => { + const push = await kcAdminClient.realms.pushRevocation({ + realm: currentRealmName, + }); + expect(push).to.be.ok; + }); + + it("logs out all sessions", async () => { + const logout = await kcAdminClient.realms.logoutAll({ + realm: currentRealmName, + }); + expect(logout).to.be.ok; + }); + + after(async () => { + deleteRealm(kcAdminClient, currentRealmName); + }); + }); + + describe("Realm connection settings", () => { + it("should fail with invalid ldap settings", async () => { + try { + await kcAdminClient.realms.testLDAPConnection( + { realm: "master" }, + { + action: "testConnection", + authType: "simple", + bindCredential: "1", + bindDn: "1", + connectionTimeout: "", + connectionUrl: "1", + startTls: "", + useTruststoreSpi: "ldapsOnly", + } + ); + fail("exception should have been thrown"); + } catch (error) { + expect(error).to.be.ok; + } + }); + + it("should fail with invalid smtp settings", async () => { + try { + const user = ( + await kcAdminClient.users.find({ username: credentials.username }) + )[0]; + user.email = "test@test.com"; + await kcAdminClient.users.update({ id: user.id! }, user); + await kcAdminClient.realms.testSMTPConnection( + { realm: "master" }, + { + from: "cdd1641ff4-1781a4@inbox.mailtrap.io", + host: "localhost", + port: 3025, + } + ); + fail("exception should have been thrown"); + } catch (error) { + expect(error).to.be.ok; + } + }); + + it("should fail with invalid ldap server capabilities", async () => { + try { + await kcAdminClient.realms.ldapServerCapabilities( + { realm: "master" }, + { + action: "testConnection", + authType: "simple", + bindCredential: "1", + bindDn: "1", + connectionTimeout: "", + connectionUrl: "1", + startTls: "", + useTruststoreSpi: "ldapsOnly", + } + ); + fail("exception should have been thrown"); + } catch (error) { + expect(error).to.be.ok; + } + }); + }); + + describe("Realm localization", () => { + currentRealmName = "master"; + + it.skip("enable localization", async () => { + await kcAdminClient.realms.getRealmLocalizationTexts({ + realm: currentRealmName, + selectedLocale: "nl", + }); + }); + + it.skip("should add localization", async () => { + await kcAdminClient.realms.addLocalization( + { realm: currentRealmName, selectedLocale: "nl", key: "theKey" }, + "value" + ); + }); + + it.skip("should get realm specific locales", async () => { + const locales = await kcAdminClient.realms.getRealmSpecificLocales({ + realm: currentRealmName, + }); + + expect(locales).to.be.ok; + expect(locales).to.be.deep.eq(["nl"]); + }); + + it.skip("should get localization for specified locale", async () => { + const texts = await kcAdminClient.realms.getRealmLocalizationTexts({ + realm: currentRealmName, + selectedLocale: "nl", + }); + + expect(texts).to.be.ok; + expect(texts.theKey).to.be.eq("value"); + }); + + it.skip("should delete localization for specified locale key", async () => { + await kcAdminClient.realms.deleteRealmLocalizationTexts({ + realm: currentRealmName, + selectedLocale: "nl", + key: "theKey", + }); + + const texts = await kcAdminClient.realms.getRealmLocalizationTexts({ + realm: currentRealmName, + selectedLocale: "nl", + }); + expect(texts).to.be.ok; + expect(texts).to.be.deep.eq({}); + }); + + it.skip("should delete localization for specified locale", async () => { + await kcAdminClient.realms.deleteRealmLocalizationTexts({ + realm: currentRealmName, + selectedLocale: "nl", + }); + + const locales = await kcAdminClient.realms.getRealmSpecificLocales({ + realm: currentRealmName, + }); + expect(locales).to.be.ok; + expect(locales).to.be.deep.eq([]); + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/roles.spec.ts b/js/libs/keycloak-admin-client/test/roles.spec.ts new file mode 100644 index 0000000000..dc15837f26 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/roles.spec.ts @@ -0,0 +1,227 @@ +// tslint:disable:no-unused-expression +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type ClientRepresentation from "../src/defs/clientRepresentation.js"; +import type RoleRepresentation from "../src/defs/roleRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Roles", () => { + let client: KeycloakAdminClient; + let currentRole: RoleRepresentation; + + before(async () => { + client = new KeycloakAdminClient(); + await client.auth(credentials); + }); + + after(async () => { + // delete the currentRole with id + await client.roles.delById({ + id: currentRole.id!, + }); + }); + + it("list roles", async () => { + const roles = await client.roles.find(); + expect(roles).to.be.ok; + }); + + it("create roles and get by name", async () => { + const roleName = "cool-role"; + const createdRole = await client.roles.create({ + name: roleName, + }); + + expect(createdRole.roleName).to.be.equal(roleName); + const role = await client.roles.findOneByName({ name: roleName }); + expect(role).to.be.ok; + currentRole = role!; + }); + + it("get single roles by id", async () => { + const roleId = currentRole.id; + const role = await client.roles.findOneById({ + id: roleId!, + }); + expect(role).to.deep.include(currentRole); + }); + + it("update single role by name & by id", async () => { + await client.roles.updateByName( + { name: currentRole.name! }, + { + // dont know why if role name not exist in payload, role name will be overriden with empty string + // todo: open an issue on keycloak + name: "cool-role", + description: "cool", + } + ); + + const role = await client.roles.findOneByName({ + name: currentRole.name!, + }); + expect(role).to.include({ + description: "cool", + }); + + await client.roles.updateById( + { id: currentRole.id! }, + { + description: "another description", + } + ); + + const roleById = await client.roles.findOneById({ + id: currentRole.id!, + }); + expect(roleById).to.include({ + description: "another description", + }); + }); + + it("delete single roles by id", async () => { + await client.roles.create({ + name: "for-delete", + }); + + await client.roles.delByName({ + name: "for-delete", + }); + + const roleDelByName = await client.roles.findOneByName({ + name: "for-delete", + }); + expect(roleDelByName).to.be.null; + }); + + it("get users with role by name in realm", async () => { + const users = await client.roles.findUsersWithRole({ + name: "admin", + }); + expect(users).to.be.ok; + expect(users).to.be.an("array"); + }); + + it.skip("Enable fine grained permissions", async () => { + const permission = await client.roles.updatePermission( + { id: currentRole.id! }, + { enabled: true } + ); + expect(permission).to.include({ + enabled: true, + }); + }); + + it.skip("List fine grained permissions for this role", async () => { + const permissions = (await client.roles.listPermissions({ + id: currentRole.id!, + }))!; + + expect(permissions.scopePermissions).to.be.an("object"); + }); + + describe("Composite roles", () => { + const compositeRoleName = "compositeRole"; + let compositeRole: RoleRepresentation; + + beforeEach(async () => { + await client.roles.create({ + name: compositeRoleName, + }); + compositeRole = (await client.roles.findOneByName({ + name: compositeRoleName, + }))!; + await client.roles.createComposite({ roleId: currentRole.id! }, [ + compositeRole, + ]); + }); + + afterEach(async () => { + await client.roles.delByName({ + name: compositeRoleName, + }); + }); + + it("make the role a composite role by associating some child roles", async () => { + const children = await client.roles.getCompositeRoles({ + id: currentRole.id!, + }); + + // attributes on the composite role are empty and when fetched not there. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { attributes, ...rest } = compositeRole; + expect(children).to.be.eql([rest]); + }); + + it("search for composite roles", async () => { + const children = await client.roles.getCompositeRoles({ + id: currentRole.id!, + search: "not", + }); + + expect(children).to.be.an("array").that.is.length(0); + }); + + it("delete composite roles", async () => { + await client.roles.delCompositeRoles({ id: currentRole.id! }, [ + compositeRole, + ]); + const children = await client.roles.getCompositeRoles({ + id: currentRole.id!, + }); + + expect(children).to.be.an("array").that.is.empty; + }); + + describe("Get composite roles for client and realm", () => { + let createdClient: ClientRepresentation; + let clientRole: RoleRepresentation; + before(async () => { + createdClient = await client.clients.create({ clientId: "test" }); + const clientRoleName = "clientRole"; + await client.clients.createRole({ + id: createdClient.id, + name: clientRoleName, + }); + clientRole = await client.clients.findRole({ + id: createdClient.id!, + roleName: clientRoleName, + }); + + await client.roles.createComposite({ roleId: currentRole.id! }, [ + clientRole, + ]); + }); + + after(async () => { + await client.clients.del({ id: createdClient.id! }); + }); + + it("get composite role for the realm", async () => { + const realmChildren = await client.roles.getCompositeRolesForRealm({ + id: currentRole.id!, + }); + const children = await client.roles.getCompositeRoles({ + id: currentRole.id!, + }); + + delete compositeRole.attributes; + expect(realmChildren).to.be.eql([compositeRole]); + + expect(children).to.be.an("array").that.is.length(2); + }); + + it("get composite for the client", async () => { + const clientChildren = await client.roles.getCompositeRolesForClient({ + id: currentRole.id!, + clientId: createdClient.id!, + }); + + delete clientRole.attributes; + expect(clientChildren).to.be.eql([clientRole]); + }); + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/serverInfo.spec.ts b/js/libs/keycloak-admin-client/test/serverInfo.spec.ts new file mode 100644 index 0000000000..2037c311be --- /dev/null +++ b/js/libs/keycloak-admin-client/test/serverInfo.spec.ts @@ -0,0 +1,20 @@ +// tslint:disable:no-unused-expression +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Server Info", () => { + let client: KeycloakAdminClient; + + before(async () => { + client = new KeycloakAdminClient(); + await client.auth(credentials); + }); + + it("list server info", async () => { + const serverInfo = await client.serverInfo.find(); + expect(serverInfo).to.be.ok; + }); +}); diff --git a/js/libs/keycloak-admin-client/test/sessions.spec.ts b/js/libs/keycloak-admin-client/test/sessions.spec.ts new file mode 100644 index 0000000000..762ca35e6a --- /dev/null +++ b/js/libs/keycloak-admin-client/test/sessions.spec.ts @@ -0,0 +1,22 @@ +// tslint:disable:no-unused-expression +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Sessions", () => { + let client: KeycloakAdminClient; + + before(async () => { + client = new KeycloakAdminClient(); + await client.auth(credentials); + }); + + it("list sessions", async () => { + const sessions = await client.sessions.find(); + expect(sessions).to.be.ok; + expect(sessions.length).to.be.eq(1); + expect(sessions[0].clientId).to.be.eq("admin-cli"); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/stringifyQueryParams.spec.ts b/js/libs/keycloak-admin-client/test/stringifyQueryParams.spec.ts new file mode 100644 index 0000000000..dbe3d4a4b5 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/stringifyQueryParams.spec.ts @@ -0,0 +1,31 @@ +import { expect } from "chai"; +import { stringifyQueryParams } from "../src/utils/stringifyQueryParams.js"; + +describe("stringifyQueryParams", () => { + it("ignores undefined and null", () => { + expect(stringifyQueryParams({ foo: undefined, bar: null })).to.equal(""); + }); + + it("ignores empty strings", () => { + expect(stringifyQueryParams({ foo: "" })).to.equal(""); + }); + + it("ignores empty arrays", () => { + expect(stringifyQueryParams({ foo: [] })).to.equal(""); + }); + + it("accepts all other values", () => { + expect( + stringifyQueryParams({ + boolTrue: true, + boolFalse: false, + numPositive: 1, + numZero: 0, + numNegative: -1, + str: "Hello World!", + }) + ).to.equal( + "boolTrue=true&boolFalse=false&numPositive=1&numZero=0&numNegative=-1&str=Hello+World%21" + ); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/userStorageProvider.spec.ts b/js/libs/keycloak-admin-client/test/userStorageProvider.spec.ts new file mode 100644 index 0000000000..42a64b8f4b --- /dev/null +++ b/js/libs/keycloak-admin-client/test/userStorageProvider.spec.ts @@ -0,0 +1,54 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type ComponentRepresentation from "../src/defs/componentRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Users federation provider", () => { + let kcAdminClient: KeycloakAdminClient; + let currentUserFed: ComponentRepresentation; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + const name = faker.internet.userName(); + currentUserFed = await kcAdminClient.components.create({ + name, + parentId: "master", + providerId: "ldap", + providerType: "org.keycloak.storage.UserStorageProvider", + config: { + editMode: ["READ_ONLY"], + }, + }); + }); + + after(async () => { + await kcAdminClient.components.del({ + id: currentUserFed.id!, + }); + }); + + it("list storage provider", async () => { + const name = await kcAdminClient.userStorageProvider.name({ + id: currentUserFed.id!, + }); + expect(name).to.be.ok; + }); + + it("remove imported users", async () => { + await kcAdminClient.userStorageProvider.removeImportedUsers({ + id: currentUserFed.id!, + }); + }); + + it("unlink users", async () => { + await kcAdminClient.userStorageProvider.unlinkUsers({ + id: currentUserFed.id!, + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/users.spec.ts b/js/libs/keycloak-admin-client/test/users.spec.ts new file mode 100644 index 0000000000..176a25f720 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/users.spec.ts @@ -0,0 +1,682 @@ +// tslint:disable:no-unused-expression +import { faker } from "@faker-js/faker"; +import { fail } from "assert"; +import * as chai from "chai"; +import { omit } from "lodash-es"; +import { KeycloakAdminClient } from "../src/client.js"; +import type ClientRepresentation from "../src/defs/clientRepresentation.js"; +import type FederatedIdentityRepresentation from "../src/defs/federatedIdentityRepresentation.js"; +import type GroupRepresentation from "../src/defs/groupRepresentation.js"; +import { RequiredActionAlias } from "../src/defs/requiredActionProviderRepresentation.js"; +import type RoleRepresentation from "../src/defs/roleRepresentation.js"; +import type UserRepresentation from "../src/defs/userRepresentation.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Users", () => { + let kcAdminClient: KeycloakAdminClient; + let currentClient: ClientRepresentation; + let currentUser: UserRepresentation; + let currentRole: RoleRepresentation; + let federatedIdentity: FederatedIdentityRepresentation; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + // initialize user + const username = faker.internet.userName(); + const user = await kcAdminClient.users.create({ + username, + email: "test@keycloak.org", + // enabled required to be true in order to send actions email + emailVerified: true, + enabled: true, + attributes: { + key: "value", + }, + }); + + expect(user.id).to.be.ok; + currentUser = (await kcAdminClient.users.findOne({ id: user.id }))!; + + // add smtp to realm + await kcAdminClient.realms.update( + { realm: "master" }, + { + smtpServer: { + auth: true, + from: "0830021730-07fb21@inbox.mailtrap.io", + host: "smtp.mailtrap.io", + user: process.env.SMTP_USER, + password: process.env.SMTP_PWD, + }, + } + ); + }); + + after(async () => { + const userId = currentUser.id; + await kcAdminClient.users.del({ + id: userId!, + }); + + const user = await kcAdminClient.users.findOne({ + id: userId!, + }); + expect(user).to.be.null; + }); + + it("list users", async () => { + const users = await kcAdminClient.users.find(); + expect(users).to.be.ok; + }); + + it("count users", async () => { + const numUsers = await kcAdminClient.users.count(); + // admin user + created user in before hook + expect(numUsers).to.equal(2); + }); + + it("count users with filter", async () => { + const numUsers = await kcAdminClient.users.count({ + email: "test@keycloak.org", + }); + expect(numUsers).to.equal(1); + }); + + it.skip("gets the profile", async () => { + const profile = await kcAdminClient.users.getProfile(); + expect(profile).to.be.ok; + }); + + it.skip("updates the profile", async () => { + const profile = await kcAdminClient.users.updateProfile({}); + expect(profile).to.be.ok; + }); + + it.skip("find users by custom attributes", async () => { + // Searching by attributes is only available from Keycloak > 15 + const users = await kcAdminClient.users.find({ key: "value" }); + expect(users.length).to.be.equal(2); + expect(users[0]).to.be.deep.include(currentUser); + }); + + it("get single users", async () => { + const userId = currentUser.id; + const user = await kcAdminClient.users.findOne({ + id: userId!, + }); + expect(user).to.be.deep.include(currentUser); + }); + + it("update single users", async () => { + const userId = currentUser.id; + await kcAdminClient.users.update( + { id: userId! }, + { + firstName: "william", + lastName: "chang", + requiredActions: [RequiredActionAlias.UPDATE_PASSWORD], + emailVerified: true, + } + ); + + const user = await kcAdminClient.users.findOne({ + id: userId!, + }); + expect(user).to.deep.include({ + firstName: "william", + lastName: "chang", + requiredActions: [RequiredActionAlias.UPDATE_PASSWORD], + emailVerified: true, + }); + }); + + /** + * reset password + */ + + it("should reset user password", async () => { + const userId = currentUser.id; + await kcAdminClient.users.resetPassword({ + id: userId!, + credential: { + temporary: false, + type: "password", + value: "test", + }, + }); + }); + + /** + * get user credentials + */ + it("get user credentials", async () => { + const userId = currentUser.id; + const result = await kcAdminClient.users.getCredentials({ + id: userId!, + }); + + expect(result.map((c) => c.type)).to.include("password"); + }); + + it("get configured user storage credential types", async () => { + const userId = currentUser.id; + const result = await kcAdminClient.users.getUserStorageCredentialTypes({ + id: userId!, + }); + + expect(result).to.be.deep.eq([]); + }); + + /** + * delete user credentials + */ + it("delete user credentials", async () => { + const userId = currentUser.id; + const result = await kcAdminClient.users.getCredentials({ + id: userId!, + }); + + expect(result.map((c) => c.type)).to.include("password"); + + const credential = result[0]; + await kcAdminClient.users.deleteCredential({ + id: userId!, + credentialId: credential.id!, + }); + + const credentialsAfterDelete = await kcAdminClient.users.getCredentials({ + id: userId!, + }); + + expect(credentialsAfterDelete).to.not.deep.include(credential); + + // Add deleted password back + await kcAdminClient.users.resetPassword({ + id: userId!, + credential: { + temporary: false, + type: "password", + value: "test", + }, + }); + }); + + /** + * update a credential label for a user + */ + it("update a credential label for a user", async () => { + const userId = currentUser.id; + const result = await kcAdminClient.users.getCredentials({ + id: userId!, + }); + + expect(result.map((c) => c.type)).to.include("password"); + + const credential = result[0]; + + await kcAdminClient.users.updateCredentialLabel( + { id: userId!, credentialId: credential.id! }, + "New user label" + ); + + const credentialsAfterLabelUpdate = + await kcAdminClient.users.getCredentials({ + id: userId!, + }); + + expect(credentialsAfterLabelUpdate.map((c) => c.userLabel)).to.include( + "New user label" + ); + }); + + /** + * Groups + */ + describe("user groups", () => { + let currentGroup: GroupRepresentation; + before(async () => { + const group = await kcAdminClient.groups.create({ + name: "cool-group", + }); + expect(group.id).to.be.ok; + currentGroup = (await kcAdminClient.groups.findOne({ id: group.id }))!; + }); + + after(async () => { + const groupId = currentGroup.id; + const groups = await kcAdminClient.groups.find({ max: 100 }); + await Promise.all( + groups.map((_group: GroupRepresentation) => { + return kcAdminClient.groups.del({ id: _group.id! }); + }) + ); + + const group = await kcAdminClient.groups.findOne({ + id: groupId!, + }); + expect(group).to.be.null; + }); + + it("add group", async () => { + let count = ( + await kcAdminClient.users.countGroups({ id: currentUser.id! }) + ).count; + expect(count).to.eq(0); + await kcAdminClient.users.addToGroup({ + groupId: currentGroup.id!, + id: currentUser.id!, + }); + count = (await kcAdminClient.users.countGroups({ id: currentUser.id! })) + .count; + expect(count).to.eq(1); + }); + + it("count groups", async () => { + let { count } = await kcAdminClient.users.countGroups({ + id: currentUser.id!, + }); + expect(count).to.eq(1); + + count = ( + await kcAdminClient.users.countGroups({ + id: currentUser.id!, + search: "cool-group", + }) + ).count; + expect(count).to.eq(1); + + count = ( + await kcAdminClient.users.countGroups({ + id: currentUser.id!, + search: "fake-group", + }) + ).count; + expect(count).to.eq(0); + }); + + it("list groups", async () => { + const groups = await kcAdminClient.users.listGroups({ + id: currentUser.id!, + }); + expect(groups).to.be.ok; + expect(groups.length).to.be.eq(1); + expect(groups[0].name).to.eq("cool-group"); + }); + + it("remove group", async () => { + const newGroup = await kcAdminClient.groups.create({ + name: "test-group", + }); + await kcAdminClient.users.addToGroup({ + id: currentUser.id!, + groupId: newGroup.id, + }); + let count = ( + await kcAdminClient.users.countGroups({ id: currentUser.id! }) + ).count; + expect(count).to.eq(2); + + try { + await kcAdminClient.users.delFromGroup({ + id: currentUser.id!, + groupId: newGroup.id, + }); + } catch (e) { + fail("Didn't expect an error when deleting a valid group id"); + } + + count = (await kcAdminClient.users.countGroups({ id: currentUser.id! })) + .count; + expect(count).to.equal(1); + + await kcAdminClient.groups.del({ id: newGroup.id }); + + // delete a non-existing group should throw an error + try { + await kcAdminClient.users.delFromGroup({ + id: currentUser.id!, + groupId: "fake-group-id", + }); + fail( + "Expected an error when deleting a fake id not assigned to the user" + ); + } catch (e) { + expect(e).to.be.ok; + } + }); + }); + + /** + * Role mappings + */ + describe("role-mappings", () => { + before(async () => { + // create new role + const roleName = faker.internet.userName(); + await kcAdminClient.roles.create({ + name: roleName, + }); + const role = await kcAdminClient.roles.findOneByName({ + name: roleName, + }); + currentRole = role!; + }); + + after(async () => { + await kcAdminClient.roles.delByName({ name: currentRole.name! }); + }); + + it("add a role to user", async () => { + // add role-mappings with role id + await kcAdminClient.users.addRealmRoleMappings({ + id: currentUser.id!, + + // at least id and name should appear + roles: [ + { + id: currentRole.id!, + name: currentRole.name!, + }, + ], + }); + }); + + it("list available role-mappings for user", async () => { + const roles = await kcAdminClient.users.listAvailableRealmRoleMappings({ + id: currentUser.id!, + }); + + // admin, create-realm + // not sure why others like offline_access, uma_authorization not included + expect(roles.length).to.be.least(2); + }); + + it("list role-mappings of user", async () => { + const res = await kcAdminClient.users.listRoleMappings({ + id: currentUser.id!, + }); + + expect(res).have.all.keys("realmMappings"); + }); + + it("list realm role-mappings of user", async () => { + const roles = await kcAdminClient.users.listRealmRoleMappings({ + id: currentUser.id!, + }); + // currentRole will have an empty `attributes`, but role-mappings do not + expect(roles).to.deep.include(omit(currentRole, "attributes")); + }); + + it("list realm composite role-mappings of user", async () => { + const roles = await kcAdminClient.users.listCompositeRealmRoleMappings({ + id: currentUser.id!, + }); + // todo: add data integrity check later + expect(roles).to.be.ok; + }); + + it("del realm role-mappings from user", async () => { + await kcAdminClient.users.delRealmRoleMappings({ + id: currentUser.id!, + roles: [ + { + id: currentRole.id!, + name: currentRole.name!, + }, + ], + }); + + const roles = await kcAdminClient.users.listRealmRoleMappings({ + id: currentUser.id!, + }); + expect(roles).to.not.deep.include(currentRole); + }); + }); + + /** + * client Role mappings + */ + describe("client role-mappings", () => { + before(async () => { + // create new client + const clientId = faker.internet.userName(); + await kcAdminClient.clients.create({ + clientId, + }); + + const clients = await kcAdminClient.clients.find({ clientId }); + expect(clients[0]).to.be.ok; + currentClient = clients[0]; + + // create new client role + const roleName = faker.internet.userName(); + await kcAdminClient.clients.createRole({ + id: currentClient.id, + name: roleName, + }); + + // assign to currentRole + currentRole = await kcAdminClient.clients.findRole({ + id: currentClient.id!, + roleName, + }); + }); + + after(async () => { + await kcAdminClient.clients.delRole({ + id: currentClient.id!, + roleName: currentRole.name!, + }); + await kcAdminClient.clients.del({ id: currentClient.id! }); + }); + + it("add a client role to user", async () => { + // add role-mappings with role id + await kcAdminClient.users.addClientRoleMappings({ + id: currentUser.id!, + clientUniqueId: currentClient.id!, + + // at least id and name should appear + roles: [ + { + id: currentRole.id!, + name: currentRole.name!, + }, + ], + }); + }); + + it("list available client role-mappings for user", async () => { + const roles = await kcAdminClient.users.listAvailableClientRoleMappings({ + id: currentUser.id!, + clientUniqueId: currentClient.id!, + }); + + expect(roles).to.be.empty; + }); + + it("list composite client role-mappings for user", async () => { + const roles = await kcAdminClient.users.listCompositeClientRoleMappings({ + id: currentUser.id!, + clientUniqueId: currentClient.id!, + }); + + expect(roles).to.be.ok; + }); + + it("list client role-mappings of user", async () => { + const roles = await kcAdminClient.users.listClientRoleMappings({ + id: currentUser.id!, + clientUniqueId: currentClient.id!, + }); + + // currentRole will have an empty `attributes`, but role-mappings do not + expect(currentRole).to.deep.include(roles[0]); + }); + + it("del client role-mappings from user", async () => { + const roleName = faker.internet.userName(); + await kcAdminClient.clients.createRole({ + id: currentClient.id, + name: roleName, + }); + const role = await kcAdminClient.clients.findRole({ + id: currentClient.id!, + roleName, + }); + + // delete the created role + await kcAdminClient.users.delClientRoleMappings({ + id: currentUser.id!, + clientUniqueId: currentClient.id!, + roles: [ + { + id: role.id!, + name: role.name!, + }, + ], + }); + + // check if mapping is successfully deleted + const roles = await kcAdminClient.users.listClientRoleMappings({ + id: currentUser.id!, + clientUniqueId: currentClient.id!, + }); + + // should only left the one we added in the previous test + expect(roles.length).to.be.eql(1); + }); + }); + + describe("User sessions", () => { + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + // create client + const clientId = faker.internet.userName(); + await kcAdminClient.clients.create({ + clientId, + consentRequired: true, + }); + + const clients = await kcAdminClient.clients.find({ clientId }); + expect(clients[0]).to.be.ok; + currentClient = clients[0]; + }); + + after(async () => { + await kcAdminClient.clients.del({ + id: currentClient.id!, + }); + }); + + it("list user sessions", async () => { + // @TODO: In order to test it, currentUser has to be logged in + const userSessions = await kcAdminClient.users.listSessions({ + id: currentUser.id!, + }); + + expect(userSessions).to.be.ok; + }); + + it("list users off-line sessions", async () => { + // @TODO: In order to test it, currentUser has to be logged in + const userOfflineSessions = await kcAdminClient.users.listOfflineSessions( + { id: currentUser.id!, clientId: currentClient.id! } + ); + + expect(userOfflineSessions).to.be.ok; + }); + + it("logout user from all sessions", async () => { + // @TODO: In order to test it, currentUser has to be logged in + await kcAdminClient.users.logout({ id: currentUser.id! }); + }); + + it("list consents granted by the user", async () => { + const consents = await kcAdminClient.users.listConsents({ + id: currentUser.id!, + }); + + expect(consents).to.be.ok; + }); + + it("revoke consent and offline tokens for particular client", async () => { + // @TODO: In order to test it, currentUser has to granted consent to client + const consents = await kcAdminClient.users.listConsents({ + id: currentUser.id!, + }); + + if (consents.length) { + const consent = consents[0]; + + await kcAdminClient.users.revokeConsent({ + id: currentUser.id!, + clientId: consent.clientId!, + }); + } + }); + + it("impersonate user", async () => { + const result = await kcAdminClient.users.impersonation( + { id: currentUser.id! }, + { user: currentUser.id!, realm: kcAdminClient.realmName } + ); + expect(result).to.be.ok; + await kcAdminClient.auth(credentials); + }); + }); + + describe("Federated Identity user integration", () => { + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + federatedIdentity = { + identityProvider: "foobar", + userId: "userid1", + userName: "username1", + }; + }); + + it("should list user federated identities and expect empty", async () => { + const federatedIdentities = + await kcAdminClient.users.listFederatedIdentities({ + id: currentUser.id!, + }); + expect(federatedIdentities).to.be.eql([]); + }); + + it("should add federated identity to user", async () => { + await kcAdminClient.users.addToFederatedIdentity({ + id: currentUser.id!, + federatedIdentityId: "foobar", + federatedIdentity, + }); + + // @TODO: In order to test the integration with federated identities, the User Federation + // would need to be created first, this is not implemented yet. + // const federatedIdentities = await kcAdminClient.users.listFederatedIdentities({ + // id: currentUser.id, + // }); + // expect(federatedIdentities[0]).to.be.eql(federatedIdentity); + }); + + it("should remove federated identity from user", async () => { + await kcAdminClient.users.delFromFederatedIdentity({ + id: currentUser.id!, + federatedIdentityId: "foobar", + }); + + const federatedIdentities = + await kcAdminClient.users.listFederatedIdentities({ + id: currentUser.id!, + }); + expect(federatedIdentities).to.be.eql([]); + }); + }); +}); diff --git a/js/libs/keycloak-admin-client/test/whoAmI.spec.ts b/js/libs/keycloak-admin-client/test/whoAmI.spec.ts new file mode 100644 index 0000000000..ce0393a7ad --- /dev/null +++ b/js/libs/keycloak-admin-client/test/whoAmI.spec.ts @@ -0,0 +1,21 @@ +// tslint:disable:no-unused-expression +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Who am I", () => { + let client: KeycloakAdminClient; + + before(async () => { + client = new KeycloakAdminClient(); + await client.auth(credentials); + }); + + it("list who I am", async () => { + const whoAmI = await client.whoAmI.find(); + expect(whoAmI).to.be.ok; + expect(whoAmI.displayName).to.be.equal("admin"); + }); +}); diff --git a/js/libs/keycloak-admin-client/tsconfig.json b/js/libs/keycloak-admin-client/tsconfig.json new file mode 100644 index 0000000000..27933244be --- /dev/null +++ b/js/libs/keycloak-admin-client/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src"], + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "lib", + "noEmit": false, + "declaration": true + } +} \ No newline at end of file diff --git a/js/libs/keycloak-admin-client/tsconfig.test.json b/js/libs/keycloak-admin-client/tsconfig.test.json new file mode 100644 index 0000000000..1bd1e8a246 --- /dev/null +++ b/js/libs/keycloak-admin-client/tsconfig.test.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig", + "include": ["test"] +} diff --git a/js/package-lock.json b/js/package-lock.json new file mode 100644 index 0000000000..3d885202fa --- /dev/null +++ b/js/package-lock.json @@ -0,0 +1,4709 @@ +{ + "name": "root", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "root", + "workspaces": [ + "libs/keycloak-admin-client" + ], + "devDependencies": { + "@types/node": "^18.11.18", + "@typescript-eslint/eslint-plugin": "^5.49.0", + "@typescript-eslint/parser": "^5.49.0", + "eslint": "^8.33.0", + "eslint-config-prettier": "^8.6.0", + "eslint-import-resolver-typescript": "^3.5.3", + "eslint-plugin-cypress": "^2.12.1", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-lodash": "^7.4.0", + "eslint-plugin-mocha": "^10.1.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.32.1", + "husky": "^8.0.3", + "lint-staged": "^13.1.0", + "prettier": "^2.8.3", + "typescript": "^4.9.4", + "wireit": "^0.9.3" + } + }, + "libs/keycloak-admin-client": { + "name": "@keycloak/keycloak-admin-client", + "version": "999.0.0-dev", + "license": "Apache-2.0", + "dependencies": { + "camelize-ts": "^2.2.0", + "lodash-es": "^4.17.21", + "url-join": "^5.0.0", + "url-template": "^3.1.0" + }, + "devDependencies": { + "@faker-js/faker": "^7.6.0", + "@types/chai": "^4.3.4", + "@types/lodash-es": "^4.17.6", + "@types/mocha": "^10.0.1", + "@types/node": "^18.11.18", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "ts-node": "^10.9.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@keycloak/keycloak-admin-client": { + "resolved": "libs/keycloak-admin-client", + "link": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", + "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "is-glob": "^4.0.3", + "open": "^8.4.0", + "picocolors": "^1.0.0", + "tiny-glob": "^0.2.9", + "tslib": "^2.4.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.6.tgz", + "integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz", + "integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.49.0", + "@typescript-eslint/type-utils": "5.49.0", + "@typescript-eslint/utils": "5.49.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.49.0.tgz", + "integrity": "sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.49.0", + "@typescript-eslint/types": "5.49.0", + "@typescript-eslint/typescript-estree": "5.49.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz", + "integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.49.0", + "@typescript-eslint/visitor-keys": "5.49.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz", + "integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.49.0", + "@typescript-eslint/utils": "5.49.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz", + "integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz", + "integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.49.0", + "@typescript-eslint/visitor-keys": "5.49.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz", + "integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.49.0", + "@typescript-eslint/types": "5.49.0", + "@typescript-eslint/typescript-estree": "5.49.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz", + "integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.49.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelize-ts": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/camelize-ts/-/camelize-ts-2.2.0.tgz", + "integrity": "sha512-6jMy83qFmNrJIMQXXU6Bi7VVkyl/nBVvoxcVsGRDA6R01pPZpnO/LaIcx6dijJjHbBA2G0uHY4Irk7LeA274NQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", + "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.3.tgz", + "integrity": "sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.10.0", + "get-tsconfig": "^4.2.0", + "globby": "^13.1.2", + "is-core-module": "^2.10.0", + "is-glob": "^4.0.3", + "synckit": "^0.8.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-cypress": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz", + "integrity": "sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==", + "dev": true, + "dependencies": { + "globals": "^11.12.0" + }, + "peerDependencies": { + "eslint": ">= 3.2.1" + } + }, + "node_modules/eslint-plugin-cypress/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-lodash": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-7.4.0.tgz", + "integrity": "sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": ">=2" + } + }, + "node_modules/eslint-plugin-mocha": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.1.0.tgz", + "integrity": "sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "rambda": "^7.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.3.0.tgz", + "integrity": "sha512-YCcF28IqSay3fqpIu5y3Krg/utCBHBeoflkZyHj/QcqI2nrLPC3ZegS9CmIo+hJb8K7aiGsuUl7PwWVjNG2HQQ==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lint-staged": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.1.0.tgz", + "integrity": "sha512-pn/sR8IrcF/T0vpWLilih8jmVouMlxqXxKuAojmbiGX5n/gDnz+abdPptlj0vYnbfE0SQNl3CY/HwtM0+yfOVQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.19", + "commander": "^9.4.1", + "debug": "^4.3.4", + "execa": "^6.1.0", + "lilconfig": "2.0.6", + "listr2": "^5.0.5", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.2", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.1.3" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.7.tgz", + "integrity": "sha512-MD+qXHPmtivrHIDRwPYdfNkrzqDiuaKU/rfBcec3WMyMF3xylQj3jMq344OtvQxz7zaCFViRAeqlr2AFhPvXHw==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.19", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/listr2/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rambda": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.4.0.tgz", + "integrity": "sha512-A9hihu7dUTLOUCM+I8E61V4kRXnN4DwYeK0DwCBydC1MqNI1PidyAtbtpsJlBBzK4icSctEcCQ1bGcLpBuETUQ==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/url-template": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-3.1.0.tgz", + "integrity": "sha512-vB/eHWttzhN+NZzk9FcQB2h1cSEgb7zDYyvyxPhw02LYw7YqIzO+w1AqkcKvZ51gPH8o4+nyiWve/xuQqMdJZw==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wireit": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/wireit/-/wireit-0.9.3.tgz", + "integrity": "sha512-o31NkFrr+AjGNgarZB094Tw6xWcmNn1uts672B1qKzSo2XMFBt5abjtg/SBUNlBcQPtPryEXJkIUQ6i9p+1Y9w==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "chokidar": "^3.5.3", + "fast-glob": "^3.2.11", + "jsonc-parser": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "bin": { + "wireit": "bin/wireit.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/js/package.json b/js/package.json new file mode 100644 index 0000000000..fa36f5d52c --- /dev/null +++ b/js/package.json @@ -0,0 +1,31 @@ +{ + "name": "root", + "workspaces": [ + "libs/keycloak-admin-client" + ], + "scripts": { + "prepare": "cd .. && husky install js/.husky" + }, + "devDependencies": { + "@types/node": "^18.11.18", + "@typescript-eslint/eslint-plugin": "^5.49.0", + "@typescript-eslint/parser": "^5.49.0", + "eslint": "^8.33.0", + "eslint-config-prettier": "^8.6.0", + "eslint-import-resolver-typescript": "^3.5.3", + "eslint-plugin-cypress": "^2.12.1", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-lodash": "^7.4.0", + "eslint-plugin-mocha": "^10.1.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.32.1", + "husky": "^8.0.3", + "lint-staged": "^13.1.0", + "prettier": "^2.8.3", + "typescript": "^4.9.4", + "wireit": "^0.9.3" + }, + "lint-staged": { + "*.{js,jsx,mjs,ts,tsx}": "eslint --cache --fix" + } +} diff --git a/js/tsconfig.eslint.json b/js/tsconfig.eslint.json new file mode 100644 index 0000000000..91cfe8d14d --- /dev/null +++ b/js/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*", ".eslintrc.js"], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/js/tsconfig.json b/js/tsconfig.json new file mode 100644 index 0000000000..6e75c3e5a9 --- /dev/null +++ b/js/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": "./", + } +}