Add JavaScript admin client to repository (#16697)

* Add JavaScript admin client to repository

* Apply review feedback

Co-authored-by: Stian Thorgersen <stian@redhat.com>

---------

Co-authored-by: Stian Thorgersen <stian@redhat.com>
This commit is contained in:
Jon Koops 2023-02-03 11:45:11 +01:00 committed by GitHub
parent 0fa209c29a
commit 8cb202eb29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 16113 additions and 4 deletions

View File

@ -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

View File

@ -2,16 +2,19 @@
#
# To test a pattern run '.github/actions/conditional/conditional.sh <remote name> <branch>'
.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

View File

@ -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"]

96
.github/workflows/js-ci.yml vendored Normal file
View File

@ -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 }}

96
js/.eslintrc.js Normal file
View File

@ -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",
},
},
],
};

38
js/.gitignore vendored Normal file
View File

@ -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

1
js/.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

4
js/.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd js
npx lint-staged

14
js/README.md Normal file
View File

@ -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

View File

@ -0,0 +1 @@
lib

View File

@ -0,0 +1 @@
lib

View File

@ -0,0 +1,5 @@
{
"node-option": [
"loader=ts-node/esm"
]
}

View File

@ -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.

View File

@ -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 roles children Returns a set of roles children provided the role is a composite. (`GET /{realm}/roles-by-id/{role-id}/composites`)
- Remove a set of roles from the roles composite (`DELETE /{realm}/roles-by-id/{role-id}/composites`)
- Get client-level roles for the client that are in the roles composite (`GET /{realm}/roles-by-id/{role-id}/composites/clients/{client}`)
- Get realm-level roles that are in the roles 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 clients scope (`POST /{realm}/client-scopes/{id}/scope-mappings/clients/{client}`)
- Get the roles associated with a clients 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 clients scope. (`DELETE /{realm}/client-scopes/{id}/scope-mappings/clients/{client}`)
- Add a set of realm-level roles to the clients scope (`POST /{realm}/client-scopes/{id}/scope-mappings/realm`)
- Get realm-level roles associated with the clients scope (`GET /{realm}/client-scopes/{id}/scope-mappings/realm`)
- Remove a set of realm-level roles from the clients scope (`DELETE /{realm}/client-scopes/{id}/scope-mappings/realm`)
- Get realm-level roles that are available to attach to this clients scope (`GET /{realm}/client-scopes/{id}/scope-mappings/realm/available`)
- Get effective realm-level roles associated with the clients 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 clients scope (`POST /{realm}/clients/{id}/scope-mappings/clients/{client}`)
- Get the roles associated with a clients scope (`GET /{realm}/clients/{id}/scope-mappings/clients/{client}`)
- Remove client-level roles from the clients 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 clients scope (`POST /{realm}/clients/{id}/scope-mappings/realm`)
- Get realm-level roles associated with the clients scope (`GET /{realm}/clients/{id}/scope-mappings/realm`)
- Remove a set of realm-level roles from the clients scope (`DELETE /{realm}/clients/{id}/scope-mappings/realm`)
- Get realm-level roles that are available to attach to this clients scope (`GET /{realm}/clients/{id}/scope-mappings/realm/available`)
- Get effective realm-level roles associated with the clients 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 actions priority (`POST /{realm}/authentication/required-actions/{alias}/lower-priority`)
- Raise required actions 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.

View File

@ -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/"
}

View File

@ -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<string | undefined>;
}
export interface ConnectionConfig {
baseUrl?: string;
realmName?: string;
requestOptions?: RequestInit;
requestArgOptions?: Pick<RequestArgs, "catchNotFound">;
}
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<RequestArgs, "catchNotFound">;
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<RequestArgs, "catchNotFound">
| 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;
}
}

View File

@ -0,0 +1,4 @@
export default interface AccessTokenAccess {
roles?: string[];
verify_caller?: boolean;
}

View File

@ -0,0 +1,6 @@
export default interface PermissionRepresentation {
claims?: { [index: string]: string };
rsid?: string;
rsname?: string;
scopes?: string[];
}

View File

@ -0,0 +1,3 @@
export default interface AccessTokenCertConf {
"x5t#S256"?: string;
}

View File

@ -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;
}

View File

@ -0,0 +1,8 @@
export default interface AddressClaimSet {
country?: string;
formatted?: string;
locality?: string;
postal_code?: string;
region?: string;
street_address?: string;
}

View File

@ -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;
}

View File

@ -0,0 +1,6 @@
export default interface AuthDetailsRepresentation {
clientId?: string;
ipAddress?: string;
realmId?: string;
userId?: string;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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[];
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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[];
}

View File

