diff --git a/.eslintignore b/.eslintignore index 12594a837..929a2b9df 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,3 +12,4 @@ docs helm terraform localdev/mnt +localdev/keycloak-provision diff --git a/.prettierignore b/.prettierignore index 6b3c27614..3887cbd45 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ **/*.yaml **/*.yml .react-email +localdev/keycloak-provision diff --git a/localdev/docker-compose.yml b/localdev/docker-compose.yml index 25eaa9cf4..63d1f1bf4 100755 --- a/localdev/docker-compose.yml +++ b/localdev/docker-compose.yml @@ -38,15 +38,18 @@ services: dockerfile: ./keycloak-provision/Dockerfile environment: KEYCLOAK_URL: http://keycloak:8080 - KEYCLOAK_ADMIN: admin - KEYCLOAK_ADMIN_PASSWORD: password # pragma: allowlist secret - REALM_NAME: platform-services - CLIENT_ID: pltsvc - CLIENT_SECRET: testsecret # pragma: allowlist secret + MASTER_ADMIN: admin + MASTER_ADMIN_PASSWORD: password # pragma: allowlist secret + AUTH_REALM_NAME: platform-services + AUTH_CLIENT_ID: pltsvc + AUTH_CLIENT_SECRET: testsecret # pragma: allowlist secret GITOPS_CLIENT_ID: registry-gitops-ci GITOPS_CLIENT_SECRET: testsecret # pragma: allowlist secret ADMIN_CLIENT_ID: pltsvc-admin-cli ADMIN_CLIENT_SECRET: testsecret # pragma: allowlist secret + PUBLIC_CLOUD_REALM_NAME: public-cloud + PUBLIC_CLOUD_CLIENT_ID: roles + PUBLIC_CLOUD_CLIENT_SECRET: testsecret # pragma: allowlist secret depends_on: - keycloak diff --git a/localdev/keycloak-provision/.dockerignore b/localdev/keycloak-provision/.dockerignore index 3c3629e64..dd87e2d73 100644 --- a/localdev/keycloak-provision/.dockerignore +++ b/localdev/keycloak-provision/.dockerignore @@ -1 +1,2 @@ node_modules +build diff --git a/localdev/keycloak-provision/.gitignore b/localdev/keycloak-provision/.gitignore index 3c3629e64..dd87e2d73 100644 --- a/localdev/keycloak-provision/.gitignore +++ b/localdev/keycloak-provision/.gitignore @@ -1 +1,2 @@ node_modules +build diff --git a/localdev/keycloak-provision/Dockerfile b/localdev/keycloak-provision/Dockerfile index 6987b5e57..7060ea44a 100644 --- a/localdev/keycloak-provision/Dockerfile +++ b/localdev/keycloak-provision/Dockerfile @@ -1,13 +1,13 @@ -FROM node:22.2.0-alpine3.19 +FROM node:22.2.0-alpine3.19 as build -WORKDIR /app +WORKDIR /app/sandbox COPY ./keycloak-provision . -COPY ./m365proxy/mocks.json mocks.json +COPY ./m365proxy/mocks.json /app/m365proxy/mocks.json RUN npm install --ignore-scripts +RUN npm run build USER node - -CMD ["node", "setup.js"] +CMD ["node", "build/main.js"] diff --git a/localdev/keycloak-provision/package-lock.json b/localdev/keycloak-provision/package-lock.json index a1978b03f..66f8cfaeb 100644 --- a/localdev/keycloak-provision/package-lock.json +++ b/localdev/keycloak-provision/package-lock.json @@ -11,6 +11,26 @@ "dependencies": { "@keycloak/keycloak-admin-client": "^24.0.0", "wait-on": "^7.0.1" + }, + "devDependencies": { + "@types/node": "^20.14.1", + "@types/wait-on": "^5.3.4", + "rimraf": "^5.0.7", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.4.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" } }, "node_modules/@hapi/hoek": { @@ -26,6 +46,48 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@keycloak/keycloak-admin-client": { "version": "24.0.5", "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-24.0.5.tgz", @@ -40,6 +102,16 @@ "node": ">=18" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -58,6 +130,99 @@ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.1.tgz", + "integrity": "sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/wait-on": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.3.4.tgz", + "integrity": "sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -73,6 +238,21 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/camelize-ts": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelize-ts/-/camelize-ts-3.0.0.tgz", @@ -81,6 +261,24 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -92,6 +290,26 @@ "node": ">= 0.8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -100,6 +318,27 @@ "node": ">=0.4.0" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -119,6 +358,22 @@ } } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -132,6 +387,61 @@ "node": ">= 6" } }, + "node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.2.3.tgz", + "integrity": "sha512-htOzIMPbpLid/Gq9/zaz9SfExABxqRe1sSCdxntlO/aMD6u0issZQiY25n2GKQUtJ02j7z5sfptlAOMpWWOmvw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/joi": { "version": "17.11.0", "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", @@ -144,11 +454,38 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -168,6 +505,21 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -176,11 +528,63 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/rimraf": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", + "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -189,11 +593,225 @@ "tslib": "^2.1.0" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/url-join": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", @@ -210,6 +828,12 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/wait-on": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", @@ -227,6 +851,121 @@ "engines": { "node": ">=12.0.0" } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } } } diff --git a/localdev/keycloak-provision/package.json b/localdev/keycloak-provision/package.json index 45895acf3..5e7eaef0d 100644 --- a/localdev/keycloak-provision/package.json +++ b/localdev/keycloak-provision/package.json @@ -1,16 +1,24 @@ { "name": "keycloak-provision", "version": "0.0.0", - "type": "module", "description": "", - "main": "index.js", + "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "rimraf ./build && tsc", + "script": "_ () { ts-node -r tsconfig-paths/register src/$@; }; _" }, "author": "", "license": "ISC", "dependencies": { "@keycloak/keycloak-admin-client": "^24.0.0", "wait-on": "^7.0.1" + }, + "devDependencies": { + "@types/node": "^20.14.1", + "@types/wait-on": "^5.3.4", + "rimraf": "^5.0.7", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.4.5" } } diff --git a/localdev/keycloak-provision/setup.js b/localdev/keycloak-provision/setup.js deleted file mode 100644 index d97832b97..000000000 --- a/localdev/keycloak-provision/setup.js +++ /dev/null @@ -1,276 +0,0 @@ -import waitOn from 'wait-on'; -import KcAdminClient from '@keycloak/keycloak-admin-client'; -import { createRequire } from 'module'; -const require = createRequire(import.meta.url); -const mockFile = require('./mocks.json'); - -const keycloakUrl = process.env.KEYCLOAK_URL; -const realmName = process.env.REALM_NAME; -const clientId = process.env.CLIENT_ID; -const clientSecret = process.env.CLIENT_SECRET; -const gitOpsclientId = process.env.GITOPS_CLIENT_ID; -const gitOpsclientSecret = process.env.GITOPS_CLIENT_SECRET; -const adminclientId = process.env.ADMIN_CLIENT_ID; -const adminclientSecret = process.env.ADMIN_CLIENT_SECRET; -const clientScope = 'https://graph.microsoft.com/.default'; - -const proxyUsers = mockFile.mocks.find((mock) => mock.request.url === 'https://graph.microsoft.com/v1.0/users?$filter*') - .response.body.value; - -async function main() { - console.log('Starting Keycloak Provision...'); - - await waitOn({ - resources: [`${keycloakUrl}/health/ready`], - delay: 1000, - window: 5000, - }); - - // Create KC admin client - const kcAdminClient = new KcAdminClient({ - baseUrl: keycloakUrl, - realmName: 'master', - requestConfig: { - timeout: 10000, - }, - }); - - // Authenticate KC admin client - await kcAdminClient.auth({ - grantType: 'password', - clientId: 'admin-cli', - username: process.env.KEYCLOAK_ADMIN, - password: process.env.KEYCLOAK_ADMIN_PASSWORD, - }); - - const findRealm = () => kcAdminClient.realms.findOne({ realm: realmName }); - - const findClient = (cid = clientId) => - kcAdminClient.clients - .findOne({ realm: realmName, clientId: cid }) - .then((clients) => (clients?.length > 0 ? clients[0] : null)); - - // Create Realm if not exist - let realm = await findRealm(); - if (!realm) { - await kcAdminClient.realms.create({ id: realm, realm: realmName, displayName: realmName }); - } - - let scope = null; - - // Update Realm - realm = await findRealm(); - if (realm) { - await kcAdminClient.realms.update( - { realm: realmName }, - { - enabled: true, - }, - ); - - scope = await kcAdminClient.clientScopes.findOneByName({ realm: realmName, name: clientScope }); - if (!scope) { - await kcAdminClient.clientScopes.create({ - realm: realmName, - name: clientScope, - type: 'optional', - protocol: 'openid-connect', - attributes: { - 'consent.screen.text': '', - 'display.on.consent.screen': 'true', - 'include.in.token.scope': 'true', - 'gui.order': '', - }, - }); - - scope = await kcAdminClient.clientScopes.findOneByName({ realm: realmName, name: clientScope }); - await kcAdminClient.clientScopes.addDefaultOptionalClientScope({ realm: realmName, id: scope.id }); - } - - scope = await kcAdminClient.clientScopes.findOneByName({ realm: realmName, name: clientScope }); - } - - realm = await findRealm(); - - // Create Client if not exist - let client = await findClient(); - if (!client) { - await kcAdminClient.clients.create({ realm: realmName, clientId }); - } - - const clientRoles = {}; - const roleNamesExists = proxyUsers.map((v) => v.jobTitle).filter((v) => !!v); - - // Update Client - client = await findClient(); - if (client) { - await kcAdminClient.clients.update( - { realm: realmName, id: client.id }, - { - enabled: true, - publicClient: false, - serviceAccountsEnabled: true, - standardFlowEnabled: true, - implicitFlowEnabled: false, - directAccessGrantsEnabled: false, - redirectUris: ['*'], - secret: clientSecret, - }, - ); - - client = await findClient(); - kcAdminClient.clients.addDefaultClientScope({ - realm: realmName, - id: client.id, - clientScopeId: scope.id, - }); - - for (let x = 0; x < roleNamesExists.length; x++) { - const roleName = roleNamesExists[x]; - - const getRole = () => - kcAdminClient.clients.findRole({ - realm: realmName, - id: client.id, - roleName, - }); - - let role = await getRole(); - if (!role) { - await kcAdminClient.clients.createRole({ - realm: realmName, - id: client.id, - name: roleName, - }); - - role = await getRole(); - } - - clientRoles[roleName] = role; - } - } - - client = await findClient(); - - const allClientRoles = await kcAdminClient.clients.listRoles({ - realm: realmName, - id: client.id, - }); - - // Upsert GitOps client - (async () => { - let gitopsClient = await findClient(gitOpsclientId); - if (!gitopsClient) { - await kcAdminClient.clients.create({ realm: realmName, clientId: gitOpsclientId }); - } - - gitopsClient = await findClient(gitOpsclientId); - if (gitopsClient) { - await kcAdminClient.clients.update( - { realm: realmName, id: gitopsClient.id }, - { - enabled: true, - publicClient: false, - serviceAccountsEnabled: true, - standardFlowEnabled: false, - implicitFlowEnabled: false, - directAccessGrantsEnabled: false, - secret: gitOpsclientSecret, - }, - ); - } - })(); - - // Upsert Admin client - (async () => { - let adminClient = await findClient(adminclientId); - if (!adminClient) { - await kcAdminClient.clients.create({ realm: realmName, clientId: adminclientId }); - } - - adminClient = await findClient(adminclientId); - if (adminClient) { - await kcAdminClient.clients.update( - { realm: realmName, id: adminClient.id }, - { - enabled: true, - publicClient: false, - serviceAccountsEnabled: true, - standardFlowEnabled: false, - implicitFlowEnabled: false, - directAccessGrantsEnabled: false, - secret: adminclientSecret, - }, - ); - } - - const realmManagementClient = await findClient('realm-management'); - const realmAdminRole = await kcAdminClient.clients.findRole({ - realm: realmName, - id: realmManagementClient.id, - roleName: 'realm-admin', - }); - - const adminClientUser = await kcAdminClient.clients.getServiceAccountUser({ realm: realmName, id: adminClient.id }); - await kcAdminClient.users.addClientRoleMappings({ - realm: realmName, - id: adminClientUser.id, - clientUniqueId: realmManagementClient.id, - roles: [realmAdminRole], - }); - })(); - - // Create Users - await Promise.all( - proxyUsers.map(async ({ surname, givenName, mail, jobTitle }) => { - try { - const user = await kcAdminClient.users.create({ - enabled: true, - realm: realmName, - emailVerified: true, - username: mail, - email: mail, - firstName: givenName, - lastName: surname, - }); - - await kcAdminClient.users.resetPassword({ - realm: realmName, - id: user.id, - credential: { temporary: false, type: 'password', value: mail.toLowerCase() }, - }); - } catch {} - - const allUsers = await kcAdminClient.users.find({ realm: realmName }); - const currUser = allUsers.find((user) => user.username === mail.toLowerCase()); - - if (currUser) { - // Revoke all client roles from the user - await kcAdminClient.users.delClientRoleMappings({ - realm: realmName, - id: currUser.id, - clientUniqueId: client.id, - roles: allClientRoles, - }); - - // Assign a role based on their job title - if (jobTitle) { - await kcAdminClient.users.addClientRoleMappings({ - realm: realmName, - id: currUser.id, - clientUniqueId: client.id, - roles: [clientRoles[jobTitle]], - }); - } - } - }), - ); - - return { - realm, - client, - clientRoles, - }; -} - -main().then(console.log).catch(console.error); diff --git a/localdev/keycloak-provision/src/config.ts b/localdev/keycloak-provision/src/config.ts new file mode 100644 index 000000000..b0eb938f3 --- /dev/null +++ b/localdev/keycloak-provision/src/config.ts @@ -0,0 +1,13 @@ +export const KEYCLOAK_URL = process.env.KEYCLOAK_URL || ''; +export const MASTER_ADMIN = process.env.MASTER_ADMIN || ''; +export const MASTER_ADMIN_PASSWORD = process.env.MASTER_ADMIN_PASSWORD || ''; +export const AUTH_REALM_NAME = process.env.AUTH_REALM_NAME || ''; +export const AUTH_CLIENT_ID = process.env.AUTH_CLIENT_ID || ''; +export const AUTH_CLIENT_SECRET = process.env.AUTH_CLIENT_SECRET || ''; +export const GITOPS_CLIENT_ID = process.env.GITOPS_CLIENT_ID || ''; +export const GITOPS_CLIENT_SECRET = process.env.GITOPS_CLIENT_SECRET || ''; +export const ADMIN_CLIENT_ID = process.env.ADMIN_CLIENT_ID || ''; +export const ADMIN_CLIENT_SECRET = process.env.ADMIN_CLIENT_SECRET || ''; +export const PUBLIC_CLOUD_REALM_NAME = process.env.PUBLIC_CLOUD_REALM_NAME || ''; +export const PUBLIC_CLOUD_CLIENT_ID = process.env.PUBLIC_CLOUD_CLIENT_ID || ''; +export const PUBLIC_CLOUD_CLIENT_SECRET = process.env.PUBLIC_CLOUD_CLIENT_SECRET || ''; diff --git a/localdev/keycloak-provision/src/core.ts b/localdev/keycloak-provision/src/core.ts new file mode 100644 index 000000000..f281dee77 --- /dev/null +++ b/localdev/keycloak-provision/src/core.ts @@ -0,0 +1,276 @@ +import KcAdminClient from '@keycloak/keycloak-admin-client'; +import RealmRepresentation from '@keycloak/keycloak-admin-client/lib/defs/realmRepresentation'; +import ClientRepresentation from '@keycloak/keycloak-admin-client/lib/defs/clientRepresentation'; +import { RoleMappingPayload } from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation'; + +function castArray(value: string | string[]) { + if (Array.isArray(value)) { + return value; + } + + return [value]; +} + +function onlyUnique(value: string, index: number, array: string[]) { + return array.indexOf(value) === index; +} + +function uniq(values: string[]) { + return values.filter(onlyUnique); +} + +interface KcUser { + email: string; + username: string; + firstName: string; + lastName: string; + password: string; + roles: string[]; +} + +export class KcAdmin { + private _username: string; + + private _password: string; + + private _kcAdminClient: KcAdminClient; + + constructor({ + baseUrl, + realmName, + username, + password, + }: { + baseUrl: string; + realmName: string; + username: string; + password: string; + }) { + this._username = username; + this._password = password; + this._kcAdminClient = new KcAdminClient({ + baseUrl, + realmName, + }); + } + + get kcAdminClient() { + return this._kcAdminClient; + } + + async auth() { + await this._kcAdminClient.auth({ + grantType: 'password', + clientId: 'admin-cli', + username: this._username, + password: this._password, + }); + } + + async findRealm(realm: string) { + return this._kcAdminClient.realms.findOne({ realm }); + } + + async createRealm(realm: string) { + const _realm = await this.findRealm(realm); + if (_realm) return _realm; + + await this._kcAdminClient.realms.create({ realm, displayName: realm }); + return this.findRealm(realm); + } + + async upsertRealm(realm: string, payload: RealmRepresentation) { + await this.createRealm(realm); + await this._kcAdminClient.realms.update({ realm }, payload); + return this.findRealm(realm); + } + + async findClient(realm: string, clientId: string) { + const _clients = await this._kcAdminClient.clients.find({ realm, clientId }); + return _clients?.length > 0 ? _clients[0] : null; + } + + async createClient(realm: string, clientId: string) { + const _client = await this.findClient(realm, clientId); + if (_client) return _client; + + await this._kcAdminClient.clients.create({ realm, clientId }); + return this.findClient(realm, clientId); + } + + async upsertClient(realm: string, clientId: string, payload: ClientRepresentation) { + const _client = await this.createClient(realm, clientId); + + await this._kcAdminClient.clients.update({ realm, id: _client?.id as string }, payload); + return this.findClient(realm, clientId); + } + + async createPrivateClient(realm: string, clientId: string, clientSecret: string) { + const _client = await this.upsertClient(realm, clientId, { + enabled: true, + publicClient: false, + serviceAccountsEnabled: true, + standardFlowEnabled: true, + implicitFlowEnabled: false, + directAccessGrantsEnabled: false, + redirectUris: ['*'], + secret: clientSecret, + }); + + return _client; + } + + async createServiceAccount(realm: string, clientId: string, clientSecret: string) { + const _client = await this.upsertClient(realm, clientId, { + enabled: true, + publicClient: false, + serviceAccountsEnabled: true, + standardFlowEnabled: false, + implicitFlowEnabled: false, + directAccessGrantsEnabled: false, + secret: clientSecret, + }); + + return _client; + } + + async createRealmAdminServiceAccount(realm: string, clientId: string, clientSecret: string) { + const _client = await this.createServiceAccount(realm, clientId, clientSecret); + + const realmManagementClient = await this.findClient(realm, 'realm-management'); + const realmAdminRole = await this._kcAdminClient.clients.findRole({ + realm, + id: realmManagementClient?.id as string, + roleName: 'realm-admin', + }); + + const adminClientUser = await this._kcAdminClient.clients.getServiceAccountUser({ + realm, + id: _client?.id as string, + }); + + await this._kcAdminClient.users.addClientRoleMappings({ + realm, + id: adminClientUser.id as string, + clientUniqueId: realmManagementClient?.id as string, + roles: [realmAdminRole as RoleMappingPayload], + }); + } + + async createRealmClientScope(realm: string, clientScope: string) { + let scope = await this._kcAdminClient.clientScopes.findOneByName({ realm, name: clientScope }); + if (scope) return scope; + + const newscope = await this._kcAdminClient.clientScopes.create({ + realm, + name: clientScope, + protocol: 'openid-connect', + attributes: { + 'consent.screen.text': '', + 'display.on.consent.screen': 'true', + 'include.in.token.scope': 'true', + 'gui.order': '', + }, + }); + + await this._kcAdminClient.clientScopes.addDefaultOptionalClientScope({ + realm, + id: newscope.id, + }); + + scope = await this._kcAdminClient.clientScopes.findOneByName({ realm, name: clientScope }); + return scope; + } + + async findUserByEmail(realm: string, email: string) { + const emailUsers = await this._kcAdminClient.users.find({ realm, email, exact: true }); + const user = emailUsers.find((user) => user.username === email); + return user; + } + + async createUser(realm: string, user: KcUser) { + const { password, roles, ...rest } = user; + + // Catch an error if the user already exists + try { + const user = await this._kcAdminClient.users.create({ + ...rest, + enabled: true, + realm, + emailVerified: true, + }); + + await this._kcAdminClient.users.resetPassword({ + realm, + id: user.id, + credential: { temporary: false, type: 'password', value: password }, + }); + } catch (err) { + console.error('createUser:', user, err); + } + + const currUser = await this.findUserByEmail(realm, rest.email); + return currUser; + } + + async findClinetRole(realm: string, clientUniqueId: string, roleName: string) { + const _role = await this._kcAdminClient.clients.findRole({ + realm, + id: clientUniqueId, + roleName, + }); + + return _role; + } + + async createClientRole(realm: string, clientUniqueId: string, roleName: string) { + let role = await this.findClinetRole(realm, clientUniqueId, roleName); + if (role) return role; + + try { + await this._kcAdminClient.clients.createRole({ + realm, + id: clientUniqueId, + name: roleName, + }); + } catch (err) { + console.error('createClientRole:', roleName, err); + } + + role = await this.findClinetRole(realm, clientUniqueId, roleName); + return role; + } + + async upsertUsersWithClientRoles(realm: string, clientUniqueId: string, users: KcUser[]) { + const allClientRoles = await this._kcAdminClient.clients.listRoles({ + realm, + id: clientUniqueId, + }); + + await Promise.all( + users.map(async ({ email, username, firstName, lastName, password, roles = [] }) => { + email = email.toLowerCase(); + roles = uniq(castArray(roles)).filter(Boolean); + + const currUser = await this.createUser(realm, { email, username, firstName, lastName, password, roles }); + + // Revoke all client roles from the user + await this._kcAdminClient.users.delClientRoleMappings({ + realm, + id: currUser?.id as string, + clientUniqueId, + roles: allClientRoles as RoleMappingPayload[], + }); + + // Assign a roles + const _roles = await Promise.all(roles.map((role) => this.createClientRole(realm, clientUniqueId, role))); + await this._kcAdminClient.users.addClientRoleMappings({ + realm, + id: currUser?.id as string, + clientUniqueId, + roles: _roles as never as RoleMappingPayload[], + }); + }), + ); + } +} diff --git a/localdev/keycloak-provision/src/main.ts b/localdev/keycloak-provision/src/main.ts new file mode 100644 index 000000000..351ec4e12 --- /dev/null +++ b/localdev/keycloak-provision/src/main.ts @@ -0,0 +1,93 @@ +import waitOn from 'wait-on'; +import mockFile from '../../m365proxy/mocks.json' with { type: 'json' }; +import { KcAdmin } from './core.js'; +import { + KEYCLOAK_URL, + MASTER_ADMIN, + MASTER_ADMIN_PASSWORD, + AUTH_REALM_NAME, + AUTH_CLIENT_ID, + AUTH_CLIENT_SECRET, + GITOPS_CLIENT_ID, + GITOPS_CLIENT_SECRET, + ADMIN_CLIENT_ID, + ADMIN_CLIENT_SECRET, + PUBLIC_CLOUD_REALM_NAME, + PUBLIC_CLOUD_CLIENT_ID, + PUBLIC_CLOUD_CLIENT_SECRET, +} from './config.js'; + +interface MsUser { + id: string; + onPremisesSamAccountName: string; + userPrincipalName: string; + mail: string; + displayName: string; + givenName: string; + surname: string; + jobTitle: string; +} + +const clientScope = 'https://graph.microsoft.com/.default'; + +let proxyUsers: MsUser[] = []; +const usersMock = mockFile.mocks.find((mock) => mock.request.url === 'https://graph.microsoft.com/v1.0/users?$filter*'); +if (usersMock?.response.body?.value) { + proxyUsers = usersMock.response.body?.value; +} + +async function main() { + console.log('Starting Keycloak Provision...'); + + await waitOn({ + resources: [`${KEYCLOAK_URL}/health/ready`], + delay: 1000, + window: 5000, + }); + + const kc = new KcAdmin({ + baseUrl: KEYCLOAK_URL, + realmName: 'master', + username: MASTER_ADMIN, + password: MASTER_ADMIN_PASSWORD, + }); + + await kc.auth(); + + // Create auth realm & client + const authRealm = await kc.upsertRealm(AUTH_REALM_NAME, { enabled: true }); + const authClient = await kc.createPrivateClient(AUTH_REALM_NAME, AUTH_CLIENT_ID, AUTH_CLIENT_SECRET); + + const scope = await kc.createRealmClientScope(AUTH_REALM_NAME, clientScope); + + kc.kcAdminClient.clients.addDefaultClientScope({ + realm: AUTH_REALM_NAME, + id: authClient?.id as string, + clientScopeId: scope?.id as string, + }); + + // Upsert GitOps client + await kc.createServiceAccount(AUTH_REALM_NAME, GITOPS_CLIENT_ID, GITOPS_CLIENT_SECRET); + + // Upsert Admin client + await kc.createRealmAdminServiceAccount(AUTH_REALM_NAME, ADMIN_CLIENT_ID, ADMIN_CLIENT_SECRET); + + const authUsers = proxyUsers.map(({ surname, givenName, mail, jobTitle }) => ({ + username: mail, + email: mail, + firstName: givenName, + lastName: surname, + password: mail, + roles: [jobTitle], + })); + + // Create Auth Users with auth roles assigned + await kc.upsertUsersWithClientRoles(AUTH_REALM_NAME, authClient?.id as string, authUsers); + + return { + authRealm, + authClient, + }; +} + +main().then(console.log).catch(console.error); diff --git a/localdev/keycloak-provision/tsconfig.json b/localdev/keycloak-provision/tsconfig.json new file mode 100644 index 000000000..85795b801 --- /dev/null +++ b/localdev/keycloak-provision/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "rootDir": "src", + "baseUrl": "src", + "resolveJsonModule": true, + "allowJs": true, + "outDir": "build", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitAny": true, + "skipLibCheck": true + } +}