From a7a45e61fd79941cf4cfb7e85035647fb1e6dfb9 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Thu, 15 Jan 2026 09:50:35 +0100 Subject: [PATCH 1/5] Added typescript based module for the client admin v2 Based on the new openapi client admin api this module can be generated based on the defenition. Signed-off-by: Erik Jan de Wit --- js/libs/keycloak-admin-client-v2/.gitignore | 24 + js/libs/keycloak-admin-client-v2/openapi.yaml | 242 ++++++ .../package-lock.json | 754 ++++++++++++++++++ js/libs/keycloak-admin-client-v2/package.json | 26 + .../scripts/generate.ts | 181 +++++ .../keycloak-admin-client-v2/test/test-app.ts | 177 ++++ .../keycloak-admin-client-v2/tsconfig.json | 39 + js/package.json | 1 + js/pnpm-lock.yaml | 280 +++++-- .../quarkus/runtime/oas/OASModelFilter.java | 40 + .../admin/v2/BaseRepresentation.java | 2 +- 11 files changed, 1717 insertions(+), 49 deletions(-) create mode 100644 js/libs/keycloak-admin-client-v2/.gitignore create mode 100644 js/libs/keycloak-admin-client-v2/openapi.yaml create mode 100644 js/libs/keycloak-admin-client-v2/package-lock.json create mode 100644 js/libs/keycloak-admin-client-v2/package.json create mode 100644 js/libs/keycloak-admin-client-v2/scripts/generate.ts create mode 100644 js/libs/keycloak-admin-client-v2/test/test-app.ts create mode 100644 js/libs/keycloak-admin-client-v2/tsconfig.json diff --git a/js/libs/keycloak-admin-client-v2/.gitignore b/js/libs/keycloak-admin-client-v2/.gitignore new file mode 100644 index 00000000000..6ccef181db4 --- /dev/null +++ b/js/libs/keycloak-admin-client-v2/.gitignore @@ -0,0 +1,24 @@ +# Dependencies +node_modules/ + +# Build output +dist/ + +# Kiota workspace files (regenerated on each run) +.kiota/ +.openapi-temp.yaml +src/ + +# Logs +*.log +src/.kiota.log + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/js/libs/keycloak-admin-client-v2/openapi.yaml b/js/libs/keycloak-admin-client-v2/openapi.yaml new file mode 100644 index 00000000000..5b2577e237d --- /dev/null +++ b/js/libs/keycloak-admin-client-v2/openapi.yaml @@ -0,0 +1,242 @@ +--- +openapi: 3.1.0 +components: + schemas: + Auth: + type: object + properties: + method: + type: string + secret: + type: string + certificate: + type: string + BaseClientRepresentation: + type: object + properties: + clientId: + type: string + displayName: + type: string + description: + type: string + enabled: + type: boolean + appUrl: + type: string + redirectUris: + type: array + uniqueItems: true + items: + type: string + roles: + type: array + uniqueItems: true + items: + type: string + additionalFields: + type: object + additionalProperties: {} + Flow: + type: string + enum: + - STANDARD + - IMPLICIT + - DIRECT_GRANT + - SERVICE_ACCOUNT + - TOKEN_EXCHANGE + - DEVICE + - CIBA + OIDCClientRepresentation: + type: object + allOf: + - $ref: "#/components/schemas/BaseClientRepresentation" + properties: + loginFlows: + type: array + uniqueItems: true + items: + $ref: "#/components/schemas/Flow" + auth: + $ref: "#/components/schemas/Auth" + webOrigins: + type: array + uniqueItems: true + items: + type: string + serviceAccountRoles: + type: array + uniqueItems: true + items: + type: string + protocol: + type: string + SAMLClientRepresentation: + type: object + description: SAML Client configuration + allOf: + - $ref: "#/components/schemas/BaseClientRepresentation" + properties: + nameIdFormat: + type: string + forceNameIdFormat: + type: boolean + includeAuthnStatement: + type: boolean + signDocuments: + type: boolean + signAssertions: + type: boolean + clientSignatureRequired: + type: boolean + forcePostBinding: + type: boolean + frontChannelLogout: + type: boolean + signatureAlgorithm: + type: string + signatureCanonicalizationMethod: + type: string + signingCertificate: + type: string + allowEcpFlow: + type: boolean + protocol: + type: string +tags: +- name: Clients (v2) + x-smallrye-profile-admin: "" +paths: + /admin/api/v2/realms/{name}/clients: + get: + summary: Get all clients + description: Returns a list of all clients in the realm + tags: + - Clients (v2) + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + oneOf: + - $ref: "#/components/schemas/OIDCClientRepresentation" + - $ref: "#/components/schemas/SAMLClientRepresentation" + discriminator: + propertyName: protocol + mapping: + openid-connect: "#/components/schemas/OIDCClientRepresentation" + saml: "#/components/schemas/SAMLClientRepresentation" + post: + summary: Create a new client + description: Creates a new client in the realm + tags: + - Clients (v2) + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/OIDCClientRepresentation" + - $ref: "#/components/schemas/SAMLClientRepresentation" + discriminator: + propertyName: protocol + mapping: + openid-connect: "#/components/schemas/OIDCClientRepresentation" + saml: "#/components/schemas/SAMLClientRepresentation" + required: true + responses: + "200": + description: OK + content: + application/json: + schema: {} + parameters: + - name: name + in: path + required: true + schema: + type: string + /admin/api/v2/realms/{name}/clients/{id}: + get: + tags: + - Clients (v2) + responses: + "200": + description: OK + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/OIDCClientRepresentation" + - $ref: "#/components/schemas/SAMLClientRepresentation" + discriminator: + propertyName: protocol + mapping: + openid-connect: "#/components/schemas/OIDCClientRepresentation" + saml: "#/components/schemas/SAMLClientRepresentation" + put: + tags: + - Clients (v2) + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/OIDCClientRepresentation" + - $ref: "#/components/schemas/SAMLClientRepresentation" + discriminator: + propertyName: protocol + mapping: + openid-connect: "#/components/schemas/OIDCClientRepresentation" + saml: "#/components/schemas/SAMLClientRepresentation" + required: true + responses: + "200": + description: OK + content: + application/json: + schema: {} + patch: + tags: + - Clients (v2) + requestBody: + content: + application/merge-patch+json: {} + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/OIDCClientRepresentation" + - $ref: "#/components/schemas/SAMLClientRepresentation" + discriminator: + propertyName: protocol + mapping: + openid-connect: "#/components/schemas/OIDCClientRepresentation" + saml: "#/components/schemas/SAMLClientRepresentation" + delete: + tags: + - Clients (v2) + responses: + "204": + description: No Content + parameters: + - name: name + in: path + required: true + schema: + type: string + - name: id + in: path + required: true + schema: + type: string +info: + title: Keycloak API + version: 999.0.0-SNAPSHOT diff --git a/js/libs/keycloak-admin-client-v2/package-lock.json b/js/libs/keycloak-admin-client-v2/package-lock.json new file mode 100644 index 00000000000..59ca70e5910 --- /dev/null +++ b/js/libs/keycloak-admin-client-v2/package-lock.json @@ -0,0 +1,754 @@ +{ + "name": "keycloak-admin-client-v2", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "keycloak-admin-client-v2", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@microsoft/kiota-bundle": "^1.0.0-preview.99", + "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.99" + }, + "devDependencies": { + "@microsoft/kiota": "^1.29.0", + "@types/node": "^25.0.8", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@microsoft/kiota": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@microsoft/kiota/-/kiota-1.29.0.tgz", + "integrity": "sha512-qqIlTz48OJ5ZMRoTA/uQA70B7ltS4lPSs9atG5PUn+dKZcgXny3LzQPe12B1LsKoBJYbwhaU3fD8/C1DsLW6Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "adm-zip": "^0.5.16", + "original-fs": "^1.2.0", + "uuid": "^13.0.0", + "vscode-jsonrpc": "^8.2.1" + } + }, + "node_modules/@microsoft/kiota-abstractions": { + "version": "1.0.0-preview.99", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-abstractions/-/kiota-abstractions-1.0.0-preview.99.tgz", + "integrity": "sha512-6qrlwGCO3DbvpA7tszqk7JNQPFPDg8gv5NGMTziwdtHqaQrUALKusm3vdJGPVGrbNiZL6/lBaY5NSUvLtlhRCg==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.7.0", + "@std-uritemplate/std-uritemplate": "^2.0.0", + "tinyduration": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@microsoft/kiota-bundle": { + "version": "1.0.0-preview.99", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-bundle/-/kiota-bundle-1.0.0-preview.99.tgz", + "integrity": "sha512-AxvO+z6UgWMAT2NfXN36CMhAUm/39tUQt8o32axeEJDS/EvZINDAPstbQV+zK7bJRC8kCqUHhCE/fuHX2dXA+g==", + "license": "MIT", + "dependencies": { + "@microsoft/kiota-abstractions": "^1.0.0-preview.99", + "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.99", + "@microsoft/kiota-serialization-form": "^1.0.0-preview.99", + "@microsoft/kiota-serialization-json": "^1.0.0-preview.99", + "@microsoft/kiota-serialization-multipart": "^1.0.0-preview.99", + "@microsoft/kiota-serialization-text": "^1.0.0-preview.99" + } + }, + "node_modules/@microsoft/kiota-http-fetchlibrary": { + "version": "1.0.0-preview.99", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-http-fetchlibrary/-/kiota-http-fetchlibrary-1.0.0-preview.99.tgz", + "integrity": "sha512-lIruiYf8L7DPG0Fm92QN5YK4zx4sh76EtTONUAqBCxt5AtEJ7KQceBajaXQsjfcndUAp9LmDVdqR44o8sc9/mA==", + "license": "MIT", + "dependencies": { + "@microsoft/kiota-abstractions": "^1.0.0-preview.99", + "@opentelemetry/api": "^1.7.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@microsoft/kiota-serialization-form": { + "version": "1.0.0-preview.99", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-form/-/kiota-serialization-form-1.0.0-preview.99.tgz", + "integrity": "sha512-BdXxqNfy+5yWTyxpBguqJYy6E9RRotrDClRcUO89okFcR5N6s6xenjsvGw++q9KWNi7qcHrqaG8xlRz1TLk81Q==", + "license": "MIT", + "dependencies": { + "@microsoft/kiota-abstractions": "^1.0.0-preview.99", + "tslib": "^2.6.2" + } + }, + "node_modules/@microsoft/kiota-serialization-json": { + "version": "1.0.0-preview.99", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-json/-/kiota-serialization-json-1.0.0-preview.99.tgz", + "integrity": "sha512-kDmMYmB7XkRprlLviEynRPDkE45JDxqiuUopfBRMvNK4qhzFiJTXGLihZ2FG6fdVu9LbV3/W0QxbeGP35kDK6A==", + "license": "MIT", + "dependencies": { + "@microsoft/kiota-abstractions": "^1.0.0-preview.99", + "tslib": "^2.6.2" + } + }, + "node_modules/@microsoft/kiota-serialization-multipart": { + "version": "1.0.0-preview.99", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-multipart/-/kiota-serialization-multipart-1.0.0-preview.99.tgz", + "integrity": "sha512-cfviCAVTlZPD+MUgdTIgsX/IfHND+gKQhem3vJeo7l5Qqz2rvvVFXOHwECI19umO9Un1QFKe1V5wg+M+aj90+g==", + "license": "MIT", + "dependencies": { + "@microsoft/kiota-abstractions": "^1.0.0-preview.99", + "tslib": "^2.6.2" + } + }, + "node_modules/@microsoft/kiota-serialization-text": { + "version": "1.0.0-preview.99", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-text/-/kiota-serialization-text-1.0.0-preview.99.tgz", + "integrity": "sha512-gcBffQRI1soHVAiS4YjfMr3SQ9fC8xVUGMfarTTszU4PjpxyFOknaWoFdt/0rvMeavI9jOtbyvDKAf77cZyYIQ==", + "license": "MIT", + "dependencies": { + "@microsoft/kiota-abstractions": "^1.0.0-preview.99", + "tslib": "^2.6.2" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@std-uritemplate/std-uritemplate": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@std-uritemplate/std-uritemplate/-/std-uritemplate-2.0.8.tgz", + "integrity": "sha512-8oj7jKksoTRxjdPkWKX9FyOKDS8ORBm+ZfTSHm0f3eYha8cI0O1d57gyduOIAX7Y2uUukNDNlxlvGb+FFkE7yQ==", + "license": "Apache-2.0" + }, + "node_modules/@types/node": { + "version": "25.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", + "integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/original-fs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/original-fs/-/original-fs-1.2.0.tgz", + "integrity": "sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tinyduration": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tinyduration/-/tinyduration-3.4.1.tgz", + "integrity": "sha512-NemFoamVYn7TmtwZKZ3OiliM9fZkr6EWiTM+wKknco6POSy2gS689xx/pXip0JYp40HXpUw6k65CUYHWYUXdaA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", + "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + } + } +} diff --git a/js/libs/keycloak-admin-client-v2/package.json b/js/libs/keycloak-admin-client-v2/package.json new file mode 100644 index 00000000000..e8c2180a1dd --- /dev/null +++ b/js/libs/keycloak-admin-client-v2/package.json @@ -0,0 +1,26 @@ +{ + "name": "@keycloak/keycloak-admin-client-v2", + "version": "999.0.0-SNAPSHOT", + "description": "Keycloak Admin Client v2 - Generated with Microsoft Kiota", + "type": "module", + "main": "dist/index.js", + "scripts": { + "generate": "tsx scripts/generate.ts", + "generate:file": "OPENAPI_FILE=openapi.yaml tsx scripts/generate.ts", + "build": "tsc", + "test": "tsx test/test-app.ts" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@microsoft/kiota": "^1.29.0", + "@types/node": "^25.0.8", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + }, + "dependencies": { + "@microsoft/kiota-abstractions": "1.0.0-preview.99", + "@microsoft/kiota-bundle": "^1.0.0-preview.99", + "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.99" + } +} diff --git a/js/libs/keycloak-admin-client-v2/scripts/generate.ts b/js/libs/keycloak-admin-client-v2/scripts/generate.ts new file mode 100644 index 00000000000..9806f7c4609 --- /dev/null +++ b/js/libs/keycloak-admin-client-v2/scripts/generate.ts @@ -0,0 +1,181 @@ +// Use require for CommonJS compatibility with @microsoft/kiota +// The ESM build of @microsoft/kiota is broken (missing files), so we use require() +import { createRequire } from "module"; +import { fileURLToPath } from "url"; +import { dirname, resolve } from "path"; +import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"; + +const require = createRequire(import.meta.url); +const { + generateClient, + KiotaGenerationLanguage, + ConsumerOperation, +} = require("@microsoft/kiota"); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const projectRoot = resolve(__dirname, ".."); + +// Configuration +const OPENAPI_URL = process.env.OPENAPI_URL || "http://localhost:9000/openapi"; +const OPENAPI_FILE = process.env.OPENAPI_FILE; // Optional: use a local file instead +const OUTPUT_PATH = resolve(projectRoot, "src"); +const CLIENT_CLASS_NAME = "AdminClient"; +const CLIENT_NAMESPACE = "ApiSdk"; + +async function downloadOpenApiSpec(url: string): Promise { + console.log(`šŸ“„ Downloading OpenAPI spec from ${url}...`); + const response = await fetch(url); + if (!response.ok) { + throw new Error( + `Failed to download OpenAPI spec: ${response.status} ${response.statusText}`, + ); + } + const content = await response.text(); + + // Save to a temp file + const tempFile = resolve(projectRoot, ".openapi-temp.yaml"); + writeFileSync(tempFile, content); + console.log(`āœ… Downloaded and saved to ${tempFile}`); + return tempFile; +} + +async function main() { + console.log("šŸš€ Keycloak Admin Client v2 - Kiota Generator\n"); + + let openApiFilePath: string; + + if (OPENAPI_FILE) { + // Use local file + openApiFilePath = resolve(projectRoot, OPENAPI_FILE); + if (!existsSync(openApiFilePath)) { + console.error(`āŒ OpenAPI file not found: ${openApiFilePath}`); + process.exit(1); + } + console.log(`šŸ“„ Using local OpenAPI file: ${openApiFilePath}`); + } else { + // Download from URL + openApiFilePath = await downloadOpenApiSpec(OPENAPI_URL); + } + + // Ensure output directory exists + if (!existsSync(OUTPUT_PATH)) { + mkdirSync(OUTPUT_PATH, { recursive: true }); + } + + console.log(`\nšŸ“¦ Generating TypeScript client...`); + console.log(` Output: ${OUTPUT_PATH}`); + console.log(` Client class: ${CLIENT_CLASS_NAME}`); + console.log(` Namespace: ${CLIENT_NAMESPACE}\n`); + + try { + const result = await generateClient({ + openAPIFilePath: openApiFilePath, + clientClassName: CLIENT_CLASS_NAME, + clientNamespaceName: CLIENT_NAMESPACE, + language: KiotaGenerationLanguage.TypeScript, + outputPath: OUTPUT_PATH, + operation: ConsumerOperation.Generate, + workingDirectory: projectRoot, + cleanOutput: true, + }); + + if (result) { + console.log("\nāœ… Client generated successfully!"); + + if (result.logs && result.logs.length > 0) { + console.log("\nšŸ“‹ Generation logs:"); + for (const log of result.logs) { + const level = log.level === 1 ? "āš ļø" : log.level === 2 ? "āŒ" : "ā„¹ļø"; + console.log(` ${level} ${log.message}`); + } + } + } else { + console.log("\nāš ļø Generation completed but returned no result"); + } + } catch (error) { + console.error("\nāŒ Generation failed:", error); + process.exit(1); + } + + // Post-process: Fix empty if blocks in adminClient.ts that cause TS2774 errors + postProcessGeneratedCode(); + + console.log("\nšŸŽ‰ Done! You can now build the project with: npm run build"); +} + +/** + * Fix known issues in Kiota-generated code + */ +function postProcessGeneratedCode() { + const adminClientPath = resolve(OUTPUT_PATH, "adminClient.ts"); + + if (!existsSync(adminClientPath)) { + console.log("āš ļø adminClient.ts not found, skipping post-processing"); + return; + } + + console.log("\nšŸ”§ Post-processing generated code..."); + + let content = readFileSync(adminClientPath, "utf-8"); + let modified = false; + + // The Kiota npm package generates empty if-blocks for serializer registration + // We need to fill them in with the actual registration calls + + // Check if the if-blocks are empty (missing the registration calls) + if ( + content.includes( + "if (parseNodeFactoryRegistry.registerDefaultDeserializer) {\n }", + ) + ) { + // Add required imports for serializers/deserializers + const serializerImports = `// @ts-ignore +import { FormParseNodeFactory, FormSerializationWriterFactory } from '@microsoft/kiota-serialization-form'; +// @ts-ignore +import { JsonParseNodeFactory, JsonSerializationWriterFactory } from '@microsoft/kiota-serialization-json'; +// @ts-ignore +import { MultipartSerializationWriterFactory } from '@microsoft/kiota-serialization-multipart'; +// @ts-ignore +import { TextParseNodeFactory, TextSerializationWriterFactory } from '@microsoft/kiota-serialization-text'; +`; + + // Add imports after the existing imports + content = content.replace( + /import \{ apiClientProxifier,/, + serializerImports + "\nimport { apiClientProxifier,", + ); + + // Fill in the deserializer registration + content = content.replace( + /if \(parseNodeFactoryRegistry\.registerDefaultDeserializer\) \{\n {4}\}/, + `if (parseNodeFactoryRegistry.registerDefaultDeserializer) { + parseNodeFactoryRegistry.registerDefaultDeserializer(JsonParseNodeFactory, backingStoreFactory); + parseNodeFactoryRegistry.registerDefaultDeserializer(TextParseNodeFactory, backingStoreFactory); + parseNodeFactoryRegistry.registerDefaultDeserializer(FormParseNodeFactory, backingStoreFactory); + }`, + ); + + // Fill in the serializer registration + content = content.replace( + /if \(serializationWriterFactory\.registerDefaultSerializer\) \{\n {4}\}/, + `if (serializationWriterFactory.registerDefaultSerializer) { + serializationWriterFactory.registerDefaultSerializer(JsonSerializationWriterFactory); + serializationWriterFactory.registerDefaultSerializer(TextSerializationWriterFactory); + serializationWriterFactory.registerDefaultSerializer(FormSerializationWriterFactory); + serializationWriterFactory.registerDefaultSerializer(MultipartSerializationWriterFactory); + }`, + ); + + modified = true; + console.log(" āœ… Added serializer/deserializer registration code"); + } + + if (modified) { + writeFileSync(adminClientPath, content); + } else { + console.log(" ā„¹ļø No fixes needed"); + } +} + +void main(); diff --git a/js/libs/keycloak-admin-client-v2/test/test-app.ts b/js/libs/keycloak-admin-client-v2/test/test-app.ts new file mode 100644 index 00000000000..fd38be7dfa8 --- /dev/null +++ b/js/libs/keycloak-admin-client-v2/test/test-app.ts @@ -0,0 +1,177 @@ +import { FetchRequestAdapter } from "@microsoft/kiota-http-fetchlibrary"; +import { + type AccessTokenProvider, + AllowedHostsValidator, + BaseBearerTokenAuthenticationProvider, +} from "@microsoft/kiota-abstractions"; +import { createAdminClient } from "../src/adminClient.js"; +import type { OIDCClientRepresentation } from "../src/models/index.js"; + +// Configuration +const KEYCLOAK_URL = process.env.KEYCLOAK_URL || "http://localhost:8080"; +const REALM = process.env.KEYCLOAK_REALM || "master"; +const CLIENT_ID = process.env.KEYCLOAK_CLIENT_ID || "admin-cli"; +const USERNAME = process.env.KEYCLOAK_USERNAME || "admin"; +const PASSWORD = process.env.KEYCLOAK_PASSWORD || "admin"; + +interface TokenResponse { + access_token: string; + expires_in: number; + refresh_token?: string; + token_type: string; +} + +/** + * Simple access token provider that fetches tokens from Keycloak + */ +class KeycloakAccessTokenProvider implements AccessTokenProvider { + #accessToken: string | null = null; + #tokenExpiry: number = 0; + #allowedHostsValidator: AllowedHostsValidator; + #keycloakUrl: string; + #realm: string; + #clientId: string; + #username: string; + #password: string; + + constructor( + keycloakUrl: string, + realm: string, + clientId: string, + username: string, + password: string, + ) { + this.#keycloakUrl = keycloakUrl; + this.#realm = realm; + this.#clientId = clientId; + this.#username = username; + this.#password = password; + this.#allowedHostsValidator = new AllowedHostsValidator( + new Set([new URL(keycloakUrl).host]), + ); + } + + async getAuthorizationToken(): Promise { + // Check if we have a valid cached token + if (this.#accessToken && Date.now() < this.#tokenExpiry - 30000) { + return this.#accessToken; + } + + // Fetch a new token + const tokenUrl = `${this.#keycloakUrl}/realms/${this.#realm}/protocol/openid-connect/token`; + const response = await fetch(tokenUrl, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + grant_type: "password", + client_id: this.#clientId, + username: this.#username, + password: this.#password, + }), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error( + `Failed to get access token: ${response.status} - ${error}`, + ); + } + + const tokenData: TokenResponse = (await response.json()) as TokenResponse; + this.#accessToken = tokenData.access_token; + this.#tokenExpiry = Date.now() + tokenData.expires_in * 1000; + + return this.#accessToken; + } + + getAllowedHostsValidator(): AllowedHostsValidator { + return this.#allowedHostsValidator; + } +} + +async function main() { + console.log("šŸ” Keycloak Admin Client v2 - Test Application\n"); + console.log(`Keycloak URL: ${KEYCLOAK_URL}`); + console.log(`Realm: ${REALM}`); + + // Create the authentication provider + const tokenProvider = new KeycloakAccessTokenProvider( + KEYCLOAK_URL, + REALM, + CLIENT_ID, + USERNAME, + PASSWORD, + ); + const authProvider = new BaseBearerTokenAuthenticationProvider(tokenProvider); + + // Create the request adapter + const adapter = new FetchRequestAdapter(authProvider); + adapter.baseUrl = KEYCLOAK_URL; + + // Create the admin client + const client = createAdminClient(adapter); + + try { + // List all clients in the realm + console.log(`šŸ“‹ Listing clients in realm '${REALM}'...\n`); + const clients = await client.admin.api.v2.realms + .byName(REALM) + .clients.get(); + + if (clients && clients.length > 0) { + console.log(`Found ${clients.length} client(s):\n`); + for (const c of clients) { + console.log(` - ${c.clientId} (${c.protocol || "unknown protocol"})`); + if (c.displayName) { + console.log(` Display Name: ${c.displayName}`); + } + if (c.description) { + console.log(` Description: ${c.description}`); + } + console.log(` Enabled: ${c.enabled ?? "unknown"}`); + console.log(); + } + } else { + console.log("No clients found."); + } + + // Create a new test client + console.log("\nšŸ†• Creating a new OIDC test client...\n"); + const newClient: OIDCClientRepresentation = { + clientId: `test-client-${Date.now()}`, + displayName: "Test Client", + description: "A test client created by the admin client v2", + enabled: true, + protocol: "openid-connect", + redirectUris: ["http://localhost:3000/callback"], + webOrigins: ["http://localhost:3000"], + }; + + await client.admin.api.v2.realms.byName(REALM).clients.post(newClient); + console.log(`āœ… Created client: ${newClient.clientId}`); + + // List clients again to verify + console.log("\nšŸ“‹ Listing clients again to verify...\n"); + const updatedClients = await client.admin.api.v2.realms + .byName(REALM) + .clients.get(); + const createdClient = updatedClients?.find( + (c) => c.clientId === newClient.clientId, + ); + + if (createdClient) { + console.log(`āœ… Verified: Client '${createdClient.clientId}' exists!`); + } else { + console.log(`āŒ Could not find the created client`); + } + + console.log("\nšŸŽ‰ Test completed successfully!"); + } catch (error) { + console.error("āŒ Error:", error); + process.exit(1); + } +} + +void main(); diff --git a/js/libs/keycloak-admin-client-v2/tsconfig.json b/js/libs/keycloak-admin-client-v2/tsconfig.json new file mode 100644 index 00000000000..795b8bf9227 --- /dev/null +++ b/js/libs/keycloak-admin-client-v2/tsconfig.json @@ -0,0 +1,39 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + "rootDir": ".", + "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "lib": ["esnext"], + "types": ["node"], + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + // Relaxed for Kiota generated code compatibility + "noUncheckedIndexedAccess": false, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitAny": true, + "noImplicitThis": true, + "alwaysStrict": true, + + // Recommended Options + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true + }, + "include": ["src/**/*", "test/**/*", "scripts/**/*"] +} diff --git a/js/package.json b/js/package.json index e0d205368dc..f4ded39c3f8 100644 --- a/js/package.json +++ b/js/package.json @@ -13,6 +13,7 @@ "./apps/account-ui:build", "./apps/admin-ui:build", "./libs/keycloak-admin-client:build", + "./libs/keycloak-admin-client-v2:generate:file", "./libs/ui-shared:build", "./themes-vendor:build" ] diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 03cf299df5c..9d0276b5537 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -128,7 +128,7 @@ importers: version: 18.3.7(@types/react@18.3.23) '@vitejs/plugin-react-swc': specifier: ^4.2.2 - version: 4.2.2(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 4.2.2(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) cross-env: specifier: ^10.1.0 version: 10.1.0 @@ -137,13 +137,13 @@ importers: version: 1.30.2 vite: specifier: ^7.3.0 - version: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + version: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-checker: specifier: ^0.12.0 - version: 0.12.0(eslint@9.39.2)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 0.12.0(eslint@9.39.2)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@25.0.3)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 4.5.4(@types/node@25.0.8)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) apps/admin-ui: dependencies: @@ -258,7 +258,7 @@ importers: version: 18.3.7(@types/react@18.3.23) '@vitejs/plugin-react-swc': specifier: ^4.2.2 - version: 4.2.2(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 4.2.2(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) cross-env: specifier: ^10.1.0 version: 10.1.0 @@ -273,22 +273,22 @@ importers: version: 3.6.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3) + version: 10.9.2(@swc/core@1.15.8)(@types/node@25.0.8)(typescript@5.9.3) uuid: specifier: ^13.0.0 version: 13.0.0 vite: specifier: ^7.3.0 - version: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + version: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-checker: specifier: ^0.12.0 - version: 0.12.0(eslint@9.39.2)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 0.12.0(eslint@9.39.2)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@25.0.3)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 4.5.4(@types/node@25.0.8)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: ^4.0.16 - version: 4.0.16(@types/node@25.0.3)(jsdom@27.4.0)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.8)(jsdom@27.4.0)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) apps/create-keycloak-theme: dependencies: @@ -363,6 +363,31 @@ importers: specifier: ^10.9.2 version: 10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3) + libs/keycloak-admin-client-v2: + dependencies: + '@microsoft/kiota-abstractions': + specifier: 1.0.0-preview.99 + version: 1.0.0-preview.99 + '@microsoft/kiota-bundle': + specifier: ^1.0.0-preview.99 + version: 1.0.0-preview.99 + '@microsoft/kiota-http-fetchlibrary': + specifier: ^1.0.0-preview.99 + version: 1.0.0-preview.99 + devDependencies: + '@microsoft/kiota': + specifier: ^1.29.0 + version: 1.29.0 + '@types/node': + specifier: ^25.0.8 + version: 25.0.8 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + libs/ui-shared: dependencies: '@keycloak/keycloak-admin-client': @@ -413,25 +438,25 @@ importers: version: 18.3.7(@types/react@18.3.23) '@vitejs/plugin-react-swc': specifier: ^4.2.2 - version: 4.2.2(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 4.2.2(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) rollup-plugin-peer-deps-external: specifier: ^2.2.4 version: 2.2.4(rollup@4.55.1) vite: specifier: ^7.3.0 - version: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + version: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-checker: specifier: ^0.12.0 - version: 0.12.0(eslint@9.39.2)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 0.12.0(eslint@9.39.2)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@25.0.3)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 4.5.4(@types/node@25.0.8)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) vite-plugin-lib-inject-css: specifier: ^2.2.2 - version: 2.2.2(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + version: 2.2.2(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: ^4.0.16 - version: 4.0.16(@types/node@25.0.3)(jsdom@27.4.0)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.8)(jsdom@27.4.0)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) themes-vendor: dependencies: @@ -1046,6 +1071,30 @@ packages: resolution: {integrity: sha512-LhKytJM5ZJkbHQVfW/3o747rZUNs/MGg6j/wt/9qwwqEOfvUDTYXXxIBuMgrRXhJ528p41iyz4zjBVHZU74Odg==} hasBin: true + '@microsoft/kiota-abstractions@1.0.0-preview.99': + resolution: {integrity: sha512-6qrlwGCO3DbvpA7tszqk7JNQPFPDg8gv5NGMTziwdtHqaQrUALKusm3vdJGPVGrbNiZL6/lBaY5NSUvLtlhRCg==} + + '@microsoft/kiota-bundle@1.0.0-preview.99': + resolution: {integrity: sha512-AxvO+z6UgWMAT2NfXN36CMhAUm/39tUQt8o32axeEJDS/EvZINDAPstbQV+zK7bJRC8kCqUHhCE/fuHX2dXA+g==} + + '@microsoft/kiota-http-fetchlibrary@1.0.0-preview.99': + resolution: {integrity: sha512-lIruiYf8L7DPG0Fm92QN5YK4zx4sh76EtTONUAqBCxt5AtEJ7KQceBajaXQsjfcndUAp9LmDVdqR44o8sc9/mA==} + + '@microsoft/kiota-serialization-form@1.0.0-preview.99': + resolution: {integrity: sha512-BdXxqNfy+5yWTyxpBguqJYy6E9RRotrDClRcUO89okFcR5N6s6xenjsvGw++q9KWNi7qcHrqaG8xlRz1TLk81Q==} + + '@microsoft/kiota-serialization-json@1.0.0-preview.99': + resolution: {integrity: sha512-kDmMYmB7XkRprlLviEynRPDkE45JDxqiuUopfBRMvNK4qhzFiJTXGLihZ2FG6fdVu9LbV3/W0QxbeGP35kDK6A==} + + '@microsoft/kiota-serialization-multipart@1.0.0-preview.99': + resolution: {integrity: sha512-cfviCAVTlZPD+MUgdTIgsX/IfHND+gKQhem3vJeo7l5Qqz2rvvVFXOHwECI19umO9Un1QFKe1V5wg+M+aj90+g==} + + '@microsoft/kiota-serialization-text@1.0.0-preview.99': + resolution: {integrity: sha512-gcBffQRI1soHVAiS4YjfMr3SQ9fC8xVUGMfarTTszU4PjpxyFOknaWoFdt/0rvMeavI9jOtbyvDKAf77cZyYIQ==} + + '@microsoft/kiota@1.29.0': + resolution: {integrity: sha512-qqIlTz48OJ5ZMRoTA/uQA70B7ltS4lPSs9atG5PUn+dKZcgXny3LzQPe12B1LsKoBJYbwhaU3fD8/C1DsLW6Cw==} + '@microsoft/tsdoc-config@0.17.1': resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} @@ -1120,6 +1169,10 @@ packages: '@octokit/types@16.0.0': resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@patternfly/patternfly@4.224.5': resolution: {integrity: sha512-io0huj+LCP5FgDZJDaLv1snxktTYs8iCFz/W1VDRneYoebNHLmGfQdF7Yn8bS6PF7qmN6oJKEBlq3AjmmE8vdA==} @@ -1410,6 +1463,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@std-uritemplate/std-uritemplate@2.0.8': + resolution: {integrity: sha512-8oj7jKksoTRxjdPkWKX9FyOKDS8ORBm+ZfTSHm0f3eYha8cI0O1d57gyduOIAX7Y2uUukNDNlxlvGb+FFkE7yQ==} + '@swc/core-darwin-arm64@1.15.8': resolution: {integrity: sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==} engines: {node: '>=10'} @@ -1673,6 +1729,9 @@ packages: '@types/node@25.0.3': resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} + '@types/node@25.0.8': + resolution: {integrity: sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==} + '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} @@ -1849,6 +1908,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -2672,6 +2735,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -3363,6 +3429,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + original-fs@1.2.0: + resolution: {integrity: sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -3644,6 +3713,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -3924,6 +3996,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyduration@3.4.1: + resolution: {integrity: sha512-NemFoamVYn7TmtwZKZ3OiliM9fZkr6EWiTM+wKknco6POSy2gS689xx/pXip0JYp40HXpUw6k65CUYHWYUXdaA==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -3984,6 +4059,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -4238,6 +4318,10 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + vscode-jsonrpc@8.2.1: + resolution: {integrity: sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==} + engines: {node: '>=14.0.0'} + vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} @@ -5027,23 +5111,23 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} - '@microsoft/api-extractor-model@7.30.7(@types/node@25.0.3)': + '@microsoft/api-extractor-model@7.30.7(@types/node@25.0.8)': dependencies: '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.14.0(@types/node@25.0.3) + '@rushstack/node-core-library': 5.14.0(@types/node@25.0.8) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.52.10(@types/node@25.0.3)': + '@microsoft/api-extractor@7.52.10(@types/node@25.0.8)': dependencies: - '@microsoft/api-extractor-model': 7.30.7(@types/node@25.0.3) + '@microsoft/api-extractor-model': 7.30.7(@types/node@25.0.8) '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.14.0(@types/node@25.0.3) + '@rushstack/node-core-library': 5.14.0(@types/node@25.0.8) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.15.4(@types/node@25.0.3) - '@rushstack/ts-command-line': 5.0.2(@types/node@25.0.3) + '@rushstack/terminal': 0.15.4(@types/node@25.0.8) + '@rushstack/ts-command-line': 5.0.2(@types/node@25.0.8) lodash: 4.17.21 minimatch: 10.0.3 resolve: 1.22.10 @@ -5053,6 +5137,55 @@ snapshots: transitivePeerDependencies: - '@types/node' + '@microsoft/kiota-abstractions@1.0.0-preview.99': + dependencies: + '@opentelemetry/api': 1.9.0 + '@std-uritemplate/std-uritemplate': 2.0.8 + tinyduration: 3.4.1 + tslib: 2.8.1 + + '@microsoft/kiota-bundle@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + '@microsoft/kiota-http-fetchlibrary': 1.0.0-preview.99 + '@microsoft/kiota-serialization-form': 1.0.0-preview.99 + '@microsoft/kiota-serialization-json': 1.0.0-preview.99 + '@microsoft/kiota-serialization-multipart': 1.0.0-preview.99 + '@microsoft/kiota-serialization-text': 1.0.0-preview.99 + + '@microsoft/kiota-http-fetchlibrary@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + '@opentelemetry/api': 1.9.0 + tslib: 2.8.1 + + '@microsoft/kiota-serialization-form@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + tslib: 2.8.1 + + '@microsoft/kiota-serialization-json@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + tslib: 2.8.1 + + '@microsoft/kiota-serialization-multipart@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + tslib: 2.8.1 + + '@microsoft/kiota-serialization-text@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + tslib: 2.8.1 + + '@microsoft/kiota@1.29.0': + dependencies: + adm-zip: 0.5.16 + original-fs: 1.2.0 + uuid: 13.0.0 + vscode-jsonrpc: 8.2.1 + '@microsoft/tsdoc-config@0.17.1': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -5138,6 +5271,8 @@ snapshots: dependencies: '@octokit/openapi-types': 27.0.0 + '@opentelemetry/api@1.9.0': {} + '@patternfly/patternfly@4.224.5': {} '@patternfly/patternfly@5.4.2': {} @@ -5392,7 +5527,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.55.1': optional: true - '@rushstack/node-core-library@5.14.0(@types/node@25.0.3)': + '@rushstack/node-core-library@5.14.0(@types/node@25.0.8)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -5403,23 +5538,23 @@ snapshots: resolve: 1.22.10 semver: 7.5.4 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.0.8 '@rushstack/rig-package@0.5.3': dependencies: resolve: 1.22.10 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.15.4(@types/node@25.0.3)': + '@rushstack/terminal@0.15.4(@types/node@25.0.8)': dependencies: - '@rushstack/node-core-library': 5.14.0(@types/node@25.0.3) + '@rushstack/node-core-library': 5.14.0(@types/node@25.0.8) supports-color: 8.1.1 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.0.8 - '@rushstack/ts-command-line@5.0.2(@types/node@25.0.3)': + '@rushstack/ts-command-line@5.0.2(@types/node@25.0.8)': dependencies: - '@rushstack/terminal': 0.15.4(@types/node@25.0.3) + '@rushstack/terminal': 0.15.4(@types/node@25.0.8) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -5428,6 +5563,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@std-uritemplate/std-uritemplate@2.0.8': {} + '@swc/core-darwin-arm64@1.15.8': optional: true @@ -5696,6 +5833,10 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/node@25.0.8': + dependencies: + undici-types: 7.16.0 + '@types/prismjs@1.26.5': {} '@types/prop-types@15.7.15': {} @@ -5718,7 +5859,7 @@ snapshots: '@types/tar-stream@3.1.4': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.0.8 '@types/unist@2.0.11': {} @@ -5827,11 +5968,11 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react-swc@4.2.2(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2))': + '@vitejs/plugin-react-swc@4.2.2(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.47 '@swc/core': 1.15.8 - vite: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - '@swc/helpers' @@ -5844,13 +5985,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2))': + '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) '@vitest/pretty-format@4.0.16': dependencies: @@ -5929,6 +6070,8 @@ snapshots: acorn@8.15.0: {} + adm-zip@0.5.16: {} + agent-base@7.1.4: {} ajv-draft-04@1.0.0(ajv@8.13.0): @@ -6850,6 +6993,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7596,6 +7743,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + original-fs@1.2.0: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -7911,6 +8060,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -8281,6 +8432,8 @@ snapshots: tinybench@2.9.0: {} + tinyduration@3.4.1: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -8336,8 +8489,35 @@ snapshots: optionalDependencies: '@swc/core': 1.15.8 + ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.8)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 25.0.8 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.15.8 + tslib@2.8.1: {} + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -8490,7 +8670,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plugin-checker@0.12.0(eslint@9.39.2)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)): + vite-plugin-checker@0.12.0(eslint@9.39.2)(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@babel/code-frame': 7.27.1 chokidar: 4.0.3 @@ -8499,16 +8679,16 @@ snapshots: picomatch: 4.0.3 tiny-invariant: 1.3.3 tinyglobby: 0.2.15 - vite: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) vscode-uri: 3.1.0 optionalDependencies: eslint: 9.39.2 optionator: 0.9.4 typescript: 5.9.3 - vite-plugin-dts@4.5.4(@types/node@25.0.3)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)): + vite-plugin-dts@4.5.4(@types/node@25.0.8)(rollup@4.55.1)(typescript@5.9.3)(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: - '@microsoft/api-extractor': 7.52.10(@types/node@25.0.3) + '@microsoft/api-extractor': 7.52.10(@types/node@25.0.8) '@rollup/pluginutils': 5.2.0(rollup@4.55.1) '@volar/typescript': 2.4.22 '@vue/language-core': 2.2.0(typescript@5.9.3) @@ -8519,20 +8699,20 @@ snapshots: magic-string: 0.30.17 typescript: 5.9.3 optionalDependencies: - vite: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-lib-inject-css@2.2.2(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)): + vite-plugin-lib-inject-css@2.2.2(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@ast-grep/napi': 0.36.3 magic-string: 0.30.17 picocolors: 1.1.1 - vite: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) - vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2): + vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -8541,16 +8721,17 @@ snapshots: rollup: 4.55.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.0.8 fsevents: 2.3.3 lightningcss: 1.30.2 terser: 5.43.1 + tsx: 4.21.0 yaml: 2.8.2 - vitest@4.0.16(@types/node@25.0.3)(jsdom@27.4.0)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2): + vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.0.8)(jsdom@27.4.0)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2)) + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -8567,10 +8748,11 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@25.0.3)(lightningcss@1.30.2)(terser@5.43.1)(yaml@2.8.2) + vite: 7.3.0(@types/node@25.0.8)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.0.3 + '@opentelemetry/api': 1.9.0 + '@types/node': 25.0.8 jsdom: 27.4.0 transitivePeerDependencies: - jiti @@ -8587,6 +8769,8 @@ snapshots: void-elements@3.1.0: {} + vscode-jsonrpc@8.2.1: {} + vscode-uri@3.1.0: {} w3c-xmlserializer@5.0.0: diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/oas/OASModelFilter.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/oas/OASModelFilter.java index 7b0ea62daab..1ba69117cb0 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/oas/OASModelFilter.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/oas/OASModelFilter.java @@ -3,6 +3,7 @@ package org.keycloak.quarkus.runtime.oas; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -58,6 +59,9 @@ public class OASModelFilter implements OASFilter { newPaths.forEach(paths::addPathItem); openAPI.setPaths(paths); + // Remove dangling allOf references (references to schemas that don't exist, e.g. hidden schemas) + removeDanglingAllOfReferences(openAPI); + Map> discriminatorPropertiesToBeAdded = new HashMap<>(); // Reflect Jackson annotations in OpenAPI spec @@ -111,6 +115,42 @@ public class OASModelFilter implements OASFilter { }); } + /** + * Removes allOf references that point to schemas that don't exist in the OpenAPI document. + * This can happen when a parent class is marked with @Schema(hidden = true). + */ + private void removeDanglingAllOfReferences(OpenAPI openAPI) { + if (openAPI.getComponents() == null || openAPI.getComponents().getSchemas() == null) { + return; + } + + Set existingSchemaNames = openAPI.getComponents().getSchemas().keySet(); + + openAPI.getComponents().getSchemas().values().forEach(schema -> { + if (schema.getAllOf() != null && !schema.getAllOf().isEmpty()) { + List filteredAllOf = schema.getAllOf().stream() + .filter(allOfSchema -> { + if (allOfSchema.getRef() != null) { + String refName = allOfSchema.getRef().substring(REF_PREFIX.length()); + boolean isDangling = !existingSchemaNames.contains(refName); + if (isDangling) { + log.debugf("Removing dangling allOf reference to: %s", refName); + } + return !isDangling; + } + return true; + }) + .collect(Collectors.toList()); + + if (filteredAllOf.isEmpty()) { + schema.setAllOf(null); + } else if (filteredAllOf.size() != schema.getAllOf().size()) { + schema.setAllOf(filteredAllOf); + } + } + }); + } + private PathItem sortOperationsByMethod(PathItem pathItem) { PathItem sortedPathItem = OASFactory.createPathItem(); diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/BaseRepresentation.java b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/BaseRepresentation.java index 86c90c89aec..be054e03613 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/BaseRepresentation.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/BaseRepresentation.java @@ -10,7 +10,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import org.eclipse.microprofile.openapi.annotations.media.Schema; @JsonInclude(JsonInclude.Include.NON_ABSENT) -@Schema +@Schema(hidden = true) public class BaseRepresentation { @JsonIgnore From 79e6ed85ceffd3cbed71636bbfb3c242b31ebbf2 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 20 Jan 2026 12:21:17 +0100 Subject: [PATCH 2/5] now uses openapitools to generate and moved it into the existing module for better adoption Signed-off-by: Erik Jan de Wit --- .../keycloak-server/scripts/start-server.js | 3 +- js/libs/keycloak-admin-client-v2/.gitignore | 24 - .../package-lock.json | 754 ------------------ js/libs/keycloak-admin-client-v2/package.json | 26 - .../scripts/generate.ts | 181 ----- .../keycloak-admin-client-v2/test/test-app.ts | 177 ---- .../keycloak-admin-client-v2/tsconfig.json | 39 - js/libs/keycloak-admin-client/.gitignore | 1 + .../api.yml} | 25 +- .../keycloak-admin-client/openapitools.json | 7 + js/libs/keycloak-admin-client/package.json | 14 + .../scripts/fix-generated-runtime.mjs | 88 ++ js/libs/keycloak-admin-client/src/index.ts | 17 + .../src/resources/clients.ts | 9 + .../src/resources/clientsV2.ts | 66 ++ .../test/clientsV2.spec.ts | 211 +++++ js/package.json | 1 - js/pnpm-lock.yaml | 133 +-- 18 files changed, 441 insertions(+), 1335 deletions(-) delete mode 100644 js/libs/keycloak-admin-client-v2/.gitignore delete mode 100644 js/libs/keycloak-admin-client-v2/package-lock.json delete mode 100644 js/libs/keycloak-admin-client-v2/package.json delete mode 100644 js/libs/keycloak-admin-client-v2/scripts/generate.ts delete mode 100644 js/libs/keycloak-admin-client-v2/test/test-app.ts delete mode 100644 js/libs/keycloak-admin-client-v2/tsconfig.json rename js/libs/{keycloak-admin-client-v2/openapi.yaml => keycloak-admin-client/api.yml} (92%) create mode 100644 js/libs/keycloak-admin-client/openapitools.json create mode 100644 js/libs/keycloak-admin-client/scripts/fix-generated-runtime.mjs create mode 100644 js/libs/keycloak-admin-client/src/resources/clientsV2.ts create mode 100644 js/libs/keycloak-admin-client/test/clientsV2.spec.ts diff --git a/js/apps/keycloak-server/scripts/start-server.js b/js/apps/keycloak-server/scripts/start-server.js index 5e8b3d9d747..087fd7326fa 100755 --- a/js/apps/keycloak-server/scripts/start-server.js +++ b/js/apps/keycloak-server/scripts/start-server.js @@ -60,7 +60,8 @@ async function startServer() { path.join(SERVER_DIR, `bin/kc${SCRIPT_EXTENSION}`), [ "start-dev", - `--features="transient-users,oid4vc-vci,declarative-ui,quick-theme,spiffe,kubernetes-service-accounts,workflows,client-auth-federated,jwt-authorization-grant"`, + `--features="transient-users,oid4vc-vci,declarative-ui,quick-theme,spiffe,kubernetes-service-accounts,workflows,client-auth-federated,openapi,client-admin-api:v2"`, + "--openapi-enabled=true", ...keycloakArgs, ], { diff --git a/js/libs/keycloak-admin-client-v2/.gitignore b/js/libs/keycloak-admin-client-v2/.gitignore deleted file mode 100644 index 6ccef181db4..00000000000 --- a/js/libs/keycloak-admin-client-v2/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Dependencies -node_modules/ - -# Build output -dist/ - -# Kiota workspace files (regenerated on each run) -.kiota/ -.openapi-temp.yaml -src/ - -# Logs -*.log -src/.kiota.log - -# IDE -.idea/ -.vscode/ -*.swp -*.swo - -# OS -.DS_Store -Thumbs.db diff --git a/js/libs/keycloak-admin-client-v2/package-lock.json b/js/libs/keycloak-admin-client-v2/package-lock.json deleted file mode 100644 index 59ca70e5910..00000000000 --- a/js/libs/keycloak-admin-client-v2/package-lock.json +++ /dev/null @@ -1,754 +0,0 @@ -{ - "name": "keycloak-admin-client-v2", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "keycloak-admin-client-v2", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@microsoft/kiota-bundle": "^1.0.0-preview.99", - "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.99" - }, - "devDependencies": { - "@microsoft/kiota": "^1.29.0", - "@types/node": "^25.0.8", - "tsx": "^4.21.0", - "typescript": "^5.9.3" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@microsoft/kiota": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@microsoft/kiota/-/kiota-1.29.0.tgz", - "integrity": "sha512-qqIlTz48OJ5ZMRoTA/uQA70B7ltS4lPSs9atG5PUn+dKZcgXny3LzQPe12B1LsKoBJYbwhaU3fD8/C1DsLW6Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "adm-zip": "^0.5.16", - "original-fs": "^1.2.0", - "uuid": "^13.0.0", - "vscode-jsonrpc": "^8.2.1" - } - }, - "node_modules/@microsoft/kiota-abstractions": { - "version": "1.0.0-preview.99", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-abstractions/-/kiota-abstractions-1.0.0-preview.99.tgz", - "integrity": "sha512-6qrlwGCO3DbvpA7tszqk7JNQPFPDg8gv5NGMTziwdtHqaQrUALKusm3vdJGPVGrbNiZL6/lBaY5NSUvLtlhRCg==", - "license": "MIT", - "dependencies": { - "@opentelemetry/api": "^1.7.0", - "@std-uritemplate/std-uritemplate": "^2.0.0", - "tinyduration": "^3.3.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@microsoft/kiota-bundle": { - "version": "1.0.0-preview.99", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-bundle/-/kiota-bundle-1.0.0-preview.99.tgz", - "integrity": "sha512-AxvO+z6UgWMAT2NfXN36CMhAUm/39tUQt8o32axeEJDS/EvZINDAPstbQV+zK7bJRC8kCqUHhCE/fuHX2dXA+g==", - "license": "MIT", - "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.0-preview.99", - "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.99", - "@microsoft/kiota-serialization-form": "^1.0.0-preview.99", - "@microsoft/kiota-serialization-json": "^1.0.0-preview.99", - "@microsoft/kiota-serialization-multipart": "^1.0.0-preview.99", - "@microsoft/kiota-serialization-text": "^1.0.0-preview.99" - } - }, - "node_modules/@microsoft/kiota-http-fetchlibrary": { - "version": "1.0.0-preview.99", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-http-fetchlibrary/-/kiota-http-fetchlibrary-1.0.0-preview.99.tgz", - "integrity": "sha512-lIruiYf8L7DPG0Fm92QN5YK4zx4sh76EtTONUAqBCxt5AtEJ7KQceBajaXQsjfcndUAp9LmDVdqR44o8sc9/mA==", - "license": "MIT", - "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.0-preview.99", - "@opentelemetry/api": "^1.7.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@microsoft/kiota-serialization-form": { - "version": "1.0.0-preview.99", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-form/-/kiota-serialization-form-1.0.0-preview.99.tgz", - "integrity": "sha512-BdXxqNfy+5yWTyxpBguqJYy6E9RRotrDClRcUO89okFcR5N6s6xenjsvGw++q9KWNi7qcHrqaG8xlRz1TLk81Q==", - "license": "MIT", - "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.0-preview.99", - "tslib": "^2.6.2" - } - }, - "node_modules/@microsoft/kiota-serialization-json": { - "version": "1.0.0-preview.99", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-json/-/kiota-serialization-json-1.0.0-preview.99.tgz", - "integrity": "sha512-kDmMYmB7XkRprlLviEynRPDkE45JDxqiuUopfBRMvNK4qhzFiJTXGLihZ2FG6fdVu9LbV3/W0QxbeGP35kDK6A==", - "license": "MIT", - "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.0-preview.99", - "tslib": "^2.6.2" - } - }, - "node_modules/@microsoft/kiota-serialization-multipart": { - "version": "1.0.0-preview.99", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-multipart/-/kiota-serialization-multipart-1.0.0-preview.99.tgz", - "integrity": "sha512-cfviCAVTlZPD+MUgdTIgsX/IfHND+gKQhem3vJeo7l5Qqz2rvvVFXOHwECI19umO9Un1QFKe1V5wg+M+aj90+g==", - "license": "MIT", - "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.0-preview.99", - "tslib": "^2.6.2" - } - }, - "node_modules/@microsoft/kiota-serialization-text": { - "version": "1.0.0-preview.99", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-text/-/kiota-serialization-text-1.0.0-preview.99.tgz", - "integrity": "sha512-gcBffQRI1soHVAiS4YjfMr3SQ9fC8xVUGMfarTTszU4PjpxyFOknaWoFdt/0rvMeavI9jOtbyvDKAf77cZyYIQ==", - "license": "MIT", - "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.0-preview.99", - "tslib": "^2.6.2" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@std-uritemplate/std-uritemplate": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@std-uritemplate/std-uritemplate/-/std-uritemplate-2.0.8.tgz", - "integrity": "sha512-8oj7jKksoTRxjdPkWKX9FyOKDS8ORBm+ZfTSHm0f3eYha8cI0O1d57gyduOIAX7Y2uUukNDNlxlvGb+FFkE7yQ==", - "license": "Apache-2.0" - }, - "node_modules/@types/node": { - "version": "25.0.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", - "integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/adm-zip": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", - "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/original-fs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/original-fs/-/original-fs-1.2.0.tgz", - "integrity": "sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tinyduration": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tinyduration/-/tinyduration-3.4.1.tgz", - "integrity": "sha512-NemFoamVYn7TmtwZKZ3OiliM9fZkr6EWiTM+wKknco6POSy2gS689xx/pXip0JYp40HXpUw6k65CUYHWYUXdaA==", - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", - "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - } - } -} diff --git a/js/libs/keycloak-admin-client-v2/package.json b/js/libs/keycloak-admin-client-v2/package.json deleted file mode 100644 index e8c2180a1dd..00000000000 --- a/js/libs/keycloak-admin-client-v2/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@keycloak/keycloak-admin-client-v2", - "version": "999.0.0-SNAPSHOT", - "description": "Keycloak Admin Client v2 - Generated with Microsoft Kiota", - "type": "module", - "main": "dist/index.js", - "scripts": { - "generate": "tsx scripts/generate.ts", - "generate:file": "OPENAPI_FILE=openapi.yaml tsx scripts/generate.ts", - "build": "tsc", - "test": "tsx test/test-app.ts" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "@microsoft/kiota": "^1.29.0", - "@types/node": "^25.0.8", - "tsx": "^4.21.0", - "typescript": "^5.9.3" - }, - "dependencies": { - "@microsoft/kiota-abstractions": "1.0.0-preview.99", - "@microsoft/kiota-bundle": "^1.0.0-preview.99", - "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.99" - } -} diff --git a/js/libs/keycloak-admin-client-v2/scripts/generate.ts b/js/libs/keycloak-admin-client-v2/scripts/generate.ts deleted file mode 100644 index 9806f7c4609..00000000000 --- a/js/libs/keycloak-admin-client-v2/scripts/generate.ts +++ /dev/null @@ -1,181 +0,0 @@ -// Use require for CommonJS compatibility with @microsoft/kiota -// The ESM build of @microsoft/kiota is broken (missing files), so we use require() -import { createRequire } from "module"; -import { fileURLToPath } from "url"; -import { dirname, resolve } from "path"; -import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"; - -const require = createRequire(import.meta.url); -const { - generateClient, - KiotaGenerationLanguage, - ConsumerOperation, -} = require("@microsoft/kiota"); - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const projectRoot = resolve(__dirname, ".."); - -// Configuration -const OPENAPI_URL = process.env.OPENAPI_URL || "http://localhost:9000/openapi"; -const OPENAPI_FILE = process.env.OPENAPI_FILE; // Optional: use a local file instead -const OUTPUT_PATH = resolve(projectRoot, "src"); -const CLIENT_CLASS_NAME = "AdminClient"; -const CLIENT_NAMESPACE = "ApiSdk"; - -async function downloadOpenApiSpec(url: string): Promise { - console.log(`šŸ“„ Downloading OpenAPI spec from ${url}...`); - const response = await fetch(url); - if (!response.ok) { - throw new Error( - `Failed to download OpenAPI spec: ${response.status} ${response.statusText}`, - ); - } - const content = await response.text(); - - // Save to a temp file - const tempFile = resolve(projectRoot, ".openapi-temp.yaml"); - writeFileSync(tempFile, content); - console.log(`āœ… Downloaded and saved to ${tempFile}`); - return tempFile; -} - -async function main() { - console.log("šŸš€ Keycloak Admin Client v2 - Kiota Generator\n"); - - let openApiFilePath: string; - - if (OPENAPI_FILE) { - // Use local file - openApiFilePath = resolve(projectRoot, OPENAPI_FILE); - if (!existsSync(openApiFilePath)) { - console.error(`āŒ OpenAPI file not found: ${openApiFilePath}`); - process.exit(1); - } - console.log(`šŸ“„ Using local OpenAPI file: ${openApiFilePath}`); - } else { - // Download from URL - openApiFilePath = await downloadOpenApiSpec(OPENAPI_URL); - } - - // Ensure output directory exists - if (!existsSync(OUTPUT_PATH)) { - mkdirSync(OUTPUT_PATH, { recursive: true }); - } - - console.log(`\nšŸ“¦ Generating TypeScript client...`); - console.log(` Output: ${OUTPUT_PATH}`); - console.log(` Client class: ${CLIENT_CLASS_NAME}`); - console.log(` Namespace: ${CLIENT_NAMESPACE}\n`); - - try { - const result = await generateClient({ - openAPIFilePath: openApiFilePath, - clientClassName: CLIENT_CLASS_NAME, - clientNamespaceName: CLIENT_NAMESPACE, - language: KiotaGenerationLanguage.TypeScript, - outputPath: OUTPUT_PATH, - operation: ConsumerOperation.Generate, - workingDirectory: projectRoot, - cleanOutput: true, - }); - - if (result) { - console.log("\nāœ… Client generated successfully!"); - - if (result.logs && result.logs.length > 0) { - console.log("\nšŸ“‹ Generation logs:"); - for (const log of result.logs) { - const level = log.level === 1 ? "āš ļø" : log.level === 2 ? "āŒ" : "ā„¹ļø"; - console.log(` ${level} ${log.message}`); - } - } - } else { - console.log("\nāš ļø Generation completed but returned no result"); - } - } catch (error) { - console.error("\nāŒ Generation failed:", error); - process.exit(1); - } - - // Post-process: Fix empty if blocks in adminClient.ts that cause TS2774 errors - postProcessGeneratedCode(); - - console.log("\nšŸŽ‰ Done! You can now build the project with: npm run build"); -} - -/** - * Fix known issues in Kiota-generated code - */ -function postProcessGeneratedCode() { - const adminClientPath = resolve(OUTPUT_PATH, "adminClient.ts"); - - if (!existsSync(adminClientPath)) { - console.log("āš ļø adminClient.ts not found, skipping post-processing"); - return; - } - - console.log("\nšŸ”§ Post-processing generated code..."); - - let content = readFileSync(adminClientPath, "utf-8"); - let modified = false; - - // The Kiota npm package generates empty if-blocks for serializer registration - // We need to fill them in with the actual registration calls - - // Check if the if-blocks are empty (missing the registration calls) - if ( - content.includes( - "if (parseNodeFactoryRegistry.registerDefaultDeserializer) {\n }", - ) - ) { - // Add required imports for serializers/deserializers - const serializerImports = `// @ts-ignore -import { FormParseNodeFactory, FormSerializationWriterFactory } from '@microsoft/kiota-serialization-form'; -// @ts-ignore -import { JsonParseNodeFactory, JsonSerializationWriterFactory } from '@microsoft/kiota-serialization-json'; -// @ts-ignore -import { MultipartSerializationWriterFactory } from '@microsoft/kiota-serialization-multipart'; -// @ts-ignore -import { TextParseNodeFactory, TextSerializationWriterFactory } from '@microsoft/kiota-serialization-text'; -`; - - // Add imports after the existing imports - content = content.replace( - /import \{ apiClientProxifier,/, - serializerImports + "\nimport { apiClientProxifier,", - ); - - // Fill in the deserializer registration - content = content.replace( - /if \(parseNodeFactoryRegistry\.registerDefaultDeserializer\) \{\n {4}\}/, - `if (parseNodeFactoryRegistry.registerDefaultDeserializer) { - parseNodeFactoryRegistry.registerDefaultDeserializer(JsonParseNodeFactory, backingStoreFactory); - parseNodeFactoryRegistry.registerDefaultDeserializer(TextParseNodeFactory, backingStoreFactory); - parseNodeFactoryRegistry.registerDefaultDeserializer(FormParseNodeFactory, backingStoreFactory); - }`, - ); - - // Fill in the serializer registration - content = content.replace( - /if \(serializationWriterFactory\.registerDefaultSerializer\) \{\n {4}\}/, - `if (serializationWriterFactory.registerDefaultSerializer) { - serializationWriterFactory.registerDefaultSerializer(JsonSerializationWriterFactory); - serializationWriterFactory.registerDefaultSerializer(TextSerializationWriterFactory); - serializationWriterFactory.registerDefaultSerializer(FormSerializationWriterFactory); - serializationWriterFactory.registerDefaultSerializer(MultipartSerializationWriterFactory); - }`, - ); - - modified = true; - console.log(" āœ… Added serializer/deserializer registration code"); - } - - if (modified) { - writeFileSync(adminClientPath, content); - } else { - console.log(" ā„¹ļø No fixes needed"); - } -} - -void main(); diff --git a/js/libs/keycloak-admin-client-v2/test/test-app.ts b/js/libs/keycloak-admin-client-v2/test/test-app.ts deleted file mode 100644 index fd38be7dfa8..00000000000 --- a/js/libs/keycloak-admin-client-v2/test/test-app.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { FetchRequestAdapter } from "@microsoft/kiota-http-fetchlibrary"; -import { - type AccessTokenProvider, - AllowedHostsValidator, - BaseBearerTokenAuthenticationProvider, -} from "@microsoft/kiota-abstractions"; -import { createAdminClient } from "../src/adminClient.js"; -import type { OIDCClientRepresentation } from "../src/models/index.js"; - -// Configuration -const KEYCLOAK_URL = process.env.KEYCLOAK_URL || "http://localhost:8080"; -const REALM = process.env.KEYCLOAK_REALM || "master"; -const CLIENT_ID = process.env.KEYCLOAK_CLIENT_ID || "admin-cli"; -const USERNAME = process.env.KEYCLOAK_USERNAME || "admin"; -const PASSWORD = process.env.KEYCLOAK_PASSWORD || "admin"; - -interface TokenResponse { - access_token: string; - expires_in: number; - refresh_token?: string; - token_type: string; -} - -/** - * Simple access token provider that fetches tokens from Keycloak - */ -class KeycloakAccessTokenProvider implements AccessTokenProvider { - #accessToken: string | null = null; - #tokenExpiry: number = 0; - #allowedHostsValidator: AllowedHostsValidator; - #keycloakUrl: string; - #realm: string; - #clientId: string; - #username: string; - #password: string; - - constructor( - keycloakUrl: string, - realm: string, - clientId: string, - username: string, - password: string, - ) { - this.#keycloakUrl = keycloakUrl; - this.#realm = realm; - this.#clientId = clientId; - this.#username = username; - this.#password = password; - this.#allowedHostsValidator = new AllowedHostsValidator( - new Set([new URL(keycloakUrl).host]), - ); - } - - async getAuthorizationToken(): Promise { - // Check if we have a valid cached token - if (this.#accessToken && Date.now() < this.#tokenExpiry - 30000) { - return this.#accessToken; - } - - // Fetch a new token - const tokenUrl = `${this.#keycloakUrl}/realms/${this.#realm}/protocol/openid-connect/token`; - const response = await fetch(tokenUrl, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - grant_type: "password", - client_id: this.#clientId, - username: this.#username, - password: this.#password, - }), - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error( - `Failed to get access token: ${response.status} - ${error}`, - ); - } - - const tokenData: TokenResponse = (await response.json()) as TokenResponse; - this.#accessToken = tokenData.access_token; - this.#tokenExpiry = Date.now() + tokenData.expires_in * 1000; - - return this.#accessToken; - } - - getAllowedHostsValidator(): AllowedHostsValidator { - return this.#allowedHostsValidator; - } -} - -async function main() { - console.log("šŸ” Keycloak Admin Client v2 - Test Application\n"); - console.log(`Keycloak URL: ${KEYCLOAK_URL}`); - console.log(`Realm: ${REALM}`); - - // Create the authentication provider - const tokenProvider = new KeycloakAccessTokenProvider( - KEYCLOAK_URL, - REALM, - CLIENT_ID, - USERNAME, - PASSWORD, - ); - const authProvider = new BaseBearerTokenAuthenticationProvider(tokenProvider); - - // Create the request adapter - const adapter = new FetchRequestAdapter(authProvider); - adapter.baseUrl = KEYCLOAK_URL; - - // Create the admin client - const client = createAdminClient(adapter); - - try { - // List all clients in the realm - console.log(`šŸ“‹ Listing clients in realm '${REALM}'...\n`); - const clients = await client.admin.api.v2.realms - .byName(REALM) - .clients.get(); - - if (clients && clients.length > 0) { - console.log(`Found ${clients.length} client(s):\n`); - for (const c of clients) { - console.log(` - ${c.clientId} (${c.protocol || "unknown protocol"})`); - if (c.displayName) { - console.log(` Display Name: ${c.displayName}`); - } - if (c.description) { - console.log(` Description: ${c.description}`); - } - console.log(` Enabled: ${c.enabled ?? "unknown"}`); - console.log(); - } - } else { - console.log("No clients found."); - } - - // Create a new test client - console.log("\nšŸ†• Creating a new OIDC test client...\n"); - const newClient: OIDCClientRepresentation = { - clientId: `test-client-${Date.now()}`, - displayName: "Test Client", - description: "A test client created by the admin client v2", - enabled: true, - protocol: "openid-connect", - redirectUris: ["http://localhost:3000/callback"], - webOrigins: ["http://localhost:3000"], - }; - - await client.admin.api.v2.realms.byName(REALM).clients.post(newClient); - console.log(`āœ… Created client: ${newClient.clientId}`); - - // List clients again to verify - console.log("\nšŸ“‹ Listing clients again to verify...\n"); - const updatedClients = await client.admin.api.v2.realms - .byName(REALM) - .clients.get(); - const createdClient = updatedClients?.find( - (c) => c.clientId === newClient.clientId, - ); - - if (createdClient) { - console.log(`āœ… Verified: Client '${createdClient.clientId}' exists!`); - } else { - console.log(`āŒ Could not find the created client`); - } - - console.log("\nšŸŽ‰ Test completed successfully!"); - } catch (error) { - console.error("āŒ Error:", error); - process.exit(1); - } -} - -void main(); diff --git a/js/libs/keycloak-admin-client-v2/tsconfig.json b/js/libs/keycloak-admin-client-v2/tsconfig.json deleted file mode 100644 index 795b8bf9227..00000000000 --- a/js/libs/keycloak-admin-client-v2/tsconfig.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - // Visit https://aka.ms/tsconfig to read more about this file - "compilerOptions": { - // File Layout - "rootDir": ".", - "outDir": "./dist", - - // Environment Settings - // See also https://aka.ms/tsconfig/module - "module": "nodenext", - "target": "esnext", - "lib": ["esnext"], - "types": ["node"], - - // Other Outputs - "sourceMap": true, - "declaration": true, - "declarationMap": true, - - // Stricter Typechecking Options - // Relaxed for Kiota generated code compatibility - "noUncheckedIndexedAccess": false, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitAny": true, - "noImplicitThis": true, - "alwaysStrict": true, - - // Recommended Options - "verbatimModuleSyntax": true, - "isolatedModules": true, - "noUncheckedSideEffectImports": true, - "moduleDetection": "force", - "skipLibCheck": true - }, - "include": ["src/**/*", "test/**/*", "scripts/**/*"] -} diff --git a/js/libs/keycloak-admin-client/.gitignore b/js/libs/keycloak-admin-client/.gitignore index c3af857904e..4012caca7ba 100644 --- a/js/libs/keycloak-admin-client/.gitignore +++ b/js/libs/keycloak-admin-client/.gitignore @@ -1 +1,2 @@ lib/ +generated/ diff --git a/js/libs/keycloak-admin-client-v2/openapi.yaml b/js/libs/keycloak-admin-client/api.yml similarity index 92% rename from js/libs/keycloak-admin-client-v2/openapi.yaml rename to js/libs/keycloak-admin-client/api.yml index 5b2577e237d..bf4cf5e01a9 100644 --- a/js/libs/keycloak-admin-client-v2/openapi.yaml +++ b/js/libs/keycloak-admin-client/api.yml @@ -107,7 +107,7 @@ tags: - name: Clients (v2) x-smallrye-profile-admin: "" paths: - /admin/api/v2/realms/{name}/clients: + /admin/api/{realmName}/clients/{version}: get: summary: Get all clients description: Returns a list of all clients in the realm @@ -154,12 +154,18 @@ paths: application/json: schema: {} parameters: - - name: name + - name: realmName in: path required: true schema: type: string - /admin/api/v2/realms/{name}/clients/{id}: + - name: version + in: path + required: true + schema: + type: string + pattern: v\d+ + /admin/api/{realmName}/clients/{version}/{id}: get: tags: - Clients (v2) @@ -204,7 +210,10 @@ paths: - Clients (v2) requestBody: content: - application/merge-patch+json: {} + application/merge-patch+json: + schema: + type: object + additionalProperties: true required: true responses: "200": @@ -227,11 +236,17 @@ paths: "204": description: No Content parameters: - - name: name + - name: realmName in: path required: true schema: type: string + - name: version + in: path + required: true + schema: + type: string + pattern: v\d+ - name: id in: path required: true diff --git a/js/libs/keycloak-admin-client/openapitools.json b/js/libs/keycloak-admin-client/openapitools.json new file mode 100644 index 00000000000..bd8d09d6317 --- /dev/null +++ b/js/libs/keycloak-admin-client/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.18.0" + } +} diff --git a/js/libs/keycloak-admin-client/package.json b/js/libs/keycloak-admin-client/package.json index 15f338092c9..ee0afd13cfa 100644 --- a/js/libs/keycloak-admin-client/package.json +++ b/js/libs/keycloak-admin-client/package.json @@ -18,11 +18,15 @@ "build": "wireit", "lint": "wireit", "test": "wireit", + "generate:openapi": "wireit", "prepublishOnly": "pnpm build" }, "wireit": { "build": { "command": "tsc --pretty", + "dependencies": [ + "generate:openapi" + ], "files": [ "src", "tsconfig.json" @@ -31,6 +35,16 @@ "lib" ] }, + "generate:openapi": { + "command": "pnpm dlx @openapitools/openapi-generator-cli generate -i api.yml -g typescript-fetch -o src/generated --additional-properties=supportsES6=true,typescriptThreePlus=true,importFileExtension=.js && node scripts/fix-generated-runtime.mjs", + "files": [ + "api.yml", + "scripts/fix-generated-runtime.mjs" + ], + "output": [ + "src/generated" + ] + }, "lint": { "command": "eslint ." }, diff --git a/js/libs/keycloak-admin-client/scripts/fix-generated-runtime.mjs b/js/libs/keycloak-admin-client/scripts/fix-generated-runtime.mjs new file mode 100644 index 00000000000..c875738b4c9 --- /dev/null +++ b/js/libs/keycloak-admin-client/scripts/fix-generated-runtime.mjs @@ -0,0 +1,88 @@ +/** + * Post-processing script to fix OpenAPI Generator TypeScript output for Node.js compatibility. + * + * Fixes: + * 1. Adds missing type definitions for Node.js (RequestCredentials) + * 2. Fixes type compatibility issues in runtime.ts + * + * Note: Import file extensions are handled by the generator's importFileExtension option. + */ + +import { readFileSync, writeFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const generatedDir = join(__dirname, '..', 'src', 'generated'); + +/** + * Fix runtime.ts for Node.js compatibility + */ +function fixRuntime(content) { + // Add type definitions at the top of the file (after the eslint-disable comments) + const typeDefinitions = ` +// Node.js compatibility types +type RequestCredentials = 'include' | 'omit' | 'same-origin'; +`; + + // Replace WindowOrWorkerGlobalScope['fetch'] with typeof fetch + let fixed = content.replace( + /export type FetchAPI = WindowOrWorkerGlobalScope\['fetch'\];/g, + 'export type FetchAPI = typeof fetch;' + ); + + // Add type definitions after the eslint-disable comment block + if (!fixed.includes('// Node.js compatibility types')) { + fixed = fixed.replace( + /(\* Do not edit the class manually\.\s*\*\/)\s*\n/, + '$1\n' + typeDefinitions + '\n' + ); + } + + // Fix 'response' possibly undefined errors + fixed = fixed.replace( + /throw new ResponseError\(response, 'Response returned an error code'\);/g, + "throw new ResponseError(response!, 'Response returned an error code');" + ); + + fixed = fixed.replace( + /response: response\.clone\(\),/g, + 'response: response!.clone(),' + ); + + // Fix fetchApi type compatibility in middleware contexts + fixed = fixed.replace( + /fetch: this\.fetchApi,/g, + 'fetch: this.fetchApi as FetchAPI,' + ); + + return fixed; +} + +function main() { + console.log('Fixing OpenAPI Generator output for Node.js compatibility...\n'); + + const runtimePath = join(generatedDir, 'runtime.ts'); + + try { + const content = readFileSync(runtimePath, 'utf-8'); + const fixed = fixRuntime(content); + + if (content !== fixed) { + writeFileSync(runtimePath, fixed); + console.log(` Fixed: ${runtimePath}`); + console.log('\nāœ… Applied runtime fixes'); + } else { + console.log('āœ… No fixes needed'); + } + } catch (error) { + if (error.code === 'ENOENT') { + console.log('āš ļø runtime.ts not found, skipping'); + } else { + throw error; + } + } +} + +main(); diff --git a/js/libs/keycloak-admin-client/src/index.ts b/js/libs/keycloak-admin-client/src/index.ts index fbed14bac0c..190ffc2b1b1 100644 --- a/js/libs/keycloak-admin-client/src/index.ts +++ b/js/libs/keycloak-admin-client/src/index.ts @@ -8,3 +8,20 @@ export type { NetworkErrorOptions } from "./utils/fetchWithError.js"; export type { default as OrganizationInvitationRepresentation } from "./defs/organizationInvitationRepresentation.js"; export { OrganizationInvitationStatus } from "./defs/organizationInvitationRepresentation.js"; + +// V2 API types and classes +export { + ClientsV2Api, + createClientsV2Api, +} from "./resources/clientsV2.js"; +export type { + OIDCClientRepresentation, + SAMLClientRepresentation, + ClientRepresentationV2, + AdminApiRealmNameClientsVersionGetRequest, + AdminApiRealmNameClientsVersionIdDeleteRequest, + AdminApiRealmNameClientsVersionIdGetRequest, + AdminApiRealmNameClientsVersionIdPatchRequest, + AdminApiRealmNameClientsVersionIdPutRequest, + AdminApiRealmNameClientsVersionPostRequest, +} from "./resources/clientsV2.js"; diff --git a/js/libs/keycloak-admin-client/src/resources/clients.ts b/js/libs/keycloak-admin-client/src/resources/clients.ts index a8e3c77c0b8..14f12bd6efa 100644 --- a/js/libs/keycloak-admin-client/src/resources/clients.ts +++ b/js/libs/keycloak-admin-client/src/resources/clients.ts @@ -19,6 +19,7 @@ import type ScopeRepresentation from "../defs/scopeRepresentation.js"; import type UserRepresentation from "../defs/userRepresentation.js"; import type UserSessionRepresentation from "../defs/userSessionRepresentation.js"; import Resource from "./resource.js"; +import { ClientsV2 } from "./clientsV2.js"; export interface PaginatedQuery { first?: number; @@ -53,6 +54,11 @@ export interface PolicyQuery extends PaginatedQuery { } export class Clients extends Resource<{ realm?: string }> { + /** + * Clients v2 API - New versioned API with OpenAPI-generated client. + */ + public v2: ClientsV2; + public find = this.makeRequest({ method: "GET", }); @@ -1055,6 +1061,9 @@ export class Clients extends Resource<{ realm?: string }> { }), getBaseUrl: () => client.baseUrl, }); + + // Initialize v2 API + this.v2 = new ClientsV2(client); } /** diff --git a/js/libs/keycloak-admin-client/src/resources/clientsV2.ts b/js/libs/keycloak-admin-client/src/resources/clientsV2.ts new file mode 100644 index 00000000000..0ef2ad67fd6 --- /dev/null +++ b/js/libs/keycloak-admin-client/src/resources/clientsV2.ts @@ -0,0 +1,66 @@ +import type { KeycloakAdminClient } from "../client.js"; +import { Configuration, ClientsV2Api } from "../generated/index.js"; + +// Re-export types for convenience +export { ClientsV2Api } from "../generated/index.js"; +export type { + AdminApiRealmNameClientsVersionGetRequest, + AdminApiRealmNameClientsVersionIdDeleteRequest, + AdminApiRealmNameClientsVersionIdGetRequest, + AdminApiRealmNameClientsVersionIdPatchRequest, + AdminApiRealmNameClientsVersionIdPutRequest, + AdminApiRealmNameClientsVersionPostRequest, +} from "../generated/apis/ClientsV2Api.js"; +export type { + OIDCClientRepresentation, + SAMLClientRepresentation, + AdminApiRealmNameClientsVersionGet200ResponseInner as ClientRepresentationV2, +} from "../generated/models/index.js"; + +/** + * Creates a ClientsV2Api instance configured with the KeycloakAdminClient's + * base URL and access token. + */ +export async function createClientsV2Api( + client: KeycloakAdminClient, +): Promise { + const accessToken = await client.getAccessToken(); + + const config = new Configuration({ + basePath: client.baseUrl, + headers: { + ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), + }, + }); + + return new ClientsV2Api(config); +} + +/** + * Clients v2 API resource. + * Provides access to the OpenAPI-generated ClientsV2Api. + */ +export class ClientsV2 { + #client: KeycloakAdminClient; + + constructor(client: KeycloakAdminClient) { + this.#client = client; + } + + /** + * Get the ClientsV2Api instance configured with the current access token. + * Call this method to get an API instance, then use its methods directly. + * + * @example + * ```typescript + * const api = await client.clients.v2.api(); + * const clients = await api.adminApiRealmNameClientsVersionGet({ + * realmName: "master", + * version: "v2", + * }); + * ``` + */ + async api(): Promise { + return createClientsV2Api(this.#client); + } +} diff --git a/js/libs/keycloak-admin-client/test/clientsV2.spec.ts b/js/libs/keycloak-admin-client/test/clientsV2.spec.ts new file mode 100644 index 00000000000..a15e7753d45 --- /dev/null +++ b/js/libs/keycloak-admin-client/test/clientsV2.spec.ts @@ -0,0 +1,211 @@ +import { faker } from "@faker-js/faker"; +import * as chai from "chai"; +import { KeycloakAdminClient } from "../src/client.js"; +import type { ClientsV2Api } from "../src/resources/clientsV2.js"; +import type { OIDCClientRepresentation } from "../src/generated/models/index.js"; +import { credentials } from "./constants.js"; + +const expect = chai.expect; + +describe("Clients V2 API", () => { + let kcAdminClient: KeycloakAdminClient; + let clientsV2Api: ClientsV2Api; + let currentClientId: string; + + before(async () => { + kcAdminClient = new KeycloakAdminClient(); + await kcAdminClient.auth(credentials); + + // Get the v2 API instance + clientsV2Api = await kcAdminClient.clients.v2.api(); + + // Create a client for testing using v2 API + currentClientId = faker.internet.username(); + await clientsV2Api.adminApiRealmNameClientsVersionPost({ + realmName: kcAdminClient.realmName, + version: "v2", + adminApiRealmNameClientsVersionGet200ResponseInner: { + clientId: currentClientId, + protocol: "openid-connect", + enabled: true, + }, + }); + }); + + after(async () => { + // Delete the test client + if (currentClientId) { + await clientsV2Api.adminApiRealmNameClientsVersionIdDelete({ + realmName: kcAdminClient.realmName, + version: "v2", + id: currentClientId, + }); + } + }); + + it("should list clients", async () => { + const clients = await clientsV2Api.adminApiRealmNameClientsVersionGet({ + realmName: kcAdminClient.realmName, + version: "v2", + }); + + expect(clients).to.be.ok; + expect(clients).to.be.an("array"); + expect(clients.length).to.be.greaterThan(0); + + // Verify our test client is in the list + const testClient = clients.find( + (c) => (c as OIDCClientRepresentation).clientId === currentClientId, + ); + expect(testClient).to.be.ok; + }); + + it("should get a single client by clientId", async () => { + const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ + realmName: kcAdminClient.realmName, + version: "v2", + id: currentClientId, + }); + + expect(client).to.be.ok; + expect((client as OIDCClientRepresentation).clientId).to.equal( + currentClientId, + ); + }); + + it("should update a client with PUT", async () => { + const updatedDescription = "Updated via V2 API test"; + + await clientsV2Api.adminApiRealmNameClientsVersionIdPut({ + realmName: kcAdminClient.realmName, + version: "v2", + id: currentClientId, + adminApiRealmNameClientsVersionGet200ResponseInner: { + clientId: currentClientId, + protocol: "openid-connect", + description: updatedDescription, + }, + }); + + const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ + realmName: kcAdminClient.realmName, + version: "v2", + id: currentClientId, + }); + + expect((client as OIDCClientRepresentation).description).to.equal( + updatedDescription, + ); + }); + + it("should patch a client", async () => { + const patchedDisplayName = "Patched Display Name"; + + const patchedClient = + await clientsV2Api.adminApiRealmNameClientsVersionIdPatch({ + realmName: kcAdminClient.realmName, + version: "v2", + id: currentClientId, + requestBody: { + displayName: patchedDisplayName, + }, + }); + + expect((patchedClient as OIDCClientRepresentation).displayName).to.equal( + patchedDisplayName, + ); + + // Verify the change persisted + const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ + realmName: kcAdminClient.realmName, + version: "v2", + id: currentClientId, + }); + + expect((client as OIDCClientRepresentation).displayName).to.equal( + patchedDisplayName, + ); + }); + + it("should create and delete a client", async () => { + const clientId = faker.internet.username(); + + // Create a new client using v2 API + await clientsV2Api.adminApiRealmNameClientsVersionPost({ + realmName: kcAdminClient.realmName, + version: "v2", + adminApiRealmNameClientsVersionGet200ResponseInner: { + clientId, + protocol: "openid-connect", + enabled: true, + description: "Test client for deletion", + }, + }); + + // Verify we can get it via v2 API + const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ + realmName: kcAdminClient.realmName, + version: "v2", + id: clientId, + }); + expect((client as OIDCClientRepresentation).clientId).to.equal(clientId); + + // Delete the client using v2 API + await clientsV2Api.adminApiRealmNameClientsVersionIdDelete({ + realmName: kcAdminClient.realmName, + version: "v2", + id: clientId, + }); + + // Verify it's deleted by checking it's no longer in the list + const clients = await clientsV2Api.adminApiRealmNameClientsVersionGet({ + realmName: kcAdminClient.realmName, + version: "v2", + }); + + const deletedClient = clients.find( + (c) => (c as OIDCClientRepresentation).clientId === clientId, + ); + expect(deletedClient).to.be.undefined; + }); + + it("should create an OIDC client with full configuration", async () => { + const clientId = `full-config-${faker.internet.username()}`; + + await clientsV2Api.adminApiRealmNameClientsVersionPost({ + realmName: kcAdminClient.realmName, + version: "v2", + adminApiRealmNameClientsVersionGet200ResponseInner: { + clientId, + protocol: "openid-connect", + enabled: true, + displayName: "Full Config Test Client", + description: "A client with full OIDC configuration", + redirectUris: new Set(["http://localhost:3000/callback"]), + webOrigins: new Set(["http://localhost:3000"]), + }, + }); + + // Get via v2 API and verify + const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ + realmName: kcAdminClient.realmName, + version: "v2", + id: clientId, + }); + + expect(client).to.be.ok; + expect((client as OIDCClientRepresentation).displayName).to.equal( + "Full Config Test Client", + ); + expect((client as OIDCClientRepresentation).protocol).to.equal( + "openid-connect", + ); + + // Cleanup + await clientsV2Api.adminApiRealmNameClientsVersionIdDelete({ + realmName: kcAdminClient.realmName, + version: "v2", + id: clientId, + }); + }); +}); diff --git a/js/package.json b/js/package.json index f4ded39c3f8..e0d205368dc 100644 --- a/js/package.json +++ b/js/package.json @@ -13,7 +13,6 @@ "./apps/account-ui:build", "./apps/admin-ui:build", "./libs/keycloak-admin-client:build", - "./libs/keycloak-admin-client-v2:generate:file", "./libs/ui-shared:build", "./themes-vendor:build" ] diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 9d0276b5537..a95ff807d8e 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -363,31 +363,6 @@ importers: specifier: ^10.9.2 version: 10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3) - libs/keycloak-admin-client-v2: - dependencies: - '@microsoft/kiota-abstractions': - specifier: 1.0.0-preview.99 - version: 1.0.0-preview.99 - '@microsoft/kiota-bundle': - specifier: ^1.0.0-preview.99 - version: 1.0.0-preview.99 - '@microsoft/kiota-http-fetchlibrary': - specifier: ^1.0.0-preview.99 - version: 1.0.0-preview.99 - devDependencies: - '@microsoft/kiota': - specifier: ^1.29.0 - version: 1.29.0 - '@types/node': - specifier: ^25.0.8 - version: 25.0.8 - tsx: - specifier: ^4.21.0 - version: 4.21.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - libs/ui-shared: dependencies: '@keycloak/keycloak-admin-client': @@ -1071,30 +1046,6 @@ packages: resolution: {integrity: sha512-LhKytJM5ZJkbHQVfW/3o747rZUNs/MGg6j/wt/9qwwqEOfvUDTYXXxIBuMgrRXhJ528p41iyz4zjBVHZU74Odg==} hasBin: true - '@microsoft/kiota-abstractions@1.0.0-preview.99': - resolution: {integrity: sha512-6qrlwGCO3DbvpA7tszqk7JNQPFPDg8gv5NGMTziwdtHqaQrUALKusm3vdJGPVGrbNiZL6/lBaY5NSUvLtlhRCg==} - - '@microsoft/kiota-bundle@1.0.0-preview.99': - resolution: {integrity: sha512-AxvO+z6UgWMAT2NfXN36CMhAUm/39tUQt8o32axeEJDS/EvZINDAPstbQV+zK7bJRC8kCqUHhCE/fuHX2dXA+g==} - - '@microsoft/kiota-http-fetchlibrary@1.0.0-preview.99': - resolution: {integrity: sha512-lIruiYf8L7DPG0Fm92QN5YK4zx4sh76EtTONUAqBCxt5AtEJ7KQceBajaXQsjfcndUAp9LmDVdqR44o8sc9/mA==} - - '@microsoft/kiota-serialization-form@1.0.0-preview.99': - resolution: {integrity: sha512-BdXxqNfy+5yWTyxpBguqJYy6E9RRotrDClRcUO89okFcR5N6s6xenjsvGw++q9KWNi7qcHrqaG8xlRz1TLk81Q==} - - '@microsoft/kiota-serialization-json@1.0.0-preview.99': - resolution: {integrity: sha512-kDmMYmB7XkRprlLviEynRPDkE45JDxqiuUopfBRMvNK4qhzFiJTXGLihZ2FG6fdVu9LbV3/W0QxbeGP35kDK6A==} - - '@microsoft/kiota-serialization-multipart@1.0.0-preview.99': - resolution: {integrity: sha512-cfviCAVTlZPD+MUgdTIgsX/IfHND+gKQhem3vJeo7l5Qqz2rvvVFXOHwECI19umO9Un1QFKe1V5wg+M+aj90+g==} - - '@microsoft/kiota-serialization-text@1.0.0-preview.99': - resolution: {integrity: sha512-gcBffQRI1soHVAiS4YjfMr3SQ9fC8xVUGMfarTTszU4PjpxyFOknaWoFdt/0rvMeavI9jOtbyvDKAf77cZyYIQ==} - - '@microsoft/kiota@1.29.0': - resolution: {integrity: sha512-qqIlTz48OJ5ZMRoTA/uQA70B7ltS4lPSs9atG5PUn+dKZcgXny3LzQPe12B1LsKoBJYbwhaU3fD8/C1DsLW6Cw==} - '@microsoft/tsdoc-config@0.17.1': resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} @@ -1463,9 +1414,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@std-uritemplate/std-uritemplate@2.0.8': - resolution: {integrity: sha512-8oj7jKksoTRxjdPkWKX9FyOKDS8ORBm+ZfTSHm0f3eYha8cI0O1d57gyduOIAX7Y2uUukNDNlxlvGb+FFkE7yQ==} - '@swc/core-darwin-arm64@1.15.8': resolution: {integrity: sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==} engines: {node: '>=10'} @@ -1908,10 +1856,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - adm-zip@0.5.16: - resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} - engines: {node: '>=12.0'} - agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -3429,9 +3373,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - original-fs@1.2.0: - resolution: {integrity: sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==} - own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -3996,9 +3937,6 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyduration@3.4.1: - resolution: {integrity: sha512-NemFoamVYn7TmtwZKZ3OiliM9fZkr6EWiTM+wKknco6POSy2gS689xx/pXip0JYp40HXpUw6k65CUYHWYUXdaA==} - tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -4318,10 +4256,6 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} - vscode-jsonrpc@8.2.1: - resolution: {integrity: sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==} - engines: {node: '>=14.0.0'} - vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} @@ -5137,55 +5071,6 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@microsoft/kiota-abstractions@1.0.0-preview.99': - dependencies: - '@opentelemetry/api': 1.9.0 - '@std-uritemplate/std-uritemplate': 2.0.8 - tinyduration: 3.4.1 - tslib: 2.8.1 - - '@microsoft/kiota-bundle@1.0.0-preview.99': - dependencies: - '@microsoft/kiota-abstractions': 1.0.0-preview.99 - '@microsoft/kiota-http-fetchlibrary': 1.0.0-preview.99 - '@microsoft/kiota-serialization-form': 1.0.0-preview.99 - '@microsoft/kiota-serialization-json': 1.0.0-preview.99 - '@microsoft/kiota-serialization-multipart': 1.0.0-preview.99 - '@microsoft/kiota-serialization-text': 1.0.0-preview.99 - - '@microsoft/kiota-http-fetchlibrary@1.0.0-preview.99': - dependencies: - '@microsoft/kiota-abstractions': 1.0.0-preview.99 - '@opentelemetry/api': 1.9.0 - tslib: 2.8.1 - - '@microsoft/kiota-serialization-form@1.0.0-preview.99': - dependencies: - '@microsoft/kiota-abstractions': 1.0.0-preview.99 - tslib: 2.8.1 - - '@microsoft/kiota-serialization-json@1.0.0-preview.99': - dependencies: - '@microsoft/kiota-abstractions': 1.0.0-preview.99 - tslib: 2.8.1 - - '@microsoft/kiota-serialization-multipart@1.0.0-preview.99': - dependencies: - '@microsoft/kiota-abstractions': 1.0.0-preview.99 - tslib: 2.8.1 - - '@microsoft/kiota-serialization-text@1.0.0-preview.99': - dependencies: - '@microsoft/kiota-abstractions': 1.0.0-preview.99 - tslib: 2.8.1 - - '@microsoft/kiota@1.29.0': - dependencies: - adm-zip: 0.5.16 - original-fs: 1.2.0 - uuid: 13.0.0 - vscode-jsonrpc: 8.2.1 - '@microsoft/tsdoc-config@0.17.1': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -5271,7 +5156,8 @@ snapshots: dependencies: '@octokit/openapi-types': 27.0.0 - '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api@1.9.0': + optional: true '@patternfly/patternfly@4.224.5': {} @@ -5563,8 +5449,6 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@std-uritemplate/std-uritemplate@2.0.8': {} - '@swc/core-darwin-arm64@1.15.8': optional: true @@ -6070,8 +5954,6 @@ snapshots: acorn@8.15.0: {} - adm-zip@0.5.16: {} - agent-base@7.1.4: {} ajv-draft-04@1.0.0(ajv@8.13.0): @@ -6996,6 +6878,7 @@ snapshots: get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 + optional: true glob-parent@5.1.2: dependencies: @@ -7743,8 +7626,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - original-fs@1.2.0: {} - own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -8060,7 +7941,8 @@ snapshots: resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} + resolve-pkg-maps@1.0.0: + optional: true resolve@1.22.10: dependencies: @@ -8432,8 +8314,6 @@ snapshots: tinybench@2.9.0: {} - tinyduration@3.4.1: {} - tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -8517,6 +8397,7 @@ snapshots: get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3 + optional: true type-check@0.4.0: dependencies: @@ -8769,8 +8650,6 @@ snapshots: void-elements@3.1.0: {} - vscode-jsonrpc@8.2.1: {} - vscode-uri@3.1.0: {} w3c-xmlserializer@5.0.0: From a411fbb5260f2205deacb72051559eb1850103c5 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 20 Jan 2026 19:11:51 +0100 Subject: [PATCH 3/5] changed back to use kiota as it offers a nicer fluent api Signed-off-by: Erik Jan de Wit --- .../keycloak-admin-client/openapitools.json | 7 - js/libs/keycloak-admin-client/package.json | 11 +- .../scripts/fix-generated-runtime.mjs | 88 ---------- js/libs/keycloak-admin-client/src/index.ts | 16 +- .../src/resources/clientsV2.ts | 103 ++++++++---- .../test/clientsV2.spec.ts | 154 ++++++------------ js/pnpm-lock.yaml | 82 +++++++++- 7 files changed, 217 insertions(+), 244 deletions(-) delete mode 100644 js/libs/keycloak-admin-client/openapitools.json delete mode 100644 js/libs/keycloak-admin-client/scripts/fix-generated-runtime.mjs diff --git a/js/libs/keycloak-admin-client/openapitools.json b/js/libs/keycloak-admin-client/openapitools.json deleted file mode 100644 index bd8d09d6317..00000000000 --- a/js/libs/keycloak-admin-client/openapitools.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", - "spaces": 2, - "generator-cli": { - "version": "7.18.0" - } -} diff --git a/js/libs/keycloak-admin-client/package.json b/js/libs/keycloak-admin-client/package.json index ee0afd13cfa..b0e3ff5c2a7 100644 --- a/js/libs/keycloak-admin-client/package.json +++ b/js/libs/keycloak-admin-client/package.json @@ -36,10 +36,9 @@ ] }, "generate:openapi": { - "command": "pnpm dlx @openapitools/openapi-generator-cli generate -i api.yml -g typescript-fetch -o src/generated --additional-properties=supportsES6=true,typescriptThreePlus=true,importFileExtension=.js && node scripts/fix-generated-runtime.mjs", + "command": "kiota generate -l TypeScript -d api.yml -o src/generated -c AdminClient -n ApiSdk", "files": [ - "api.yml", - "scripts/fix-generated-runtime.mjs" + "api.yml" ], "output": [ "src/generated" @@ -53,6 +52,12 @@ } }, "dependencies": { + "@microsoft/kiota-abstractions": "^1.0.0-preview.86", + "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.80", + "@microsoft/kiota-serialization-form": "^1.0.0-preview.74", + "@microsoft/kiota-serialization-json": "^1.0.0-preview.86", + "@microsoft/kiota-serialization-multipart": "^1.0.0-preview.67", + "@microsoft/kiota-serialization-text": "^1.0.0-preview.79", "camelize-ts": "^3.0.0", "url-template": "^3.1.1" }, diff --git a/js/libs/keycloak-admin-client/scripts/fix-generated-runtime.mjs b/js/libs/keycloak-admin-client/scripts/fix-generated-runtime.mjs deleted file mode 100644 index c875738b4c9..00000000000 --- a/js/libs/keycloak-admin-client/scripts/fix-generated-runtime.mjs +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Post-processing script to fix OpenAPI Generator TypeScript output for Node.js compatibility. - * - * Fixes: - * 1. Adds missing type definitions for Node.js (RequestCredentials) - * 2. Fixes type compatibility issues in runtime.ts - * - * Note: Import file extensions are handled by the generator's importFileExtension option. - */ - -import { readFileSync, writeFileSync } from 'fs'; -import { join, dirname } from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const generatedDir = join(__dirname, '..', 'src', 'generated'); - -/** - * Fix runtime.ts for Node.js compatibility - */ -function fixRuntime(content) { - // Add type definitions at the top of the file (after the eslint-disable comments) - const typeDefinitions = ` -// Node.js compatibility types -type RequestCredentials = 'include' | 'omit' | 'same-origin'; -`; - - // Replace WindowOrWorkerGlobalScope['fetch'] with typeof fetch - let fixed = content.replace( - /export type FetchAPI = WindowOrWorkerGlobalScope\['fetch'\];/g, - 'export type FetchAPI = typeof fetch;' - ); - - // Add type definitions after the eslint-disable comment block - if (!fixed.includes('// Node.js compatibility types')) { - fixed = fixed.replace( - /(\* Do not edit the class manually\.\s*\*\/)\s*\n/, - '$1\n' + typeDefinitions + '\n' - ); - } - - // Fix 'response' possibly undefined errors - fixed = fixed.replace( - /throw new ResponseError\(response, 'Response returned an error code'\);/g, - "throw new ResponseError(response!, 'Response returned an error code');" - ); - - fixed = fixed.replace( - /response: response\.clone\(\),/g, - 'response: response!.clone(),' - ); - - // Fix fetchApi type compatibility in middleware contexts - fixed = fixed.replace( - /fetch: this\.fetchApi,/g, - 'fetch: this.fetchApi as FetchAPI,' - ); - - return fixed; -} - -function main() { - console.log('Fixing OpenAPI Generator output for Node.js compatibility...\n'); - - const runtimePath = join(generatedDir, 'runtime.ts'); - - try { - const content = readFileSync(runtimePath, 'utf-8'); - const fixed = fixRuntime(content); - - if (content !== fixed) { - writeFileSync(runtimePath, fixed); - console.log(` Fixed: ${runtimePath}`); - console.log('\nāœ… Applied runtime fixes'); - } else { - console.log('āœ… No fixes needed'); - } - } catch (error) { - if (error.code === 'ENOENT') { - console.log('āš ļø runtime.ts not found, skipping'); - } else { - throw error; - } - } -} - -main(); diff --git a/js/libs/keycloak-admin-client/src/index.ts b/js/libs/keycloak-admin-client/src/index.ts index 190ffc2b1b1..7a039422919 100644 --- a/js/libs/keycloak-admin-client/src/index.ts +++ b/js/libs/keycloak-admin-client/src/index.ts @@ -9,19 +9,13 @@ export type { NetworkErrorOptions } from "./utils/fetchWithError.js"; export type { default as OrganizationInvitationRepresentation } from "./defs/organizationInvitationRepresentation.js"; export { OrganizationInvitationStatus } from "./defs/organizationInvitationRepresentation.js"; -// V2 API types and classes -export { - ClientsV2Api, - createClientsV2Api, -} from "./resources/clientsV2.js"; +// V2 API types and classes (Kiota-generated) +export { createKiotaAdminClient } from "./resources/clientsV2.js"; export type { OIDCClientRepresentation, SAMLClientRepresentation, ClientRepresentationV2, - AdminApiRealmNameClientsVersionGetRequest, - AdminApiRealmNameClientsVersionIdDeleteRequest, - AdminApiRealmNameClientsVersionIdGetRequest, - AdminApiRealmNameClientsVersionIdPatchRequest, - AdminApiRealmNameClientsVersionIdPutRequest, - AdminApiRealmNameClientsVersionPostRequest, } from "./resources/clientsV2.js"; + +// Re-export Kiota AdminClient type +export type { AdminClient } from "./generated/adminClient.js"; diff --git a/js/libs/keycloak-admin-client/src/resources/clientsV2.ts b/js/libs/keycloak-admin-client/src/resources/clientsV2.ts index 0ef2ad67fd6..1ad3e755703 100644 --- a/js/libs/keycloak-admin-client/src/resources/clientsV2.ts +++ b/js/libs/keycloak-admin-client/src/resources/clientsV2.ts @@ -1,44 +1,66 @@ +import { + type AuthenticationProvider, + type RequestInformation, +} from "@microsoft/kiota-abstractions"; +import { FetchRequestAdapter } from "@microsoft/kiota-http-fetchlibrary"; + import type { KeycloakAdminClient } from "../client.js"; -import { Configuration, ClientsV2Api } from "../generated/index.js"; +import { + createAdminClient, + type AdminClient, +} from "../generated/adminClient.js"; // Re-export types for convenience -export { ClientsV2Api } from "../generated/index.js"; -export type { - AdminApiRealmNameClientsVersionGetRequest, - AdminApiRealmNameClientsVersionIdDeleteRequest, - AdminApiRealmNameClientsVersionIdGetRequest, - AdminApiRealmNameClientsVersionIdPatchRequest, - AdminApiRealmNameClientsVersionIdPutRequest, - AdminApiRealmNameClientsVersionPostRequest, -} from "../generated/apis/ClientsV2Api.js"; export type { OIDCClientRepresentation, SAMLClientRepresentation, - AdminApiRealmNameClientsVersionGet200ResponseInner as ClientRepresentationV2, } from "../generated/models/index.js"; +export type ClientRepresentationV2 = + | import("../generated/models/index.js").OIDCClientRepresentation + | import("../generated/models/index.js").SAMLClientRepresentation; + /** - * Creates a ClientsV2Api instance configured with the KeycloakAdminClient's + * Authentication provider for Keycloak Admin Client that adds Bearer token to requests. + */ +class KeycloakAuthProvider implements AuthenticationProvider { + #getAccessToken: () => Promise; + + constructor(getAccessToken: () => Promise) { + this.#getAccessToken = getAccessToken; + } + + async authenticateRequest(request: RequestInformation): Promise { + const token = await this.#getAccessToken(); + if (token) { + request.headers.add("Authorization", `Bearer ${token}`); + } + } +} + +/** + * Creates a Kiota AdminClient instance configured with the KeycloakAdminClient's * base URL and access token. */ -export async function createClientsV2Api( +export async function createKiotaAdminClient( client: KeycloakAdminClient, -): Promise { - const accessToken = await client.getAccessToken(); +): Promise { + const authProvider = new KeycloakAuthProvider(() => client.getAccessToken()); + const adapter = new FetchRequestAdapter(authProvider); + adapter.baseUrl = client.baseUrl; - const config = new Configuration({ - basePath: client.baseUrl, - headers: { - ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), - }, - }); - - return new ClientsV2Api(config); + return createAdminClient(adapter); } /** * Clients v2 API resource. - * Provides access to the OpenAPI-generated ClientsV2Api. + * Provides access to the Kiota-generated AdminClient with fluent API. + * + * @example + * ```typescript + * const api = await client.clients.v2.api(); + * const clients = await api.admin.api.byRealmName("master").clients.byVersion("v2").get(); + * ``` */ export class ClientsV2 { #client: KeycloakAdminClient; @@ -48,19 +70,38 @@ export class ClientsV2 { } /** - * Get the ClientsV2Api instance configured with the current access token. - * Call this method to get an API instance, then use its methods directly. + * Get the AdminClient instance configured with the current access token. + * Call this method to get an API instance, then use its fluent methods. * * @example * ```typescript * const api = await client.clients.v2.api(); - * const clients = await api.adminApiRealmNameClientsVersionGet({ - * realmName: "master", - * version: "v2", + * + * // List all clients + * const clients = await api.admin.api.byRealmName("master").clients.byVersion("v2").get(); + * + * // Get a single client + * const client = await api.admin.api.byRealmName("master").clients.byVersion("v2").byId("my-client").get(); + * + * // Create a client + * await api.admin.api.byRealmName("master").clients.byVersion("v2").post({ + * clientId: "my-client", + * protocol: "openid-connect", + * enabled: true, * }); + * + * // Update a client + * await api.admin.api.byRealmName("master").clients.byVersion("v2").byId("my-client").put({ + * clientId: "my-client", + * protocol: "openid-connect", + * description: "Updated description", + * }); + * + * // Delete a client + * await api.admin.api.byRealmName("master").clients.byVersion("v2").byId("my-client").delete(); * ``` */ - async api(): Promise { - return createClientsV2Api(this.#client); + async api(): Promise { + return createKiotaAdminClient(this.#client); } } diff --git a/js/libs/keycloak-admin-client/test/clientsV2.spec.ts b/js/libs/keycloak-admin-client/test/clientsV2.spec.ts index a15e7753d45..ed895c3bbb0 100644 --- a/js/libs/keycloak-admin-client/test/clientsV2.spec.ts +++ b/js/libs/keycloak-admin-client/test/clientsV2.spec.ts @@ -1,7 +1,7 @@ import { faker } from "@faker-js/faker"; import * as chai from "chai"; import { KeycloakAdminClient } from "../src/client.js"; -import type { ClientsV2Api } from "../src/resources/clientsV2.js"; +import type { AdminClient } from "../src/generated/adminClient.js"; import type { OIDCClientRepresentation } from "../src/generated/models/index.js"; import { credentials } from "./constants.js"; @@ -9,63 +9,54 @@ const expect = chai.expect; describe("Clients V2 API", () => { let kcAdminClient: KeycloakAdminClient; - let clientsV2Api: ClientsV2Api; + let adminClient: AdminClient; let currentClientId: string; + // Helper to get the clients endpoint for the realm + const getClientsEndpoint = () => + adminClient.admin.api + .byRealmName(kcAdminClient.realmName) + .clients.byVersion("v2"); + before(async () => { kcAdminClient = new KeycloakAdminClient(); await kcAdminClient.auth(credentials); - // Get the v2 API instance - clientsV2Api = await kcAdminClient.clients.v2.api(); + // Get the v2 API instance (Kiota AdminClient) + adminClient = await kcAdminClient.clients.v2.api(); // Create a client for testing using v2 API currentClientId = faker.internet.username(); - await clientsV2Api.adminApiRealmNameClientsVersionPost({ - realmName: kcAdminClient.realmName, - version: "v2", - adminApiRealmNameClientsVersionGet200ResponseInner: { - clientId: currentClientId, - protocol: "openid-connect", - enabled: true, - }, + await getClientsEndpoint().post({ + clientId: currentClientId, + protocol: "openid-connect", + enabled: true, }); }); after(async () => { // Delete the test client if (currentClientId) { - await clientsV2Api.adminApiRealmNameClientsVersionIdDelete({ - realmName: kcAdminClient.realmName, - version: "v2", - id: currentClientId, - }); + await getClientsEndpoint().byId(currentClientId).delete(); } }); it("should list clients", async () => { - const clients = await clientsV2Api.adminApiRealmNameClientsVersionGet({ - realmName: kcAdminClient.realmName, - version: "v2", - }); + const clients = await getClientsEndpoint().get(); expect(clients).to.be.ok; expect(clients).to.be.an("array"); - expect(clients.length).to.be.greaterThan(0); + expect(clients!.length).to.be.greaterThan(0); // Verify our test client is in the list - const testClient = clients.find( + const testClient = clients!.find( (c) => (c as OIDCClientRepresentation).clientId === currentClientId, ); expect(testClient).to.be.ok; }); it("should get a single client by clientId", async () => { - const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ - realmName: kcAdminClient.realmName, - version: "v2", - id: currentClientId, - }); + const client = await getClientsEndpoint().byId(currentClientId).get(); expect(client).to.be.ok; expect((client as OIDCClientRepresentation).clientId).to.equal( @@ -76,22 +67,13 @@ describe("Clients V2 API", () => { it("should update a client with PUT", async () => { const updatedDescription = "Updated via V2 API test"; - await clientsV2Api.adminApiRealmNameClientsVersionIdPut({ - realmName: kcAdminClient.realmName, - version: "v2", - id: currentClientId, - adminApiRealmNameClientsVersionGet200ResponseInner: { - clientId: currentClientId, - protocol: "openid-connect", - description: updatedDescription, - }, + await getClientsEndpoint().byId(currentClientId).put({ + clientId: currentClientId, + protocol: "openid-connect", + description: updatedDescription, }); - const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ - realmName: kcAdminClient.realmName, - version: "v2", - id: currentClientId, - }); + const client = await getClientsEndpoint().byId(currentClientId).get(); expect((client as OIDCClientRepresentation).description).to.equal( updatedDescription, @@ -101,26 +83,21 @@ describe("Clients V2 API", () => { it("should patch a client", async () => { const patchedDisplayName = "Patched Display Name"; - const patchedClient = - await clientsV2Api.adminApiRealmNameClientsVersionIdPatch({ - realmName: kcAdminClient.realmName, - version: "v2", - id: currentClientId, - requestBody: { - displayName: patchedDisplayName, - }, - }); + // Note: Kiota's patch expects an ArrayBuffer for merge-patch+json + const patchBody = JSON.stringify({ displayName: patchedDisplayName }); + const encoder = new TextEncoder(); + const patchBuffer = encoder.encode(patchBody).buffer; + + const patchedClient = await getClientsEndpoint() + .byId(currentClientId) + .patch(patchBuffer); expect((patchedClient as OIDCClientRepresentation).displayName).to.equal( patchedDisplayName, ); // Verify the change persisted - const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ - realmName: kcAdminClient.realmName, - version: "v2", - id: currentClientId, - }); + const client = await getClientsEndpoint().byId(currentClientId).get(); expect((client as OIDCClientRepresentation).displayName).to.equal( patchedDisplayName, @@ -131,39 +108,24 @@ describe("Clients V2 API", () => { const clientId = faker.internet.username(); // Create a new client using v2 API - await clientsV2Api.adminApiRealmNameClientsVersionPost({ - realmName: kcAdminClient.realmName, - version: "v2", - adminApiRealmNameClientsVersionGet200ResponseInner: { - clientId, - protocol: "openid-connect", - enabled: true, - description: "Test client for deletion", - }, + await getClientsEndpoint().post({ + clientId, + protocol: "openid-connect", + enabled: true, + description: "Test client for deletion", }); // Verify we can get it via v2 API - const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ - realmName: kcAdminClient.realmName, - version: "v2", - id: clientId, - }); + const client = await getClientsEndpoint().byId(clientId).get(); expect((client as OIDCClientRepresentation).clientId).to.equal(clientId); // Delete the client using v2 API - await clientsV2Api.adminApiRealmNameClientsVersionIdDelete({ - realmName: kcAdminClient.realmName, - version: "v2", - id: clientId, - }); + await getClientsEndpoint().byId(clientId).delete(); // Verify it's deleted by checking it's no longer in the list - const clients = await clientsV2Api.adminApiRealmNameClientsVersionGet({ - realmName: kcAdminClient.realmName, - version: "v2", - }); + const clients = await getClientsEndpoint().get(); - const deletedClient = clients.find( + const deletedClient = clients!.find( (c) => (c as OIDCClientRepresentation).clientId === clientId, ); expect(deletedClient).to.be.undefined; @@ -172,26 +134,18 @@ describe("Clients V2 API", () => { it("should create an OIDC client with full configuration", async () => { const clientId = `full-config-${faker.internet.username()}`; - await clientsV2Api.adminApiRealmNameClientsVersionPost({ - realmName: kcAdminClient.realmName, - version: "v2", - adminApiRealmNameClientsVersionGet200ResponseInner: { - clientId, - protocol: "openid-connect", - enabled: true, - displayName: "Full Config Test Client", - description: "A client with full OIDC configuration", - redirectUris: new Set(["http://localhost:3000/callback"]), - webOrigins: new Set(["http://localhost:3000"]), - }, + await getClientsEndpoint().post({ + clientId, + protocol: "openid-connect", + enabled: true, + displayName: "Full Config Test Client", + description: "A client with full OIDC configuration", + redirectUris: ["http://localhost:3000/callback"], + webOrigins: ["http://localhost:3000"], }); // Get via v2 API and verify - const client = await clientsV2Api.adminApiRealmNameClientsVersionIdGet({ - realmName: kcAdminClient.realmName, - version: "v2", - id: clientId, - }); + const client = await getClientsEndpoint().byId(clientId).get(); expect(client).to.be.ok; expect((client as OIDCClientRepresentation).displayName).to.equal( @@ -202,10 +156,6 @@ describe("Clients V2 API", () => { ); // Cleanup - await clientsV2Api.adminApiRealmNameClientsVersionIdDelete({ - realmName: kcAdminClient.realmName, - version: "v2", - id: clientId, - }); + await getClientsEndpoint().byId(clientId).delete(); }); }); diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index a95ff807d8e..3902b823af5 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -328,6 +328,24 @@ importers: libs/keycloak-admin-client: dependencies: + '@microsoft/kiota-abstractions': + specifier: ^1.0.0-preview.86 + version: 1.0.0-preview.99 + '@microsoft/kiota-http-fetchlibrary': + specifier: ^1.0.0-preview.80 + version: 1.0.0-preview.99 + '@microsoft/kiota-serialization-form': + specifier: ^1.0.0-preview.74 + version: 1.0.0-preview.99 + '@microsoft/kiota-serialization-json': + specifier: ^1.0.0-preview.86 + version: 1.0.0-preview.99 + '@microsoft/kiota-serialization-multipart': + specifier: ^1.0.0-preview.67 + version: 1.0.0-preview.99 + '@microsoft/kiota-serialization-text': + specifier: ^1.0.0-preview.79 + version: 1.0.0-preview.99 camelize-ts: specifier: ^3.0.0 version: 3.0.0 @@ -1046,6 +1064,24 @@ packages: resolution: {integrity: sha512-LhKytJM5ZJkbHQVfW/3o747rZUNs/MGg6j/wt/9qwwqEOfvUDTYXXxIBuMgrRXhJ528p41iyz4zjBVHZU74Odg==} hasBin: true + '@microsoft/kiota-abstractions@1.0.0-preview.99': + resolution: {integrity: sha512-6qrlwGCO3DbvpA7tszqk7JNQPFPDg8gv5NGMTziwdtHqaQrUALKusm3vdJGPVGrbNiZL6/lBaY5NSUvLtlhRCg==} + + '@microsoft/kiota-http-fetchlibrary@1.0.0-preview.99': + resolution: {integrity: sha512-lIruiYf8L7DPG0Fm92QN5YK4zx4sh76EtTONUAqBCxt5AtEJ7KQceBajaXQsjfcndUAp9LmDVdqR44o8sc9/mA==} + + '@microsoft/kiota-serialization-form@1.0.0-preview.99': + resolution: {integrity: sha512-BdXxqNfy+5yWTyxpBguqJYy6E9RRotrDClRcUO89okFcR5N6s6xenjsvGw++q9KWNi7qcHrqaG8xlRz1TLk81Q==} + + '@microsoft/kiota-serialization-json@1.0.0-preview.99': + resolution: {integrity: sha512-kDmMYmB7XkRprlLviEynRPDkE45JDxqiuUopfBRMvNK4qhzFiJTXGLihZ2FG6fdVu9LbV3/W0QxbeGP35kDK6A==} + + '@microsoft/kiota-serialization-multipart@1.0.0-preview.99': + resolution: {integrity: sha512-cfviCAVTlZPD+MUgdTIgsX/IfHND+gKQhem3vJeo7l5Qqz2rvvVFXOHwECI19umO9Un1QFKe1V5wg+M+aj90+g==} + + '@microsoft/kiota-serialization-text@1.0.0-preview.99': + resolution: {integrity: sha512-gcBffQRI1soHVAiS4YjfMr3SQ9fC8xVUGMfarTTszU4PjpxyFOknaWoFdt/0rvMeavI9jOtbyvDKAf77cZyYIQ==} + '@microsoft/tsdoc-config@0.17.1': resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} @@ -1414,6 +1450,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@std-uritemplate/std-uritemplate@2.0.8': + resolution: {integrity: sha512-8oj7jKksoTRxjdPkWKX9FyOKDS8ORBm+ZfTSHm0f3eYha8cI0O1d57gyduOIAX7Y2uUukNDNlxlvGb+FFkE7yQ==} + '@swc/core-darwin-arm64@1.15.8': resolution: {integrity: sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==} engines: {node: '>=10'} @@ -3937,6 +3976,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyduration@3.4.1: + resolution: {integrity: sha512-NemFoamVYn7TmtwZKZ3OiliM9fZkr6EWiTM+wKknco6POSy2gS689xx/pXip0JYp40HXpUw6k65CUYHWYUXdaA==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -5071,6 +5113,39 @@ snapshots: transitivePeerDependencies: - '@types/node' + '@microsoft/kiota-abstractions@1.0.0-preview.99': + dependencies: + '@opentelemetry/api': 1.9.0 + '@std-uritemplate/std-uritemplate': 2.0.8 + tinyduration: 3.4.1 + tslib: 2.8.1 + + '@microsoft/kiota-http-fetchlibrary@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + '@opentelemetry/api': 1.9.0 + tslib: 2.8.1 + + '@microsoft/kiota-serialization-form@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + tslib: 2.8.1 + + '@microsoft/kiota-serialization-json@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + tslib: 2.8.1 + + '@microsoft/kiota-serialization-multipart@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + tslib: 2.8.1 + + '@microsoft/kiota-serialization-text@1.0.0-preview.99': + dependencies: + '@microsoft/kiota-abstractions': 1.0.0-preview.99 + tslib: 2.8.1 + '@microsoft/tsdoc-config@0.17.1': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -5156,8 +5231,7 @@ snapshots: dependencies: '@octokit/openapi-types': 27.0.0 - '@opentelemetry/api@1.9.0': - optional: true + '@opentelemetry/api@1.9.0': {} '@patternfly/patternfly@4.224.5': {} @@ -5449,6 +5523,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@std-uritemplate/std-uritemplate@2.0.8': {} + '@swc/core-darwin-arm64@1.15.8': optional: true @@ -8314,6 +8390,8 @@ snapshots: tinybench@2.9.0: {} + tinyduration@3.4.1: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: From 44baef17a5469bfc71e7bc543806d308db6c3415 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Wed, 21 Jan 2026 15:23:00 +0100 Subject: [PATCH 4/5] fixed build Signed-off-by: Erik Jan de Wit --- .github/workflows/js-ci.yml | 2 +- js/libs/keycloak-admin-client/.gitignore | 1 + js/libs/keycloak-admin-client/generate.ts | 181 ++++++++++++++++++ js/libs/keycloak-admin-client/package.json | 7 +- .../keycloak-admin-client/tsconfig.test.json | 2 +- js/pnpm-lock.yaml | 38 +++- 6 files changed, 223 insertions(+), 8 deletions(-) create mode 100644 js/libs/keycloak-admin-client/generate.ts diff --git a/.github/workflows/js-ci.yml b/.github/workflows/js-ci.yml index 0300d61a892..2f0b0ec4f79 100644 --- a/.github/workflows/js-ci.yml +++ b/.github/workflows/js-ci.yml @@ -169,7 +169,7 @@ jobs: - name: Start Keycloak server run: | tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz - keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=transient-users,oid4vc-vci &> ~/server.log & + keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=transient-users,oid4vc-vci,client-admin-api:v2 &> ~/server.log & env: KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: admin diff --git a/js/libs/keycloak-admin-client/.gitignore b/js/libs/keycloak-admin-client/.gitignore index 4012caca7ba..2e6b0791d2c 100644 --- a/js/libs/keycloak-admin-client/.gitignore +++ b/js/libs/keycloak-admin-client/.gitignore @@ -1,2 +1,3 @@ lib/ generated/ +.kiota/ diff --git a/js/libs/keycloak-admin-client/generate.ts b/js/libs/keycloak-admin-client/generate.ts new file mode 100644 index 00000000000..eaa8b90bbc8 --- /dev/null +++ b/js/libs/keycloak-admin-client/generate.ts @@ -0,0 +1,181 @@ +// Use require for CommonJS compatibility with @microsoft/kiota +// The ESM build of @microsoft/kiota is broken (missing files), so we use require() +import { createRequire } from "module"; +import { fileURLToPath } from "url"; +import { dirname, resolve } from "path"; +import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"; + +const require = createRequire(import.meta.url); +const { + generateClient, + KiotaGenerationLanguage, + ConsumerOperation, +} = require("@microsoft/kiota"); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const projectRoot = resolve(__dirname, "."); + +// Configuration +const OPENAPI_URL = process.env.OPENAPI_URL || "http://localhost:9000/openapi"; +const OPENAPI_FILE = process.env.OPENAPI_FILE; // Optional: use a local file instead +const OUTPUT_PATH = resolve(projectRoot, "src/generated"); +const CLIENT_CLASS_NAME = "AdminClient"; +const CLIENT_NAMESPACE = "ApiSdk"; + +async function downloadOpenApiSpec(url: string): Promise { + console.log(`šŸ“„ Downloading OpenAPI spec from ${url}...`); + const response = await fetch(url); + if (!response.ok) { + throw new Error( + `Failed to download OpenAPI spec: ${response.status} ${response.statusText}`, + ); + } + const content = await response.text(); + + // Save to a temp file + const tempFile = resolve(projectRoot, ".openapi-temp.yaml"); + writeFileSync(tempFile, content); + console.log(`āœ… Downloaded and saved to ${tempFile}`); + return tempFile; +} + +async function main() { + console.log("šŸš€ Keycloak Admin Client v2 - Kiota Generator\n"); + + let openApiFilePath: string; + + if (OPENAPI_FILE) { + // Use local file + openApiFilePath = resolve(projectRoot, OPENAPI_FILE); + if (!existsSync(openApiFilePath)) { + console.error(`āŒ OpenAPI file not found: ${openApiFilePath}`); + process.exit(1); + } + console.log(`šŸ“„ Using local OpenAPI file: ${openApiFilePath}`); + } else { + // Download from URL + openApiFilePath = await downloadOpenApiSpec(OPENAPI_URL); + } + + // Ensure output directory exists + if (!existsSync(OUTPUT_PATH)) { + mkdirSync(OUTPUT_PATH, { recursive: true }); + } + + console.log(`\nšŸ“¦ Generating TypeScript client...`); + console.log(` Output: ${OUTPUT_PATH}`); + console.log(` Client class: ${CLIENT_CLASS_NAME}`); + console.log(` Namespace: ${CLIENT_NAMESPACE}\n`); + + try { + const result = await generateClient({ + openAPIFilePath: openApiFilePath, + clientClassName: CLIENT_CLASS_NAME, + clientNamespaceName: CLIENT_NAMESPACE, + language: KiotaGenerationLanguage.TypeScript, + outputPath: OUTPUT_PATH, + operation: ConsumerOperation.Generate, + workingDirectory: projectRoot, + cleanOutput: true, + }); + + if (result) { + console.log("\nāœ… Client generated successfully!"); + + if (result.logs && result.logs.length > 0) { + console.log("\nšŸ“‹ Generation logs:"); + for (const log of result.logs) { + const level = log.level === 1 ? "āš ļø" : log.level === 2 ? "āŒ" : "ā„¹ļø"; + console.log(` ${level} ${log.message}`); + } + } + } else { + console.log("\nāš ļø Generation completed but returned no result"); + } + } catch (error) { + console.error("\nāŒ Generation failed:", error); + process.exit(1); + } + + // Post-process: Fix empty if blocks in adminClient.ts that cause TS2774 errors + postProcessGeneratedCode(); + + console.log("\nšŸŽ‰ Done! You can now build the project with: npm run build"); +} + +/** + * Fix known issues in Kiota-generated code + */ +function postProcessGeneratedCode() { + const adminClientPath = resolve(OUTPUT_PATH, "adminClient.ts"); + + if (!existsSync(adminClientPath)) { + console.log("āš ļø adminClient.ts not found, skipping post-processing"); + return; + } + + console.log("\nšŸ”§ Post-processing generated code..."); + + let content = readFileSync(adminClientPath, "utf-8"); + let modified = false; + + // The Kiota npm package generates empty if-blocks for serializer registration + // We need to fill them in with the actual registration calls + + // Check if the if-blocks are empty (missing the registration calls) + if ( + content.includes( + "if (parseNodeFactoryRegistry.registerDefaultDeserializer) {\n }", + ) + ) { + // Add required imports for serializers/deserializers + const serializerImports = `// @ts-ignore +import { FormParseNodeFactory, FormSerializationWriterFactory } from '@microsoft/kiota-serialization-form'; +// @ts-ignore +import { JsonParseNodeFactory, JsonSerializationWriterFactory } from '@microsoft/kiota-serialization-json'; +// @ts-ignore +import { MultipartSerializationWriterFactory } from '@microsoft/kiota-serialization-multipart'; +// @ts-ignore +import { TextParseNodeFactory, TextSerializationWriterFactory } from '@microsoft/kiota-serialization-text'; +`; + + // Add imports after the existing imports + content = content.replace( + /import \{ apiClientProxifier,/, + serializerImports + "\nimport { apiClientProxifier,", + ); + + // Fill in the deserializer registration + content = content.replace( + /if \(parseNodeFactoryRegistry\.registerDefaultDeserializer\) \{\n {4}\}/, + `if (parseNodeFactoryRegistry.registerDefaultDeserializer) { + parseNodeFactoryRegistry.registerDefaultDeserializer(JsonParseNodeFactory, backingStoreFactory); + parseNodeFactoryRegistry.registerDefaultDeserializer(TextParseNodeFactory, backingStoreFactory); + parseNodeFactoryRegistry.registerDefaultDeserializer(FormParseNodeFactory, backingStoreFactory); + }`, + ); + + // Fill in the serializer registration + content = content.replace( + /if \(serializationWriterFactory\.registerDefaultSerializer\) \{\n {4}\}/, + `if (serializationWriterFactory.registerDefaultSerializer) { + serializationWriterFactory.registerDefaultSerializer(JsonSerializationWriterFactory); + serializationWriterFactory.registerDefaultSerializer(TextSerializationWriterFactory); + serializationWriterFactory.registerDefaultSerializer(FormSerializationWriterFactory); + serializationWriterFactory.registerDefaultSerializer(MultipartSerializationWriterFactory); + }`, + ); + + modified = true; + console.log(" āœ… Added serializer/deserializer registration code"); + } + + if (modified) { + writeFileSync(adminClientPath, content); + } else { + console.log(" ā„¹ļø No fixes needed"); + } +} + +void main(); diff --git a/js/libs/keycloak-admin-client/package.json b/js/libs/keycloak-admin-client/package.json index b0e3ff5c2a7..2972172a917 100644 --- a/js/libs/keycloak-admin-client/package.json +++ b/js/libs/keycloak-admin-client/package.json @@ -19,6 +19,7 @@ "lint": "wireit", "test": "wireit", "generate:openapi": "wireit", + "postinstall": "pnpm generate:openapi", "prepublishOnly": "pnpm build" }, "wireit": { @@ -36,7 +37,7 @@ ] }, "generate:openapi": { - "command": "kiota generate -l TypeScript -d api.yml -o src/generated -c AdminClient -n ApiSdk", + "command": "OPENAPI_FILE=api.yml tsx generate.ts", "files": [ "api.yml" ], @@ -63,6 +64,7 @@ }, "devDependencies": { "@faker-js/faker": "^10.2.0", + "@microsoft/kiota": "^1.29.0", "@types/chai": "^5.2.2", "@types/lodash-es": "^4.17.12", "@types/mocha": "^10.0.10", @@ -70,7 +72,8 @@ "chai": "^6.2.2", "lodash-es": "^4.17.23", "mocha": "^11.7.5", - "ts-node": "^10.9.2" + "ts-node": "^10.9.2", + "tsx": "^4.19.2" }, "author": { "name": "Red Hat, Inc.", diff --git a/js/libs/keycloak-admin-client/tsconfig.test.json b/js/libs/keycloak-admin-client/tsconfig.test.json index 1bd1e8a2460..dd0e52a653a 100644 --- a/js/libs/keycloak-admin-client/tsconfig.test.json +++ b/js/libs/keycloak-admin-client/tsconfig.test.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig", - "include": ["test"] + "include": ["src", "test"] } diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 3902b823af5..0a57bfd2c9d 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -356,6 +356,9 @@ importers: '@faker-js/faker': specifier: ^10.2.0 version: 10.2.0 + '@microsoft/kiota': + specifier: ^1.29.0 + version: 1.29.0 '@types/chai': specifier: ^5.2.2 version: 5.2.2 @@ -380,6 +383,9 @@ importers: ts-node: specifier: ^10.9.2 version: 10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3) + tsx: + specifier: ^4.19.2 + version: 4.21.0 libs/ui-shared: dependencies: @@ -1082,6 +1088,9 @@ packages: '@microsoft/kiota-serialization-text@1.0.0-preview.99': resolution: {integrity: sha512-gcBffQRI1soHVAiS4YjfMr3SQ9fC8xVUGMfarTTszU4PjpxyFOknaWoFdt/0rvMeavI9jOtbyvDKAf77cZyYIQ==} + '@microsoft/kiota@1.29.0': + resolution: {integrity: sha512-qqIlTz48OJ5ZMRoTA/uQA70B7ltS4lPSs9atG5PUn+dKZcgXny3LzQPe12B1LsKoBJYbwhaU3fD8/C1DsLW6Cw==} + '@microsoft/tsdoc-config@0.17.1': resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} @@ -1895,6 +1904,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -3412,6 +3425,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + original-fs@1.2.0: + resolution: {integrity: sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -4298,6 +4314,10 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + vscode-jsonrpc@8.2.1: + resolution: {integrity: sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==} + engines: {node: '>=14.0.0'} + vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} @@ -5146,6 +5166,13 @@ snapshots: '@microsoft/kiota-abstractions': 1.0.0-preview.99 tslib: 2.8.1 + '@microsoft/kiota@1.29.0': + dependencies: + adm-zip: 0.5.16 + original-fs: 1.2.0 + uuid: 13.0.0 + vscode-jsonrpc: 8.2.1 + '@microsoft/tsdoc-config@0.17.1': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -6030,6 +6057,8 @@ snapshots: acorn@8.15.0: {} + adm-zip@0.5.16: {} + agent-base@7.1.4: {} ajv-draft-04@1.0.0(ajv@8.13.0): @@ -6954,7 +6983,6 @@ snapshots: get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 - optional: true glob-parent@5.1.2: dependencies: @@ -7702,6 +7730,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + original-fs@1.2.0: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -8017,8 +8047,7 @@ snapshots: resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: - optional: true + resolve-pkg-maps@1.0.0: {} resolve@1.22.10: dependencies: @@ -8475,7 +8504,6 @@ snapshots: get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3 - optional: true type-check@0.4.0: dependencies: @@ -8728,6 +8756,8 @@ snapshots: void-elements@3.1.0: {} + vscode-jsonrpc@8.2.1: {} + vscode-uri@3.1.0: {} w3c-xmlserializer@5.0.0: From 7883c7e997392547a5ea36c4abff161db1935a7d Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Thu, 29 Jan 2026 17:16:23 +0100 Subject: [PATCH 5/5] better api Signed-off-by: Erik Jan de Wit --- js/libs/keycloak-admin-client/src/index.ts | 6 +- .../src/resources/clients.ts | 40 +++++++++++- .../src/resources/clientsV2.ts | 61 +++++-------------- .../test/clientsV2.spec.ts | 42 +++++-------- 4 files changed, 69 insertions(+), 80 deletions(-) diff --git a/js/libs/keycloak-admin-client/src/index.ts b/js/libs/keycloak-admin-client/src/index.ts index 7a039422919..90fed83b2b1 100644 --- a/js/libs/keycloak-admin-client/src/index.ts +++ b/js/libs/keycloak-admin-client/src/index.ts @@ -9,13 +9,9 @@ export type { NetworkErrorOptions } from "./utils/fetchWithError.js"; export type { default as OrganizationInvitationRepresentation } from "./defs/organizationInvitationRepresentation.js"; export { OrganizationInvitationStatus } from "./defs/organizationInvitationRepresentation.js"; -// V2 API types and classes (Kiota-generated) -export { createKiotaAdminClient } from "./resources/clientsV2.js"; +// V2 API types (Kiota-generated) export type { OIDCClientRepresentation, SAMLClientRepresentation, ClientRepresentationV2, } from "./resources/clientsV2.js"; - -// Re-export Kiota AdminClient type -export type { AdminClient } from "./generated/adminClient.js"; diff --git a/js/libs/keycloak-admin-client/src/resources/clients.ts b/js/libs/keycloak-admin-client/src/resources/clients.ts index 14f12bd6efa..2ccdc6449ba 100644 --- a/js/libs/keycloak-admin-client/src/resources/clients.ts +++ b/js/libs/keycloak-admin-client/src/resources/clients.ts @@ -57,7 +57,7 @@ export class Clients extends Resource<{ realm?: string }> { /** * Clients v2 API - New versioned API with OpenAPI-generated client. */ - public v2: ClientsV2; + #v2: ClientsV2; public find = this.makeRequest({ method: "GET", @@ -1063,7 +1063,43 @@ export class Clients extends Resource<{ realm?: string }> { }); // Initialize v2 API - this.v2 = new ClientsV2(client); + this.#v2 = new ClientsV2(client); + } + + /** + * Get the clients v2 API endpoint for the currently configured realm. + * Returns a fluent API builder for client operations using the new versioned API. + * + * @example + * ```typescript + * // List all clients + * const clients = await kcAdminClient.clients.v2().get(); + * + * // Get a single client by clientId + * const client = await kcAdminClient.clients.v2().byId("my-client").get(); + * + * // Create a new client + * await kcAdminClient.clients.v2().post({ + * clientId: "my-client", + * protocol: "openid-connect", + * enabled: true, + * }); + * + * // Update a client + * await kcAdminClient.clients.v2().byId("my-client").put({ + * clientId: "my-client", + * protocol: "openid-connect", + * description: "Updated description", + * }); + * + * // Delete a client + * await kcAdminClient.clients.v2().byId("my-client").delete(); + * ``` + * + * @returns A promise that resolves to the clients v2 endpoint + */ + v2() { + return this.#v2.api(); } /** diff --git a/js/libs/keycloak-admin-client/src/resources/clientsV2.ts b/js/libs/keycloak-admin-client/src/resources/clientsV2.ts index 1ad3e755703..d6d2fb54fb7 100644 --- a/js/libs/keycloak-admin-client/src/resources/clientsV2.ts +++ b/js/libs/keycloak-admin-client/src/resources/clientsV2.ts @@ -5,10 +5,8 @@ import { import { FetchRequestAdapter } from "@microsoft/kiota-http-fetchlibrary"; import type { KeycloakAdminClient } from "../client.js"; -import { - createAdminClient, - type AdminClient, -} from "../generated/adminClient.js"; +import { createAdminClient } from "../generated/adminClient.js"; +import type { WithVersionItemRequestBuilder } from "../generated/admin/api/item/clients/item/index.js"; // Re-export types for convenience export type { @@ -39,28 +37,23 @@ class KeycloakAuthProvider implements AuthenticationProvider { } /** - * Creates a Kiota AdminClient instance configured with the KeycloakAdminClient's - * base URL and access token. + * Creates a clients v2 endpoint configured with the KeycloakAdminClient's + * base URL, access token, and realm. */ -export async function createKiotaAdminClient( +function createClientsV2Endpoint( client: KeycloakAdminClient, -): Promise { +): WithVersionItemRequestBuilder { const authProvider = new KeycloakAuthProvider(() => client.getAccessToken()); const adapter = new FetchRequestAdapter(authProvider); adapter.baseUrl = client.baseUrl; - return createAdminClient(adapter); + const adminClient = createAdminClient(adapter); + return adminClient.admin.api.byRealmName(client.realmName).clients.byVersion("v2"); } /** * Clients v2 API resource. - * Provides access to the Kiota-generated AdminClient with fluent API. - * - * @example - * ```typescript - * const api = await client.clients.v2.api(); - * const clients = await api.admin.api.byRealmName("master").clients.byVersion("v2").get(); - * ``` + * Provides access to the new versioned clients API using the configured realm. */ export class ClientsV2 { #client: KeycloakAdminClient; @@ -70,38 +63,12 @@ export class ClientsV2 { } /** - * Get the AdminClient instance configured with the current access token. - * Call this method to get an API instance, then use its fluent methods. + * Get the clients v2 endpoint for the currently configured realm. + * Returns a fluent API builder for client operations. * - * @example - * ```typescript - * const api = await client.clients.v2.api(); - * - * // List all clients - * const clients = await api.admin.api.byRealmName("master").clients.byVersion("v2").get(); - * - * // Get a single client - * const client = await api.admin.api.byRealmName("master").clients.byVersion("v2").byId("my-client").get(); - * - * // Create a client - * await api.admin.api.byRealmName("master").clients.byVersion("v2").post({ - * clientId: "my-client", - * protocol: "openid-connect", - * enabled: true, - * }); - * - * // Update a client - * await api.admin.api.byRealmName("master").clients.byVersion("v2").byId("my-client").put({ - * clientId: "my-client", - * protocol: "openid-connect", - * description: "Updated description", - * }); - * - * // Delete a client - * await api.admin.api.byRealmName("master").clients.byVersion("v2").byId("my-client").delete(); - * ``` + * @returns The clients v2 endpoint */ - async api(): Promise { - return createKiotaAdminClient(this.#client); + api(): WithVersionItemRequestBuilder { + return createClientsV2Endpoint(this.#client); } } diff --git a/js/libs/keycloak-admin-client/test/clientsV2.spec.ts b/js/libs/keycloak-admin-client/test/clientsV2.spec.ts index ed895c3bbb0..a1103ce28b9 100644 --- a/js/libs/keycloak-admin-client/test/clientsV2.spec.ts +++ b/js/libs/keycloak-admin-client/test/clientsV2.spec.ts @@ -1,7 +1,6 @@ import { faker } from "@faker-js/faker"; import * as chai from "chai"; import { KeycloakAdminClient } from "../src/client.js"; -import type { AdminClient } from "../src/generated/adminClient.js"; import type { OIDCClientRepresentation } from "../src/generated/models/index.js"; import { credentials } from "./constants.js"; @@ -9,25 +8,15 @@ const expect = chai.expect; describe("Clients V2 API", () => { let kcAdminClient: KeycloakAdminClient; - let adminClient: AdminClient; let currentClientId: string; - // Helper to get the clients endpoint for the realm - const getClientsEndpoint = () => - adminClient.admin.api - .byRealmName(kcAdminClient.realmName) - .clients.byVersion("v2"); - before(async () => { kcAdminClient = new KeycloakAdminClient(); await kcAdminClient.auth(credentials); - // Get the v2 API instance (Kiota AdminClient) - adminClient = await kcAdminClient.clients.v2.api(); - // Create a client for testing using v2 API currentClientId = faker.internet.username(); - await getClientsEndpoint().post({ + await kcAdminClient.clients.v2().post({ clientId: currentClientId, protocol: "openid-connect", enabled: true, @@ -37,12 +26,12 @@ describe("Clients V2 API", () => { after(async () => { // Delete the test client if (currentClientId) { - await getClientsEndpoint().byId(currentClientId).delete(); + await kcAdminClient.clients.v2().byId(currentClientId).delete(); } }); it("should list clients", async () => { - const clients = await getClientsEndpoint().get(); + const clients = await kcAdminClient.clients.v2().get(); expect(clients).to.be.ok; expect(clients).to.be.an("array"); @@ -56,7 +45,7 @@ describe("Clients V2 API", () => { }); it("should get a single client by clientId", async () => { - const client = await getClientsEndpoint().byId(currentClientId).get(); + const client = await kcAdminClient.clients.v2().byId(currentClientId).get(); expect(client).to.be.ok; expect((client as OIDCClientRepresentation).clientId).to.equal( @@ -67,13 +56,13 @@ describe("Clients V2 API", () => { it("should update a client with PUT", async () => { const updatedDescription = "Updated via V2 API test"; - await getClientsEndpoint().byId(currentClientId).put({ + await kcAdminClient.clients.v2().byId(currentClientId).put({ clientId: currentClientId, protocol: "openid-connect", description: updatedDescription, }); - const client = await getClientsEndpoint().byId(currentClientId).get(); + const client = await kcAdminClient.clients.v2().byId(currentClientId).get(); expect((client as OIDCClientRepresentation).description).to.equal( updatedDescription, @@ -88,7 +77,8 @@ describe("Clients V2 API", () => { const encoder = new TextEncoder(); const patchBuffer = encoder.encode(patchBody).buffer; - const patchedClient = await getClientsEndpoint() + const patchedClient = await kcAdminClient.clients + .v2() .byId(currentClientId) .patch(patchBuffer); @@ -97,7 +87,7 @@ describe("Clients V2 API", () => { ); // Verify the change persisted - const client = await getClientsEndpoint().byId(currentClientId).get(); + const client = await kcAdminClient.clients.v2().byId(currentClientId).get(); expect((client as OIDCClientRepresentation).displayName).to.equal( patchedDisplayName, @@ -108,7 +98,7 @@ describe("Clients V2 API", () => { const clientId = faker.internet.username(); // Create a new client using v2 API - await getClientsEndpoint().post({ + await kcAdminClient.clients.v2().post({ clientId, protocol: "openid-connect", enabled: true, @@ -116,14 +106,14 @@ describe("Clients V2 API", () => { }); // Verify we can get it via v2 API - const client = await getClientsEndpoint().byId(clientId).get(); + const client = await kcAdminClient.clients.v2().byId(clientId).get(); expect((client as OIDCClientRepresentation).clientId).to.equal(clientId); // Delete the client using v2 API - await getClientsEndpoint().byId(clientId).delete(); + await kcAdminClient.clients.v2().byId(clientId).delete(); // Verify it's deleted by checking it's no longer in the list - const clients = await getClientsEndpoint().get(); + const clients = await kcAdminClient.clients.v2().get(); const deletedClient = clients!.find( (c) => (c as OIDCClientRepresentation).clientId === clientId, @@ -134,7 +124,7 @@ describe("Clients V2 API", () => { it("should create an OIDC client with full configuration", async () => { const clientId = `full-config-${faker.internet.username()}`; - await getClientsEndpoint().post({ + await kcAdminClient.clients.v2().post({ clientId, protocol: "openid-connect", enabled: true, @@ -145,7 +135,7 @@ describe("Clients V2 API", () => { }); // Get via v2 API and verify - const client = await getClientsEndpoint().byId(clientId).get(); + const client = await kcAdminClient.clients.v2().byId(clientId).get(); expect(client).to.be.ok; expect((client as OIDCClientRepresentation).displayName).to.equal( @@ -156,6 +146,6 @@ describe("Clients V2 API", () => { ); // Cleanup - await getClientsEndpoint().byId(clientId).delete(); + await kcAdminClient.clients.v2().byId(clientId).delete(); }); });