@ -0,0 +1,7 @@
/**
* https://www.keycloak.org/docs-api/15.0/rest-api/#_clientpolicyconditionrepresentation
*/
export default interface ClientPolicyConditionRepresentation {
condition?: string;
configuration?: object;
}

View File

@ -0,0 +1,7 @@
/**
* https://www.keycloak.org/docs-api/15.0/rest-api/#_clientpolicyexecutorrepresentation
*/
export default interface ClientPolicyExecutorRepresentation {
configuration?: object;
executor?: string;
}

View File

@ -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[];
}

View File

@ -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;
}

View File

@ -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[];
}

View File

@ -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<string, boolean>;
adminUrl?: string;
attributes?: Record<string, any>;
authenticationFlowBindingOverrides?: Record<string, any>;
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<string, any>;
registrationAccessToken?: string;
rootUrl?: string;
secret?: string;
serviceAccountsEnabled?: boolean;
standardFlowEnabled?: boolean;
surrogateAuthRequired?: boolean;
webOrigins?: string[];
}

View File

@ -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<string, any>;
description?: string;
id?: string;
name?: string;
protocol?: string;
protocolMappers?: ProtocolMapperRepresentation[];
}

View File

@ -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 };
}

View File

@ -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[] };
}

View File

@ -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 };
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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[];
}

View File

@ -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<string, any>;
error?: string;
ipAddress?: string;
realmId?: string;
sessionId?: string;
time?: number;
type?: EventType;
userId?: string;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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[];
}

View File

@ -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<string, boolean>;
attributes?: Record<string, any>;
clientRoles?: Record<string, any>;
realmRoles?: string[];
}

View File

@ -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;
}

View File

@ -0,0 +1,9 @@
import type { ConfigPropertyRepresentation } from "./configPropertyRepresentation.js";
export interface IdentityProviderMapperTypeRepresentation {
id?: string;
name?: string;
category?: string;
helpText?: string;
properties?: ConfigPropertyRepresentation[];
}

View File

@ -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<string, any>;
displayName?: string;
enabled?: boolean;
firstBrokerLoginFlowAlias?: string;
internalId?: string;
linkOnly?: boolean;
postBrokerLoginFlowAlias?: string;
providerId?: string;
storeToken?: boolean;
trustEmail?: boolean;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
export interface ManagementPermissionReference {
enabled?: boolean;
resource?: string;
scopePermissions?: Record<string, string>;
}

View File

@ -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<string, any>;
realmMappings?: RoleRepresentation[];
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
export default interface PolicyProviderRepresentation {
type?: string;
name?: string;
group?: string;
}

View File

@ -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<string, any>;
decisionStrategy?: DecisionStrategy;
description?: string;
id?: string;
logic?: Logic;
name?: string;
owner?: string;
policies?: string[];
resources?: string[];
scopes?: string[];
type?: string;
users?: string[];
roles?: PolicyRoleRepresentation[];
}

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -0,0 +1,11 @@
/**
* https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_protocolmapperrepresentation
*/
export default interface ProtocolMapperRepresentation {
config?: Record<string, any>;
id?: string;
name?: string;
protocol?: string;
protocolMapper?: string;
}

View File

@ -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;
}

View File

@ -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<string, any>;
// AuthenticationFlowRepresentation
authenticationFlows?: any[];
// AuthenticatorConfigRepresentation
authenticatorConfig?: any[];
browserFlow?: string;
browserSecurityHeaders?: Record<string, any>;
bruteForceProtected?: boolean;
clientAuthenticationFlow?: string;
clientScopeMappings?: Record<string, any>;
// 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<string, any>;
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;
};

View File

@ -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<string, any>;
defaultAction?: boolean;
enabled?: boolean;
name?: string;
providerId?: string;
priority?: number;
}

View File

@ -0,0 +1,5 @@
export default interface RequiredActionProviderSimpleRepresentation {
id?: string;
name?: string;
providerId?: string;
}

View File

@ -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;
};
};
}

View File

@ -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;
}

View File

@ -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";

View File

@ -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;
}

View File

@ -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[] };
}

View File

@ -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[];
}

View File

@ -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<string, string>;
}
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[];
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<string, Record<string, unknown>>;
annotations?: Record<string, unknown>[];
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<string, unknown>;
}

View File

@ -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<string, boolean>;
// optional from response
attributes?: Record<string, any>;
clientConsents?: UserConsentRepresentation[];
clientRoles?: Record<string, any>;
credentials?: CredentialRepresentation[];
email?: string;
federatedIdentities?: FederatedIdentityRepresentation[];
federationLink?: string;
firstName?: string;
groups?: string[];
lastName?: string;
origin?: string;
realmRoles?: string[];
self?: string;
serviceAccountClientId?: string;
}

View File

@ -0,0 +1,9 @@
export default interface UserSessionRepresentation {
id?: string;
clients?: Record<string, string>;
ipAddress?: string;
lastAccess?: number;
start?: number;
userId?: string;
username?: string;
}

View File

@ -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[] };
}

View File

@ -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";

View File

@ -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<string, string>;
// 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<string, any>;
private getBaseUrl?: () => string;
constructor({
client,
path = "/",
getUrlParams = () => ({}),
getBaseUrl = () => client.baseUrl,
}: {
client: KeycloakAdminClient;
path?: string;
getUrlParams?: () => Record<string, any>;
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<RequestArgs, "catchNotFound">
) => {
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<string, string>;
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<string, string> = {};
// 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<string, string>) {
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];
});
}
}

View File

@ -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<string, any> | 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,
});
}
}

View File

@ -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<Record<string, any>>({
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 actions priority
public lowerRequiredActionPriority = this.makeRequest<{
alias: string;
}>({
method: "POST",
path: "/required-actions/{alias}/lower-priority",
urlParamKeys: ["alias"],
});
// Raise required actions 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,
});
}
}

View File

@ -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,
});
}
}

View File

@ -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<ClientProfilesRepresentation, void>({
method: "PUT",
path: "/profiles",
});
/* Client Policies */
public listPolicies = this.makeRequest<{}, ClientPoliciesRepresentation>({
method: "GET",
path: "/policies",
});
public updatePolicy = this.makeRequest<ClientPoliciesRepresentation, void>({
method: "PUT",
path: "/policies",
});
}

View File

@ -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<ClientScopeRepresentation, { id: string }>({
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<ClientScopeRepresentation | undefined> {
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<void> {
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<ProtocolMapperRepresentation | undefined> {
const allProtocolMappers = await this.listProtocolMappers({
id: payload.id,
...(payload.realm ? { realm: payload.realm } : {}),
});
return allProtocolMappers.find((mapper) => mapper.name === payload.name);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<ComponentQuery, ComponentRepresentation[]>({
method: "GET",
});
public create = this.makeRequest<ComponentRepresentation, { id: string }>({
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,
});
}
}

View File

@ -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<GroupQuery, GroupRepresentation[]>({
method: "GET",
});
public create = this.makeRequest<GroupRepresentation, { id: string }>({
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<GroupCountQuery, { count: number }>({
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 doesnt 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,
});
}
}

View File

@ -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<string, IdentityProviderMapperTypeRepresentation>
>({
method: "GET",
path: "/instances/{alias}/mapper-types",
urlParamKeys: ["alias"],
});
public importFromUrl = this.makeRequest<
{
fromUrl: string;
providerId: string;
},
Record<string, string>
>({
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,
});
}
}

View File

@ -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<RealmRepresentation, { realmName: string }>({
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<string, string | number>
>({
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<string, string>
>({
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,
});
}
}

View File

@ -0,0 +1,42 @@
import type { KeycloakAdminClient } from "../client.js";
import { Agent, RequestArgs } from "./agent.js";
export default class Resource<ParamType = {}> {
private agent: Agent;
constructor(
client: KeycloakAdminClient,
settings: {
path?: string;
getUrlParams?: () => Record<string, any>;
getBaseUrl?: () => string;
} = {}
) {
this.agent = new Agent({
client,
...settings,
});
}
public makeRequest = <PayloadType = any, ResponseType = any>(
args: RequestArgs
): ((
payload?: PayloadType & ParamType,
options?: Pick<RequestArgs, "catchNotFound">
) => Promise<ResponseType>) => {
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<ResponseType>) => {
return this.agent.updateRequest(args);
};
}

View File

@ -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<RoleQuery, RoleRepresentation[]>({
method: "GET",
path: "/roles",
});
public create = this.makeRequest<RoleRepresentation, { roleName: string }>({
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,
});
}
}

View File

@ -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: "/",
});
}

View File

@ -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<string, any>[]>({
method: "GET",
});
constructor(client: KeycloakAdminClient) {
super(client, {
path: "/admin/realms/{realm}/client-session-stats",
getUrlParams: () => ({
realm: client.realmName,
}),
getBaseUrl: () => client.baseUrl,
});
}
}

View File

@ -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,
});
}
}

View File

@ -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<UserQuery, UserRepresentation[]>({
method: "GET",
});
public create = this.makeRequest<UserRepresentation, { id: string }>({
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<UserBaseQuery & SearchQuery, number>({
method: "GET",
path: "/count",
});
public getProfile = this.makeRequest<{}, UserProfileConfig>({
method: "GET",
path: "/profile",
});
public updateProfile = this.makeRequest<UserProfileConfig, UserProfileConfig>(
{
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<string, any>
>({
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,
});
}
}

Some files were not shown because too many files have changed in this diff Show More